From e05f3ceefcd025269d303aef9d87b7b45a38fc6a Mon Sep 17 00:00:00 2001 From: water111 <48171810+water111@users.noreply.github.com> Date: Tue, 8 Dec 2020 21:41:36 -0500 Subject: [PATCH] Implement `gkernel`: Part 2 (#155) * update * small fixes * deactivate * simple kernel test --- decompiler/config/all-types.gc | 123 ++-- doc/changelog.md | 3 +- goal_src/build/all_files.gc | 19 +- goal_src/build/{dgos.txt => game_dgos.txt} | 11 - goal_src/build/kernel_dgos.txt | 10 + goal_src/goal-lib.gc | 13 +- goal_src/kernel/gkernel-h.gc | 25 +- goal_src/kernel/gkernel.gc | 638 +++++++++++++++--- goalc/compiler/Compiler.cpp | 17 + goalc/compiler/Compiler.h | 1 + goalc/compiler/compilation/Function.cpp | 2 +- goalc/listener/Listener.cpp | 10 + goalc/listener/Listener.h | 1 + goalc/regalloc/Allocator.cpp | 2 +- goalc/regalloc/allocate_common.h | 8 + test/CMakeLists.txt | 1 + .../source_templates/kernel/kernel-test.gc | 64 ++ test/goalc/test_goal_kernel.cpp | 89 +++ 18 files changed, 844 insertions(+), 193 deletions(-) rename goal_src/build/{dgos.txt => game_dgos.txt} (99%) create mode 100644 goal_src/build/kernel_dgos.txt create mode 100644 test/goalc/source_templates/kernel/kernel-test.gc create mode 100644 test/goalc/test_goal_kernel.cpp diff --git a/decompiler/config/all-types.gc b/decompiler/config/all-types.gc index f6a9e1e66..f1015fbc2 100644 --- a/decompiler/config/all-types.gc +++ b/decompiler/config/all-types.gc @@ -190,6 +190,7 @@ (declare-type stack-frame basic) (declare-type cpu-thread basic) (declare-type state basic) +(declare-type dead-pool basic) ;; gkernel-h (deftype thread (basic) @@ -246,8 +247,8 @@ (:methods (new ((allocation symbol) (type-to-make type) (name basic)) _type_ 0) - (activate ((obj _type_) (dest process-tree) (name basic) (stack-top pointer)) basic 9) - (deactivate ((obj _type_)) basic 10) + (activate ((obj _type_) (dest process-tree) (name basic) (stack-top pointer)) process-tree 9) + (deactivate ((obj _type_)) none 10) (dummy-method-11 () none 11) (run-logic? ((obj _type_)) symbol 12) (dummy-method () none 13) @@ -260,7 +261,7 @@ ;; gkernel (deftype process (process-tree) - ((pool basic :offset-assert #x20) + ((pool dead-pool :offset-assert #x20) (status basic :offset-assert #x24) (pid int32 :offset-assert #x28) (main-thread cpu-thread :offset-assert #x2c) @@ -280,10 +281,11 @@ (stack uint8 :dynamic :offset-assert #x70) ) + (:methods (new ((allocation symbol) (type-to-make type) (name basic) (stack-size int)) _type_ 0) - (activate ((obj process) (dest process-tree) (name basic) (stack-top pointer)) basic 9) - (deactivate ((obj process)) basic 10) + (activate ((obj _type_) (dest process-tree) (name basic) (stack-top pointer)) process-tree 9) + (deactivate ((obj process)) none 10) (dummy-method-11 () none 11) (run-logic? ((obj process)) symbol 12) (dummy-method () none 13) @@ -384,8 +386,12 @@ ) (deftype protect-frame (stack-frame) - ((exit function :offset-assert 12) + ((exit (function object) :offset-assert 12) ) + (:methods + (new ((allocation symbol) (type-to-make type) (func (function object))) protect-frame) + ) + :method-count-assert 9 :size-assert #x10 :flag-assert #x900000010 @@ -483,73 +489,55 @@ (define-extern kernel-dispatcher (function (function object))) (define-extern inspect-process-tree (function process-tree int int symbol process-tree)) +(define-extern throw-dispatch (function catch-frame object none)) +(define-extern run-function-in-process (function process function object object object object object object object)) +(define-extern throw (function symbol object int)) +(define-extern set-to-run (function cpu-thread function object object object object object object pointer)) +(define-extern set-to-run-bootstrap (function none)) +(define-extern entity-deactivate-handler (function process object none)) +(define-extern process-disconnect (function object none)) -;;(define-extern stack-frame object) ;; unknown type +(define-extern stack-frame type) (define-extern state type) -;;(define-extern dead-pool-heap-rec object) ;; unknown type -;;(define-extern dead-pool object) ;; unknown type -;;(define-extern catch-frame object) ;; unknown type -;;(define-extern thread object) ;; unknown type -;;(define-extern handle object) ;; unknown type -;;(define-extern cpu-thread object) ;; unknown type -;;(define-extern dead-pool-heap object) ;; unknown type +(define-extern dead-pool-heap-rec type) +(define-extern dead-pool type) +(define-extern catch-frame type) +(define-extern thread type) +(define-extern handle type) +(define-extern cpu-thread type) +(define-extern dead-pool-heap type) (define-extern kernel-context type) -;;(define-extern protect-frame object) ;; unknown type -;;(define-extern event-message-block object) ;; unknown type -;;(define-extern process-tree object) ;; unknown type -;;(define-extern *listener-process* object) ;; unknown type -;;(define-extern *entity-pool* object) ;; unknown type -;;(define-extern *default-pool* object) ;; unknown type -;;(define-extern malloc object) ;; unknown type -;;(define-extern ready object) ;; unknown type -;;(define-extern *camera-pool* object) ;; unknown type -(define-extern throw-dispatch function) -;;(define-extern game object) ;; unknown type -(define-extern run-function-in-process function) -(define-extern throw function) -;;(define-extern *nk-dead-pool* object) ;; unknown type +(define-extern protect-frame type) +(define-extern event-message-block type) +(define-extern process-tree type) +(define-extern *listener-process* process) +(define-extern *entity-pool* process-tree) +(define-extern *default-pool* process-tree) +(define-extern malloc (function symbol int pointer)) ;; from kernel-defs.gc +(define-extern *camera-pool* process-tree) + +(define-extern *nk-dead-pool* dead-pool-heap) (define-extern change-to-last-brother function) -;;(define-extern *pickup-dead-pool* object) ;; unknown type -;;(define-extern *camera-master-dead-pool* object) ;; unknown type -(define-extern set-to-run function) -;;(define-extern default-pool object) ;; unknown type -;;(define-extern listener object) ;; unknown type -;;(define-extern target-pool object) ;; unknown type -(define-extern set-to-run-bootstrap function) -;;(define-extern *camera-dead-pool* object) ;; unknown type -;;(define-extern process object) ;; unknown type -(define-extern previous-brother function) -;;(define-extern dead-state object) ;; unknown type -;;(define-extern debug object) ;; unknown type -;;(define-extern *16k-dead-pool* object) ;; unknown type -(define-extern entity-deactivate-handler function) -;;(define-extern *target-pool* object) ;; unknown type -;;(define-extern entity-pool object) ;; unknown type -;;(define-extern main object) ;; unknown type -(define-extern change-brother function) -;;(define-extern *active-pool* object) ;; unknown type -;;(define-extern display-pool object) ;; unknown type -;;(define-extern *target-dead-pool* object) ;; unknown type -;;(define-extern *4k-dead-pool* object) ;; unknown type -;;(define-extern *default-dead-pool* object) ;; unknown type -;;(define-extern *8k-dead-pool* object) ;; unknown type -;;(define-extern *display-pool* object) ;; unknown type -;;(define-extern active-pool object) ;; unknown type -;;(define-extern *dead-pool-list* object) ;; unknown type -;;(define-extern camera-pool object) ;; unknown type -;;(define-extern suspended object) ;; unknown type -;;(define-extern dgo-load object) ;; unknown type -;;(define-extern initialize-dead object) ;; unknown type -;;(define-extern #f object) ;; unknown type -;;(define-extern waiting-to-run object) ;; unknown type +(define-extern *pickup-dead-pool* dead-pool) +(define-extern *camera-master-dead-pool* dead-pool) +(define-extern *camera-dead-pool* dead-pool) +(define-extern process type) +(define-extern dead-state state) +(define-extern *16k-dead-pool* dead-pool) +(define-extern *target-pool* process-tree) +(define-extern *active-pool* process-tree) +(define-extern *target-dead-pool* dead-pool) +(define-extern *4k-dead-pool* dead-pool) +(define-extern *default-dead-pool* dead-pool) +(define-extern *8k-dead-pool* dead-pool) +(define-extern *display-pool* process-tree) + +(define-extern #f symbol) ;;(define-extern *stdcon0* object) ;; unknown type -;;(define-extern initialize-go object) ;; unknown type -;;(define-extern trans object) ;; unknown type -;;(define-extern code object) ;; unknown type ;;(define-extern *listener-function* object) ;; unknown type ;;(define-extern *stdcon1* object) ;; unknown type ;;(define-extern initialize object) ;; unknown type -(define-extern process-disconnect function) + ;;(define-extern *debug-draw-pauseable* object) ;; unknown type ;;(define-extern _empty_ object) ;; unknown type ;;(define-extern running object) ;; unknown type @@ -557,6 +545,11 @@ ;;(define-extern post object) ;; unknown type ;;(define-extern dead object) ;; unknown type +;; todo +(define-extern previous-brother function) +(define-extern change-brother function) +;;(define-extern *dead-pool-list* object) ;; unknown type + ;; pskernel (deftype lowmemmap (structure) ((irq-info-stack uint32 :offset-assert 0) diff --git a/doc/changelog.md b/doc/changelog.md index b481a8572..943213c44 100644 --- a/doc/changelog.md +++ b/doc/changelog.md @@ -79,4 +79,5 @@ - Fixed a bug where early returns out of methods would not change the return type of the method. - Fixed a bug where the return instruction was still emitted and the overridden return type of `asm-func` was ignored for methods - Rearranged function stack frames so spilled register variable slots come after stack structures. -- Added `stack` allocated and constructed basic/structure types. \ No newline at end of file +- Added `stack` allocated and constructed basic/structure types. +- Fixed a bug where functions with exactly 8 parameters created a compiler error. \ No newline at end of file diff --git a/goal_src/build/all_files.gc b/goal_src/build/all_files.gc index 438da9a6b..078a166e8 100644 --- a/goal_src/build/all_files.gc +++ b/goal_src/build/all_files.gc @@ -1,13 +1,16 @@ +(defglobalconstant all-kernel-goal-files + ("goal_src/kernel/gcommon.gc" + "goal_src/kernel/gstring-h.gc" + "goal_src/kernel/gkernel-h.gc" + "goal_src/kernel/gkernel.gc" + "goal_src/kernel/pskernel.gc" + "goal_src/kernel/gstring.gc" + "goal_src/kernel/dgo-h.gc" + "goal_src/kernel/gstate.gc") + ) + (defglobalconstant all-goal-files ( - "goal_src/kernel/gcommon.gc" - "goal_src/kernel/gstring-h.gc" - "goal_src/kernel/gkernel-h.gc" - "goal_src/kernel/gkernel.gc" - "goal_src/kernel/pskernel.gc" - "goal_src/kernel/gstring.gc" - "goal_src/kernel/dgo-h.gc" - "goal_src/kernel/gstate.gc" "goal_src/engine/util/types-h.gc" "goal_src/engine/ps2/vu1-macros.gc" "goal_src/engine/math/math.gc" diff --git a/goal_src/build/dgos.txt b/goal_src/build/game_dgos.txt similarity index 99% rename from goal_src/build/dgos.txt rename to goal_src/build/game_dgos.txt index 14bee9078..40d4cb339 100644 --- a/goal_src/build/dgos.txt +++ b/goal_src/build/game_dgos.txt @@ -1002,17 +1002,6 @@ ("launcherdoor.o" "launcherdoor") ) -("KERNEL.CGO" - ("gcommon.o" "gcommon") - ("gstring-h.o" "gstring-h") - ("gkernel-h.o" "gkernel-h") - ("gkernel.o" "gkernel") - ("pskernel.o" "pskernel") - ("gstring.o" "gstring") - ("dgo-h.o" "dgo-h") - ("gstate.o" "gstate") - ) - ("L1.CGO" ("rigid-body-h.o" "rigid-body-h") ("water-anim.o" "water-anim") diff --git a/goal_src/build/kernel_dgos.txt b/goal_src/build/kernel_dgos.txt new file mode 100644 index 000000000..4df9a0e14 --- /dev/null +++ b/goal_src/build/kernel_dgos.txt @@ -0,0 +1,10 @@ +("KERNEL.CGO" + ("gcommon.o" "gcommon") + ("gstring-h.o" "gstring-h") + ("gkernel-h.o" "gkernel-h") + ("gkernel.o" "gkernel") + ("pskernel.o" "pskernel") + ("gstring.o" "gstring") + ("dgo-h.o" "dgo-h") + ("gstate.o" "gstate") + ) \ No newline at end of file diff --git a/goal_src/goal-lib.gc b/goal_src/goal-lib.gc index d7cd14e29..b3e5b1cc1 100644 --- a/goal_src/goal-lib.gc +++ b/goal_src/goal-lib.gc @@ -32,10 +32,19 @@ `(asm-file ,file :color :write) ) + +(defmacro build-kernel () + `(begin + ,@(apply make-build-command all-kernel-goal-files) + (build-dgos "goal_src/build/kernel_dgos.txt") + ) + ) + (defmacro build-game () `(begin + (build-kernel) ,@(apply make-build-command all-goal-files) - (build-dgos "goal_src/build/dgos.txt") + (build-dgos "goal_src/build/game_dgos.txt") ) ) @@ -62,7 +71,7 @@ `(:exit) ) -(defmacro db () +(defmacro dbc () `(begin (set-config! print-ir #t) (set-config! print-regalloc #t) diff --git a/goal_src/kernel/gkernel-h.gc b/goal_src/kernel/gkernel-h.gc index 73e263d5b..b6df25f9e 100644 --- a/goal_src/kernel/gkernel-h.gc +++ b/goal_src/kernel/gkernel-h.gc @@ -147,6 +147,7 @@ (declare-type stack-frame basic) (declare-type state basic) (declare-type cpu-thread basic) +(declare-type dead-pool basic) ; DANGER - this type is created in kscheme.cpp. It has room for 12 methods and size 0x28 bytes. (deftype thread (basic) @@ -207,8 +208,8 @@ (:methods (new ((allocation symbol) (type-to-make type) (name basic)) _type_ 0) - (activate ((obj _type_) (dest process-tree) (name basic) (stack-top pointer)) basic 9) - (deactivate ((obj _type_)) basic 10) + (activate ((obj _type_) (dest process-tree) (name basic) (stack-top pointer)) process-tree 9) + (deactivate ((obj _type_)) none 10) (dummy-method-11 () none 11) (run-logic? ((obj _type_)) symbol 12) (dummy-method () none 13) @@ -222,7 +223,7 @@ ;; A GOAL process. A GOAL process contains memory and a suspendable main-thread. (deftype process (process-tree) - ((pool basic :offset-assert #x20) + ((pool dead-pool :offset-assert #x20) (status basic :offset-assert #x24) (pid int32 :offset-assert #x28) (main-thread cpu-thread :offset-assert #x2c) @@ -244,8 +245,8 @@ (:methods (new ((allocation symbol) (type-to-make type) (name basic) (stack-size int)) _type_ 0) - (activate ((obj process) (dest process-tree) (name basic) (stack-top pointer)) basic 9) - (deactivate ((obj process)) basic 10) + (activate ((obj _type_) (dest process-tree) (name basic) (stack-top pointer)) process-tree 9) + (deactivate ((obj process)) none 10) (dummy-method-11 () none 11) (run-logic? ((obj process)) symbol 12) (dummy-method () none 13) @@ -361,8 +362,11 @@ ;; A protect frame is a frame which has a cleanup function called on exit. (deftype protect-frame (stack-frame) - ((exit function :offset-assert 12)) ;; function to call to clean up + ((exit (function object) :offset-assert 12)) ;; function to call to clean up + (:methods + (new ((allocation symbol) (type-to-make type) (func (function object))) protect-frame) + ) :size-assert 16 :method-count-assert 9 :flag-assert #x900000010 @@ -483,4 +487,13 @@ (defmacro process-mask-set! (mask enum-value) `(set! ,mask (logior ,mask (process-mask ,enum-value))) + ) + +(defmacro suspend () + `(rlet ((pp :reg r13)) + (.push pp) + (set! pp (-> (the process pp) top-thread)) + ((-> (the cpu-thread pp) suspend-hook) (the cpu-thread 0)) + (.pop pp) + ) ) \ No newline at end of file diff --git a/goal_src/kernel/gkernel.gc b/goal_src/kernel/gkernel.gc index 57409bd54..1ef791a22 100644 --- a/goal_src/kernel/gkernel.gc +++ b/goal_src/kernel/gkernel.gc @@ -44,6 +44,8 @@ ;; Objects on a dynamic process heap may be relocated. ;; They should provide their own relocate method to do any fixups ;; for any references. + +;; Note - the actual relocation method of process is in relocate.gc. (defmethod relocate object ((this object) (offset int)) this ) @@ -93,7 +95,10 @@ ) ;; the main stack for running GOAL code! +;; all user code (that I know of) runs using *dram-stack* (define *dram-stack* (new 'global 'array 'uint8 DPROCESS_STACK_SIZE)) +;; note - this name is a bit confusing. The kernel-dram-stack is not the stack that the kernel runs in. +;; I think it refers to the fact that it's _not_ the scratchpad stack (which wasn't used anyway) (defconstant *kernel-dram-stack* (&+ *dram-stack* DPROCESS_STACK_SIZE)) ;; I don't think this stack is used, but I'm not sure. @@ -114,14 +119,19 @@ ; A "temporary thread" cannot suspend and resume, but a "main thread" can. ; The currently executing thread of a process is the "top-thread". -; Some GOAL threads also have the ability to "back up" their stack, while others are "temporary". -; The main thread of a process can "back up" it's stack, and all others are temporary. +; Threads that suspend do so by saving their saved registers and their stack. +; All threads run on a single large stack and have small "backup" stacks that are much smaller than the main stack. +; as a result, suspending can fail if you are using more stack than the size of your backup stack. +; This "backup stack" can be different sizes for different threads and makes the thread type dynamic. +; The main thread is stored on the process heap, as they need the same lifetime as the process. +; The temporary threads are stored on the stack. There can be only one temporary thread at a time. ; All threads are actually cpu-threads. It's not clear why there are two separate types. ; Perhaps the thread was the public interface and cpu-thread is internal to the kernel? (defmethod delete thread ((obj thread)) - "Clean up a thread. This assumes it's the top-thread of the process and restores the previous top thread." + "Clean up a temporary thread after it is done being used. + This assumes it's the top-thread of the process and restores the previous top thread." (when (eq? obj (-> obj process main-thread)) ;; We have attempted to delete the main thread, which is bad. (break) @@ -139,7 +149,7 @@ (defmethod stack-size-set! thread ((this thread) (stack-size int)) "Set the backup stack size of a thread. This should only be done on the main-thread. - This should be done immediately after allocating the main-thread" + This should be done immediately after allocating the main-thread." (let ((proc (-> this process))) (cond @@ -167,7 +177,8 @@ ) (defmethod new cpu-thread ((allocation symbol) (type-to-make type) (parent-process process) (name symbol) (stack-size int) (stack-top pointer)) - "Create a new CPU thread. Will allocate the main thread if none exists, otherwise a temp thread. + "Create a new CPU thread. If there is no main thread, it will allocate the main thread on the process. + If there is already a main thread, it will allocate a temporary thread on the given stack. Sets the thread as the top-thread of the process This is a special new method which ignores the allocation symbol. The stack-top is for the execution stack. @@ -178,7 +189,7 @@ ((-> parent-process top-thread) ;; we're allocating a temporary thread, the main thread already exists. ;; we can stash the cpu-thread structure at the bottom of the stack. - ;; we assume the smaller PROCESS_STACK_SIZE + ;; we use the smaller PROCESS_STACK_SIZE, which is only half the size of the real stack. (the cpu-thread (&+ stack-top (- PROCESS_STACK_SIZE) *gtype-basic-offset* @@ -488,7 +499,7 @@ ;; Context Suspend And Resume - Kernel ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; the following functions are used for going from the kernel to temporary threads and back. +;; the following functions are used for going from the kernel to threads and back. ;; saved registers: rbx, rbp, r10, r11, r12 ;; DANGER - THE KERNEL DOES NOT SAVE ITS FLOATING POINT CONTEXT!!!! @@ -499,8 +510,10 @@ (defun return-from-thread () "Context switch to the saved kernel context now. - This is intended to be jumped to with the ret instruction - at the end of a normal function, so this should preserve rax." + This is intended to be jumped to with the ret instruction (return trampoline) + at the end of a normal function, so this should preserve rax. + To make sure this happens, all ops should be asm ops and we should have no + GOAL expressions." (declare (asm-func none) ;(print-asm) ) @@ -512,19 +525,20 @@ (s3 :reg r11 :type uint) (s4 :reg r12 :type uint) ) - ;; get the kernel stack pointer as a GOAL pointer + ;; get the kernel stack pointer as a GOAL pointer (won't use a temp reg) (.load-sym :sext #f sp *kernel-sp*) ;; convert it back to a real pointer (.add sp off) ;; restore saved registers... - ;; without coloring system because this is "cheating". + ;; without coloring system because this is "cheating" and modifying saved registers without backing up. (.pop :color #f s4) (.pop :color #f s3) (.pop :color #f s2) (.pop :color #f s1) (.pop :color #f s0) ;; return to the kernel function that called the user code + ;; rax should still contain the return value. (.ret) ) ) @@ -545,7 +559,7 @@ (s4 :reg r12 :type uint) ) - ;; first call the deactivate method. + ;; first call the deactivate method. (todo - is the stack properly aligned for this?) (deactivate pp) ;; get the kernel stack pointer as a GOAL pointer (.load-sym :sext #f sp *kernel-sp*) @@ -567,7 +581,9 @@ (defun reset-and-call ((obj thread) (func function)) "Make the given thread the top thread, reset the stack, and call the function. Sets up a return trampoline so when the function returns it will return to the - kernel context." + kernel context. Will NOT deactivate on return, so this is intended for temporary threads. + NOTE: this should only be done from the kernel, running on the + kernel's stack." (declare (asm-func object) ;(print-asm) ) @@ -606,10 +622,9 @@ (.add sp off) ;; push the return trampoline to the stack for the user code to return to - ;(.push 0) ;; for 16-byte stack alignment. (set! temp (the uint return-from-thread)) (.add temp off) - (.push temp) + (.push temp) ;; stack now 16 + 8 aligned ;; and call the function! (.add func off) (.jr func) @@ -621,7 +636,7 @@ ;; Context Suspend And Resume - Thread ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; these are for resuming and suspending a thread. +;; these are for resuming and suspending main threads. (defmethod thread-suspend cpu-thread ((unused cpu-thread)) "Suspend the thread and return to the kernel." @@ -643,7 +658,7 @@ (s3 :reg r11 :type uint) (s4 :reg r12 :type uint)) - ;; get the return address pushed by "call" + ;; get the return address pushed by "call" in the suspend. (.pop temp) ;; convert to a GOAL address (.sub temp off) @@ -711,6 +726,9 @@ (defmethod thread-resume cpu-thread ((thread-to-resume cpu-thread)) + "Resume a suspended thread. Call this from the kernel only. + This is also used to start a thread initialized with set-to-run. + As a result of MIPS/x86 differences, there is a hack for this." (declare (asm-func none) ;(print-asm) ) @@ -723,7 +741,10 @@ (s1 :reg rbp :type uint) (s2 :reg r10 :type uint) (s3 :reg r11 :type uint) - (s4 :reg r12 :type uint)) + (s4 :reg r12 :type uint) + (a4 :reg r8 :type uint) + (a5 :reg r9 :type uint) + ) ;; save the current kernel regs (.push :color #f s0) @@ -740,10 +761,10 @@ ;; temp, stash thread in process-pointer (set! obj thread-to-resume) - ;; set stack pointer for the thread. + ;; set stack pointer for the thread. leave it as a GOAL pointer for now.. (set! sp (the uint (-> obj sp))) - ;; restore the stack. + ;; restore the stack (sp is a GOAL pointer) (let ((cur (the (pointer uint64) (-> obj stack-top))) (restore (&+ (the (pointer uint64) (-> obj stack)) (-> obj stack-size))) ) @@ -754,7 +775,7 @@ ) ) - ;; offset sp after we're done looking at it. + ;; offset sp after we're done using it as a GOAL pointer. (.add sp off) ;; setup process @@ -772,12 +793,30 @@ (.mov :color #f s3 temp) (set! temp (-> obj rreg 4)) (.mov :color #f s4 temp) - ;; todo restore fpr. + ;; hack for set-to-run-bootstrap. The set-to-run-bootstrap in MIPS + ;; expects to receive 7 values from the cpu thread's rregs. + ;; usually rreg holds saved registers, but on the first resume after + ;; a set-to-run, they hold arguments, and set-to-run-bootstrap copies them. + + ;; We only have 5 saved regs, so we need to cheat and directly pass + ;; two values in other registers + ;; so we load the a4/a5 argument registers with rreg 5 and rreg 6 + ;; In the case where we are doing a normal resume, the + ;; compiler should assume that these registers are overwritten anyway. + (set! temp (-> obj rreg 5)) + (.mov a4 temp) + (set! temp (-> obj rreg 6)) + (.mov a5 temp) + + ;; get the resume address (set! temp (the uint (-> obj pc))) (.add temp off) + + ;; setup the process (set! obj (the cpu-thread (-> obj process))) + ;; resume! (.jr temp) ) (none) @@ -1576,7 +1615,7 @@ (execute-process-tree *active-pool* (lambda ((obj process)) - (format 0 "Call to dispatcher lambda!~%") + ;(format 0 "Call to dispatcher lambda!~%") (let ((context *kernel-context*)) (cond @@ -1707,6 +1746,14 @@ ) ) +(defmacro set-u64-from-u128! (dst src) + `(set! ,dst (-> (the (pointer uint64) (& ,src)))) + ) + +(defmacro the-super-u64-fucntion (func) + `(the-as (function uint uint uint uint uint uint object) ,func) + ) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Stack Frame Stuff (TODO) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -1715,94 +1762,181 @@ ;; The catch frames are managed per process (you can't throw to a frame outside your process) ;; But otherwise it is fully dynamic. -; (defmethod new catch-frame ((allocation symbol) (type-to-make type) (name symbol) (func function) (param-block (pointer uint64))) -; "Run func in a catch frame with the given 8 parameters. -; The return value is the result of the function. -; The allocation must be an address" -; (declare (asm-func object) -; (print-asm) -; ) +(defmethod new catch-frame ((allocation symbol) (type-to-make type) (name symbol) (func function) (param-block (pointer uint64))) + "Run func in a catch frame with the given 8 parameters. + The return value is the result of the function. + The allocation must be an address. + Unlike the original, this only works on the first six parameters, but I think this doesn't matter." + (declare (asm-func object) + ;(print-asm) + (allow-saved-regs) ;; very dangerous! + ) -; (rlet ((pp :reg r13 :type process) -; (temp :reg rax :type uint) -; (off :reg r15 :type uint) -; (sp :reg rsp :type uint) -; (s0 :reg rbx :type uint) -; (s1 :reg rbp :type uint) -; (s2 :reg r10 :type uint) -; (s3 :reg r11 :type uint) -; (s4 :reg r12 :type uint) -; (a0 :reg rdi :type uint) -; (a1 :reg rsi :type uint) -; (a2 :reg rdx :type uint) -; (a3 :reg rcx :type uint) -; (a4 :reg r8 :type uint) -; (a5 :reg r9 :type uint) -; (a6 :reg r10 :type uint) -; (a7 :reg r11 :type uint) -; ) + (rlet ((pp :reg r13 :type process) + (temp :reg rax :type uint) + (off :reg r15 :type uint) + (sp :reg rsp :type uint) + (s0 :reg rbx :type uint) + (s1 :reg rbp :type uint) + (s2 :reg r10 :type (pointer uint64)) + (s3 :reg r11 :type uint) + (s4 :reg r12 :type uint) + ) -; ;; we treat the allocation as an address. -; (let ((obj (the catch-frame (&+ allocation *gtype-basic-offset*)))) -; ;; setup catch frame -; (set! (-> obj type) type-to-make) -; (set! (-> obj name) name) -; ;; get the return address -; (.pop temp) -; (.push temp) -; ;; make it a GOAL address so it fits in 32 bitys -; (.sub temp off) -; ;; store it -; (set! (-> obj ra) (the int temp)) + ;; we treat the allocation as an address. + (let ((obj (the catch-frame (&+ allocation *gtype-basic-offset*)))) + ;; setup catch frame + (set! (-> obj type) type-to-make) + (set! (-> obj name) name) + ;; get the return address (the compiler won't touch the stack because we're an asm-func) + (.pop temp) + (.push temp) + ;; make it a GOAL address so it fits in 32 bitys + (.sub temp off) + ;; store it + (set! (-> obj ra) (the int temp)) -; ;; todo, do we need a stack offset here? -; (set! temp sp) -; (.sub temp off) -; (set! (-> obj sp) (the int sp)) + ;; todo, do we need a stack offset here? + ;; remember the stack pointer + (set! temp sp) + (.sub temp off) + (set! (-> obj sp) (the int sp)) -; ;; back up registers -; (.mov :color #f temp s0) -; (set-u128-as-u64! (-> obj rreg 0) temp) -; (.mov :color #f temp s1) -; (set-u128-as-u64! (-> obj rreg 1) temp) -; (.mov :color #f temp s2) -; (set-u128-as-u64! (-> obj rreg 2) temp) -; (.mov :color #f temp s3) -; (set-u128-as-u64! (-> obj rreg 3) temp) -; (.mov :color #f temp s4) -; (set-u128-as-u64! (-> obj rreg 4) temp) -; ;; todo save fprs + ;; back up registers we care about + (.mov :color #f temp s0) + (set-u128-as-u64! (-> obj rreg 0) temp) + (.mov :color #f temp s1) + (set-u128-as-u64! (-> obj rreg 1) temp) + (.mov :color #f temp s2) + (set-u128-as-u64! (-> obj rreg 2) temp) + (.mov :color #f temp s3) + (set-u128-as-u64! (-> obj rreg 3) temp) + (.mov :color #f temp s4) + (set-u128-as-u64! (-> obj rreg 4) temp) + ;; todo save fprs -; ;; push this stack frame -; (set! (-> obj next) (-> pp stack-frame-top)) -; (set! (-> pp stack-frame-top) obj) + ;; push this stack frame + (set! (-> obj next) (-> pp stack-frame-top)) + (set! (-> pp stack-frame-top) obj) -; (let ((ret ((the-super-u64-fucntion func) -; ;(-> param-block 0) -; (-> param-block) -; ;(-> param-block 1) -; (-> (&+ param-block 8)) -; (-> (&+ param-block 16)) -; (-> (&+ param-block 24)) -; ;(-> (&+ param-block 32)) -; ; (-> param-block 5) -; )) -; ) + ;; help coloring, it isn't smart enough to realize it's "safe" to use these registers. + (.push :color #f s3) + (.push :color #f s2) + (set! s3 (the uint func)) + (set! s2 param-block) + + ;; todo - are we aligned correctly here? + (let ((ret ((the-super-u64-fucntion s3) + (-> s2 0) + (-> s2 1) + (-> s2 2) + (-> s2 3) + (-> s2 4) + (-> s2 5) + )) + ) -; ; (set! (-> pp stack-frame-top) (-> pp stack-frame-top next)) -; ) -; ) -; ) -; ;; the code in here may throw at any point in time, without properly resetting saved registers. -; ;; so we should save them ourself. + (.pop :color #f s2) + (.pop :color #f s3) + (set! (-> pp stack-frame-top) (-> pp stack-frame-top next)) + (.ret) + (the object ret) + ) + ) + ) + ) + +(defun throw-dispatch ((obj catch-frame) value) + "Throw the given value to the catch frame. + Only can throw a 64-bit value. The original could throw 128 bits." + (declare (asm-func none) + ;(print-asm) + ) -; (the object #f) -; ) + (rlet ((pp :reg r13 :type process) + (temp :reg rax :type uint) + (off :reg r15 :type uint) + (sp :reg rsp :type uint) + (s0 :reg rbx :type uint) + (s1 :reg rbp :type uint) + (s2 :reg r10 :type (pointer uint64)) + (s3 :reg r11 :type uint) + (s4 :reg r12 :type uint) + ) + + ;; pop everything we threw past + (set! (-> pp stack-frame-top) (-> obj next)) + + ;; restore regs we care about. + (set-u64-from-u128! temp (-> obj rreg 0)) + (.mov :color #f s0 temp) + (set-u64-from-u128! temp (-> obj rreg 1)) + (.mov :color #f s1 temp) + (set-u64-from-u128! temp (-> obj rreg 2)) + (.mov :color #f s2 temp) + (set-u64-from-u128! temp (-> obj rreg 3)) + (.mov :color #f s3 temp) + (set-u64-from-u128! temp (-> obj rreg 4)) + (.mov :color #f s4 temp) + ;; todo fpr + + ;; set stack pointer + (set! sp (the uint (-> obj sp))) + (.add sp off) + + ;; overwrite our return address + (.pop temp) + (set! temp (the uint (-> obj ra))) + (.add temp off) + (.push temp) + + ;; load the return register + (.mov temp value) + (.ret) + ) + ) + +(defun throw ((name symbol) value) + "Dynamic throw." + (rlet ((pp :reg r13 :type process)) + (let ((cur (-> pp stack-frame-top))) + (while cur + (when (and (eq? (-> cur name) name) (eq? (-> cur type) catch-frame)) + ;; match! + (throw-dispatch (the catch-frame cur) value) + ) + + (if (eq? (-> cur type) protect-frame) + ;; call the cleanup function + ((-> (the protect-frame cur) exit)) + ) + ) + ) + ) + (format 0 "ERROR: throw could not find tag ~A~%" name) + (break) + ) + +(defmethod new protect-frame ((allocation symbol) (type-to-make type) (func (function object))) + (let ((obj (the protect-frame (&+ allocation *gtype-basic-offset*)))) + (set! (-> obj type) type-to-make) + (set! (-> obj name) 'protect-frame) + (set! (-> obj exit) func) + + (rlet ((pp :reg r13 :type process)) + (set! (-> obj next) (-> pp stack-frame-top)) + (set! (-> pp stack-frame-top) obj) + ) + obj + ) + ) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Tree Stuff ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; todo previous-brother + (defun change-parent ((obj process-tree) (new-parent process-tree)) "Make obj a child of new-parent" (let ((parent (-> obj parent))) @@ -1834,6 +1968,298 @@ ) ) +;; todo change-brother +;; todo change-to-last-brother + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Process Control +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defmethod activate process ((obj process) (dest process-tree) (name basic) (stack-top pointer)) + "Activate a process! Put it on the given active tree and set up the main thread." + (set! (-> obj mask) (logand (-> dest mask) PROCESS_CLEAR_MASK)) + (set! (-> obj status) 'ready) + (let ((pid (-> *kernel-context* next-pid))) + (set! (-> obj pid) pid) + (set! (-> *kernel-context* next-pid) (+ 1 pid))) + (set! (-> obj top-thread) #f) + (set! (-> obj main-thread) #f) + (set! (-> obj name) name) + (set! (-> obj heap-base) (set! (-> obj heap-cur) (&+ (-> obj stack) (-> obj type heap-base)))) + (set! (-> obj stack-frame-top) #f) + (mem-set32! (-> obj stack) (the int (/ (-> obj type heap-base) 4)) 0) + + (set! (-> obj trans-hook) #f) + (set! (-> obj post-hook) #f) + (set! (-> obj event-hook) #f) + (set! (-> obj state) #f) + (set! (-> obj next-state) #f) + (if (process-mask? (-> dest mask) process-tree) + (set! (-> obj entity) #f) + (set! (-> obj entity) (-> (the process dest) entity)) + ) + + (set! (-> obj connection-list next1) #f) + (set! (-> obj connection-list prev1) #f) + + ;; todo global -> process + (let ((thread (new 'global 'cpu-thread obj 'code PROCESS_STACK_SAVE_SIZE stack-top))) + (set! (-> obj main-thread) thread) + ) + (change-parent obj dest) + ) + +(defun run-function-in-process ((obj process) (func function) a0 a1 a2 a3 a4 a5) + "Switch to the given process and run the function. This is used to initialize a process. + The function will run until it attempts to change state. At the first attempt to change state, + this function will return. The idea is that you use this when you want to initialize a process NOW. + This will then return the value of the function you called!" + + (rlet ((pp :reg r13 :type process)) + + (let ((param-array (new 'stack 'array 'uint64 6))) + ;; copy params to the stack. + (set! (-> param-array 0) (the uint64 a0)) + (set! (-> param-array 1) (the uint64 a1)) + (set! (-> param-array 2) (the uint64 a2)) + (set! (-> param-array 3) (the uint64 a3)) + (set! (-> param-array 4) (the uint64 a4)) + (set! (-> param-array 5) (the uint64 a5)) + + (let* ((old-pp pp) + (func-val (begin + ;; set the process + (set! pp obj) + ;; set us as initializing + (set! (-> pp status) 'initialize) + ;; run! + (the object (new 'stack 'catch-frame 'initialize func param-array)) + ))) + ;; the function returned, either through a throw or through actually returning. + ;; the status will give us a clue of what happened. + (cond + ((= (-> pp status) 'initialize) + ;; we returned and didn't change status. + (set! (-> pp status) 'initialize-dead) + ;; this means we died, and we should be deactivated. + (deactivate pp) + ) + ((= (-> pp status) 'initalize-go) + ;; we returned with a (suspend) or (go) ? not sure + ;; either way, we're ready for next time! + (set! (-> pp status) 'waiting-to-run) + (when (eq? (-> pp pool type) dead-pool-heap) + ;; we can shrink the heap now. + (shrink-heap (the dead-pool-heap (-> pp pool)) pp) + ) + ) + (else + (format 0 "GOT UNKNOWN INIT: ~A~%" (-> pp status)) + ) + ) + ;; restore the old pp + (set! pp old-pp) + func-val + ) + ) + ) + ) + +(defun set-to-run-bootstrap () + "This function is a clever hack. + To reset a thread to running a new function, we stash the arguments as saved registers. + These are then restored by thread-resume on the next run of the kernel. + This stub remaps these saved registers to argument registers. + It also creates a return trampoline to return-from-thread-dead" + (declare (asm-func none) + ;(print-asm) + ) + + (rlet ((s0 :reg rbx :type uint) + (s1 :reg rbp :type uint) + (s2 :reg r10 :type uint) + (s3 :reg r11 :type uint) + (s4 :reg r12 :type uint) + (a0 :reg rdi :type uint) ; ok + (a1 :reg rsi :type uint) ; ok + (a2 :reg rdx :type uint) ; ok + (a3 :reg rcx :type uint) ; ok + (off :reg r15 :type uint) + (temp :reg rax) + ) + + + (.mov temp return-from-thread-dead) + (.add temp off) + (.push temp) + + ;; stack is 16 + 8 aligned now + + (.mov :color #f a0 s1) + (.mov :color #f a1 s2) + (.mov :color #f a2 s3) + (.mov :color #f a3 s4) + + (.add :color #f s0 off) + (.jr :color #f s0) + + ) + + ) + + +(defun set-to-run ((thread cpu-thread) (func function) a0 a1 a2 a3 a4 a5) + "Set the given thread to call the given function with the given arguments next time it resumes. + Only for main threads. + Once the function returns, the process deactivates." + (let ((proc (-> thread process))) + (set! (-> proc status) 'waiting-to-run) + + ;; we store arguments and the function to call in saved registers + (set! (-> thread rreg 0) (the uint func)) + (set! (-> thread rreg 1) (the uint a0)) + (set! (-> thread rreg 2) (the uint a1)) + (set! (-> thread rreg 3) (the uint a2)) + (set! (-> thread rreg 4) (the uint a3)) + (set! (-> thread rreg 5) (the uint a4)) + (set! (-> thread rreg 6) (the uint a5)) + + ;; and have the thread first call set-to-run-bootstrap, which will properly call + ;; the function with the arguments and install a return trampoline for + ;; deactivating and returning to the kernel on return. + (set! (-> thread pc) (the pointer set-to-run-bootstrap)) + ;; reset sp. + (set! (-> thread sp) (-> thread stack-top)) + ) + ) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Process Deactivation +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defmethod deactivate process-tree ((obj process-tree)) + ;; todo + (format 0 "CALL TO DEACTIVATE~%") + (none) + ) + +;; todo defstate +(define dead-state + (new 'static 'state + :name #f + :next #f + :exit #f + :code #f + :trans #f + :post #f + :enter #f + :event #f)) + +(set! (-> dead-state code) nothing) + + +;; hack +(define-extern entity-deactivate-handler (function process object none)) +(define-extern process-disconnect (function object none)) + +(defmethod deactivate process ((obj process)) + "Deactivate a process. This returns the process to the dead pool + it came from. You can use this on your own process to kill yourself + and immediately return to the kernel. + You can also use this during initialization to kill yourself and return + to the process that initialzed you. + All protects/states will be cleaned up, with pp set correctly for the process. + But you might not have the stack of your main thread, so don't reference stack + vars from within your exit handlers." + + ;; don't do anything if we already died. + (unless (eq? (-> obj status) 'dead) + (set! (-> obj next-state) dead-state) + + ;; call entity handler + (when (-> obj entity) + (entity-deactivate-handler obj (-> obj entity)) + ) + + ;; clean up stack frames the process is in. + ;; first, set pp so the cleanup code thinks its running in the right process. + (rlet ((pp :reg r13 :type process)) + (let ((old-pp pp)) + (set! pp obj) + (let ((cur (-> pp stack-frame-top))) + (while cur + (when (or + (= (-> cur type) protect-frame) + (= (-> cur type) state) + ) + ;; we're a state or protect-frame, we can exit. + ((-> (the protect-frame cur) exit)) + ) + (set! cur (-> cur next)) + ) + ) + (set! pp old-pp) + ) + ) + + ;; hack - if this isn't defined yet, don't try it. + (if (!= 0 (the uint process-disconnect)) + (process-disconnect obj) + ) + + ;; kill our child and their brothers + (let ((bro (-> obj child))) + (while bro + (let ((temp (-> (-> bro) brother))) + (deactivate (-> bro)) + (set! bro temp) + ) + ) + ) + + ;; return ourself to the pool + (return-process (-> obj pool) obj) + (set! (-> obj state) #f) + (set! (-> obj next-state) #f) + (set! (-> obj entity) #f) + (set! (-> obj pid) 0) + + ;; deal with getting out of here. + (cond + ;; first case - we deactivated the running process + ;; (note, we don't check against pp because run-function-in-process + ;; will change pp for running initializations.) + ((eq? obj (-> *kernel-context* current-process)) + ;; go straight to dead. + (set! (-> obj status) 'dead) + ;; and return (with no deactivate) + (let ((temp (the uint return-from-thread))) + (rlet ((off :reg r15 :type uint)) + (+! temp off) + (.push temp) + (.ret) + ) + ) + ) + ;; second case - we deactivated while initializing. + ((eq? (-> obj status) 'initialize) + ;; added this + + ; (if (!= pp obj) + ; (format 0 "ERROR: deactivated a non-current initializing process!") + ; (break) + ; ) + (set! (-> obj status) 'dead) + (throw 'initalize #f) + ) + ) + (set! (-> obj status) 'dead) + ) + (none) + ) + + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Process Globals ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -1869,9 +2295,25 @@ ;; todo dead pool list + +;; main active pool (define *active-pool* (new 'global 'process-tree 'active-pool)) + +;; other active pools (change-parent (define *display-pool* (new 'global 'process-tree 'display-pool)) *active-pool*) + (change-parent (define *camera-pool* (new 'global 'process-tree 'camera-pool)) *active-pool*) +(set! (-> *camera-pool* mask) (process-mask pause menu progress camera process-tree)) + +(change-parent (define *target-pool* (new 'global 'process-tree 'target-pool)) *active-pool*) +(set! (-> *target-pool* mask) (process-mask pause menu progress process-tree)) + +(change-parent (define *entity-pool* (new 'global 'process-tree 'entity-pool)) *active-pool*) +(set! (-> *entity-pool* mask) (process-mask pause menu progress entity process-tree)) + +(change-parent (define *default-pool* (new 'global 'process-tree 'default-pool)) *active-pool*) +(set! (-> *default-pool* mask) (process-mask pause menu progress process-tree)) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Temp Hacks diff --git a/goalc/compiler/Compiler.cpp b/goalc/compiler/Compiler.cpp index 3d08109a6..929a9586e 100644 --- a/goalc/compiler/Compiler.cpp +++ b/goalc/compiler/Compiler.cpp @@ -204,6 +204,23 @@ bool Compiler::codegen_and_disassemble_object_file(FileEnv* env, return ok; } +void Compiler::compile_and_send_from_string(const std::string& source_code) { + if (!connect_to_target()) { + throw std::runtime_error( + "Compiler failed to connect to target for compile_and_send_from_string."); + } + + auto code = m_goos.reader.read_from_string(source_code); + auto compiled = compile_object_file("test-code", code, true); + assert(!compiled->is_empty()); + color_object_file(compiled); + auto data = codegen_object_file(compiled); + m_listener.send_code(data); + if (!m_listener.most_recent_send_was_acked()) { + print_compiler_warning("Runtime is not responding after sending test code. Did it crash?\n"); + } +} + std::vector Compiler::run_test_from_file(const std::string& source_code) { try { if (!connect_to_target()) { diff --git a/goalc/compiler/Compiler.h b/goalc/compiler/Compiler.h index 9b9803084..a48aaca42 100644 --- a/goalc/compiler/Compiler.h +++ b/goalc/compiler/Compiler.h @@ -35,6 +35,7 @@ class Compiler { std::vector run_test_from_string(const std::string& src, const std::string& obj_name = "*listener*"); std::vector run_test_no_load(const std::string& source_code); + void compile_and_send_from_string(const std::string& source_code); void run_front_end_on_string(const std::string& src); void shutdown_target(); void enable_throw_on_redefines() { m_throw_on_define_extern_redefinition = true; } diff --git a/goalc/compiler/compilation/Function.cpp b/goalc/compiler/compilation/Function.cpp index 26e68397c..92ba5891f 100644 --- a/goalc/compiler/compilation/Function.cpp +++ b/goalc/compiler/compilation/Function.cpp @@ -141,7 +141,7 @@ Val* Compiler::compile_lambda(const goos::Object& form, const goos::Object& rest new_func_env->set_segment(segment); // set up arguments - if (lambda.params.size() >= 8) { + if (lambda.params.size() > 8) { throw_compiler_error(form, "Cannot generate an x86-64 function for a lambda with {} parameters. " "The current limit is 8.", diff --git a/goalc/listener/Listener.cpp b/goalc/listener/Listener.cpp index a398ee3f4..483616d4f 100644 --- a/goalc/listener/Listener.cpp +++ b/goalc/listener/Listener.cpp @@ -304,6 +304,16 @@ std::vector Listener::stop_recording_messages() { return result; } +/*! + * Get the number of messages recorded so far. + */ +int Listener::get_received_message_count() { + rcv_mtx.lock(); + auto result = message_record.size(); + rcv_mtx.unlock(); + return result; +} + /*! * Send a "CODE" message for the target to execute as the Listener Function. * Returns once the target acks the code. diff --git a/goalc/listener/Listener.h b/goalc/listener/Listener.h index 0ff187490..f8fe829ad 100644 --- a/goalc/listener/Listener.h +++ b/goalc/listener/Listener.h @@ -30,6 +30,7 @@ class Listener { const std::string& ip = "127.0.0.1", int port = DECI2_PORT); void record_messages(ListenerMessageKind kind); + int get_received_message_count(); std::vector stop_recording_messages(); bool is_connected() const; void send_reset(bool shutdown); diff --git a/goalc/regalloc/Allocator.cpp b/goalc/regalloc/Allocator.cpp index aaf02c874..34f6c8b81 100644 --- a/goalc/regalloc/Allocator.cpp +++ b/goalc/regalloc/Allocator.cpp @@ -895,4 +895,4 @@ bool run_allocator(RegAllocCache* cache, const AllocationInput& in, int debug_tr } } return true; -} \ No newline at end of file +} diff --git a/goalc/regalloc/allocate_common.h b/goalc/regalloc/allocate_common.h index 9f5499dc3..653ab3951 100644 --- a/goalc/regalloc/allocate_common.h +++ b/goalc/regalloc/allocate_common.h @@ -221,6 +221,14 @@ struct LiveInfo { return assignment.at(id - min); } + int size() const { return 1 + max - min; } + + bool overlaps(const LiveInfo& other) const { + auto overlap_min = std::max(min, other.min); + auto overlap_max = std::min(max, other.max); + return overlap_min <= overlap_max; + } + std::string print_assignment(); }; #endif // JAK_ALLOCATE_COMMON_H diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 4f84573c1..ca7a4b50d 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -16,6 +16,7 @@ add_executable(goalc-test test_common_util.cpp test_pretty_print.cpp test_zydis.cpp + goalc/test_goal_kernel.cpp ${GOALC_TEST_FRAMEWORK_SOURCES} ${GOALC_TEST_CASES}) diff --git a/test/goalc/source_templates/kernel/kernel-test.gc b/test/goalc/source_templates/kernel/kernel-test.gc new file mode 100644 index 000000000..2ee6697c9 --- /dev/null +++ b/test/goalc/source_templates/kernel/kernel-test.gc @@ -0,0 +1,64 @@ +(defun deactivate-myself () + "Deactivate the current process. A workaround because rlet isn't working well." + (rlet ((pp :reg r13 :type process)) + (deactivate pp) + ) + ) + +(defun target-function ((a0 uint) (a1 uint) (a2 uint) (a3 uint) (a4 uint) (a5 uint)) + (format #t "TARGET FUNCTION ~D ~D ~D~%" a0 a1 a2) + (format #t "~D ~D ~D~%" a3 a4 a5) + + (let ((stack-arr (new 'stack 'array 'uint8 12))) + (format #t "Stack Alignemnt ~D/16~%" (logand 15 (the uint stack-arr))) + ) + + (dotimes (i 10) + (format #t "proc1: ~D~%" i) + (when (> i 4) + (format #t "DEACTIVATE PROC 1~%") + (deactivate-myself) + ) + (suspend) + ) + ) + +(define-extern recurse (function int (pointer int32) int)) +(defun recurse ((i int) (ptr (pointer int32))) + (if (> i 0) + (recurse (- i 1) ptr) + (suspend) + ) + (set! (-> ptr) (+ (-> ptr) 1)) + 1 + ) + +(defun target-function-2 () + (let ((stack-var (new 'stack 'array 'int32 1))) + (set! (-> stack-var) 0) + (countdown (i 10) + (format #t "proc2: ~D~%" (-> stack-var)) + (recurse 5 stack-var) + ) + ) + + ) + +(defun kernel-test () + (define test-process (get-process *nk-dead-pool* process 1024)) + + (activate test-process *active-pool* 'test-proc *kernel-dram-stack*) + + + (set-to-run (-> test-process main-thread) + target-function + 1 2 3 4 5 6 + ) + + (define test-process-2 (get-process *nk-dead-pool* process 1024)) + (activate test-process-2 *active-pool* 'test-2 *kernel-dram-stack*) + (set-to-run (-> test-process-2 main-thread) + target-function-2 + 0 0 0 0 0 0) + 0 + ) diff --git a/test/goalc/test_goal_kernel.cpp b/test/goalc/test_goal_kernel.cpp new file mode 100644 index 000000000..d270e38d9 --- /dev/null +++ b/test/goalc/test_goal_kernel.cpp @@ -0,0 +1,89 @@ +#include +#include "goalc/compiler/Compiler.h" +#include "test/goalc/framework/test_runner.h" +#include "gtest/gtest.h" + +class KernelTest : public testing::Test { + public: + static void SetUpTestSuite() { + printf("Building kernel...\n"); + try { + // a macro in goal-lib.gc + compiler.run_front_end_on_string("(build-kernel)"); + } catch (std::exception& e) { + fprintf(stderr, "caught exception %s\n", e.what()); + EXPECT_TRUE(false); + } + + printf("Starting GOAL Kernel...\n"); + runtime_thread = std::thread(GoalTest::runtime_with_kernel); + runner.c = &compiler; + } + + static void TearDownTestSuite() { + // send message to shutdown + compiler.shutdown_target(); + // wait for shutdown. + runtime_thread.join(); + } + + void SetUp() {} + + void TearDown() {} + + static std::thread runtime_thread; + static Compiler compiler; + static GoalTest::CompilerTestRunner runner; +}; + +std::thread KernelTest::runtime_thread; +Compiler KernelTest::compiler; +GoalTest::CompilerTestRunner KernelTest::runner; + +TEST_F(KernelTest, Basic) { + // first, let's load the kernel test code + runner.c->run_test_from_string("(ml \"test/goalc/source_templates/kernel/kernel-test.gc\")"); + auto& listener = runner.c->listener(); + + // record all print messages + listener.record_messages(ListenerMessageKind::MSG_PRINT); + + // run the test. + runner.c->compile_and_send_from_string("(kernel-test)"); + + // kinda hacky, but wait until the kernel runs and sends all the messages + while (listener.get_received_message_count() < 10) { + std::this_thread::sleep_for(std::chrono::microseconds(1000)); + } + + auto messages = listener.stop_recording_messages(); + std::string result; + for (auto& m : messages) { + result += m; + } + + std::string expected = + "0\n" + "proc2: 0\n" + "TARGET FUNCTION 1 2 3\n" + "4 5 6\n" + "Stack Alignemnt 0/16\n" + "proc1: 0\n" + "proc2: 6\n" + "proc1: 1\n" + "proc2: 12\n" + "proc1: 2\n" + "proc2: 18\n" + "proc1: 3\n" + "proc2: 24\n" + "proc1: 4\n" + "proc2: 30\n" + "proc1: 5\n" + "DEACTIVATE PROC 1\n" + "proc2: 36\n" + "proc2: 42\n" + "proc2: 48\n" + "proc2: 54\n"; + + EXPECT_EQ(expected, result); +} \ No newline at end of file