Implement gkernel: Part 2 (#155)

* update

* small fixes

* deactivate

* simple kernel test
This commit is contained in:
water111 2020-12-08 21:41:36 -05:00 committed by GitHub
parent d86964985a
commit e05f3ceefc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 844 additions and 193 deletions

View File

@ -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)

View File

@ -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.
- Added `stack` allocated and constructed basic/structure types.
- Fixed a bug where functions with exactly 8 parameters created a compiler error.

View File

@ -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"

View File

@ -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")

View File

@ -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")
)

View File

@ -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)

View File

@ -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)
)
)

View File

@ -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

View File

@ -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<std::string> Compiler::run_test_from_file(const std::string& source_code) {
try {
if (!connect_to_target()) {

View File

@ -35,6 +35,7 @@ class Compiler {
std::vector<std::string> run_test_from_string(const std::string& src,
const std::string& obj_name = "*listener*");
std::vector<std::string> 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; }

View File

@ -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.",

View File

@ -304,6 +304,16 @@ std::vector<std::string> 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.

View File

@ -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<std::string> stop_recording_messages();
bool is_connected() const;
void send_reset(bool shutdown);

View File

@ -895,4 +895,4 @@ bool run_allocator(RegAllocCache* cache, const AllocationInput& in, int debug_tr
}
}
return true;
}
}

View File

@ -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

View File

@ -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})

View File

@ -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
)

View File

@ -0,0 +1,89 @@
#include <thread>
#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);
}