mirror of
https://github.com/open-goal/jak-project
synced 2026-07-11 23:30:16 -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.
This commit is contained in:
@@ -211,14 +211,14 @@
|
||||
|
||||
;; Bind vars in body
|
||||
(defmacro let (bindings &rest body)
|
||||
`((lambda :inline-only #t ,(apply first bindings) ,@body)
|
||||
`((lambda :immediate #t ,(apply first bindings) ,@body)
|
||||
,@(apply second bindings)))
|
||||
|
||||
;; Let, but recursive, allowing you to define variables in terms of others.
|
||||
(defmacro let* (bindings &rest body)
|
||||
(if (null? bindings)
|
||||
`(begin ,@body)
|
||||
`((lambda :inline-only #t (,(caar bindings))
|
||||
`((lambda :immediate #t (,(caar bindings))
|
||||
(let* ,(cdr bindings) ,@body))
|
||||
,(car (cdar bindings))
|
||||
)
|
||||
@@ -229,7 +229,7 @@
|
||||
(defmacro mlet* (bindings &rest body)
|
||||
(if (null? bindings)
|
||||
`(begin ,@body)
|
||||
`((lambda :inline-only #t (,(caar bindings))
|
||||
`((lambda :immediate #t (,(caar bindings))
|
||||
(mlet* ,(cdr bindings) ,@body))
|
||||
,(car (cdar bindings))
|
||||
)
|
||||
@@ -503,7 +503,7 @@
|
||||
`(let ((,(car bindings) ,(cadr bindings)))
|
||||
(while (not (null? ,(car bindings)))
|
||||
,@body
|
||||
|
||||
|
||||
(set! ,(car bindings) (cdr ,(car bindings)))
|
||||
)
|
||||
)
|
||||
|
||||
@@ -131,10 +131,11 @@ class SymbolValueVal : public Val {
|
||||
*/
|
||||
class LambdaVal : public Val {
|
||||
public:
|
||||
explicit LambdaVal(TypeSpec ts) : Val(std::move(ts)) {}
|
||||
explicit LambdaVal(TypeSpec ts, bool immediate) : Val(std::move(ts)), is_immediate(immediate) {}
|
||||
std::string print() const override { return "lambda-" + lambda.debug_name; }
|
||||
FunctionEnv* func = nullptr;
|
||||
Lambda lambda;
|
||||
bool is_immediate = false;
|
||||
RegVal* to_reg(const goos::Object& form, Env* fe) override;
|
||||
};
|
||||
|
||||
|
||||
@@ -101,15 +101,18 @@ Val* Compiler::compile_lambda(const goos::Object& form, const goos::Object& rest
|
||||
auto obj_env = env->file_env();
|
||||
auto args = get_va(form, rest);
|
||||
if (args.unnamed.empty() || !args.unnamed.front().is_list() ||
|
||||
!args.only_contains_named({"name", "inline-only", "segment", "behavior"})) {
|
||||
!args.only_contains_named({"name", "segment", "behavior", "immediate"})) {
|
||||
throw_compiler_error(form, "Invalid lambda form");
|
||||
}
|
||||
|
||||
bool immediate =
|
||||
args.has_named("immediate") && symbol_string(args.get_named("immediate")) != "#f";
|
||||
|
||||
// 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.
|
||||
auto place = obj_env->alloc_val<LambdaVal>(get_none()->type());
|
||||
auto place = obj_env->alloc_val<LambdaVal>(get_none()->type(), immediate);
|
||||
auto& lambda = place->lambda;
|
||||
auto lambda_ts = m_ts.make_typespec("function");
|
||||
|
||||
@@ -141,9 +144,6 @@ Val* Compiler::compile_lambda(const goos::Object& form, const goos::Object& rest
|
||||
lambda.body = get_lambda_body(rest); // first is the argument list, rest is body
|
||||
place->func = nullptr;
|
||||
|
||||
bool inline_only =
|
||||
args.has_named("inline-only") && symbol_string(args.get_named("inline-only")) != "#f";
|
||||
|
||||
// pick default segment to store function in.
|
||||
int segment = fe->segment_for_static_data();
|
||||
|
||||
@@ -159,7 +159,7 @@ Val* Compiler::compile_lambda(const goos::Object& form, const goos::Object& rest
|
||||
}
|
||||
}
|
||||
|
||||
if (!inline_only) {
|
||||
if (!immediate) {
|
||||
// compile a function! First create a unique name...
|
||||
std::string function_name = lambda.debug_name;
|
||||
if (function_name.empty()) {
|
||||
@@ -330,7 +330,7 @@ Val* Compiler::compile_function_or_method_call(const goos::Object& form, Env* en
|
||||
// it's inlinable. However, we do not always inline an inlinable function by default
|
||||
if (kv->second.inline_by_default) { // inline when possible, so we should inline
|
||||
auto_inline = true;
|
||||
auto* lv = env->function_env()->alloc_val<LambdaVal>(kv->second.type);
|
||||
auto* lv = env->function_env()->alloc_val<LambdaVal>(kv->second.type, false);
|
||||
lv->lambda = kv->second.lambda;
|
||||
head = lv;
|
||||
}
|
||||
@@ -387,10 +387,16 @@ Val* Compiler::compile_function_or_method_call(const goos::Object& form, Env* en
|
||||
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 = env->function_env()->alloc_val<LambdaVal>(head_as_inlined_lambda->lv.type);
|
||||
head_as_lambda =
|
||||
env->function_env()->alloc_val<LambdaVal>(head_as_inlined_lambda->lv.type, false);
|
||||
head_as_lambda->lambda = head_as_inlined_lambda->lv.lambda;
|
||||
got_inlined_lambda = true;
|
||||
}
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -273,7 +273,7 @@ Val* Compiler::generate_inspector_for_structure_type(const goos::Object& form,
|
||||
|
||||
// add this function to the object file
|
||||
auto fe = env->function_env();
|
||||
auto method = fe->alloc_val<LambdaVal>(m_ts.make_typespec("function"));
|
||||
auto method = fe->alloc_val<LambdaVal>(m_ts.make_typespec("function"), false);
|
||||
method->func = method_env.get();
|
||||
auto obj_env_inspect = method_env->file_env();
|
||||
obj_env_inspect->add_function(std::move(method_env));
|
||||
@@ -344,7 +344,7 @@ Val* Compiler::generate_inspector_for_bitfield_type(const goos::Object& form,
|
||||
|
||||
// add this function to the object file
|
||||
auto fe = env->function_env();
|
||||
auto method = fe->alloc_val<LambdaVal>(m_ts.make_typespec("function"));
|
||||
auto method = fe->alloc_val<LambdaVal>(m_ts.make_typespec("function"), false);
|
||||
method->func = method_env.get();
|
||||
auto obj_env_inspect = method_env->file_env();
|
||||
obj_env_inspect->add_function(std::move(method_env));
|
||||
@@ -450,7 +450,7 @@ Val* Compiler::compile_defmethod(const goos::Object& form, const goos::Object& _
|
||||
throw_compiler_error(form, "Method type must be a symbol, got {}", method_name.print());
|
||||
}
|
||||
|
||||
auto place = fe->alloc_val<LambdaVal>(get_none()->type());
|
||||
auto place = fe->alloc_val<LambdaVal>(get_none()->type(), false);
|
||||
auto& lambda = place->lambda;
|
||||
auto lambda_ts = m_ts.make_typespec("function");
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
((lambda :inline-only #t (x y z) y) 1 2 3)
|
||||
((lambda :immediate #t (x y z) y) 1 2 3)
|
||||
Reference in New Issue
Block a user