Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
home:Ledest:erlang:24
erlang
2018-Remove-unused-cerl_-modules.patch
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File 2018-Remove-unused-cerl_-modules.patch of Package erlang
From c91eaa17e235ec2e9acbb922f2b3bacd792d488f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?= <bjorn@erlang.org> Date: Fri, 17 Sep 2021 08:39:39 +0200 Subject: [PATCH 18/20] Remove unused cerl_* modules --- lib/dialyzer/src/Makefile | 4 - lib/dialyzer/src/cerl_closurean.erl | 856 ------------------------ lib/dialyzer/src/cerl_lib.erl | 457 ------------- lib/dialyzer/src/cerl_pmatch.erl | 620 ----------------- lib/dialyzer/src/cerl_typean.erl | 994 ---------------------------- lib/dialyzer/src/dialyzer.app.src | 6 +- 6 files changed, 1 insertion(+), 2936 deletions(-) delete mode 100644 lib/dialyzer/src/cerl_closurean.erl delete mode 100644 lib/dialyzer/src/cerl_lib.erl delete mode 100644 lib/dialyzer/src/cerl_pmatch.erl delete mode 100644 lib/dialyzer/src/cerl_typean.erl diff --git a/lib/dialyzer/src/Makefile b/lib/dialyzer/src/Makefile index 0847da9d5a..5e69bd147e 100644 --- a/lib/dialyzer/src/Makefile +++ b/lib/dialyzer/src/Makefile @@ -47,11 +47,7 @@ DIALYZER_DIR = $(ERL_TOP)/lib/dialyzer # Target Specs # ---------------------------------------------------- MODULES = \ - cerl_closurean \ - cerl_lib \ - cerl_pmatch \ cerl_prettypr \ - cerl_typean \ dialyzer \ dialyzer_analysis_callgraph \ dialyzer_behaviours \ diff --git a/lib/dialyzer/src/cerl_closurean.erl b/lib/dialyzer/src/cerl_closurean.erl deleted file mode 100644 index e4718cb819..0000000000 --- a/lib/dialyzer/src/cerl_closurean.erl +++ /dev/null @@ -1,856 +0,0 @@ -%% 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 2001-2002 Richard Carlsson -%% @author Richard Carlsson <carlsson.richard@gmail.com> -%% @doc Closure analysis of Core Erlang programs. - -%% TODO: might need a "top" (`any') element for any-length value lists. - --module(cerl_closurean). - --export([analyze/1, annotate/1]). -%% The following functions are exported from this module since they -%% are also used by Dialyzer (file dialyzer/src/dialyzer_dep.erl) --export([is_escape_op/2, is_escape_op/3, is_literal_op/2, is_literal_op/3]). - --import(cerl, [ann_c_apply/3, ann_c_fun/3, ann_c_var/2, apply_args/1, - apply_op/1, atom_val/1, bitstr_size/1, bitstr_val/1, - binary_segments/1, c_letrec/2, c_seq/2, c_tuple/1, - c_nil/0, call_args/1, call_module/1, call_name/1, - case_arg/1, case_clauses/1, catch_body/1, clause_body/1, - clause_guard/1, clause_pats/1, cons_hd/1, cons_tl/1, - fun_body/1, fun_vars/1, get_ann/1, is_c_atom/1, - let_arg/1, let_body/1, let_vars/1, letrec_body/1, - letrec_defs/1, module_defs/1, module_defs/1, - module_exports/1, pat_vars/1, primop_args/1, - primop_name/1, receive_action/1, receive_clauses/1, - receive_timeout/1, seq_arg/1, seq_body/1, set_ann/2, - try_arg/1, try_body/1, try_vars/1, try_evars/1, - try_handler/1, tuple_es/1, type/1, values_es/1]). - --import(cerl_trees, [get_label/1]). - -%% =========================================================================== - --type label() :: integer() | 'top' | 'external' | 'external_call'. --type ordset(X) :: [X]. % XXX: TAKE ME OUT --type labelset() :: ordset(label()). --type outlist() :: [labelset()] | 'none'. --type escapes() :: labelset(). - -%% =========================================================================== -%% annotate(Tree) -> {Tree1, OutList, Outputs, Escapes, Dependencies, Parents} -%% -%% Tree = cerl:cerl() -%% -%% Analyzes `Tree' (see `analyze') and appends terms `{callers, -%% Labels}' and `{calls, Labels}' to the annotation list of each -%% fun-expression node and apply-expression node of `Tree', -%% respectively, where `Labels' is an ordered-set list of labels of -%% fun-expressions in `Tree', possibly also containing the atom -%% `external', corresponding to the dependency information derived -%% by the analysis. Any previous such annotations are removed from -%% `Tree'. `Tree1' is the modified tree; for details on `OutList', -%% `Outputs' , `Dependencies', `Escapes' and `Parents', see -%% `analyze'. -%% -%% Note: `Tree' must be annotated with labels in order to use this -%% function; see `analyze' for details. - --spec annotate(cerl:cerl()) -> - {cerl:cerl(), outlist(), dict:dict(), - escapes(), dict:dict(), dict:dict()}. - -annotate(Tree) -> - {Xs, Out, Esc, Deps, Par} = analyze(Tree), - F = fun (T) -> - case type(T) of - 'fun' -> - L = get_label(T), - X = case dict:find(L, Deps) of - {ok, X1} -> X1; - error -> set__new() - end, - set_ann(T, append_ann(callers, - set__to_list(X), - get_ann(T))); - apply -> - L = get_label(T), - X = case dict:find(L, Deps) of - {ok, X1} -> X1; - error -> set__new() - end, - set_ann(T, append_ann(calls, - set__to_list(X), - get_ann(T))); - _ -> -%%% set_ann(T, []) % debug - T - end - end, - {cerl_trees:map(F, Tree), Xs, Out, Esc, Deps, Par}. - -append_ann(Tag, Val, [X | Xs]) -> - if tuple_size(X) >= 1, element(1, X) =:= Tag -> - append_ann(Tag, Val, Xs); - true -> - [X | append_ann(Tag, Val, Xs)] - end; -append_ann(Tag, Val, []) -> - [{Tag, Val}]. - -%% ===================================================================== -%% analyze(Tree) -> {OutList, Outputs, Escapes, Dependencies, Parents} -%% -%% Tree = cerl() -%% OutList = [LabelSet] | none -%% Outputs = dict(Label, OutList) -%% Escapes = LabelSet -%% Dependencies = dict(Label, LabelSet) -%% LabelSet = ordset(Label) -%% Label = integer() | top | external | external_call -%% Parents = dict(Label, Label) -%% -%% Analyzes a module or an expression represented by `Tree'. -%% -%% The returned `OutList' is a list of sets of labels of -%% fun-expressions which correspond to the possible closures in the -%% value list produced by `Tree' (viewed as an expression; the -%% "value" of a module contains its exported functions). The atom -%% `none' denotes missing or conflicting information. -%% -%% The atom `external' in any label set denotes any possible -%% function outside `Tree', including those in `Escapes'. The atom -%% `top' denotes the top-level expression `Tree'. -%% -%% `Outputs' is a mapping from the labels of fun-expressions in -%% `Tree' to corresponding lists of sets of labels of -%% fun-expressions (or the atom `none'), representing the possible -%% closures in the value lists returned by the respective -%% functions. -%% -%% `Dependencies' is a similar mapping from the labels of -%% fun-expressions and apply-expressions in `Tree' to sets of -%% labels of corresponding fun-expressions which may contain call -%% sites of the functions or be called from the call sites, -%% respectively. Any such label not defined in `Dependencies' -%% represents an unreachable function or a dead or faulty -%% application. -%% -%% `Escapes' is the set of labels of fun-expressions in `Tree' such -%% that corresponding closures may be accessed from outside `Tree'. -%% -%% `Parents' is a mapping from labels of fun-expressions in `Tree' -%% to the corresponding label of the nearest containing -%% fun-expression or top-level expression. This can be used to -%% extend the dependency graph, for certain analyses. -%% -%% Note: `Tree' must be annotated with labels (as done by the -%% function `cerl_trees:label/1') in order to use this function. -%% The label annotation `{label, L}' (where L should be an integer) -%% must be the first element of the annotation list of each node in -%% the tree. Instances of variables bound in `Tree' which denote -%% the same variable must have the same label; apart from this, -%% labels should be unique. Constant literals do not need to be -%% labeled. - --record(state, {vars, out, dep, work, funs, par}). - -%% Note: In order to keep our domain simple, we assume that all remote -%% calls and primops return a single value, if any. - -%% We use the terms `closure', `label', `lambda' and `fun-expression' -%% interchangeably. The exact meaning in each case can be grasped from -%% the context. -%% -%% Rules: -%% 1) The implicit top level lambda escapes. -%% 2) A lambda returned by an escaped lambda also escapes. -%% 3) An escaped lambda can be passed an external lambda as argument. -%% 4) A lambda passed as argument to an external lambda also escapes. -%% 5) An argument passed to an unknown operation escapes. -%% 6) A call to an unknown operation can return an external lambda. -%% -%% Escaped lambdas become part of the set of external lambdas, but this -%% does not need to be represented explicitly. - -%% We wrap the given syntax tree T in a fun-expression labeled `top', -%% which is initially in the set of escaped labels. `top' will be -%% visited at least once. -%% -%% We create a separate function labeled `external', defined as: -%% "'external'/1 = fun (Escape) -> do apply 'external'/1(apply Escape()) -%% 'external'/1", which will represent any and all functions outside T, -%% and which returns itself, and contains a recursive call; this models -%% rules 2 and 4 above. It will be revisited if the set of escaped -%% labels changes, or at least once. Its parameter `Escape' is a -%% variable labeled `escape', which will hold the set of escaped labels. -%% initially it contains `top' and `external'. - --spec analyze(cerl:cerl()) -> - {outlist(), dict:dict(), escapes(), dict:dict(), dict:dict()}. - -analyze(Tree) -> - %% Note that we use different name spaces for variable labels and - %% function/call site labels, so we can reuse some names here. We - %% assume that the labeling of Tree only uses integers, not atoms. - External = ann_c_var([{label, external}], {external, 1}), - Escape = ann_c_var([{label, escape}], 'Escape'), - ExtBody = c_seq(ann_c_apply([{label, loop}], External, - [ann_c_apply([{label, external_call}], - Escape, [])]), - External), - ExtFun = ann_c_fun([{label, external}], [Escape], ExtBody), -%%% io:fwrite("external fun:\n~s.\n", -%%% [cerl_prettypr:format(ExtFun, [noann])]), - Top = ann_c_var([{label, top}], {top, 0}), - TopFun = ann_c_fun([{label, top}], [], Tree), - - %% The "start fun" just makes the initialisation easier. It will not - %% be marked as escaped, and thus cannot be called. - StartFun = ann_c_fun([{label, start}], [], - c_letrec([{External, ExtFun}, {Top, TopFun}], - c_nil())), -%%% io:fwrite("start fun:\n~s.\n", -%%% [cerl_prettypr:format(StartFun, [noann])]), - - %% Gather a database of all fun-expressions in Tree and initialise - %% all their outputs and parameter variables. Bind all module- and - %% letrec-defined variables to their corresponding labels. - Funs0 = dict:new(), - Vars0 = dict:new(), - Out0 = dict:new(), - Empty = empty(), - F = fun (T, S = {Fs, Vs, Os}) -> - case type(T) of - 'fun' -> - L = get_label(T), - As = fun_vars(T), - {dict:store(L, T, Fs), - bind_vars_single(As, Empty, Vs), - dict:store(L, none, Os)}; - letrec -> - {Fs, bind_defs(letrec_defs(T), Vs), Os}; - module -> - {Fs, bind_defs(module_defs(T), Vs), Os}; - _ -> - S - end - end, - {Funs, Vars, Out} = cerl_trees:fold(F, {Funs0, Vars0, Out0}, - StartFun), - - %% Initialise Escape to the minimal set of escaped labels. - Vars1 = dict:store(escape, from_label_list([top, external]), Vars), - - %% Enter the fixpoint iteration at the StartFun. - St = loop(StartFun, start, #state{vars = Vars1, - out = Out, - dep = dict:new(), - work = init_work(), - funs = Funs, - par = dict:new()}), -%%% io:fwrite("dependencies: ~p.\n", -%%% [[{X, set__to_list(Y)} -%%% || {X, Y} <- dict:to_list(St#state.dep)]]), - {dict:fetch(top, St#state.out), - tidy_dict([start, top, external], St#state.out), - dict:fetch(escape, St#state.vars), - tidy_dict([loop], St#state.dep), - St#state.par}. - -tidy_dict([X | Xs], D) -> - tidy_dict(Xs, dict:erase(X, D)); -tidy_dict([], D) -> - D. - -loop(T, L, St0) -> -%%% io:fwrite("analyzing: ~w.\n", [L]), -%%% io:fwrite("work: ~w.\n", [St0#state.work]), - Xs0 = dict:fetch(L, St0#state.out), - {Xs, St1} = visit(fun_body(T), L, St0), - {W, M} = case equal(Xs0, Xs) of - true -> - {St1#state.work, St1#state.out}; - false -> -%%% io:fwrite("out (~w) changed: ~w <- ~w.\n", -%%% [L, Xs, Xs0]), - M1 = dict:store(L, Xs, St1#state.out), - case dict:find(L, St1#state.dep) of - {ok, S} -> - {add_work(set__to_list(S), St1#state.work), - M1}; - error -> - {St1#state.work, M1} - end - end, - St2 = St1#state{out = M}, - case take_work(W) of - {ok, L1, W1} -> - T1 = dict:fetch(L1, St2#state.funs), - loop(T1, L1, St2#state{work = W1}); - none -> - St2 - end. - -visit(T, L, St) -> - case type(T) of - literal -> - {[empty()], St}; - var -> - %% If a variable is not already in the store here, we - %% initialize it to empty(). - L1 = get_label(T), - Vars = St#state.vars, - case dict:find(L1, Vars) of - {ok, X} -> - {[X], St}; - error -> - X = empty(), - St1 = St#state{vars = dict:store(L1, X, Vars)}, - {[X], St1} - end; - 'fun' -> - %% Must revisit the fun also, because its environment might - %% have changed. (We don't keep track of such dependencies.) - L1 = get_label(T), - St1 = St#state{work = add_work([L1], St#state.work), - par = set_parent([L1], L, St#state.par)}, - {[singleton(L1)], St1}; - values -> - visit_list(values_es(T), L, St); - cons -> - {Xs, St1} = visit_list([cons_hd(T), cons_tl(T)], L, St), - {[join_single_list(Xs)], St1}; - tuple -> - {Xs, St1} = visit_list(tuple_es(T), L, St), - {[join_single_list(Xs)], St1}; - 'let' -> - {Xs, St1} = visit(let_arg(T), L, St), - Vars = bind_vars(let_vars(T), Xs, St1#state.vars), - visit(let_body(T), L, St1#state{vars = Vars}); - seq -> - {_, St1} = visit(seq_arg(T), L, St), - visit(seq_body(T), L, St1); - apply -> - {Xs, St1} = visit(apply_op(T), L, St), - {As, St2} = visit_list(apply_args(T), L, St1), - case Xs of - [X] -> - %% We store the dependency from the call site to the - %% called functions - Ls = set__to_list(X), - Out = St2#state.out, - Xs1 = join_list([dict:fetch(Lx, Out) || Lx <- Ls]), - St3 = call_site(Ls, L, As, St2), - L1 = get_label(T), - D = dict:store(L1, X, St3#state.dep), - {Xs1, St3#state{dep = D}}; - none -> - {none, St2} - end; - call -> - M = call_module(T), - F = call_name(T), - {_, St1} = visit(M, L, St), - {_, St2} = visit(F, L, St1), - {Xs, St3} = visit_list(call_args(T), L, St2), - remote_call(M, F, Xs, St3); - primop -> - As = primop_args(T), - {Xs, St1} = visit_list(As, L, St), - primop_call(atom_val(primop_name(T)), length(Xs), Xs, St1); - 'case' -> - {Xs, St1} = visit(case_arg(T), L, St), - visit_clauses(Xs, case_clauses(T), L, St1); - 'receive' -> - X = singleton(external), - {Xs1, St1} = visit_clauses([X], receive_clauses(T), L, St), - {_, St2} = visit(receive_timeout(T), L, St1), - {Xs2, St3} = visit(receive_action(T), L, St2), - {join(Xs1, Xs2), St3}; - 'try' -> - {Xs1, St1} = visit(try_arg(T), L, St), - X = singleton(external), - Vars = bind_vars(try_vars(T), [X], St1#state.vars), - {Xs2, St2} = visit(try_body(T), L, St1#state{vars = Vars}), - Evars = bind_vars(try_evars(T), [X, X, X], St2#state.vars), - {Xs3, St3} = visit(try_handler(T), L, St2#state{vars = Evars}), - {join(join(Xs1, Xs2), Xs3), St3}; - 'catch' -> - {_, St1} = visit(catch_body(T), L, St), - {[singleton(external)], St1}; - binary -> - {_, St1} = visit_list(binary_segments(T), L, St), - {[empty()], St1}; - bitstr -> - %% The other fields are constant literals. - {_, St1} = visit(bitstr_val(T), L, St), - {_, St2} = visit(bitstr_size(T), L, St1), - {none, St2}; - letrec -> - %% All the bound funs should be revisited, because the - %% environment might have changed. - Ls = [get_label(F) || {_, F} <- letrec_defs(T)], - St1 = St#state{work = add_work(Ls, St#state.work), - par = set_parent(Ls, L, St#state.par)}, - visit(letrec_body(T), L, St1); - module -> - %% All the exported functions escape, and can thus be passed - %% any external closures as arguments. We regard a module as - %% a tuple of function variables in the body of a `letrec'. - visit(c_letrec(module_defs(T), c_tuple(module_exports(T))), - L, St) - end. - -visit_clause(T, Xs, L, St) -> - Vars = bind_pats(clause_pats(T), Xs, St#state.vars), - {_, St1} = visit(clause_guard(T), L, St#state{vars = Vars}), - visit(clause_body(T), L, St1). - -%% We assume correct value-list typing. - -visit_list([T | Ts], L, St) -> - {Xs, St1} = visit(T, L, St), - {Xs1, St2} = visit_list(Ts, L, St1), - X = case Xs of - [X1] -> X1; - none -> none - end, - {[X | Xs1], St2}; -visit_list([], _L, St) -> - {[], St}. - -visit_clauses(Xs, [T | Ts], L, St) -> - {Xs1, St1} = visit_clause(T, Xs, L, St), - {Xs2, St2} = visit_clauses(Xs, Ts, L, St1), - {join(Xs1, Xs2), St2}; -visit_clauses(_, [], _L, St) -> - {none, St}. - -bind_defs([{V, F} | Ds], Vars) -> - bind_defs(Ds, dict:store(get_label(V), singleton(get_label(F)), - Vars)); -bind_defs([], Vars) -> - Vars. - -bind_pats(Ps, none, Vars) -> - bind_pats_single(Ps, empty(), Vars); -bind_pats(Ps, Xs, Vars) -> - if length(Xs) =:= length(Ps) -> - bind_pats_list(Ps, Xs, Vars); - true -> - bind_pats_single(Ps, empty(), Vars) - end. - -bind_pats_list([P | Ps], [X | Xs], Vars) -> - bind_pats_list(Ps, Xs, bind_vars_single(pat_vars(P), X, Vars)); -bind_pats_list([], [], Vars) -> - Vars. - -bind_pats_single([P | Ps], X, Vars) -> - bind_pats_single(Ps, X, bind_vars_single(pat_vars(P), X, Vars)); -bind_pats_single([], _X, Vars) -> - Vars. - -bind_vars(Vs, none, Vars) -> - bind_vars_single(Vs, empty(), Vars); -bind_vars(Vs, Xs, Vars) -> - if length(Vs) =:= length(Xs) -> - bind_vars_list(Vs, Xs, Vars); - true -> - bind_vars_single(Vs, empty(), Vars) - end. - -bind_vars_list([V | Vs], [X | Xs], Vars) -> - bind_vars_list(Vs, Xs, dict:store(get_label(V), X, Vars)); -bind_vars_list([], [], Vars) -> - Vars. - -bind_vars_single([V | Vs], X, Vars) -> - bind_vars_single(Vs, X, dict:store(get_label(V), X, Vars)); -bind_vars_single([], _X, Vars) -> - Vars. - -%% This handles a call site - adding dependencies and updating parameter -%% variables with respect to the actual parameters. The 'external' -%% function is handled specially, since it can get an arbitrary number -%% of arguments, which must be unified into a single argument. - -call_site(Ls, L, Xs, St) -> -%%% io:fwrite("call site: ~w -> ~w (~w).\n", [L, Ls, Xs]), - {D, W, V} = call_site(Ls, L, Xs, St#state.dep, St#state.work, - St#state.vars, St#state.funs), - St#state{dep = D, work = W, vars = V}. - -call_site([external | Ls], T, Xs, D, W, V, Fs) -> - D1 = add_dep(external, T, D), - X = join_single_list(Xs), - case bind_arg(escape, X, V) of - {V1, true} -> -%%% io:fwrite("escape changed: ~w <- ~w + ~w.\n", -%%% [dict:fetch(escape, V1), dict:fetch(escape, V), -%%% X]), - {W1, V2} = update_esc(set__to_list(X), W, V1, Fs), - call_site(Ls, T, Xs, D1, add_work([external], W1), V2, Fs); - {V1, false} -> - call_site(Ls, T, Xs, D1, W, V1, Fs) - end; -call_site([L | Ls], T, Xs, D, W, V, Fs) -> - D1 = add_dep(L, T, D), - Vs = fun_vars(dict:fetch(L, Fs)), - case bind_args(Vs, Xs, V) of - {V1, true} -> - call_site(Ls, T, Xs, D1, add_work([L], W), V1, Fs); - {V1, false} -> - call_site(Ls, T, Xs, D1, W, V1, Fs) - end; -call_site([], _, _, D, W, V, _) -> - {D, W, V}. - -%% Note that `visit' makes sure all lambdas are visited at least once. -%% For every called function, we add a dependency from the *called* -%% function to the function containing the call site. - -add_dep(Source, Target, Deps) -> - case dict:find(Source, Deps) of - {ok, X} -> - case set__is_member(Target, X) of - true -> - Deps; - false -> -%%% io:fwrite("new dep: ~w <- ~w.\n", [Target, Source]), - dict:store(Source, set__add(Target, X), Deps) - end; - error -> -%%% io:fwrite("new dep: ~w <- ~w.\n", [Target, Source]), - dict:store(Source, set__singleton(Target), Deps) - end. - -%% If the arity does not match the call, nothing is done here. - -bind_args(Vs, Xs, Vars) -> - if length(Vs) =:= length(Xs) -> - bind_args(Vs, Xs, Vars, false); - true -> - {Vars, false} - end. - -bind_args([V | Vs], [X | Xs], Vars, Ch) -> - L = get_label(V), - {Vars1, Ch1} = bind_arg(L, X, Vars, Ch), - bind_args(Vs, Xs, Vars1, Ch1); -bind_args([], [], Vars, Ch) -> - {Vars, Ch}. - -bind_args_single(Vs, X, Vars) -> - bind_args_single(Vs, X, Vars, false). - -bind_args_single([V | Vs], X, Vars, Ch) -> - L = get_label(V), - {Vars1, Ch1} = bind_arg(L, X, Vars, Ch), - bind_args_single(Vs, X, Vars1, Ch1); -bind_args_single([], _, Vars, Ch) -> - {Vars, Ch}. - -bind_arg(L, X, Vars) -> - bind_arg(L, X, Vars, false). - -bind_arg(L, X, Vars, Ch) -> - X0 = dict:fetch(L, Vars), - X1 = join_single(X, X0), - case equal_single(X0, X1) of - true -> - {Vars, Ch}; - false -> -%%% io:fwrite("arg (~w) changed: ~w <- ~w + ~w.\n", -%%% [L, X1, X0, X]), - {dict:store(L, X1, Vars), true} - end. - -%% This handles escapes from things like primops and remote calls. - -%% escape(none, St) -> -%% St; -escape([X], St) -> - Vars = St#state.vars, - X0 = dict:fetch(escape, Vars), - X1 = join_single(X, X0), - case equal_single(X0, X1) of - true -> - St; - false -> -%%% io:fwrite("escape changed: ~w <- ~w + ~w.\n", [X1, X0, X]), -%%% io:fwrite("updating escaping funs: ~w.\n", [set__to_list(X)]), - Vars1 = dict:store(escape, X1, Vars), - {W, Vars2} = update_esc(set__to_list(set__subtract(X, X0)), - St#state.work, Vars1, - St#state.funs), - St#state{work = add_work([external], W), vars = Vars2} - end. - -%% For all escaping lambdas, since they might be called from outside the -%% program, all their arguments may be an external lambda. (Note that we -%% only have to include the `external' label once per escaping lambda.) -%% If the escape set has changed, we need to revisit the `external' fun. - -update_esc(Ls, W, V, Fs) -> - update_esc(Ls, singleton(external), W, V, Fs). - -%% The external lambda is skipped here - the Escape variable is known to -%% contain `external' from the start. - -update_esc([external | Ls], X, W, V, Fs) -> - update_esc(Ls, X, W, V, Fs); -update_esc([L | Ls], X, W, V, Fs) -> - Vs = fun_vars(dict:fetch(L, Fs)), - case bind_args_single(Vs, X, V) of - {V1, true} -> - update_esc(Ls, X, add_work([L], W), V1, Fs); - {V1, false} -> - update_esc(Ls, X, W, V1, Fs) - end; -update_esc([], _, W, V, _) -> - {W, V}. - -set_parent([L | Ls], L1, D) -> - set_parent(Ls, L1, dict:store(L, L1, D)); -set_parent([], _L1, D) -> - D. - -%% Handle primop calls: (At present, we assume that all unknown primops -%% yield exactly one value. This might have to be changed.) - -primop_call(F, A, Xs, St0) -> - case is_pure_op(F, A) of - %% XXX: this case is currently not possible -- commented out. - %% true -> - %% case is_literal_op(F, A) of - %% true -> {[empty()], St0}; - %% false -> {[join_single_list(Xs)], St0} - %% end; - false -> - St1 = case is_escape_op(F, A) of - true -> escape([join_single_list(Xs)], St0); - false -> St0 - end, - case is_literal_op(F, A) of - true -> {none, St1}; - false -> {[singleton(external)], St1} - end - end. - -%% Handle remote-calls: (At present, we assume that all unknown calls -%% yield exactly one value. This might have to be changed.) - -remote_call(M, F, Xs, St) -> - case is_c_atom(M) andalso is_c_atom(F) of - true -> - remote_call_1(atom_val(M), atom_val(F), length(Xs), Xs, St); - false -> - %% Unknown function - {[singleton(external)], escape([join_single_list(Xs)], St)} - end. - -remote_call_1(M, F, A, Xs, St0) -> - case is_pure_op(M, F, A) of - true -> - case is_literal_op(M, F, A) of - true -> {[empty()], St0}; - false -> {[join_single_list(Xs)], St0} - end; - false -> - St1 = case is_escape_op(M, F, A) of - true -> escape([join_single_list(Xs)], St0); - false -> St0 - end, - case is_literal_op(M, F, A) of - true -> {[empty()], St1}; - false -> {[singleton(external)], St1} - end - end. - -%% Domain: none | [Vs], where Vs = set(integer()). - -join(none, Xs2) -> Xs2; -join(Xs1, none) -> Xs1; -join(Xs1, Xs2) -> - if length(Xs1) =:= length(Xs2) -> - join_1(Xs1, Xs2); - true -> - none - end. - -join_1([X1 | Xs1], [X2 | Xs2]) -> - [join_single(X1, X2) | join_1(Xs1, Xs2)]; -join_1([], []) -> - []. - -empty() -> set__new(). - -singleton(X) -> set__singleton(X). - -from_label_list(X) -> set__from_list(X). - -join_single(none, Y) -> Y; -join_single(X, none) -> X; -join_single(X, Y) -> set__union(X, Y). - -join_list([Xs | Xss]) -> - join(Xs, join_list(Xss)); -join_list([]) -> - none. - -join_single_list([X | Xs]) -> - join_single(X, join_single_list(Xs)); -join_single_list([]) -> - empty(). - -equal(none, none) -> true; -equal(none, _) -> false; -equal(_, none) -> false; -equal(X1, X2) -> equal_1(X1, X2). - -equal_1([X1 | Xs1], [X2 | Xs2]) -> - equal_single(X1, X2) andalso equal_1(Xs1, Xs2); -equal_1([], []) -> true; -equal_1(_, _) -> false. - -equal_single(X, Y) -> set__equal(X, Y). - -%% Set abstraction for label sets in the domain. - -set__new() -> []. - -set__singleton(X) -> [X]. - -set__to_list(S) -> S. - -set__from_list(S) -> ordsets:from_list(S). - -set__union(X, Y) -> ordsets:union(X, Y). - -set__add(X, S) -> ordsets:add_element(X, S). - -set__is_member(X, S) -> ordsets:is_element(X, S). - -set__subtract(X, Y) -> ordsets:subtract(X, Y). - -set__equal(X, Y) -> X =:= Y. - -%% A simple but efficient functional queue. - -queue__new() -> {[], []}. - -queue__put(X, {In, Out}) -> {[X | In], Out}. - -queue__get({In, [X | Out]}) -> {ok, X, {In, Out}}; -queue__get({[], _}) -> empty; -queue__get({In, _}) -> - [X | In1] = lists:reverse(In), - {ok, X, {[], In1}}. - -%% The work list - a queue without repeated elements. - -init_work() -> - {queue__new(), sets:new([{version, 2}])}. - -add_work(Ls, {Q, Set}) -> - add_work(Ls, Q, Set). - -%% Note that the elements are enqueued in order. - -add_work([L | Ls], Q, Set) -> - case sets:is_element(L, Set) of - true -> - add_work(Ls, Q, Set); - false -> - add_work(Ls, queue__put(L, Q), sets:add_element(L, Set)) - end; -add_work([], Q, Set) -> - {Q, Set}. - -take_work({Queue0, Set0}) -> - case queue__get(Queue0) of - {ok, L, Queue1} -> - Set1 = sets:del_element(L, Set0), - {ok, L, {Queue1, Set1}}; - empty -> - none - end. - -%% Escape operators may let their arguments escape. Unless we know -%% otherwise, and the function is not pure, we assume this is the case. -%% Error-raising functions (fault/match_fail) are not considered as -%% escapes (but throw/exit are). Zero-argument functions need not be -%% listed. - --spec is_escape_op(atom(), arity()) -> boolean(). - -is_escape_op(match_fail, 1) -> false; -is_escape_op(recv_wait_timeout, 1) -> false; -is_escape_op(F, A) when is_atom(F), is_integer(A) -> true. - --spec is_escape_op(atom(), atom(), arity()) -> boolean(). - -is_escape_op(erlang, error, 1) -> false; -is_escape_op(erlang, error, 2) -> false; -is_escape_op(M, F, A) when is_atom(M), is_atom(F), is_integer(A) -> true. - -%% "Literal" operators will never return functional values even when -%% found in their arguments. Unless we know otherwise, we assume this is -%% not the case. (More functions can be added to this list, if needed -%% for better precision. Note that the result of `term_to_binary' still -%% contains an encoding of the closure.) - --spec is_literal_op(atom(), arity()) -> boolean(). - -is_literal_op(recv_wait_timeout, 1) -> true; -is_literal_op(match_fail, 1) -> true; -is_literal_op(F, A) when is_atom(F), is_integer(A) -> false. - --spec is_literal_op(atom(), atom(), arity()) -> boolean(). - -is_literal_op(erlang, '+', 2) -> true; -is_literal_op(erlang, '-', 2) -> true; -is_literal_op(erlang, '*', 2) -> true; -is_literal_op(erlang, '/', 2) -> true; -is_literal_op(erlang, '=:=', 2) -> true; -is_literal_op(erlang, '==', 2) -> true; -is_literal_op(erlang, '=/=', 2) -> true; -is_literal_op(erlang, '/=', 2) -> true; -is_literal_op(erlang, '<', 2) -> true; -is_literal_op(erlang, '=<', 2) -> true; -is_literal_op(erlang, '>', 2) -> true; -is_literal_op(erlang, '>=', 2) -> true; -is_literal_op(erlang, 'and', 2) -> true; -is_literal_op(erlang, 'or', 2) -> true; -is_literal_op(erlang, 'not', 1) -> true; -is_literal_op(erlang, length, 1) -> true; -is_literal_op(erlang, size, 1) -> true; -is_literal_op(erlang, fun_info, 1) -> true; -is_literal_op(erlang, fun_info, 2) -> true; -is_literal_op(erlang, fun_to_list, 1) -> true; -is_literal_op(erlang, throw, 1) -> true; -is_literal_op(erlang, exit, 1) -> true; -is_literal_op(erlang, error, 1) -> true; -is_literal_op(erlang, error, 2) -> true; -is_literal_op(M, F, A) when is_atom(M), is_atom(F), is_integer(A) -> false. - -%% Pure functions neither affect the state, nor depend on it. - -is_pure_op(F, A) when is_atom(F), is_integer(A) -> false. - -is_pure_op(M, F, A) -> erl_bifs:is_pure(M, F, A). - -%% ===================================================================== diff --git a/lib/dialyzer/src/cerl_lib.erl b/lib/dialyzer/src/cerl_lib.erl deleted file mode 100644 index 3a6fb1cf51..0000000000 --- a/lib/dialyzer/src/cerl_lib.erl +++ /dev/null @@ -1,457 +0,0 @@ -%% 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 <carlsson.richard@gmail.com> -%% @doc Utility functions for Core Erlang abstract syntax trees. -%% -%% <p>Syntax trees are defined in the module <a -%% href=""><code>cerl</code></a>.</p> -%% -%% @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 <E1> <E2>' is equivalent to `<E2>' if `<E1>' 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 <E1> do <E2> <E3>' to `do do - %% <E1> <E2> <E3>' 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 <E1> <E2> in Y' is equivalent to `do - %% <E1> let X = <E2> in Y'. Note that `<E2>' 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 <V1> = <E> in <V2>': - [V] = Vs, - N1 = cerl:var_name(V), - N2 = cerl:var_name(B), - if N1 =:= N2 -> - %% `let X = <E> in X' equals `<E>' - A; - true -> - %% `let X = <E> in Y' when X and Y - %% are different variables is - %% equivalent to `do <E> Y'. - reduce_expr(cerl:c_seq(A, B), Check) - end; - literal -> - %% `let X = <E> in T' when T is a literal - %% term is equivalent to `do <E> 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. diff --git a/lib/dialyzer/src/cerl_pmatch.erl b/lib/dialyzer/src/cerl_pmatch.erl deleted file mode 100644 index 66fce3c8eb..0000000000 --- a/lib/dialyzer/src/cerl_pmatch.erl +++ /dev/null @@ -1,620 +0,0 @@ -%% 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 2000-2006 Richard Carlsson -%% @author Richard Carlsson <carlsson.richard@gmail.com> -%% -%% @doc Core Erlang pattern matching compiler. -%% -%% <p>For reference, see Simon L. Peyton Jones "The Implementation of -%% Functional Programming Languages", chapter 5 (by Phil Wadler).</p> -%% -%% @type cerl() = cerl:cerl(). -%% Abstract Core Erlang syntax trees. -%% @type cerl_records() = cerl:cerl_records(). -%% An explicit record representation of Core Erlang syntax trees. - --module(cerl_pmatch). - -%%-define(NO_UNUSED, true). - --export([clauses/2]). --ifndef(NO_UNUSED). --export([transform/2, core_transform/2, expr/2]). --endif. - --import(lists, [all/2, splitwith/2, foldr/3, keysort/2, foldl/3, - mapfoldl/3]). - --define(binary_id, {binary}). --define(cons_id, {cons}). --define(tuple_id, {tuple}). --define(literal_id(V), V). - - -%% @spec core_transform(Module::cerl_records(), Options::[term()]) -> -%% cerl_records() -%% -%% @doc Transforms a module represented by records. See -%% <code>transform/2</code> for details. -%% -%% <p>Use the compiler option <code>{core_transform, cerl_pmatch}</code> -%% to insert this function as a compilation pass.</p> -%% -%% @see transform/2 - --ifndef(NO_UNUSED). --spec core_transform(cerl:c_module(), [_]) -> cerl:c_module(). - -core_transform(M, Opts) -> - cerl:to_records(transform(cerl:from_records(M), Opts)). --endif. % NO_UNUSED -%% @clear - - -%% @spec transform(Module::cerl(), Options::[term()]) -> cerl() -%% -%% @doc Rewrites all <code>case</code>-clauses in <code>Module</code>. -%% <code>receive</code>-clauses are not affected. Currently, no options -%% are available. -%% -%% @see clauses/2 -%% @see expr/2 -%% @see core_transform/2 - --ifndef(NO_UNUSED). --spec transform(cerl:cerl(), [_]) -> cerl:cerl(). - -transform(M, _Opts) -> - expr(M, env__empty()). --endif. % NO_UNUSED -%% @clear - - -%% @spec clauses(Clauses::[Clause], Env) -> {Expr, Vars} -%% Clause = cerl() -%% Expr = cerl() -%% Vars = [cerl()] -%% Env = rec_env:environment() -%% -%% @doc Rewrites a sequence of clauses to an equivalent expression, -%% removing as much repeated testing as possible. Returns a pair -%% <code>{Expr, Vars}</code>, where <code>Expr</code> is the resulting -%% expression, and <code>Vars</code> is a list of new variables (i.e., -%% not already in the given environment) to be bound to the arguments to -%% the switch. The following is a typical example (assuming -%% <code>E</code> is a Core Erlang case expression): -%% <pre> -%% handle_case(E, Env) -> -%% Cs = case_clauses(E), -%% {E1, Vs} = cerl_pmatch(Cs, Env), -%% c_let(Vs, case_arg(E), E1). -%% </pre> -%% -%% <p>The environment is used for generating new variables which do not -%% shadow existing bindings.</p> -%% -%% @see rec_env -%% @see expr/2 -%% @see transform/2 - --spec clauses([cerl:cerl(),...], rec_env:environment()) -> - {cerl:cerl(), [cerl:cerl()]}. - -clauses(Cs, Env) -> - clauses(Cs, none, Env). - -clauses([C | _] = Cs, Else, Env) -> - Vs = new_vars(cerl:clause_arity(C), Env), - E = match(Vs, Cs, Else, add_vars(Vs, Env)), - {E, Vs}. - -%% The implementation very closely follows that described in the book. - -match([], Cs, Else, _Env) -> - %% If the "default action" is the atom 'none', it is simply not - %% added; otherwise it is put in the body of a final catch-all - %% clause (which is often removed by the below optimization). - Cs1 = if Else =:= none -> Cs; - true -> Cs ++ [cerl:c_clause([], Else)] - end, - %% This clause reduction is an important optimization. It selects a - %% clause body if possible, and otherwise just removes dead clauses. - case cerl_clauses:reduce(Cs1) of - {true, {C, []}} -> % if we get bindings, something is wrong! - cerl:clause_body(C); - {false, Cs2} -> - %% This happens when guards are nontrivial. - cerl:c_case(cerl:c_values([]), Cs2) - end; -match([V | _] = Vs, Cs, Else, Env) -> - foldr(fun (CsF, ElseF) -> - match_var_con(Vs, CsF, ElseF, Env) - end, - Else, - group([unalias(C, V) || C <- Cs], fun is_var_clause/1)). - -group([], _F) -> - []; -group([X | _] = Xs, F) -> - group(Xs, F, F(X)). - -group(Xs, F, P) -> - {First, Rest} = splitwith(fun (X) -> F(X) =:= P end, Xs), - [First | group(Rest, F)]. - -is_var_clause(C) -> - cerl:is_c_var(hd(cerl:clause_pats(C))). - -%% To avoid code duplication, if the 'Else' expression is too big, we -%% put it in a local function definition instead, and replace it with a -%% call. (Note that it is important that 'is_lightweight' does not yield -%% 'true' for a simple function application, or we will create a lot of -%% unnecessary extra functions.) - -match_var_con(Vs, Cs, none = Else, Env) -> - match_var_con_1(Vs, Cs, Else, Env); -match_var_con(Vs, Cs, Else, Env) -> - case is_lightweight(Else) of - true -> - match_var_con_1(Vs, Cs, Else, Env); - false -> - F = new_fvar("match_", 0, Env), - Else1 = cerl:c_apply(F, []), - Env1 = add_vars([F], Env), - cerl:c_letrec([{F, cerl:c_fun([], Else)}], - match_var_con_1(Vs, Cs, Else1, Env1)) - end. - -match_var_con_1(Vs, Cs, Else, Env) -> - case is_var_clause(hd(Cs)) of - true -> - match_var(Vs, Cs, Else, Env); - false -> - match_con(Vs, Cs, Else, Env) - end. - -match_var([V | Vs], Cs, Else, Env) -> - Cs1 = [begin - [P | Ps] = cerl:clause_pats(C), - G = make_let([P], V, cerl:clause_guard(C)), - B = make_let([P], V, cerl:clause_body(C)), - cerl:update_c_clause(C, Ps, G, B) - end - || C <- Cs], - match(Vs, Cs1, Else, Env). - -%% Since Erlang is dynamically typed, we must include the possibility -%% that none of the constructors in the group will match, and in that -%% case the "Else" code will be executed (unless it is 'none'), in the -%% body of a final catch-all clause. - -match_con([V | Vs], Cs, Else, Env) -> - case group_con(Cs) of - [{_, _, Gs}] -> - %% Don't create a group type switch if there is only one - %% such group - make_switch(V, [match_congroup(DG, Vs, CsG, Else, Env) - || {DG, _, CsG} <- Gs], - Else, Env); - Ts -> - Cs1 = [match_typegroup(T, V, Vs, Gs, Else, Env) - || {T, _, Gs} <- Ts], - make_switch(V, Cs1, Else, Env) - end. - - -match_typegroup(_T, _V, Vs, [{D, _, Cs}], Else, Env) when element(1, D) /= ?binary_id -> - %% Don't create a group type switch if there is only one constructor - %% in the group. (Note that this always happens for '[]'.) - %% Special case for binaries which always get a group switch - match_congroup(D, Vs, Cs, Else, Env); -match_typegroup(T, V, Vs, Gs, Else, Env) -> - Body = make_switch(V, [match_congroup(D, Vs, Cs, Else, Env) - || {D, _, Cs} <- Gs], - Else, Env), - typetest_clause(T, V, Body, Env). - -match_congroup({?binary_id, Segs}, Vs, Cs, Else, Env) -> - Body = match(Vs, Cs, Else, Env), - cerl:c_clause([make_pat(?binary_id, Segs)], Body); - -match_congroup({D, A}, Vs, Cs, Else, Env) -> - Vs1 = new_vars(A, Env), - Body = match(Vs1 ++ Vs, Cs, Else, add_vars(Vs1, Env)), - cerl:c_clause([make_pat(D, Vs1)], Body). - -make_switch(V, Cs, Else, Env) -> - cerl:c_case(V, if Else =:= none -> Cs; - true -> Cs ++ [cerl:c_clause([new_var(Env)], - Else)] - end). - -%% We preserve the relative order of different-type constructors as they -%% were originally listed. This is done by tracking the clause numbers. - -group_con(Cs) -> - {Cs1, _} = mapfoldl(fun (C, N) -> - [P | Ps] = cerl:clause_pats(C), - Ps1 = sub_pats(P) ++ Ps, - G = cerl:clause_guard(C), - B = cerl:clause_body(C), - C1 = cerl:update_c_clause(C, Ps1, G, B), - D = con_desc(P), - {{D, N, C1}, N + 1} - end, - 0, Cs), - %% Sort and group constructors. - Css = group(keysort(1, Cs1), fun ({D,_,_}) -> D end), - %% Sort each group "back" by line number, and move the descriptor - %% and line number to the wrapper for the group. - Gs = [finalize_congroup(C) || C <- Css], - %% Group by type only (put e.g. different-arity tuples together). - Gss = group(Gs, fun ({D,_,_}) -> con_desc_type(D) end), - %% Sort and wrap the type groups. - Ts = [finalize_typegroup(G) || G <- Gss], - %% Sort type-groups by first clause order - keysort(2, Ts). - -finalize_congroup(Cs) -> - [{D,N,_}|_] = Cs1 = keysort(2, Cs), - {D, N, [C || {_,_,C} <- Cs1]}. - -finalize_typegroup(Gs) -> - [{D,N,_}|_] = Gs1 = keysort(2, Gs), - {con_desc_type(D), N, Gs1}. - -%% Since Erlang clause patterns can contain "alias patterns", we must -%% eliminate these, by turning them into let-definitions in the guards -%% and bodies of the clauses. - -unalias(C, V) -> - [P | Ps] = cerl:clause_pats(C), - B = cerl:clause_body(C), - G = cerl:clause_guard(C), - unalias(P, V, Ps, B, G, C). - -unalias(P, V, Ps, B, G, C) -> - case cerl:type(P) of - alias -> - V1 = cerl:alias_var(P), - B1 = make_let([V1], V, B), - G1 = make_let([V1], V, G), - unalias(cerl:alias_pat(P), V, Ps, B1, G1, C); - _ -> - cerl:update_c_clause(C, [P | Ps], G, B) - end. - -%% Generating a type-switch clause - -typetest_clause([], _V, E, _Env) -> - cerl:c_clause([cerl:c_nil()], E); -typetest_clause(atom, V, E, _Env) -> - typetest_clause_1(is_atom, V, E); -typetest_clause(integer, V, E, _Env) -> - typetest_clause_1(is_integer, V, E); -typetest_clause(float, V, E, _Env) -> - typetest_clause_1(is_float, V, E); -typetest_clause(cons, _V, E, Env) -> - [V1, V2] = new_vars(2, Env), - cerl:c_clause([cerl:c_cons(V1, V2)], E); % there is no 'is cons' -typetest_clause(tuple, V, E, _Env) -> - typetest_clause_1(is_tuple, V, E); -typetest_clause(binary, V, E, _Env) -> - typetest_clause_1(is_binary, V, E). - -typetest_clause_1(T, V, E) -> - cerl:c_clause([V], cerl:c_call(cerl:c_atom('erlang'), - cerl:c_atom(T), [V]), E). - -%% This returns a constructor descriptor, to be used for grouping and -%% pattern generation. It consists of an identifier term and the arity. - -con_desc(E) -> - case cerl:type(E) of - cons -> {?cons_id, 2}; - tuple -> {?tuple_id, cerl:tuple_arity(E)}; - binary -> {?binary_id, cerl:binary_segments(E)}; - literal -> - case cerl:concrete(E) of - [_|_] -> {?cons_id, 2}; - T when is_tuple(T) -> {?tuple_id, tuple_size(T)}; - V -> {?literal_id(V), 0} - end; - _ -> - throw({bad_constructor, E}) - end. - -%% This returns the type class for a constructor descriptor, for -%% grouping of clauses. It does not distinguish between tuples of -%% different arity, nor between different values of atoms, integers and -%% floats. - -con_desc_type({?literal_id([]), _}) -> []; -con_desc_type({?literal_id(V), _}) when is_atom(V) -> atom; -con_desc_type({?literal_id(V), _}) when is_integer(V) -> integer; -con_desc_type({?literal_id(V), _}) when is_float(V) -> float; -con_desc_type({?cons_id, 2}) -> cons; -con_desc_type({?tuple_id, _}) -> tuple; -con_desc_type({?binary_id, _}) -> binary. - -%% This creates a new constructor pattern from a type descriptor and a -%% list of variables. - -make_pat(?cons_id, [V1, V2]) -> cerl:c_cons(V1, V2); -make_pat(?tuple_id, Vs) -> cerl:c_tuple(Vs); -make_pat(?binary_id, Segs) -> cerl:c_binary(Segs); -make_pat(?literal_id(Val), []) -> cerl:abstract(Val). - -%% This returns the list of subpatterns of a constructor pattern. - -sub_pats(E) -> - case cerl:type(E) of - cons -> - [cerl:cons_hd(E), cerl:cons_tl(E)]; - tuple -> - cerl:tuple_es(E); - binary -> - []; - literal -> - case cerl:concrete(E) of - [H|T] -> [cerl:abstract(H), cerl:abstract(T)]; - T when is_tuple(T) -> [cerl:abstract(X) - || X <- tuple_to_list(T)]; - _ -> [] - end; - _ -> - throw({bad_constructor_pattern, E}) - end. - -%% This avoids generating stupid things like "let X = ... in 'true'", -%% and "let X = Y in X", keeping the generated code cleaner. It also -%% prevents expressions from being considered "non-lightweight" when -%% code duplication is disallowed (see is_lightweight for details). - -make_let(Vs, A, B) -> - cerl_lib:reduce_expr(cerl:c_let(Vs, A, B)). - -%% --------------------------------------------------------------------- -%% Rewriting a module or other expression: - -%% @spec expr(Expression::cerl(), Env) -> cerl() -%% Env = rec_env:environment() -%% -%% @doc Rewrites all <code>case</code>-clauses in -%% <code>Expression</code>. <code>receive</code>-clauses are not -%% affected. -%% -%% <p>The environment is used for generating new variables which do not -%% shadow existing bindings.</p> -%% -%% @see clauses/2 -%% @see rec_env - --ifndef(NO_UNUSED). --spec expr(cerl:cerl(), rec_env:environment()) -> cerl:cerl(). - -expr(E, Env) -> - case cerl:type(E) of - binary -> - Es = expr_list(cerl:binary_segments(E), Env), - cerl:update_c_binary(E, Es); - bitstr -> - V = expr(cerl:bitstr_val(E), Env), - Sz = expr(cerl:bitstr_size(E), Env), - Unit = expr(cerl:bitstr_unit(E), Env), - Type = expr(cerl:bitstr_type(E), Env), - cerl:update_c_bitstr(E, V, Sz, Unit, Type, cerl:bitstr_flags(E)); - literal -> - E; - var -> - E; - values -> - Es = expr_list(cerl:values_es(E), Env), - cerl:update_c_values(E, Es); - cons -> - H = expr(cerl:cons_hd(E), Env), - T = expr(cerl:cons_tl(E), Env), - cerl:update_c_cons(E, H, T); - tuple -> - Es = expr_list(cerl:tuple_es(E), Env), - cerl:update_c_tuple(E, Es); - 'let' -> - A = expr(cerl:let_arg(E), Env), - Vs = cerl:let_vars(E), - Env1 = add_vars(Vs, Env), - B = expr(cerl:let_body(E), Env1), - cerl:update_c_let(E, Vs, A, B); - seq -> - A = expr(cerl:seq_arg(E), Env), - B = expr(cerl:seq_body(E), Env), - cerl:update_c_seq(E, A, B); - apply -> - Op = expr(cerl:apply_op(E), Env), - As = expr_list(cerl:apply_args(E), Env), - cerl:update_c_apply(E, Op, As); - call -> - M = expr(cerl:call_module(E), Env), - N = expr(cerl:call_name(E), Env), - As = expr_list(cerl:call_args(E), Env), - cerl:update_c_call(E, M, N, As); - primop -> - As = expr_list(cerl:primop_args(E), Env), - cerl:update_c_primop(E, cerl:primop_name(E), As); - 'case' -> - A = expr(cerl:case_arg(E), Env), - Cs = expr_list(cerl:case_clauses(E), Env), - {E1, Vs} = clauses(Cs, Env), - make_let(Vs, A, E1); - clause -> - Vs = cerl:clause_vars(E), - Env1 = add_vars(Vs, Env), - G = expr(cerl:clause_guard(E), Env1), - B = expr(cerl:clause_body(E), Env1), - cerl:update_c_clause(E, cerl:clause_pats(E), G, B); - 'fun' -> - Vs = cerl:fun_vars(E), - Env1 = add_vars(Vs, Env), - B = expr(cerl:fun_body(E), Env1), - cerl:update_c_fun(E, Vs, B); - 'receive' -> - %% NOTE: No pattern matching compilation is done here! The - %% receive-clauses and patterns cannot be staged as long as - %% we are working with "normal" Core Erlang. - Cs = expr_list(cerl:receive_clauses(E), Env), - T = expr(cerl:receive_timeout(E), Env), - A = expr(cerl:receive_action(E), Env), - cerl:update_c_receive(E, Cs, T, A); - 'try' -> - A = expr(cerl:try_arg(E), Env), - Vs = cerl:try_vars(E), - B = expr(cerl:try_body(E), add_vars(Vs, Env)), - Evs = cerl:try_evars(E), - H = expr(cerl:try_handler(E), add_vars(Evs, Env)), - cerl:update_c_try(E, A, Vs, B, Evs, H); - 'catch' -> - B = expr(cerl:catch_body(E), Env), - cerl:update_c_catch(E, B); - letrec -> - Ds = cerl:letrec_defs(E), - Env1 = add_defs(Ds, Env), - Ds1 = defs(Ds, Env1), - B = expr(cerl:letrec_body(E), Env1), - cerl:update_c_letrec(E, Ds1, B); - module -> - Ds = cerl:module_defs(E), - Env1 = add_defs(Ds, Env), - Ds1 = defs(Ds, Env1), - cerl:update_c_module(E, cerl:module_name(E), - cerl:module_exports(E), - cerl:module_attrs(E), Ds1) - end. - -expr_list(Es, Env) -> - [expr(E, Env) || E <- Es]. - -defs(Ds, Env) -> - [{V, expr(F, Env)} || {V, F} <- Ds]. --endif. % NO_UNUSED -%% @clear - -%% --------------------------------------------------------------------- -%% Support functions - -new_var(Env) -> - Name = env__new_vname(Env), - cerl:c_var(Name). - -new_vars(N, Env) -> - [cerl:c_var(V) || V <- env__new_vnames(N, Env)]. - -new_fvar(A, N, Env) -> - Name = env__new_fname(A, N, Env), - cerl:c_var(Name). - -add_vars(Vs, Env) -> - foldl(fun (V, E) -> env__bind(cerl:var_name(V), [], E) end, Env, Vs). - --ifndef(NO_UNUSED). -add_defs(Ds, Env) -> - foldl(fun ({V, _F}, E) -> - env__bind(cerl:var_name(V), [], E) - end, Env, Ds). --endif. % NO_UNUSED - -%% This decides whether an expression is worth lifting out to a separate -%% function instead of duplicating the code. In other words, whether its -%% cost is about the same or smaller than that of a local function call. -%% Note that variables must always be "lightweight"; otherwise, they may -%% get lifted out of the case switch that introduces them. - -is_lightweight(E) -> - case get('cerl_pmatch_duplicate_code') of - never -> cerl:type(E) =:= var; % Avoids all code duplication - always -> true; % Does not lift code to new functions - _ -> is_lightweight_1(E) - end. - -is_lightweight_1(E) -> - case cerl:type(E) of - var -> true; - literal -> true; - 'fun' -> true; - values -> all(fun is_simple/1, cerl:values_es(E)); - cons -> is_simple(cerl:cons_hd(E)) - andalso is_simple(cerl:cons_tl(E)); - tuple -> all(fun is_simple/1, cerl:tuple_es(E)); - 'let' -> (is_simple(cerl:let_arg(E)) andalso - is_lightweight_1(cerl:let_body(E))); - seq -> (is_simple(cerl:seq_arg(E)) andalso - is_lightweight_1(cerl:seq_body(E))); - primop -> - all(fun is_simple/1, cerl:primop_args(E)); - apply -> - is_simple(cerl:apply_op(E)) - andalso all(fun is_simple/1, cerl:apply_args(E)); - call -> - is_simple(cerl:call_module(E)) - andalso is_simple(cerl:call_name(E)) - andalso all(fun is_simple/1, cerl:call_args(E)); - _ -> - %% The default is to lift the code to a new function. - false - end. - -%% "Simple" things have no (or negligible) runtime cost and are free -%% from side effects. - -is_simple(E) -> - case cerl:type(E) of - var -> true; - literal -> true; - values -> all(fun is_simple/1, cerl:values_es(E)); - _ -> false - end. - - -%% --------------------------------------------------------------------- -%% Abstract datatype: environment() - -env__bind(Key, Val, Env) -> - rec_env:bind(Key, Val, Env). - --ifndef(NO_UNUSED). -%% env__bind_recursive(Ks, Vs, F, Env) -> -%% rec_env:bind_recursive(Ks, Vs, F, Env). - -%% env__lookup(Key, Env) -> -%% rec_env:lookup(Key, Env). - -%% env__get(Key, Env) -> -%% rec_env:get(Key, Env). - -%% env__is_defined(Key, Env) -> -%% rec_env:is_defined(Key, Env). - -env__empty() -> - rec_env:empty(). --endif. % NO_UNUSED - -env__new_vname(Env) -> - rec_env:new_key(Env). - -env__new_vnames(N, Env) -> - rec_env:new_keys(N, Env). - -env__new_fname(F, A, Env) -> - rec_env:new_key(fun (X) -> - S = integer_to_list(X), - {list_to_atom(F ++ S), A} - end, - Env). diff --git a/lib/dialyzer/src/cerl_typean.erl b/lib/dialyzer/src/cerl_typean.erl deleted file mode 100644 index b0e5c10d7d..0000000000 --- a/lib/dialyzer/src/cerl_typean.erl +++ /dev/null @@ -1,994 +0,0 @@ -%% -*- erlang-indent-level: 4 -*- -%% -%% 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 2001-2002 Richard Carlsson -%% @author Richard Carlsson <carlsson.richard@gmail.com> -%% @doc Type analysis of Core Erlang programs. - -%% TODO: filters must handle conjunctions for better precision! -%% TODO: should get filters from patterns as well as guards. -%% TODO: unused functions are being included in the analysis. - --module(cerl_typean). - --export([core_transform/2, analyze/1, pp_hook/0]). -%%-export([analyze/2, analyze/5, annotate/1, annotate/2, annotate/5]). - --import(erl_types, [t_any/0, t_atom/0, t_atom_vals/1, t_binary/0, - t_cons/2, t_cons_hd/1, t_cons_tl/1, t_float/0, - t_fun/0, t_fun/2, t_from_range/2, t_from_term/1, - t_inf/2, t_integer/0, - t_is_any/1, t_is_atom/1, t_is_cons/1, t_is_list/1, - t_is_maybe_improper_list/1, t_is_none/1, t_is_tuple/1, - t_limit/2, t_list_elements/1, t_maybe_improper_list/0, - t_none/0, t_number/0, t_pid/0, t_port/0, t_product/1, - t_reference/0, t_sup/2, t_to_tlist/1, t_tuple/0, t_tuple/1, - t_tuple_args/1, t_tuple_size/1, t_tuple_subtypes/1]). - --import(cerl, [ann_c_fun/3, ann_c_var/2, alias_pat/1, alias_var/1, - apply_args/1, apply_op/1, atom_val/1, bitstr_size/1, - bitstr_val/1, bitstr_type/1, bitstr_flags/1, binary_segments/1, - c_letrec/2, c_nil/0, - c_values/1, call_args/1, call_module/1, call_name/1, - case_arg/1, case_clauses/1, catch_body/1, clause_body/1, - clause_guard/1, clause_pats/1, concrete/1, cons_hd/1, - cons_tl/1, fun_body/1, fun_vars/1, get_ann/1, int_val/1, - is_c_atom/1, is_c_int/1, let_arg/1, let_body/1, let_vars/1, - letrec_body/1, letrec_defs/1, module_defs/1, - module_defs/1, module_exports/1, pat_vars/1, - primop_args/1, primop_name/1, receive_action/1, - receive_clauses/1, receive_timeout/1, seq_arg/1, - seq_body/1, set_ann/2, try_arg/1, try_body/1, - try_evars/1, try_handler/1, try_vars/1, tuple_arity/1, - tuple_es/1, type/1, values_es/1, var_name/1]). - --import(cerl_trees, [get_label/1]). - --ifdef(DEBUG). --define(ANNOTATE(X), case erl_types:t_to_string(X) of Q when length(Q) < 255 -> list_to_atom(Q); Q -> Q end). --else. --define(ANNOTATE(X), X). --endif. - -%% Limit for type representation depth. --define(DEF_LIMIT, 3). - - -%% @spec core_transform(Module::cerl_records(), Options::[term()]) -> -%% cerl_records() -%% -%% @doc Annotates a module represented by records with type -%% information. See <code>annotate/1</code> for details. -%% -%% <p>Use the compiler option <code>{core_transform, cerl_typean}</code> -%% to insert this function as a compilation pass.</p> -%% -%% @see module/2 - --spec core_transform(cerl:cerl(), [term()]) -> cerl:cerl(). - -core_transform(Code, _Opts) -> - {Code1, _} = cerl_trees:label(cerl:from_records(Code)), - %% io:fwrite("Running type analysis..."), - %% {T1,_} = statistics(runtime), - {Code2, _, _} = annotate(Code1), - %% {T2,_} = statistics(runtime), - %% io:fwrite("(~w ms).\n", [T2 - T1]), - cerl:to_records(Code2). - - -%% ===================================================================== -%% annotate(Tree) -> {Tree1, Type, Vars} -%% -%% Tree = cerl:cerl() -%% -%% Analyzes `Tree' (see `analyze') and appends terms `{type, Type}' -%% to the annotation list of each fun-expression node and -%% apply-expression node of `Tree', respectively, where `Labels' is -%% an ordered-set list of labels of fun-expressions in `Tree', -%% possibly also containing the atom `external', corresponding to -%% the dependency information derived by the analysis. Any previous -%% such annotations are removed from `Tree'. `Tree1' is the -%% modified tree; for details on `OutList', `Outputs' , -%% `Dependencies' and `Escapes', see `analyze'. -%% -%% Note: `Tree' must be annotated with labels in order to use this -%% function; see `analyze' for details. - -annotate(Tree) -> - annotate(Tree, ?DEF_LIMIT). - -annotate(Tree, Limit) -> - {_, _, Esc, Dep, Par} = cerl_closurean:analyze(Tree), - annotate(Tree, Limit, Esc, Dep, Par). - -annotate(Tree, Limit, Esc, Dep, Par) -> - {Type, Out, Vars} = analyze(Tree, Limit, Esc, Dep, Par), - DelAnn = fun (T) -> set_ann(T, delete_ann(type, get_ann(T))) end, - SetType = fun (T, Dict) -> - case dict:find(get_label(T), Dict) of - {ok, X} -> - case t_is_any(X) of - true -> - DelAnn(T); - false -> - set_ann(T, append_ann(type, - ?ANNOTATE(X), - get_ann(T))) - end; - error -> - DelAnn(T) - end - end, - F = fun (T) -> - case type(T) of - var -> - SetType(T, Vars); - apply -> - SetType(T, Out); - call -> - SetType(T, Out); - primop -> - SetType(T, Out); - 'fun' -> - SetType(T, Out); - _ -> - DelAnn(T) - end - end, - {cerl_trees:map(F, Tree), Type, Vars}. - -append_ann(Tag, Val, [X | Xs]) -> - if tuple_size(X) >= 1, element(1, X) =:= Tag -> - append_ann(Tag, Val, Xs); - true -> - [X | append_ann(Tag, Val, Xs)] - end; -append_ann(Tag, Val, []) -> - [{Tag, Val}]. - -delete_ann(Tag, [X | Xs]) -> - if tuple_size(X) >= 1, element(1, X) =:= Tag -> - delete_ann(Tag, Xs); - true -> - [X | delete_ann(Tag, Xs)] - end; -delete_ann(_, []) -> - []. - - -%% ===================================================================== -%% analyze(Tree) -> {OutList, Outputs, Dependencies} -%% -%% Tree = cerl:cerl() -%% OutList = [LabelSet] | none -%% Outputs = dict(integer(), OutList) -%% Dependencies = dict(integer(), LabelSet) -%% LabelSet = ordset(Label) -%% Label = integer() | external -%% -%% Analyzes a module or an expression represented by `Tree'. -%% -%% The returned `OutList' is a list of sets of labels of -%% fun-expressions which correspond to the possible closures in the -%% value list produced by `Tree' (viewed as an expression; the -%% "value" of a module contains its exported functions). The atom -%% `none' denotes missing or conflicting information. -%% -%% The atom `external' in any label set denotes any possible -%% function outside `Tree', including those in `Escapes'. -%% -%% `Outputs' is a mapping from the labels of fun-expressions in -%% `Tree' to corresponding lists of sets of labels of -%% fun-expressions (or the atom `none'), representing the possible -%% closures in the value lists returned by the respective -%% functions. -%% -%% `Dependencies' is a similar mapping from the labels of -%% fun-expressions and apply-expressions in `Tree' to sets of -%% labels of corresponding fun-expressions which may contain call -%% sites of the functions or be called from the call sites, -%% respectively. Any such label not defined in `Dependencies' -%% represents an unreachable function or a dead or faulty -%% application. -%% -%% `Escapes' is the set of labels of fun-expressions in `Tree' such -%% that corresponding closures may be accessed from outside `Tree'. -%% -%% Note: `Tree' must be annotated with labels (as done by the -%% function `cerl_trees:label/1') in order to use this function. -%% The label annotation `{label, L}' (where L should be an integer) -%% must be the first element of the annotation list of each node in -%% the tree. Instances of variables bound in `Tree' which denote -%% the same variable must have the same label; apart from this, -%% labels should be unique. Constant literals do not need to be -%% labeled. - --record(state, {k, vars, out, dep, work, funs, envs}). - -%% Note: In order to keep our domain simple, we assume that all remote -%% calls and primops return a single value, if any. - -%% We wrap the given syntax tree T in a fun-expression labeled `top', -%% which is initially in the set of escaped labels. `top' will be -%% visited at least once. -%% -%% We create a separate function labeled `external', defined as: -%% "External = fun () -> Any", which will represent any and all -%% functions outside T, and whose return value has unknown type. - --type label() :: integer() | 'external' | 'top'. --type ordset(X) :: [X]. % XXX: TAKE ME OUT --type labelset() :: ordset(label()). --type outlist() :: [labelset()] | 'none'. - --spec analyze(cerl:cerl()) -> {outlist(), dict:dict(), dict:dict()}. - -analyze(Tree) -> - analyze(Tree, ?DEF_LIMIT). - -analyze(Tree, Limit) -> - {_, _, Esc, Dep, Par} = cerl_closurean:analyze(Tree), - analyze(Tree, Limit, Esc, Dep, Par). - -analyze(Tree, Limit, Esc0, Dep0, Par) -> - %% Note that we use different name spaces for variable labels and - %% function/call site labels. We assume that the labeling of Tree - %% only uses integers, not atoms. - LabelExtL = [{label, external}], - External = ann_c_var(LabelExtL, {external, 1}), - ExtFun = ann_c_fun(LabelExtL, [], ann_c_var([{label, any}], 'Any')), -%%% io:fwrite("external fun:\n~s.\n", -%%% [cerl_prettypr:format(ExtFun, [noann, {paper, 80}])]), - LabelTopL = [{label, top}], - Top = ann_c_var(LabelTopL, {top, 0}), - TopFun = ann_c_fun(LabelTopL, [], Tree), - - %% The "start fun" just makes the initialisation easier. It is not - %% itself in the call graph. - StartFun = ann_c_fun([{label, start}], [], - c_letrec([{External, ExtFun}, {Top, TopFun}], - c_nil())), -%%% io:fwrite("start fun:\n~s.\n", -%%% [cerl_prettypr:format(StartFun, [{paper, 80}])]), - - %% Gather a database of all fun-expressions in Tree and initialise - %% their outputs and parameter variables. All escaping functions can - %% receive any values as inputs. Also add an extra dependency edge - %% from each fun-expression label to its parent fun-expression. -%%% io:fwrite("Escape: ~p.\n",[Esc0]), - Esc = sets:from_list(Esc0, [{version, 2}]), - Any = t_any(), - None = t_none(), - Funs0 = dict:new(), - Vars0 = dict:store(any, Any, dict:new()), - Out0 = dict:store(top, None, - dict:store(external, None, dict:new())), - Envs0 = dict:store(top, dict:new(), - dict:store(external, dict:new(), dict:new())), - F = fun (T, S = {Fs, Vs, Os, Es}) -> - case type(T) of - 'fun' -> - L = get_label(T), - As = fun_vars(T), - X = case sets:is_element(L, Esc) of - true -> Any; - false -> None - end, - {dict:store(L, T, Fs), - bind_vars_single(As, X, Vs), - dict:store(L, None, Os), - dict:store(L, dict:new(), Es)}; - _ -> - S - end - end, - {Funs, Vars, Out, Envs} = cerl_trees:fold(F, {Funs0, Vars0, Out0, - Envs0}, StartFun), - - %% Add dependencies from funs to their parent funs. - Dep = lists:foldl(fun ({L, L1}, D) -> add_dep(L, L1, D) end, - Dep0, dict:to_list(Par)), - - %% Enter the fixpoint iteration at the StartFun. - St = loop(TopFun, top, #state{vars = Vars, - out = Out, - dep = Dep, - work = init_work(), - funs = Funs, - envs = Envs, - k = Limit}), - {dict:fetch(top, St#state.out), - tidy_dict([top, external], St#state.out), - tidy_dict([any], St#state.vars)}. - -tidy_dict([X | Xs], D) -> - tidy_dict(Xs, dict:erase(X, D)); -tidy_dict([], D) -> - D. - -loop(T, L, St0) -> -%%% io:fwrite("analyzing: ~w.\n",[L]), -%%% io:fwrite("work: ~w.\n", [Queue0]), - Env = dict:fetch(L, St0#state.envs), - X0 = dict:fetch(L, St0#state.out), - {X1, St1} = visit(fun_body(T), Env, St0), - X = limit(X1, St1#state.k), - {W, M} = case equal(X0, X) of - true -> - {St1#state.work, St1#state.out}; - false -> -%%% io:fwrite("out (~w) changed: ~s <- ~s.\n", -%%% [L, erl_types:t_to_string(X), -%%% erl_types:t_to_string(X0)]), - M1 = dict:store(L, X, St1#state.out), - case dict:find(L, St1#state.dep) of - {ok, S} -> -%%% io:fwrite("adding work: ~w.\n", [S]), - {add_work(S, St1#state.work), M1}; - error -> - {St1#state.work, M1} - end - end, - St2 = St1#state{out = M}, - case take_work(W) of - {ok, L1, W1} -> - T1 = dict:fetch(L1, St2#state.funs), - loop(T1, L1, St2#state{work = W1}); - none -> - St2 - end. - -visit(T, Env, St) -> - case type(T) of - literal -> - {t_from_term(concrete(T)), St}; - var -> - %% If a variable is not already in the store at this point, - %% we initialize it to 'none()'. - L = get_label(T), - Vars = St#state.vars, - case dict:find(L, Vars) of - {ok, X} -> - case dict:find(var_name(T), Env) of - {ok, X1} -> -%%% io:fwrite("filtered variable reference: ~w:~s.\n", -%%% [var_name(T), erl_types:t_to_string(X1)]), - {meet(X, X1), St}; - error -> - {X, St} - end; - error -> - X = t_none(), - Vars1 = dict:store(L, X, Vars), - St1 = St#state{vars = Vars1}, - {X, St1} - end; - 'fun' -> - %% Must revisit the fun also, because its environment might - %% have changed. (We don't keep track of such dependencies.) - L = get_label(T), - Xs = [dict:fetch(get_label(V), St#state.vars) - || V <- fun_vars(T)], - X = dict:fetch(L, St#state.out), - St1 = St#state{work = add_work([L], St#state.work), - envs = dict:store(L, Env, St#state.envs)}, - {t_fun(Xs, X), St1}; - values -> - {Xs, St1} = visit_list(values_es(T), Env, St), - {t_product(Xs), St1}; - cons -> - {[X1, X2], St1} = visit_list([cons_hd(T), cons_tl(T)], Env, St), - {t_cons(X1, X2), St1}; - tuple -> - {Xs, St1} = visit_list(tuple_es(T), Env, St), - {t_tuple(Xs), St1}; - 'let' -> - {X, St1} = visit(let_arg(T), Env, St), - LetVars = let_vars(T), - St1Vars = St1#state.vars, - Vars = case t_is_any(X) orelse t_is_none(X) of - true -> - bind_vars_single(LetVars, X, St1Vars); - false -> - bind_vars(LetVars, t_to_tlist(X), St1Vars) - end, - visit(let_body(T), Env, St1#state{vars = Vars}); - seq -> - {_, St1} = visit(seq_arg(T), Env, St), - visit(seq_body(T), Env, St1); - apply -> - {_F, St1} = visit(apply_op(T), Env, St), - {As, St2} = visit_list(apply_args(T), Env, St1), - L = get_label(T), - Ls = get_deps(L, St#state.dep), - Out = St2#state.out, - X = join_list([dict:fetch(L1, Out) || L1 <- Ls]), - Out1 = dict:store(L, X, Out), - {X, call_site(Ls, As, St2#state{out = Out1})}; - call -> - M = call_module(T), - F = call_name(T), - As = call_args(T), - {[X1, X2], St1} = visit_list([M, F], Env, St), - {Xs, St2} = visit_list(As, Env, St1), -%%% io:fwrite("call: ~w:~w(~w).\n",[X1,X2,Xs]), - X = case {t_atom_vals(X1), t_atom_vals(X2)} of - {[M1], [F1]} -> - A = length(As), -%%% io:fwrite("known call: ~w:~w/~w.\n", -%%% [M1, F1, A]), - call_type(M1, F1, A, Xs); - _ -> - t_any() - end, - L = get_label(T), - {X, St2#state{out = dict:store(L, X, St2#state.out)}}; - primop -> - As = primop_args(T), - {Xs, St1} = visit_list(As, Env, St), - F = atom_val(primop_name(T)), - A = length(As), - L = get_label(T), - X = primop_type(F, A, Xs), - {X, St1#state{out = dict:store(L, X, St1#state.out)}}; - 'case' -> - {X, St1} = visit(case_arg(T), Env, St), - Xs = case t_is_any(X) orelse t_is_none(X) of - true -> - [X || _ <- cerl:case_clauses(T)]; - false -> - t_to_tlist(X) - end, - join_visit_clauses(Xs, case_clauses(T), Env, St1); - 'receive' -> - Any = t_any(), - {X1, St1} = join_visit_clauses([Any], receive_clauses(T), - Env, St), - {X2, St2} = visit(receive_timeout(T), Env, St1), - case t_is_atom(X2) andalso (t_atom_vals(X2) =:= [infinity]) of - true -> - {X1, St2}; - false -> - {X3, St3} = visit(receive_action(T), Env, St2), - {join(X1, X3), St3} - end; - 'try' -> - {X, St1} = visit(try_arg(T), Env, St), - Any = t_any(), - Atom = t_atom(), - TryVars = try_vars(T), - St1Vars = St1#state.vars, - Vars = case t_is_any(X) orelse t_is_none(X) of - true -> - bind_vars_single(TryVars, X, St1Vars); - false -> - bind_vars(TryVars, t_to_tlist(X), St1Vars) - end, - {X1, St2} = visit(try_body(T), Env, St1#state{vars = Vars}), - EVars = bind_vars(try_evars(T), [Atom, Any, Any], St2#state.vars), - {X2, St3} = visit(try_handler(T), Env, St2#state{vars = EVars}), - {join(X1, X2), St3}; - 'catch' -> - {_, St1} = visit(catch_body(T), Env, St), - {t_any(), St1}; - binary -> - {_, St1} = visit_list(binary_segments(T), Env, St), - {t_binary(), St1}; - bitstr -> - %% The other fields are constant literals. - {_, St1} = visit(bitstr_val(T), Env, St), - {_, St2} = visit(bitstr_size(T), Env, St1), - {t_none(), St2}; - letrec -> - %% All the bound funs should be revisited, because the - %% environment might have changed. - Vars = bind_defs(letrec_defs(T), St#state.vars, - St#state.out), - Ls = [get_label(F) || {_, F} <- letrec_defs(T)], - St1 = St#state{work = add_work(Ls, St#state.work), - vars = Vars}, - visit(letrec_body(T), Env, St1); - module -> - %% We handle a module as a sequence of function variables in - %% the body of a `letrec'. - {_, St1} = visit(c_letrec(module_defs(T), - c_values(module_exports(T))), - Env, St), - {t_none(), St1} - end. - -visit_clause(T, Xs, Env, St) -> - Env1 = Env, - Vars = bind_pats(clause_pats(T), Xs, St#state.vars), - G = clause_guard(T), - {_, St1} = visit(G, Env1, St#state{vars = Vars}), - Env2 = guard_filters(G, Env1), - visit(clause_body(T), Env2, St1). - -%% We assume correct value-list typing. - -visit_list([T | Ts], Env, St) -> - {X, St1} = visit(T, Env, St), - {Xs, St2} = visit_list(Ts, Env, St1), - {[X | Xs], St2}; -visit_list([], _Env, St) -> - {[], St}. - -join_visit_clauses(Xs, [T | Ts], Env, St) -> - {X1, St1} = visit_clause(T, Xs, Env, St), - {X2, St2} = join_visit_clauses(Xs, Ts, Env, St1), - {join(X1, X2), St2}; -join_visit_clauses(_, [], _Env, St) -> - {t_none(), St}. - -bind_defs([{V, F} | Ds], Vars, Out) -> - Xs = [dict:fetch(get_label(V1), Vars) || V1 <- fun_vars(F)], - X = dict:fetch(get_label(F), Out), - bind_defs(Ds, dict:store(get_label(V), t_fun(Xs, X), Vars), Out); -bind_defs([], Vars, _Out) -> - Vars. - -bind_pats(Ps, Xs, Vars) -> - if length(Xs) =:= length(Ps) -> - bind_pats_list(Ps, Xs, Vars); - true -> - bind_pats_single(Ps, t_none(), Vars) - end. - -bind_pats_list([P | Ps], [X | Xs], Vars) -> - Vars1 = bind_pat_vars(P, X, Vars), - bind_pats_list(Ps, Xs, Vars1); -bind_pats_list([], [], Vars) -> - Vars. - -bind_pats_single([P | Ps], X, Vars) -> - bind_pats_single(Ps, X, bind_pat_vars(P, X, Vars)); -bind_pats_single([], _X, Vars) -> - Vars. - -bind_pat_vars(P, X, Vars) -> - case type(P) of - var -> - dict:store(get_label(P), X, Vars); - literal -> - Vars; - cons -> - case t_is_cons(X) of - true -> - %% If X is "nonempty proper list of X1", then the - %% head has type X1 and the tail has type "proper - %% list of X1". (If X is just "cons cell of X1", - %% then both head and tail have type X1.) - Vars1 = bind_pat_vars(cons_hd(P), t_cons_hd(X), - Vars), - bind_pat_vars(cons_tl(P), t_cons_tl(X), Vars1); - false -> - case t_is_list(X) of - true -> - %% If X is "proper list of X1", then the - %% head has type X1 and the tail has type - %% "proper list of X1", i.e., type X. - Vars1 = bind_pat_vars(cons_hd(P), - t_list_elements(X), - Vars), - bind_pat_vars(cons_tl(P), X, Vars1); - false -> - case t_is_maybe_improper_list(X) of - true -> - %% If X is "cons cell of X1", both - %% the head and tail have type X1. - X1 = t_list_elements(X), - Vars1 = bind_pat_vars(cons_hd(P), - X1, Vars), - bind_pat_vars(cons_tl(P), X1, - Vars1); - false -> - bind_vars_single(pat_vars(P), - top_or_bottom(X), - Vars) - end - end - end; - tuple -> - case t_is_tuple(X) of - true -> - case t_tuple_subtypes(X) of - unknown -> - bind_vars_single(pat_vars(P), top_or_bottom(X), - Vars); - [Tuple] -> - case t_tuple_size(Tuple) =:= tuple_arity(P) of - true -> - bind_pats_list(tuple_es(P), - t_tuple_args(Tuple), Vars); - - false -> - bind_vars_single(pat_vars(P), - top_or_bottom(X), Vars) - end; - List when is_list(List) -> - bind_vars_single(pat_vars(P), top_or_bottom(X), - Vars) - end; - false -> - bind_vars_single(pat_vars(P), top_or_bottom(X), Vars) - end; - binary -> - bind_pats_single(binary_segments(P), t_none(), Vars); - bitstr -> - %% Only the Value field is a new binding. Size is already - %% bound, and the other fields are constant literals. - %% We could create a filter for Size being an integer(). - Size = bitstr_size(P), - ValType = - case concrete(bitstr_type(P)) of - float -> t_float(); - binary -> t_binary(); - integer -> - case is_c_int(Size) of - false -> t_integer(); - true -> - SizeVal = int_val(Size), - Flags = concrete(bitstr_flags(P)), - case lists:member(signed, Flags) of - true -> - t_from_range(-(1 bsl (SizeVal - 1)), - 1 bsl (SizeVal - 1) - 1); - false -> - t_from_range(0,1 bsl SizeVal - 1) - end - end - end, - bind_pat_vars(bitstr_val(P), ValType, Vars); - alias -> - P1 = alias_pat(P), - Vars1 = bind_pat_vars(P1, X, Vars), - dict:store(get_label(alias_var(P)), pat_type(P1, Vars1), - Vars1) - end. - -pat_type(P, Vars) -> - case type(P) of - var -> - dict:fetch(get_label(P), Vars); - literal -> - t_from_term(concrete(P)); - cons -> - t_cons(pat_type(cons_hd(P), Vars), - pat_type(cons_tl(P), Vars)); - tuple -> - t_tuple([pat_type(E, Vars) || E <- tuple_es(P)]); - binary -> - t_binary(); - alias -> - pat_type(alias_pat(P), Vars) - end. - -bind_vars(Vs, Xs, Vars) -> - if length(Vs) =:= length(Xs) -> - bind_vars_list(Vs, Xs, Vars); - true -> - bind_vars_single(Vs, t_none(), Vars) - end. - -bind_vars_list([V | Vs], [X | Xs], Vars) -> - bind_vars_list(Vs, Xs, dict:store(get_label(V), X, Vars)); -bind_vars_list([], [], Vars) -> - Vars. - -bind_vars_single([V | Vs], X, Vars) -> - bind_vars_single(Vs, X, dict:store(get_label(V), X, Vars)); -bind_vars_single([], _X, Vars) -> - Vars. - -add_dep(Source, Target, Deps) -> - case dict:find(Source, Deps) of - {ok, X} -> - case set__is_member(Target, X) of - true -> - Deps; - false -> -%%% io:fwrite("new dep: ~w <- ~w.\n", [Target, Source]), - dict:store(Source, set__add(Target, X), Deps) - end; - error -> -%%% io:fwrite("new dep: ~w <- ~w.\n", [Target, Source]), - dict:store(Source, set__singleton(Target), Deps) - end. - -%% This handles a call site, updating parameter variables with respect -%% to the actual parameters. - -call_site(Ls, Xs, St) -> -%% io:fwrite("call site: ~w ~s.\n", -%% [Ls, erl_types:t_to_string(erl_types:t_product(Xs))]), - {W, V} = call_site(Ls, Xs, St#state.work, St#state.vars, - St#state.funs, St#state.k), - St#state{work = W, vars = V}. - -call_site([L | Ls], Xs, W, V, Fs, Limit) -> - Vs = fun_vars(dict:fetch(L, Fs)), - case bind_args(Vs, Xs, V, Limit) of - {V1, true} -> - call_site(Ls, Xs, add_work([L], W), V1, Fs, Limit); - {V1, false} -> - call_site(Ls, Xs, W, V1, Fs, Limit) - end; -call_site([], _, W, V, _, _) -> - {W, V}. - -%% If the arity does not match the call, nothing is done here. - -bind_args(Vs, Xs, Vars, Limit) -> - if length(Vs) =:= length(Xs) -> - bind_args(Vs, Xs, Vars, Limit, false); - true -> - {Vars, false} - end. - -bind_args([V | Vs], [X | Xs], Vars, Limit, Ch) -> - L = get_label(V), - {Vars1, Ch1} = bind_arg(L, X, Vars, Limit, Ch), - bind_args(Vs, Xs, Vars1, Limit, Ch1); -bind_args([], [], Vars, _Limit, Ch) -> - {Vars, Ch}. - -%% bind_arg(L, X, Vars, Limit) -> -%% bind_arg(L, X, Vars, Limit, false). - -bind_arg(L, X, Vars, Limit, Ch) -> - X0 = dict:fetch(L, Vars), - X1 = limit(join(X, X0), Limit), - case equal(X0, X1) of - true -> - {Vars, Ch}; - false -> -%%% io:fwrite("arg (~w) changed: ~s <- ~s + ~s.\n", -%%% [L, erl_types:t_to_string(X1), -%%% erl_types:t_to_string(X0), -%%% erl_types:t_to_string(X)]), - {dict:store(L, X1, Vars), true} - end. - -%% Domain: type(), defined in module `erl_types'. - -meet(X, Y) -> t_inf(X, Y). - -join(X, Y) -> t_sup(X, Y). - -join_list([Xs | Xss]) -> - join(Xs, join_list(Xss)); -join_list([]) -> - t_none(). - -equal(X, Y) -> X =:= Y. - -limit(X, K) -> t_limit(X, K). - -top_or_bottom(T) -> - case t_is_none(T) of - true -> - T; - false -> - t_any() - end. - -strict(Xs, T) -> - case erl_types:any_none(Xs) of - true -> - t_none(); - false -> - T - end. - -%% Set abstraction for label sets. - -%% set__new() -> []. - -set__singleton(X) -> [X]. - -%% set__to_list(S) -> S. - -%% set__from_list(S) -> ordsets:from_list(S). - -%% set__union(X, Y) -> ordsets:union(X, Y). - -set__add(X, S) -> ordsets:add_element(X, S). - -set__is_member(X, S) -> ordsets:is_element(X, S). - -%% set__subtract(X, Y) -> ordsets:subtract(X, Y). - -%% set__equal(X, Y) -> X =:= Y. - -%% A simple but efficient functional queue. - -queue__new() -> {[], []}. - -queue__put(X, {In, Out}) -> {[X | In], Out}. - -queue__get({In, [X | Out]}) -> {ok, X, {In, Out}}; -queue__get({[], _}) -> empty; -queue__get({In, _}) -> - [X | In1] = lists:reverse(In), - {ok, X, {[], In1}}. - -%% The work list - a queue without repeated elements. - -init_work() -> - {queue__put(external, queue__new()), sets:new([{version, 2}])}. - -add_work(Ls, {Q, Set}) -> - add_work(Ls, Q, Set). - -%% Note that the elements are enqueued in order. - -add_work([L | Ls], Q, Set) -> - case sets:is_element(L, Set) of - true -> - add_work(Ls, Q, Set); - false -> - add_work(Ls, queue__put(L, Q), sets:add_element(L, Set)) - end; -add_work([], Q, Set) -> - {Q, Set}. - -take_work({Queue0, Set0}) -> - case queue__get(Queue0) of - {ok, L, Queue1} -> - Set1 = sets:del_element(L, Set0), - {ok, L, {Queue1, Set1}}; - empty -> - none - end. - -get_deps(L, Dep) -> - case dict:find(L, Dep) of - {ok, Ls} -> Ls; - error -> [] - end. - -%% Type information for built-in functions. We do not check that the -%% arguments have the correct type; if the call would actually fail, -%% rather than return a value, this is a safe overapproximation. - -primop_type(match_fail, 1, _) -> t_none(); -primop_type(_, _, Xs) -> strict(Xs, t_any()). - -call_type(M, F, A, Xs) -> - erl_bif_types:type(M, F, A, Xs). - -guard_filters(T, Env) -> - guard_filters(T, Env, dict:new()). - -guard_filters(T, Env, Vars) -> - case type(T) of - call -> - M = call_module(T), - F = call_name(T), - case is_c_atom(M) andalso is_c_atom(F) of - true -> - As = call_args(T), - case {atom_val(M), atom_val(F), length(As)} of - {erlang, 'and', 2} -> - [A1, A2] = As, - guard_filters(A1, guard_filters(A2, Env)); - {erlang, is_atom, 1} -> - filter(As, t_atom(), Env); - {erlang, is_binary, 1} -> - filter(As, t_binary(), Env); - {erlang, is_float, 1} -> - filter(As, t_float(), Env); - {erlang, is_function, 1} -> - filter(As, t_fun(), Env); - {erlang, is_integer, 1} -> - filter(As, t_integer(), Env); - {erlang, is_list, 1} -> - filter(As, t_maybe_improper_list(), Env); - {erlang, is_number, 1} -> - filter(As, t_number(), Env); - {erlang, is_pid, 1} -> - filter(As, t_pid(), Env); - {erlang, is_port, 1} -> - filter(As, t_port(), Env); - {erlang, is_reference, 1} -> - filter(As, t_reference(), Env); - {erlang, is_tuple, 1} -> - filter(As, t_tuple(), Env); - _ -> - Env - end; - false -> - Env - end; - var -> - case dict:find(var_name(T), Vars) of - {ok, T1} -> - guard_filters(T1, Env, Vars); - error -> - Env - end; - 'let' -> - case let_vars(T) of - [V] -> - guard_filters(let_body(T), Env, - dict:store(var_name(V), let_arg(T), - Vars)); - _ -> - Env - end; - values -> - case values_es(T) of - [T1] -> - guard_filters(T1, Env, Vars); - _ -> - Env - end; - _ -> - Env - end. - -filter(As, X, Env) -> - [A] = As, - case type(A) of - var -> - V = var_name(A), - case dict:find(V, Env) of - {ok, X1} -> - dict:store(V, meet(X, X1), Env); - error -> - dict:store(V, X, Env) - end; - _ -> - Env - end. - -%% Callback hook for cerl_prettypr: - --spec pp_hook() -> fun((cerl:cerl(), _, fun((_,_) -> any())) -> any()). - -pp_hook() -> - fun pp_hook/3. - -pp_hook(Node, Ctxt, Cont) -> - As = cerl:get_ann(Node), - As1 = proplists:delete(type, proplists:delete(label, As)), - As2 = proplists:delete(typesig, proplists:delete(file, As1)), - D = Cont(cerl:set_ann(Node, []), Ctxt), - T = case proplists:lookup(type, As) of - {type, T0} -> T0; - none -> - case proplists:lookup(typesig, As) of - {typesig, T0} -> T0; - none -> t_any() - end - end, - D1 = case erl_types:t_is_any(T) of - true -> - D; - false -> - case cerl:is_literal(Node) of - true -> - D; - false -> - S = erl_types:t_to_string(T), - Q = prettypr:beside(prettypr:text("::"), - prettypr:text(S)), - prettypr:beside(D, Q) - end - end, - cerl_prettypr:annotate(D1, As2, Ctxt). - -%% ===================================================================== diff --git a/lib/dialyzer/src/dialyzer.app.src b/lib/dialyzer/src/dialyzer.app.src index 3090895190..93d004d2e7 100644 --- a/lib/dialyzer/src/dialyzer.app.src +++ b/lib/dialyzer/src/dialyzer.app.src @@ -22,11 +22,7 @@ {application, dialyzer, [{description, "DIscrepancy AnaLYZer of ERlang programs, version %VSN%"}, {vsn, "%VSN%"}, - {modules, [cerl_closurean, - cerl_lib, - cerl_pmatch, - cerl_prettypr, - cerl_typean, + {modules, [cerl_prettypr, dialyzer, dialyzer_analysis_callgraph, dialyzer_behaviours, -- 2.31.1
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor