[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:
water111
2023-04-30 19:00:27 -04:00
committed by GitHub
parent 34b4020569
commit ef23fecd90
5 changed files with 24 additions and 17 deletions
+4 -4
View File
@@ -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)))
)
)
+2 -1
View File
@@ -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;
};
+14 -8
View File
@@ -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;
}
}
}
+3 -3
View File
@@ -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)