mirror of
https://github.com/open-goal/jak-project.git
synced 2024-10-20 21:27:52 -04:00
1f4044b9ff
Adds controller LED features to Jak 2: - progressive flickering denoting health - copies tomb simon says puzzle colors - unique colors for each gun - orange color for being indax - yellow color for being in mech - purple color for being darkjak - blue color for being in board - red flash when wanted. May add more features later? Also did some minor clean-up on some types.
2617 lines
89 KiB
Common Lisp
2617 lines
89 KiB
Common Lisp
;;-*-Lisp-*-
|
|
(in-package goal)
|
|
|
|
;; name: gkernel.gc
|
|
;; name in dgo: gkernel
|
|
;; dgos: KERNEL
|
|
|
|
;; The GOAL kernel provides:
|
|
;; - threads/stack management
|
|
;; - processes, and the process pools
|
|
;; - actor heap compacting GC
|
|
;; - process suspend
|
|
;; - executing the listener function and printing the result in the REPL
|
|
;; - catch/throw and stack frame utilities for the state system
|
|
;; - package loading (mostly unused loading system separate from levels)
|
|
|
|
;; Fwd
|
|
(define-extern change-parent (function process-tree process-tree process-tree))
|
|
|
|
;; DECOMP BEGINS
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; System Globals
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
;; Set to #f to not use the fancy printing of results
|
|
(define *use-old-listener-print* #f)
|
|
|
|
;; Set version number symbols
|
|
(define *kernel-version* (the binteger (logior (ash *kernel-major-version* 16) *kernel-minor-version*)))
|
|
(define *irx-version* (the binteger (logior (ash *irx-major-version* 16) *irx-minor-version*)))
|
|
|
|
;; Set default options. The C Kernel may modify these before loading the engine.
|
|
|
|
;; Can be 'boot, 'listener, or 'debug-boot
|
|
;; set to 'boot when DiskBooting.
|
|
(define *kernel-boot-mode* 'listener) ;; mostly unused.
|
|
|
|
;; DebugBootLevel in C Kernel
|
|
(define *kernel-boot-level* (the symbol #f)) ;; doesn't do anything.
|
|
|
|
;; The number of DECI messages received.
|
|
;; The C Kernel increments this.
|
|
(define *deci-count* 0)
|
|
|
|
;; Some debug stats. Unused.
|
|
(define *last-loado-length* 0)
|
|
(define *last-loado-global-usage* 0)
|
|
(define *last-loado-debug-usage* 0)
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; Relocate
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
;; 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
|
|
)
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; Kernel Package System
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
;; The kernel has a weird package system. It's not really used and doesn't do much.
|
|
;; Both the C Kernel and GOAL Kernel update the kernel-packages list.
|
|
;; The list is used to avoid loading the same package multiple times.
|
|
|
|
(define *kernel-packages* '())
|
|
|
|
(defun load-package ((package string) (allocation kheap))
|
|
"Load a Package from a CGO/DGO"
|
|
(unless (nmember package *kernel-packages*)
|
|
(dgo-load package allocation (link-flag output-load-msg output-load-true-msg execute-login print-login) #x200000)
|
|
(set! *kernel-packages* (cons package *kernel-packages*))
|
|
)
|
|
)
|
|
|
|
(defun unload-package ((package string))
|
|
"Mark a package as unloaded, if it was loaded previously"
|
|
(let ((pack (nmember package *kernel-packages*)))
|
|
(when pack
|
|
(set! *kernel-packages* (delete! (car pack) *kernel-packages*))
|
|
)
|
|
*kernel-packages*
|
|
)
|
|
)
|
|
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; Kernel Globals
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
;; The kernel context is a global which stores the state of the kernel.
|
|
(define *kernel-context* (new 'static 'kernel-context
|
|
:prevent-from-run (process-mask execute sleep)
|
|
:next-pid 2 ;; 1 will be listener
|
|
:current-process #f
|
|
:relocating-process #f
|
|
:low-memory-message #t
|
|
)
|
|
)
|
|
|
|
;; the main stack for running GOAL code!
|
|
;; most user code 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
|
|
(defconstant *kernel-dram-stack* (&+ *dram-stack* DPROCESS_STACK_SIZE))
|
|
|
|
;; another stack for running GOAL processes, this one uses the scratchpad (fast memory).
|
|
(set! (-> *kernel-context* fast-stack-top) *scratch-memory-top*)
|
|
|
|
;; A context with all process masks set to 0. This can be used to iterate through a process tree
|
|
;; without executing anything, to find a process for instance.
|
|
(define *null-kernel-context* (new 'static 'kernel-context))
|
|
|
|
|
|
;; scratchpad setup
|
|
(#cond
|
|
(PC_PORT
|
|
;; we'll create a fake scratchpad:
|
|
;; make sure the scratchpad is 64kb aligned, and make it 32 kB so we can big stacks on it.
|
|
;; some (partially buggy) code in generic tie relies on 64 kB alignment.
|
|
(let* ((mem (new 'global 'array 'uint8 (* 128 1024)))
|
|
)
|
|
(define *fake-scratchpad-data* (the pointer (align-n mem (* 64 1024))))
|
|
)
|
|
|
|
;; use the same memory for the scratchpad stacks.
|
|
;; defining it as a separate thing so we can split them for debugging stack corruption easily.
|
|
(define *fake-scratchpad-stack* *fake-scratchpad-data*)
|
|
|
|
(defmacro scratchpad-start ()
|
|
"Get the start of the scratchpad. At least 64kB aligned."
|
|
'*fake-scratchpad-data*
|
|
)
|
|
)
|
|
(else
|
|
(defmacro scratchpad-start ()
|
|
#x70000000
|
|
)
|
|
)
|
|
)
|
|
|
|
(defmacro scratchpad-end ()
|
|
"Get the end of the scratchpad memory"
|
|
`(&+ (scratchpad-start) (* 16 1024))
|
|
)
|
|
|
|
(defmacro in-scratchpad? (x)
|
|
"Is the given address in the scratchpad?"
|
|
`(and
|
|
(>= (the-as int ,x) (scratchpad-start))
|
|
(< (the-as int ,x) (scratchpad-end))
|
|
)
|
|
)
|
|
|
|
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; Thread and CPU Thread
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
;; A GOAL thread represents the execution of code in a process.
|
|
;; Each process has a "main thread", which is suspended and resumed.
|
|
;; A process may also execute various temporary threads which always run until completion.
|
|
;; A "temporary thread" cannot suspend and resume, but a "main thread" can.
|
|
;; The currently executing thread of a process is the "top-thread".
|
|
|
|
;; 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 ((this 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? this (-> this process main-thread))
|
|
;; We have attempted to delete the main thread, which is bad.
|
|
(break)
|
|
)
|
|
|
|
;; restore the old top-thread.
|
|
(set! (-> this process top-thread) (-> this previous))
|
|
(none)
|
|
)
|
|
|
|
(defmethod print thread ((this thread))
|
|
"Print thread."
|
|
(format #t "#<~A ~S of ~S pc: #x~X @ #x~X>" (-> this type) (-> this name) (-> this process name) (-> this pc) this)
|
|
this)
|
|
|
|
(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.
|
|
Users can do this if they want a larger or smaller backup stack than the default."
|
|
|
|
(let ((proc (-> this process)))
|
|
(cond
|
|
((neq? this (-> proc main-thread))
|
|
;; oops. can only change the size of a main-thread's stack.
|
|
(msg-err "illegal attempt change stack size of ~A when the main-thread is not the top-thread.~%" proc)
|
|
(break) ;; ADDED
|
|
)
|
|
|
|
((= (-> this stack-size) stack-size)
|
|
;; we already have this size. Don't do anything.
|
|
)
|
|
|
|
((eq? (-> proc heap-cur) (&+ this (-> this type size) (- *gtype-basic-offset*) (-> this stack-size)))
|
|
;; our heap cur point to right after us. So we can safely bump it forward to give us more space.
|
|
(set! (-> proc heap-cur) (the pointer (&+ this (-> this type size) (- *gtype-basic-offset*) stack-size)))
|
|
(set! (-> this stack-size) stack-size)
|
|
)
|
|
(else
|
|
(msg-err "illegal attempt change stack size of ~A after more heap allocation has occured.~%" proc)
|
|
(break)
|
|
)
|
|
)
|
|
)
|
|
(none)
|
|
)
|
|
|
|
(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. 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.
|
|
The stack-size is for the backup stack (applicable for main thread only)"
|
|
|
|
;; first, let's see if we're doing the main or temp thread
|
|
(let* ((this (cond
|
|
((-> 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 use the smaller PROCESS_STACK_SIZE, in case we're running on the scratchpad.
|
|
(the cpu-thread (&+ stack-top (- PROCESS_STACK_SIZE) *gtype-basic-offset*))
|
|
)
|
|
(else
|
|
;; the main thread. We need the main thread's cpu-thread to stick around, so we put it in the
|
|
;; process heap.
|
|
(let ((alloc (align16 (-> parent-process heap-cur)))) ;; start at heap cur, aligned
|
|
;; bump heap to include our thread + its stack
|
|
(set! (-> parent-process heap-cur) (the pointer (+ alloc (-> type-to-make size) stack-size)))
|
|
(the cpu-thread (+ alloc *gtype-basic-offset*))
|
|
)
|
|
)
|
|
)))
|
|
|
|
;; set up the type manually, as we allocated the memory manually
|
|
(set! (-> this type) type-to-make)
|
|
|
|
;; set up thread
|
|
(set! (-> this name) name)
|
|
(set! (-> this process) parent-process)
|
|
;; start stack at the top
|
|
(set! (-> this sp) stack-top)
|
|
(set! (-> this stack-top) stack-top)
|
|
;; remember the previous thread, in case we're a temp thread
|
|
(set! (-> this previous) (-> parent-process top-thread))
|
|
;; and make us the top!
|
|
(set! (-> parent-process top-thread) this)
|
|
|
|
;; set up our suspend/resume hooks. By default just use the thread's methods.
|
|
;; but something else could install a different hook if needed.
|
|
(set! (-> this suspend-hook) (method-of-object this thread-suspend))
|
|
(set! (-> this resume-hook) (method-of-object this thread-resume))
|
|
|
|
;; remember how much space we have for the backup stack.
|
|
(set! (-> this stack-size) stack-size)
|
|
this
|
|
)
|
|
)
|
|
|
|
|
|
(defmethod asize-of cpu-thread ((this cpu-thread))
|
|
"Get the size of a cpu-thread"
|
|
;; we need this because the cpu-thread is stored in the process stack
|
|
(the int (+ (-> this type size) (-> this stack-size)))
|
|
)
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; Remove Exit
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
(defbehavior remove-exit process ()
|
|
"Pops a single stack frame, if there is one.
|
|
User code can call this before doing a 'go' to avoid running the exit for the current state."
|
|
(when (-> self stack-frame-top)
|
|
(set! (-> self stack-frame-top) (-> self stack-frame-top next))
|
|
)
|
|
)
|
|
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; Process Tree
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
;; GOAL processes are stored in a left child, right sibling tree.
|
|
;; The base class of process is process-tree.
|
|
;; Each process-tree element has a process-mask which indicates what type of node it is.
|
|
|
|
(defun-debug stream<-process-mask ((arg0 object) (arg1 process-mask))
|
|
(bit-enum->string process-mask arg1 arg0)
|
|
arg1
|
|
)
|
|
|
|
(define *master-mode* 'game) ;; game, process, menu, pause
|
|
(define *pause-lock* #f) ;; set to #t when paused and doing a single frame advance with R2.
|
|
|
|
(defmethod new process-tree ((allocation symbol) (type-to-make type) (name basic))
|
|
"Create a process-tree node"
|
|
;; allocate
|
|
(let ((this (object-new allocation type-to-make)))
|
|
(set! (-> this name) name)
|
|
(set! (-> this mask) (process-mask process-tree))
|
|
(set! (-> this parent) #f)
|
|
(set! (-> this brother) #f)
|
|
(set! (-> this child) #f)
|
|
|
|
(set! (-> this self) this)
|
|
(set! (-> this ppointer) (the (pointer process) (&-> this self)))
|
|
this
|
|
)
|
|
)
|
|
|
|
(defmethod inspect process-tree ((this process-tree))
|
|
"Inspect a process-tree node."
|
|
(format #t "[~8x] ~A~%" this (-> this type))
|
|
(format #t "~Tname: ~S~%" (-> this name))
|
|
(format #t "~Tmask: #x~X~%" (-> this mask))
|
|
(format #t "~Tparent: ~A~%" (ppointer->process (-> this parent)))
|
|
(format #t "~Tbrother: ~A~%" (ppointer->process (-> this brother)))
|
|
(format #t "~Tchild: ~A~%" (ppointer->process (-> this child)))
|
|
this
|
|
)
|
|
|
|
|
|
(defmethod new process ((allocation symbol) (type-to-make type) (name basic) (stack-size int))
|
|
"Allocate a new process.
|
|
The process stack is initially set to the entire process memory."
|
|
(let ((this (if (eq? (-> allocation type) symbol)
|
|
(object-new allocation type-to-make (the int (+ (-> process size) stack-size))) ;; symbol, allocate on heap
|
|
(the process (&+ allocation *gtype-basic-offset*))))) ;; treat as address.
|
|
|
|
;; initialize
|
|
(set! (-> this name) name)
|
|
(set! (-> this status) 'dead)
|
|
(set! (-> this pid) 0)
|
|
(set! (-> this pool) #f)
|
|
(set! (-> this allocated-length) stack-size)
|
|
(set! (-> this top-thread) #f)
|
|
(set! (-> this main-thread) #f)
|
|
|
|
;; set up the heap to start at the stack
|
|
(set! (-> this heap-cur) (-> this stack))
|
|
(set! (-> this heap-base) (-> this stack))
|
|
|
|
;; and end at the end of the stack.
|
|
(set! (-> this heap-top) (&-> (-> this stack) (-> this allocated-length)))
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; heap top-base bug
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; original there was something like (set! (-> heap-top-base) (-> heap-top))
|
|
;; but this overlaps with the stack-frame-top and did nothing.
|
|
;; this is likely because they added the concept of heap "top" to kheaps in
|
|
;; general, but not to process heaps.
|
|
|
|
;; setup state stuff
|
|
(set! (-> this stack-frame-top) #f)
|
|
(set! (-> this state) #f)
|
|
(set! (-> this next-state) #f)
|
|
(set! (-> this entity) #f)
|
|
|
|
;; setup handlers
|
|
(set! (-> this trans-hook) #f)
|
|
(set! (-> this post-hook) #f)
|
|
(set! (-> this event-hook) #f)
|
|
|
|
;; setup process tree
|
|
(set! (-> this parent) #f)
|
|
(set! (-> this brother) #f)
|
|
(set! (-> this child) #f)
|
|
|
|
;; setup reference stuff.
|
|
(set! (-> this self) this)
|
|
(set! (-> this ppointer) (the (pointer process) (&-> this self)))
|
|
this
|
|
)
|
|
)
|
|
|
|
(defun inspect-process-heap ((this process))
|
|
"Inspect the heap of a process."
|
|
(let ((ptr (&+ (-> this heap-base) *gtype-basic-offset*))) ; point to first basic
|
|
;; loop over objects
|
|
(while (< (the int ptr) (the int (-> this heap-cur)))
|
|
;; inspect the object
|
|
(inspect (the basic ptr))
|
|
;; seek to the next object on the heap.
|
|
(&+! ptr (the int (align16 (asize-of (the basic ptr)))))
|
|
)
|
|
)
|
|
)
|
|
|
|
(defmethod inspect process ((this process))
|
|
(format #t "[~8x] ~A~%" this (-> this type))
|
|
(format #t "~Tname: ~S~%" (-> this name))
|
|
(format #t "~Tmask: #x~X~%" (-> this mask))
|
|
(format #t "~Tstatus: ~A~%" (-> this status))
|
|
(format #t "~Tmain-thread: ~A~%" (-> this main-thread))
|
|
(format #t "~Ttop-thread: ~A~%" (-> this top-thread))
|
|
(format #t "~Tentity: ~A~%" (-> this entity))
|
|
(format #t "~Tstate: ~A~%" (-> this state))
|
|
(format #t "~Tnext-state: ~A~%" (-> this next-state))
|
|
(format #t "~Ttrans-hook: ~A~%" (-> this trans-hook))
|
|
(format #t "~Tpost-hook: ~A~%" (-> this post-hook))
|
|
(format #t "~Tevent-hook: ~A~%" (-> this event-hook))
|
|
(format #t "~Tparent: ~A~%" (ppointer->process (-> this parent)))
|
|
(format #t "~Tbrother: ~A~%" (ppointer->process (-> this brother)))
|
|
(format #t "~Tchild: ~A~%" (ppointer->process (-> this child)))
|
|
(format #t "~Tconnection-list: ~`connectable`P~%" (-> this connection-list))
|
|
(format #t "~Tstack-frame-top: ~A~%" (-> this stack-frame-top))
|
|
(format #t "~Theap-base: #x~X~%" (-> this heap-base))
|
|
(format #t "~Theap-top: #x~X~%" (-> this heap-top))
|
|
(format #t "~Theap-cur: #x~X~%" (-> this heap-cur))
|
|
|
|
;; print all objects on the process heap
|
|
(protect (*print-column*)
|
|
(+! *print-column* *tab-size*)
|
|
(format #t "----~%")
|
|
(inspect-process-heap this)
|
|
(format #t "----~%")
|
|
)
|
|
|
|
(format #t "~Tallocated-length: ~D~%" (-> this allocated-length))
|
|
(format #t "~Tstack[~D] @ #x~X~%" (-> this allocated-length) (-> this stack))
|
|
this
|
|
)
|
|
|
|
(defmethod asize-of process ((this process))
|
|
(the int (+ (-> process size) (-> this allocated-length)))
|
|
)
|
|
|
|
(defmethod print process ((this process))
|
|
(format #t "#<~A ~S ~A :state ~S "
|
|
(-> this type)
|
|
(-> this name)
|
|
(-> this status)
|
|
(when (-> this state) (-> this state name)))
|
|
|
|
(format #t ":stack ~D/~D :heap ~D/~D @ #x~X>"
|
|
(process-stack-used this)
|
|
(process-stack-size this)
|
|
(process-heap-used this)
|
|
(process-heap-size this)
|
|
this
|
|
)
|
|
this
|
|
)
|
|
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; Context Suspend And Resume - Kernel
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
;; 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!!!!
|
|
|
|
;; we use this to store a GOAL pointer to the kernel's stack pointer when executing user code.
|
|
;; to get back to the kernel, we use this global symbol.
|
|
(define-extern *kernel-sp* pointer)
|
|
|
|
(defun return-from-thread ()
|
|
"Context switch to the saved kernel context now.
|
|
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)
|
|
)
|
|
(rlet ((sp :reg rsp :type uint)
|
|
(off :reg r15 :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)
|
|
)
|
|
;; 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" 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)
|
|
)
|
|
)
|
|
|
|
(defun return-from-thread-dead ()
|
|
"Like return from thread, but we clean up our process with deactivate first.
|
|
The return register is not preserved here, instead we return the value of deactivate"
|
|
(declare (asm-func none)
|
|
;(print-asm)
|
|
)
|
|
(rlet ((pp :reg r13 :type process)
|
|
(sp :reg rsp :type uint)
|
|
(off :reg r15 :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)
|
|
)
|
|
|
|
;; first call the deactivate method.
|
|
(deactivate pp)
|
|
;; get the kernel stack pointer as a GOAL pointer
|
|
(.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".
|
|
(.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
|
|
(.ret)
|
|
)
|
|
)
|
|
|
|
(defmacro abandon-thread ()
|
|
;; abandon this one too.
|
|
;; NOTE - this is different from GOAL.
|
|
;; GOAL installs this as the return address for this function and returns normally.
|
|
;; but we don't because I don't have an easy way to find where to stick this.
|
|
;; I can't see how this makes a difference, as all non-main threads seem
|
|
;; temporary, but if this turns out to be false, we will need to change this.
|
|
`(rlet ((temp)
|
|
(off :reg r15 :type uint :reset-here #t))
|
|
(.mov temp return-from-thread) ;; could probably just call this...
|
|
(.add temp off)
|
|
(.push temp)
|
|
(.ret)
|
|
)
|
|
)
|
|
|
|
(defun reset-and-call ((this 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. 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)
|
|
)
|
|
|
|
(rlet ((pp :reg r13 :type process)
|
|
(sp :reg rsp :type uint)
|
|
(off :reg r15 :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)
|
|
(temp :reg rax :type uint)
|
|
)
|
|
|
|
;; set up the process pointer
|
|
(set! pp (-> this process))
|
|
;; mark the process as running and set its top thread
|
|
(set! (-> pp status) 'running)
|
|
(set! (-> pp top-thread) this)
|
|
|
|
;; save the current kernel regs
|
|
(.push :color #f s0)
|
|
(.push :color #f s1)
|
|
(.push :color #f s2)
|
|
(.push :color #f s3)
|
|
(.push :color #f s4)
|
|
|
|
;; make rsp a GOAL pointer
|
|
(.sub sp off)
|
|
;; and store it
|
|
(set! *kernel-sp* (the pointer sp)) ;; todo, asm form here?
|
|
|
|
;; setup the rsp for the new thread
|
|
(set! sp (the uint (-> this stack-top)))
|
|
(.add sp off)
|
|
|
|
;; push the return trampoline to the stack for the user code to return to
|
|
(set! temp (the uint return-from-thread))
|
|
(.add temp off)
|
|
(.push temp) ;; stack now 16 + 8 aligned
|
|
;; and call the function!
|
|
(.add func off)
|
|
(.jr func)
|
|
)
|
|
)
|
|
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; Context Suspend And Resume - 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."
|
|
|
|
(declare (asm-func none))
|
|
|
|
;; we begin this function with the thread object in pp.
|
|
;; not sure why we do this, maybe at one point suspending didn't clobber
|
|
;; temp registers?
|
|
(rlet ((this :reg r13 :type cpu-thread)
|
|
(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)
|
|
|
|
(xmm8 :reg xmm8 :class fpr)
|
|
(xmm9 :reg xmm9 :class fpr)
|
|
(xmm10 :reg xmm10 :class fpr)
|
|
(xmm11 :reg xmm11 :class fpr)
|
|
(xmm12 :reg xmm12 :class fpr)
|
|
(xmm13 :reg xmm13 :class fpr)
|
|
(xmm14 :reg xmm14 :class fpr)
|
|
(xmm15 :reg xmm15 :class fpr)
|
|
)
|
|
|
|
;; get the return address pushed by "call" in the suspend.
|
|
(.pop temp)
|
|
;; convert to a GOAL address
|
|
(.sub temp off)
|
|
;; store return address in thread
|
|
(set! (-> this pc) (the pointer temp))
|
|
|
|
;; convert our stack pointer to a GOAL address
|
|
(.sub sp off)
|
|
;; store in thread.
|
|
(set! (-> this sp) (the pointer sp))
|
|
|
|
;; back up registers
|
|
(.mov :color #f temp s0)
|
|
(set! (-> this rreg 0) temp)
|
|
(.mov :color #f temp s1)
|
|
(set! (-> this rreg 1) temp)
|
|
(.mov :color #f temp s2)
|
|
(set! (-> this rreg 2) temp)
|
|
(.mov :color #f temp s3)
|
|
(set! (-> this rreg 3) temp)
|
|
(.mov :color #f temp s4)
|
|
(set! (-> this rreg 4) temp)
|
|
|
|
;; back up fprs
|
|
(.mov :color #f temp xmm8)
|
|
(set! (-> this freg 0) (the-as float temp))
|
|
(.mov :color #f temp xmm9)
|
|
(set! (-> this freg 1) (the-as float temp))
|
|
(.mov :color #f temp xmm10)
|
|
(set! (-> this freg 2) (the-as float temp))
|
|
(.mov :color #f temp xmm11)
|
|
(set! (-> this freg 3) (the-as float temp))
|
|
(.mov :color #f temp xmm12)
|
|
(set! (-> this freg 4) (the-as float temp))
|
|
(.mov :color #f temp xmm13)
|
|
(set! (-> this freg 5) (the-as float temp))
|
|
(.mov :color #f temp xmm14)
|
|
(set! (-> this freg 6) (the-as float temp))
|
|
(.mov :color #f temp xmm15)
|
|
(set! (-> this freg 7) (the-as float temp))
|
|
|
|
|
|
|
|
;; get our process
|
|
(let ((proc (-> this process)))
|
|
(when (> (process-stack-used proc) (-> this stack-size))
|
|
(break) ;; too much stack has been used and we can't suspend!
|
|
;; if you hit this, try with DEBUG_PRINT_SUSPEND_FAIL set to #t (see gkernel-h.gc)
|
|
;; it will print more info before reaching here.
|
|
)
|
|
|
|
;; mark the process as suspended and copy the stack
|
|
(set! (-> proc status) 'suspended)
|
|
(let ((cur (the (pointer uint64) (-> this stack-top)))
|
|
(save (&+ (the (pointer uint64) (-> this stack)) (-> this stack-size)))
|
|
)
|
|
(while (> (the int cur) (the int sp))
|
|
(set! cur (the (pointer uint64) (&- cur 8)))
|
|
(set! save (the (pointer uint64) (&- save 8)))
|
|
(set! (-> save) (-> cur))
|
|
)
|
|
)
|
|
)
|
|
|
|
;; actually setting pp to 0
|
|
(set! this (the cpu-thread 0))
|
|
|
|
;; get the kernel stack pointer as a GOAL pointer
|
|
(.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".
|
|
(.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
|
|
(.ret)
|
|
)
|
|
(none)
|
|
)
|
|
|
|
|
|
(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)
|
|
)
|
|
|
|
(rlet ((this :reg r13 :type cpu-thread)
|
|
(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)
|
|
(a4 :reg r8 :type uint)
|
|
(a5 :reg r9 :type uint)
|
|
|
|
(temp-float :reg xmm0 :class fpr)
|
|
(xmm8 :reg xmm8 :class fpr)
|
|
(xmm9 :reg xmm9 :class fpr)
|
|
(xmm10 :reg xmm10 :class fpr)
|
|
(xmm11 :reg xmm11 :class fpr)
|
|
(xmm12 :reg xmm12 :class fpr)
|
|
(xmm13 :reg xmm13 :class fpr)
|
|
(xmm14 :reg xmm14 :class fpr)
|
|
(xmm15 :reg xmm15 :class fpr)
|
|
)
|
|
|
|
;; save the current kernel regs
|
|
(.push :color #f s0)
|
|
(.push :color #f s1)
|
|
(.push :color #f s2)
|
|
(.push :color #f s3)
|
|
(.push :color #f s4)
|
|
|
|
;; make rsp a GOAL pointer
|
|
(.sub sp off)
|
|
;; and store it
|
|
(set! *kernel-sp* (the pointer sp)) ;; todo, asm form here?
|
|
|
|
;; temp, stash thread in process-pointer
|
|
(set! this thread-to-resume)
|
|
|
|
;; set stack pointer for the thread. leave it as a GOAL pointer for now..
|
|
(set! sp (the uint (-> this sp)))
|
|
|
|
;; restore the stack (sp is a GOAL pointer)
|
|
(let ((cur (the (pointer uint64) (-> this stack-top)))
|
|
(restore (&+ (the (pointer uint64) (-> this stack)) (-> this stack-size)))
|
|
)
|
|
(while (> (the int cur) (the int sp))
|
|
(set! cur (the (pointer uint64) (&- cur 8)))
|
|
(set! restore (the (pointer uint64) (&- restore 8)))
|
|
(set! (-> cur) (-> restore))
|
|
)
|
|
)
|
|
|
|
;; offset sp after we're done using it as a GOAL pointer.
|
|
(.add sp off)
|
|
|
|
;; setup process
|
|
(set! (-> (-> this process) top-thread) this)
|
|
(set! (-> (-> this process) status) 'running)
|
|
|
|
;; restore reg
|
|
(set! temp (-> this rreg 0))
|
|
(.mov :color #f s0 temp)
|
|
(set! temp (-> this rreg 1))
|
|
(.mov :color #f s1 temp)
|
|
(set! temp (-> this rreg 2))
|
|
(.mov :color #f s2 temp)
|
|
(set! temp (-> this rreg 3))
|
|
(.mov :color #f s3 temp)
|
|
(set! temp (-> this rreg 4))
|
|
(.mov :color #f s4 temp)
|
|
(set! temp-float (-> this freg 0))
|
|
(.mov :color #f xmm8 temp-float)
|
|
(set! temp-float (-> this freg 1))
|
|
(.mov :color #f xmm9 temp-float)
|
|
(set! temp-float (-> this freg 2))
|
|
(.mov :color #f xmm10 temp-float)
|
|
(set! temp-float (-> this freg 3))
|
|
(.mov :color #f xmm11 temp-float)
|
|
(set! temp-float (-> this freg 4))
|
|
(.mov :color #f xmm12 temp-float)
|
|
(set! temp-float (-> this freg 5))
|
|
(.mov :color #f xmm13 temp-float)
|
|
(set! temp-float (-> this freg 6))
|
|
(.mov :color #f xmm14 temp-float)
|
|
(set! temp-float (-> this freg 7))
|
|
(.mov :color #f xmm15 temp-float)
|
|
|
|
;; 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 (-> this rreg 5))
|
|
(.mov a4 temp)
|
|
(set! temp (-> this rreg 6))
|
|
(.mov a5 temp)
|
|
|
|
;; get the resume address
|
|
(set! temp (the uint (-> this pc)))
|
|
(.add temp off)
|
|
|
|
;; setup the process
|
|
(set! this (the cpu-thread (-> this process)))
|
|
;; resume!
|
|
(.jr temp)
|
|
(.add a4 a4)
|
|
(.add a5 a5)
|
|
)
|
|
(none)
|
|
)
|
|
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; Process Dead Pool
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
;; a dead pool is just a collection of dead processes of a fixed size.
|
|
|
|
|
|
(define-extern *debug-dead-pool* dead-pool-heap)
|
|
|
|
(defmethod new dead-pool ((allocation symbol) (type-to-make type) (count int) (stack-size int) (name basic))
|
|
"Create a pool of count dead processes, each with a fixed size stack-size"
|
|
(let ((this (object-new allocation type-to-make)))
|
|
;; setup process naming
|
|
(set! (-> this name) name)
|
|
(set! (-> this mask) (process-mask process-tree))
|
|
;; setup process tree
|
|
(set! (-> this parent) #f)
|
|
(set! (-> this brother) #f)
|
|
(set! (-> this child) #f)
|
|
;; setup ref
|
|
(set! (-> this self) this)
|
|
(set! (-> this ppointer) (the (pointer process) (&-> this self)))
|
|
|
|
(dotimes (i count)
|
|
;; create each process
|
|
(let ((old-bro (-> this child))
|
|
(next ((method-of-type process new) allocation process 'dead stack-size)))
|
|
(set! (-> this child) (process->ppointer next))
|
|
(set! (-> next parent) (process->ppointer this))
|
|
(set! (-> next pool) this)
|
|
(set! (-> next brother) old-bro)
|
|
)
|
|
)
|
|
this
|
|
)
|
|
)
|
|
|
|
(defmethod get-process dead-pool ((this dead-pool) (type-to-make type) (stack-size int))
|
|
"Get a process from this dead pool of the given type."
|
|
(let ((proc (-> this child)))
|
|
|
|
(when (and (not proc) *debug-segment* (neq? this *debug-dead-pool*))
|
|
;; we failed, but we're in debug mode and not looking at the debug pool
|
|
;; try again from the debug pool and warn if this works
|
|
(set! proc (the (pointer process-tree) (get-process *debug-dead-pool* type-to-make stack-size)))
|
|
(when proc
|
|
(format 0 "WARNING: ~A ~A had to be allocated from the debug pool, because ~A was empty.~%"
|
|
type-to-make (ppointer->process proc) (-> this name))
|
|
)
|
|
;; there's a bug here. proc is a process here, but will be used as a process pointer.
|
|
;; let's just kill the program here.
|
|
;; this is likely a copy-paste bug from get-process dead-pool-heap.
|
|
(break)
|
|
)
|
|
|
|
(cond
|
|
(proc
|
|
;; success! set our type and return.
|
|
(set! (-> (-> proc) type) type-to-make)
|
|
(the process (-> proc)) ;; cast from process-tree to process.
|
|
)
|
|
(else
|
|
(format 0 "WARNING: ~A ~A could not be allocated, because ~A was empty.~%"
|
|
type-to-make (ppointer->process proc) (-> this name))
|
|
(the process #f)
|
|
)
|
|
)
|
|
)
|
|
)
|
|
|
|
|
|
(defmethod return-process dead-pool ((this dead-pool) (proc process))
|
|
"Return a process to its pool once you are done with it."
|
|
(change-parent proc this)
|
|
(none)
|
|
)
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; Process Dead Pool Heap
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
;; a dead-pool-heap is a chunk of memory where you can allocate variable sized processes.
|
|
;; these processes start out with a lot of memory, then shrink their heap (compact) to the size
|
|
;; they actually need. To avoid heap fragmentation, the dead-pool-heap system will relocate
|
|
;; processes. This requires that you implement the relocate method on your process.
|
|
|
|
;; DANGER: the dead pool heap is _not_ a proper process tree. Do not attempt to treat it like one.
|
|
;; If you get-process, you should immediately activate it. The activate method will (change-parent)
|
|
;; and this will get stuck in an endless loop if you do it on a process that wasn't the most recent one.
|
|
|
|
(define-extern *null-process* process)
|
|
|
|
(defmethod new dead-pool-heap ((allocation symbol) (type-to-make type) (name basic) (allocated-length int) (heap-size int))
|
|
"Create a new dead pool heap. It will support allocated-length processes and have a total heap size of heap-size"
|
|
(let ((this (object-new allocation type-to-make (+ (the int (-> type-to-make size))
|
|
(the int (align16 (* allocated-length 12)))
|
|
heap-size))))
|
|
(set! (-> this name) name)
|
|
(set! (-> this mask) (process-mask process-tree))
|
|
(set! (-> this allocated-length) allocated-length)
|
|
(set! (-> this parent) #f)
|
|
(set! (-> this brother) #f)
|
|
(set! (-> this child) #f)
|
|
|
|
(set! (-> this self) this)
|
|
(set! (-> this ppointer) (the (pointer process) (&-> this self)))
|
|
|
|
;; initialize each process handle
|
|
;; build them into a linked list of null-process
|
|
(countdown (i allocated-length)
|
|
(let ((rec (-> this process-list i)))
|
|
(set! (-> rec process) *null-process*)
|
|
(set! (-> rec next) (-> this process-list (+ i 1)))
|
|
)
|
|
)
|
|
|
|
;; set up the dead-list
|
|
(set! (-> this dead-list next) (-> this process-list 0))
|
|
(set! (-> this alive-list process) #f) ;; likely typo here, should be dead-list
|
|
(set! (-> this process-list (- allocated-length 1) next) #f)
|
|
|
|
;; nothing is alive
|
|
(set! (-> this last) (-> this alive-list))
|
|
(set! (-> this alive-list next) #f)
|
|
(set! (-> this alive-list process) #f)
|
|
(set! (-> this first-gap) (-> this alive-list))
|
|
(set! (-> this first-shrink) #f)
|
|
|
|
;; setup the heap. It just begins after the process records.
|
|
(set! (-> this heap base) (the pointer (align16 (-> this process-list allocated-length))))
|
|
(set! (-> this heap current) (-> this heap base))
|
|
(set! (-> this heap top) (&+ (-> this heap base) heap-size))
|
|
(set! (-> this heap top-base) (-> this heap top))
|
|
this
|
|
)
|
|
)
|
|
|
|
(defmethod gap-location dead-pool-heap ((this dead-pool-heap) (rec dead-pool-heap-rec))
|
|
"Get the gap after the given process.
|
|
If root of the alive list is given, will give the first gap between the heap and the first process.
|
|
If there is no gap, may point to the next process. Not 16-byte aligned."
|
|
(cond
|
|
((-> rec process)
|
|
(the pointer (&+ (-> rec process) (-> process size) (-> rec process allocated-length) (- *gtype-basic-offset*)))
|
|
;; start of proc end of type data process's heap basic offset
|
|
)
|
|
(else
|
|
(-> this heap base)
|
|
)
|
|
)
|
|
)
|
|
|
|
(defmethod gap-size dead-pool-heap ((this dead-pool-heap) (rec dead-pool-heap-rec))
|
|
"Determine the size between the given process and the next process or end of the heap.
|
|
If you give the first rec, it will given the gap between the beginning of the heap and the next process."
|
|
(the int
|
|
(cond
|
|
((-> rec process)
|
|
;; compute the end of my process (no basic offset)
|
|
(let ((my-end (&+ (-> rec process) (-> process size) (-> rec process allocated-length))))
|
|
(if (-> rec next)
|
|
;; if there's a next process, look at the difference to the next (basic offsets cancel)
|
|
(&- (-> rec next process) my-end)
|
|
;; no next process, look at the top of the heap.
|
|
(&- (-> this heap top) (&+ my-end *gtype-basic-offset*))
|
|
)
|
|
)
|
|
)
|
|
(else
|
|
(if (-> rec next)
|
|
(&- (-> rec next process) (&+ (-> this heap base) *gtype-basic-offset*))
|
|
(&- (-> this heap top) (-> this heap base)))
|
|
)
|
|
)
|
|
)
|
|
)
|
|
|
|
(defmethod find-gap dead-pool-heap ((this dead-pool-heap) (rec dead-pool-heap-rec))
|
|
"Start at the given record and find the closest gap after it. Returns the rec
|
|
which has the gap after it. If no gaps, returns the last rec."
|
|
(while (and (-> rec next) (zero? (gap-size this rec)))
|
|
; no gap here!
|
|
(set! rec (-> rec next))
|
|
)
|
|
rec
|
|
)
|
|
|
|
|
|
(defmethod inspect dead-pool-heap ((this dead-pool-heap))
|
|
"Inspect a dead-pool-heap and all of the recs and their gaps"
|
|
(format #t "[~8x] ~A~%" this (-> this type))
|
|
(format #t "~Tname: ~A~%" (-> this name))
|
|
(format #t "~Tmask: ~D~%" (-> this mask))
|
|
(format #t "~Tparent: #x~X~%" (-> this parent))
|
|
(format #t "~Tbrother: #x~X~%" (-> this brother))
|
|
(format #t "~Tchild: #x~X~%" (-> this child))
|
|
(format #t "~Tppointer: #x~X~%" (-> this ppointer))
|
|
(format #t "~Tself: ~A~%" (-> this self))
|
|
(format #t "~Tallocated-length: ~D~%" (-> this allocated-length))
|
|
(format #t "~Theap: #<kheap @ #x~X>~%" (-> this heap))
|
|
(format #t "~Tfirst-gap: #<dead-pool-heap-rec @ #x~X>~%" (-> this first-gap))
|
|
(format #t "~Tfirst-shrink: #<dead-pool-heap-rec @ #x~X>~%" (-> this first-shrink))
|
|
(format #t "~Talive-list: #<dead-pool-heap-rec @ #x~X>~%" (-> this alive-list))
|
|
(format #t "~Tlast: #<dead-pool-heap-rec @ #x~X>~%" (-> this last))
|
|
(format #t "~Tdead-list: #<dead-pool-heap-rec @ #x~X>~%" (-> this dead-list))
|
|
|
|
;; here we consider the free memory to be all of the stuff after the last process.
|
|
;; we don't consider random gaps to be "free".
|
|
;; this means you can do a single allocation of free bytes and it will always succeed.
|
|
(let* ((total (the int (&- (-> this heap top) (-> this heap base))))
|
|
(free (if (-> this last)
|
|
(gap-size this (-> this last))
|
|
total))
|
|
)
|
|
(format #t "~Tprocess-list[0] @ #x~X ~D/~D bytes used~%" (-> this process-list) (- total free) total)
|
|
)
|
|
|
|
(let ((rec (-> this alive-list))
|
|
(i 0)
|
|
)
|
|
(while rec
|
|
(when (-> rec process)
|
|
(format #t "~T [~3D] #<dead-pool-heap-rec @ #x~X> ~A~%" i rec (-> rec process))
|
|
)
|
|
(let ((gap (gap-size this rec)))
|
|
(unless (zero? gap)
|
|
(format #t "~T gap: ~D bytes @ #x~X~%" gap (gap-location this rec)))
|
|
)
|
|
(set! rec (-> rec next))
|
|
(+! i 1)
|
|
)
|
|
)
|
|
|
|
this)
|
|
|
|
(defmethod asize-of dead-pool-heap ((this dead-pool-heap))
|
|
"Get our total size. Uses the heap top as the end."
|
|
(- (the int (-> this heap top)) (the int this) *gtype-basic-offset*)
|
|
)
|
|
|
|
(defmethod memory-used dead-pool-heap ((this dead-pool-heap))
|
|
"Get the amount of memory used. This includes gaps between processes."
|
|
(if (-> this last)
|
|
; we have at least one process, get the not-last-gap memory
|
|
(- (memory-total this) (gap-size this (-> this last)))
|
|
; no processes.
|
|
0
|
|
)
|
|
)
|
|
|
|
(defmethod memory-total dead-pool-heap ((this dead-pool-heap))
|
|
"Get the total amount of memory for processes"
|
|
(the int (&- (-> this heap top) (-> this heap base)))
|
|
)
|
|
|
|
(defmethod memory-free dead-pool-heap ((this dead-pool-heap))
|
|
"Get the total memory free."
|
|
(let ((top (-> this heap top)))
|
|
(if (-> this last)
|
|
; get the last gap size
|
|
(gap-size this (-> this last))
|
|
; otherwise just the whole heap.
|
|
(the int (&- top (-> this heap base)))
|
|
)
|
|
)
|
|
)
|
|
|
|
(defmethod compact-time dead-pool-heap ((this dead-pool-heap))
|
|
"Access the compact time field."
|
|
(-> this compact-time)
|
|
)
|
|
|
|
(defmethod find-gap-by-size dead-pool-heap ((this dead-pool-heap) (size int))
|
|
"Find a gap which will fit at least size bytes. Returns the rec for the proc before the gap.
|
|
Will return a #f rec if there's no gap big enough."
|
|
; start our search at first-gap
|
|
(let ((rec (-> this first-gap)))
|
|
(while (and rec (< (gap-size this rec) size))
|
|
;; nope, not big enough.
|
|
(set! rec (-> rec next))
|
|
)
|
|
rec
|
|
)
|
|
)
|
|
|
|
;; this will be set to #t if we're using visibility data.
|
|
;; if we aren't, there will be many cases where we try to spawn too many actors, and we shouldn't
|
|
;; warn when this happens.
|
|
(define-extern *vis-boot* basic)
|
|
|
|
(defmethod get-process dead-pool-heap ((this dead-pool-heap) (type-to-make type) (stack-size int))
|
|
"Allocate a process"
|
|
|
|
;; get a record for the new process
|
|
(let ((rec (-> this dead-list next))
|
|
;; will eventually hold our new process
|
|
(proc (the process #f))
|
|
;; find the rec which has a big enough gap
|
|
(insert (find-gap-by-size this (+ (the int (-> process size)) stack-size)))
|
|
)
|
|
|
|
(cond
|
|
;; check we got both a record and a gap
|
|
((and rec insert)
|
|
|
|
;; pop the record off of the list
|
|
(set! (-> this dead-list next) (-> rec next))
|
|
|
|
;; splice it into the alive list in the right spot
|
|
(let ((next (-> insert next)))
|
|
;; after the gap rec
|
|
(set! (-> insert next) rec)
|
|
;; us to the process after the gap
|
|
(set! (-> rec next) next)
|
|
|
|
;; link the proc after us back
|
|
(when next
|
|
(set! (-> next prev) rec)
|
|
)
|
|
;; and us back to the proc before the gap
|
|
(set! (-> rec prev) insert)
|
|
|
|
;; if we are inserting after the last process, we should update the last.
|
|
(when (eq? insert (-> this last))
|
|
(set! (-> this last) rec)
|
|
)
|
|
|
|
;; get the gap
|
|
(set! proc (the process (gap-location this insert)))
|
|
;; and allocate! The method new does the offset for us.
|
|
(set! proc ((method-of-type process new) (the symbol proc) process 'process stack-size))
|
|
|
|
;; update our rec to contain this process.
|
|
(set! (-> rec process) proc)
|
|
;; and the ppointer should point to the rec, not the processs, so we can track the process if it moves.
|
|
(set! (-> proc ppointer) (&-> rec process))
|
|
|
|
;; if we used the first gap, update first gap
|
|
(when (eq? (-> this first-gap) insert)
|
|
(set! (-> this first-gap) (find-gap this rec))
|
|
)
|
|
|
|
;; we haven't shrunk yet. If we don't have a first-shrink, or we are before it,
|
|
;; mark us as first shrink.
|
|
(when (or (not (-> this first-shrink))
|
|
(< (the int proc) (the int (-> this first-shrink process)))
|
|
)
|
|
(set! (-> this first-shrink) rec)
|
|
)
|
|
|
|
;; update tree stuff.
|
|
(set! (-> proc parent) (-> this ppointer))
|
|
(set! (-> proc pool) this)
|
|
(set! (-> this child) (&-> rec process))
|
|
)
|
|
|
|
)
|
|
(else
|
|
;; allocation failed! try again on the debug heap if we're debugging.
|
|
(when (and *debug-segment* (not (eq? this *debug-dead-pool*)))
|
|
(set! proc (get-process *debug-dead-pool* type-to-make stack-size))
|
|
(when (and proc *vis-boot*)
|
|
(format 0 "WARNING: ~A ~A had to be allocated from the debug pool, because ~A was empty.~%" type-to-make proc (-> this name)))
|
|
)
|
|
|
|
)
|
|
)
|
|
|
|
(cond
|
|
(proc
|
|
;; success! set type and return.
|
|
(set! (-> proc type) type-to-make)
|
|
)
|
|
(else
|
|
;; failure. complain.
|
|
(format 0 "WARNING: ~A ~A could not be allocated, because ~A was empty.~%" type-to-make proc (-> this name))
|
|
)
|
|
)
|
|
|
|
proc)
|
|
)
|
|
|
|
(defmethod return-process dead-pool-heap ((this dead-pool-heap) (proc process))
|
|
"Return a process to a dead pool heap"
|
|
|
|
;; check we are returning to the correct pool
|
|
(unless (eq? this (-> proc pool))
|
|
(format 0 "ERROR: process ~A does not belong to dead-pool-heap ~A.~%" proc this)
|
|
)
|
|
|
|
;; reclaim us.
|
|
(change-parent proc this)
|
|
|
|
;; we don't maintain a real tree for a dead-pool-heap, so undo any change to child
|
|
;; done by change-parent
|
|
(set! (-> this child) #f)
|
|
|
|
;; we know our ppointer is really a rec for a dead-pool-heap process, so we can use
|
|
;; this trick to quickly find our rec.
|
|
(let ((rec (the dead-pool-heap-rec (-> proc ppointer))))
|
|
|
|
;; if we are at or below the first gap, update first gap.
|
|
(when (or (eq? (-> this first-gap) rec)
|
|
(< (the int (gap-location this rec)) (the int (gap-location this (-> this first-gap))))
|
|
)
|
|
(set! (-> this first-gap) (-> rec prev))
|
|
)
|
|
|
|
|
|
;; update the first-shrink. We aren't smart about this and just move it backward.
|
|
(when (eq? (-> this first-shrink) rec)
|
|
(set! (-> this first-shrink) (-> rec prev))
|
|
(when (not (-> this first-shrink process))
|
|
(set! (-> this first-shrink) #f))
|
|
)
|
|
|
|
;; remove us from list
|
|
(set! (-> rec prev next) (-> rec next))
|
|
(cond
|
|
((-> rec next)
|
|
;; update links
|
|
(set! (-> rec next prev) (-> rec prev))
|
|
)
|
|
(else
|
|
;; we were last, update that.
|
|
(set! (-> this last) (-> rec prev))
|
|
)
|
|
)
|
|
|
|
;; insert at the front of the dead list.
|
|
(set! (-> rec next) (-> this dead-list next))
|
|
(set! (-> this dead-list next) rec)
|
|
(set! (-> rec process) *null-process*)
|
|
|
|
(none)
|
|
)
|
|
)
|
|
|
|
(defmethod shrink-heap dead-pool-heap ((this dead-pool-heap) (proc process))
|
|
"Shrink the heap of a process.
|
|
This resizes the process heap to be the exact size it is currently using."
|
|
(when proc
|
|
;; get our rec.
|
|
(let ((rec (the dead-pool-heap-rec (-> proc ppointer))))
|
|
;; check if it's ok to shrink
|
|
(unless (or (process-mask? (-> proc mask) heap-shrunk) ;; already shrunk
|
|
(and (not (-> proc next-state)) ;; uninitialized
|
|
(not (-> proc state))) ;; uninitialized
|
|
)
|
|
;; shrink!
|
|
(set! (-> proc allocated-length) (the int (&- (-> proc heap-cur) (-> proc stack))))
|
|
(set! (-> proc heap-top) (&-> (-> proc stack) (-> proc allocated-length)))
|
|
|
|
;; update first gap
|
|
(when (< (the int proc) (the int (gap-location this (-> this first-gap))))
|
|
(set! (-> this first-gap) (find-gap this rec))
|
|
)
|
|
|
|
;; mark us as shrunk
|
|
(process-mask-set! (-> proc mask) heap-shrunk)
|
|
)
|
|
|
|
;; update first shrink
|
|
(when (eq? (-> this first-shrink) rec)
|
|
(set! (-> this first-shrink) (-> rec next))
|
|
)
|
|
)
|
|
)
|
|
this
|
|
)
|
|
|
|
(defmethod compact dead-pool-heap ((this dead-pool-heap) (count int))
|
|
"Do heap compaction. The count argument tells us how much work to do.
|
|
If the heap is very full we will automatically do more work than requested."
|
|
|
|
;; first we see how much memory is in use.
|
|
(let ((free (memory-free this))
|
|
(total (memory-total this))
|
|
)
|
|
(let ((perc (/ (the float free) (the float total))))
|
|
(cond
|
|
((< perc 0.1)
|
|
;; 90% full! set count very large to try to fix this and complain.
|
|
(set! count 1000)
|
|
(when (and *debug-segment* (-> *kernel-context* low-memory-message))
|
|
(format *stdcon* "~3LLow Actor Memory~%~0L")
|
|
)
|
|
)
|
|
((< perc 0.2)
|
|
;; 80% full, try 4x harder
|
|
(set! count (* count 4))
|
|
)
|
|
((< perc 0.3)
|
|
;; 70% full, try 2x harder
|
|
(set! count (* count 2))
|
|
)
|
|
)
|
|
)
|
|
)
|
|
|
|
;; update stats
|
|
(set! (-> this compact-count-targ) count)
|
|
(set! (-> this compact-count) 0)
|
|
|
|
;; and do compaction!
|
|
(countdown (ii count)
|
|
|
|
;; first try to shrink a heap.
|
|
(let ((shrink (-> this first-shrink)))
|
|
(when (not shrink)
|
|
;; not sure when this happens, but reset shrink if we need to.
|
|
(set! shrink (set! (-> this first-shrink) (-> this alive-list next)))
|
|
)
|
|
(when shrink
|
|
;; do a shrink!
|
|
(shrink-heap this (-> shrink process))
|
|
)
|
|
)
|
|
|
|
;; now find the first gap
|
|
(let ((gap (-> this first-gap)))
|
|
;; and the thing after it
|
|
(when (-> gap next)
|
|
(let ((proc (-> gap next process))
|
|
(size (gap-size this gap)))
|
|
(unless (zero? size)
|
|
;;(format #t "[kernel] Relocating process ~A by ~D.~%" proc (- size))
|
|
(when (< size 0)
|
|
;; bug!
|
|
(break)
|
|
)
|
|
|
|
;; try shrinking before relocating.
|
|
(shrink-heap this proc)
|
|
;; relocate!
|
|
(relocate proc (- size))
|
|
;; update first gap
|
|
(set! (-> this first-gap) (find-gap this gap))
|
|
;; and update stats.
|
|
(+! (-> this compact-count) 1)
|
|
)
|
|
)
|
|
)
|
|
)
|
|
)
|
|
|
|
(none)
|
|
)
|
|
|
|
(defmethod churn dead-pool-heap ((this dead-pool-heap) (count int))
|
|
"Mess with the heap"
|
|
|
|
(countdown (ii count)
|
|
(let ((rec (-> this alive-list next)))
|
|
(when rec
|
|
(when (or (eq? (-> this first-gap) rec)
|
|
(< (the int (gap-location this rec)) (the int (gap-location this (-> this first-gap))))
|
|
)
|
|
(set! (-> this first-gap) (-> rec prev)))
|
|
|
|
|
|
(when (eq? (-> this first-shrink) rec)
|
|
(set! (-> this first-shrink) (-> rec prev))
|
|
(when (not (-> this first-shrink process))
|
|
(set! (-> this first-shrink) #f))
|
|
)
|
|
|
|
(set! (-> rec prev next) (-> rec next))
|
|
(cond
|
|
((-> rec next)
|
|
(set! (-> rec next prev) (-> rec prev))
|
|
)
|
|
(else
|
|
(set! (-> this last) (-> rec prev))
|
|
)
|
|
)
|
|
|
|
(let* ((insert (-> this last))
|
|
(next (-> insert next))
|
|
)
|
|
|
|
(set! (-> insert next) rec)
|
|
(set! (-> rec next) next)
|
|
(when next
|
|
(set! (-> next prev) rec))
|
|
(set! (-> rec prev) insert)
|
|
|
|
(set! (-> this last) rec)
|
|
(set! (-> rec process) (relocate (-> rec process) (the int (&- (gap-location this insert)
|
|
(the int (&- (-> rec process) *gtype-basic-offset*))))))
|
|
)
|
|
)
|
|
)
|
|
)
|
|
|
|
(none)
|
|
)
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; Process Finding
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
;; GOAL lambdas aren't real lambdas, so you have to do this.
|
|
(define *global-search-name* (the basic #f))
|
|
(define *global-search-count* 0)
|
|
|
|
(define-extern search-process-tree (function process-tree (function process-tree object) process))
|
|
(define-extern iterate-process-tree (function process-tree (function object object) kernel-context object))
|
|
(define-extern execute-process-tree (function process-tree (function object object) kernel-context object))
|
|
|
|
(defun process-by-name (name (pool process-tree))
|
|
"Look up a process in the given pool by name"
|
|
(set! *global-search-name* (the basic name))
|
|
(search-process-tree pool (lambda ((var process)) (name= (-> var name) *global-search-name*)))
|
|
)
|
|
|
|
(defun process-not-name (name (pool process-tree))
|
|
"Look up a process with not the given name."
|
|
(set! *global-search-name* (the basic name))
|
|
(search-process-tree pool (lambda ((var process)) (not (name= (-> var name) *global-search-name*))))
|
|
)
|
|
|
|
(defun process-count ((this process-tree))
|
|
"Count number of processes in the given tree using iterate-process-tree"
|
|
(set! *global-search-count* 0)
|
|
(iterate-process-tree this
|
|
(lambda ((this process))
|
|
(+! *global-search-count* 1)
|
|
#t)
|
|
*null-kernel-context*)
|
|
*global-search-count*)
|
|
|
|
(defun kill-by-name (name (pool process-tree))
|
|
"Call deactivate on all process with the given name."
|
|
(let ((proc (the process-tree #f)))
|
|
(while (set! proc (process-by-name name pool))
|
|
(deactivate proc)
|
|
)
|
|
)
|
|
)
|
|
|
|
(defun kill-by-type (type (pool process-tree))
|
|
"Call deactivate on all processes with the given type"
|
|
(set! *global-search-name* (the basic type))
|
|
(let ((proc (the process #f)))
|
|
(while (set! proc (search-process-tree pool (lambda ((var process))
|
|
(= (the type *global-search-name*)
|
|
(-> var type)))))
|
|
(deactivate proc)
|
|
)
|
|
)
|
|
)
|
|
|
|
(defun kill-not-name (name (pool process-tree))
|
|
"Call deactivate on all processes that don't match the name"
|
|
(let ((proc (the process-tree #f)))
|
|
(while (set! proc (process-not-name name pool))
|
|
(deactivate proc)
|
|
)
|
|
)
|
|
)
|
|
|
|
(defun kill-not-type (type (pool process-tree))
|
|
"Call deactivate on all prcesses that don't match the given type"
|
|
(set! *global-search-name* (the basic type))
|
|
(let ((proc (the process-tree #f)))
|
|
(while (set! proc (search-process-tree pool (lambda ((var process))
|
|
(!= (the type *global-search-name*)
|
|
(-> var type)))))
|
|
(deactivate proc)
|
|
)
|
|
)
|
|
)
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; Process Iterating
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
(defmethod run-logic? process ((this process))
|
|
"Return if the process should be run or not.
|
|
Children of process should override this with more interesting functions"
|
|
#t)
|
|
|
|
;; the following three functions recursively iterate through process trees.
|
|
|
|
(defun iterate-process-tree ((this process-tree) (func (function object object)) (context kernel-context))
|
|
"Call func on all processes that aren't a process-tree. If func returns 'dead, stop.
|
|
The kernel-context is ignored."
|
|
(let ((ret (or (process-mask? (-> this mask) process-tree)
|
|
(func this))))
|
|
(cond
|
|
((eq? ret 'dead)
|
|
;; stop.
|
|
)
|
|
(else
|
|
;; iterate through brothers
|
|
(let ((brother (-> this child)))
|
|
(while brother
|
|
;; kinda weird, we use the brother from _before_ recursing.
|
|
(let ((old-brother (-> (-> brother) brother)))
|
|
(iterate-process-tree (-> brother) func context)
|
|
(set! brother old-brother)
|
|
)
|
|
)
|
|
)
|
|
)
|
|
)
|
|
ret
|
|
)
|
|
)
|
|
|
|
(defun execute-process-tree ((this process-tree) (func (function object object)) (context kernel-context))
|
|
"Like iterate, but also requires that prevent-from-run's mask doesn't block, and that run-logic?
|
|
is true in order to call the function."
|
|
|
|
;; check mask for tree, mask for prevent, run-logic?, then run!
|
|
(let ((ret (or (process-mask? (-> this mask) process-tree)
|
|
(not (and (or (zero? (logand (-> context prevent-from-run) (-> this mask))))
|
|
(run-logic? this)))
|
|
(func this)
|
|
)))
|
|
|
|
;; run on our children
|
|
(cond
|
|
((eq? ret 'dead) ;; if dead, don't bother checking children.
|
|
)
|
|
(else (let ((brother (-> this child)))
|
|
(while brother
|
|
(let ((temp (-> (-> brother) brother)))
|
|
(execute-process-tree (-> brother) func context)
|
|
(set! brother temp))
|
|
)
|
|
)
|
|
)
|
|
)
|
|
ret)
|
|
)
|
|
|
|
|
|
(defun search-process-tree ((this process-tree) (func (function process-tree object)))
|
|
"Find the first process which func return true on. Won't find process-tree's (by mask)"
|
|
|
|
;; reject process-tree
|
|
(unless (process-mask? (-> this mask) process-tree)
|
|
;; is this a match?
|
|
(when (func this)
|
|
(return (the process this))
|
|
)
|
|
)
|
|
|
|
;; not a match, check out children
|
|
(let ((brother (-> this child)))
|
|
(while brother
|
|
(let ((temp (-> (-> brother) brother)))
|
|
(let ((ret (search-process-tree (-> brother) func)))
|
|
(when ret
|
|
(return ret)
|
|
)
|
|
)
|
|
(set! brother temp)
|
|
)
|
|
)
|
|
)
|
|
(the process #f)
|
|
)
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; Kernel Dispatcher
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
(defmacro process-name-as-string (proc)
|
|
`(let ((proc-type (-> ,proc name type)))
|
|
(cond
|
|
((= proc-type string)
|
|
(the string (-> ,proc name))
|
|
)
|
|
((= proc-type symbol)
|
|
(symbol->string (-> ,proc name))
|
|
)
|
|
(else
|
|
"??"
|
|
)
|
|
)
|
|
)
|
|
)
|
|
|
|
(define-extern *listener-process* process)
|
|
(define-extern *active-pool* process-tree)
|
|
|
|
(defun kernel-dispatcher ()
|
|
"Run the kernel!
|
|
This is the entry point from C++ to GOAL."
|
|
|
|
;; outside of all profiler events, set a ROOT event
|
|
(profiler-instant-event "ROOT")
|
|
|
|
;; execute the listener function, if we got one.
|
|
(when *listener-function*
|
|
(+! *enable-method-set* 1) ;; allow out-of-order method definitions (slower)
|
|
(let ((result (reset-and-call (-> *listener-process* main-thread) *listener-function*))) ;; run function!
|
|
;; print result.
|
|
(if *use-old-listener-print*
|
|
(format #t "~D~%" result result result)
|
|
(format #t "~D #x~X ~F ~A~%" result result result result)
|
|
)
|
|
)
|
|
;; clear pending function
|
|
(set! *listener-function* #f)
|
|
(+! *enable-method-set* -1)
|
|
)
|
|
|
|
;; iterate over all processes, running this lambda:
|
|
(execute-process-tree
|
|
*active-pool*
|
|
(lambda ((this process))
|
|
(let ((context *kernel-context*))
|
|
(cond
|
|
((or (eq? (-> this status) 'waiting-to-run)
|
|
(eq? (-> this status) 'suspended))
|
|
|
|
;; begin event in profiler.
|
|
(profiler-start-event (process-name-as-string this))
|
|
|
|
;; set current process to us
|
|
(set! (-> context current-process) this)
|
|
|
|
;; pause tricks to prevent debug text and drawings from disappearing when pausing:
|
|
(cond
|
|
((process-mask? (-> this mask) pause)
|
|
;; we are a pausable object, so we should put our *stdcon* to the one that isn't cleared during pause:
|
|
(set! *stdcon* *stdcon1*)
|
|
;; and we should do our debug drawing to a buffer that isn't cleared during pause:
|
|
(set! *debug-draw-pauseable* #t)
|
|
)
|
|
(else
|
|
;; non-pausable, use the *stdcon* that's cleared each time
|
|
(set! *stdcon* *stdcon0*)
|
|
;; and don't debug draw to a buffer
|
|
(set! *debug-draw-pauseable* #f)
|
|
)
|
|
)
|
|
|
|
;; TRANS
|
|
;; this function should run before resuming.
|
|
(cond
|
|
((-> this trans-hook)
|
|
;; we have a trans hook defined. let's create a thread and run it. we can reuse the stack of the main-thread
|
|
;; it is safe to do this because the main-thread is currently suspended or hasn't run yet.
|
|
(let ((trans (new 'process 'cpu-thread this 'trans PROCESS_STACK_SAVE_SIZE (-> this main-thread stack-top))))
|
|
;; call the function in the thread.
|
|
(reset-and-call trans (-> this trans-hook))
|
|
|
|
(#when KERNEL_DEBUG
|
|
(when (!= (-> trans type) cpu-thread)
|
|
(format 0 "corrupted stack after trans for ~A~%" this)
|
|
)
|
|
)
|
|
;; remove the cpu-thread
|
|
(delete trans)
|
|
;; check for deadness
|
|
(when (eq? (-> this status) 'dead)
|
|
(set! (-> context current-process) #f)
|
|
(profiler-end-event)
|
|
(return 'dead) ;; tells the execute-process-tree function to skip our children.
|
|
)
|
|
)
|
|
)
|
|
)
|
|
|
|
;; MAIN CODE
|
|
(if (process-mask? (-> this mask) sleep-code)
|
|
;; we're sleeping. Move us to suspended, in case we were in waiting to run.
|
|
(set! (-> this status) 'suspended)
|
|
|
|
;; not sleeping. call resume hook. This will return once the main thread suspends again.
|
|
((-> this main-thread resume-hook) (-> this main-thread))
|
|
|
|
)
|
|
;; check for deadness
|
|
(cond
|
|
((eq? (-> this status) 'dead)
|
|
;; oops we died. return 'dead
|
|
(set! (-> context current-process) #f)
|
|
(profiler-end-event)
|
|
'dead
|
|
)
|
|
(else
|
|
;; not dead.
|
|
;; POST CODE
|
|
(cond
|
|
((-> this post-hook)
|
|
;; Note: use dram stack always. This will allow actors to use the scratchpad stack and get a speedup for trans/code
|
|
;; but still be able to do joint-animation stuff that uses the scratchpad in post.
|
|
(let ((post (new 'process 'cpu-thread this 'post PROCESS_STACK_SAVE_SIZE *kernel-dram-stack*)))
|
|
(reset-and-call post (-> this post-hook))
|
|
(delete post)
|
|
(when (eq? (-> this status) 'dead)
|
|
;; oops we died.
|
|
(set! (-> context current-process) #f)
|
|
(profiler-end-event)
|
|
(return 'dead)
|
|
)
|
|
(set! (-> this status) 'suspended)
|
|
)
|
|
)
|
|
)
|
|
(set! (-> context current-process) #f)
|
|
(profiler-end-event)
|
|
#f
|
|
)
|
|
)
|
|
|
|
)
|
|
|
|
((eq? (-> this status) 'dead)
|
|
'dead)
|
|
)
|
|
)
|
|
)
|
|
*kernel-context*
|
|
)
|
|
)
|
|
|
|
(define-extern inspect-process-tree (function process-tree int int symbol process-tree))
|
|
(defun inspect-process-tree ((this process-tree) (level int) (mask int) (detail symbol))
|
|
"Debug print a pocess-tree"
|
|
(print-tree-bitmask mask (+ 0 level))
|
|
|
|
;; print us
|
|
(cond
|
|
(detail
|
|
(format #t "__________________~%")
|
|
;; this is here, but I removed it because it prints at the wrong indent and looks weird.
|
|
;(format #t "~S~A~%" (if (zero? level) "" "+---") this)
|
|
(protect (*print-column*)
|
|
(set! *print-column* (the binteger (* level 4)))
|
|
(inspect this)
|
|
)
|
|
)
|
|
(else
|
|
(format #t "~S~A~%" (if (zero? level) "" "+---") this)
|
|
)
|
|
)
|
|
|
|
;; print our children
|
|
(let ((child (-> this child)))
|
|
(while child
|
|
(inspect-process-tree (-> child) (+ level 1) (if (not (-> (-> child) brother)) mask (logior mask (ash 1 (+ 1 level)))) detail)
|
|
(set! child (-> (-> child) brother))
|
|
)
|
|
)
|
|
this
|
|
)
|
|
|
|
(defmacro set-u128-as-u64! (dst src)
|
|
`(set! (-> (the (pointer uint64) (& ,dst)))
|
|
,src
|
|
)
|
|
)
|
|
|
|
(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
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
;; The GOAL kernel supports dynamic throw and catch.
|
|
;; 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.
|
|
Unlike the original, this only works on the first six parameters, but I think this doesn't matter."
|
|
(declare (asm-func object)
|
|
(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 (pointer uint64))
|
|
(s3 :reg r11 :type uint)
|
|
(s4 :reg r12 :type uint)
|
|
|
|
(xmm8 :reg xmm8 :class fpr)
|
|
(xmm9 :reg xmm9 :class fpr)
|
|
(xmm10 :reg xmm10 :class fpr)
|
|
(xmm11 :reg xmm11 :class fpr)
|
|
(xmm12 :reg xmm12 :class fpr)
|
|
(xmm13 :reg xmm13 :class fpr)
|
|
(xmm14 :reg xmm14 :class fpr)
|
|
(xmm15 :reg xmm15 :class fpr)
|
|
)
|
|
|
|
;; we treat the allocation as an address.
|
|
(let ((this (the catch-frame (&+ allocation *gtype-basic-offset*))))
|
|
;; setup catch frame
|
|
(set! (-> this type) type-to-make)
|
|
(set! (-> this 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 bits
|
|
(.sub temp off)
|
|
;; store it
|
|
(set! (-> this ra) (the int temp))
|
|
|
|
;; todo, do we need a stack offset here?
|
|
;; remember the stack pointer
|
|
(set! temp sp)
|
|
(.sub temp off)
|
|
(set! (-> this sp) (the int temp))
|
|
|
|
;; back up registers we care about
|
|
(.mov :color #f temp s0)
|
|
(set-u128-as-u64! (-> this rreg 0) temp)
|
|
(.mov :color #f temp s1)
|
|
(set-u128-as-u64! (-> this rreg 1) temp)
|
|
(.mov :color #f temp s2)
|
|
(set-u128-as-u64! (-> this rreg 2) temp)
|
|
(.mov :color #f temp s3)
|
|
(set-u128-as-u64! (-> this rreg 3) temp)
|
|
(.mov :color #f temp s4)
|
|
(set-u128-as-u64! (-> this rreg 4) temp)
|
|
|
|
(.mov :color #f temp xmm8)
|
|
(set! (-> this freg 0) (the-as float temp))
|
|
(.mov :color #f temp xmm9)
|
|
(set! (-> this freg 1) (the-as float temp))
|
|
(.mov :color #f temp xmm10)
|
|
(set! (-> this freg 2) (the-as float temp))
|
|
(.mov :color #f temp xmm11)
|
|
(set! (-> this freg 3) (the-as float temp))
|
|
(.mov :color #f temp xmm12)
|
|
(set! (-> this freg 4) (the-as float temp))
|
|
(.mov :color #f temp xmm13)
|
|
(set! (-> this freg 5) (the-as float temp))
|
|
(.mov :color #f temp xmm14)
|
|
(set! (-> this freg 6) (the-as float temp))
|
|
(.mov :color #f temp xmm15)
|
|
(set! (-> this freg 7) (the-as float temp))
|
|
|
|
;; push this stack frame
|
|
(set! (-> this next) (-> pp stack-frame-top))
|
|
(set! (-> pp stack-frame-top) this)
|
|
|
|
;; help coloring, it isn't smart enough to realize it's "safe" to use these registers.
|
|
(.push :color #f s3)
|
|
(.push :color #f s2)
|
|
(.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)
|
|
))
|
|
)
|
|
(.pop :color #f s2)
|
|
(.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 ((this 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))
|
|
|
|
(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)
|
|
|
|
(temp-float :reg xmm0 :class fpr)
|
|
(xmm8 :reg xmm8 :class fpr)
|
|
(xmm9 :reg xmm9 :class fpr)
|
|
(xmm10 :reg xmm10 :class fpr)
|
|
(xmm11 :reg xmm11 :class fpr)
|
|
(xmm12 :reg xmm12 :class fpr)
|
|
(xmm13 :reg xmm13 :class fpr)
|
|
(xmm14 :reg xmm14 :class fpr)
|
|
(xmm15 :reg xmm15 :class fpr)
|
|
)
|
|
|
|
;; pop everything we threw past
|
|
(set! (-> pp stack-frame-top) (-> this next))
|
|
|
|
;; restore regs we care about.
|
|
(set-u64-from-u128! temp (-> this rreg 0))
|
|
(.mov :color #f s0 temp)
|
|
(set-u64-from-u128! temp (-> this rreg 1))
|
|
(.mov :color #f s1 temp)
|
|
(set-u64-from-u128! temp (-> this rreg 2))
|
|
(.mov :color #f s2 temp)
|
|
(set-u64-from-u128! temp (-> this rreg 3))
|
|
(.mov :color #f s3 temp)
|
|
(set-u64-from-u128! temp (-> this rreg 4))
|
|
(.mov :color #f s4 temp)
|
|
|
|
(set! temp-float (-> this freg 0))
|
|
(.mov :color #f xmm8 temp-float)
|
|
(set! temp-float (-> this freg 1))
|
|
(.mov :color #f xmm9 temp-float)
|
|
(set! temp-float (-> this freg 2))
|
|
(.mov :color #f xmm10 temp-float)
|
|
(set! temp-float (-> this freg 3))
|
|
(.mov :color #f xmm11 temp-float)
|
|
(set! temp-float (-> this freg 4))
|
|
(.mov :color #f xmm12 temp-float)
|
|
(set! temp-float (-> this freg 5))
|
|
(.mov :color #f xmm13 temp-float)
|
|
(set! temp-float (-> this freg 6))
|
|
(.mov :color #f xmm14 temp-float)
|
|
(set! temp-float (-> this freg 7))
|
|
(.mov :color #f xmm15 temp-float)
|
|
|
|
;; set stack pointer
|
|
(set! sp (the uint (-> this sp)))
|
|
(.add sp off)
|
|
|
|
;; overwrite our return address
|
|
(.pop temp)
|
|
(set! temp (the uint (-> this 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))
|
|
)
|
|
(set! cur (-> cur next))
|
|
)
|
|
)
|
|
)
|
|
(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 ((this (the protect-frame (&+ allocation *gtype-basic-offset*))))
|
|
(set! (-> this type) type-to-make)
|
|
(set! (-> this name) 'protect-frame)
|
|
(set! (-> this exit) func)
|
|
|
|
(rlet ((pp :reg r13 :type process))
|
|
(set! (-> this next) (-> pp stack-frame-top))
|
|
(set! (-> pp stack-frame-top) this)
|
|
)
|
|
this
|
|
)
|
|
)
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; Tree Stuff
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
(defun previous-brother ((proc process-tree))
|
|
"Get the process p where (-> p brother) is proc. Unused"
|
|
(local-vars (parent (pointer process-tree))
|
|
(child (pointer process-tree))
|
|
)
|
|
;; look up the tree to find our parent
|
|
(set! parent (-> proc parent))
|
|
(the-as process-tree
|
|
(when parent
|
|
;; make sure we aren't the only child.
|
|
(set! child (-> parent 0 child))
|
|
(if (= child proc) (return '#f))
|
|
;; iterate, until we find the one.
|
|
(while child
|
|
(if (= (-> child 0 brother) proc) (return child))
|
|
(set! child (-> child 0 brother))
|
|
)
|
|
;; nope, didn't find it. bad tree.
|
|
'#f
|
|
)
|
|
)
|
|
)
|
|
|
|
|
|
(defun change-parent ((this process-tree) (new-parent process-tree))
|
|
"Make this a child of new-parent"
|
|
(let ((parent (-> this parent)))
|
|
;; parent is a ppointer.
|
|
|
|
;; need to remove this from its current parent
|
|
(when parent
|
|
(let ((proc (-> (-> parent) child)))
|
|
(if (eq? (ppointer->process proc) this)
|
|
;; case where we're the first child is easy!
|
|
(set! (-> (-> parent) child) (-> this brother))
|
|
;; otherwise, look through brothers to find us.
|
|
(begin
|
|
(while (not (eq? (ppointer->process (-> (-> proc) brother)) this))
|
|
(set! proc (-> (-> proc) brother))
|
|
)
|
|
;; ok, got us, splice out of list.
|
|
(set! (-> (-> proc) brother) (-> this brother))
|
|
)
|
|
)
|
|
)
|
|
)
|
|
|
|
;; add to new parent
|
|
(set! (-> this parent) (-> new-parent ppointer))
|
|
(set! (-> this brother) (-> new-parent child))
|
|
(set! (-> new-parent child) (-> this ppointer))
|
|
this
|
|
)
|
|
)
|
|
|
|
(defun change-brother ((arg0 process-tree) (arg1 process-tree))
|
|
"Unused, and wrong.
|
|
It seems like this was written when processes store process-trees, not (pointer process-tree)."
|
|
(local-vars
|
|
(v1-4 (pointer process-tree))
|
|
(a1-1 symbol)
|
|
(a2-1 (pointer process-tree))
|
|
(a3-1 (pointer process-tree))
|
|
(t0-0 (pointer process-tree))
|
|
(t1-0 (pointer process-tree))
|
|
(t1-3 (pointer process-tree))
|
|
(t1-4 (pointer process-tree))
|
|
(t1-7 (pointer process-tree))
|
|
(t1-8 (pointer process-tree))
|
|
(t1-12 (pointer process-tree))
|
|
(t1-13 (pointer process-tree))
|
|
(t1-17 (pointer process-tree))
|
|
)
|
|
(when (and arg0 (!= (-> arg0 brother) arg1) (!= arg0 arg1))
|
|
(set! a2-1 (-> arg0 parent))
|
|
(when a2-1
|
|
(set! t0-0 (-> a2-1 0 child))
|
|
(set! a3-1 '#f)
|
|
(set! v1-4 '#f)
|
|
(set! t1-0 t0-0)
|
|
(when (= (if t1-0 (-> t1-0 0 self)) arg0)
|
|
(set! a3-1 a2-1)
|
|
(set! t1-3 a3-1)
|
|
)
|
|
(set! t1-4 t0-0)
|
|
(when (= (if t1-4 (-> t1-4 0 self)) arg1)
|
|
(set! v1-4 a2-1)
|
|
(set! t1-7 v1-4)
|
|
)
|
|
(while (and (-> t0-0 0 brother) (or (not a3-1) (not v1-4)))
|
|
(set! t1-8 t0-0)
|
|
(when (= (-> (if t1-8 (-> t1-8 0 self)) brother) arg1)
|
|
(set! v1-4 t0-0)
|
|
(set! t1-12 v1-4)
|
|
)
|
|
(set! t1-13 t0-0)
|
|
(when (= (-> (if t1-13 (-> t1-13 0 self)) brother) arg0)
|
|
(set! a3-1 t0-0)
|
|
(set! t1-17 a3-1)
|
|
)
|
|
(set! t0-0 (-> t0-0 0 brother))
|
|
)
|
|
(if (or (not a3-1) (not v1-4))
|
|
(return 0)
|
|
(if (= a3-1 a2-1)
|
|
(set! (-> a3-1 4) (the process-tree (-> arg0 brother))) ;; wrong
|
|
(set! (-> a3-1 3) (the process-tree (-> arg0 brother))) ;; wrong
|
|
)
|
|
)
|
|
(cond
|
|
((= v1-4 a2-1)
|
|
(set! (-> arg0 brother) (the (pointer process-tree) (-> v1-4 4))) ;; wrong
|
|
(set! (-> v1-4 4) (the process-tree (-> arg0 ppointer))) ;; wrong
|
|
)
|
|
(else
|
|
(set! (-> arg0 brother) (the (pointer process-tree) (-> v1-4 3)))
|
|
(set! (-> v1-4 3) (the process-tree (-> arg0 ppointer)))
|
|
)
|
|
)
|
|
)
|
|
)
|
|
(the-as process-tree arg0)
|
|
)
|
|
|
|
(defun change-to-last-brother ((arg0 process-tree))
|
|
(local-vars
|
|
(v1-4 (pointer process-tree))
|
|
(v1-8 symbol)
|
|
(a1-0 (pointer process-tree))
|
|
(a1-5 symbol)
|
|
(a1-9 symbol)
|
|
)
|
|
(when (and (-> arg0 brother) (-> arg0 parent))
|
|
(set! a1-0 (-> arg0 parent))
|
|
(set! v1-4 (-> a1-0 0 child))
|
|
(cond
|
|
((= (-> v1-4 0) arg0)
|
|
(set! (-> a1-0 0 child) (-> arg0 brother)))
|
|
(else
|
|
(while (!= (-> v1-4 0 brother 0) arg0)
|
|
(nop!)
|
|
(nop!)
|
|
(nop!)
|
|
(nop!)
|
|
(set! v1-4 (-> v1-4 0 brother))
|
|
)
|
|
(set! (-> v1-4 0 brother) (-> arg0 brother))
|
|
)
|
|
)
|
|
(while (-> v1-4 0 brother)
|
|
(nop!)
|
|
(nop!)
|
|
(nop!)
|
|
(nop!)
|
|
(set! v1-4 (-> v1-4 0 brother))
|
|
)
|
|
(set! (-> v1-4 0 brother) (-> arg0 ppointer))
|
|
(set! (-> arg0 brother) '#f)
|
|
)
|
|
arg0
|
|
)
|
|
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; Process Control
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
(defmethod activate process ((this 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."
|
|
|
|
;; if we got the scratchpad stack, move to the fake scratchpad.
|
|
(#when PC_PORT
|
|
(when (= stack-top *scratch-memory-top*)
|
|
(set! stack-top (&+ *fake-scratchpad-stack* (* 32 1024)))
|
|
)
|
|
)
|
|
;; inherit mask (minus stuff like sleep)
|
|
(set! (-> this mask) (logand (-> dest mask) PROCESS_CLEAR_MASK))
|
|
(set! (-> this status) 'ready)
|
|
|
|
;; get a unique pid.
|
|
(let ((pid (-> *kernel-context* next-pid)))
|
|
(set! (-> this pid) pid)
|
|
(set! (-> *kernel-context* next-pid) (+ 1 pid)))
|
|
(set! (-> this top-thread) #f)
|
|
(set! (-> this main-thread) #f)
|
|
(set! (-> this name) name)
|
|
|
|
;; set up our heap. Note that we apply an offset of heap-base to leave room for fields of the actual type.
|
|
;; unclear why we can't just use the size of our type... but I guess this gives you the option
|
|
;; to put some other stuff in between the fields and the heap.
|
|
(set! (-> this heap-base) (set! (-> this heap-cur) (&+ (-> this stack) (-> this type heap-base))))
|
|
(set! (-> this stack-frame-top) #f)
|
|
;; heaps should be 0 initialized.
|
|
(mem-set32! (-> this stack) (the int (/ (-> this type heap-base) 4)) 0)
|
|
|
|
(set! (-> this trans-hook) #f)
|
|
(set! (-> this post-hook) #f)
|
|
(set! (-> this event-hook) #f)
|
|
(set! (-> this state) #f)
|
|
(set! (-> this next-state) #f)
|
|
|
|
;; inherit entity if our parent is another process
|
|
(if (process-mask? (-> dest mask) process-tree)
|
|
(set! (-> this entity) #f)
|
|
(set! (-> this entity) (-> (the process dest) entity))
|
|
)
|
|
|
|
;; reset all connections
|
|
(set! (-> this connection-list next1) #f)
|
|
(set! (-> this connection-list prev1) #f)
|
|
|
|
;; allocate the main thread (sets everything up.)
|
|
(let ((thread (new 'process 'cpu-thread this 'code PROCESS_STACK_SAVE_SIZE stack-top)))
|
|
(set! (-> this main-thread) thread)
|
|
)
|
|
|
|
(change-parent this dest)
|
|
)
|
|
|
|
(defun run-function-in-process ((this 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-no-clear '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 this)
|
|
;; 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.
|
|
(case (-> 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)
|
|
)
|
|
(('initialize-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, so if the main thread returns, the
|
|
process is properly cleaned up by deactivate."
|
|
(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)
|
|
(a4 :reg r8 :type uint)
|
|
(a5 :reg r9 :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)
|
|
|
|
(.add a4 a4)
|
|
(.add a5 a5)
|
|
)
|
|
|
|
)
|
|
|
|
|
|
(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 ((this process-tree))
|
|
"Can't deactivate a process-tree"
|
|
(none)
|
|
)
|
|
|
|
;; The defstate macro isn't defined yet, so we do it manually.
|
|
(define dead-state
|
|
(the (state process) (new 'static 'state
|
|
:name 'dead-state
|
|
:next #f
|
|
:exit #f
|
|
:code #f
|
|
:trans #f
|
|
:post #f
|
|
:enter #f
|
|
:event #f)))
|
|
|
|
(set! (-> dead-state code) (the (function object :behavior process) nothing))
|
|
|
|
|
|
;; this is not yet defined.
|
|
(define entity-deactivate-handler (the (function process entity-actor none) nothing))
|
|
|
|
(define-extern process-disconnect (function process int))
|
|
|
|
(defmethod deactivate process ((this 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? (-> this status) 'dead)
|
|
;; set our state to a dead-state that does nothing.
|
|
(set! (-> this next-state) dead-state)
|
|
|
|
;; call entity handler if we're from an entity.
|
|
(when (-> this entity)
|
|
(entity-deactivate-handler this (-> this entity))
|
|
)
|
|
|
|
;; clean up stack frames the process is in.
|
|
;; first, set pp so the cleanup code thinks its running in the right process.
|
|
;; we might be getting deactivated from another process.
|
|
(rlet ((pp :reg r13 :type process))
|
|
(let ((old-pp pp))
|
|
(set! pp this)
|
|
(let ((cur (-> pp stack-frame-top)))
|
|
;; loop over frames...
|
|
(while cur
|
|
(case (-> cur type)
|
|
((protect-frame state)
|
|
;; we're a state or protect-frame, we can exit.
|
|
((-> (the-as protect-frame cur) exit))
|
|
)
|
|
)
|
|
(set! cur (-> cur next))
|
|
)
|
|
)
|
|
(set! pp old-pp)
|
|
)
|
|
)
|
|
|
|
;; remove our connections.
|
|
;; hack - if this isn't defined yet, don't try it.
|
|
(if (!= 0 (the uint process-disconnect))
|
|
(process-disconnect this)
|
|
)
|
|
|
|
;; kill our child and their brothers
|
|
(let ((bro (-> this child)))
|
|
(while bro
|
|
(let ((temp (-> (-> bro) brother)))
|
|
(deactivate (-> bro))
|
|
(set! bro temp)
|
|
)
|
|
)
|
|
)
|
|
|
|
;; return ourself to the pool
|
|
(return-process (-> this pool) this)
|
|
(set! (-> this state) #f)
|
|
(set! (-> this next-state) #f)
|
|
(set! (-> this entity) #f)
|
|
(set! (-> this 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? this (-> *kernel-context* current-process))
|
|
;; go straight to dead.
|
|
(set! (-> this status) 'dead)
|
|
;; and return (with no deactivate)
|
|
;; TODO: replace with abandon.
|
|
(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? (-> this status) 'initialize)
|
|
(set! (-> this status) 'dead)
|
|
;; throw back to the place where we started initializing.
|
|
(throw 'initialize #f)
|
|
)
|
|
)
|
|
(set! (-> this status) 'dead)
|
|
)
|
|
(none)
|
|
)
|
|
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; Process Globals
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
;; the listener process is used to run functions from the REPL.
|
|
(let ((this (define *listener-process* (new 'global 'process 'listener 2048))))
|
|
(set! (-> this status) 'ready)
|
|
(set! (-> this pid) 1)
|
|
(set! (-> this main-thread) (new 'process 'cpu-thread this 'main 256 *kernel-dram-stack*))
|
|
)
|
|
|
|
;; a dummy process for process records that don't have a process to point to.
|
|
;; it has a PID of 0, which matches nothing so the handle->process will return #f if the
|
|
;; ppointer points to this
|
|
(define *null-process* (new 'global 'process 'listener 16))
|
|
|
|
;; do we have visibility data? level.gc will set this once we do, and once that happens, we expect
|
|
;; actors to fit in the non-debug heap.
|
|
(define *vis-boot* #f)
|
|
|
|
;; a few pools of fixed size processes that are shared.
|
|
(define *16k-dead-pool* (new 'global 'dead-pool 1 (* 16 1024) '*16k-dead-pool*))
|
|
(define *8k-dead-pool* (new 'global 'dead-pool 1 (* 8 1024) '*8k-dead-pool*))
|
|
(define *4k-dead-pool* (new 'global 'dead-pool 4 (* 4 1024) '*4k-dead-pool*))
|
|
|
|
;; some very important process pools
|
|
(define *target-dead-pool* (new 'global 'dead-pool 1 (* 48 1024) '*target-dead-pool*))
|
|
(define *camera-dead-pool* (new 'global 'dead-pool 7 (* 4 1024) '*camera-dead-pool*))
|
|
(define *camera-master-dead-pool* (new 'global 'dead-pool 1 (* 8 1024) '*camera-master-dead-pool*))
|
|
|
|
;; used if other pools run out of space in debug mode
|
|
(if *debug-segment*
|
|
(define *debug-dead-pool* (new 'debug 'dead-pool-heap '*debug-dead-pool* 768 (* 1024 1024)))
|
|
)
|
|
|
|
;; variable sized actor pool (most actors go here)
|
|
(define *nk-dead-pool* (new 'global 'dead-pool-heap '*nk-dead-pool* (* PROCESS_HEAP_MULT 768) PROCESS_HEAP_SIZE))
|
|
|
|
;; use the nk-dead-pool in most places
|
|
(define *default-dead-pool* (the dead-pool *nk-dead-pool*))
|
|
(define *pickup-dead-pool* (the dead-pool *nk-dead-pool*))
|
|
|
|
;; list of all dead pools held by the kernel.
|
|
(define *dead-pool-list* '(*4k-dead-pool* *8k-dead-pool* *16k-dead-pool* *nk-dead-pool* *target-dead-pool* *camera-dead-pool* *camera-master-dead-pool*))
|
|
|
|
;; main active pool. All active processes are under here. This is what the kernel-dispatcher looks at.
|
|
(define *active-pool* (new 'global 'process-tree 'active-pool))
|
|
|
|
;; create pools within *active-pool*
|
|
|
|
;; the *display-pool* will contain the display process which does rendering, vsync, and many per-frame updates.
|
|
(change-parent (define *display-pool* (new 'global 'process-tree 'display-pool)) *active-pool*)
|
|
|
|
;; the *camera-pool* will contain all cameras
|
|
(change-parent (define *camera-pool* (new 'global 'process-tree 'camera-pool)) *active-pool*)
|
|
;; all cameras should pause in menus
|
|
(set! (-> *camera-pool* mask) (process-mask pause menu progress camera process-tree))
|
|
|
|
;; the *target-pool* will contain jak
|
|
(change-parent (define *target-pool* (new 'global 'process-tree 'target-pool)) *active-pool*)
|
|
;; pause in menus
|
|
(set! (-> *target-pool* mask) (process-mask pause menu progress process-tree))
|
|
|
|
;; the *entity-pool* contains all processes spawned from entities.
|
|
(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))
|
|
|
|
;; the *default-pool* is used for processes that don't fall into the others.
|
|
(change-parent (define *default-pool* (new 'global 'process-tree 'default-pool)) *active-pool*)
|
|
(set! (-> *default-pool* mask) (process-mask pause menu progress process-tree))
|
|
|