%% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. %% You may obtain a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, software %% distributed under the License is distributed on an "AS IS" BASIS, %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %% See the License for the specific language governing permissions and %% limitations under the License. %% %% @copyright 1999-2002 Richard Carlsson %% @author Richard Carlsson %% @doc Utility functions for Core Erlang abstract syntax trees. %% %%

Syntax trees are defined in the module cerl.

%% %% @type cerl() = cerl:cerl() -module(cerl_lib). -define(NO_UNUSED, true). -export([is_safe_expr/2, reduce_expr/1, is_simple_clause/1, is_bool_switch/1, bool_switch_cases/1]). -ifndef(NO_UNUSED). -export([is_safe_expr/1, is_pure_expr/1, is_pure_expr/2, make_bool_switch/3]). -endif. %% Test if a clause has a single pattern and an always-true guard. -spec is_simple_clause(cerl:c_clause()) -> boolean(). is_simple_clause(C) -> case cerl:clause_pats(C) of [_P] -> G = cerl:clause_guard(C), case cerl_clauses:eval_guard(G) of {value, true} -> true; _ -> false end; _ -> false end. %% Creating an if-then-else construct that can be recognized as such. %% `Test' *must* be guaranteed to return a boolean. -ifndef(NO_UNUSED). make_bool_switch(Test, True, False) -> Cs = [cerl:c_clause([cerl:c_atom(true)], True), cerl:c_clause([cerl:c_atom(false)], False)], cerl:c_case(Test, Cs). -endif. %% A boolean switch cannot have a catch-all; only true/false branches. -spec is_bool_switch([cerl:c_clause()]) -> boolean(). is_bool_switch([C1, C2]) -> case is_simple_clause(C1) andalso is_simple_clause(C2) of true -> [P1] = cerl:clause_pats(C1), [P2] = cerl:clause_pats(C2), case cerl:is_c_atom(P1) andalso cerl:is_c_atom(P2) of true -> A1 = cerl:concrete(P1), A2 = cerl:concrete(P2), is_boolean(A1) andalso is_boolean(A2) andalso A1 =/= A2; false -> false end; false -> false end; is_bool_switch(_) -> false. %% Returns the true-body and the false-body for boolean switch clauses. -spec bool_switch_cases([cerl:c_clause()]) -> {cerl:cerl(), cerl:cerl()}. bool_switch_cases([C1, C2]) -> B1 = cerl:clause_body(C1), B2 = cerl:clause_body(C2), [P1] = cerl:clause_pats(C1), case cerl:concrete(P1) of true -> {B1, B2}; false -> {B2, B1} end. %% %% The type of the check functions like the default check below - XXX: refine %% -type check_fun() :: fun((_, _) -> boolean()). %% The default function property check always returns `false': default_check(_Property, _Function) -> false. %% @spec is_safe_expr(Expr::cerl()) -> boolean() %% %% @doc Returns `true' if `Expr' represents a "safe" Core Erlang %% expression, otherwise `false'. An expression is safe if it always %% completes normally and does not modify the state (although the return %% value may depend on the state). %% %% Expressions of type `apply', `case', `receive' and `binary' are %% always considered unsafe by this function. %% TODO: update cerl_inline to use these functions instead. -ifndef(NO_UNUSED). is_safe_expr(E) -> Check = fun default_check/2, is_safe_expr(E, Check). -endif. %% @clear -spec is_safe_expr(cerl:cerl(), check_fun()) -> boolean(). is_safe_expr(E, Check) -> case cerl:type(E) of literal -> true; var -> true; 'fun' -> true; values -> is_safe_expr_list(cerl:values_es(E), Check); tuple -> is_safe_expr_list(cerl:tuple_es(E), Check); cons -> case is_safe_expr(cerl:cons_hd(E), Check) of true -> is_safe_expr(cerl:cons_tl(E), Check); false -> false end; 'let' -> case is_safe_expr(cerl:let_arg(E), Check) of true -> is_safe_expr(cerl:let_body(E), Check); false -> false end; letrec -> is_safe_expr(cerl:letrec_body(E), Check); seq -> case is_safe_expr(cerl:seq_arg(E), Check) of true -> is_safe_expr(cerl:seq_body(E), Check); false -> false end; 'catch' -> is_safe_expr(cerl:catch_body(E), Check); 'try' -> %% If the guarded expression is safe, the try-handler will %% never be evaluated, so we need only check the body. If %% the guarded expression is pure, but could fail, we also %% have to check the handler. case is_safe_expr(cerl:try_arg(E), Check) of true -> is_safe_expr(cerl:try_body(E), Check); false -> case is_pure_expr(cerl:try_arg(E), Check) of true -> case is_safe_expr(cerl:try_body(E), Check) of true -> is_safe_expr(cerl:try_handler(E), Check); false -> false end; false -> false end end; primop -> Name = cerl:atom_val(cerl:primop_name(E)), As = cerl:primop_args(E), case Check(safe, {Name, length(As)}) of true -> is_safe_expr_list(As, Check); false -> false end; call -> Module = cerl:call_module(E), Name = cerl:call_name(E), case cerl:is_c_atom(Module) and cerl:is_c_atom(Name) of true -> M = cerl:atom_val(Module), F = cerl:atom_val(Name), As = cerl:call_args(E), case Check(safe, {M, F, length(As)}) of true -> is_safe_expr_list(As, Check); false -> false end; false -> false % Call to unknown function end; _ -> false end. is_safe_expr_list([E | Es], Check) -> case is_safe_expr(E, Check) of true -> is_safe_expr_list(Es, Check); false -> false end; is_safe_expr_list([], _Check) -> true. %% @spec (Expr::cerl()) -> bool() %% %% @doc Returns `true' if `Expr' represents a "pure" Core Erlang %% expression, otherwise `false'. An expression is pure if it does not %% affect the state, nor depend on the state, although its evaluation is %% not guaranteed to complete normally for all input. %% %% Expressions of type `apply', `case', `receive' and `binary' are %% always considered impure by this function. -ifndef(NO_UNUSED). is_pure_expr(E) -> Check = fun default_check/2, is_pure_expr(E, Check). -endif. %% @clear is_pure_expr(E, Check) -> case cerl:type(E) of literal -> true; var -> true; 'fun' -> true; values -> is_pure_expr_list(cerl:values_es(E), Check); tuple -> is_pure_expr_list(cerl:tuple_es(E), Check); cons -> case is_pure_expr(cerl:cons_hd(E), Check) of true -> is_pure_expr(cerl:cons_tl(E), Check); false -> false end; 'let' -> case is_pure_expr(cerl:let_arg(E), Check) of true -> is_pure_expr(cerl:let_body(E), Check); false -> false end; letrec -> is_pure_expr(cerl:letrec_body(E), Check); seq -> case is_pure_expr(cerl:seq_arg(E), Check) of true -> is_pure_expr(cerl:seq_body(E), Check); false -> false end; 'catch' -> is_pure_expr(cerl:catch_body(E), Check); 'try' -> case is_pure_expr(cerl:try_arg(E), Check) of true -> case is_pure_expr(cerl:try_body(E), Check) of true -> is_pure_expr(cerl:try_handler(E), Check); false -> false end; false -> false end; primop -> Name = cerl:atom_val(cerl:primop_name(E)), As = cerl:primop_args(E), case Check(pure, {Name, length(As)}) of true -> is_pure_expr_list(As, Check); false -> false end; call -> Module = cerl:call_module(E), Name = cerl:call_name(E), case cerl:is_c_atom(Module) and cerl:is_c_atom(Name) of true -> M = cerl:atom_val(Module), F = cerl:atom_val(Name), As = cerl:call_args(E), case Check(pure, {M, F, length(As)}) of true -> is_pure_expr_list(As, Check); false -> false end; false -> false % Call to unknown function end; _ -> false end. is_pure_expr_list([E | Es], Check) -> case is_pure_expr(E, Check) of true -> is_pure_expr_list(Es, Check); false -> false end; is_pure_expr_list([], _Check) -> true. %% Peephole optimizations %% %% This is only intended to be a light-weight cleanup optimizer, %% removing small things that may e.g. have been generated by other %% optimization passes or in the translation from higher-level code. %% It is not recursive in general - it only descends until it can do no %% more work in the current context. %% %% To expose hidden cases of final expressions (enabling last call %% optimization), we try to remove all trivial let-bindings (`let X = Y %% in X', `let X = Y in Y', `let X = Y in let ... in ...', `let X = let %% ... in ... in ...', etc.). We do not, however, try to recognize any %% other similar cases, even for simple `case'-expressions like `case E %% of X -> X end', or simultaneous multiple-value bindings. -spec reduce_expr(cerl:cerl()) -> cerl:cerl(). reduce_expr(E) -> Check = fun default_check/2, reduce_expr(E, Check). -spec reduce_expr(cerl:cerl(), check_fun()) -> cerl:cerl(). reduce_expr(E, Check) -> case cerl:type(E) of values -> case cerl:values_es(E) of [E1] -> %% Not really an "optimization" in itself, but %% enables other rewritings by removing the wrapper. reduce_expr(E1, Check); _ -> E end; 'seq' -> A = reduce_expr(cerl:seq_arg(E), Check), B = reduce_expr(cerl:seq_body(E), Check), %% `do ' is equivalent to `' if `' is %% "safe" (cannot effect the behaviour in any way). case is_safe_expr(A, Check) of true -> B; false -> case cerl:is_c_seq(B) of true -> %% Rewrite `do do ' to `do do %% ' so that the "body" of the %% outermost seq-operator is the expression %% which produces the final result (i.e., %% E3). This can make other optimizations %% easier; see `let'. B1 = cerl:seq_arg(B), B2 = cerl:seq_body(B), cerl:c_seq(cerl:c_seq(A, B1), B2); false -> cerl:c_seq(A, B) end end; 'let' -> A = reduce_expr(cerl:let_arg(E), Check), case cerl:is_c_seq(A) of true -> %% `let X = do in Y' is equivalent to `do %% let X = in Y'. Note that `' cannot %% be a seq-operator, due to the `seq' optimization. A1 = cerl:seq_arg(A), A2 = cerl:seq_body(A), E1 = cerl:update_c_let(E, cerl:let_vars(E), A2, cerl:let_body(E)), cerl:c_seq(A1, reduce_expr(E1, Check)); false -> B = reduce_expr(cerl:let_body(E), Check), Vs = cerl:let_vars(E), %% We give up if the body does not reduce to a %% single variable. This is not a generic copy %% propagation. case cerl:type(B) of var when length(Vs) =:= 1 -> %% We have `let = in ': [V] = Vs, N1 = cerl:var_name(V), N2 = cerl:var_name(B), if N1 =:= N2 -> %% `let X = in X' equals `' A; true -> %% `let X = in Y' when X and Y %% are different variables is %% equivalent to `do Y'. reduce_expr(cerl:c_seq(A, B), Check) end; literal -> %% `let X = in T' when T is a literal %% term is equivalent to `do T'. reduce_expr(cerl:c_seq(A, B), Check); _ -> cerl:update_c_let(E, Vs, A, B) end end; 'try' -> %% Get rid of unnecessary try-expressions. A = reduce_expr(cerl:try_arg(E), Check), B = reduce_expr(cerl:try_body(E), Check), case is_safe_expr(A, Check) of true -> B; false -> cerl:update_c_try(E, A, cerl:try_vars(E), B, cerl:try_evars(E), cerl:try_handler(E)) end; 'catch' -> %% Just a simpler form of try-expressions. B = reduce_expr(cerl:catch_body(E), Check), case is_safe_expr(B, Check) of true -> B; false -> cerl:update_c_catch(E, B) end; _ -> E end.