2020-09-24 17:19:23 -04:00
|
|
|
/*!
|
|
|
|
* @file Function.cpp
|
|
|
|
* Calling and defining functions, lambdas, and inlining.
|
|
|
|
*/
|
|
|
|
|
2024-05-12 12:37:59 -04:00
|
|
|
#include "common/util/string_util.h"
|
|
|
|
|
2020-09-12 13:11:42 -04:00
|
|
|
#include "goalc/compiler/Compiler.h"
|
2021-05-20 20:12:49 -04:00
|
|
|
#include "goalc/emitter/CallingConvention.h"
|
2022-06-22 23:37:46 -04:00
|
|
|
|
2024-03-05 22:11:52 -05:00
|
|
|
#include "fmt/core.h"
|
2020-09-12 13:11:42 -04:00
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
2020-09-24 17:19:23 -04:00
|
|
|
/*!
|
|
|
|
* Hacky function to seek past arguments to get a goos::Object containing the body of a lambda.
|
|
|
|
*/
|
2020-09-12 13:11:42 -04:00
|
|
|
const goos::Object& get_lambda_body(const goos::Object& def) {
|
|
|
|
auto* iter = &def;
|
|
|
|
while (true) {
|
|
|
|
auto car = iter->as_pair()->car;
|
[goalc] Cleaned up speedups (#3066)
Started at 349,880,038 allocations and 42s
- Switched to making `Symbol` in GOOS be a "fixed type", just a wrapper
around a `const char*` pointing to the string in the symbol table. This
is a step toward making a lot of things better, but by itself not a huge
improvement. Some things may be worse due to more temp `std::string`
allocations, but one day all these can be removed. On linux it saved
allocations (347,685,429), and saved a second or two (41 s).
- cache `#t` and `#f` in interpreter, better lookup for special
forms/builtins (hashtable of pointers instead of strings, vector for the
small special form list). Dropped time to 38s.
- special-case in quasiquote when splicing is the last thing in a list.
Allocation dropped to 340,603,082
- custom hash table for environment lookups (lexical vars). Dropped to
36s and 314,637,194
- less allocation in `read_list` 311,613,616. Time about the same.
- `let` and `let*` in Interpreter.cpp 191,988,083, time down to 28s.
2023-10-07 10:48:17 -04:00
|
|
|
if (car.is_symbol() && car.as_symbol().name_ptr[0] == ':') {
|
2020-09-12 13:11:42 -04:00
|
|
|
iter = &iter->as_pair()->cdr;
|
|
|
|
iter = &iter->as_pair()->cdr;
|
|
|
|
} else {
|
2022-02-08 19:02:47 -05:00
|
|
|
ASSERT(car.is_list());
|
2020-09-12 13:11:42 -04:00
|
|
|
return iter->as_pair()->cdr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
2020-09-24 17:19:23 -04:00
|
|
|
/*!
|
|
|
|
* 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.
|
|
|
|
*
|
2023-02-24 18:32:30 -05:00
|
|
|
* If inlining is not possible (function didn't save its code), throw an error.
|
2020-09-24 17:19:23 -04:00
|
|
|
*/
|
2020-09-12 13:11:42 -04:00
|
|
|
Val* Compiler::compile_inline(const goos::Object& form, const goos::Object& rest, Env* env) {
|
|
|
|
(void)env;
|
|
|
|
auto args = get_va(form, rest);
|
|
|
|
va_check(form, args, {goos::ObjectType::SYMBOL}, {});
|
|
|
|
|
|
|
|
auto kv = m_inlineable_functions.find(args.unnamed.at(0).as_symbol());
|
|
|
|
if (kv == m_inlineable_functions.end()) {
|
2020-12-01 21:39:46 -05:00
|
|
|
throw_compiler_error(form, "Cannot inline {} because the function's code could not be found.",
|
|
|
|
args.unnamed.at(0).print());
|
2020-09-12 13:11:42 -04:00
|
|
|
}
|
|
|
|
|
2021-08-24 22:15:26 -04:00
|
|
|
auto fe = env->function_env();
|
2023-02-24 18:32:30 -05:00
|
|
|
return fe->alloc_val<InlinedLambdaVal>(kv->second.type, kv->second);
|
2020-09-12 13:11:42 -04:00
|
|
|
}
|
|
|
|
|
2021-02-01 20:41:37 -05:00
|
|
|
Val* Compiler::compile_local_vars(const goos::Object& form, const goos::Object& rest, Env* env) {
|
2021-08-24 22:15:26 -04:00
|
|
|
auto fe = env->function_env();
|
2021-02-01 20:41:37 -05:00
|
|
|
|
|
|
|
for_each_in_list(rest, [&](const goos::Object& o) {
|
|
|
|
if (o.is_symbol()) {
|
|
|
|
// if it has no type, assume object.
|
[goalc] Cleaned up speedups (#3066)
Started at 349,880,038 allocations and 42s
- Switched to making `Symbol` in GOOS be a "fixed type", just a wrapper
around a `const char*` pointing to the string in the symbol table. This
is a step toward making a lot of things better, but by itself not a huge
improvement. Some things may be worse due to more temp `std::string`
allocations, but one day all these can be removed. On linux it saved
allocations (347,685,429), and saved a second or two (41 s).
- cache `#t` and `#f` in interpreter, better lookup for special
forms/builtins (hashtable of pointers instead of strings, vector for the
small special form list). Dropped time to 38s.
- special-case in quasiquote when splicing is the last thing in a list.
Allocation dropped to 340,603,082
- custom hash table for environment lookups (lexical vars). Dropped to
36s and 314,637,194
- less allocation in `read_list` 311,613,616. Time about the same.
- `let` and `let*` in Interpreter.cpp 191,988,083, time down to 28s.
2023-10-07 10:48:17 -04:00
|
|
|
auto name = o.as_symbol();
|
2021-02-01 20:41:37 -05:00
|
|
|
if (fe->params.find(name) != fe->params.end()) {
|
[goalc] Cleaned up speedups (#3066)
Started at 349,880,038 allocations and 42s
- Switched to making `Symbol` in GOOS be a "fixed type", just a wrapper
around a `const char*` pointing to the string in the symbol table. This
is a step toward making a lot of things better, but by itself not a huge
improvement. Some things may be worse due to more temp `std::string`
allocations, but one day all these can be removed. On linux it saved
allocations (347,685,429), and saved a second or two (41 s).
- cache `#t` and `#f` in interpreter, better lookup for special
forms/builtins (hashtable of pointers instead of strings, vector for the
small special form list). Dropped time to 38s.
- special-case in quasiquote when splicing is the last thing in a list.
Allocation dropped to 340,603,082
- custom hash table for environment lookups (lexical vars). Dropped to
36s and 314,637,194
- less allocation in `read_list` 311,613,616. Time about the same.
- `let` and `let*` in Interpreter.cpp 191,988,083, time down to 28s.
2023-10-07 10:48:17 -04:00
|
|
|
throw_compiler_error(form, "Cannot declare a local named {}, this already exists.",
|
|
|
|
name.name_ptr);
|
2021-02-01 20:41:37 -05:00
|
|
|
}
|
|
|
|
auto ireg = fe->make_ireg(m_ts.make_typespec("object"), RegClass::GPR_64);
|
|
|
|
ireg->mark_as_settable();
|
|
|
|
fe->params[name] = ireg;
|
|
|
|
} else {
|
|
|
|
auto param_args = get_va(o, o);
|
|
|
|
va_check(o, param_args, {goos::ObjectType::SYMBOL, {}}, {});
|
[goalc] Cleaned up speedups (#3066)
Started at 349,880,038 allocations and 42s
- Switched to making `Symbol` in GOOS be a "fixed type", just a wrapper
around a `const char*` pointing to the string in the symbol table. This
is a step toward making a lot of things better, but by itself not a huge
improvement. Some things may be worse due to more temp `std::string`
allocations, but one day all these can be removed. On linux it saved
allocations (347,685,429), and saved a second or two (41 s).
- cache `#t` and `#f` in interpreter, better lookup for special
forms/builtins (hashtable of pointers instead of strings, vector for the
small special form list). Dropped time to 38s.
- special-case in quasiquote when splicing is the last thing in a list.
Allocation dropped to 340,603,082
- custom hash table for environment lookups (lexical vars). Dropped to
36s and 314,637,194
- less allocation in `read_list` 311,613,616. Time about the same.
- `let` and `let*` in Interpreter.cpp 191,988,083, time down to 28s.
2023-10-07 10:48:17 -04:00
|
|
|
auto name = param_args.unnamed.at(0).as_symbol();
|
2022-04-07 19:13:22 -04:00
|
|
|
auto type = parse_typespec(param_args.unnamed.at(1), env);
|
2021-02-01 20:41:37 -05:00
|
|
|
|
|
|
|
if (fe->params.find(name) != fe->params.end()) {
|
[goalc] Cleaned up speedups (#3066)
Started at 349,880,038 allocations and 42s
- Switched to making `Symbol` in GOOS be a "fixed type", just a wrapper
around a `const char*` pointing to the string in the symbol table. This
is a step toward making a lot of things better, but by itself not a huge
improvement. Some things may be worse due to more temp `std::string`
allocations, but one day all these can be removed. On linux it saved
allocations (347,685,429), and saved a second or two (41 s).
- cache `#t` and `#f` in interpreter, better lookup for special
forms/builtins (hashtable of pointers instead of strings, vector for the
small special form list). Dropped time to 38s.
- special-case in quasiquote when splicing is the last thing in a list.
Allocation dropped to 340,603,082
- custom hash table for environment lookups (lexical vars). Dropped to
36s and 314,637,194
- less allocation in `read_list` 311,613,616. Time about the same.
- `let` and `let*` in Interpreter.cpp 191,988,083, time down to 28s.
2023-10-07 10:48:17 -04:00
|
|
|
throw_compiler_error(form, "Cannot declare a local named {}, this already exists.",
|
|
|
|
name.name_ptr);
|
2021-02-01 20:41:37 -05:00
|
|
|
}
|
|
|
|
|
2021-03-28 20:26:30 -04:00
|
|
|
if (m_ts.tc(TypeSpec("float"), type)) {
|
2021-02-01 20:41:37 -05:00
|
|
|
auto ireg = fe->make_ireg(type, RegClass::FLOAT);
|
|
|
|
ireg->mark_as_settable();
|
|
|
|
fe->params[name] = ireg;
|
2021-03-28 20:26:30 -04:00
|
|
|
} else if (m_ts.tc(TypeSpec("int128"), type) || m_ts.tc(TypeSpec("uint128"), type)) {
|
|
|
|
auto ireg = fe->make_ireg(type, RegClass::INT_128);
|
|
|
|
ireg->mark_as_settable();
|
|
|
|
fe->params[name] = ireg;
|
2021-02-01 20:41:37 -05:00
|
|
|
} else {
|
|
|
|
auto ireg = fe->make_ireg(type, RegClass::GPR_64);
|
|
|
|
ireg->mark_as_settable();
|
|
|
|
fe->params[name] = ireg;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
return get_none();
|
|
|
|
}
|
|
|
|
|
2020-09-24 17:19:23 -04:00
|
|
|
/*!
|
|
|
|
* Compile a lambda. This is used for real lambdas, lets, and defuns. So there are a million
|
|
|
|
* confusing special cases...
|
|
|
|
*/
|
2020-09-12 13:11:42 -04:00
|
|
|
Val* Compiler::compile_lambda(const goos::Object& form, const goos::Object& rest, Env* env) {
|
2021-08-24 22:15:26 -04:00
|
|
|
auto fe = env->function_env();
|
|
|
|
auto obj_env = env->file_env();
|
2020-09-12 13:11:42 -04:00
|
|
|
auto args = get_va(form, rest);
|
|
|
|
if (args.unnamed.empty() || !args.unnamed.front().is_list() ||
|
[goalc] default to non-immediate lambdas if not requested (#2604)
This fixes a long time issue with `lambda`. The `lambda` is a bit
overloaded in OpenGOAL: it's used in the implementation of `let`, and
also to define local anonymous functions.
```
(defmacro let (bindings &rest body)
`((lambda :inline #t ,(apply first bindings) ,@body)
,@(apply second bindings)))
```
```
(defmacro defun (name bindings &rest body)
(let ((docstring ""))
(when (and (> (length body) 1) (string? (first body)))
(set! docstring (first body))
(set! body (cdr body)))
`(define ,name ,docstring (lambda :name ,name ,bindings ,@body))))
```
In the first case of a `let`, a `return` from inside the `let` should
return from the functioning containing the `let`, not the scope of the
`lambda`. In the second case, we should return from the lambda. The way
we told the different between these cases was if the `lambda` was used
"immeidately", in the head of an expression (like it would be for the
`let` macro). But, this falsely triggers when an anonymous function is
used immediately: eg
```
((lambda () (return #f)))
```
should generate and call a real x86 function that returns immediately.
This should fix some death/mission failed stuff in jak 2.
2023-04-30 19:00:27 -04:00
|
|
|
!args.only_contains_named({"name", "segment", "behavior", "immediate"})) {
|
2020-12-01 21:39:46 -05:00
|
|
|
throw_compiler_error(form, "Invalid lambda form");
|
2020-09-12 13:11:42 -04:00
|
|
|
}
|
|
|
|
|
[goalc] default to non-immediate lambdas if not requested (#2604)
This fixes a long time issue with `lambda`. The `lambda` is a bit
overloaded in OpenGOAL: it's used in the implementation of `let`, and
also to define local anonymous functions.
```
(defmacro let (bindings &rest body)
`((lambda :inline #t ,(apply first bindings) ,@body)
,@(apply second bindings)))
```
```
(defmacro defun (name bindings &rest body)
(let ((docstring ""))
(when (and (> (length body) 1) (string? (first body)))
(set! docstring (first body))
(set! body (cdr body)))
`(define ,name ,docstring (lambda :name ,name ,bindings ,@body))))
```
In the first case of a `let`, a `return` from inside the `let` should
return from the functioning containing the `let`, not the scope of the
`lambda`. In the second case, we should return from the lambda. The way
we told the different between these cases was if the `lambda` was used
"immeidately", in the head of an expression (like it would be for the
`let` macro). But, this falsely triggers when an anonymous function is
used immediately: eg
```
((lambda () (return #f)))
```
should generate and call a real x86 function that returns immediately.
This should fix some death/mission failed stuff in jak 2.
2023-04-30 19:00:27 -04:00
|
|
|
bool immediate =
|
|
|
|
args.has_named("immediate") && symbol_string(args.get_named("immediate")) != "#f";
|
|
|
|
|
2021-02-03 11:07:47 -05:00
|
|
|
// allocate this lambda from the object file environment. This makes it safe for this to hold
|
|
|
|
// on to references to this as an inlineable function even if the enclosing function fails.
|
|
|
|
// for example, the top-level may (define some-func (lambda...)) and even if top-level fails,
|
|
|
|
// we keep around a reference to some-func to be possibly inlined.
|
[goalc] default to non-immediate lambdas if not requested (#2604)
This fixes a long time issue with `lambda`. The `lambda` is a bit
overloaded in OpenGOAL: it's used in the implementation of `let`, and
also to define local anonymous functions.
```
(defmacro let (bindings &rest body)
`((lambda :inline #t ,(apply first bindings) ,@body)
,@(apply second bindings)))
```
```
(defmacro defun (name bindings &rest body)
(let ((docstring ""))
(when (and (> (length body) 1) (string? (first body)))
(set! docstring (first body))
(set! body (cdr body)))
`(define ,name ,docstring (lambda :name ,name ,bindings ,@body))))
```
In the first case of a `let`, a `return` from inside the `let` should
return from the functioning containing the `let`, not the scope of the
`lambda`. In the second case, we should return from the lambda. The way
we told the different between these cases was if the `lambda` was used
"immeidately", in the head of an expression (like it would be for the
`let` macro). But, this falsely triggers when an anonymous function is
used immediately: eg
```
((lambda () (return #f)))
```
should generate and call a real x86 function that returns immediately.
This should fix some death/mission failed stuff in jak 2.
2023-04-30 19:00:27 -04:00
|
|
|
auto place = obj_env->alloc_val<LambdaVal>(get_none()->type(), immediate);
|
2020-09-12 13:11:42 -04:00
|
|
|
auto& lambda = place->lambda;
|
|
|
|
auto lambda_ts = m_ts.make_typespec("function");
|
|
|
|
|
|
|
|
// parse the argument list.
|
|
|
|
for_each_in_list(args.unnamed.front(), [&](const goos::Object& o) {
|
|
|
|
if (o.is_symbol()) {
|
|
|
|
// if it has no type, assume object.
|
|
|
|
lambda.params.push_back({symbol_string(o), m_ts.make_typespec("object")});
|
|
|
|
lambda_ts.add_arg(m_ts.make_typespec("object"));
|
|
|
|
} else {
|
|
|
|
auto param_args = get_va(o, o);
|
2020-09-24 17:19:23 -04:00
|
|
|
va_check(o, param_args, {goos::ObjectType::SYMBOL, {}}, {});
|
2020-09-12 13:11:42 -04:00
|
|
|
|
|
|
|
GoalArg parm;
|
|
|
|
parm.name = symbol_string(param_args.unnamed.at(0));
|
2022-04-07 19:13:22 -04:00
|
|
|
parm.type = parse_typespec(param_args.unnamed.at(1), env);
|
2020-09-12 13:11:42 -04:00
|
|
|
|
|
|
|
lambda.params.push_back(parm);
|
|
|
|
lambda_ts.add_arg(parm.type);
|
|
|
|
}
|
|
|
|
});
|
2022-02-08 19:02:47 -05:00
|
|
|
ASSERT(lambda.params.size() == lambda_ts.arg_count());
|
2020-09-12 13:11:42 -04:00
|
|
|
|
2020-09-24 17:19:23 -04:00
|
|
|
// optional name for debugging (defun sets this)
|
2020-09-12 13:11:42 -04:00
|
|
|
if (args.has_named("name")) {
|
|
|
|
lambda.debug_name = symbol_string(args.get_named("name"));
|
|
|
|
}
|
|
|
|
|
|
|
|
lambda.body = get_lambda_body(rest); // first is the argument list, rest is body
|
|
|
|
place->func = nullptr;
|
|
|
|
|
2020-09-24 17:19:23 -04:00
|
|
|
// pick default segment to store function in.
|
2021-12-12 12:52:23 -05:00
|
|
|
int segment = fe->segment_for_static_data();
|
2020-09-24 17:19:23 -04:00
|
|
|
|
|
|
|
// 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 {
|
2020-12-01 21:39:46 -05:00
|
|
|
throw_compiler_error(form, "Segment {} was not recognized in lambda option.", segment_name);
|
2020-09-24 17:19:23 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
[goalc] default to non-immediate lambdas if not requested (#2604)
This fixes a long time issue with `lambda`. The `lambda` is a bit
overloaded in OpenGOAL: it's used in the implementation of `let`, and
also to define local anonymous functions.
```
(defmacro let (bindings &rest body)
`((lambda :inline #t ,(apply first bindings) ,@body)
,@(apply second bindings)))
```
```
(defmacro defun (name bindings &rest body)
(let ((docstring ""))
(when (and (> (length body) 1) (string? (first body)))
(set! docstring (first body))
(set! body (cdr body)))
`(define ,name ,docstring (lambda :name ,name ,bindings ,@body))))
```
In the first case of a `let`, a `return` from inside the `let` should
return from the functioning containing the `let`, not the scope of the
`lambda`. In the second case, we should return from the lambda. The way
we told the different between these cases was if the `lambda` was used
"immeidately", in the head of an expression (like it would be for the
`let` macro). But, this falsely triggers when an anonymous function is
used immediately: eg
```
((lambda () (return #f)))
```
should generate and call a real x86 function that returns immediately.
This should fix some death/mission failed stuff in jak 2.
2023-04-30 19:00:27 -04:00
|
|
|
if (!immediate) {
|
2020-11-13 22:33:57 -05:00
|
|
|
// compile a function! First create a unique name...
|
|
|
|
std::string function_name = lambda.debug_name;
|
|
|
|
if (function_name.empty()) {
|
2020-12-05 17:09:46 -05:00
|
|
|
function_name = obj_env->get_anon_function_name();
|
2020-11-13 22:33:57 -05:00
|
|
|
}
|
2021-08-26 20:33:00 -04:00
|
|
|
auto new_func_env = std::make_unique<FunctionEnv>(env, function_name, &m_goos.reader);
|
2020-09-24 17:19:23 -04:00
|
|
|
new_func_env->set_segment(segment);
|
2020-09-12 13:11:42 -04:00
|
|
|
|
|
|
|
// set up arguments
|
2020-12-08 21:41:36 -05:00
|
|
|
if (lambda.params.size() > 8) {
|
2020-12-01 21:39:46 -05:00
|
|
|
throw_compiler_error(form,
|
2021-05-20 20:12:49 -04:00
|
|
|
"Cannot generate a real function for a lambda with {} parameters. "
|
2020-12-01 21:39:46 -05:00
|
|
|
"The current limit is 8.",
|
|
|
|
lambda.params.size());
|
2020-09-24 17:19:23 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// set up argument register constraints.
|
2021-07-04 16:54:07 -04:00
|
|
|
std::vector<RegVal*> reset_args_for_coloring;
|
2021-05-20 20:12:49 -04:00
|
|
|
std::vector<TypeSpec> arg_types;
|
|
|
|
for (auto& parm : lambda.params) {
|
|
|
|
arg_types.push_back(parm.type);
|
|
|
|
}
|
|
|
|
auto arg_regs = get_arg_registers(m_ts, arg_types);
|
|
|
|
|
2020-09-12 13:11:42 -04:00
|
|
|
for (u32 i = 0; i < lambda.params.size(); i++) {
|
|
|
|
IRegConstraint constr;
|
|
|
|
constr.instr_idx = 0; // constraint at function start
|
2021-08-01 17:46:55 -04:00
|
|
|
auto ireg_arg = new_func_env->make_ireg(
|
2021-05-20 20:12:49 -04:00
|
|
|
lambda.params.at(i).type, arg_regs.at(i).is_gpr() ? RegClass::GPR_64 : RegClass::INT_128);
|
2021-08-01 17:46:55 -04:00
|
|
|
ireg_arg->mark_as_settable();
|
|
|
|
constr.ireg = ireg_arg->ireg();
|
2021-05-20 20:12:49 -04:00
|
|
|
constr.desired_register = arg_regs.at(i);
|
2020-09-12 13:11:42 -04:00
|
|
|
new_func_env->constrain(constr);
|
2021-08-01 17:46:55 -04:00
|
|
|
reset_args_for_coloring.push_back(ireg_arg);
|
2021-07-04 16:54:07 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
if (args.has_named("behavior")) {
|
|
|
|
const std::string behavior_type = symbol_string(args.get_named("behavior"));
|
|
|
|
auto self_var = new_func_env->make_gpr(m_ts.make_typespec(behavior_type));
|
2022-09-25 12:07:37 -04:00
|
|
|
self_var->mark_as_settable();
|
2021-07-04 16:54:07 -04:00
|
|
|
IRegConstraint constr;
|
|
|
|
constr.contrain_everywhere = true;
|
|
|
|
constr.desired_register = emitter::gRegInfo.get_process_reg();
|
|
|
|
constr.ireg = self_var->ireg();
|
|
|
|
self_var->set_rlet_constraint(constr.desired_register);
|
|
|
|
new_func_env->constrain(constr);
|
|
|
|
|
[goalc] Cleaned up speedups (#3066)
Started at 349,880,038 allocations and 42s
- Switched to making `Symbol` in GOOS be a "fixed type", just a wrapper
around a `const char*` pointing to the string in the symbol table. This
is a step toward making a lot of things better, but by itself not a huge
improvement. Some things may be worse due to more temp `std::string`
allocations, but one day all these can be removed. On linux it saved
allocations (347,685,429), and saved a second or two (41 s).
- cache `#t` and `#f` in interpreter, better lookup for special
forms/builtins (hashtable of pointers instead of strings, vector for the
small special form list). Dropped time to 38s.
- special-case in quasiquote when splicing is the last thing in a list.
Allocation dropped to 340,603,082
- custom hash table for environment lookups (lexical vars). Dropped to
36s and 314,637,194
- less allocation in `read_list` 311,613,616. Time about the same.
- `let` and `let*` in Interpreter.cpp 191,988,083, time down to 28s.
2023-10-07 10:48:17 -04:00
|
|
|
if (new_func_env->params.find(m_goos.intern_ptr("self")) != new_func_env->params.end()) {
|
2021-07-04 16:54:07 -04:00
|
|
|
throw_compiler_error(form, "Cannot have an argument named self in a behavior");
|
|
|
|
}
|
[goalc] Cleaned up speedups (#3066)
Started at 349,880,038 allocations and 42s
- Switched to making `Symbol` in GOOS be a "fixed type", just a wrapper
around a `const char*` pointing to the string in the symbol table. This
is a step toward making a lot of things better, but by itself not a huge
improvement. Some things may be worse due to more temp `std::string`
allocations, but one day all these can be removed. On linux it saved
allocations (347,685,429), and saved a second or two (41 s).
- cache `#t` and `#f` in interpreter, better lookup for special
forms/builtins (hashtable of pointers instead of strings, vector for the
small special form list). Dropped time to 38s.
- special-case in quasiquote when splicing is the last thing in a list.
Allocation dropped to 340,603,082
- custom hash table for environment lookups (lexical vars). Dropped to
36s and 314,637,194
- less allocation in `read_list` 311,613,616. Time about the same.
- `let` and `let*` in Interpreter.cpp 191,988,083, time down to 28s.
2023-10-07 10:48:17 -04:00
|
|
|
new_func_env->params[m_goos.intern_ptr("self")] = self_var;
|
2021-07-04 16:54:07 -04:00
|
|
|
reset_args_for_coloring.push_back(self_var);
|
|
|
|
lambda_ts.add_new_tag("behavior", behavior_type);
|
2020-09-12 13:11:42 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
place->func = new_func_env.get();
|
|
|
|
|
|
|
|
// nasty function block env setup
|
2020-12-30 15:33:51 -05:00
|
|
|
auto return_reg = new_func_env->make_gpr(get_none()->type());
|
2020-09-12 13:11:42 -04:00
|
|
|
auto func_block_env = new_func_env->alloc_env<BlockEnv>(new_func_env.get(), "#f");
|
|
|
|
func_block_env->return_value = return_reg;
|
|
|
|
func_block_env->end_label = Label(new_func_env.get());
|
2021-08-26 20:33:00 -04:00
|
|
|
func_block_env->emit_ir<IR_ValueReset>(form, reset_args_for_coloring);
|
2020-09-12 13:11:42 -04:00
|
|
|
|
2021-08-01 17:46:55 -04:00
|
|
|
for (u32 i = 0; i < lambda.params.size(); i++) {
|
|
|
|
auto ireg = new_func_env->make_ireg(
|
|
|
|
lambda.params.at(i).type, arg_regs.at(i).is_gpr() ? RegClass::GPR_64 : RegClass::INT_128);
|
|
|
|
ireg->mark_as_settable();
|
[goalc] Cleaned up speedups (#3066)
Started at 349,880,038 allocations and 42s
- Switched to making `Symbol` in GOOS be a "fixed type", just a wrapper
around a `const char*` pointing to the string in the symbol table. This
is a step toward making a lot of things better, but by itself not a huge
improvement. Some things may be worse due to more temp `std::string`
allocations, but one day all these can be removed. On linux it saved
allocations (347,685,429), and saved a second or two (41 s).
- cache `#t` and `#f` in interpreter, better lookup for special
forms/builtins (hashtable of pointers instead of strings, vector for the
small special form list). Dropped time to 38s.
- special-case in quasiquote when splicing is the last thing in a list.
Allocation dropped to 340,603,082
- custom hash table for environment lookups (lexical vars). Dropped to
36s and 314,637,194
- less allocation in `read_list` 311,613,616. Time about the same.
- `let` and `let*` in Interpreter.cpp 191,988,083, time down to 28s.
2023-10-07 10:48:17 -04:00
|
|
|
if (!new_func_env->params.insert({m_goos.intern_ptr(lambda.params.at(i).name), ireg})
|
|
|
|
.second) {
|
2021-08-31 22:12:30 -04:00
|
|
|
throw_compiler_error(form, "lambda has multiple arguments named {}",
|
|
|
|
lambda.params.at(i).name);
|
|
|
|
}
|
2021-08-26 20:33:00 -04:00
|
|
|
new_func_env->emit_ir<IR_RegSet>(form, ireg, reset_args_for_coloring.at(i));
|
2021-08-01 17:46:55 -04:00
|
|
|
}
|
|
|
|
|
2020-09-24 17:19:23 -04:00
|
|
|
// compile the function, iterating through the body.
|
2020-09-12 13:11:42 -04:00
|
|
|
Val* result = nullptr;
|
|
|
|
bool first_thing = true;
|
|
|
|
for_each_in_list(lambda.body, [&](const goos::Object& o) {
|
|
|
|
result = compile_error_guard(o, func_block_env);
|
2020-09-24 17:19:23 -04:00
|
|
|
if (!dynamic_cast<None*>(result)) {
|
2021-08-26 20:33:00 -04:00
|
|
|
result = result->to_reg(o, func_block_env);
|
2020-09-24 17:19:23 -04:00
|
|
|
}
|
2020-09-12 13:11:42 -04:00
|
|
|
if (first_thing) {
|
|
|
|
first_thing = false;
|
2020-09-24 17:19:23 -04:00
|
|
|
// 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.
|
2020-09-12 13:11:42 -04:00
|
|
|
new_func_env->settings.is_set = true;
|
|
|
|
}
|
|
|
|
});
|
2020-12-04 12:57:10 -05:00
|
|
|
|
|
|
|
if (new_func_env->is_asm_func) {
|
|
|
|
// don't add return automatically!
|
|
|
|
lambda_ts.add_arg(new_func_env->asm_func_return_type);
|
2023-09-22 05:54:49 -04:00
|
|
|
} else if (result && !dynamic_cast<None*>(result) && result->type() != TypeSpec("none")) {
|
2020-09-24 17:19:23 -04:00
|
|
|
// got a result, so to_gpr it and return it.
|
2021-05-20 20:12:49 -04:00
|
|
|
|
|
|
|
RegVal* final_result;
|
|
|
|
emitter::Register ret_hw_reg = emitter::gRegInfo.get_gpr_ret_reg();
|
2023-09-22 05:54:49 -04:00
|
|
|
if (m_ts.lookup_type(result->type())->get_load_size() == 16) {
|
2021-05-20 20:12:49 -04:00
|
|
|
ret_hw_reg = emitter::gRegInfo.get_xmm_ret_reg();
|
2021-08-26 20:33:00 -04:00
|
|
|
final_result = result->to_xmm128(form, new_func_env.get());
|
2021-05-20 20:12:49 -04:00
|
|
|
return_reg->change_class(RegClass::INT_128);
|
|
|
|
} else {
|
2021-08-26 20:33:00 -04:00
|
|
|
final_result = result->to_gpr(form, new_func_env.get());
|
2021-05-20 20:12:49 -04:00
|
|
|
}
|
|
|
|
|
2020-11-22 20:10:33 -05:00
|
|
|
func_block_env->return_types.push_back(final_result->type());
|
2021-05-20 20:12:49 -04:00
|
|
|
for (const auto& possible_type : func_block_env->return_types) {
|
|
|
|
if (possible_type != TypeSpec("none") &&
|
|
|
|
m_ts.lookup_type(possible_type)->get_load_size() == 16) {
|
|
|
|
return_reg->change_class(RegClass::INT_128);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-26 20:33:00 -04:00
|
|
|
new_func_env->emit_ir<IR_Return>(form, return_reg, final_result, ret_hw_reg);
|
2021-05-20 20:12:49 -04:00
|
|
|
|
2020-11-22 20:10:33 -05:00
|
|
|
auto return_type = m_ts.lowest_common_ancestor(func_block_env->return_types);
|
|
|
|
lambda_ts.add_arg(return_type);
|
2020-09-12 13:11:42 -04:00
|
|
|
} else {
|
2020-11-22 20:10:33 -05:00
|
|
|
// empty body or returning none, return none
|
2020-09-12 13:11:42 -04:00
|
|
|
lambda_ts.add_arg(m_ts.make_typespec("none"));
|
|
|
|
}
|
2020-09-24 17:19:23 -04:00
|
|
|
// put null instruction at the end so jumps to the end have somewhere to go.
|
2020-09-12 13:11:42 -04:00
|
|
|
func_block_env->end_label.idx = new_func_env->code().size();
|
2021-08-26 20:33:00 -04:00
|
|
|
new_func_env->emit_ir<IR_Null>(form);
|
2020-09-19 13:22:14 -04:00
|
|
|
new_func_env->finish();
|
2020-09-12 13:11:42 -04:00
|
|
|
|
2020-09-24 17:19:23 -04:00
|
|
|
// save our code for possible inlining
|
2022-02-08 19:02:47 -05:00
|
|
|
ASSERT(obj_env);
|
2020-09-12 13:11:42 -04:00
|
|
|
if (new_func_env->settings.save_code) {
|
|
|
|
obj_env->add_function(std::move(new_func_env));
|
|
|
|
}
|
2021-07-04 16:54:07 -04:00
|
|
|
} else {
|
|
|
|
if (args.has_named("behavior")) {
|
|
|
|
throw_compiler_error(form, "Inline behaviors are not yet implemented.");
|
|
|
|
}
|
2020-09-12 13:11:42 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
place->set_type(lambda_ts);
|
|
|
|
return place;
|
|
|
|
}
|
|
|
|
|
2020-09-24 17:19:23 -04:00
|
|
|
/*!
|
|
|
|
* 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.
|
|
|
|
*/
|
2020-09-12 13:11:42 -04:00
|
|
|
Val* Compiler::compile_function_or_method_call(const goos::Object& form, Env* env) {
|
|
|
|
goos::Object f = form;
|
2024-05-12 12:37:59 -04:00
|
|
|
const auto fe = env->function_env();
|
|
|
|
|
|
|
|
const auto args = get_va(form, form);
|
|
|
|
const auto& uneval_head = args.unnamed.at(0);
|
|
|
|
if (m_settings.check_for_requires) {
|
|
|
|
const auto& symbol_info = m_symbol_info.lookup_exact_name(uneval_head.print());
|
|
|
|
if (!symbol_info.empty()) {
|
|
|
|
const auto& result = symbol_info.at(0);
|
|
|
|
if (result->m_def_location.has_value() &&
|
|
|
|
!env->file_env()->m_missing_required_files.contains(result->m_def_location->file_path) &&
|
|
|
|
env->file_env()->m_required_files.find(result->m_def_location->file_path) ==
|
|
|
|
env->file_env()->m_required_files.end() &&
|
|
|
|
!str_util::ends_with(result->m_def_location->file_path,
|
|
|
|
env->file_env()->name() + ".gc")) {
|
|
|
|
lg::warn("Missing require in {} for {} over {}", env->file_env()->name(),
|
|
|
|
result->m_def_location->file_path, uneval_head.print());
|
|
|
|
env->file_env()->m_missing_required_files.insert(result->m_def_location->file_path);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-09-12 13:11:42 -04:00
|
|
|
Val* head = get_none();
|
|
|
|
|
|
|
|
// determine if this call should be automatically inlined.
|
|
|
|
// this logic will not trigger for a manually inlined call [using the (inline func) form]
|
|
|
|
bool auto_inline = false;
|
|
|
|
if (uneval_head.is_symbol()) {
|
2020-09-24 17:19:23 -04:00
|
|
|
// we can only auto-inline the function if its name is explicitly given.
|
2020-09-12 13:11:42 -04:00
|
|
|
// look it up:
|
|
|
|
auto kv = m_inlineable_functions.find(uneval_head.as_symbol());
|
|
|
|
if (kv != m_inlineable_functions.end()) {
|
|
|
|
// it's inlinable. However, we do not always inline an inlinable function by default
|
2023-02-24 18:32:30 -05:00
|
|
|
if (kv->second.inline_by_default) { // inline when possible, so we should inline
|
2020-09-12 13:11:42 -04:00
|
|
|
auto_inline = true;
|
[goalc] default to non-immediate lambdas if not requested (#2604)
This fixes a long time issue with `lambda`. The `lambda` is a bit
overloaded in OpenGOAL: it's used in the implementation of `let`, and
also to define local anonymous functions.
```
(defmacro let (bindings &rest body)
`((lambda :inline #t ,(apply first bindings) ,@body)
,@(apply second bindings)))
```
```
(defmacro defun (name bindings &rest body)
(let ((docstring ""))
(when (and (> (length body) 1) (string? (first body)))
(set! docstring (first body))
(set! body (cdr body)))
`(define ,name ,docstring (lambda :name ,name ,bindings ,@body))))
```
In the first case of a `let`, a `return` from inside the `let` should
return from the functioning containing the `let`, not the scope of the
`lambda`. In the second case, we should return from the lambda. The way
we told the different between these cases was if the `lambda` was used
"immeidately", in the head of an expression (like it would be for the
`let` macro). But, this falsely triggers when an anonymous function is
used immediately: eg
```
((lambda () (return #f)))
```
should generate and call a real x86 function that returns immediately.
This should fix some death/mission failed stuff in jak 2.
2023-04-30 19:00:27 -04:00
|
|
|
auto* lv = env->function_env()->alloc_val<LambdaVal>(kv->second.type, false);
|
2023-02-24 18:32:30 -05:00
|
|
|
lv->lambda = kv->second.lambda;
|
|
|
|
head = lv;
|
2020-09-12 13:11:42 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool is_method_call = false;
|
|
|
|
if (!auto_inline) {
|
|
|
|
// if auto-inlining failed, we must get the thing to call in a different way.
|
|
|
|
if (uneval_head.is_symbol()) {
|
[goalc] Cleaned up speedups (#3066)
Started at 349,880,038 allocations and 42s
- Switched to making `Symbol` in GOOS be a "fixed type", just a wrapper
around a `const char*` pointing to the string in the symbol table. This
is a step toward making a lot of things better, but by itself not a huge
improvement. Some things may be worse due to more temp `std::string`
allocations, but one day all these can be removed. On linux it saved
allocations (347,685,429), and saved a second or two (41 s).
- cache `#t` and `#f` in interpreter, better lookup for special
forms/builtins (hashtable of pointers instead of strings, vector for the
small special form list). Dropped time to 38s.
- special-case in quasiquote when splicing is the last thing in a list.
Allocation dropped to 340,603,082
- custom hash table for environment lookups (lexical vars). Dropped to
36s and 314,637,194
- less allocation in `read_list` 311,613,616. Time about the same.
- `let` and `let*` in Interpreter.cpp 191,988,083, time down to 28s.
2023-10-07 10:48:17 -04:00
|
|
|
if (uneval_head.as_symbol() == "inspect" || uneval_head.as_symbol() == "print") {
|
2020-09-12 13:11:42 -04:00
|
|
|
is_method_call = true;
|
2021-02-26 15:27:13 -05:00
|
|
|
} else {
|
2024-04-06 16:01:17 -04:00
|
|
|
if (is_local_symbol(uneval_head, env) || m_symbol_types.lookup(uneval_head.as_symbol())) {
|
2021-02-26 15:27:13 -05:00
|
|
|
// the local environment (mlets, lexicals, constants, globals) defines this symbol.
|
|
|
|
// this will "win" over a method name lookup, so we should compile as normal
|
|
|
|
head = compile_error_guard(args.unnamed.front(), env);
|
|
|
|
} else {
|
|
|
|
// we don't think compiling the head give us a function, so it's either a method or an
|
|
|
|
// error
|
|
|
|
is_method_call = true;
|
|
|
|
}
|
2020-09-12 13:11:42 -04:00
|
|
|
}
|
2021-02-26 15:27:13 -05:00
|
|
|
|
2020-09-12 13:11:42 -04:00
|
|
|
} else {
|
|
|
|
// the head is some expression. Could be something like (inline my-func) or (-> obj
|
|
|
|
// func-ptr-field) in either case, compile it - and it can't be a method call.
|
|
|
|
head = compile_error_guard(args.unnamed.front(), env);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!is_method_call) {
|
|
|
|
// typecheck that we got a function
|
|
|
|
typecheck(form, m_ts.make_typespec("function"), head->type(), "Function call head");
|
|
|
|
}
|
|
|
|
|
|
|
|
// see if its an "immediate" application. This happens in three cases:
|
|
|
|
// 1). the user directly puts a (lambda ...) form in the head (like with a (let) macro)
|
|
|
|
// 2). the user used a (inline my-func) to grab the LambdaPlace of the function.
|
|
|
|
// 3). the auto-inlining above looked up the LambdaPlace of an inlinable_function.
|
|
|
|
|
|
|
|
// note that an inlineable function looked up by symbol or other way WILL NOT cast to a
|
|
|
|
// LambdaPlace! so this cast will only succeed if the auto-inliner succeeded, or the user has
|
|
|
|
// passed use explicitly a lambda either with the lambda form, or with the (inline ...) form.
|
|
|
|
LambdaVal* head_as_lambda = nullptr;
|
2020-09-24 17:19:23 -04:00
|
|
|
bool got_inlined_lambda = false;
|
2020-09-12 13:11:42 -04:00
|
|
|
if (!is_method_call) {
|
2020-09-24 17:19:23 -04:00
|
|
|
// try directly as a lambda
|
2020-09-12 13:11:42 -04:00
|
|
|
head_as_lambda = dynamic_cast<LambdaVal*>(head);
|
2020-09-24 17:19:23 -04:00
|
|
|
|
|
|
|
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.
|
[goalc] default to non-immediate lambdas if not requested (#2604)
This fixes a long time issue with `lambda`. The `lambda` is a bit
overloaded in OpenGOAL: it's used in the implementation of `let`, and
also to define local anonymous functions.
```
(defmacro let (bindings &rest body)
`((lambda :inline #t ,(apply first bindings) ,@body)
,@(apply second bindings)))
```
```
(defmacro defun (name bindings &rest body)
(let ((docstring ""))
(when (and (> (length body) 1) (string? (first body)))
(set! docstring (first body))
(set! body (cdr body)))
`(define ,name ,docstring (lambda :name ,name ,bindings ,@body))))
```
In the first case of a `let`, a `return` from inside the `let` should
return from the functioning containing the `let`, not the scope of the
`lambda`. In the second case, we should return from the lambda. The way
we told the different between these cases was if the `lambda` was used
"immeidately", in the head of an expression (like it would be for the
`let` macro). But, this falsely triggers when an anonymous function is
used immediately: eg
```
((lambda () (return #f)))
```
should generate and call a real x86 function that returns immediately.
This should fix some death/mission failed stuff in jak 2.
2023-04-30 19:00:27 -04:00
|
|
|
head_as_lambda =
|
|
|
|
env->function_env()->alloc_val<LambdaVal>(head_as_inlined_lambda->lv.type, false);
|
2023-02-24 18:32:30 -05:00
|
|
|
head_as_lambda->lambda = head_as_inlined_lambda->lv.lambda;
|
2020-09-24 17:19:23 -04:00
|
|
|
got_inlined_lambda = true;
|
|
|
|
}
|
[goalc] default to non-immediate lambdas if not requested (#2604)
This fixes a long time issue with `lambda`. The `lambda` is a bit
overloaded in OpenGOAL: it's used in the implementation of `let`, and
also to define local anonymous functions.
```
(defmacro let (bindings &rest body)
`((lambda :inline #t ,(apply first bindings) ,@body)
,@(apply second bindings)))
```
```
(defmacro defun (name bindings &rest body)
(let ((docstring ""))
(when (and (> (length body) 1) (string? (first body)))
(set! docstring (first body))
(set! body (cdr body)))
`(define ,name ,docstring (lambda :name ,name ,bindings ,@body))))
```
In the first case of a `let`, a `return` from inside the `let` should
return from the functioning containing the `let`, not the scope of the
`lambda`. In the second case, we should return from the lambda. The way
we told the different between these cases was if the `lambda` was used
"immeidately", in the head of an expression (like it would be for the
`let` macro). But, this falsely triggers when an anonymous function is
used immediately: eg
```
((lambda () (return #f)))
```
should generate and call a real x86 function that returns immediately.
This should fix some death/mission failed stuff in jak 2.
2023-04-30 19:00:27 -04:00
|
|
|
} else {
|
|
|
|
// we got a lambda: but we don't want to use immediates by default:
|
|
|
|
if (!auto_inline && !head_as_lambda->is_immediate) {
|
|
|
|
head_as_lambda = nullptr;
|
|
|
|
}
|
2020-09-24 17:19:23 -04:00
|
|
|
}
|
2020-09-12 13:11:42 -04:00
|
|
|
}
|
|
|
|
|
2020-09-24 17:19:23 -04:00
|
|
|
// no lambda (not inlining or immediate), and not a method call, so we should actually get
|
|
|
|
// the function pointer.
|
2020-09-18 22:02:27 -04:00
|
|
|
if (!head_as_lambda && !is_method_call) {
|
2021-08-26 20:33:00 -04:00
|
|
|
head = head->to_gpr(form, env);
|
2020-09-14 16:45:42 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// compile arguments
|
|
|
|
std::vector<RegVal*> eval_args;
|
|
|
|
for (uint32_t i = 1; i < args.unnamed.size(); i++) {
|
|
|
|
auto intermediate = compile_error_guard(args.unnamed.at(i), env);
|
2021-08-26 20:33:00 -04:00
|
|
|
eval_args.push_back(intermediate->to_reg(args.unnamed.at(i), env));
|
2020-09-14 16:45:42 -04:00
|
|
|
}
|
|
|
|
|
2020-09-12 13:11:42 -04:00
|
|
|
if (head_as_lambda) {
|
2020-09-24 17:19:23 -04:00
|
|
|
// inline/immediate the function!
|
2020-09-12 13:11:42 -04:00
|
|
|
|
|
|
|
// check args are ok
|
|
|
|
if (head_as_lambda->lambda.params.size() != eval_args.size()) {
|
2020-12-01 21:39:46 -05:00
|
|
|
throw_compiler_error(form, "Expected {} arguments but got {} for inlined lambda.",
|
|
|
|
head_as_lambda->lambda.params.size(), eval_args.size());
|
2020-09-12 13:11:42 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// construct a lexical environment
|
|
|
|
auto lexical_env = fe->alloc_env<LexicalEnv>(env);
|
|
|
|
|
2020-11-22 20:10:33 -05:00
|
|
|
Env* inlined_compile_env = lexical_env;
|
2020-09-12 13:11:42 -04:00
|
|
|
|
2020-09-24 17:19:23 -04:00
|
|
|
// if need to, create a label env.
|
|
|
|
// we don't want a separate label env with lets, but we do for inlined functions.
|
|
|
|
// either inlined through the auto-inliner, or through an explicit (inline x) form.
|
|
|
|
if (auto_inline || got_inlined_lambda) {
|
2020-11-22 20:10:33 -05:00
|
|
|
inlined_compile_env = fe->alloc_env<LabelEnv>(lexical_env);
|
2020-09-12 13:11:42 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// check arg types
|
|
|
|
if (!head->type().arg_count()) {
|
|
|
|
if (head->type().arg_count() - 1 != eval_args.size()) {
|
2020-12-01 21:39:46 -05:00
|
|
|
throw_compiler_error(form,
|
|
|
|
"Expected {} arguments for an inlined lambda with type {} but got {}.",
|
|
|
|
head->type().arg_count() - 1, head->type().print(), eval_args.size());
|
2020-09-12 13:11:42 -04:00
|
|
|
}
|
2020-09-24 17:19:23 -04:00
|
|
|
// immediate lambdas (lets) will have all types as the most general object by default
|
|
|
|
// inlined functions will have real types that are checked...
|
2020-09-12 13:11:42 -04:00
|
|
|
for (uint32_t i = 0; i < eval_args.size(); i++) {
|
|
|
|
typecheck(form, head->type().get_arg(i), eval_args.at(i)->type(),
|
2020-09-24 17:19:23 -04:00
|
|
|
"function/lambda (inline/immediate) argument");
|
2020-09-12 13:11:42 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// copy args...
|
|
|
|
for (uint32_t i = 0; i < eval_args.size(); i++) {
|
2020-09-24 17:19:23 -04:00
|
|
|
// note, inlined functions will get a more specific type if possible
|
|
|
|
// todo, is this right?
|
2020-09-12 13:11:42 -04:00
|
|
|
auto type = eval_args.at(i)->type();
|
2022-10-14 20:47:59 -04:00
|
|
|
auto copy =
|
|
|
|
env->make_ireg(type, m_ts.lookup_type_allow_partial_def(type)->get_preferred_reg_class());
|
2021-08-26 20:33:00 -04:00
|
|
|
env->emit_ir<IR_RegSet>(form, copy, eval_args.at(i));
|
2020-11-29 18:01:30 -05:00
|
|
|
copy->mark_as_settable();
|
[goalc] Cleaned up speedups (#3066)
Started at 349,880,038 allocations and 42s
- Switched to making `Symbol` in GOOS be a "fixed type", just a wrapper
around a `const char*` pointing to the string in the symbol table. This
is a step toward making a lot of things better, but by itself not a huge
improvement. Some things may be worse due to more temp `std::string`
allocations, but one day all these can be removed. On linux it saved
allocations (347,685,429), and saved a second or two (41 s).
- cache `#t` and `#f` in interpreter, better lookup for special
forms/builtins (hashtable of pointers instead of strings, vector for the
small special form list). Dropped time to 38s.
- special-case in quasiquote when splicing is the last thing in a list.
Allocation dropped to 340,603,082
- custom hash table for environment lookups (lexical vars). Dropped to
36s and 314,637,194
- less allocation in `read_list` 311,613,616. Time about the same.
- `let` and `let*` in Interpreter.cpp 191,988,083, time down to 28s.
2023-10-07 10:48:17 -04:00
|
|
|
lexical_env->vars[m_goos.intern_ptr(head_as_lambda->lambda.params.at(i).name)] = copy;
|
2020-09-12 13:11:42 -04:00
|
|
|
}
|
|
|
|
|
2020-11-22 20:10:33 -05:00
|
|
|
// setup env
|
|
|
|
BlockEnv* inlined_block_env = nullptr;
|
|
|
|
RegVal* result_reg_if_return_from = nullptr;
|
|
|
|
if (auto_inline || got_inlined_lambda) {
|
|
|
|
inlined_block_env = fe->alloc_env<BlockEnv>(inlined_compile_env, "#f");
|
2021-05-20 20:12:49 -04:00
|
|
|
RegClass ret_class = RegClass::GPR_64;
|
|
|
|
if (head->type().last_arg() != TypeSpec("none") &&
|
|
|
|
m_ts.lookup_type(head->type().last_arg())->get_load_size() == 16) {
|
|
|
|
ret_class = RegClass::INT_128;
|
|
|
|
}
|
|
|
|
result_reg_if_return_from =
|
|
|
|
inlined_compile_env->make_ireg(head->type().last_arg(), ret_class);
|
|
|
|
|
2020-11-22 20:10:33 -05:00
|
|
|
inlined_block_env->return_value = result_reg_if_return_from;
|
|
|
|
inlined_block_env->end_label = Label(fe);
|
|
|
|
inlined_compile_env = inlined_block_env;
|
|
|
|
}
|
|
|
|
|
2020-09-12 13:11:42 -04:00
|
|
|
// compile inline!
|
|
|
|
bool first_thing = true;
|
|
|
|
Val* result = get_none();
|
|
|
|
for_each_in_list(head_as_lambda->lambda.body, [&](const goos::Object& o) {
|
2020-11-22 20:10:33 -05:00
|
|
|
result = compile_error_guard(o, inlined_compile_env);
|
2020-09-24 17:19:23 -04:00
|
|
|
if (!dynamic_cast<None*>(result)) {
|
2021-08-26 20:33:00 -04:00
|
|
|
result = result->to_reg(o, inlined_compile_env);
|
2020-09-24 17:19:23 -04:00
|
|
|
}
|
2020-09-12 13:11:42 -04:00
|
|
|
if (first_thing) {
|
|
|
|
first_thing = false;
|
|
|
|
lexical_env->settings.is_set = true;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2020-09-24 17:19:23 -04:00
|
|
|
// 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?
|
2020-11-22 20:10:33 -05:00
|
|
|
|
|
|
|
if (inlined_block_env && !inlined_block_env->return_types.empty()) {
|
|
|
|
// there were return froms used in the function, so we fall back to using the separate
|
|
|
|
// return gpr.
|
|
|
|
if (!dynamic_cast<None*>(result)) {
|
2021-08-26 20:33:00 -04:00
|
|
|
auto final_result = result->to_reg(form, inlined_compile_env);
|
2021-05-20 20:12:49 -04:00
|
|
|
inlined_block_env->return_types.push_back(final_result->type());
|
|
|
|
|
|
|
|
for (const auto& possible_type : inlined_block_env->return_types) {
|
|
|
|
if (possible_type != TypeSpec("none") &&
|
|
|
|
m_ts.lookup_type(possible_type)->get_load_size() == 16) {
|
|
|
|
result_reg_if_return_from->change_class(RegClass::INT_128);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-26 20:33:00 -04:00
|
|
|
inlined_compile_env->emit_ir<IR_RegSet>(form, result_reg_if_return_from, final_result);
|
2021-05-20 20:12:49 -04:00
|
|
|
|
2020-11-22 20:10:33 -05:00
|
|
|
auto return_type = m_ts.lowest_common_ancestor(inlined_block_env->return_types);
|
|
|
|
inlined_block_env->return_value->set_type(return_type);
|
|
|
|
} else {
|
|
|
|
inlined_block_env->return_value->set_type(get_none()->type());
|
|
|
|
}
|
|
|
|
|
2021-08-26 20:33:00 -04:00
|
|
|
inlined_compile_env->emit_ir<IR_Null>(form);
|
2020-11-22 20:10:33 -05:00
|
|
|
inlined_block_env->end_label.idx = inlined_block_env->end_label.func->code().size();
|
|
|
|
return inlined_block_env->return_value;
|
|
|
|
}
|
|
|
|
|
2021-08-26 20:33:00 -04:00
|
|
|
inlined_compile_env->emit_ir<IR_Null>(form);
|
2020-09-12 13:11:42 -04:00
|
|
|
return result;
|
|
|
|
} else {
|
2020-09-24 17:19:23 -04:00
|
|
|
// 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.
|
2020-09-12 13:11:42 -04:00
|
|
|
if (is_method_call) {
|
2020-09-24 17:19:23 -04:00
|
|
|
// method needs at least one argument to tell what we're calling the method on.
|
2020-09-19 16:50:42 -04:00
|
|
|
if (eval_args.empty()) {
|
2020-12-01 21:39:46 -05:00
|
|
|
throw_compiler_error(form, "Unrecognized symbol {} as head of form.", uneval_head.print());
|
2020-09-19 16:50:42 -04:00
|
|
|
}
|
2021-04-25 14:48:54 -04:00
|
|
|
|
2020-09-24 17:19:23 -04:00
|
|
|
// get the method function pointer
|
2021-04-25 14:48:54 -04:00
|
|
|
head = compile_get_method_of_object(form, eval_args.front(), symbol_string(uneval_head), env,
|
|
|
|
true);
|
2020-09-12 13:11:42 -04:00
|
|
|
}
|
|
|
|
|
2020-09-24 17:19:23 -04:00
|
|
|
// convert the head to a GPR (if function, this is already done)
|
2021-08-26 20:33:00 -04:00
|
|
|
auto head_as_gpr = head->to_gpr(form, env);
|
2020-09-12 13:11:42 -04:00
|
|
|
if (head_as_gpr) {
|
2020-09-24 17:19:23 -04:00
|
|
|
// method calls have special rules for typing _type_ arguments.
|
2020-09-19 16:50:42 -04:00
|
|
|
if (is_method_call) {
|
|
|
|
return compile_real_function_call(form, head_as_gpr, eval_args, env,
|
|
|
|
eval_args.front()->type().base_type());
|
|
|
|
} else {
|
|
|
|
return compile_real_function_call(form, head_as_gpr, eval_args, env);
|
|
|
|
}
|
|
|
|
|
2020-09-12 13:11:42 -04:00
|
|
|
} else {
|
2020-12-01 21:39:46 -05:00
|
|
|
throw_compiler_error(form, "Invalid function call! Possibly a compiler bug.");
|
2020-09-12 13:11:42 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-08 19:02:47 -05:00
|
|
|
ASSERT(false);
|
2020-09-12 13:11:42 -04:00
|
|
|
return get_none();
|
|
|
|
}
|
|
|
|
|
2020-09-24 17:19:23 -04:00
|
|
|
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.
|
|
|
|
*/
|
2020-09-12 13:11:42 -04:00
|
|
|
Val* Compiler::compile_real_function_call(const goos::Object& form,
|
|
|
|
RegVal* function,
|
|
|
|
const std::vector<RegVal*>& args,
|
2020-09-19 16:50:42 -04:00
|
|
|
Env* env,
|
2020-09-24 17:19:23 -04:00
|
|
|
const std::string& method_type_name) {
|
2021-08-24 22:15:26 -04:00
|
|
|
auto fe = env->function_env();
|
2020-09-12 13:11:42 -04:00
|
|
|
fe->require_aligned_stack();
|
|
|
|
TypeSpec return_ts;
|
|
|
|
if (function->type().arg_count() == 0) {
|
2020-09-25 21:11:27 -04:00
|
|
|
// if the type system doesn't know what the function will return, don't allow it to be called
|
2020-12-01 21:39:46 -05:00
|
|
|
throw_compiler_error(
|
|
|
|
form, "This function call has unknown argument and return types and cannot be called.");
|
2020-09-12 13:11:42 -04:00
|
|
|
} else {
|
|
|
|
return_ts = function->type().last_arg();
|
|
|
|
}
|
|
|
|
|
2021-05-20 20:12:49 -04:00
|
|
|
auto cc = get_function_calling_convention(function->type(), m_ts);
|
|
|
|
RegClass ret_reg_class = RegClass::GPR_64;
|
|
|
|
if (cc.return_reg && cc.return_reg->is_xmm()) {
|
|
|
|
ret_reg_class = RegClass::INT_128;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto return_reg = env->make_ireg(return_ts, ret_reg_class);
|
2020-09-12 13:11:42 -04:00
|
|
|
|
|
|
|
// check arg count:
|
2020-09-24 17:19:23 -04:00
|
|
|
if (function->type().arg_count() && !is_varargs_function(function->type())) {
|
2020-09-12 13:11:42 -04:00
|
|
|
if (function->type().arg_count() - 1 != args.size()) {
|
2020-12-01 21:39:46 -05:00
|
|
|
throw_compiler_error(form,
|
|
|
|
"Expected {} arguments but got {} for a real function call on type {}.",
|
|
|
|
function->type().arg_count() - 1, args.size(), function->type().print());
|
2020-09-12 13:11:42 -04:00
|
|
|
}
|
|
|
|
for (uint32_t i = 0; i < args.size(); i++) {
|
2020-09-19 16:50:42 -04:00
|
|
|
if (method_type_name.empty()) {
|
2020-11-22 12:59:55 -05:00
|
|
|
typecheck(form, function->type().get_arg(i), args.at(i)->type(),
|
|
|
|
fmt::format("function argument {}", i));
|
2020-09-19 16:50:42 -04:00
|
|
|
} else {
|
|
|
|
typecheck(form, function->type().get_arg(i).substitute_for_method_call(method_type_name),
|
2020-11-22 12:59:55 -05:00
|
|
|
args.at(i)->type(), fmt::format("function argument {}", i));
|
2020-09-19 16:50:42 -04:00
|
|
|
}
|
2020-09-12 13:11:42 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-16 17:08:26 -04:00
|
|
|
if (args.size() > 8) {
|
2020-12-01 21:39:46 -05:00
|
|
|
throw_compiler_error(form, "Function call cannot use more than 8 parameters.");
|
2020-10-16 17:08:26 -04:00
|
|
|
}
|
|
|
|
|
2020-09-12 13:11:42 -04:00
|
|
|
// set args (introducing a move here makes coloring more likely to be possible)
|
|
|
|
std::vector<RegVal*> arg_outs;
|
2021-05-20 20:12:49 -04:00
|
|
|
for (int i = 0; i < (int)args.size(); i++) {
|
|
|
|
const auto& arg = args.at(i);
|
|
|
|
auto reg = cc.arg_regs.at(i);
|
|
|
|
arg_outs.push_back(
|
|
|
|
env->make_ireg(arg->type(), reg.is_xmm() ? RegClass::INT_128 : RegClass::GPR_64));
|
2020-11-29 18:01:30 -05:00
|
|
|
arg_outs.back()->mark_as_settable();
|
2021-08-26 20:33:00 -04:00
|
|
|
env->emit_ir<IR_RegSet>(form, arg_outs.back(), arg);
|
2020-09-12 13:11:42 -04:00
|
|
|
}
|
|
|
|
|
2020-09-24 17:19:23 -04:00
|
|
|
// todo, there's probably a more efficient way to do this.
|
|
|
|
auto temp_function = fe->make_gpr(function->type());
|
2021-08-26 20:33:00 -04:00
|
|
|
env->emit_ir<IR_RegSet>(form, temp_function, function);
|
|
|
|
env->emit_ir<IR_FunctionCall>(form, temp_function, return_reg, arg_outs, cc.arg_regs,
|
|
|
|
cc.return_reg);
|
2020-09-12 20:41:12 -04:00
|
|
|
|
|
|
|
if (m_settings.emit_move_after_return) {
|
2021-05-20 20:12:49 -04:00
|
|
|
auto result_reg = env->make_ireg(return_reg->type(), ret_reg_class);
|
2021-08-26 20:33:00 -04:00
|
|
|
env->emit_ir<IR_RegSet>(form, result_reg, return_reg);
|
2020-09-12 20:41:12 -04:00
|
|
|
return result_reg;
|
|
|
|
} else {
|
|
|
|
return return_reg;
|
|
|
|
}
|
2020-09-13 10:40:21 -04:00
|
|
|
}
|
|
|
|
|
2020-09-24 17:19:23 -04:00
|
|
|
/*!
|
|
|
|
* 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.
|
|
|
|
*/
|
2020-09-13 10:40:21 -04:00
|
|
|
Val* Compiler::compile_declare(const goos::Object& form, const goos::Object& rest, Env* env) {
|
2021-08-24 22:15:26 -04:00
|
|
|
auto& settings = get_parent_env_of_type_slow<DeclareEnv>(env)->settings;
|
2020-09-13 10:40:21 -04:00
|
|
|
|
|
|
|
if (settings.is_set) {
|
2020-12-01 21:39:46 -05:00
|
|
|
throw_compiler_error(form, "Function cannot have multiple declares");
|
2020-09-13 10:40:21 -04:00
|
|
|
}
|
|
|
|
settings.is_set = true;
|
|
|
|
|
|
|
|
for_each_in_list(rest, [&](const goos::Object& o) {
|
|
|
|
if (!o.is_pair()) {
|
2020-12-01 21:39:46 -05:00
|
|
|
throw_compiler_error(o, "Invalid declare specification.");
|
2020-09-13 10:40:21 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
auto first = o.as_pair()->car;
|
2020-12-04 12:57:10 -05:00
|
|
|
auto rrest = &o.as_pair()->cdr;
|
2020-09-13 10:40:21 -04:00
|
|
|
|
|
|
|
if (!first.is_symbol()) {
|
2020-12-01 21:39:46 -05:00
|
|
|
throw_compiler_error(
|
|
|
|
first, "Invalid declare option specification, expected a symbol, but got {} instead.",
|
|
|
|
first.print());
|
2020-09-13 10:40:21 -04:00
|
|
|
}
|
|
|
|
|
[goalc] Cleaned up speedups (#3066)
Started at 349,880,038 allocations and 42s
- Switched to making `Symbol` in GOOS be a "fixed type", just a wrapper
around a `const char*` pointing to the string in the symbol table. This
is a step toward making a lot of things better, but by itself not a huge
improvement. Some things may be worse due to more temp `std::string`
allocations, but one day all these can be removed. On linux it saved
allocations (347,685,429), and saved a second or two (41 s).
- cache `#t` and `#f` in interpreter, better lookup for special
forms/builtins (hashtable of pointers instead of strings, vector for the
small special form list). Dropped time to 38s.
- special-case in quasiquote when splicing is the last thing in a list.
Allocation dropped to 340,603,082
- custom hash table for environment lookups (lexical vars). Dropped to
36s and 314,637,194
- less allocation in `read_list` 311,613,616. Time about the same.
- `let` and `let*` in Interpreter.cpp 191,988,083, time down to 28s.
2023-10-07 10:48:17 -04:00
|
|
|
if (first.as_symbol() == "inline") {
|
2020-12-04 12:57:10 -05:00
|
|
|
if (!rrest->is_empty_list()) {
|
2020-12-01 21:39:46 -05:00
|
|
|
throw_compiler_error(first, "Invalid inline declare, no options were expected.");
|
2020-09-13 10:40:21 -04:00
|
|
|
}
|
|
|
|
settings.allow_inline = true;
|
|
|
|
settings.inline_by_default = true;
|
|
|
|
settings.save_code = true;
|
[goalc] Cleaned up speedups (#3066)
Started at 349,880,038 allocations and 42s
- Switched to making `Symbol` in GOOS be a "fixed type", just a wrapper
around a `const char*` pointing to the string in the symbol table. This
is a step toward making a lot of things better, but by itself not a huge
improvement. Some things may be worse due to more temp `std::string`
allocations, but one day all these can be removed. On linux it saved
allocations (347,685,429), and saved a second or two (41 s).
- cache `#t` and `#f` in interpreter, better lookup for special
forms/builtins (hashtable of pointers instead of strings, vector for the
small special form list). Dropped time to 38s.
- special-case in quasiquote when splicing is the last thing in a list.
Allocation dropped to 340,603,082
- custom hash table for environment lookups (lexical vars). Dropped to
36s and 314,637,194
- less allocation in `read_list` 311,613,616. Time about the same.
- `let` and `let*` in Interpreter.cpp 191,988,083, time down to 28s.
2023-10-07 10:48:17 -04:00
|
|
|
} else if (first.as_symbol() == "allow-inline") {
|
2020-12-04 12:57:10 -05:00
|
|
|
if (!rrest->is_empty_list()) {
|
2020-12-01 21:39:46 -05:00
|
|
|
throw_compiler_error(first, "Invalid allow-inline declare");
|
2020-09-13 10:40:21 -04:00
|
|
|
}
|
|
|
|
settings.allow_inline = true;
|
|
|
|
settings.inline_by_default = false;
|
|
|
|
settings.save_code = true;
|
[goalc] Cleaned up speedups (#3066)
Started at 349,880,038 allocations and 42s
- Switched to making `Symbol` in GOOS be a "fixed type", just a wrapper
around a `const char*` pointing to the string in the symbol table. This
is a step toward making a lot of things better, but by itself not a huge
improvement. Some things may be worse due to more temp `std::string`
allocations, but one day all these can be removed. On linux it saved
allocations (347,685,429), and saved a second or two (41 s).
- cache `#t` and `#f` in interpreter, better lookup for special
forms/builtins (hashtable of pointers instead of strings, vector for the
small special form list). Dropped time to 38s.
- special-case in quasiquote when splicing is the last thing in a list.
Allocation dropped to 340,603,082
- custom hash table for environment lookups (lexical vars). Dropped to
36s and 314,637,194
- less allocation in `read_list` 311,613,616. Time about the same.
- `let` and `let*` in Interpreter.cpp 191,988,083, time down to 28s.
2023-10-07 10:48:17 -04:00
|
|
|
} else if (first.as_symbol() == "asm-func") {
|
2021-08-24 22:15:26 -04:00
|
|
|
auto fe = env->function_env();
|
2020-12-04 12:57:10 -05:00
|
|
|
fe->is_asm_func = true;
|
|
|
|
if (!rrest->is_pair()) {
|
|
|
|
throw_compiler_error(
|
|
|
|
form, "Declare asm-func must provide the function's return type as an argument.");
|
|
|
|
}
|
2022-04-07 19:13:22 -04:00
|
|
|
fe->asm_func_return_type = parse_typespec(rrest->as_pair()->car, env);
|
2020-12-04 12:57:10 -05:00
|
|
|
if (!rrest->as_pair()->cdr.is_empty_list()) {
|
|
|
|
throw_compiler_error(first, "Invalid asm-func declare");
|
|
|
|
}
|
[goalc] Cleaned up speedups (#3066)
Started at 349,880,038 allocations and 42s
- Switched to making `Symbol` in GOOS be a "fixed type", just a wrapper
around a `const char*` pointing to the string in the symbol table. This
is a step toward making a lot of things better, but by itself not a huge
improvement. Some things may be worse due to more temp `std::string`
allocations, but one day all these can be removed. On linux it saved
allocations (347,685,429), and saved a second or two (41 s).
- cache `#t` and `#f` in interpreter, better lookup for special
forms/builtins (hashtable of pointers instead of strings, vector for the
small special form list). Dropped time to 38s.
- special-case in quasiquote when splicing is the last thing in a list.
Allocation dropped to 340,603,082
- custom hash table for environment lookups (lexical vars). Dropped to
36s and 314,637,194
- less allocation in `read_list` 311,613,616. Time about the same.
- `let` and `let*` in Interpreter.cpp 191,988,083, time down to 28s.
2023-10-07 10:48:17 -04:00
|
|
|
} else if (first.as_symbol() == "print-asm") {
|
2020-12-04 12:57:10 -05:00
|
|
|
if (!rrest->is_empty_list()) {
|
|
|
|
throw_compiler_error(first, "Invalid print-asm declare");
|
|
|
|
}
|
|
|
|
settings.print_asm = true;
|
2020-12-06 15:42:26 -05:00
|
|
|
|
[goalc] Cleaned up speedups (#3066)
Started at 349,880,038 allocations and 42s
- Switched to making `Symbol` in GOOS be a "fixed type", just a wrapper
around a `const char*` pointing to the string in the symbol table. This
is a step toward making a lot of things better, but by itself not a huge
improvement. Some things may be worse due to more temp `std::string`
allocations, but one day all these can be removed. On linux it saved
allocations (347,685,429), and saved a second or two (41 s).
- cache `#t` and `#f` in interpreter, better lookup for special
forms/builtins (hashtable of pointers instead of strings, vector for the
small special form list). Dropped time to 38s.
- special-case in quasiquote when splicing is the last thing in a list.
Allocation dropped to 340,603,082
- custom hash table for environment lookups (lexical vars). Dropped to
36s and 314,637,194
- less allocation in `read_list` 311,613,616. Time about the same.
- `let` and `let*` in Interpreter.cpp 191,988,083, time down to 28s.
2023-10-07 10:48:17 -04:00
|
|
|
} else if (first.as_symbol() == "allow-saved-regs") {
|
2020-12-06 15:42:26 -05:00
|
|
|
if (!rrest->is_empty_list()) {
|
|
|
|
throw_compiler_error(first, "Invalid allow-saved-regs declare");
|
|
|
|
}
|
2021-08-24 22:15:26 -04:00
|
|
|
auto fe = env->function_env();
|
2020-12-06 15:42:26 -05:00
|
|
|
fe->asm_func_saved_regs = true;
|
|
|
|
|
2020-12-01 21:39:46 -05:00
|
|
|
} else {
|
|
|
|
throw_compiler_error(first, "Unrecognized declare option {}.", first.print());
|
2020-09-13 10:40:21 -04:00
|
|
|
}
|
|
|
|
});
|
|
|
|
return get_none();
|
|
|
|
}
|
2021-12-12 12:52:23 -05:00
|
|
|
|
|
|
|
Val* Compiler::compile_declare_file(const goos::Object& /*form*/,
|
|
|
|
const goos::Object& rest,
|
|
|
|
Env* env) {
|
|
|
|
for_each_in_list(rest, [&](const goos::Object& o) {
|
|
|
|
if (!o.is_pair()) {
|
|
|
|
throw_compiler_error(o, "Invalid declare-file specification.");
|
|
|
|
}
|
|
|
|
|
|
|
|
auto first = o.as_pair()->car;
|
|
|
|
auto rrest = &o.as_pair()->cdr;
|
|
|
|
|
|
|
|
if (!first.is_symbol()) {
|
|
|
|
throw_compiler_error(
|
|
|
|
first, "Invalid declare option specification, expected a symbol, but got {} instead.",
|
|
|
|
first.print());
|
|
|
|
}
|
|
|
|
|
[goalc] Cleaned up speedups (#3066)
Started at 349,880,038 allocations and 42s
- Switched to making `Symbol` in GOOS be a "fixed type", just a wrapper
around a `const char*` pointing to the string in the symbol table. This
is a step toward making a lot of things better, but by itself not a huge
improvement. Some things may be worse due to more temp `std::string`
allocations, but one day all these can be removed. On linux it saved
allocations (347,685,429), and saved a second or two (41 s).
- cache `#t` and `#f` in interpreter, better lookup for special
forms/builtins (hashtable of pointers instead of strings, vector for the
small special form list). Dropped time to 38s.
- special-case in quasiquote when splicing is the last thing in a list.
Allocation dropped to 340,603,082
- custom hash table for environment lookups (lexical vars). Dropped to
36s and 314,637,194
- less allocation in `read_list` 311,613,616. Time about the same.
- `let` and `let*` in Interpreter.cpp 191,988,083, time down to 28s.
2023-10-07 10:48:17 -04:00
|
|
|
if (first.as_symbol() == "debug") {
|
2021-12-12 12:52:23 -05:00
|
|
|
if (!rrest->is_empty_list()) {
|
|
|
|
throw_compiler_error(first, "Invalid debug declare");
|
|
|
|
}
|
2023-02-13 16:39:14 -05:00
|
|
|
if (!env->file_env()->is_debug_file()) {
|
|
|
|
env->file_env()->set_debug_file();
|
|
|
|
throw DebugFileDeclareException();
|
|
|
|
}
|
2021-12-12 12:52:23 -05:00
|
|
|
|
|
|
|
} else {
|
|
|
|
throw_compiler_error(first, "Unrecognized declare-file option {}.", first.print());
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
return get_none();
|
2023-02-13 16:39:14 -05:00
|
|
|
}
|