mirror of
https://github.com/open-goal/jak-project.git
synced 2024-10-20 11:26:18 -04:00
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:
parent
27b865c0df
commit
2d11e44eaf
|
@ -9,6 +9,7 @@ TypeSystem::TypeSystem() {
|
||||||
// the "none" and "_type_" types are included by default.
|
// the "none" and "_type_" types are included by default.
|
||||||
add_type("none", std::make_unique<NullType>("none"));
|
add_type("none", std::make_unique<NullType>("none"));
|
||||||
add_type("_type_", std::make_unique<NullType>("_type_"));
|
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!
|
// newly defined!
|
||||||
|
|
||||||
// none/object get to skip these checks because they are roots.
|
// 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()) {
|
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(),
|
fmt::print("[TypeSystem] Type {} has incompletely defined parent {}\n", type->get_name(),
|
||||||
type->get_parent());
|
type->get_parent());
|
||||||
|
@ -234,8 +235,9 @@ Type* TypeSystem::lookup_type(const TypeSpec& ts) const {
|
||||||
|
|
||||||
MethodInfo TypeSystem::add_method(const std::string& type_name,
|
MethodInfo TypeSystem::add_method(const std::string& type_name,
|
||||||
const std::string& method_name,
|
const std::string& method_name,
|
||||||
const TypeSpec& ts) {
|
const TypeSpec& ts,
|
||||||
return add_method(lookup_type(make_typespec(type_name)), method_name, 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
|
* is overriding the "new" method - the TypeSystem will track that because overridden new methods
|
||||||
* may have different arguments.
|
* 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") {
|
if (method_name == "new") {
|
||||||
return add_new_method(type, ts);
|
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;
|
return existing_info;
|
||||||
} else {
|
} 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!
|
// add a new method!
|
||||||
return type->add_method({get_next_method_id(type), method_name, ts, type->get_name()});
|
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;
|
MethodInfo existing;
|
||||||
if (type->get_my_new_method(&existing)) {
|
if (type->get_my_new_method(&existing)) {
|
||||||
// it exists!
|
// it exists!
|
||||||
if (existing.type != ts) {
|
if (!existing.type.is_compatible_child_method(ts, type->get_name())) {
|
||||||
fmt::print(
|
fmt::print(
|
||||||
"[TypeSystem] The new method of {} was originally defined as {}, but has been redefined "
|
"[TypeSystem] The new method of {} was originally defined as {}, but has been redefined "
|
||||||
"as {}\n",
|
"as {}\n",
|
||||||
|
@ -487,7 +497,7 @@ int TypeSystem::add_field_to_type(StructureType* type,
|
||||||
// we need to compute the offset ourself!
|
// we need to compute the offset ourself!
|
||||||
offset = align(type->get_size_in_memory(), field_alignment);
|
offset = align(type->get_size_in_memory(), field_alignment);
|
||||||
} else {
|
} else {
|
||||||
int aligned_offset = align(type->get_size_in_memory(), field_alignment);
|
int aligned_offset = align(offset, field_alignment);
|
||||||
if (offset != aligned_offset) {
|
if (offset != aligned_offset) {
|
||||||
fmt::print(
|
fmt::print(
|
||||||
"[TypeSystem] Tried to overwrite offset of field to be {}, but it is not aligned "
|
"[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.
|
// 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"));
|
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 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
|
// BASIC
|
||||||
// we intentionally don't inherit from structure because structure's size is weird.
|
// we intentionally don't inherit from structure because structure's size is weird.
|
||||||
|
|
|
@ -54,8 +54,12 @@ class TypeSystem {
|
||||||
|
|
||||||
MethodInfo add_method(const std::string& type_name,
|
MethodInfo add_method(const std::string& type_name,
|
||||||
const std::string& method_name,
|
const std::string& method_name,
|
||||||
const TypeSpec& ts);
|
const TypeSpec& ts,
|
||||||
MethodInfo add_method(Type* type, const std::string& method_name, 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 add_new_method(Type* type, const TypeSpec& ts);
|
||||||
MethodInfo lookup_method(const std::string& type_name, const std::string& method_name);
|
MethodInfo lookup_method(const std::string& type_name, const std::string& method_name);
|
||||||
MethodInfo lookup_new_method(const std::string& type_name);
|
MethodInfo lookup_new_method(const std::string& type_name);
|
||||||
|
|
277
doc/decompilation_notes.md
Normal file
277
doc/decompilation_notes.md
Normal 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
1001
doc/goal_doc.md
Normal file
File diff suppressed because it is too large
Load diff
|
@ -70,16 +70,24 @@ set(RUNTIME_SOURCE
|
||||||
overlord/stream.cpp)
|
overlord/stream.cpp)
|
||||||
|
|
||||||
# the runtime should be built without any static/dynamic libraries.
|
# 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
|
# 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.
|
# can be used to test other things.
|
||||||
add_library(runtime ${RUNTIME_SOURCE})
|
add_library(runtime ${RUNTIME_SOURCE})
|
||||||
|
|
||||||
|
add_executable(gk main.cpp)
|
||||||
|
|
||||||
IF (WIN32)
|
IF (WIN32)
|
||||||
# set stuff for windows
|
# 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()
|
ELSE()
|
||||||
# set stuff for other systems
|
# 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()
|
ENDIF()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -143,15 +143,15 @@ void KernelCheckAndDispatch() {
|
||||||
call_goal(Ptr<Function>(kernel_dispatcher->value), 0, 0, 0, s7.offset, g_ee_main_mem);
|
call_goal(Ptr<Function>(kernel_dispatcher->value), 0, 0, 0, s7.offset, g_ee_main_mem);
|
||||||
} else {
|
} else {
|
||||||
if (ListenerFunction->value != s7.offset) {
|
if (ListenerFunction->value != s7.offset) {
|
||||||
fprintf(stderr, "Running Listener Function:\n");
|
// fprintf(stderr, "Running Listener Function:\n");
|
||||||
auto cptr = Ptr<u8>(ListenerFunction->value).c();
|
// auto cptr = Ptr<u8>(ListenerFunction->value).c();
|
||||||
for (int i = 0; i < 40; i++) {
|
// for (int i = 0; i < 40; i++) {
|
||||||
fprintf(stderr, "%x ", cptr[i]);
|
// fprintf(stderr, "%x ", cptr[i]);
|
||||||
}
|
// }
|
||||||
fprintf(stderr, "\n");
|
// fprintf(stderr, "\n");
|
||||||
auto result =
|
auto result =
|
||||||
call_goal(Ptr<Function>(ListenerFunction->value), 0, 0, 0, s7.offset, g_ee_main_mem);
|
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__
|
#ifdef __linux__
|
||||||
cprintf("%ld\n", result);
|
cprintf("%ld\n", result);
|
||||||
#else
|
#else
|
||||||
|
|
|
@ -149,7 +149,6 @@ void ProcessListenerMessage(Ptr<char> msg) {
|
||||||
// this setup allows listener function execution to clean up after itself.
|
// this setup allows listener function execution to clean up after itself.
|
||||||
ListenerFunction->value =
|
ListenerFunction->value =
|
||||||
link_and_exec(buffer, "*listener*", 0, kdebugheap, LINK_FLAG_FORCE_DEBUG).offset;
|
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.
|
return; // don't ack yet, this will happen after the function runs.
|
||||||
} break;
|
} break;
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -887,8 +887,6 @@ u64 method_set(u32 type_, u32 method_id, u32 method) {
|
||||||
if (method_id > 127)
|
if (method_id > 127)
|
||||||
printf("[METHOD SET ERROR] tried to set method %d\n", method_id);
|
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;
|
auto existing_method = type->get_method(method_id).offset;
|
||||||
|
|
||||||
if (method == 1) {
|
if (method == 1) {
|
||||||
|
|
|
@ -194,8 +194,8 @@ void Deci2Server::run() {
|
||||||
}
|
}
|
||||||
|
|
||||||
auto* hdr = (Deci2Header*)(buffer);
|
auto* hdr = (Deci2Header*)(buffer);
|
||||||
fprintf(stderr, "[DECI2] Got message:\n");
|
fprintf(stderr, "[DECI2] Got message: %d %d 0x%x %c -> %c\n", hdr->len, hdr->rsvd, hdr->proto,
|
||||||
fprintf(stderr, " %d %d 0x%x %c -> %c\n", hdr->len, hdr->rsvd, hdr->proto, hdr->src, hdr->dst);
|
hdr->src, hdr->dst);
|
||||||
|
|
||||||
hdr->rsvd = got;
|
hdr->rsvd = got;
|
||||||
|
|
||||||
|
|
|
@ -92,6 +92,12 @@
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
(defmacro shutdown-target ()
|
||||||
|
`(begin
|
||||||
|
(reset-target :shutdown)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;
|
||||||
;; GOAL Syntax
|
;; GOAL Syntax
|
||||||
|
@ -366,3 +372,34 @@
|
||||||
`(the ,(current-method-type) ((-> object method-table 0) allocation type-to-make ,@sz))
|
`(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*)
|
||||||
|
)
|
|
@ -71,7 +71,7 @@
|
||||||
(define-extern load (function string kheap object))
|
(define-extern load (function string kheap object))
|
||||||
(define-extern loado (function string kheap object))
|
(define-extern loado (function string kheap object))
|
||||||
(define-extern unload (function string none))
|
(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 malloc (function kheap int pointer))
|
||||||
(define-extern kmalloc (function kheap int int string))
|
(define-extern kmalloc (function kheap int int string))
|
||||||
(define-extern new-dynamic-structure (function kheap type int structure))
|
(define-extern new-dynamic-structure (function kheap type int structure))
|
||||||
|
|
|
@ -301,9 +301,9 @@
|
||||||
(object-type object))
|
(object-type object))
|
||||||
(until (eq? (set! basics-type (-> basics-type parent)) object-type)
|
(until (eq? (set! basics-type (-> basics-type parent)) object-type)
|
||||||
(if (eq? basics-type input-type)
|
(if (eq? basics-type input-type)
|
||||||
;; return-from #f will return from the function with the value of #t
|
;; return-from #f will return from the function with the value of #t
|
||||||
(return-from #f #t)
|
(return-from #f #t)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
#f ;; didn't find it, return false
|
#f ;; didn't find it, return false
|
||||||
|
@ -315,8 +315,8 @@
|
||||||
;; it's not clear why a might be zero?
|
;; it's not clear why a might be zero?
|
||||||
;; perhaps if the type system is not yet initialized fully for the type?
|
;; perhaps if the type system is not yet initialized fully for the type?
|
||||||
(if (or (eq? a b) (zero? a))
|
(if (or (eq? a b) (zero? a))
|
||||||
(return-from #f #t)
|
(return-from #f #t)
|
||||||
)
|
)
|
||||||
(set! a (-> a parent))
|
(set! a (-> a parent))
|
||||||
)
|
)
|
||||||
#f
|
#f
|
||||||
|
@ -330,19 +330,19 @@
|
||||||
(let* ((child-method (-> the-type method-table method-id))
|
(let* ((child-method (-> the-type method-table method-id))
|
||||||
(parent-method child-method)
|
(parent-method child-method)
|
||||||
)
|
)
|
||||||
|
|
||||||
;; keep looking until we find a different parent method
|
;; keep looking until we find a different parent method
|
||||||
(until (not (eq? parent-method child-method))
|
(until (not (eq? parent-method child-method))
|
||||||
;; at the top of the type tree.
|
;; at the top of the type tree.
|
||||||
(if (eq? the-type object)
|
(if (eq? the-type object)
|
||||||
(return-from #f nothing)
|
(return-from #f nothing)
|
||||||
)
|
)
|
||||||
|
|
||||||
(set! the-type (-> the-type parent))
|
(set! the-type (-> the-type parent))
|
||||||
(set! parent-method (-> the-type method-table method-id))
|
(set! parent-method (-> the-type method-table method-id))
|
||||||
(if (eq? 0 (the int parent-method))
|
(if (eq? 0 (the int parent-method))
|
||||||
(return-from #f nothing)
|
(return-from #f nothing)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
parent-method
|
parent-method
|
||||||
)
|
)
|
||||||
|
@ -362,17 +362,17 @@
|
||||||
(defmethod length pair ((obj pair))
|
(defmethod length pair ((obj pair))
|
||||||
"Get the number of elements in a proper list"
|
"Get the number of elements in a proper list"
|
||||||
(if (eq? obj '())
|
(if (eq? obj '())
|
||||||
(return-from #f 0)
|
(return-from #f 0)
|
||||||
)
|
)
|
||||||
|
|
||||||
(let ((lst (cdr obj))
|
(let ((lst (cdr obj))
|
||||||
(len 1))
|
(len 1))
|
||||||
(while (and (not (eq? lst '()))
|
(while (and (not (eq? lst '()))
|
||||||
(pair? lst)
|
(pair? lst)
|
||||||
)
|
)
|
||||||
(+1! len)
|
(+1! len)
|
||||||
(set! lst (cdr lst))
|
(set! lst (cdr lst))
|
||||||
)
|
)
|
||||||
len)
|
len)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -386,8 +386,8 @@
|
||||||
(defun last ((obj object))
|
(defun last ((obj object))
|
||||||
"Get the last pair in a list."
|
"Get the last pair in a list."
|
||||||
(while (not (eq? (cdr obj) '()))
|
(while (not (eq? (cdr obj) '()))
|
||||||
(set! obj (cdr obj))
|
(set! obj (cdr obj))
|
||||||
)
|
)
|
||||||
obj
|
obj
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -396,42 +396,42 @@
|
||||||
if not, return #f."
|
if not, return #f."
|
||||||
(while (and (not (eq? lst '()))
|
(while (and (not (eq? lst '()))
|
||||||
(not (eq? (car lst) obj)))
|
(not (eq? (car lst) obj)))
|
||||||
(set! lst (cdr lst))
|
(set! lst (cdr lst))
|
||||||
)
|
|
||||||
|
|
||||||
(if (eq? lst '())
|
|
||||||
#f
|
|
||||||
lst
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
(if (eq? lst '())
|
||||||
|
#f
|
||||||
|
lst
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
(define-extern name= (function basic basic symbol))
|
(define-extern name= (function basic basic symbol))
|
||||||
|
|
||||||
(defun nmember ((obj basic) (lst object))
|
(defun nmember ((obj basic) (lst object))
|
||||||
"If obj is a member of the list, return the pair containing obj as its car.
|
"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."
|
If not, return #f. Use name= (see gstring.gc) to check equality."
|
||||||
(while (and (not (eq? lst '()))
|
(while (and (not (eq? lst '()))
|
||||||
(not (name= (the basic (car lst)) obj))
|
(not (name= (the basic (car lst)) obj))
|
||||||
)
|
)
|
||||||
(set! lst (cdr lst))
|
(set! lst (cdr lst))
|
||||||
)
|
)
|
||||||
|
|
||||||
(if (eq? lst '())
|
(if (eq? lst '())
|
||||||
#f
|
#f
|
||||||
lst
|
lst
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
(defun assoc ((item object) (alst object))
|
(defun assoc ((item object) (alst object))
|
||||||
"Get a pair with car of item from the association list (list of pairs) alst."
|
"Get a pair with car of item from the association list (list of pairs) alst."
|
||||||
(while (and (not (null? alst))
|
(while (and (not (null? alst))
|
||||||
(not (eq? (caar alst) item)))
|
(not (eq? (caar alst) item)))
|
||||||
(set! alst (cdr alst))
|
(set! alst (cdr alst))
|
||||||
)
|
|
||||||
(if (not (null? alst))
|
|
||||||
(car alst)
|
|
||||||
#f
|
|
||||||
)
|
)
|
||||||
|
(if (not (null? alst))
|
||||||
|
(car alst)
|
||||||
|
#f
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
(defun assoce ((item object) (alst object))
|
(defun assoce ((item object) (alst object))
|
||||||
|
@ -440,12 +440,12 @@
|
||||||
(not (eq? (caar alst) item))
|
(not (eq? (caar alst) item))
|
||||||
(not (eq? (caar alst) 'else))
|
(not (eq? (caar alst) 'else))
|
||||||
)
|
)
|
||||||
(set! alst (cdr alst))
|
(set! alst (cdr alst))
|
||||||
)
|
|
||||||
(if (not (null? alst))
|
|
||||||
(car alst)
|
|
||||||
#f
|
|
||||||
)
|
)
|
||||||
|
(if (not (null? alst))
|
||||||
|
(car alst)
|
||||||
|
#f
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
;; todo
|
;; todo
|
||||||
|
@ -455,20 +455,20 @@
|
||||||
(defun append! ((front object) (back object))
|
(defun append! ((front object) (back object))
|
||||||
"Append back to front."
|
"Append back to front."
|
||||||
(if (null? front)
|
(if (null? front)
|
||||||
(return-from #f back)
|
(return-from #f back)
|
||||||
)
|
)
|
||||||
|
|
||||||
(let ((lst front))
|
(let ((lst front))
|
||||||
;; seek to the end of front
|
;; seek to the end of front
|
||||||
(while (not (null? (cdr lst)))
|
(while (not (null? (cdr lst)))
|
||||||
(set! lst (cdr lst))
|
(set! lst (cdr lst))
|
||||||
)
|
)
|
||||||
|
|
||||||
;; this check seems not needed
|
;; this check seems not needed
|
||||||
(if (not (null? lst))
|
(if (not (null? lst))
|
||||||
(set! (cdr lst) back)
|
(set! (cdr lst) back)
|
||||||
)
|
)
|
||||||
|
|
||||||
front
|
front
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -477,21 +477,21 @@
|
||||||
"Delete the first occurance of item from a list and return the list.
|
"Delete the first occurance of item from a list and return the list.
|
||||||
Does nothing if the item isn't in the list."
|
Does nothing if the item isn't in the list."
|
||||||
(if (eq? (car lst) item)
|
(if (eq? (car lst) item)
|
||||||
(return-from #f (cdr lst))
|
(return-from #f (cdr lst))
|
||||||
)
|
)
|
||||||
|
|
||||||
(let ((iter (cdr lst))
|
(let ((iter (cdr lst))
|
||||||
(rep lst))
|
(rep lst))
|
||||||
|
|
||||||
(while (and (not (null? iter))
|
(while (and (not (null? iter))
|
||||||
(not (eq? (car iter) item)))
|
(not (eq? (car iter) item)))
|
||||||
(set! rep iter)
|
(set! rep iter)
|
||||||
(set! iter (cdr iter))
|
(set! iter (cdr iter))
|
||||||
)
|
|
||||||
|
|
||||||
(if (not (null? iter))
|
|
||||||
(set! (cdr rep) (cdr iter))
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
(if (not (null? iter))
|
||||||
|
(set! (cdr rep) (cdr iter))
|
||||||
|
)
|
||||||
)
|
)
|
||||||
(the pair lst)
|
(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."
|
"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)
|
;(format #t "call to delete car: ~A ~A~%" item lst)
|
||||||
(if (eq? (caar lst) item)
|
(if (eq? (caar lst) item)
|
||||||
(return-from #f (cdr lst))
|
(return-from #f (cdr lst))
|
||||||
)
|
)
|
||||||
|
|
||||||
(let ((rep lst)
|
(let ((rep lst)
|
||||||
(iter (cdr lst)))
|
(iter (cdr lst)))
|
||||||
(while (and (not (null? iter))
|
(while (and (not (null? iter))
|
||||||
(not (eq? (caar iter) item)))
|
(not (eq? (caar iter) item)))
|
||||||
(set! rep iter)
|
(set! rep iter)
|
||||||
(set! iter (cdr iter))
|
(set! iter (cdr iter))
|
||||||
)
|
|
||||||
|
|
||||||
(if (not (null? iter))
|
|
||||||
(set! (cdr rep) (cdr iter))
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
(if (not (null? iter))
|
||||||
|
(set! (cdr rep) (cdr iter))
|
||||||
|
)
|
||||||
)
|
)
|
||||||
lst
|
lst
|
||||||
)
|
)
|
||||||
|
@ -523,7 +523,7 @@
|
||||||
(cons kv (delete-car! (car kv) alst))
|
(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
|
"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."
|
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.
|
;; 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)
|
((length int32 :offset-assert 4)
|
||||||
(allocated-length int32 :offset-assert 8)
|
(allocated-length int32 :offset-assert 8)
|
||||||
(data uint8 :dynamic)
|
(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
|
"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"
|
of the type-to-make to determine the element size"
|
||||||
(let* ((sz (+ (-> type-to-make size) (* (-> type-to-make heap-base) cnt)))
|
(let* ((sz (+ (-> type-to-make size) (* (-> type-to-make heap-base) cnt)))
|
||||||
|
@ -620,11 +621,24 @@
|
||||||
)
|
)
|
||||||
|
|
||||||
(while (< i size)
|
(while (< i size)
|
||||||
(set! (-> (the (pointer uint8) d) 0) (-> (the (pointer uint8) s) 0))
|
(set! (-> (the (pointer uint8) d) 0) (-> (the (pointer uint8) s) 0))
|
||||||
(&+! d 1)
|
(&+! d 1)
|
||||||
(&+! s 1)
|
(&+! s 1)
|
||||||
(+1! i)
|
(+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
|
dst
|
||||||
)
|
)
|
49
goal_src/test/test-approx-pi.gc
Normal file
49
goal_src/test/test-approx-pi.gc
Normal 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)
|
2
goal_src/test/test-binteger-print.gc
Normal file
2
goal_src/test/test-binteger-print.gc
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
(format #t "~A~%" (the binteger -17))
|
||||||
|
0
|
64
goal_src/test/test-dynamic-type.gc
Normal file
64
goal_src/test/test-dynamic-type.gc
Normal 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)
|
|
@ -1,4 +1,3 @@
|
||||||
(define-extern _format function)
|
|
||||||
(define format _format)
|
(define format _format)
|
||||||
|
|
||||||
(format #t "test ~D ~D ~D ~D ~D ~D~%" 1 2 3 4 5 6)
|
(format #t "test ~D ~D ~D ~D ~D ~D~%" 1 2 3 4 5 6)
|
||||||
|
|
18
goal_src/test/test-memset.gc
Normal file
18
goal_src/test/test-memset.gc
Normal 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)
|
||||||
|
)
|
|
@ -1,4 +1,3 @@
|
||||||
(define-extern _format function)
|
|
||||||
(define format _format)
|
(define format _format)
|
||||||
|
|
||||||
(defun float-testing-function-2 ((x float) (y float))
|
(defun float-testing-function-2 ((x float) (y float))
|
||||||
|
|
20
goal_src/test/test-number-comparison.gc
Normal file
20
goal_src/test/test-number-comparison.gc
Normal 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)
|
|
@ -1,4 +1,3 @@
|
||||||
(define-extern _format function)
|
|
||||||
(define format _format)
|
(define format _format)
|
||||||
;(db)
|
;(db)
|
||||||
|
|
||||||
|
|
8
goal_src/test/test-tests.gc
Normal file
8
goal_src/test/test-tests.gc
Normal 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)
|
74
goal_src/test/test-type-arrays.gc
Normal file
74
goal_src/test/test-type-arrays.gc
Normal 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)
|
|
@ -82,7 +82,7 @@ class Compiler {
|
||||||
RegVal* function,
|
RegVal* function,
|
||||||
const std::vector<RegVal*>& args,
|
const std::vector<RegVal*>& args,
|
||||||
Env* env,
|
Env* env,
|
||||||
std::string method_type_name = "");
|
const std::string& method_type_name = "");
|
||||||
|
|
||||||
TypeSystem m_ts;
|
TypeSystem m_ts;
|
||||||
std::unique_ptr<GlobalEnv> m_global_env = nullptr;
|
std::unique_ptr<GlobalEnv> m_global_env = nullptr;
|
||||||
|
|
|
@ -9,6 +9,8 @@ CompilerSettings::CompilerSettings() {
|
||||||
|
|
||||||
m_settings["disable-math-const-prop"].kind = SettingKind::BOOL;
|
m_settings["disable-math-const-prop"].kind = SettingKind::BOOL;
|
||||||
m_settings["disable-math-const-prop"].boolp = &disable_math_const_prop;
|
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) {
|
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) {
|
if (kv->second.boolp) {
|
||||||
*kv->second.boolp = !(value.is_symbol() && value.as_symbol()->name == "#f");
|
*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;
|
||||||
}
|
}
|
|
@ -14,10 +14,12 @@ class CompilerSettings {
|
||||||
bool debug_print_regalloc = false;
|
bool debug_print_regalloc = false;
|
||||||
bool disable_math_const_prop = false;
|
bool disable_math_const_prop = false;
|
||||||
bool emit_move_after_return = true;
|
bool emit_move_after_return = true;
|
||||||
|
bool print_timing = false;
|
||||||
|
|
||||||
void set(const std::string& name, const goos::Object& value);
|
void set(const std::string& name, const goos::Object& value);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void link(bool& val, const std::string& name);
|
||||||
enum class SettingKind { BOOL, INVALID };
|
enum class SettingKind { BOOL, INVALID };
|
||||||
|
|
||||||
struct SettingsEntry {
|
struct SettingsEntry {
|
||||||
|
|
|
@ -287,6 +287,7 @@ std::string IR_FunctionCall::print() {
|
||||||
RegAllocInstr IR_FunctionCall::to_rai() {
|
RegAllocInstr IR_FunctionCall::to_rai() {
|
||||||
RegAllocInstr rai;
|
RegAllocInstr rai;
|
||||||
rai.read.push_back(m_func->ireg());
|
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());
|
rai.write.push_back(m_ret->ireg());
|
||||||
for (auto& arg : m_args) {
|
for (auto& arg : m_args) {
|
||||||
rai.read.push_back(arg->ireg());
|
rai.read.push_back(arg->ireg());
|
||||||
|
@ -299,8 +300,6 @@ RegAllocInstr IR_FunctionCall::to_rai() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo, clobber call reg?
|
|
||||||
|
|
||||||
return rai;
|
return rai;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -326,6 +325,7 @@ void IR_FunctionCall::do_codegen(emitter::ObjectGenerator* gen,
|
||||||
auto freg = get_reg(m_func, allocs, irec);
|
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::add_gpr64_gpr64(freg, emitter::gRegInfo.get_offset_reg()), irec);
|
||||||
gen->add_instr(IGen::call_r64(freg), 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());
|
return fmt::format("divss {}, {}", m_dest->print(), m_arg->print());
|
||||||
case FloatMathKind::MUL_SS:
|
case FloatMathKind::MUL_SS:
|
||||||
return fmt::format("mulss {}, {}", m_dest->print(), m_arg->print());
|
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:
|
default:
|
||||||
throw std::runtime_error("Unsupported FloatMathKind");
|
throw std::runtime_error("Unsupported FloatMathKind");
|
||||||
}
|
}
|
||||||
|
@ -533,6 +537,14 @@ void IR_FloatMath::do_codegen(emitter::ObjectGenerator* gen,
|
||||||
gen->add_instr(
|
gen->add_instr(
|
||||||
IGen::mulss_xmm_xmm(get_reg(m_dest, allocs, irec), get_reg(m_arg, allocs, irec)), irec);
|
IGen::mulss_xmm_xmm(get_reg(m_dest, allocs, irec), get_reg(m_arg, allocs, irec)), irec);
|
||||||
break;
|
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:
|
default:
|
||||||
assert(false);
|
assert(false);
|
||||||
}
|
}
|
||||||
|
@ -664,7 +676,9 @@ void IR_ConditionalBranch::do_codegen(emitter::ObjectGenerator* gen,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (condition.is_float) {
|
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 {
|
} else {
|
||||||
gen->add_instr(IGen::cmp_gpr64_gpr64(get_reg(condition.a, allocs, irec),
|
gen->add_instr(IGen::cmp_gpr64_gpr64(get_reg(condition.a, allocs, irec),
|
||||||
get_reg(condition.b, allocs, irec)),
|
get_reg(condition.b, allocs, irec)),
|
||||||
|
@ -790,3 +804,51 @@ void IR_FunctionStart::do_codegen(emitter::ObjectGenerator* gen,
|
||||||
(void)allocs;
|
(void)allocs;
|
||||||
(void)irec;
|
(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);
|
||||||
|
}
|
|
@ -223,7 +223,7 @@ class IR_IntegerMath : public IR {
|
||||||
RegVal* m_arg;
|
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 {
|
class IR_FloatMath : public IR {
|
||||||
public:
|
public:
|
||||||
|
@ -325,4 +325,32 @@ class IR_FunctionStart : public IR {
|
||||||
std::vector<RegVal*> m_args;
|
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
|
#endif // JAK_IR_H
|
||||||
|
|
|
@ -87,6 +87,11 @@ RegVal* LambdaVal::to_reg(Env* fe) {
|
||||||
return re;
|
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) {
|
RegVal* FloatConstantVal::to_reg(Env* fe) {
|
||||||
auto re = fe->make_xmm(coerce_to_reg_type(m_ts));
|
auto re = fe->make_xmm(coerce_to_reg_type(m_ts));
|
||||||
fe->emit(std::make_unique<IR_StaticVarLoad>(re, m_value));
|
fe->emit(std::make_unique<IR_StaticVarLoad>(re, m_value));
|
||||||
|
|
|
@ -44,9 +44,12 @@ class Val {
|
||||||
|
|
||||||
const TypeSpec& type() const { return m_ts; }
|
const TypeSpec& type() const { return m_ts; }
|
||||||
void set_type(TypeSpec ts) { m_ts = std::move(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:
|
protected:
|
||||||
TypeSpec m_ts;
|
TypeSpec m_ts;
|
||||||
|
bool m_is_settable = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
@ -117,6 +120,14 @@ class LambdaVal : public Val {
|
||||||
RegVal* to_reg(Env* fe) override;
|
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 {
|
class StaticVal : public Val {
|
||||||
public:
|
public:
|
||||||
StaticVal(StaticObject* _obj, TypeSpec _ts) : Val(std::move(_ts)), obj(_obj) {}
|
StaticVal(StaticObject* _obj, TypeSpec _ts) : Val(std::move(_ts)), obj(_obj) {}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*!
|
/*!
|
||||||
* @file Atoms.cpp
|
* @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"
|
#include "goalc/compiler/Compiler.h"
|
||||||
|
@ -20,41 +20,38 @@ static const std::unordered_map<
|
||||||
// {".jmp", &Compiler::compile_asm},
|
// {".jmp", &Compiler::compile_asm},
|
||||||
// {".sub", &Compiler::compile_asm},
|
// {".sub", &Compiler::compile_asm},
|
||||||
// {".ret-reg", &Compiler::compile_asm},
|
// {".ret-reg", &Compiler::compile_asm},
|
||||||
//
|
|
||||||
// // BLOCK FORMS
|
// BLOCK FORMS
|
||||||
{"top-level", &Compiler::compile_top_level},
|
{"top-level", &Compiler::compile_top_level},
|
||||||
{"begin", &Compiler::compile_begin},
|
{"begin", &Compiler::compile_begin},
|
||||||
{"block", &Compiler::compile_block},
|
{"block", &Compiler::compile_block},
|
||||||
{"return-from", &Compiler::compile_return_from},
|
{"return-from", &Compiler::compile_return_from},
|
||||||
{"label", &Compiler::compile_label},
|
{"label", &Compiler::compile_label},
|
||||||
{"goto", &Compiler::compile_goto},
|
{"goto", &Compiler::compile_goto},
|
||||||
//
|
|
||||||
// // COMPILER CONTROL
|
// COMPILER CONTROL
|
||||||
{"gs", &Compiler::compile_gs},
|
{"gs", &Compiler::compile_gs},
|
||||||
{":exit", &Compiler::compile_exit},
|
{":exit", &Compiler::compile_exit},
|
||||||
{"asm-file", &Compiler::compile_asm_file},
|
{"asm-file", &Compiler::compile_asm_file},
|
||||||
{"listen-to-target", &Compiler::compile_listen_to_target},
|
{"listen-to-target", &Compiler::compile_listen_to_target},
|
||||||
{"reset-target", &Compiler::compile_reset_target},
|
{"reset-target", &Compiler::compile_reset_target},
|
||||||
{":status", &Compiler::compile_poke},
|
{":status", &Compiler::compile_poke},
|
||||||
// {"test", &Compiler::compile_test},
|
|
||||||
{"in-package", &Compiler::compile_in_package},
|
{"in-package", &Compiler::compile_in_package},
|
||||||
//
|
|
||||||
// // CONDITIONAL COMPILATION
|
// CONDITIONAL COMPILATION
|
||||||
{"#cond", &Compiler::compile_gscond},
|
{"#cond", &Compiler::compile_gscond},
|
||||||
{"defglobalconstant", &Compiler::compile_defglobalconstant},
|
{"defglobalconstant", &Compiler::compile_defglobalconstant},
|
||||||
{"seval", &Compiler::compile_seval},
|
{"seval", &Compiler::compile_seval},
|
||||||
//
|
|
||||||
// // CONTROL FLOW
|
// CONTROL FLOW
|
||||||
{"cond", &Compiler::compile_cond},
|
{"cond", &Compiler::compile_cond},
|
||||||
{"when-goto", &Compiler::compile_when_goto},
|
{"when-goto", &Compiler::compile_when_goto},
|
||||||
//
|
|
||||||
// // DEFINITION
|
// DEFINITION
|
||||||
{"define", &Compiler::compile_define},
|
{"define", &Compiler::compile_define},
|
||||||
{"define-extern", &Compiler::compile_define_extern},
|
{"define-extern", &Compiler::compile_define_extern},
|
||||||
{"set!", &Compiler::compile_set},
|
{"set!", &Compiler::compile_set},
|
||||||
// {"defun-extern", &Compiler::compile_defun_extern},
|
|
||||||
// {"declare-method", &Compiler::compile_declare_method},
|
|
||||||
//
|
|
||||||
// TYPE
|
// TYPE
|
||||||
{"deftype", &Compiler::compile_deftype},
|
{"deftype", &Compiler::compile_deftype},
|
||||||
{"defmethod", &Compiler::compile_defmethod},
|
{"defmethod", &Compiler::compile_defmethod},
|
||||||
|
@ -68,30 +65,24 @@ static const std::unordered_map<
|
||||||
{"car", &Compiler::compile_car},
|
{"car", &Compiler::compile_car},
|
||||||
{"cdr", &Compiler::compile_cdr},
|
{"cdr", &Compiler::compile_cdr},
|
||||||
{"method", &Compiler::compile_method},
|
{"method", &Compiler::compile_method},
|
||||||
//
|
|
||||||
//
|
// LAMBDA
|
||||||
// // LAMBDA
|
|
||||||
{"lambda", &Compiler::compile_lambda},
|
{"lambda", &Compiler::compile_lambda},
|
||||||
{"declare", &Compiler::compile_declare},
|
{"declare", &Compiler::compile_declare},
|
||||||
{"inline", &Compiler::compile_inline},
|
{"inline", &Compiler::compile_inline},
|
||||||
// {"with-inline", &Compiler::compile_with_inline},
|
// {"with-inline", &Compiler::compile_with_inline},
|
||||||
// {"rlet", &Compiler::compile_rlet},
|
// {"rlet", &Compiler::compile_rlet},
|
||||||
// {"get-ra-ptr", &Compiler::compile_get_ra_ptr},
|
// {"get-ra-ptr", &Compiler::compile_get_ra_ptr},
|
||||||
//
|
|
||||||
//
|
// MACRO
|
||||||
//
|
|
||||||
// // MACRO
|
|
||||||
//
|
|
||||||
{"quote", &Compiler::compile_quote},
|
{"quote", &Compiler::compile_quote},
|
||||||
{"mlet", &Compiler::compile_mlet},
|
{"mlet", &Compiler::compile_mlet},
|
||||||
// {"defconstant", &Compiler::compile_defconstant},
|
// {"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_add},
|
||||||
{"-", &Compiler::compile_sub},
|
{"-", &Compiler::compile_sub},
|
||||||
{"*", &Compiler::compile_mul},
|
{"*", &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},
|
{"<", &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},
|
|
||||||
|
|
||||||
//
|
// BUILDER (build-dgo/build-cgo?)
|
||||||
// // temporary testing hacks...
|
{"build-dgos", &Compiler::compile_build_dgo},
|
||||||
// {"send-test", &Compiler::compile_send_test_data},
|
|
||||||
|
// UTIL
|
||||||
|
{"set-config!", &Compiler::compile_set_config},
|
||||||
};
|
};
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
@ -148,6 +135,12 @@ Val* Compiler::compile(const goos::Object& code, Env* env) {
|
||||||
return get_none();
|
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) {
|
Val* Compiler::compile_pair(const goos::Object& code, Env* env) {
|
||||||
auto pair = code.as_pair();
|
auto pair = code.as_pair();
|
||||||
auto head = pair->car;
|
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);
|
return ((*this).*(kv_gfs->second))(code, rest, env);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// next try as a macro
|
||||||
goos::Object macro_obj;
|
goos::Object macro_obj;
|
||||||
if (try_getting_macro_from_goos(head, ¯o_obj)) {
|
if (try_getting_macro_from_goos(head, ¯o_obj)) {
|
||||||
return compile_goos_macro(code, macro_obj, rest, env);
|
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);
|
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) {
|
Val* Compiler::compile_integer(const goos::Object& code, Env* env) {
|
||||||
|
assert(code.is_int());
|
||||||
return compile_integer(code.integer_obj.value, env);
|
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) {
|
Val* Compiler::compile_integer(s64 value, Env* env) {
|
||||||
auto fe = get_parent_env_of_type<FunctionEnv>(env);
|
auto fe = get_parent_env_of_type<FunctionEnv>(env);
|
||||||
return fe->alloc_val<IntegerConstantVal>(m_ts.make_typespec("int"), value);
|
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) {
|
SymbolVal* Compiler::compile_get_sym_obj(const std::string& name, Env* env) {
|
||||||
auto fe = get_parent_env_of_type<FunctionEnv>(env);
|
auto fe = get_parent_env_of_type<FunctionEnv>(env);
|
||||||
return fe->alloc_val<SymbolVal>(name, m_ts.make_typespec("symbol"));
|
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) {
|
Val* Compiler::compile_symbol(const goos::Object& form, Env* env) {
|
||||||
auto name = symbol_string(form);
|
auto name = symbol_string(form);
|
||||||
|
|
||||||
|
// special case to get "nothing", used as a return value when nothing should be returned.
|
||||||
if (name == "none") {
|
if (name == "none") {
|
||||||
return get_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);
|
auto mlet_env = get_parent_env_of_type<SymbolMacroEnv>(env);
|
||||||
while (mlet_env) {
|
while (mlet_env) {
|
||||||
auto mlkv = mlet_env->macros.find(form.as_symbol());
|
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());
|
mlet_env = get_parent_env_of_type<SymbolMacroEnv>(mlet_env->parent());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// see if it's a local variable
|
||||||
auto lexical = env->lexical_lookup(form);
|
auto lexical = env->lexical_lookup(form);
|
||||||
if (lexical) {
|
if (lexical) {
|
||||||
return 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 global_constant = m_global_constants.find(form.as_symbol());
|
||||||
auto existing_symbol = m_symbol_types.find(form.as_symbol()->name);
|
auto existing_symbol = m_symbol_types.find(form.as_symbol()->name);
|
||||||
|
|
||||||
|
// see if it's a constant
|
||||||
if (global_constant != m_global_constants.end()) {
|
if (global_constant != m_global_constants.end()) {
|
||||||
// check there is no symbol with the same name
|
// check there is no symbol with the same name
|
||||||
if (existing_symbol != m_symbol_types.end()) {
|
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);
|
return compile_error_guard(global_constant->second, env);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// none of those, so get a global symbol.
|
||||||
return compile_get_symbol_value(name, env);
|
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);
|
* Compile a string constant. The constant is placed in the same segment as the parent function.
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
Val* Compiler::compile_string(const goos::Object& form, Env* env) {
|
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) {
|
Val* Compiler::compile_string(const std::string& str, Env* env, int seg) {
|
||||||
auto obj = std::make_unique<StaticString>(str, seg);
|
auto obj = std::make_unique<StaticString>(str, seg);
|
||||||
auto fe = get_parent_env_of_type<FunctionEnv>(env);
|
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;
|
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) {
|
Val* Compiler::compile_float(const goos::Object& code, Env* env) {
|
||||||
assert(code.is_float());
|
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) {
|
Val* Compiler::compile_float(float value, Env* env, int seg) {
|
||||||
auto obj = std::make_unique<StaticFloat>(value, seg);
|
auto obj = std::make_unique<StaticFloat>(value, seg);
|
||||||
auto fe = get_parent_env_of_type<FunctionEnv>(env);
|
auto fe = get_parent_env_of_type<FunctionEnv>(env);
|
||||||
|
|
|
@ -1,26 +1,50 @@
|
||||||
|
/*!
|
||||||
|
* @file Block.cpp
|
||||||
|
* Compiler implementation for blocks / gotos / labels
|
||||||
|
*/
|
||||||
|
|
||||||
#include "goalc/compiler/Compiler.h"
|
#include "goalc/compiler/Compiler.h"
|
||||||
#include "goalc/compiler/IR.h"
|
#include "goalc/compiler/IR.h"
|
||||||
|
|
||||||
using namespace goos;
|
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) {
|
Val* Compiler::compile_top_level(const goos::Object& form, const goos::Object& rest, Env* env) {
|
||||||
return compile_begin(form, rest, 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) {
|
Val* Compiler::compile_begin(const goos::Object& form, const goos::Object& rest, Env* env) {
|
||||||
(void)form;
|
(void)form;
|
||||||
Val* result = get_none();
|
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;
|
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) {
|
Val* Compiler::compile_block(const goos::Object& form, const goos::Object& _rest, Env* env) {
|
||||||
auto rest = &_rest;
|
auto rest = &_rest;
|
||||||
auto name = pair_car(*rest);
|
auto name = pair_car(*rest);
|
||||||
rest = &pair_cdr(*rest);
|
rest = &pair_cdr(*rest);
|
||||||
|
|
||||||
if (!rest->is_pair()) {
|
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);
|
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));
|
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
|
// 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
|
// set it. for now it has a type of none, but we will set it after compiling the block.
|
||||||
// block.
|
// TODO - determine if GOAL blocks _always_ return gprs, or if it's possible to return xmms.
|
||||||
// block_env->return_value = env->alloc_reg(get_base_typespec("none"));
|
|
||||||
block_env->return_value = env->make_gpr(m_ts.make_typespec("none"));
|
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...)
|
// 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
|
// compile everything in the body
|
||||||
Val* result = get_none();
|
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
|
// 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
|
// 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.
|
// an xmm register, which is likely to eliminate a gpr->xmm move.
|
||||||
|
// TODO - does this happen in GOAL?
|
||||||
if (block_env->return_types.empty()) {
|
if (block_env->return_types.empty()) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -71,6 +100,11 @@ Val* Compiler::compile_block(const goos::Object& form, const goos::Object& _rest
|
||||||
return block_env->return_value;
|
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) {
|
Val* Compiler::compile_return_from(const goos::Object& form, const goos::Object& _rest, Env* env) {
|
||||||
const Object* rest = &_rest;
|
const Object* rest = &_rest;
|
||||||
auto block_name = symbol_string(pair_car(*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));
|
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);
|
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));
|
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();
|
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) {
|
Val* Compiler::compile_label(const goos::Object& form, const goos::Object& rest, Env* env) {
|
||||||
auto label_name = symbol_string(pair_car(rest));
|
auto label_name = symbol_string(pair_car(rest));
|
||||||
expect_empty_list(pair_cdr(rest));
|
expect_empty_list(pair_cdr(rest));
|
||||||
|
|
||||||
// make sure we don't have a label with this name already
|
// 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& labels = env->get_label_map();
|
||||||
auto kv = labels.find(label_name);
|
auto kv = labels.find(label_name);
|
||||||
if (kv != labels.end()) {
|
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");
|
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);
|
auto func_env = get_parent_env_of_type<FunctionEnv>(env);
|
||||||
labels[label_name] = Label(func_env, func_env->code().size());
|
labels[label_name] = Label(func_env, func_env->code().size());
|
||||||
return get_none();
|
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) {
|
Val* Compiler::compile_goto(const goos::Object& form, const goos::Object& rest, Env* env) {
|
||||||
(void)form;
|
(void)form;
|
||||||
auto label_name = symbol_string(pair_car(rest));
|
auto label_name = symbol_string(pair_car(rest));
|
||||||
|
|
|
@ -1,9 +1,18 @@
|
||||||
|
/*!
|
||||||
|
* @file CompilerControl.cpp
|
||||||
|
* Compiler implementation for forms which actually control the compiler.
|
||||||
|
*/
|
||||||
|
|
||||||
#include "goalc/compiler/Compiler.h"
|
#include "goalc/compiler/Compiler.h"
|
||||||
#include "goalc/compiler/IR.h"
|
#include "goalc/compiler/IR.h"
|
||||||
#include "common/util/Timer.h"
|
#include "common/util/Timer.h"
|
||||||
#include "common/util/DgoWriter.h"
|
#include "common/util/DgoWriter.h"
|
||||||
#include "common/util/FileUtil.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) {
|
Val* Compiler::compile_exit(const goos::Object& form, const goos::Object& rest, Env* env) {
|
||||||
(void)env;
|
(void)env;
|
||||||
auto args = get_va(form, rest);
|
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()) {
|
if (m_listener.is_connected()) {
|
||||||
m_listener.send_reset(false);
|
m_listener.send_reset(false);
|
||||||
}
|
}
|
||||||
|
// flag for the REPL.
|
||||||
m_want_exit = true;
|
m_want_exit = true;
|
||||||
return get_none();
|
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) {
|
Val* Compiler::compile_seval(const goos::Object& form, const goos::Object& rest, Env* env) {
|
||||||
(void)env;
|
(void)env;
|
||||||
try {
|
try {
|
||||||
|
@ -27,6 +41,10 @@ Val* Compiler::compile_seval(const goos::Object& form, const goos::Object& rest,
|
||||||
return get_none();
|
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) {
|
Val* Compiler::compile_asm_file(const goos::Object& form, const goos::Object& rest, Env* env) {
|
||||||
(void)env;
|
(void)env;
|
||||||
int i = 0;
|
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;
|
std::vector<std::pair<std::string, float>> timing;
|
||||||
Timer total_timer;
|
Timer total_timer;
|
||||||
|
|
||||||
|
// parse arguments
|
||||||
for_each_in_list(rest, [&](const goos::Object& o) {
|
for_each_in_list(rest, [&](const goos::Object& o) {
|
||||||
if (i == 0) {
|
if (i == 0) {
|
||||||
filename = as_string(o);
|
filename = as_string(o);
|
||||||
|
@ -59,43 +78,49 @@ Val* Compiler::compile_asm_file(const goos::Object& form, const goos::Object& re
|
||||||
i++;
|
i++;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// READ
|
||||||
Timer reader_timer;
|
Timer reader_timer;
|
||||||
auto code = m_goos.reader.read_from_file({filename});
|
auto code = m_goos.reader.read_from_file({filename});
|
||||||
timing.emplace_back("read", reader_timer.getMs());
|
timing.emplace_back("read", reader_timer.getMs());
|
||||||
|
|
||||||
Timer compile_timer;
|
Timer compile_timer;
|
||||||
std::string obj_file_name = filename;
|
std::string obj_file_name = filename;
|
||||||
|
|
||||||
|
// Extract object name from file name.
|
||||||
for (int idx = int(filename.size()) - 1; idx-- > 0;) {
|
for (int idx = int(filename.size()) - 1; idx-- > 0;) {
|
||||||
if (filename.at(idx) == '\\' || filename.at(idx) == '/') {
|
if (filename.at(idx) == '\\' || filename.at(idx) == '/') {
|
||||||
obj_file_name = filename.substr(idx + 1);
|
obj_file_name = filename.substr(idx + 1);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
obj_file_name = obj_file_name.substr(0, obj_file_name.find_last_of('.'));
|
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);
|
auto obj_file = compile_object_file(obj_file_name, code, !no_code);
|
||||||
timing.emplace_back("compile", compile_timer.getMs());
|
timing.emplace_back("compile", compile_timer.getMs());
|
||||||
|
|
||||||
if (color) {
|
if (color) {
|
||||||
|
// register allocation
|
||||||
Timer color_timer;
|
Timer color_timer;
|
||||||
color_object_file(obj_file);
|
color_object_file(obj_file);
|
||||||
timing.emplace_back("color", color_timer.getMs());
|
timing.emplace_back("color", color_timer.getMs());
|
||||||
|
|
||||||
|
// code/object file generation
|
||||||
Timer codegen_timer;
|
Timer codegen_timer;
|
||||||
auto data = codegen_object_file(obj_file);
|
auto data = codegen_object_file(obj_file);
|
||||||
timing.emplace_back("codegen", codegen_timer.getMs());
|
timing.emplace_back("codegen", codegen_timer.getMs());
|
||||||
|
|
||||||
|
// send to target
|
||||||
if (load) {
|
if (load) {
|
||||||
if (m_listener.is_connected()) {
|
if (m_listener.is_connected()) {
|
||||||
m_listener.send_code(data);
|
m_listener.send_code(data);
|
||||||
} else {
|
} 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) {
|
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";
|
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());
|
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"))) {
|
if (m_settings.print_timing) {
|
||||||
printf("F: %36s ", obj_file_name.c_str());
|
printf("F: %36s ", obj_file_name.c_str());
|
||||||
for (auto& e : timing) {
|
timing.emplace_back("total", total_timer.getMs());
|
||||||
printf(" %12s %4.2f", e.first.c_str(), e.second);
|
for (auto& e : timing) {
|
||||||
|
printf(" %12s %4.2f", e.first.c_str(), e.second / 1000.f);
|
||||||
|
}
|
||||||
|
printf("\n");
|
||||||
}
|
}
|
||||||
printf("\n");
|
|
||||||
// }
|
|
||||||
|
|
||||||
return get_none();
|
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,
|
Val* Compiler::compile_listen_to_target(const goos::Object& form,
|
||||||
const goos::Object& rest,
|
const goos::Object& rest,
|
||||||
Env* env) {
|
Env* env) {
|
||||||
(void)env;
|
(void)env;
|
||||||
std::string ip = "127.0.0.1";
|
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;
|
bool got_port = false, got_ip = false;
|
||||||
|
|
||||||
for_each_in_list(rest, [&](const goos::Object& o) {
|
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();
|
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) {
|
Val* Compiler::compile_reset_target(const goos::Object& form, const goos::Object& rest, Env* env) {
|
||||||
(void)env;
|
(void)env;
|
||||||
bool shutdown = false;
|
bool shutdown = false;
|
||||||
|
@ -164,6 +200,11 @@ Val* Compiler::compile_reset_target(const goos::Object& form, const goos::Object
|
||||||
return get_none();
|
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) {
|
Val* Compiler::compile_poke(const goos::Object& form, const goos::Object& rest, Env* env) {
|
||||||
(void)env;
|
(void)env;
|
||||||
auto args = get_va(form, rest);
|
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();
|
return get_none();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Enter a goos REPL.
|
||||||
|
*/
|
||||||
Val* Compiler::compile_gs(const goos::Object& form, const goos::Object& rest, Env* env) {
|
Val* Compiler::compile_gs(const goos::Object& form, const goos::Object& rest, Env* env) {
|
||||||
(void)env;
|
(void)env;
|
||||||
auto args = get_va(form, rest);
|
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();
|
return get_none();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Set a compiler setting by name.
|
||||||
|
*/
|
||||||
Val* Compiler::compile_set_config(const goos::Object& form, const goos::Object& rest, Env* env) {
|
Val* Compiler::compile_set_config(const goos::Object& form, const goos::Object& rest, Env* env) {
|
||||||
(void)env;
|
(void)env;
|
||||||
auto args = get_va(form, rest);
|
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();
|
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) {
|
Val* Compiler::compile_in_package(const goos::Object& form, const goos::Object& rest, Env* env) {
|
||||||
(void)form;
|
(void)form;
|
||||||
(void)rest;
|
(void)rest;
|
||||||
|
@ -195,6 +245,10 @@ Val* Compiler::compile_in_package(const goos::Object& form, const goos::Object&
|
||||||
return get_none();
|
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) {
|
Val* Compiler::compile_build_dgo(const goos::Object& form, const goos::Object& rest, Env* env) {
|
||||||
(void)env;
|
(void)env;
|
||||||
auto args = get_va(form, rest);
|
auto args = get_va(form, rest);
|
||||||
|
|
|
@ -1,10 +1,18 @@
|
||||||
|
/*!
|
||||||
|
* @file ControlFlow.cpp
|
||||||
|
* Compiler forms related to conditional branching and control flow.
|
||||||
|
*/
|
||||||
|
|
||||||
#include "goalc/compiler/Compiler.h"
|
#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
|
* 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)
|
* (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.
|
* 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
|
* 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
|
* 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
|
// todo - it's possible to optimize a false comparison because the false offset is zero
|
||||||
gc.kind = invert ? ConditionKind::EQUAL : ConditionKind::NOT_EQUAL;
|
gc.kind = invert ? ConditionKind::EQUAL : ConditionKind::NOT_EQUAL;
|
||||||
gc.a = compile_error_guard(condition, env)->to_gpr(env);
|
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;
|
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,
|
Val* Compiler::compile_condition_as_bool(const goos::Object& form,
|
||||||
const goos::Object& rest,
|
const goos::Object& rest,
|
||||||
Env* env) {
|
Env* env) {
|
||||||
|
@ -124,6 +138,10 @@ Val* Compiler::compile_condition_as_bool(const goos::Object& form,
|
||||||
return result;
|
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) {
|
Val* Compiler::compile_when_goto(const goos::Object& form, const goos::Object& _rest, Env* env) {
|
||||||
(void)form;
|
(void)form;
|
||||||
auto* rest = &_rest;
|
auto* rest = &_rest;
|
||||||
|
@ -141,6 +159,12 @@ Val* Compiler::compile_when_goto(const goos::Object& form, const goos::Object& _
|
||||||
return get_none();
|
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) {
|
Val* Compiler::compile_cond(const goos::Object& form, const goos::Object& rest, Env* env) {
|
||||||
auto result = env->make_gpr(m_ts.make_typespec("object"));
|
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();
|
Val* case_result = get_none();
|
||||||
for_each_in_list(clauses, [&](const goos::Object& clause) {
|
for_each_in_list(clauses, [&](const goos::Object& clause) {
|
||||||
case_result = compile_error_guard(clause, env);
|
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());
|
case_result_types.push_back(case_result->type());
|
||||||
|
|
||||||
// optimization - if we get junk, don't bother moving it, just leave junk in return.
|
// optimization - if we get junk, don't bother moving it, just leave junk in return.
|
||||||
if (!is_none(case_result)) {
|
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)));
|
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();
|
Val* case_result = get_none();
|
||||||
for_each_in_list(clauses, [&](const goos::Object& clause) {
|
for_each_in_list(clauses, [&](const goos::Object& clause) {
|
||||||
case_result = compile_error_guard(clause, env);
|
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());
|
case_result_types.push_back(case_result->type());
|
||||||
if (!is_none(case_result)) {
|
if (!is_none(case_result)) {
|
||||||
|
// todo, what does GOAL do here?
|
||||||
env->emit(std::make_unique<IR_RegSet>(result, case_result->to_gpr(env)));
|
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 (!got_else) {
|
||||||
// if no else, clause, return #f. But don't retype. I don't know how I feel about this typing
|
// if no else, clause, return #f. But don't retype. todo what does goal do here?
|
||||||
// setup.
|
|
||||||
auto get_false = std::make_unique<IR_LoadSymbolPointer>(result, "#f");
|
auto get_false = std::make_unique<IR_LoadSymbolPointer>(result, "#f");
|
||||||
env->emit(std::move(get_false));
|
env->emit(std::move(get_false));
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,15 @@
|
||||||
|
/*!
|
||||||
|
* @file Define.cpp
|
||||||
|
* Forms which define or set things.
|
||||||
|
*/
|
||||||
|
|
||||||
#include "goalc/compiler/Compiler.h"
|
#include "goalc/compiler/Compiler.h"
|
||||||
#include "goalc/logger/Logger.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) {
|
Val* Compiler::compile_define(const goos::Object& form, const goos::Object& rest, Env* env) {
|
||||||
auto args = get_va(form, rest);
|
auto args = get_va(form, rest);
|
||||||
va_check(form, args, {goos::ObjectType::SYMBOL, {}}, {});
|
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;
|
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) {
|
Val* Compiler::compile_define_extern(const goos::Object& form, const goos::Object& rest, Env* env) {
|
||||||
(void)env;
|
(void)env;
|
||||||
auto args = get_va(form, rest);
|
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));
|
auto existing_type = m_symbol_types.find(symbol_string(sym));
|
||||||
if (existing_type != m_symbol_types.end() && existing_type->second != new_type) {
|
if (existing_type != m_symbol_types.end() && existing_type->second != new_type) {
|
||||||
|
// todo spdlog
|
||||||
gLogger.log(
|
gLogger.log(
|
||||||
MSG_WARN,
|
MSG_WARN,
|
||||||
"[Warning] define-extern has redefined the type of symbol %s\npreviously: %s\nnow: %s\n",
|
"[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();
|
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) {
|
Val* Compiler::compile_set(const goos::Object& form, const goos::Object& rest, Env* env) {
|
||||||
auto args = get_va(form, rest);
|
auto args = get_va(form, rest);
|
||||||
va_check(form, args, {{}, {}}, {});
|
va_check(form, args, {{}, {}}, {});
|
||||||
|
|
||||||
auto& destination = args.unnamed.at(0);
|
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);
|
auto source = compile_error_guard(args.unnamed.at(1), env)->to_reg(env);
|
||||||
|
|
||||||
if (destination.is_symbol()) {
|
if (destination.is_symbol()) {
|
||||||
|
@ -97,25 +116,29 @@ Val* Compiler::compile_set(const goos::Object& form, const goos::Object& rest, E
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// destination is some complex expression, so compile it and hopefully get something settable.
|
||||||
auto dest = compile_error_guard(destination, env);
|
auto dest = compile_error_guard(destination, env);
|
||||||
auto as_mem_deref = dynamic_cast<MemoryDerefVal*>(dest);
|
auto as_mem_deref = dynamic_cast<MemoryDerefVal*>(dest);
|
||||||
auto as_pair = dynamic_cast<PairEntryVal*>(dest);
|
auto as_pair = dynamic_cast<PairEntryVal*>(dest);
|
||||||
if (as_mem_deref) {
|
if (as_mem_deref) {
|
||||||
|
// setting somewhere in memory
|
||||||
auto base = as_mem_deref->base;
|
auto base = as_mem_deref->base;
|
||||||
auto base_as_mco = dynamic_cast<MemoryOffsetConstantVal*>(base);
|
auto base_as_mco = dynamic_cast<MemoryOffsetConstantVal*>(base);
|
||||||
if (base_as_mco) {
|
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());
|
auto ti = m_ts.lookup_type(base->type());
|
||||||
env->emit(std::make_unique<IR_StoreConstOffset>(
|
env->emit(std::make_unique<IR_StoreConstOffset>(
|
||||||
source, base_as_mco->offset, base_as_mco->base->to_gpr(env), ti->get_load_size()));
|
source, base_as_mco->offset, base_as_mco->base->to_gpr(env), ti->get_load_size()));
|
||||||
return source;
|
return source;
|
||||||
} else {
|
} else {
|
||||||
|
// nope, the pointer to dereference is some compliated thing.
|
||||||
auto ti = m_ts.lookup_type(base->type());
|
auto ti = m_ts.lookup_type(base->type());
|
||||||
env->emit(std::make_unique<IR_StoreConstOffset>(source, 0, base->to_gpr(env),
|
env->emit(std::make_unique<IR_StoreConstOffset>(source, 0, base->to_gpr(env),
|
||||||
ti->get_load_size()));
|
ti->get_load_size()));
|
||||||
return source;
|
return source;
|
||||||
throw_compile_error(form, "Set not implemented for this (non-mco) yet");
|
|
||||||
}
|
}
|
||||||
} else if (as_pair) {
|
} 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,
|
env->emit(std::make_unique<IR_StoreConstOffset>(source, as_pair->is_car ? -2 : 2,
|
||||||
as_pair->base->to_gpr(env), 4));
|
as_pair->base->to_gpr(env), 4));
|
||||||
return source;
|
return source;
|
||||||
|
|
|
@ -1,7 +1,15 @@
|
||||||
|
/*!
|
||||||
|
* @file Function.cpp
|
||||||
|
* Calling and defining functions, lambdas, and inlining.
|
||||||
|
*/
|
||||||
|
|
||||||
#include "goalc/compiler/Compiler.h"
|
#include "goalc/compiler/Compiler.h"
|
||||||
#include "goalc/logger/Logger.h"
|
#include "goalc/logger/Logger.h"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
/*!
|
||||||
|
* Get the preference to inline of the given environment.
|
||||||
|
*/
|
||||||
bool get_inline_preference(Env* env) {
|
bool get_inline_preference(Env* env) {
|
||||||
auto ile = get_parent_env_of_type<WithInlineEnv>(env);
|
auto ile = get_parent_env_of_type<WithInlineEnv>(env);
|
||||||
if (ile) {
|
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) {
|
const goos::Object& get_lambda_body(const goos::Object& def) {
|
||||||
auto* iter = &def;
|
auto* iter = &def;
|
||||||
while (true) {
|
while (true) {
|
||||||
|
@ -26,6 +37,14 @@ const goos::Object& get_lambda_body(const goos::Object& def) {
|
||||||
}
|
}
|
||||||
} // namespace
|
} // 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) {
|
Val* Compiler::compile_inline(const goos::Object& form, const goos::Object& rest, Env* env) {
|
||||||
(void)env;
|
(void)env;
|
||||||
auto args = get_va(form, rest);
|
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) {
|
if (kv->second->func && !kv->second->func->settings.allow_inline) {
|
||||||
throw_compile_error(form, "Found function to inline, but it isn't allowed.");
|
throw_compile_error(form, "Found function to inline, but it isn't allowed.");
|
||||||
}
|
}
|
||||||
|
auto fe = get_parent_env_of_type<FunctionEnv>(env);
|
||||||
// todo, this should return a "view" of the lambda which indicates its inlined
|
return fe->alloc_val<InlinedLambdaVal>(kv->second->type(), kv->second);
|
||||||
// so the correct label namespace behavior can be used.
|
|
||||||
return 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) {
|
Val* Compiler::compile_lambda(const goos::Object& form, const goos::Object& rest, Env* env) {
|
||||||
auto fe = get_parent_env_of_type<FunctionEnv>(env);
|
auto fe = get_parent_env_of_type<FunctionEnv>(env);
|
||||||
auto args = get_va(form, rest);
|
auto args = get_va(form, rest);
|
||||||
if (args.unnamed.empty() || !args.unnamed.front().is_list() ||
|
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");
|
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"));
|
lambda_ts.add_arg(m_ts.make_typespec("object"));
|
||||||
} else {
|
} else {
|
||||||
auto param_args = get_va(o, o);
|
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;
|
GoalArg parm;
|
||||||
parm.name = symbol_string(param_args.unnamed.at(0));
|
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());
|
assert(lambda.params.size() == lambda_ts.arg_count());
|
||||||
|
|
||||||
// optional name for debugging
|
// optional name for debugging (defun sets this)
|
||||||
if (args.has_named("name")) {
|
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"));
|
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 =
|
bool inline_only =
|
||||||
args.has_named("inline-only") && symbol_string(args.get_named("inline-only")) != "#f";
|
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) {
|
if (!inline_only) {
|
||||||
// compile a function! First create env
|
// compile a function! First create env
|
||||||
auto new_func_env = std::make_unique<FunctionEnv>(env, lambda.debug_name);
|
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
|
// 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;
|
std::vector<RegVal*> args_for_coloring;
|
||||||
for (u32 i = 0; i < lambda.params.size(); i++) {
|
for (u32 i = 0; i < lambda.params.size(); i++) {
|
||||||
IRegConstraint constr;
|
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->end_label = Label(new_func_env.get());
|
||||||
func_block_env->emit(std::make_unique<IR_FunctionStart>(args_for_coloring));
|
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;
|
Val* result = nullptr;
|
||||||
bool first_thing = true;
|
bool first_thing = true;
|
||||||
for_each_in_list(lambda.body, [&](const goos::Object& o) {
|
for_each_in_list(lambda.body, [&](const goos::Object& o) {
|
||||||
result = compile_error_guard(o, func_block_env);
|
result = compile_error_guard(o, func_block_env);
|
||||||
|
if (!dynamic_cast<None*>(result)) {
|
||||||
|
result = result->to_reg(func_block_env);
|
||||||
|
}
|
||||||
if (first_thing) {
|
if (first_thing) {
|
||||||
first_thing = false;
|
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;
|
new_func_env->settings.is_set = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (result) {
|
if (result) {
|
||||||
|
// got a result, so to_gpr it and return it.
|
||||||
auto final_result = result->to_gpr(new_func_env.get());
|
auto final_result = result->to_gpr(new_func_env.get());
|
||||||
new_func_env->emit(std::make_unique<IR_Return>(return_reg, final_result));
|
new_func_env->emit(std::make_unique<IR_Return>(return_reg, final_result));
|
||||||
lambda_ts.add_arg(final_result->type());
|
lambda_ts.add_arg(final_result->type());
|
||||||
} else {
|
} else {
|
||||||
|
// empty body, return none
|
||||||
lambda_ts.add_arg(m_ts.make_typespec("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();
|
func_block_env->end_label.idx = new_func_env->code().size();
|
||||||
new_func_env->emit(std::make_unique<IR_Null>());
|
new_func_env->emit(std::make_unique<IR_Null>());
|
||||||
new_func_env->finish();
|
new_func_env->finish();
|
||||||
|
|
||||||
|
// save our code for possible inlining
|
||||||
auto obj_env = get_parent_env_of_type<FileEnv>(new_func_env.get());
|
auto obj_env = get_parent_env_of_type<FileEnv>(new_func_env.get());
|
||||||
assert(obj_env);
|
assert(obj_env);
|
||||||
if (new_func_env->settings.save_code) {
|
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;
|
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) {
|
Val* Compiler::compile_function_or_method_call(const goos::Object& form, Env* env) {
|
||||||
goos::Object f = form;
|
goos::Object f = form;
|
||||||
auto fe = get_parent_env_of_type<FunctionEnv>(env);
|
auto fe = get_parent_env_of_type<FunctionEnv>(env);
|
||||||
|
|
||||||
auto args = get_va(form, form);
|
auto args = get_va(form, form);
|
||||||
|
|
||||||
auto uneval_head = args.unnamed.at(0);
|
auto uneval_head = args.unnamed.at(0);
|
||||||
Val* head = get_none();
|
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]
|
// this logic will not trigger for a manually inlined call [using the (inline func) form]
|
||||||
bool auto_inline = false;
|
bool auto_inline = false;
|
||||||
if (uneval_head.is_symbol()) {
|
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:
|
// look it up:
|
||||||
auto kv = m_inlineable_functions.find(uneval_head.as_symbol());
|
auto kv = m_inlineable_functions.find(uneval_head.as_symbol());
|
||||||
if (kv != m_inlineable_functions.end()) {
|
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
|
// 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.
|
// passed use explicitly a lambda either with the lambda form, or with the (inline ...) form.
|
||||||
LambdaVal* head_as_lambda = nullptr;
|
LambdaVal* head_as_lambda = nullptr;
|
||||||
|
bool got_inlined_lambda = false;
|
||||||
if (!is_method_call) {
|
if (!is_method_call) {
|
||||||
|
// try directly as a lambda
|
||||||
head_as_lambda = dynamic_cast<LambdaVal*>(head);
|
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) {
|
if (!head_as_lambda && !is_method_call) {
|
||||||
head = head->to_gpr(env);
|
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;
|
std::vector<RegVal*> eval_args;
|
||||||
for (uint32_t i = 1; i < args.unnamed.size(); i++) {
|
for (uint32_t i = 1; i < args.unnamed.size(); i++) {
|
||||||
auto intermediate = compile_error_guard(args.unnamed.at(i), env);
|
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));
|
eval_args.push_back(intermediate->to_reg(env));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (head_as_lambda) {
|
if (head_as_lambda) {
|
||||||
// inline the function!
|
// inline/immediate the function!
|
||||||
|
|
||||||
// check args are ok
|
// check args are ok
|
||||||
if (head_as_lambda->lambda.params.size() != eval_args.size()) {
|
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;
|
Env* compile_env = lexical_env;
|
||||||
|
|
||||||
// if needed create a label env.
|
// if need to, create a label env.
|
||||||
// we don't want a separate label env with lets, but we do in other cases.
|
// we don't want a separate label env with lets, but we do for inlined functions.
|
||||||
if (auto_inline) {
|
// either inlined through the auto-inliner, or through an explicit (inline x) form.
|
||||||
// TODO - this misses the case of (inline func)!
|
if (auto_inline || got_inlined_lambda) {
|
||||||
compile_env = fe->alloc_env<LabelEnv>(lexical_env);
|
compile_env = fe->alloc_env<LabelEnv>(lexical_env);
|
||||||
}
|
}
|
||||||
|
|
||||||
// check arg types
|
// check arg types
|
||||||
if (!head->type().arg_count()) {
|
if (!head->type().arg_count()) {
|
||||||
if (head->type().arg_count() - 1 != eval_args.size()) {
|
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++) {
|
for (uint32_t i = 0; i < eval_args.size(); i++) {
|
||||||
typecheck(form, head->type().get_arg(i), eval_args.at(i)->type(),
|
typecheck(form, head->type().get_arg(i), eval_args.at(i)->type(),
|
||||||
"function (inline) argument");
|
"function/lambda (inline/immediate) argument");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// copy args...
|
// copy args...
|
||||||
for (uint32_t i = 0; i < eval_args.size(); i++) {
|
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 type = eval_args.at(i)->type();
|
||||||
auto copy = env->make_ireg(type, get_preferred_reg_kind(type));
|
auto copy = env->make_ireg(type, get_preferred_reg_kind(type));
|
||||||
env->emit(std::make_unique<IR_RegSet>(copy, eval_args.at(i)));
|
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();
|
Val* result = get_none();
|
||||||
for_each_in_list(head_as_lambda->lambda.body, [&](const goos::Object& o) {
|
for_each_in_list(head_as_lambda->lambda.body, [&](const goos::Object& o) {
|
||||||
result = compile_error_guard(o, compile_env);
|
result = compile_error_guard(o, compile_env);
|
||||||
|
if (!dynamic_cast<None*>(result)) {
|
||||||
|
result = result->to_reg(compile_env);
|
||||||
|
}
|
||||||
if (first_thing) {
|
if (first_thing) {
|
||||||
first_thing = false;
|
first_thing = false;
|
||||||
lexical_env->settings.is_set = true;
|
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;
|
return result;
|
||||||
} else {
|
} 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) {
|
if (is_method_call) {
|
||||||
|
// method needs at least one argument to tell what we're calling the method on.
|
||||||
if (eval_args.empty()) {
|
if (eval_args.empty()) {
|
||||||
throw_compile_error(form,
|
throw_compile_error(form,
|
||||||
"Unrecognized symbol " + uneval_head.print() + " as head of 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);
|
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);
|
auto head_as_gpr = head->to_gpr(env);
|
||||||
if (head_as_gpr) {
|
if (head_as_gpr) {
|
||||||
|
// method calls have special rules for typing _type_ arguments.
|
||||||
if (is_method_call) {
|
if (is_method_call) {
|
||||||
return compile_real_function_call(form, head_as_gpr, eval_args, env,
|
return compile_real_function_call(form, head_as_gpr, eval_args, env,
|
||||||
eval_args.front()->type().base_type());
|
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();
|
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,
|
Val* Compiler::compile_real_function_call(const goos::Object& form,
|
||||||
RegVal* function,
|
RegVal* function,
|
||||||
const std::vector<RegVal*>& args,
|
const std::vector<RegVal*>& args,
|
||||||
Env* env,
|
Env* env,
|
||||||
std::string method_type_name) {
|
const std::string& method_type_name) {
|
||||||
auto fe = get_parent_env_of_type<FunctionEnv>(env);
|
auto fe = get_parent_env_of_type<FunctionEnv>(env);
|
||||||
fe->require_aligned_stack();
|
fe->require_aligned_stack();
|
||||||
TypeSpec return_ts;
|
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.
|
// if the type system doesn't know what the function will return, just make it object.
|
||||||
// the user is responsible for getting this right.
|
// the user is responsible for getting this right.
|
||||||
return_ts = m_ts.make_typespec("object");
|
return_ts = m_ts.make_typespec("object");
|
||||||
gLogger.log(MSG_WARN, "[Warning] Function call could not determine return type: %s\n",
|
gLogger.log(MSG_WARN, "[Warning] Function call could not determine return type: %s, %s, %s\n",
|
||||||
form.print().c_str());
|
form.print().c_str(), function->print().c_str(), function->type().print().c_str());
|
||||||
// todo, should this be a warning? not a great thing if we don't know what a function will
|
// todo, consider making this an error once object-new works better.
|
||||||
// return?
|
|
||||||
} else {
|
} else {
|
||||||
return_ts = function->type().last_arg();
|
return_ts = function->type().last_arg();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto return_reg = env->make_ireg(return_ts, emitter::RegKind::GPR);
|
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:
|
// 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()) {
|
if (function->type().arg_count() - 1 != args.size()) {
|
||||||
throw_compile_error(form, "invalid number of arguments to function call: got " +
|
throw_compile_error(form, "invalid number of arguments to function call: got " +
|
||||||
std::to_string(args.size()) + " and expected " +
|
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_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) {
|
if (m_settings.emit_move_after_return) {
|
||||||
auto result_reg = env->make_gpr(return_reg->type());
|
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) {
|
Val* Compiler::compile_declare(const goos::Object& form, const goos::Object& rest, Env* env) {
|
||||||
auto& settings = get_parent_env_of_type<DeclareEnv>(env)->settings;
|
auto& settings = get_parent_env_of_type<DeclareEnv>(env)->settings;
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,9 @@
|
||||||
|
|
||||||
using namespace goos;
|
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) {
|
bool Compiler::try_getting_macro_from_goos(const goos::Object& macro_name, goos::Object* dest) {
|
||||||
Object macro_obj;
|
Object macro_obj;
|
||||||
bool got_macro = false;
|
bool got_macro = false;
|
||||||
|
@ -20,6 +23,9 @@ bool Compiler::try_getting_macro_from_goos(const goos::Object& macro_name, goos:
|
||||||
return got_macro;
|
return got_macro;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Expand a macro, then compile the result.
|
||||||
|
*/
|
||||||
Val* Compiler::compile_goos_macro(const goos::Object& o,
|
Val* Compiler::compile_goos_macro(const goos::Object& o,
|
||||||
const goos::Object& macro_obj,
|
const goos::Object& macro_obj,
|
||||||
const goos::Object& rest,
|
const goos::Object& rest,
|
||||||
|
@ -37,6 +43,9 @@ Val* Compiler::compile_goos_macro(const goos::Object& o,
|
||||||
return compile_error_guard(goos_result, env);
|
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) {
|
Val* Compiler::compile_gscond(const goos::Object& form, const goos::Object& rest, Env* env) {
|
||||||
if (!rest.is_pair()) {
|
if (!rest.is_pair()) {
|
||||||
throw_compile_error(form, "#cond must have at least one clause, which must be a form");
|
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!
|
// got a match!
|
||||||
result = get_none();
|
result = get_none();
|
||||||
|
|
||||||
for_each_in_list(current_case.as_pair()->cdr,
|
for_each_in_list(current_case.as_pair()->cdr, [&](const Object& o) {
|
||||||
[&](const Object& o) { result = compile_error_guard(o, env); });
|
result = compile_error_guard(o, env);
|
||||||
|
if (!dynamic_cast<None*>(result)) {
|
||||||
|
result = result->to_reg(env);
|
||||||
|
}
|
||||||
|
});
|
||||||
return result;
|
return result;
|
||||||
} else {
|
} else {
|
||||||
// no match, continue.
|
// 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) {
|
Val* Compiler::compile_quote(const goos::Object& form, const goos::Object& rest, Env* env) {
|
||||||
auto args = get_va(form, rest);
|
auto args = get_va(form, rest);
|
||||||
va_check(form, args, {{}}, {});
|
va_check(form, args, {{}}, {});
|
||||||
|
@ -95,6 +112,9 @@ Val* Compiler::compile_quote(const goos::Object& form, const goos::Object& rest,
|
||||||
return get_none();
|
return get_none();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Compile defglobalconstant forms, which define a constant in both GOOS and GOAL.
|
||||||
|
*/
|
||||||
Val* Compiler::compile_defglobalconstant(const goos::Object& form,
|
Val* Compiler::compile_defglobalconstant(const goos::Object& form,
|
||||||
const goos::Object& _rest,
|
const goos::Object& _rest,
|
||||||
Env* env) {
|
Env* env) {
|
||||||
|
@ -122,6 +142,9 @@ Val* Compiler::compile_defglobalconstant(const goos::Object& form,
|
||||||
return get_none();
|
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) {
|
Val* Compiler::compile_mlet(const goos::Object& form, const goos::Object& rest, Env* env) {
|
||||||
auto defs = pair_car(rest);
|
auto defs = pair_car(rest);
|
||||||
auto body = pair_cdr(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();
|
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;
|
return result;
|
||||||
}
|
}
|
|
@ -44,7 +44,10 @@ Val* Compiler::number_to_integer(Val* in, Env* env) {
|
||||||
if (is_binteger(ts)) {
|
if (is_binteger(ts)) {
|
||||||
throw std::runtime_error("Can't convert " + in->print() + " (a binteger) to an integer.");
|
throw std::runtime_error("Can't convert " + in->print() + " (a binteger) to an integer.");
|
||||||
} else if (is_float(ts)) {
|
} 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)) {
|
} else if (is_integer(ts)) {
|
||||||
return in;
|
return in;
|
||||||
}
|
}
|
||||||
|
@ -59,7 +62,11 @@ Val* Compiler::number_to_binteger(Val* in, Env* env) {
|
||||||
} else if (is_float(ts)) {
|
} else if (is_float(ts)) {
|
||||||
throw std::runtime_error("Can't convert " + in->print() + " (a float) to a binteger.");
|
throw std::runtime_error("Can't convert " + in->print() + " (a float) to a binteger.");
|
||||||
} else if (is_integer(ts)) {
|
} 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.");
|
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)) {
|
} else if (is_float(ts)) {
|
||||||
return in;
|
return in;
|
||||||
} else if (is_integer(ts)) {
|
} 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 {
|
} else {
|
||||||
throw std::runtime_error("Can't convert " + in->print() + " a float.");
|
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;
|
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:
|
case MATH_INVALID:
|
||||||
throw_compile_error(
|
throw_compile_error(
|
||||||
form, "Cannot determine the math mode for object of type " + first_type.print());
|
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;
|
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:
|
case MATH_INVALID:
|
||||||
throw_compile_error(
|
throw_compile_error(
|
||||||
form, "Cannot determine the math mode for object of type " + first_type.print());
|
form, "Cannot determine the math mode for object of type " + first_type.print());
|
||||||
|
|
|
@ -184,8 +184,6 @@ Val* Compiler::compile_defmethod(const goos::Object& form, const goos::Object& _
|
||||||
if (result) {
|
if (result) {
|
||||||
auto final_result = result->to_gpr(new_func_env.get());
|
auto final_result = result->to_gpr(new_func_env.get());
|
||||||
new_func_env->emit(std::make_unique<IR_Return>(return_reg, final_result));
|
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());
|
lambda_ts.add_arg(final_result->type());
|
||||||
} else {
|
} else {
|
||||||
lambda_ts.add_arg(m_ts.make_typespec("none"));
|
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);
|
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 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 id_val = compile_integer(info.id, env)->to_gpr(env);
|
||||||
auto method_val = place->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 (is_number(base->type())) {
|
||||||
if (m_ts.typecheck(m_ts.make_typespec("binteger"), desired_ts, "", false, false)) {
|
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)) {
|
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)) {
|
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);
|
auto args = get_va(form, rest);
|
||||||
va_check(form, args, {{}}, {});
|
va_check(form, args, {{}}, {});
|
||||||
auto fe = get_parent_env_of_type<FunctionEnv>(env);
|
auto fe = get_parent_env_of_type<FunctionEnv>(env);
|
||||||
return fe->alloc_val<PairEntryVal>(m_ts.make_typespec("object"),
|
auto pair = compile_error_guard(args.unnamed.at(0), env);
|
||||||
compile_error_guard(args.unnamed.at(0), env), true);
|
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) {
|
Val* Compiler::compile_cdr(const goos::Object& form, const goos::Object& rest, Env* env) {
|
||||||
auto args = get_va(form, rest);
|
auto args = get_va(form, rest);
|
||||||
va_check(form, args, {{}}, {});
|
va_check(form, args, {{}}, {});
|
||||||
auto fe = get_parent_env_of_type<FunctionEnv>(env);
|
auto fe = get_parent_env_of_type<FunctionEnv>(env);
|
||||||
return fe->alloc_val<PairEntryVal>(m_ts.make_typespec("object"),
|
auto pair = compile_error_guard(args.unnamed.at(0), env);
|
||||||
compile_error_guard(args.unnamed.at(0), env), false);
|
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?
|
// todo, consider splitting into method-of-object and method-of-type?
|
||||||
|
|
|
@ -1750,7 +1750,7 @@ class IGen {
|
||||||
Instruction instr(0xf3);
|
Instruction instr(0xf3);
|
||||||
instr.set_op2(0x0f);
|
instr.set_op2(0x0f);
|
||||||
instr.set_op3(0x2c);
|
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();
|
instr.swap_op0_rex();
|
||||||
return instr;
|
return instr;
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,6 +58,7 @@ void Logger::log(LoggerMessageKind kind, const char* format, ...) {
|
||||||
|
|
||||||
if (settings.color != COLOR_NORMAL) {
|
if (settings.color != COLOR_NORMAL) {
|
||||||
printf("\033[0m");
|
printf("\033[0m");
|
||||||
|
fflush(stdout);
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo, does this make things slow?
|
// todo, does this make things slow?
|
||||||
|
|
|
@ -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
|
} // namespace
|
||||||
|
|
||||||
TEST(CompilerAndRuntime, BuildGameAndTest) {
|
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-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-new-inline-array-class.gc", {"2820\n"});
|
||||||
runner.run_test("test-memcpy.gc", {"13\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();
|
runner.print_summary();
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue