jak-project/goal_src/kernel/gcommon.gc
water111 abcd444a3b
Add deftype (#48)
* initial deftype implementation

* fix library setup for windows

* implement deftype

* fix memory bug

* fix formatting
2020-09-17 21:47:52 -04:00

248 lines
9.1 KiB
Common Lisp

;-*-Lisp-*-
(in-package goal)
;; name: gcommon.gc
;; name in dgo: gcommon
;; dgos: KERNEL
;; gcommon is the first file compiled and loaded.
;; it's expected that this function will mostly be hand-decompiled
;; The "identity" returns its input unchanged. It uses the special GOAL "object"
;; type, which can basically be anything, so this will work on integers, floats,
;; strings, structures, arrays, etc. The only things which doesn't work with "object"
;; is a 128-bit integer. The upper 64-bits of the integer will usually be lost.
(defun identity ((x object))
;; there is an optional "docstring" that can go at the beginning of a function
"Function which returns its input. The first function of the game!"
;; the last thing in the function body is the return value. This is like "return x;" in C
;; the return type of the function is figured out automatically by the compiler
;; you don't have to specify it manually.
x
)
(defun 1/ ((x float))
"Reciprocal floating point"
;; this function computes 1.0 / x. GOAL allows strange function names like "1/".
;; Declaring this an inline function is like a C inline function, however code is
;; still generated so it can be used a function object. GOAL inline functions have type
;; checking, so they are preferable to macros when possible, to get better error messages.
(declare (inline))
;; the division form will pick the math type (float, int) based on the type of the first
;; argument. In this case, "1." is a floating point constant, so this becomes a floating point division.
(/ 1. x)
)
(defun + ((x int) (y int))
"Compute the sum of two integers"
;; this wraps the compiler's built-in handling of "add two integers" in a GOAL function.
;; now "+" can be used as a function object, but is limited to adding two integers when used like this.
;; The compiler is smart enough to not use this function unless "+" is being used as a function object.
;; ex: (+ a b c), (+ a b) ; won't use this function, uses built-in addition
;; (set-combination-function! my-thing +) ; + becomes a function pointer in this case
(+ x y)
)
(defun - ((x int) (y int))
"Compute the difference of two integers"
(- x y)
)
(defun * ((x int) (y int))
"Compute the product of two integers"
;; TODO - verify that this matches the PS2 exactly.
;; Uses mult (three operand form) in MIPS
(* x y)
)
(defun / ((x int) (y int))
"Compute the quotient of two integers"
;; TODO - verify this matches the PS2 exactly
(/ x y)
)
(defun ash ((value integer) (shift-amount integer))
"Arithmetic shift value by shift-amount.
A positive shift-amount will shift to the left and a negative will shift to the right.
"
;; currently the compiler does not support "ash", so this function is also used to implement "ash".
;; in the future, the compiler should be able to use constant propagation to turn constant shifts
;; into x86 constant shifts when possible (which are faster). The GOAL compiler seems to do this.
;; The original implementation was inline assembly, to take advantage of branch delay slots:
;; (or v1 a0 r0) ;; likely inserted by register coloring, not entirely needed
;; (bgezl a1 end) ;; branch to function end if positive shift (left)...
;; (dsllv v0 v1 a1) ;; do left shift in delay slot
;;
;; (dsubu a0 r0 a1) ;; negative shift amount for right shift
;; (dsrav v0 v1 a0) ;; do right shift
;; (label end)
(declare (inline))
(if (> shift-amount 0)
;; these correspond to x86-64 variable shift instructions.
;; the exact behavior of GOAL shifts (signed/unsigned) are unknown so for now shifts must
;; be manually specified.
(shlv value shift-amount)
(sarv value (- shift-amount))
)
)
(defun mod ((a integer) (b integer))
"Compute mod. It does what you expect for positive numbers. For negative numbers, nobody knows what to expect.
This is a 32-bit operation. It uses an idiv on x86 and gets the remainder."
;; The original implementation is div, mfhi
;; todo - verify this is exactly the same as the PS2.
(mod a b)
)
(defun rem ((a integer) (b integer))
"Compute remainder (32-bit). It is identical to mod. It uses a idiv and gets the remainder"
;; The original implementation is div, mfhi
;; todo - verify this is exactly the same as the PS2.
(mod a b)
)
(defun abs ((a int))
"Take the absolute value of an integer"
;; short function, good candidate for inlining
(declare (inline))
;; The original implementation was inline assembly, to take advantage of branch delay slots:
;; (or v0 a0 r0) ;; move input to output unchanged, for positive case
;; (bltzl v0 end) ;; if negative, execute the branch delay slot below...
;; (dsubu v0 r0 v0) ;; negate
;; (label end)
(if (> a 0) ;; condition is "a > 0"
a ;; true case, return a
(- a) ;; false case, return -a. (- a) is like (- 0 a)
)
)
(defun min ((a integer) (b integer))
"Compute minimum."
;; The original implementation was inline assembly, to take advantage of branch delay slots:
;; (or v0 a0 r0) ;; move first arg to output (case of second arg being min)
;; (or v1 a1 r0) ;; move second arg to v1 (likely strange coloring)
;; (slt a0 v0 v1) ;; compare args
;; (movz v0 v1 a0) ;; conditional move the second arg to v0 if it's the minimum
(declare (inline))
(if (> a b) b a)
)
(defun max ((a integer) (b integer))
"Compute maximum."
(declare (inline))
(if (> a b) a b)
)
(defun logior ((a integer) (b integer))
"Compute the bitwise inclusive-or"
(logior a b)
)
(defun logand ((a integer) (b integer))
"Compute the bitwise and"
(logand a b)
)
(defun lognor ((a integer) (b integer))
"Compute not or."
;; Note - MIPS has a 'nor' instruction, but x86 doesn't.
;; the GOAL x86 compiler therefore doesn't have a nor operation,
;; so lognor is implemented by this inline function instead.
(declare (inline))
(lognot (logior a b))
)
(defun logxor ((a integer) (b integer))
"Compute the logical exclusive-or"
(logxor a b)
)
(defun lognot ((a integer))
"Compute the bitwise not"
(lognot a)
)
(defun false-func ()
"Return false"
;; In GOAL, #f is false. It's a symbol. Each symbol exists as an object, and each symbol has a value
;; The value of the false symbol #f is the false symbol #f.
;; To get the symbol, instead of its value, we use quote. Writing 'x is equivalent to (quote x)
'#f
)
(defun true-func ()
"Return true"
;; GOAL consideres anything that's not #f to be true. But there's also an explicit true symbol.
'#t
)
;; The C Kernel implements the format function and creates a trampoline function in the GOAL heap which jumps to
;; format. (In OpenGOAL, there's actually two trampoline functions, to make the 8 arguments all work.)
;; For some reason, the C Kernel names this trampoline function _format. We need to set the value of format
;; _format in order for format to work.
;; I suspect this was to let us define (yet another) function here which set up C-style var args (supported from C Kernel)
;; or 128-bit arguments (unimplemented in C Kernel), but both of these were never finished.
(define format _format)
;; TODO - vec4s
;; The "boxed float" type bfloat is just a float wrapped in a basic (structure type that has runtime type information)
;; define a type called "bfloat" which is a child of "basic".
;; "basic" is just a type with structures and runtime type information
(deftype bfloat (basic)
;; the field list.
;; there is a single field named data, of type float.
;; the :offset-assert makes sure that OpenGOAL's type layout places the field at the given offset.
;; if not, it creates a compiler error. This is used to make sure we exactly copy the game's memory layout,
;; as we can get the exact offset of fields from the disassembly
((data float :offset-assert 4))
;; declare methods. If you are overriding a parent method, you don't have to declare it, but you can if you want
;; The number after the return type is the method ID, that can be checked against the disassembly to make sure
;; the type and method hierarchy is correct. If OpenGOAL's method table layout doesn't match, it will create
;; compiler error.
(:methods (print (_type_) _type_ 2) ;; we will override print later on
(inspect (_type_) _type_ 3) ;; this is a parent method we won't override. It's fine to put it here anyway.
)
;; Note that the special type "_type_" can be used in methods to indicate "the type of the object method is called on".
;; this is used for 2 things:
;; 1. Child who overrides it can use their own type as an argument, rather than a less specific parent type.
;; 2. Caller who calls an overriden method and knows it at compile time can know a return type more specifically.
;; make sure the size of the type is correct (this is stored in the type structure, so we can check it)
:size-assert 8
;; make sure method count is correct (again, stored in the type structure)
:method-count-assert 9
;; flags passed to the new_type function in the runtime.
:flag-assert #x900000008
)
;; todo print bfloat