Compiler Cleanup and Documentation (#54)

* start cleanup

* fix typos

* fix syntax highlighting in doc

* lots of documentation updates

* clean and add tests

* more documentation and more error messages

* more document and try building kernel differently
This commit is contained in:
water111 2020-09-24 17:19:23 -04:00 committed by GitHub
parent 27b865c0df
commit 2d11e44eaf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
41 changed files with 2334 additions and 261 deletions

View file

@ -9,6 +9,7 @@ TypeSystem::TypeSystem() {
// the "none" and "_type_" types are included by default.
add_type("none", std::make_unique<NullType>("none"));
add_type("_type_", std::make_unique<NullType>("_type_"));
add_type("_varargs_", std::make_unique<NullType>("_varargs_"));
}
/*!
@ -41,7 +42,7 @@ Type* TypeSystem::add_type(const std::string& name, std::unique_ptr<Type> type)
// newly defined!
// none/object get to skip these checks because they are roots.
if (name != "object" && name != "none" && name != "_type_") {
if (name != "object" && name != "none" && name != "_type_" && name != "_varargs_") {
if (m_forward_declared_types.find(type->get_parent()) != m_forward_declared_types.end()) {
fmt::print("[TypeSystem] Type {} has incompletely defined parent {}\n", type->get_name(),
type->get_parent());
@ -234,8 +235,9 @@ Type* TypeSystem::lookup_type(const TypeSpec& ts) const {
MethodInfo TypeSystem::add_method(const std::string& type_name,
const std::string& method_name,
const TypeSpec& ts) {
return add_method(lookup_type(make_typespec(type_name)), method_name, ts);
const TypeSpec& ts,
bool allow_new_method) {
return add_method(lookup_type(make_typespec(type_name)), method_name, ts, allow_new_method);
}
/*!
@ -247,7 +249,10 @@ MethodInfo TypeSystem::add_method(const std::string& type_name,
* is overriding the "new" method - the TypeSystem will track that because overridden new methods
* may have different arguments.
*/
MethodInfo TypeSystem::add_method(Type* type, const std::string& method_name, const TypeSpec& ts) {
MethodInfo TypeSystem::add_method(Type* type,
const std::string& method_name,
const TypeSpec& ts,
bool allow_new_method) {
if (method_name == "new") {
return add_new_method(type, ts);
}
@ -285,6 +290,11 @@ MethodInfo TypeSystem::add_method(Type* type, const std::string& method_name, co
return existing_info;
} else {
if (!allow_new_method) {
fmt::print("[TypeSystem] Attempted to add method {} to type {} but it was not declared.\n",
method_name, type->get_name());
throw std::runtime_error("illegal method definition");
}
// add a new method!
return type->add_method({get_next_method_id(type), method_name, ts, type->get_name()});
}
@ -299,7 +309,7 @@ MethodInfo TypeSystem::add_new_method(Type* type, const TypeSpec& ts) {
MethodInfo existing;
if (type->get_my_new_method(&existing)) {
// it exists!
if (existing.type != ts) {
if (!existing.type.is_compatible_child_method(ts, type->get_name())) {
fmt::print(
"[TypeSystem] The new method of {} was originally defined as {}, but has been redefined "
"as {}\n",
@ -487,7 +497,7 @@ int TypeSystem::add_field_to_type(StructureType* type,
// we need to compute the offset ourself!
offset = align(type->get_size_in_memory(), field_alignment);
} else {
int aligned_offset = align(type->get_size_in_memory(), field_alignment);
int aligned_offset = align(offset, field_alignment);
if (offset != aligned_offset) {
fmt::print(
"[TypeSystem] Tried to overwrite offset of field to be {}, but it is not aligned "
@ -584,7 +594,7 @@ void TypeSystem::add_builtin_types() {
// the type. Dynamic structures use new-dynamic-structure, which is used exactly once ever.
add_method(structure_type, "new", make_function_typespec({"symbol", "type"}, "structure"));
// structure_type is a field-less StructureType, so we have to do this to match the runtime.
structure_type->override_size_in_memory(4);
// structure_type->override_size_in_memory(4);
// BASIC
// we intentionally don't inherit from structure because structure's size is weird.

View file

@ -54,8 +54,12 @@ class TypeSystem {
MethodInfo add_method(const std::string& type_name,
const std::string& method_name,
const TypeSpec& ts);
MethodInfo add_method(Type* type, const std::string& method_name, const TypeSpec& ts);
const TypeSpec& ts,
bool allow_new_method = true);
MethodInfo add_method(Type* type,
const std::string& method_name,
const TypeSpec& ts,
bool allow_new_method = true);
MethodInfo add_new_method(Type* type, const TypeSpec& ts);
MethodInfo lookup_method(const std::string& type_name, const std::string& method_name);
MethodInfo lookup_new_method(const std::string& type_name);

277
doc/decompilation_notes.md Normal file
View file

@ -0,0 +1,277 @@
# GOAL Operations
## `div.s`
Suspected source
```
(/ 1.0 x)
```
where `x` is in a GPR:
```
lwc1 f0, L345(fp) ;; first argument prepared first?
mtc1 f1, a0 ;; second argument prepared second?
div.s f0, f0, f1
```
Sequence
- Compile first
- First to FPR
- Compile second
- Second to FPR
## `daddu`
Used for `int` and `uint` addition.
Two element form:
```
daddu v0, a0, a1
```
is `(+ a0 a1)` - the order in the opcode matches the order in the expression.
## `daddiu` to get a symbol
```
daddiu v0, s7, #t
```
Note for `#t`: `#t` is linked when the code literally has a `#t` in it. Other cases are currently unknown.
## `dsubu`
Used for `int` and `uint` subtraction.
## `mult3` (EE `mult`)
Used for `int` multiplication.
Like `daddu` for opcode ordering:
```
mult3 v0, a0, a1
```
is `(* a0 a1)`.
## `div`
Used for `int` division.
```
div a0, a1
mflo v0
```
is `(/ a0 a1)`.
and also for `int` mod
```
div a0, a1
mfhi v0
```
## `or` used to get the value of false
```
or v0, s7, r0
```
## `or` used as a bitwise or
```
or v0, a0, a1
```
is `(logior a0 a1)`
## `and` used as a bitwise and
```
and v0, a0, a1
```
is `(logand a0 a1)`.
```
(logand #xfffffff0 (+ (ash (-> thing field) 2) 43))
```
is
```
ld v1, L346(fp) ;; first arg to the and
lhu a0, 14(a0) ;; second arg evaluation...
dsll a0, a0, 2
daddiu a0, a0, 43
and v0, v1, a0 ;; and result, first, second
```
## `nor` used as a bitwise nor
```
nor v0, a0, a1
```
is `(lognor a0 a1)`
## `xor` used as a bitwise xor
```
xor v0, a0, a1
```
is `(logxor a0 a1)`
## `nor` used as a logical not
```
nor v0, a0, r0
```
is `(lognot a0)`
# Common "Idioms"
## `ash`
Variable shift (`ash`) is an inline function
```
or v1, a0, r0
bgezl a1, L306
dsllv v0, v1, a1
dsubu a0, r0, a1
dsrav v0, v1, a0
L306:
```
## `abs` of integer
```
or v0, a0, r0
bltzl v0, L302
dsubu v0, r0, v0
L302:
```
## `min` of integers
```
or v0, a0, r0
or v1, a1, r0
slt a0, v0, v1
movz v0, v1, a0
```
## `max` of integers
```
or v0, a0, r0
or v1, a1, r0
slt a0, v0, v1
movn v0, v1, a0
```
# Others
## Integer constants that are large
A constant of `0xfffffff0` is loaded with `ld`
## Access value of symbol
Seems to always use `lw`?
# Control Flow Info
## Begin-like forms flush everything always, immediately after compiling
Example in `vector` with flushing:
```
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; .function vector3s+!
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
L8:
daddiu sp, sp, -16
sd fp, 8(sp)
or fp, t9, r0
daddiu v1, fp, L109 ;; string: "Add 2 vectors3."
lwc1 f0, 0(a1)
lwc1 f1, 0(a2)
add.s f0, f0, f1
swc1 f0, 0(a0)
lwc1 f0, 4(a1)
lwc1 f1, 4(a2)
add.s f0, f0, f1
swc1 f0, 4(a0)
lwc1 f0, 8(a1)
lwc1 f1, 8(a2)
add.s f0, f0, f1
swc1 f0, 8(a0)
or v0, a0, r0
ld fp, 8(sp)
jr ra
daddiu sp, sp, 16
```
The `daddiu v1, fp, L109` loads a `string` into the `v1` register which is never used, immediately after the prologue. This will only happen if the value is flushed. This is very likely a documentation comment that accidentally got included as a string constant. It's unused, so there was likely no consumer of the string that did the `flush` - it was done by the top level evaluation.
```
(defun vector3s+! (stuff)
"Add 2 vectors3." ;; oops, a string constant instead of a comment.
... ; rest of the function
)
```
## Return-From evaluates to 0 bug
We would expect the value of `(return-from #f x)` to be nothing, as there's no possible way to use it. However, GOAL seems to have a small bug where `(return-from #f x)` always attempts to evaluate to 0. This would be like implementing it as:
```lisp
(set! retrun-reg return-value)
(goto end-of-function)
0 ;; oops
```
by accident.
Example in GOAL:
```
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; .function basic-type? (in gcommon)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
L285:
lwu v1, -4(a0)
lw a0, object(s7)
L286:
bne v1, a1, L287
or a2, s7, r0
; (return-from #f #t) starts here
daddiu v1, s7, #t ; compile/flush the #t
or v0, v1, r0 ; move to return register
beq r0, r0, L288 ; branch to end
sll r0, r0, 0 ; branch delay slot (usual filler)
or v1, r0, r0 ; unreachable loading of 0 into a register.
L287:
lwu v1, 4(v1)
bne v1, a0, L286
sll r0, r0, 0
or v0, s7, r0
L288:
jr ra
daddu sp, sp, r0
sll r0, r0, 0
sll r0, r0, 0
```
## Unused else case returning false in cond
From `delete!` in gcommon.
```
beq a2, a0, L222 ; (if (= a2 a0) only-one-case)
or a0, s7, r0 ; a0 is unused return value of if
lw a0, 2(a2) ; (set! (cdr v1) (cdr a2)), will evaluate to (cdr a2) which is stored in a0
sw a0, 2(v1)
L222: ; a0 = #f or a0 = (cdr a2) depending on branch taken, but it's totally unused!
or v0, a1, r0 ; return a1
jr ra
daddu sp, sp, r0
```
Also note that all cases stored their result in `a0`, even though nothing uses the result.
## Function Calls evaluate arguments in order:
```
lw t9, format(s7) ;; head of function
daddiu a0, s7, #t ;; first arg
daddiu a1, fp, L344 ;; second arg
sllv a2, gp, r0 ;; third arg
dsra32 a3, gp, 0 ;; fourth arg
pcpyud v1, gp, r0
sllv t0, v1, r0 ;; fifth arg
pcpyud v1, gp, r0
dsra32 t1, v1, 0 ;; sixth arg
por t2, gp, r0 ;; seventh arg
jalr ra, t9
sll v0, ra, 0
```
also an example of lack of common subexpression elimination on the `pcpyud v1, gp, r0`s.
### A second example with register type conversions:
```
lw t9, format(s7) ;; function
daddiu a0, s7, #t ;; first arg
daddiu a1, fp, L343 ;; second arg
lwc1 f0, 0(gp) ;; compile and flush third arg
mfc1 a2, f0 ;; move to correct reg type
jalr ra, t9
sll v0, ra, 0
```

1001
doc/goal_doc.md Normal file

File diff suppressed because it is too large Load diff

View file

@ -70,16 +70,24 @@ set(RUNTIME_SOURCE
overlord/stream.cpp)
# the runtime should be built without any static/dynamic libraries.
add_executable(gk ${RUNTIME_SOURCE} main.cpp)
#add_executable(gk ${RUNTIME_SOURCE} main.cpp)
# we also build a runtime library for testing. This version is likely unable to call GOAL code correctly, but
# can be used to test other things.
add_library(runtime ${RUNTIME_SOURCE})
add_executable(gk main.cpp)
IF (WIN32)
# set stuff for windows
target_link_libraries(gk cross_sockets mman common_util)
target_link_libraries(runtime mman cross_sockets common_util)
target_link_libraries(gk cross_sockets mman common_util runtime)
ELSE()
# set stuff for other systems
target_link_libraries(gk cross_sockets pthread common_util)
target_link_libraries(runtime pthread cross_sockets common_util)
target_link_libraries(gk cross_sockets pthread common_util runtime)
ENDIF()

View file

@ -143,15 +143,15 @@ void KernelCheckAndDispatch() {
call_goal(Ptr<Function>(kernel_dispatcher->value), 0, 0, 0, s7.offset, g_ee_main_mem);
} else {
if (ListenerFunction->value != s7.offset) {
fprintf(stderr, "Running Listener Function:\n");
auto cptr = Ptr<u8>(ListenerFunction->value).c();
for (int i = 0; i < 40; i++) {
fprintf(stderr, "%x ", cptr[i]);
}
fprintf(stderr, "\n");
// fprintf(stderr, "Running Listener Function:\n");
// auto cptr = Ptr<u8>(ListenerFunction->value).c();
// for (int i = 0; i < 40; i++) {
// fprintf(stderr, "%x ", cptr[i]);
// }
// fprintf(stderr, "\n");
auto result =
call_goal(Ptr<Function>(ListenerFunction->value), 0, 0, 0, s7.offset, g_ee_main_mem);
fprintf(stderr, "result of listener function: %lld\n", result);
// fprintf(stderr, "result of listener function: %lld\n", result);
#ifdef __linux__
cprintf("%ld\n", result);
#else

View file

@ -149,7 +149,6 @@ void ProcessListenerMessage(Ptr<char> msg) {
// this setup allows listener function execution to clean up after itself.
ListenerFunction->value =
link_and_exec(buffer, "*listener*", 0, kdebugheap, LINK_FLAG_FORCE_DEBUG).offset;
fprintf(stderr, "ListenerFunction is now 0x%x\n", ListenerFunction->value);
return; // don't ack yet, this will happen after the function runs.
} break;
default:

View file

@ -887,8 +887,6 @@ u64 method_set(u32 type_, u32 method_id, u32 method) {
if (method_id > 127)
printf("[METHOD SET ERROR] tried to set method %d\n", method_id);
// printf("METHOD SET id %d to 0x%x type 0x%x!\n", method_id, method, type_);
auto existing_method = type->get_method(method_id).offset;
if (method == 1) {

View file

@ -194,8 +194,8 @@ void Deci2Server::run() {
}
auto* hdr = (Deci2Header*)(buffer);
fprintf(stderr, "[DECI2] Got message:\n");
fprintf(stderr, " %d %d 0x%x %c -> %c\n", hdr->len, hdr->rsvd, hdr->proto, hdr->src, hdr->dst);
fprintf(stderr, "[DECI2] Got message: %d %d 0x%x %c -> %c\n", hdr->len, hdr->rsvd, hdr->proto,
hdr->src, hdr->dst);
hdr->rsvd = got;

View file

@ -92,6 +92,12 @@
)
)
(defmacro shutdown-target ()
`(begin
(reset-target :shutdown)
)
)
;;;;;;;;;;;;;;;;;;;
;; GOAL Syntax
@ -366,3 +372,34 @@
`(the ,(current-method-type) ((-> object method-table 0) allocation type-to-make ,@sz))
)
)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; TEST STUFF
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defmacro expect-eq (a b &key (name "unknown"))
`(if (!= ,a ,b)
(format #t "Test Failed On Test ~D: ~A~%" *test-count* ,name)
(+! *test-count* 1)
)
)
(defmacro expect-true (a)
`(expect-eq ,a #t)
)
(defmacro expect-false (a)
`(expect-eq ,a #f)
)
(defmacro start-test (test-name)
`(begin
(define *test-name* ,test-name)
(define *test-count* 0)
)
)
(defmacro finish-test ()
`(format #t "Test ~A: ~D Passes~%" *test-name* *test-count*)
)

View file

@ -71,7 +71,7 @@
(define-extern load (function string kheap object))
(define-extern loado (function string kheap object))
(define-extern unload (function string none))
(define-extern _format function)
(define-extern _format (function _varargs_ object))
(define-extern malloc (function kheap int pointer))
(define-extern kmalloc (function kheap int int string))
(define-extern new-dynamic-structure (function kheap type int structure))

View file

@ -301,9 +301,9 @@
(object-type object))
(until (eq? (set! basics-type (-> basics-type parent)) object-type)
(if (eq? basics-type input-type)
;; return-from #f will return from the function with the value of #t
(return-from #f #t)
)
;; return-from #f will return from the function with the value of #t
(return-from #f #t)
)
)
)
#f ;; didn't find it, return false
@ -315,8 +315,8 @@
;; it's not clear why a might be zero?
;; perhaps if the type system is not yet initialized fully for the type?
(if (or (eq? a b) (zero? a))
(return-from #f #t)
)
(return-from #f #t)
)
(set! a (-> a parent))
)
#f
@ -330,19 +330,19 @@
(let* ((child-method (-> the-type method-table method-id))
(parent-method child-method)
)
;; keep looking until we find a different parent method
(until (not (eq? parent-method child-method))
;; at the top of the type tree.
(if (eq? the-type object)
(return-from #f nothing)
)
(return-from #f nothing)
)
(set! the-type (-> the-type parent))
(set! parent-method (-> the-type method-table method-id))
(if (eq? 0 (the int parent-method))
(return-from #f nothing)
)
(return-from #f nothing)
)
)
parent-method
)
@ -362,17 +362,17 @@
(defmethod length pair ((obj pair))
"Get the number of elements in a proper list"
(if (eq? obj '())
(return-from #f 0)
)
(return-from #f 0)
)
(let ((lst (cdr obj))
(len 1))
(while (and (not (eq? lst '()))
(pair? lst)
)
(+1! len)
(set! lst (cdr lst))
)
(+1! len)
(set! lst (cdr lst))
)
len)
)
@ -386,8 +386,8 @@
(defun last ((obj object))
"Get the last pair in a list."
(while (not (eq? (cdr obj) '()))
(set! obj (cdr obj))
)
(set! obj (cdr obj))
)
obj
)
@ -396,42 +396,42 @@
if not, return #f."
(while (and (not (eq? lst '()))
(not (eq? (car lst) obj)))
(set! lst (cdr lst))
)
(if (eq? lst '())
#f
lst
(set! lst (cdr lst))
)
(if (eq? lst '())
#f
lst
)
)
(define-extern name= (function basic basic symbol))
(defun nmember ((obj basic) (lst object))
"If obj is a member of the list, return the pair containing obj as its car.
If not, return #f. Use name= (see gstring.gc) to check equality."
(while (and (not (eq? lst '()))
(not (name= (the basic (car lst)) obj))
)
(set! lst (cdr lst))
)
(if (eq? lst '())
#f
lst
)
If not, return #f. Use name= (see gstring.gc) to check equality."
(while (and (not (eq? lst '()))
(not (name= (the basic (car lst)) obj))
)
(set! lst (cdr lst))
)
(if (eq? lst '())
#f
lst
)
)
(defun assoc ((item object) (alst object))
"Get a pair with car of item from the association list (list of pairs) alst."
(while (and (not (null? alst))
(not (eq? (caar alst) item)))
(set! alst (cdr alst))
)
(if (not (null? alst))
(car alst)
#f
(set! alst (cdr alst))
)
(if (not (null? alst))
(car alst)
#f
)
)
(defun assoce ((item object) (alst object))
@ -440,12 +440,12 @@
(not (eq? (caar alst) item))
(not (eq? (caar alst) 'else))
)
(set! alst (cdr alst))
)
(if (not (null? alst))
(car alst)
#f
(set! alst (cdr alst))
)
(if (not (null? alst))
(car alst)
#f
)
)
;; todo
@ -455,20 +455,20 @@
(defun append! ((front object) (back object))
"Append back to front."
(if (null? front)
(return-from #f back)
)
(return-from #f back)
)
(let ((lst front))
;; seek to the end of front
(while (not (null? (cdr lst)))
(set! lst (cdr lst))
)
(set! lst (cdr lst))
)
;; this check seems not needed
(if (not (null? lst))
(set! (cdr lst) back)
)
(set! (cdr lst) back)
)
front
)
)
@ -477,21 +477,21 @@
"Delete the first occurance of item from a list and return the list.
Does nothing if the item isn't in the list."
(if (eq? (car lst) item)
(return-from #f (cdr lst))
)
(return-from #f (cdr lst))
)
(let ((iter (cdr lst))
(rep lst))
(while (and (not (null? iter))
(not (eq? (car iter) item)))
(set! rep iter)
(set! iter (cdr iter))
)
(if (not (null? iter))
(set! (cdr rep) (cdr iter))
(set! rep iter)
(set! iter (cdr iter))
)
(if (not (null? iter))
(set! (cdr rep) (cdr iter))
)
)
(the pair lst)
)
@ -500,20 +500,20 @@
"Like delete, but will delete if (car item-from-list) is equal to item. Useful for deleting from association list by key."
;(format #t "call to delete car: ~A ~A~%" item lst)
(if (eq? (caar lst) item)
(return-from #f (cdr lst))
)
(return-from #f (cdr lst))
)
(let ((rep lst)
(iter (cdr lst)))
(while (and (not (null? iter))
(not (eq? (caar iter) item)))
(set! rep iter)
(set! iter (cdr iter))
)
(if (not (null? iter))
(set! (cdr rep) (cdr iter))
(set! rep iter)
(set! iter (cdr iter))
)
(if (not (null? iter))
(set! (cdr rep) (cdr iter))
)
)
lst
)
@ -523,7 +523,7 @@
(cons kv (delete-car! (car kv) alst))
)
(defun sort ((lst object) (compare function))
(defun sort ((lst object) (compare (function object object object)))
"Sort the given list in place. Uses the given comparison function. The comparison function can
either return #t/#f or an integer, in which case the sign of the integer determines lt/gt."
;; in each iteration, we count how many changes we make. Once we make no changes, the list is sorted.
@ -573,11 +573,12 @@
((length int32 :offset-assert 4)
(allocated-length int32 :offset-assert 8)
(data uint8 :dynamic)
;; ??
)
(:methods (new (symbol type int) _type_ 0) ;; we will override print later on. This is optional to include
)
)
(defmethod new inline-array-class ((allocation symbol) (type-to-make type) (cnt integer))
(defmethod new inline-array-class ((allocation symbol) (type-to-make type) (cnt int))
"Create a new inline-array. Sets the length, allocated-length to cnt. Uses the mysterious heap-base field
of the type-to-make to determine the element size"
(let* ((sz (+ (-> type-to-make size) (* (-> type-to-make heap-base) cnt)))
@ -620,11 +621,24 @@
)
(while (< i size)
(set! (-> (the (pointer uint8) d) 0) (-> (the (pointer uint8) s) 0))
(&+! d 1)
(&+! s 1)
(+1! i)
)
(set! (-> (the (pointer uint8) d) 0) (-> (the (pointer uint8) s) 0))
(&+! d 1)
(&+! s 1)
(+1! i)
)
)
dst
)
(defun mem-set32! ((dst pointer) (value int) (n int))
"Memset a 32-bit value n times. Total memory filled is 4 * n bytes."
(let ((p (the pointer dst))
(i 0))
(while (< i n)
(set! (-> (the (pointer int32) p) 0) value)
(&+! p 4)
(+1! i)
)
)
dst
)

View file

@ -0,0 +1,49 @@
(start-test "approx-pi")
(defun test-approx-pi ((res integer))
(let ((rad (* res res))
(count 0))
(dotimes (x res)
(dotimes (y res)
(if (> rad (+ (* x x) (* y y)))
(+! count 1)
)
)
)
(* 4.0 (/ (the float count) (the float rad)))
)
)
(let ((approx-pi (test-approx-pi 1000)))
(expect-true (> approx-pi 3.14))
(expect-true (< approx-pi 3.15))
)
(defun test-approx-pi-float ((res float))
(let* ((rad (the float (* res res)))
(count (the float 0))
(x (the float 0))
(y (the float 0))
(scale (/ 1.0 rad)))
(while (< x res)
(set! y 0.0)
(while (< y res)
; (format #t "tapf ~f ~f ~f~%" x y res)
(if (> rad (+ (* x x) (* y y)))
(+! count scale)
)
(+! y 1.0)
)
(+! x 1.0)
)
(* 4.0 count)
)
)
(let ((approx-pi (test-approx-pi-float 500.0)))
(expect-true (> approx-pi 3.14))
(expect-true (< approx-pi 3.15))
)
(finish-test)

View file

@ -0,0 +1,2 @@
(format #t "~A~%" (the binteger -17))
0

View file

@ -0,0 +1,64 @@
(start-test "dynamic-type")
(deftype test-dynamic-type (basic)
(
(pad0 int16 :offset 4)
(allocated-length int32 :offset 8)
(data int32 :dynamic :offset 12)
(over1 int32 :offset 12)
(over2 int32 :offset 16)
)
)
(defmethod new test-dynamic-type ((allocation symbol) (type-to-make type) (cnt integer))
;"Create a new inline-array. Sets the length, allocated-length to cnt. Uses the mysterious heap-base field
;of the type-to-make to determine the element size"
(let* ((sz (+ (-> type-to-make size) (* 4 cnt)))
(new-object (object-new sz)))
;;(format 0 "create sz ~d at #x~X~%" sz new-object)
(unless (zero? new-object)
(set! (-> new-object allocated-length) cnt)
)
new-object
)
)
(defmethod length test-dynamic-type ((obj test-dynamic-type))
;"Get the length of it"
(-> obj allocated-length)
)
(defmethod asize-of test-dynamic-type ((obj test-dynamic-type))
;"Get the size in memory of it"
(the int (+ (-> obj type size)
(* (-> obj allocated-length) 4)
))
)
(define test-dynamic-obj
(the test-dynamic-type ((-> test-dynamic-type method-table 0) 'global test-dynamic-type 40)))
; ;(define test-dynamic-obj (new 'global 'test-dynamic-type 40))
; ;(inspect test-dynamic-obj)
(set! (-> test-dynamic-obj data 0) 12)
(set! (-> test-dynamic-obj data 1) 20)
; ;(inspect test-dynamic-obj)
; ; (format #t "should be same (~d ~d) (~d ~d)~%" (-> test-dynamic-obj data 0) (-> test-dynamic-obj over1)
; ; (-> test-dynamic-obj data 1) (-> test-dynamic-obj over2))
(expect-true (= (-> test-dynamic-obj data 0) (-> test-dynamic-obj over1)))
(expect-true (= (-> test-dynamic-obj data 1) (-> test-dynamic-obj over2)))
(set! (-> test-dynamic-obj pad0) 0)
(expect-true (= (-> test-dynamic-obj type) test-dynamic-type))
(expect-true (= (asize-of test-dynamic-obj) 180))
(finish-test)

View file

@ -1,4 +1,3 @@
(define-extern _format function)
(define format _format)
(format #t "test ~D ~D ~D ~D ~D ~D~%" 1 2 3 4 5 6)

View file

@ -0,0 +1,18 @@
(let* ((base-addr #x6000000)
(word-cnt 23)
(base (the (pointer int32) base-addr))
(foo (mem-set32! base #x0 (+ 1 word-cnt)))
(last-byte (the (pointer uint8) (+ base-addr 3 (* 4 (- word-cnt 1)))))
(dst (mem-set32! base #x0badbeef word-cnt))
)
(if (!= dst base)
(format #t "test failed, bad base returned!~%")
)
(if (!= 0 (-> last-byte 1))
(format #t "set too many bytes!~%")
)
(-> last-byte 0)
)

View file

@ -1,4 +1,3 @@
(define-extern _format function)
(define format _format)
(defun float-testing-function-2 ((x float) (y float))

View file

@ -0,0 +1,20 @@
(start-test "number-comparison")
(expect-true (= 1.2 1.2))
(expect-true (= 1 1))
(expect-false (!= 1.2 1.2))
(expect-false (= 1 2))
(expect-true (= 1 1.2))
(expect-false (= 1.2 1))
(expect-true (> 2 1))
(expect-false (< 2 1))
(expect-true (> 3.2 3))
(expect-true (= 1 (the integer 1.2)))
(expect-true (= (the float 1) (the integer 1.2)))
(expect-true (= 0.6 (- 1.2 0.6)))
(expect-true (= 0.6 (- 1.0 0.2 0.2)))
(expect-true (= 0.6 (- -0.6)))
(finish-test)

View file

@ -1,4 +1,3 @@
(define-extern _format function)
(define format _format)
;(db)

View file

@ -0,0 +1,8 @@
(start-test "test-of-test")
(expect-eq 1 2)
(expect-eq 1 3 :name "test")
(expect-eq 5 5)
(finish-test)

View file

@ -0,0 +1,74 @@
(start-test "test-type-arrays")
(defmacro new-type-hack (type-name)
`(the ,type-name ((-> ,type-name method-table 0) 'global ,type-name))
)
(deftype basic-type (basic)
(
(flt float)
(dec int32)
)
)
(deftype struct-type (structure)
(
(flt float :offset-assert 0)
(dec int32 :offset-assert 4)
(flt-overlay float :offset 4)
(dec-overlay int32 :offset 4)
)
)
(let ((obj-test (new-type-hack struct-type)))
;(inspect obj-test)
(set! (-> obj-test dec) #x00f0f0f0)
(set! (-> obj-test flt) 123.456)
(expect-eq (-> obj-test dec-overlay) #x00f0f0f0)
;((-> struct-type methods 3) obj-test)
)
;;;;;; TEST 000
;; create a new typ000 (no value, no array, no inline)
(deftype type000 (basic)
(
(pad int32)
(x basic-type)
)
)
(let ((obj000 (new-type-hack type000))
(bo (new-type-hack basic-type))
)
(set! (-> obj000 x) bo)
(expect-eq (-> obj000 x type) basic-type)
)
;;;;;; TEST 001
;; create a new type001 (no value, no array, yes inline)
(deftype type001 (basic)
((pad int32)
(x struct-type)
)
)
(let ((obj001 (new-type-hack type001))
(sub-obj (new-type-hack struct-type)))
;;(inspect obj001)
(set! (-> obj001 x) sub-obj)
(set! (-> obj001 x dec) 12)
(expect-eq 12 (-> sub-obj dec))
;(inspect obj001)
;((-> struct-type methods 3) (-> obj001 x))
)
;; todo - test the other 6 (or maybe 4?) types?
(finish-test)

View file

@ -82,7 +82,7 @@ class Compiler {
RegVal* function,
const std::vector<RegVal*>& args,
Env* env,
std::string method_type_name = "");
const std::string& method_type_name = "");
TypeSystem m_ts;
std::unique_ptr<GlobalEnv> m_global_env = nullptr;

View file

@ -9,6 +9,8 @@ CompilerSettings::CompilerSettings() {
m_settings["disable-math-const-prop"].kind = SettingKind::BOOL;
m_settings["disable-math-const-prop"].boolp = &disable_math_const_prop;
link(print_timing, "print-timing");
}
void CompilerSettings::set(const std::string& name, const goos::Object& value) {
@ -21,4 +23,9 @@ void CompilerSettings::set(const std::string& name, const goos::Object& value) {
if (kv->second.boolp) {
*kv->second.boolp = !(value.is_symbol() && value.as_symbol()->name == "#f");
}
}
void CompilerSettings::link(bool& val, const std::string& name) {
m_settings[name].kind = SettingKind::BOOL;
m_settings[name].boolp = &val;
}

View file

@ -14,10 +14,12 @@ class CompilerSettings {
bool debug_print_regalloc = false;
bool disable_math_const_prop = false;
bool emit_move_after_return = true;
bool print_timing = false;
void set(const std::string& name, const goos::Object& value);
private:
void link(bool& val, const std::string& name);
enum class SettingKind { BOOL, INVALID };
struct SettingsEntry {

View file

@ -287,6 +287,7 @@ std::string IR_FunctionCall::print() {
RegAllocInstr IR_FunctionCall::to_rai() {
RegAllocInstr rai;
rai.read.push_back(m_func->ireg());
rai.write.push_back(m_func->ireg()); // todo, can we avoid this?
rai.write.push_back(m_ret->ireg());
for (auto& arg : m_args) {
rai.read.push_back(arg->ireg());
@ -299,8 +300,6 @@ RegAllocInstr IR_FunctionCall::to_rai() {
}
}
// todo, clobber call reg?
return rai;
}
@ -326,6 +325,7 @@ void IR_FunctionCall::do_codegen(emitter::ObjectGenerator* gen,
auto freg = get_reg(m_func, allocs, irec);
gen->add_instr(IGen::add_gpr64_gpr64(freg, emitter::gRegInfo.get_offset_reg()), irec);
gen->add_instr(IGen::call_r64(freg), irec);
// todo, can we do a sub to undo the modification to the register? does that actually work?
}
/////////////////////
@ -508,6 +508,10 @@ std::string IR_FloatMath::print() {
return fmt::format("divss {}, {}", m_dest->print(), m_arg->print());
case FloatMathKind::MUL_SS:
return fmt::format("mulss {}, {}", m_dest->print(), m_arg->print());
case FloatMathKind::ADD_SS:
return fmt::format("addss {}, {}", m_dest->print(), m_arg->print());
case FloatMathKind::SUB_SS:
return fmt::format("subss {}, {}", m_dest->print(), m_arg->print());
default:
throw std::runtime_error("Unsupported FloatMathKind");
}
@ -533,6 +537,14 @@ void IR_FloatMath::do_codegen(emitter::ObjectGenerator* gen,
gen->add_instr(
IGen::mulss_xmm_xmm(get_reg(m_dest, allocs, irec), get_reg(m_arg, allocs, irec)), irec);
break;
case FloatMathKind::ADD_SS:
gen->add_instr(
IGen::addss_xmm_xmm(get_reg(m_dest, allocs, irec), get_reg(m_arg, allocs, irec)), irec);
break;
case FloatMathKind::SUB_SS:
gen->add_instr(
IGen::subss_xmm_xmm(get_reg(m_dest, allocs, irec), get_reg(m_arg, allocs, irec)), irec);
break;
default:
assert(false);
}
@ -664,7 +676,9 @@ void IR_ConditionalBranch::do_codegen(emitter::ObjectGenerator* gen,
}
if (condition.is_float) {
assert(false); // for now
gen->add_instr(
IGen::cmp_flt_flt(get_reg(condition.a, allocs, irec), get_reg(condition.b, allocs, irec)),
irec);
} else {
gen->add_instr(IGen::cmp_gpr64_gpr64(get_reg(condition.a, allocs, irec),
get_reg(condition.b, allocs, irec)),
@ -790,3 +804,51 @@ void IR_FunctionStart::do_codegen(emitter::ObjectGenerator* gen,
(void)allocs;
(void)irec;
}
///////////////////////
// FloatToInt
///////////////////////
IR_FloatToInt::IR_FloatToInt(const RegVal* dest, const RegVal* src) : m_dest(dest), m_src(src) {}
std::string IR_FloatToInt::print() {
return fmt::format("f2i {}, {}", m_dest->print(), m_src->print());
}
RegAllocInstr IR_FloatToInt::to_rai() {
RegAllocInstr rai;
rai.read.push_back(m_src->ireg());
rai.write.push_back(m_dest->ireg());
return rai;
}
void IR_FloatToInt::do_codegen(emitter::ObjectGenerator* gen,
const AllocationResult& allocs,
emitter::IR_Record irec) {
gen->add_instr(IGen::float_to_int32(get_reg(m_dest, allocs, irec), get_reg(m_src, allocs, irec)),
irec);
}
///////////////////////
// IntToFloat
///////////////////////
IR_IntToFloat::IR_IntToFloat(const RegVal* dest, const RegVal* src) : m_dest(dest), m_src(src) {}
std::string IR_IntToFloat::print() {
return fmt::format("i2f {}, {}", m_dest->print(), m_src->print());
}
RegAllocInstr IR_IntToFloat::to_rai() {
RegAllocInstr rai;
rai.read.push_back(m_src->ireg());
rai.write.push_back(m_dest->ireg());
return rai;
}
void IR_IntToFloat::do_codegen(emitter::ObjectGenerator* gen,
const AllocationResult& allocs,
emitter::IR_Record irec) {
gen->add_instr(IGen::int32_to_float(get_reg(m_dest, allocs, irec), get_reg(m_src, allocs, irec)),
irec);
}

View file

@ -223,7 +223,7 @@ class IR_IntegerMath : public IR {
RegVal* m_arg;
};
enum class FloatMathKind { DIV_SS, MUL_SS };
enum class FloatMathKind { DIV_SS, MUL_SS, ADD_SS, SUB_SS };
class IR_FloatMath : public IR {
public:
@ -325,4 +325,32 @@ class IR_FunctionStart : public IR {
std::vector<RegVal*> m_args;
};
class IR_FloatToInt : public IR {
public:
IR_FloatToInt(const RegVal* dest, const RegVal* src);
std::string print() override;
RegAllocInstr to_rai() override;
void do_codegen(emitter::ObjectGenerator* gen,
const AllocationResult& allocs,
emitter::IR_Record irec) override;
private:
const RegVal* m_dest = nullptr;
const RegVal* m_src = nullptr;
};
class IR_IntToFloat : public IR {
public:
IR_IntToFloat(const RegVal* dest, const RegVal* src);
std::string print() override;
RegAllocInstr to_rai() override;
void do_codegen(emitter::ObjectGenerator* gen,
const AllocationResult& allocs,
emitter::IR_Record irec) override;
private:
const RegVal* m_dest = nullptr;
const RegVal* m_src = nullptr;
};
#endif // JAK_IR_H

View file

@ -87,6 +87,11 @@ RegVal* LambdaVal::to_reg(Env* fe) {
return re;
}
RegVal* InlinedLambdaVal::to_reg(Env* fe) {
throw std::runtime_error("Cannot put InlinedLambdaVal in a register.");
return lv->to_reg(fe);
}
RegVal* FloatConstantVal::to_reg(Env* fe) {
auto re = fe->make_xmm(coerce_to_reg_type(m_ts));
fe->emit(std::make_unique<IR_StaticVarLoad>(re, m_value));

View file

@ -44,9 +44,12 @@ class Val {
const TypeSpec& type() const { return m_ts; }
void set_type(TypeSpec ts) { m_ts = std::move(ts); }
bool settable() const { return m_is_settable; }
void mark_as_settable() { m_is_settable = true; }
protected:
TypeSpec m_ts;
bool m_is_settable = false;
};
/*!
@ -117,6 +120,14 @@ class LambdaVal : public Val {
RegVal* to_reg(Env* fe) override;
};
class InlinedLambdaVal : public Val {
public:
explicit InlinedLambdaVal(TypeSpec ts, LambdaVal* _lv) : Val(std::move(ts)), lv(_lv) {}
std::string print() const override { return "inline-lambda-" + lv->lambda.debug_name; }
LambdaVal* lv = nullptr;
RegVal* to_reg(Env* fe) override;
};
class StaticVal : public Val {
public:
StaticVal(StaticObject* _obj, TypeSpec _ts) : Val(std::move(_ts)), obj(_obj) {}

View file

@ -1,6 +1,6 @@
/*!
* @file Atoms.cpp
* Compiler implementation for atoms - things which aren't compound forms.
* Top-level compilation forms for each of the GOOS object types.
*/
#include "goalc/compiler/Compiler.h"
@ -20,41 +20,38 @@ static const std::unordered_map<
// {".jmp", &Compiler::compile_asm},
// {".sub", &Compiler::compile_asm},
// {".ret-reg", &Compiler::compile_asm},
//
// // BLOCK FORMS
// BLOCK FORMS
{"top-level", &Compiler::compile_top_level},
{"begin", &Compiler::compile_begin},
{"block", &Compiler::compile_block},
{"return-from", &Compiler::compile_return_from},
{"label", &Compiler::compile_label},
{"goto", &Compiler::compile_goto},
//
// // COMPILER CONTROL
// COMPILER CONTROL
{"gs", &Compiler::compile_gs},
{":exit", &Compiler::compile_exit},
{"asm-file", &Compiler::compile_asm_file},
{"listen-to-target", &Compiler::compile_listen_to_target},
{"reset-target", &Compiler::compile_reset_target},
{":status", &Compiler::compile_poke},
// {"test", &Compiler::compile_test},
{"in-package", &Compiler::compile_in_package},
//
// // CONDITIONAL COMPILATION
// CONDITIONAL COMPILATION
{"#cond", &Compiler::compile_gscond},
{"defglobalconstant", &Compiler::compile_defglobalconstant},
{"seval", &Compiler::compile_seval},
//
// // CONTROL FLOW
// CONTROL FLOW
{"cond", &Compiler::compile_cond},
{"when-goto", &Compiler::compile_when_goto},
//
// // DEFINITION
// DEFINITION
{"define", &Compiler::compile_define},
{"define-extern", &Compiler::compile_define_extern},
{"set!", &Compiler::compile_set},
// {"defun-extern", &Compiler::compile_defun_extern},
// {"declare-method", &Compiler::compile_declare_method},
//
// TYPE
{"deftype", &Compiler::compile_deftype},
{"defmethod", &Compiler::compile_defmethod},
@ -68,30 +65,24 @@ static const std::unordered_map<
{"car", &Compiler::compile_car},
{"cdr", &Compiler::compile_cdr},
{"method", &Compiler::compile_method},
//
//
// // LAMBDA
// LAMBDA
{"lambda", &Compiler::compile_lambda},
{"declare", &Compiler::compile_declare},
{"inline", &Compiler::compile_inline},
// {"with-inline", &Compiler::compile_with_inline},
// {"rlet", &Compiler::compile_rlet},
// {"get-ra-ptr", &Compiler::compile_get_ra_ptr},
//
//
//
// // MACRO
//
// MACRO
{"quote", &Compiler::compile_quote},
{"mlet", &Compiler::compile_mlet},
// {"defconstant", &Compiler::compile_defconstant},
//
// // OBJECT
// {"current-method-type", &Compiler::compile_current_method_type},
//
//
// // IT IS MATH
// OBJECT
// {"current-method-type", &Compiler::compile_current_method_type},
// MATH
{"+", &Compiler::compile_add},
{"-", &Compiler::compile_sub},
{"*", &Compiler::compile_mul},
@ -115,16 +106,12 @@ static const std::unordered_map<
{">=", &Compiler::compile_condition_as_bool},
{"<", &Compiler::compile_condition_as_bool},
{">", &Compiler::compile_condition_as_bool},
//
// // BUILDER (build-dgo/build-cgo?)
{"build-dgos", &Compiler::compile_build_dgo},
//
// // UTIL
{"set-config!", &Compiler::compile_set_config},
//
// // temporary testing hacks...
// {"send-test", &Compiler::compile_send_test_data},
// BUILDER (build-dgo/build-cgo?)
{"build-dgos", &Compiler::compile_build_dgo},
// UTIL
{"set-config!", &Compiler::compile_set_config},
};
/*!
@ -148,6 +135,12 @@ Val* Compiler::compile(const goos::Object& code, Env* env) {
return get_none();
}
/*!
* Compile a pair/list.
* Can be a compiler form, function call (possibly inlined), method call, immediate application of a
* lambda, or a goos macro.
* TODO - enums.
*/
Val* Compiler::compile_pair(const goos::Object& code, Env* env) {
auto pair = code.as_pair();
auto head = pair->car;
@ -161,41 +154,80 @@ Val* Compiler::compile_pair(const goos::Object& code, Env* env) {
return ((*this).*(kv_gfs->second))(code, rest, env);
}
// next try as a macro
goos::Object macro_obj;
if (try_getting_macro_from_goos(head, &macro_obj)) {
return compile_goos_macro(code, macro_obj, rest, env);
}
// todo enum
// try as an enum (not yet implemented)
}
// todo function or method call
// if none of the above cases worked, then treat it like a function/method call.
return compile_function_or_method_call(code, env);
// throw_compile_error(code, "Unrecognized symbol at head of form");
// return nullptr;
}
/*!
* Compile an integer constant. Returns an IntegerConstantVal and emits no code.
* These integer constants do not generate static data and are stored directly in the code
* which is generated with to_gpr.
* The type is always int.
*/
Val* Compiler::compile_integer(const goos::Object& code, Env* env) {
assert(code.is_int());
return compile_integer(code.integer_obj.value, env);
}
/*!
* Compile an integer constant. Returns an IntegerConstantVal and emits no code.
* These integer constants do not generate static data and are stored directly in the code
* which is generated with to_gpr.
* The type is always int.
*/
Val* Compiler::compile_integer(s64 value, Env* env) {
auto fe = get_parent_env_of_type<FunctionEnv>(env);
return fe->alloc_val<IntegerConstantVal>(m_ts.make_typespec("int"), value);
}
/*!
* Get a SymbolVal representing a GOAL symbol object.
*/
SymbolVal* Compiler::compile_get_sym_obj(const std::string& name, Env* env) {
auto fe = get_parent_env_of_type<FunctionEnv>(env);
return fe->alloc_val<SymbolVal>(name, m_ts.make_typespec("symbol"));
}
/*!
* Get a SymbolValueVal representing the value of a GOAL symbol.
* Will throw a compilation error if the symbol wasn't previously defined.
* TODO - determine sign extension behavior when loading symbol values.
*/
Val* Compiler::compile_get_symbol_value(const std::string& name, Env* env) {
auto existing_symbol = m_symbol_types.find(name);
if (existing_symbol == m_symbol_types.end()) {
throw std::runtime_error("The symbol " + name + " was not defined");
}
auto ts = existing_symbol->second;
auto sext = m_ts.lookup_type(ts)->get_load_signed();
auto fe = get_parent_env_of_type<FunctionEnv>(env);
auto sym = fe->alloc_val<SymbolVal>(name, m_ts.make_typespec("symbol"));
auto re = fe->alloc_val<SymbolValueVal>(sym, ts, sext);
return re;
}
/*!
* Compile a symbol. Can get mlet macro symbols, local variables, constants, or symbols.
*/
Val* Compiler::compile_symbol(const goos::Object& form, Env* env) {
auto name = symbol_string(form);
// special case to get "nothing", used as a return value when nothing should be returned.
if (name == "none") {
return get_none();
}
// see if the symbol is defined in any enclosing symbol macro envs (mlet's).
auto mlet_env = get_parent_env_of_type<SymbolMacroEnv>(env);
while (mlet_env) {
auto mlkv = mlet_env->macros.find(form.as_symbol());
@ -205,6 +237,7 @@ Val* Compiler::compile_symbol(const goos::Object& form, Env* env) {
mlet_env = get_parent_env_of_type<SymbolMacroEnv>(mlet_env->parent());
}
// see if it's a local variable
auto lexical = env->lexical_lookup(form);
if (lexical) {
return lexical;
@ -213,6 +246,7 @@ Val* Compiler::compile_symbol(const goos::Object& form, Env* env) {
auto global_constant = m_global_constants.find(form.as_symbol());
auto existing_symbol = m_symbol_types.find(form.as_symbol()->name);
// see if it's a constant
if (global_constant != m_global_constants.end()) {
// check there is no symbol with the same name
if (existing_symbol != m_symbol_types.end()) {
@ -225,28 +259,21 @@ Val* Compiler::compile_symbol(const goos::Object& form, Env* env) {
return compile_error_guard(global_constant->second, env);
}
// none of those, so get a global symbol.
return compile_get_symbol_value(name, env);
}
Val* Compiler::compile_get_symbol_value(const std::string& name, Env* env) {
auto existing_symbol = m_symbol_types.find(name);
if (existing_symbol == m_symbol_types.end()) {
// assert(false);
throw std::runtime_error("The symbol " + name + " was not defined");
}
auto ts = existing_symbol->second;
auto sext = m_ts.lookup_type(ts)->get_load_signed();
auto fe = get_parent_env_of_type<FunctionEnv>(env);
auto sym = fe->alloc_val<SymbolVal>(name, m_ts.make_typespec("symbol"));
auto re = fe->alloc_val<SymbolValueVal>(sym, ts, sext);
return re;
}
/*!
* Compile a string constant. The constant is placed in the same segment as the parent function.
*/
Val* Compiler::compile_string(const goos::Object& form, Env* env) {
return compile_string(form.as_string()->data, env, MAIN_SEGMENT);
return compile_string(form.as_string()->data, env,
get_parent_env_of_type<FunctionEnv>(env)->segment);
}
/*!
* Compile a string constant and place it in the given segment.
*/
Val* Compiler::compile_string(const std::string& str, Env* env, int seg) {
auto obj = std::make_unique<StaticString>(str, seg);
auto fe = get_parent_env_of_type<FunctionEnv>(env);
@ -256,11 +283,22 @@ Val* Compiler::compile_string(const std::string& str, Env* env, int seg) {
return result;
}
/*!
* Compile a floating point constant and place it in the same segment as the containing function.
* Unlike integers, all floating point constants are stored separately as static data outside
* of the code, at least in Jak 1.
*/
Val* Compiler::compile_float(const goos::Object& code, Env* env) {
assert(code.is_float());
return compile_float(code.float_obj.value, env, MAIN_SEGMENT);
return compile_float(code.float_obj.value, env,
get_parent_env_of_type<FunctionEnv>(env)->segment);
}
/*!
* Compile a floating point constant and place it in given segment.
* Unlike integers, all floating point constants are stored separately as static data outside
* of the code, at least in Jak 1.
*/
Val* Compiler::compile_float(float value, Env* env, int seg) {
auto obj = std::make_unique<StaticFloat>(value, seg);
auto fe = get_parent_env_of_type<FunctionEnv>(env);

View file

@ -1,26 +1,50 @@
/*!
* @file Block.cpp
* Compiler implementation for blocks / gotos / labels
*/
#include "goalc/compiler/Compiler.h"
#include "goalc/compiler/IR.h"
using namespace goos;
/*!
* Compile "top-level" form, which is equivalent to a begin.
*/
Val* Compiler::compile_top_level(const goos::Object& form, const goos::Object& rest, Env* env) {
return compile_begin(form, rest, env);
}
/*!
* Compile "begin" form, which compiles each segment in a row.
* TODO - determine if a GOAL begin matches this behavior for not "to_reg"ing anything.
*/
Val* Compiler::compile_begin(const goos::Object& form, const goos::Object& rest, Env* env) {
(void)form;
Val* result = get_none();
for_each_in_list(rest, [&](const Object& o) { result = compile_error_guard(o, env); });
for_each_in_list(rest, [&](const Object& o) {
result = compile_error_guard(o, env);
if (!dynamic_cast<None*>(result)) {
result = result->to_reg(env);
}
});
return result;
}
/*!
* Compile "block" form. Code inside of a block can "return from" the block and provide a value
* for the block. If this doesn't happen, the block takes on the value of the last thing in
* the block.
* TODO - determine if a GOAL block matches this behavior for not "to_reg"ing anything,
* and also using a gpr as a return value always.
*/
Val* Compiler::compile_block(const goos::Object& form, const goos::Object& _rest, Env* env) {
auto rest = &_rest;
auto name = pair_car(*rest);
rest = &pair_cdr(*rest);
if (!rest->is_pair()) {
throw_compile_error(form, "Block form has an empty or invliad body");
throw_compile_error(form, "Block form has an empty or invalid body");
}
auto fe = get_parent_env_of_type<FunctionEnv>(env);
@ -29,9 +53,8 @@ Val* Compiler::compile_block(const goos::Object& form, const goos::Object& _rest
auto block_env = fe->alloc_env<BlockEnv>(env, symbol_string(name));
// we need to create a return value register, as a "return-from" statement inside the block may
// set it. for now it has a type of none, but we will set it more accurate after compiling the
// block.
// block_env->return_value = env->alloc_reg(get_base_typespec("none"));
// set it. for now it has a type of none, but we will set it after compiling the block.
// TODO - determine if GOAL blocks _always_ return gprs, or if it's possible to return xmms.
block_env->return_value = env->make_gpr(m_ts.make_typespec("none"));
// create label to the end of the block (we don't yet know where it is...)
@ -39,11 +62,17 @@ Val* Compiler::compile_block(const goos::Object& form, const goos::Object& _rest
// compile everything in the body
Val* result = get_none();
for_each_in_list(*rest, [&](const Object& o) { result = compile_error_guard(o, block_env); });
for_each_in_list(*rest, [&](const Object& o) {
result = compile_error_guard(o, block_env);
if (!dynamic_cast<None*>(result)) {
result = result->to_reg(env);
}
});
// if no return-from's were used, we can ignore the return_value register, and basically turn this
// into a begin. this allows a block which returns a floating point value to return the value in
// an xmm register, which is likely to eliminate a gpr->xmm move.
// TODO - does this happen in GOAL?
if (block_env->return_types.empty()) {
return result;
}
@ -71,6 +100,11 @@ Val* Compiler::compile_block(const goos::Object& form, const goos::Object& _rest
return block_env->return_value;
}
/*!
* Compile a "return-from" statement. These can be used to return from a block and give a value.
* Note that there is a special "block" containing all code in a function called "#f", and you can
* use (return-from #f value) to return early from an entire function.
*/
Val* Compiler::compile_return_from(const goos::Object& form, const goos::Object& _rest, Env* env) {
const Object* rest = &_rest;
auto block_name = symbol_string(pair_car(*rest));
@ -97,23 +131,27 @@ Val* Compiler::compile_return_from(const goos::Object& form, const goos::Object&
env->emit(std::move(ir_move_rv));
// jump to end of block
// jump to end of block (by label object)
auto ir_jump = std::make_unique<IR_GotoLabel>(&block->end_label);
// we know this label is a real label. even though end_label doesn't know where it is, there is an
// actual label object. this means we won't try to resolve this label _by name_ later on when the
// block is done.
// ir_jump->resolved = true;
env->emit(std::move(ir_jump));
// In the real GOAL, there is likely a bug here where a non-none value is returned.
// In the real GOAL, there is likely a bug here where a non-none value is returned and to_gpr'd
// todo, determine if we should replicate this bug and if it can have side effects.
return get_none();
}
/*!
* Compile a label form, which creates a named label that can be jumped to with goto.
*/
Val* Compiler::compile_label(const goos::Object& form, const goos::Object& rest, Env* env) {
auto label_name = symbol_string(pair_car(rest));
expect_empty_list(pair_cdr(rest));
// make sure we don't have a label with this name already
// note that you cannot jump out of your label space - they are not nested like lexical scopes!
// generally there is one label space per function. Inlined functions have their own label space
// and they will not permit jumping outside of the inlined function into the caller because
// that seems like a really bad idea.
auto& labels = env->get_label_map();
auto kv = labels.find(label_name);
if (kv != labels.end()) {
@ -121,12 +159,16 @@ Val* Compiler::compile_label(const goos::Object& form, const goos::Object& rest,
form, "There are two labels named " + label_name + " in the same label environment");
}
// make a label pointing to the end of the current function env.
// make a label pointing to the end of the current function env. safe because we'll always add
// a terminating "null" instruction at the end.
auto func_env = get_parent_env_of_type<FunctionEnv>(env);
labels[label_name] = Label(func_env, func_env->code().size());
return get_none();
}
/*!
* Compile a goto form, which unconditionally jumps to a label by name in the same label space.
*/
Val* Compiler::compile_goto(const goos::Object& form, const goos::Object& rest, Env* env) {
(void)form;
auto label_name = symbol_string(pair_car(rest));

View file

@ -1,9 +1,18 @@
/*!
* @file CompilerControl.cpp
* Compiler implementation for forms which actually control the compiler.
*/
#include "goalc/compiler/Compiler.h"
#include "goalc/compiler/IR.h"
#include "common/util/Timer.h"
#include "common/util/DgoWriter.h"
#include "common/util/FileUtil.h"
/*!
* Exit the compiler. Disconnects the listener and tells the target to reset itself.
* Will actually exit the next time the REPL runs.
*/
Val* Compiler::compile_exit(const goos::Object& form, const goos::Object& rest, Env* env) {
(void)env;
auto args = get_va(form, rest);
@ -11,10 +20,15 @@ Val* Compiler::compile_exit(const goos::Object& form, const goos::Object& rest,
if (m_listener.is_connected()) {
m_listener.send_reset(false);
}
// flag for the REPL.
m_want_exit = true;
return get_none();
}
/*!
* Evaluate GOOS code. It's not possible to get the result, so this is really only useful to get
* a side effect. Used to bootstrap the GOAL/GOOS macro system.
*/
Val* Compiler::compile_seval(const goos::Object& form, const goos::Object& rest, Env* env) {
(void)env;
try {
@ -27,6 +41,10 @@ Val* Compiler::compile_seval(const goos::Object& form, const goos::Object& rest,
return get_none();
}
/*!
* Compile a file, and optionally color, save, or load.
* This should only be used for v3 "code object" files.
*/
Val* Compiler::compile_asm_file(const goos::Object& form, const goos::Object& rest, Env* env) {
(void)env;
int i = 0;
@ -39,6 +57,7 @@ Val* Compiler::compile_asm_file(const goos::Object& form, const goos::Object& re
std::vector<std::pair<std::string, float>> timing;
Timer total_timer;
// parse arguments
for_each_in_list(rest, [&](const goos::Object& o) {
if (i == 0) {
filename = as_string(o);
@ -59,43 +78,49 @@ Val* Compiler::compile_asm_file(const goos::Object& form, const goos::Object& re
i++;
});
// READ
Timer reader_timer;
auto code = m_goos.reader.read_from_file({filename});
timing.emplace_back("read", reader_timer.getMs());
Timer compile_timer;
std::string obj_file_name = filename;
// Extract object name from file name.
for (int idx = int(filename.size()) - 1; idx-- > 0;) {
if (filename.at(idx) == '\\' || filename.at(idx) == '/') {
obj_file_name = filename.substr(idx + 1);
break;
}
}
obj_file_name = obj_file_name.substr(0, obj_file_name.find_last_of('.'));
// COMPILE
auto obj_file = compile_object_file(obj_file_name, code, !no_code);
timing.emplace_back("compile", compile_timer.getMs());
if (color) {
// register allocation
Timer color_timer;
color_object_file(obj_file);
timing.emplace_back("color", color_timer.getMs());
// code/object file generation
Timer codegen_timer;
auto data = codegen_object_file(obj_file);
timing.emplace_back("codegen", codegen_timer.getMs());
// send to target
if (load) {
if (m_listener.is_connected()) {
m_listener.send_code(data);
} else {
printf("WARNING - couldn't load because listener isn't connected\n");
printf("WARNING - couldn't load because listener isn't connected\n"); // todo spdlog warn
}
}
// save file
if (write) {
// auto output_dir = as_string(get_constant_or_error(form, "*compiler-output-path*"));
// todo, change extension based on v3/v4
auto output_name = m_goos.reader.get_source_dir() + "/data/" + obj_file_name + ".o";
file_util::write_binary_file(output_name, (void*)data.data(), data.size());
}
@ -109,23 +134,29 @@ Val* Compiler::compile_asm_file(const goos::Object& form, const goos::Object& re
}
}
// if(truthy(get_config("print-asm-file-time"))) {
printf("F: %36s ", obj_file_name.c_str());
for (auto& e : timing) {
printf(" %12s %4.2f", e.first.c_str(), e.second);
if (m_settings.print_timing) {
printf("F: %36s ", obj_file_name.c_str());
timing.emplace_back("total", total_timer.getMs());
for (auto& e : timing) {
printf(" %12s %4.2f", e.first.c_str(), e.second / 1000.f);
}
printf("\n");
}
printf("\n");
// }
return get_none();
}
/*!
* Connect the compiler to a target. Takes an optional IP address / port, defaults to
* 127.0.0.1 and 8112, which is the local computer and the default port for the DECI2 over IP
* implementation.
*/
Val* Compiler::compile_listen_to_target(const goos::Object& form,
const goos::Object& rest,
Env* env) {
(void)env;
std::string ip = "127.0.0.1";
int port = 8112; // todo, get from some constant somewhere
int port = DECI2_PORT;
bool got_port = false, got_ip = false;
for_each_in_list(rest, [&](const goos::Object& o) {
@ -150,6 +181,11 @@ Val* Compiler::compile_listen_to_target(const goos::Object& form,
return get_none();
}
/*!
* Send the target a command to reset, which totally resets the state of the target.
* Optionally takes a :shutdown command which causes the exec_runtime function of the target
* to return after MachineShutdown.
*/
Val* Compiler::compile_reset_target(const goos::Object& form, const goos::Object& rest, Env* env) {
(void)env;
bool shutdown = false;
@ -164,6 +200,11 @@ Val* Compiler::compile_reset_target(const goos::Object& form, const goos::Object
return get_none();
}
/*!
* Send a "poke" message to the target. This can be used to check if the target is still alive and
* acknowledges commands, and also tells that target that somebody is connected so it will flush
* its outgoing buffers that have been storing data from startup.
*/
Val* Compiler::compile_poke(const goos::Object& form, const goos::Object& rest, Env* env) {
(void)env;
auto args = get_va(form, rest);
@ -172,6 +213,9 @@ Val* Compiler::compile_poke(const goos::Object& form, const goos::Object& rest,
return get_none();
}
/*!
* Enter a goos REPL.
*/
Val* Compiler::compile_gs(const goos::Object& form, const goos::Object& rest, Env* env) {
(void)env;
auto args = get_va(form, rest);
@ -180,6 +224,9 @@ Val* Compiler::compile_gs(const goos::Object& form, const goos::Object& rest, En
return get_none();
}
/*!
* Set a compiler setting by name.
*/
Val* Compiler::compile_set_config(const goos::Object& form, const goos::Object& rest, Env* env) {
(void)env;
auto args = get_va(form, rest);
@ -188,6 +235,9 @@ Val* Compiler::compile_set_config(const goos::Object& form, const goos::Object&
return get_none();
}
/*!
* Ignore the "in-package" statement and anything it contains at the top of GOAL files.
*/
Val* Compiler::compile_in_package(const goos::Object& form, const goos::Object& rest, Env* env) {
(void)form;
(void)rest;
@ -195,6 +245,10 @@ Val* Compiler::compile_in_package(const goos::Object& form, const goos::Object&
return get_none();
}
/*!
* Build dgo files. Takes a string argument pointing to the DGO description file, which is read
* and parsed here.
*/
Val* Compiler::compile_build_dgo(const goos::Object& form, const goos::Object& rest, Env* env) {
(void)env;
auto args = get_va(form, rest);

View file

@ -1,10 +1,18 @@
/*!
* @file ControlFlow.cpp
* Compiler forms related to conditional branching and control flow.
*/
#include "goalc/compiler/Compiler.h"
/*!
* Convert a condition expression into a GoalCondition for use in a conditional branch.
* Convert an expression into a GoalCondition for use in a conditional branch.
*
* The reason for this design is to allow an optimization for
* (if (< a b) ...) to be compiled without actually computing a true/false value for the (< a b)
* expression. Instead, it will generate a cmp + jle sequence of instructions, which is much faster.
* In particular, getting GOAL "true" requires a few instructions, so it's to avoid this when
* possible.
*
* This can be applied to _any_ GOAL form, and will return a GoalCondition which can be used with a
* Branch IR to branch if the condition is true/false. When possible it applies the optimization
@ -95,7 +103,7 @@ Condition Compiler::compile_condition(const goos::Object& condition, Env* env, b
}
}
// not something we can process more. Just check if we get false.
// not something we can process more. Just evaluate as normal and check if we get false.
// todo - it's possible to optimize a false comparison because the false offset is zero
gc.kind = invert ? ConditionKind::EQUAL : ConditionKind::NOT_EQUAL;
gc.a = compile_error_guard(condition, env)->to_gpr(env);
@ -104,6 +112,12 @@ Condition Compiler::compile_condition(const goos::Object& condition, Env* env, b
return gc;
}
/*!
* Compile a comparison when we explicitly want a boolean result. This is used whenever a condition
* _isn't_ used as a branch condition. Like (set! x (< 1 2))
*
* TODO, this could be optimized quite a bit.
*/
Val* Compiler::compile_condition_as_bool(const goos::Object& form,
const goos::Object& rest,
Env* env) {
@ -124,6 +138,10 @@ Val* Compiler::compile_condition_as_bool(const goos::Object& form,
return result;
}
/*!
* The when-goto form is a better version of (if condition (goto x))
* It compiles into a single conditional branch.
*/
Val* Compiler::compile_when_goto(const goos::Object& form, const goos::Object& _rest, Env* env) {
(void)form;
auto* rest = &_rest;
@ -141,6 +159,12 @@ Val* Compiler::compile_when_goto(const goos::Object& form, const goos::Object& _
return get_none();
}
/*!
* The Scheme/Lisp "cond" form.
* Works like you expect. Return type is the lowest common ancestor of all possible return values.
* If no cases match and there's no else, returns #f.
* TODO - how should the return type work if #f can possibly be returned?
*/
Val* Compiler::compile_cond(const goos::Object& form, const goos::Object& rest, Env* env) {
auto result = env->make_gpr(m_ts.make_typespec("object"));
@ -170,12 +194,16 @@ Val* Compiler::compile_cond(const goos::Object& form, const goos::Object& rest,
Val* case_result = get_none();
for_each_in_list(clauses, [&](const goos::Object& clause) {
case_result = compile_error_guard(clause, env);
if (!dynamic_cast<None*>(case_result)) {
case_result = case_result->to_reg(env);
}
});
case_result_types.push_back(case_result->type());
// optimization - if we get junk, don't bother moving it, just leave junk in return.
if (!is_none(case_result)) {
// todo, what does GOAL do here? does it matter?
env->emit(std::make_unique<IR_RegSet>(result, case_result->to_gpr(env)));
}
@ -193,10 +221,14 @@ Val* Compiler::compile_cond(const goos::Object& form, const goos::Object& rest,
Val* case_result = get_none();
for_each_in_list(clauses, [&](const goos::Object& clause) {
case_result = compile_error_guard(clause, env);
if (!dynamic_cast<None*>(case_result)) {
case_result = case_result->to_reg(env);
}
});
case_result_types.push_back(case_result->type());
if (!is_none(case_result)) {
// todo, what does GOAL do here?
env->emit(std::make_unique<IR_RegSet>(result, case_result->to_gpr(env)));
}
@ -210,8 +242,7 @@ Val* Compiler::compile_cond(const goos::Object& form, const goos::Object& rest,
});
if (!got_else) {
// if no else, clause, return #f. But don't retype. I don't know how I feel about this typing
// setup.
// if no else, clause, return #f. But don't retype. todo what does goal do here?
auto get_false = std::make_unique<IR_LoadSymbolPointer>(result, "#f");
env->emit(std::move(get_false));
}

View file

@ -1,6 +1,15 @@
/*!
* @file Define.cpp
* Forms which define or set things.
*/
#include "goalc/compiler/Compiler.h"
#include "goalc/logger/Logger.h"
/*!
* Define or set a global value. Has some special magic to store data for functions which may be
* inlined.
*/
Val* Compiler::compile_define(const goos::Object& form, const goos::Object& rest, Env* env) {
auto args = get_va(form, rest);
va_check(form, args, {goos::ObjectType::SYMBOL, {}}, {});
@ -41,6 +50,9 @@ Val* Compiler::compile_define(const goos::Object& form, const goos::Object& rest
return in_gpr;
}
/*!
* Inform the compiler of the type of a global. Will warn on changing type.
*/
Val* Compiler::compile_define_extern(const goos::Object& form, const goos::Object& rest, Env* env) {
(void)env;
auto args = get_va(form, rest);
@ -52,6 +64,7 @@ Val* Compiler::compile_define_extern(const goos::Object& form, const goos::Objec
auto existing_type = m_symbol_types.find(symbol_string(sym));
if (existing_type != m_symbol_types.end() && existing_type->second != new_type) {
// todo spdlog
gLogger.log(
MSG_WARN,
"[Warning] define-extern has redefined the type of symbol %s\npreviously: %s\nnow: %s\n",
@ -62,12 +75,18 @@ Val* Compiler::compile_define_extern(const goos::Object& form, const goos::Objec
return get_none();
}
/*!
* Set something to something.
* Lots of special cases.
*/
Val* Compiler::compile_set(const goos::Object& form, const goos::Object& rest, Env* env) {
auto args = get_va(form, rest);
va_check(form, args, {{}, {}}, {});
auto& destination = args.unnamed.at(0);
// todo, I don't know if this is the correct order or not.
// todo, I don't know if this is the correct order or not. Right now the value is computed
// and to_reg'd first, then the destination is computed, if the destination requires math to
// compute.
auto source = compile_error_guard(args.unnamed.at(1), env)->to_reg(env);
if (destination.is_symbol()) {
@ -97,25 +116,29 @@ Val* Compiler::compile_set(const goos::Object& form, const goos::Object& rest, E
}
}
} else {
// destination is some complex expression, so compile it and hopefully get something settable.
auto dest = compile_error_guard(destination, env);
auto as_mem_deref = dynamic_cast<MemoryDerefVal*>(dest);
auto as_pair = dynamic_cast<PairEntryVal*>(dest);
if (as_mem_deref) {
// setting somewhere in memory
auto base = as_mem_deref->base;
auto base_as_mco = dynamic_cast<MemoryOffsetConstantVal*>(base);
if (base_as_mco) {
// if it is a constant offset, we can use a fancy x86-64 addressing mode to simplify
auto ti = m_ts.lookup_type(base->type());
env->emit(std::make_unique<IR_StoreConstOffset>(
source, base_as_mco->offset, base_as_mco->base->to_gpr(env), ti->get_load_size()));
return source;
} else {
// nope, the pointer to dereference is some compliated thing.
auto ti = m_ts.lookup_type(base->type());
env->emit(std::make_unique<IR_StoreConstOffset>(source, 0, base->to_gpr(env),
ti->get_load_size()));
return source;
throw_compile_error(form, "Set not implemented for this (non-mco) yet");
}
} else if (as_pair) {
// this could probably be part of MemoryDerefVal and not a special case here.
env->emit(std::make_unique<IR_StoreConstOffset>(source, as_pair->is_car ? -2 : 2,
as_pair->base->to_gpr(env), 4));
return source;

View file

@ -1,7 +1,15 @@
/*!
* @file Function.cpp
* Calling and defining functions, lambdas, and inlining.
*/
#include "goalc/compiler/Compiler.h"
#include "goalc/logger/Logger.h"
namespace {
/*!
* Get the preference to inline of the given environment.
*/
bool get_inline_preference(Env* env) {
auto ile = get_parent_env_of_type<WithInlineEnv>(env);
if (ile) {
@ -11,6 +19,9 @@ bool get_inline_preference(Env* env) {
}
}
/*!
* Hacky function to seek past arguments to get a goos::Object containing the body of a lambda.
*/
const goos::Object& get_lambda_body(const goos::Object& def) {
auto* iter = &def;
while (true) {
@ -26,6 +37,14 @@ const goos::Object& get_lambda_body(const goos::Object& def) {
}
} // namespace
/*!
* The (inline my-func) form is like my-func, except my-func will be inlined instead of called,
* when used in a function call. This only works for immediaate function calls, you can't "save"
* an (inline my-func) into a function pointer.
*
* If inlining is not possible (function disallows inlining or didn't save its code), throw an
* error.
*/
Val* Compiler::compile_inline(const goos::Object& form, const goos::Object& rest, Env* env) {
(void)env;
auto args = get_va(form, rest);
@ -39,17 +58,19 @@ Val* Compiler::compile_inline(const goos::Object& form, const goos::Object& rest
if (kv->second->func && !kv->second->func->settings.allow_inline) {
throw_compile_error(form, "Found function to inline, but it isn't allowed.");
}
// todo, this should return a "view" of the lambda which indicates its inlined
// so the correct label namespace behavior can be used.
return kv->second;
auto fe = get_parent_env_of_type<FunctionEnv>(env);
return fe->alloc_val<InlinedLambdaVal>(kv->second->type(), kv->second);
}
/*!
* Compile a lambda. This is used for real lambdas, lets, and defuns. So there are a million
* confusing special cases...
*/
Val* Compiler::compile_lambda(const goos::Object& form, const goos::Object& rest, Env* env) {
auto fe = get_parent_env_of_type<FunctionEnv>(env);
auto args = get_va(form, rest);
if (args.unnamed.empty() || !args.unnamed.front().is_list() ||
!args.only_contains_named({"name", "inline-only"})) {
!args.only_contains_named({"name", "inline-only", "segment"})) {
throw_compile_error(form, "Invalid lambda form");
}
@ -65,7 +86,7 @@ Val* Compiler::compile_lambda(const goos::Object& form, const goos::Object& rest
lambda_ts.add_arg(m_ts.make_typespec("object"));
} else {
auto param_args = get_va(o, o);
va_check(o, param_args, {goos::ObjectType::SYMBOL, goos::ObjectType::SYMBOL}, {});
va_check(o, param_args, {goos::ObjectType::SYMBOL, {}}, {});
GoalArg parm;
parm.name = symbol_string(param_args.unnamed.at(0));
@ -77,9 +98,8 @@ Val* Compiler::compile_lambda(const goos::Object& form, const goos::Object& rest
});
assert(lambda.params.size() == lambda_ts.arg_count());
// optional name for debugging
// optional name for debugging (defun sets this)
if (args.has_named("name")) {
// todo, this probably prints a nasty error if name isn't a string.
lambda.debug_name = symbol_string(args.get_named("name"));
}
@ -89,13 +109,36 @@ Val* Compiler::compile_lambda(const goos::Object& form, const goos::Object& rest
bool inline_only =
args.has_named("inline-only") && symbol_string(args.get_named("inline-only")) != "#f";
// pick default segment to store function in.
int segment = MAIN_SEGMENT;
if (fe->segment == DEBUG_SEGMENT) {
// make anonymous lambdas in debug functions also go to debug
segment = DEBUG_SEGMENT;
}
// override default segment.
if (args.has_named("segment")) {
auto segment_name = symbol_string(args.get_named("segment"));
if (segment_name == "main") {
segment = MAIN_SEGMENT;
} else if (segment_name == "debug") {
segment = DEBUG_SEGMENT;
} else {
throw_compile_error(form, "invalid segment override in lambda");
}
}
if (!inline_only) {
// compile a function! First create env
auto new_func_env = std::make_unique<FunctionEnv>(env, lambda.debug_name);
new_func_env->set_segment(MAIN_SEGMENT); // todo, how do we set debug?
new_func_env->set_segment(segment);
// set up arguments
assert(lambda.params.size() < 8); // todo graceful error
if (lambda.params.size() >= 8) {
throw_compile_error(form, "lambda generating code has too many parameters!");
}
// set up argument register constraints.
std::vector<RegVal*> args_for_coloring;
for (u32 i = 0; i < lambda.params.size(); i++) {
IRegConstraint constr;
@ -117,28 +160,37 @@ Val* Compiler::compile_lambda(const goos::Object& form, const goos::Object& rest
func_block_env->end_label = Label(new_func_env.get());
func_block_env->emit(std::make_unique<IR_FunctionStart>(args_for_coloring));
// compile the function!
// compile the function, iterating through the body.
Val* result = nullptr;
bool first_thing = true;
for_each_in_list(lambda.body, [&](const goos::Object& o) {
result = compile_error_guard(o, func_block_env);
if (!dynamic_cast<None*>(result)) {
result = result->to_reg(func_block_env);
}
if (first_thing) {
first_thing = false;
// you could probably cheat and do a (begin (blorp) (declare ...)) to get around this.
// you could cheat and do a (begin (blorp) (declare ...)) to get around this.
// but I see no strong reason why "declare"s need to go at the beginning, so no reason
// to make this better.
new_func_env->settings.is_set = true;
}
});
if (result) {
// got a result, so to_gpr it and return it.
auto final_result = result->to_gpr(new_func_env.get());
new_func_env->emit(std::make_unique<IR_Return>(return_reg, final_result));
lambda_ts.add_arg(final_result->type());
} else {
// empty body, return none
lambda_ts.add_arg(m_ts.make_typespec("none"));
}
// put null instruction at the end so jumps to the end have somewhere to go.
func_block_env->end_label.idx = new_func_env->code().size();
new_func_env->emit(std::make_unique<IR_Null>());
new_func_env->finish();
// save our code for possible inlining
auto obj_env = get_parent_env_of_type<FileEnv>(new_func_env.get());
assert(obj_env);
if (new_func_env->settings.save_code) {
@ -150,12 +202,16 @@ Val* Compiler::compile_lambda(const goos::Object& form, const goos::Object& rest
return place;
}
/*!
* Compile a form which should be either a function call (possibly inline) or method call.
* Note - calling method "new" isn't handled by this.
* Again, there are way too many special cases here.
*/
Val* Compiler::compile_function_or_method_call(const goos::Object& form, Env* env) {
goos::Object f = form;
auto fe = get_parent_env_of_type<FunctionEnv>(env);
auto args = get_va(form, form);
auto uneval_head = args.unnamed.at(0);
Val* head = get_none();
@ -163,7 +219,7 @@ Val* Compiler::compile_function_or_method_call(const goos::Object& form, Env* en
// this logic will not trigger for a manually inlined call [using the (inline func) form]
bool auto_inline = false;
if (uneval_head.is_symbol()) {
// we can only auto-inline the function if its name is explicit.
// we can only auto-inline the function if its name is explicitly given.
// look it up:
auto kv = m_inlineable_functions.find(uneval_head.as_symbol());
if (kv != m_inlineable_functions.end()) {
@ -214,10 +270,24 @@ Val* Compiler::compile_function_or_method_call(const goos::Object& form, Env* en
// LambdaPlace! so this cast will only succeed if the auto-inliner succeeded, or the user has
// passed use explicitly a lambda either with the lambda form, or with the (inline ...) form.
LambdaVal* head_as_lambda = nullptr;
bool got_inlined_lambda = false;
if (!is_method_call) {
// try directly as a lambda
head_as_lambda = dynamic_cast<LambdaVal*>(head);
if (!head_as_lambda) {
// nope, so try as an (inline x)
auto head_as_inlined_lambda = dynamic_cast<InlinedLambdaVal*>(head);
if (head_as_inlined_lambda) {
// yes, remember the lambda that contains and flag that we're inlining.
head_as_lambda = head_as_inlined_lambda->lv;
got_inlined_lambda = true;
}
}
}
// no lambda (not inlining or immediate), and not a method call, so we should actually get
// the function pointer.
if (!head_as_lambda && !is_method_call) {
head = head->to_gpr(env);
}
@ -226,11 +296,12 @@ Val* Compiler::compile_function_or_method_call(const goos::Object& form, Env* en
std::vector<RegVal*> eval_args;
for (uint32_t i = 1; i < args.unnamed.size(); i++) {
auto intermediate = compile_error_guard(args.unnamed.at(i), env);
// todo, are the eval/to_reg'd in batches?
eval_args.push_back(intermediate->to_reg(env));
}
if (head_as_lambda) {
// inline the function!
// inline/immediate the function!
// check args are ok
if (head_as_lambda->lambda.params.size() != eval_args.size()) {
@ -242,26 +313,32 @@ Val* Compiler::compile_function_or_method_call(const goos::Object& form, Env* en
Env* compile_env = lexical_env;
// if needed create a label env.
// we don't want a separate label env with lets, but we do in other cases.
if (auto_inline) {
// TODO - this misses the case of (inline func)!
// if need to, create a label env.
// we don't want a separate label env with lets, but we do for inlined functions.
// either inlined through the auto-inliner, or through an explicit (inline x) form.
if (auto_inline || got_inlined_lambda) {
compile_env = fe->alloc_env<LabelEnv>(lexical_env);
}
// check arg types
if (!head->type().arg_count()) {
if (head->type().arg_count() - 1 != eval_args.size()) {
throw_compile_error(form, "invalid number of arguments to function call (inline)");
throw_compile_error(form,
"invalid number of arguments to function call (inline or immediate "
"lambda application)");
}
// immediate lambdas (lets) will have all types as the most general object by default
// inlined functions will have real types that are checked...
for (uint32_t i = 0; i < eval_args.size(); i++) {
typecheck(form, head->type().get_arg(i), eval_args.at(i)->type(),
"function (inline) argument");
"function/lambda (inline/immediate) argument");
}
}
// copy args...
for (uint32_t i = 0; i < eval_args.size(); i++) {
// note, inlined functions will get a more specific type if possible
// todo, is this right?
auto type = eval_args.at(i)->type();
auto copy = env->make_ireg(type, get_preferred_reg_kind(type));
env->emit(std::make_unique<IR_RegSet>(copy, eval_args.at(i)));
@ -273,27 +350,36 @@ Val* Compiler::compile_function_or_method_call(const goos::Object& form, Env* en
Val* result = get_none();
for_each_in_list(head_as_lambda->lambda.body, [&](const goos::Object& o) {
result = compile_error_guard(o, compile_env);
if (!dynamic_cast<None*>(result)) {
result = result->to_reg(compile_env);
}
if (first_thing) {
first_thing = false;
lexical_env->settings.is_set = true;
}
});
// this doesn't require a return type.
// ignore the user specified return type and return the most specific type.
// todo - does this make sense for an inline function? Should we check the return type?
return result;
} else {
// not an inline call
// not an inlined/immediate, it's a real function call.
// todo, this order is extremely likely to be wrong, we should get the method way earlier.
if (is_method_call) {
// method needs at least one argument to tell what we're calling the method on.
if (eval_args.empty()) {
throw_compile_error(form,
"Unrecognized symbol " + uneval_head.print() + " as head of form");
}
// get the method function pointer
head = compile_get_method_of_object(eval_args.front(), symbol_string(uneval_head), env);
fmt::format("method of object {} {}\n", head->print(), head->type().print());
}
// convert the head to a GPR
// convert the head to a GPR (if function, this is already done)
auto head_as_gpr = head->to_gpr(env);
if (head_as_gpr) {
// method calls have special rules for typing _type_ arguments.
if (is_method_call) {
return compile_real_function_call(form, head_as_gpr, eval_args, env,
eval_args.front()->type().base_type());
@ -310,11 +396,23 @@ Val* Compiler::compile_function_or_method_call(const goos::Object& form, Env* en
return get_none();
}
namespace {
/*!
* Is the given typespec for a varargs function? Assumes typespec is a function to begin with.
*/
bool is_varargs_function(const TypeSpec& ts) {
return ts.arg_count() >= 2 && ts.get_arg(0).print() == "_varargs_";
}
} // namespace
/*!
* Do a real x86-64 function call.
*/
Val* Compiler::compile_real_function_call(const goos::Object& form,
RegVal* function,
const std::vector<RegVal*>& args,
Env* env,
std::string method_type_name) {
const std::string& method_type_name) {
auto fe = get_parent_env_of_type<FunctionEnv>(env);
fe->require_aligned_stack();
TypeSpec return_ts;
@ -322,27 +420,17 @@ Val* Compiler::compile_real_function_call(const goos::Object& form,
// if the type system doesn't know what the function will return, just make it object.
// the user is responsible for getting this right.
return_ts = m_ts.make_typespec("object");
gLogger.log(MSG_WARN, "[Warning] Function call could not determine return type: %s\n",
form.print().c_str());
// todo, should this be a warning? not a great thing if we don't know what a function will
// return?
gLogger.log(MSG_WARN, "[Warning] Function call could not determine return type: %s, %s, %s\n",
form.print().c_str(), function->print().c_str(), function->type().print().c_str());
// todo, consider making this an error once object-new works better.
} else {
return_ts = function->type().last_arg();
}
auto return_reg = env->make_ireg(return_ts, emitter::RegKind::GPR);
// TODO - VERY IMPORTANT
// CREATE A TEMP COPY OF FUNCTION! WILL BE DESTROYED.
// nope! not anymore.
// for(auto& arg : args) {
// // note: this has to be done in here, because we might want to const prop across lexical
// envs. arg = resolve_to_gpr(arg, env);
// }
// check arg count:
if (function->type().arg_count()) {
if (function->type().arg_count() && !is_varargs_function(function->type())) {
if (function->type().arg_count() - 1 != args.size()) {
throw_compile_error(form, "invalid number of arguments to function call: got " +
std::to_string(args.size()) + " and expected " +
@ -366,7 +454,10 @@ Val* Compiler::compile_real_function_call(const goos::Object& form,
env->emit(std::make_unique<IR_RegSet>(arg_outs.back(), arg));
}
env->emit(std::make_unique<IR_FunctionCall>(function, return_reg, arg_outs));
// todo, there's probably a more efficient way to do this.
auto temp_function = fe->make_gpr(function->type());
env->emit(std::make_unique<IR_RegSet>(temp_function, function));
env->emit(std::make_unique<IR_FunctionCall>(temp_function, return_reg, arg_outs));
if (m_settings.emit_move_after_return) {
auto result_reg = env->make_gpr(return_reg->type());
@ -377,6 +468,10 @@ Val* Compiler::compile_real_function_call(const goos::Object& form,
}
}
/*!
* A (declare ...) form can be used to configure settings inside a function.
* Currently there aren't many useful settings, but more may be added in the future.
*/
Val* Compiler::compile_declare(const goos::Object& form, const goos::Object& rest, Env* env) {
auto& settings = get_parent_env_of_type<DeclareEnv>(env)->settings;

View file

@ -2,6 +2,9 @@
using namespace goos;
/*!
* Try to find a macro with the given name in the GOOS "goal_env". Return if it succeeded.
*/
bool Compiler::try_getting_macro_from_goos(const goos::Object& macro_name, goos::Object* dest) {
Object macro_obj;
bool got_macro = false;
@ -20,6 +23,9 @@ bool Compiler::try_getting_macro_from_goos(const goos::Object& macro_name, goos:
return got_macro;
}
/*!
* Expand a macro, then compile the result.
*/
Val* Compiler::compile_goos_macro(const goos::Object& o,
const goos::Object& macro_obj,
const goos::Object& rest,
@ -37,6 +43,9 @@ Val* Compiler::compile_goos_macro(const goos::Object& o,
return compile_error_guard(goos_result, env);
}
/*!
* Compile the #cond form, which is a compile-time conditional statement.
*/
Val* Compiler::compile_gscond(const goos::Object& form, const goos::Object& rest, Env* env) {
if (!rest.is_pair()) {
throw_compile_error(form, "#cond must have at least one clause, which must be a form");
@ -61,8 +70,12 @@ Val* Compiler::compile_gscond(const goos::Object& form, const goos::Object& rest
// got a match!
result = get_none();
for_each_in_list(current_case.as_pair()->cdr,
[&](const Object& o) { result = compile_error_guard(o, env); });
for_each_in_list(current_case.as_pair()->cdr, [&](const Object& o) {
result = compile_error_guard(o, env);
if (!dynamic_cast<None*>(result)) {
result = result->to_reg(env);
}
});
return result;
} else {
// no match, continue.
@ -76,6 +89,10 @@ Val* Compiler::compile_gscond(const goos::Object& form, const goos::Object& rest
}
}
/*!
* Compile (quote x) or 'x forms.
* Current only supports 'thing or '(). Static lists/pairs should be added at some point.
*/
Val* Compiler::compile_quote(const goos::Object& form, const goos::Object& rest, Env* env) {
auto args = get_va(form, rest);
va_check(form, args, {{}}, {});
@ -95,6 +112,9 @@ Val* Compiler::compile_quote(const goos::Object& form, const goos::Object& rest,
return get_none();
}
/*!
* Compile defglobalconstant forms, which define a constant in both GOOS and GOAL.
*/
Val* Compiler::compile_defglobalconstant(const goos::Object& form,
const goos::Object& _rest,
Env* env) {
@ -122,6 +142,9 @@ Val* Compiler::compile_defglobalconstant(const goos::Object& form,
return get_none();
}
/*!
* Compile an "mlet" scoped constant/symbol macro form
*/
Val* Compiler::compile_mlet(const goos::Object& form, const goos::Object& rest, Env* env) {
auto defs = pair_car(rest);
auto body = pair_cdr(rest);
@ -136,6 +159,11 @@ Val* Compiler::compile_mlet(const goos::Object& form, const goos::Object& rest,
});
Val* result = get_none();
for_each_in_list(body, [&](const goos::Object& o) { result = compile_error_guard(o, menv); });
for_each_in_list(body, [&](const goos::Object& o) {
result = compile_error_guard(o, menv);
if (!dynamic_cast<None*>(result)) {
result = result->to_reg(menv);
}
});
return result;
}

View file

@ -44,7 +44,10 @@ Val* Compiler::number_to_integer(Val* in, Env* env) {
if (is_binteger(ts)) {
throw std::runtime_error("Can't convert " + in->print() + " (a binteger) to an integer.");
} else if (is_float(ts)) {
throw std::runtime_error("Can't convert " + in->print() + " (a float) to an integer.");
auto fe = get_parent_env_of_type<FunctionEnv>(env);
auto result = fe->make_gpr(m_ts.make_typespec("int"));
env->emit(std::make_unique<IR_FloatToInt>(result, in->to_xmm(env)));
return result;
} else if (is_integer(ts)) {
return in;
}
@ -59,7 +62,11 @@ Val* Compiler::number_to_binteger(Val* in, Env* env) {
} else if (is_float(ts)) {
throw std::runtime_error("Can't convert " + in->print() + " (a float) to a binteger.");
} else if (is_integer(ts)) {
throw std::runtime_error("Can't convert " + in->print() + " (an integer) to a binteger.");
auto fe = get_parent_env_of_type<FunctionEnv>(env);
RegVal* input = in->to_reg(env);
auto sa = fe->make_gpr(m_ts.make_typespec("int"));
env->emit(std::make_unique<IR_LoadConstant64>(sa, 3));
return compile_variable_shift(input, sa, env, IntegerMathKind::SHLV_64);
}
throw std::runtime_error("Can't convert " + in->print() + " to a binteger.");
}
@ -72,7 +79,10 @@ Val* Compiler::number_to_float(Val* in, Env* env) {
} else if (is_float(ts)) {
return in;
} else if (is_integer(ts)) {
throw std::runtime_error("Can't convert " + in->print() + " (an integer) to a float.");
auto fe = get_parent_env_of_type<FunctionEnv>(env);
auto result = fe->make_xmm(m_ts.make_typespec("float"));
env->emit(std::make_unique<IR_IntToFloat>(result, in->to_gpr(env)));
return result;
} else {
throw std::runtime_error("Can't convert " + in->print() + " a float.");
}
@ -114,6 +124,19 @@ Val* Compiler::compile_add(const goos::Object& form, const goos::Object& rest, E
}
return result;
}
case MATH_FLOAT: {
auto result = env->make_xmm(first_type);
env->emit(std::make_unique<IR_RegSet>(result, first_val->to_xmm(env)));
for (size_t i = 1; i < args.unnamed.size(); i++) {
env->emit(std::make_unique<IR_FloatMath>(
FloatMathKind::ADD_SS, result,
to_math_type(compile_error_guard(args.unnamed.at(i), env), math_type, env)
->to_xmm(env)));
}
return result;
}
case MATH_INVALID:
throw_compile_error(
form, "Cannot determine the math mode for object of type " + first_type.print());
@ -204,6 +227,30 @@ Val* Compiler::compile_sub(const goos::Object& form, const goos::Object& rest, E
return result;
}
case MATH_FLOAT:
if (args.unnamed.size() == 1) {
auto result =
compile_float(0, env, get_parent_env_of_type<FunctionEnv>(env)->segment)->to_xmm(env);
env->emit(std::make_unique<IR_FloatMath>(
FloatMathKind::SUB_SS, result,
to_math_type(compile_error_guard(args.unnamed.at(0), env), math_type, env)
->to_xmm(env)));
return result;
} else {
auto result = env->make_xmm(first_type);
env->emit(std::make_unique<IR_RegSet>(
result, to_math_type(compile_error_guard(args.unnamed.at(0), env), math_type, env)
->to_xmm(env)));
for (size_t i = 1; i < args.unnamed.size(); i++) {
env->emit(std::make_unique<IR_FloatMath>(
FloatMathKind::SUB_SS, result,
to_math_type(compile_error_guard(args.unnamed.at(i), env), math_type, env)
->to_xmm(env)));
}
return result;
}
case MATH_INVALID:
throw_compile_error(
form, "Cannot determine the math mode for object of type " + first_type.print());

View file

@ -184,8 +184,6 @@ Val* Compiler::compile_defmethod(const goos::Object& form, const goos::Object& _
if (result) {
auto final_result = result->to_gpr(new_func_env.get());
new_func_env->emit(std::make_unique<IR_Return>(return_reg, final_result));
printf("return type is %s from %s from %s\n", final_result->type().print().c_str(),
final_result->print().c_str(), result->print().c_str());
lambda_ts.add_arg(final_result->type());
} else {
lambda_ts.add_arg(m_ts.make_typespec("none"));
@ -201,7 +199,8 @@ Val* Compiler::compile_defmethod(const goos::Object& form, const goos::Object& _
}
place->set_type(lambda_ts);
auto info = m_ts.add_method(symbol_string(type_name), symbol_string(method_name), lambda_ts);
auto info =
m_ts.add_method(symbol_string(type_name), symbol_string(method_name), lambda_ts, false);
auto type_obj = compile_get_symbol_value(symbol_string(type_name), env)->to_gpr(env);
auto id_val = compile_integer(info.id, env)->to_gpr(env);
auto method_val = place->to_gpr(env);
@ -309,15 +308,17 @@ Val* Compiler::compile_the(const goos::Object& form, const goos::Object& rest, E
if (is_number(base->type())) {
if (m_ts.typecheck(m_ts.make_typespec("binteger"), desired_ts, "", false, false)) {
throw std::runtime_error("the convert to binteger not yet supported");
return number_to_binteger(base, env);
}
if (m_ts.typecheck(m_ts.make_typespec("integer"), desired_ts, "", false, false)) {
throw std::runtime_error("the convert to integer not yet supported");
auto result = number_to_integer(base, env);
result->set_type(desired_ts);
return result;
}
if (m_ts.typecheck(m_ts.make_typespec("float"), desired_ts, "", false, false)) {
throw std::runtime_error("the convert to float not yet supported");
return number_to_float(base, env);
}
}
@ -374,16 +375,22 @@ Val* Compiler::compile_car(const goos::Object& form, const goos::Object& rest, E
auto args = get_va(form, rest);
va_check(form, args, {{}}, {});
auto fe = get_parent_env_of_type<FunctionEnv>(env);
return fe->alloc_val<PairEntryVal>(m_ts.make_typespec("object"),
compile_error_guard(args.unnamed.at(0), env), true);
auto pair = compile_error_guard(args.unnamed.at(0), env);
if (pair->type() != m_ts.make_typespec("object")) {
typecheck(form, m_ts.make_typespec("pair"), pair->type(), "Type of argument to car");
}
return fe->alloc_val<PairEntryVal>(m_ts.make_typespec("object"), pair, true);
}
Val* Compiler::compile_cdr(const goos::Object& form, const goos::Object& rest, Env* env) {
auto args = get_va(form, rest);
va_check(form, args, {{}}, {});
auto fe = get_parent_env_of_type<FunctionEnv>(env);
return fe->alloc_val<PairEntryVal>(m_ts.make_typespec("object"),
compile_error_guard(args.unnamed.at(0), env), false);
auto pair = compile_error_guard(args.unnamed.at(0), env);
if (pair->type() != m_ts.make_typespec("object")) {
typecheck(form, m_ts.make_typespec("pair"), pair->type(), "Type of argument to cdr");
}
return fe->alloc_val<PairEntryVal>(m_ts.make_typespec("object"), pair, false);
}
// todo, consider splitting into method-of-object and method-of-type?

View file

@ -1750,7 +1750,7 @@ class IGen {
Instruction instr(0xf3);
instr.set_op2(0x0f);
instr.set_op3(0x2c);
instr.set_modrm_and_rex(dst.hw_id(), src.hw_id(), 3, false);
instr.set_modrm_and_rex(dst.hw_id(), src.hw_id(), 3, true);
instr.swap_op0_rex();
return instr;
}

View file

@ -58,6 +58,7 @@ void Logger::log(LoggerMessageKind kind, const char* format, ...) {
if (settings.color != COLOR_NORMAL) {
printf("\033[0m");
fflush(stdout);
}
// todo, does this make things slow?

View file

@ -111,6 +111,10 @@ struct CompilerTestRunner {
}
};
std::vector<std::string> get_test_pass_string(const std::string& name, int n_tests) {
return {fmt::format("Test \"{}\": {} Passes\n0\n", name, n_tests)};
}
} // namespace
TEST(CompilerAndRuntime, BuildGameAndTest) {
@ -157,6 +161,14 @@ TEST(CompilerAndRuntime, BuildGameAndTest) {
runner.run_test("test-insert-cons.gc", {"((c . w) (a . b) (e . f))\n0\n"});
runner.run_test("test-new-inline-array-class.gc", {"2820\n"});
runner.run_test("test-memcpy.gc", {"13\n"});
runner.run_test("test-memset.gc", {"11\n"});
runner.run_test("test-binteger-print.gc", {"-17\n0\n"});
runner.run_test("test-tests.gc", {"Test Failed On Test 0: \"unknown\"\nTest Failed On Test 0: "
"\"test\"\nTest \"test-of-test\": 1 Passes\n0\n"});
runner.run_test("test-type-arrays.gc", {"Test \"test-type-arrays\": 3 Passes\n0\n"});
runner.run_test("test-number-comparison.gc", {"Test \"number-comparison\": 14 Passes\n0\n"});
runner.run_test("test-approx-pi.gc", get_test_pass_string("approx-pi", 4));
runner.run_test("test-dynamic-type.gc", get_test_pass_string("dynamic-type", 4));
runner.print_summary();