%-----------------------------------------------------------------------------% % vim: ft=mercury ts=4 sw=4 et %-----------------------------------------------------------------------------% % Copyright (C) 2000-2011 The University of Melbourne. % This file may only be copied under the terms of the GNU General % Public License - see the file COPYING in the Mercury distribution. %-----------------------------------------------------------------------------% % % File: ml_optimize.m. % Main author: trd, fjh. % % This module runs various optimizations on the MLDS. % % Currently the optimizations we do here are % - turning tailcalls into loops; % - converting assignments to local variables into variable initializers. % - eliminating initialized local variables entirely, % by replacing occurrences of such variables with their initializer % % Note that tailcall detection is done in ml_tailcall.m. % It might be nice to move the detection here, and do both the % loop transformation (in the case of self-tailcalls) and marking % tailcalls at the same time. % % Ultimately this module should just consist of a skeleton to traverse % the MLDS, and should call various optimization modules along the way. % % It would probably be a good idea to make each transformation optional. % Previously the tailcall transformation depended on emit_c_loops, but % this is a bit misleading given the documentation of emit_c_loops. % %-----------------------------------------------------------------------------% :- module ml_backend.ml_optimize. :- interface. :- import_module libs.globals. :- import_module ml_backend.mlds. :- pred mlds_optimize(globals::in, mlds::in, mlds::out) is det. %-----------------------------------------------------------------------------% %-----------------------------------------------------------------------------% :- implementation. :- import_module libs.options. :- import_module mdbcomp.prim_data. :- import_module ml_backend.ml_code_util. :- import_module ml_backend.ml_util. :- import_module bool. :- import_module int. :- import_module list. :- import_module maybe. :- import_module require. :- import_module std_util. :- import_module string. %-----------------------------------------------------------------------------% :- type opt_info ---> opt_info( oi_globals :: globals, oi_module_name :: mlds_module_name, oi_entity_name :: mlds_entity_name, oi_func_params :: mlds_func_params, oi_context :: mlds_context ). mlds_optimize(Globals, !MLDS) :- Defns0 = !.MLDS ^ mlds_defns, ModuleName = mercury_module_name_to_mlds(!.MLDS ^ mlds_name), optimize_in_defns(Globals, ModuleName, Defns0, Defns), !MLDS ^ mlds_defns := Defns. :- pred optimize_in_defns(globals::in, mlds_module_name::in, list(mlds_defn)::in, list(mlds_defn)::out) is det. optimize_in_defns(Globals, ModuleName, !Defns) :- list.map(optimize_in_defn(ModuleName, Globals), !Defns). :- pred optimize_in_defn(mlds_module_name::in, globals::in, mlds_defn::in, mlds_defn::out) is det. optimize_in_defn(ModuleName, Globals, Defn0, Defn) :- Defn0 = mlds_defn(Name, Context, Flags, DefnBody0), ( DefnBody0 = mlds_function(PredProcId, Params, FuncBody0, Attributes, EnvVarNames), OptInfo = opt_info(Globals, ModuleName, Name, Params, Context), optimize_func(OptInfo, FuncBody0, FuncBody1), optimize_in_function_body(OptInfo, FuncBody1, FuncBody), DefnBody = mlds_function(PredProcId, Params, FuncBody, Attributes, EnvVarNames), Defn = mlds_defn(Name, Context, Flags, DefnBody) ; DefnBody0 = mlds_data(_, _, _), Defn = Defn0 ; DefnBody0 = mlds_class(ClassDefn0), ClassDefn0 = mlds_class_defn(Kind, Imports, BaseClasses, Implements, TypeParams, CtorDefns0, MemberDefns0), optimize_in_defns(Globals, ModuleName, MemberDefns0, MemberDefns), optimize_in_defns(Globals, ModuleName, CtorDefns0, CtorDefns), ClassDefn = mlds_class_defn(Kind, Imports, BaseClasses, Implements, TypeParams, CtorDefns, MemberDefns), DefnBody = mlds_class(ClassDefn), Defn = mlds_defn(Name, Context, Flags, DefnBody) ). :- pred optimize_in_function_body(opt_info::in, mlds_function_body::in, mlds_function_body::out) is det. optimize_in_function_body(OptInfo, !Body) :- ( !.Body = body_external ; !.Body = body_defined_here(Statement0), optimize_in_statement(OptInfo, Statement0, Statement), !:Body = body_defined_here(Statement) ). :- pred optimize_in_maybe_statement(opt_info::in, maybe(statement)::in, maybe(statement)::out) is det. optimize_in_maybe_statement(OptInfo, !MaybeStatement) :- ( !.MaybeStatement = no ; !.MaybeStatement = yes(Statement0), optimize_in_statement(OptInfo, Statement0, Statement), !:MaybeStatement = yes(Statement) ). :- pred optimize_in_statements(opt_info::in, list(statement)::in, list(statement)::out) is det. optimize_in_statements(OptInfo, !Statements) :- list.map(optimize_in_statement(OptInfo), !Statements). :- pred optimize_in_statement(opt_info::in, statement::in, statement::out) is det. optimize_in_statement(!.OptInfo, !Statement) :- !.Statement = statement(Stmt0, Context), !OptInfo ^ oi_context := Context, optimize_in_stmt(!.OptInfo, Stmt0, Stmt), !:Statement = statement(Stmt, Context). :- pred optimize_in_stmt(opt_info::in, mlds_stmt::in, mlds_stmt::out) is det. optimize_in_stmt(OptInfo, Stmt0, Stmt) :- ( Stmt0 = ml_stmt_call(_, _, _, _, _, _), optimize_in_call_stmt(OptInfo, Stmt0, Stmt) ; Stmt0 = ml_stmt_block(Defns0, Statements0), maybe_convert_assignments_into_initializers(OptInfo, Defns0, Defns1, Statements0, Statements1), maybe_eliminate_locals(OptInfo, Defns1, Defns, Statements1, Statements2), maybe_flatten_block(Statements2, Statements3), optimize_in_statements(OptInfo, Statements3, Statements), Stmt = ml_stmt_block(Defns, Statements) ; Stmt0 = ml_stmt_while(Kind, Rval, Statement0), optimize_in_statement(OptInfo, Statement0, Statement), Stmt = ml_stmt_while(Kind, Rval, Statement) ; Stmt0 = ml_stmt_if_then_else(Rval, Then0, MaybeElse0), optimize_in_statement(OptInfo, Then0, Then), optimize_in_maybe_statement(OptInfo, MaybeElse0, MaybeElse), Stmt = ml_stmt_if_then_else(Rval, Then, MaybeElse) ; Stmt0 = ml_stmt_switch(Type, Rval, Range, Cases0, Default0), list.map(optimize_in_case(OptInfo), Cases0, Cases), optimize_in_default(OptInfo, Default0, Default), Stmt = ml_stmt_switch(Type, Rval, Range, Cases, Default) ; Stmt0 = ml_stmt_try_commit(Ref, TryGoal0, HandlerGoal0), optimize_in_statement(OptInfo, TryGoal0, TryGoal), optimize_in_statement(OptInfo, HandlerGoal0, HandlerGoal), Stmt = ml_stmt_try_commit(Ref, TryGoal, HandlerGoal) ; ( Stmt0 = ml_stmt_do_commit(_) ; Stmt0 = ml_stmt_return(_) ; Stmt0 = ml_stmt_label(_Label) ; Stmt0 = ml_stmt_goto(_Label) ; Stmt0 = ml_stmt_computed_goto(_Rval, _Label) ; Stmt0 = ml_stmt_atomic(_Atomic) ), Stmt = Stmt0 ). :- pred optimize_in_case(opt_info::in, mlds_switch_case::in, mlds_switch_case::out) is det. optimize_in_case(OptInfo, Case0, Case) :- Case0 = mlds_switch_case(FirstCond, LaterConds, Statement0), optimize_in_statement(OptInfo, Statement0, Statement), Case = mlds_switch_case(FirstCond, LaterConds, Statement). :- pred optimize_in_default(opt_info::in, mlds_switch_default::in, mlds_switch_default::out) is det. optimize_in_default(OptInfo, Default0, Default) :- ( Default0 = default_is_unreachable, Default = default_is_unreachable ; Default0 = default_do_nothing, Default = default_do_nothing ; Default0 = default_case(Statement0), optimize_in_statement(OptInfo, Statement0, Statement), Default = default_case(Statement) ). %-----------------------------------------------------------------------------% :- inst mlcall ---> ml_stmt_call(ground, ground, ground, ground, ground, ground). :- pred optimize_in_call_stmt(opt_info::in, mlds_stmt::in(mlcall), mlds_stmt::out) is det. optimize_in_call_stmt(OptInfo, Stmt0, Stmt) :- Stmt0 = ml_stmt_call(_Signature, FuncRval, _MaybeObject, CallArgs, _Results, _IsTailCall), % If we have a self-tailcall, assign to the arguments and % then goto the top of the tailcall loop. Globals = OptInfo ^ oi_globals, globals.lookup_bool_option(Globals, optimize_tailcalls, OptTailCalls), ( OptTailCalls = yes, ModuleName = OptInfo ^ oi_module_name, EntityName = OptInfo ^ oi_entity_name, can_optimize_tailcall(qual(ModuleName, module_qual, EntityName), Stmt0) -> Context = OptInfo ^ oi_context, CommentStatement = statement( ml_stmt_atomic(comment("direct tailcall eliminated")), Context), GotoStatement = statement(ml_stmt_goto(tailcall_loop_top(Globals)), Context), OptInfo ^ oi_func_params = mlds_func_params(FuncArgs, _RetTypes), generate_assign_args(OptInfo, FuncArgs, CallArgs, AssignStatements, AssignDefns), AssignVarsStatement = statement(ml_stmt_block(AssignDefns, AssignStatements), Context), CallReplaceStatements = [CommentStatement, AssignVarsStatement, GotoStatement], Stmt = ml_stmt_block([], CallReplaceStatements) ; % Convert calls to `mark_hp' and `restore_hp' to the corresponding % MLDS instructions. This ensures that they get generated as % inline code. (Without this they won't, since HLDS inlining doesn't % get run again after the add_heap_ops pass that adds these calls.) % This approach is better than running HLDS inlining again, % both because it cheaper in compilation time and because inlining % the C code doesn't help with the --target asm back-end, whereas % generating the appropriate MLDS instructions does. FuncRval = ml_const(mlconst_code_addr( code_addr_proc(qual(ModName, module_qual, ProcLabel), _FuncSignature))), ProcLabel = mlds_proc_label(PredLabel, _ProcId), PredLabel = mlds_user_pred_label(pf_predicate, _DefnModName, PredName, _Arity, _CodeModel, _NonOutputFunc), ( PredName = "mark_hp", CallArgs = [ml_mem_addr(Lval)], AtomicStmt = mark_hp(Lval) ; PredName = "restore_hp", CallArgs = [Rval], AtomicStmt = restore_hp(Rval) ), PrivateBuiltin = mercury_private_builtin_module, ModName = mercury_module_name_to_mlds(PrivateBuiltin) -> Stmt = ml_stmt_atomic(AtomicStmt) ; Stmt = Stmt0 ). % This specifies how we should branch to the top of the loop % introduced by tailcall opptimization. % :- func tailcall_loop_top(globals) = mlds_goto_target. tailcall_loop_top(Globals) = Target :- ( target_supports_break_and_continue(Globals) -> % The function body has been wrapped inside % `while (true) { ... break; }', and so to branch to the top of the % function, we just do a `continue' which will continue the next % iteration of the loop. Target = goto_continue ; % A label has been inserted at the start of the function, and so to % branch to the top of the function, we just branch to that label. Target = goto_label(tailcall_loop_label_name) ). % The label name we use for the top of the loop introduced by % tailcall optimization, when we're doing it with labels & gotos. % :- func tailcall_loop_label_name = string. tailcall_loop_label_name = "loop_top". %---------------------------------------------------------------------------- % Assign the specified list of rvals to the arguments. % This is used as part of tail recursion optimization (see above). % :- pred generate_assign_args(opt_info::in, mlds_arguments::in, list(mlds_rval)::in, list(statement)::out, list(mlds_defn)::out) is det. generate_assign_args(_, [], [], [], []). generate_assign_args(_, [_|_], [], [], []) :- unexpected(this_file, "generate_assign_args: length mismatch"). generate_assign_args(_, [], [_|_], [], []) :- unexpected(this_file, "generate_assign_args: length mismatch"). generate_assign_args(OptInfo, [Arg | Args], [ArgRval | ArgRvals], Statements, TempDefns) :- Arg = mlds_argument(Name, Type, _ArgGCStatement), ( % Extract the variable name. Name = entity_data(mlds_data_var(VarName)) -> ModuleName = OptInfo ^ oi_module_name, QualVarName = qual(ModuleName, module_qual, VarName), ( % Don't bother assigning a variable to itself. ArgRval = ml_lval(ml_var(QualVarName, _VarType)) -> generate_assign_args(OptInfo, Args, ArgRvals, Statements, TempDefns) ; % Declare a temporary variable, initialized it to the arg, % recursively process the remaining args, and then assign the % temporary to the parameter: % % SomeType argN__tmp_copy; % argN__tmp_copy = new_argN_value; % ... % argN = argN_tmp_copy; % % The temporaries are needed for the case where we are e.g. % assigning v1, v2 to v2, v1; they ensure that we don't try % to reference the old value of a parameter after it has already % been clobbered by the new value. % % Note that we have to use an assignment rather than an initializer % to initialize the temp, because this pass comes before % ml_elem_nested.m, and ml_elim_nested.m doesn't handle code % containing initializers. VarName = mlds_var_name(VarNameStr, MaybeNum), TempName = mlds_var_name(VarNameStr ++ "__tmp_copy", MaybeNum), QualTempName = qual(ModuleName, module_qual, TempName), Initializer = no_initializer, % We don't need to trace the temporary variables for GC, since they % are not live across a call or a heap allocation. GCStatement = gc_no_stmt, Context = OptInfo ^ oi_context, TempDefn = ml_gen_mlds_var_decl_init(mlds_data_var(TempName), Type, Initializer, GCStatement, Context), TempInitStatement = statement( ml_stmt_atomic(assign(ml_var(QualTempName, Type), ArgRval)), Context), AssignStatement = statement( ml_stmt_atomic(assign( ml_var(QualVarName, Type), ml_lval(ml_var(QualTempName, Type)))), Context), generate_assign_args(OptInfo, Args, ArgRvals, Statements0, TempDefns0), Statements = [TempInitStatement | Statements0] ++ [AssignStatement], TempDefns = [TempDefn | TempDefns0] ) ; unexpected(this_file, "generate_assign_args: function param is not a var") ). %---------------------------------------------------------------------------- :- pred optimize_func(opt_info::in, mlds_function_body::in, mlds_function_body::out) is det. optimize_func(OptInfo, Body0, Body) :- ( Body0 = body_external, Body = body_external ; Body0 = body_defined_here(Statement0), optimize_func_stmt(OptInfo, Statement0, Statement), Body = body_defined_here(Statement) ). :- pred optimize_func_stmt(opt_info::in, statement::in, statement::out) is det. optimize_func_stmt(OptInfo, Statement0, Statement) :- Statement0 = statement(Stmt0, Context), % Tailcall optimization -- if we do a self tailcall, we can turn it % into a loop. Globals = OptInfo ^ oi_globals, ( globals.lookup_bool_option(Globals, optimize_tailcalls, yes), stmt_contains_statement(Stmt0, Call), Call = statement(CallStmt, _), ModuleName = OptInfo ^ oi_module_name, EntityName = OptInfo ^ oi_entity_name, can_optimize_tailcall(qual(ModuleName, module_qual, EntityName), CallStmt) -> Comment = ml_stmt_atomic(comment("tailcall optimized into a loop")), CommentStmt = statement(Comment, Context), % The loop can be defined either using while, break, and continue, % or using a label and goto. We prefer to use the former, if possible, % since it is a higher-level construct that may help the back-end % compiler's optimizer. ( target_supports_break_and_continue(Globals) -> % Wrap a while loop around the function body: % while (true) { % /* tailcall optimized into a loop */ % % break; % } % Any tail calls in the function body will have % been replaced with `continue' statements. Stmt = ml_stmt_while(may_loop_zero_times, ml_const(mlconst_true), statement(ml_stmt_block([], [CommentStmt, statement(Stmt0, Context), statement(ml_stmt_goto(goto_break), Context)]), Context)) ; % Add a loop_top label at the start of the function % body: % { % loop_top: % /* tailcall optimized into a loop */ % % } % Any tail calls in the function body will have % been replaced with `goto loop_top' statements. Label = ml_stmt_label(tailcall_loop_label_name), Stmt = ml_stmt_block([], [CommentStmt, statement(Label, Context), statement(Stmt0, Context)]) ) ; Stmt = Stmt0 ), Statement = statement(Stmt, Context). :- pred target_supports_break_and_continue(globals::in) is semidet. target_supports_break_and_continue(Globals) :- globals.get_target(Globals, Target), target_supports_break_and_continue_2(Target) = yes. :- func target_supports_break_and_continue_2(compilation_target) = bool. target_supports_break_and_continue_2(target_c) = yes. target_supports_break_and_continue_2(target_asm) = no. % asm means via gnu back-end target_supports_break_and_continue_2(target_il) = no. target_supports_break_and_continue_2(target_csharp) = yes. target_supports_break_and_continue_2(target_java) = yes. % target_supports_break_and_continue_2(target_c_sharp) = yes. target_supports_break_and_continue_2(target_x86_64) = _ :- unexpected(this_file, "target x86_64 with --high-level-code"). target_supports_break_and_continue_2(target_erlang) = _ :- unexpected(this_file, "target erlang"). %-----------------------------------------------------------------------------% % If the list of statements contains a block with no local variables, % then bring the block up one level. This optimization is needed to avoid % a compiler limit in the Microsoft C compiler (version 13.10.3077) for % too deeply nested blocks. % :- pred maybe_flatten_block(list(statement)::in, list(statement)::out) is det. maybe_flatten_block(!Stmts) :- !:Stmts = list.condense(list.map(flatten_block, !.Stmts)). :- func flatten_block(statement) = list(statement). flatten_block(Statement) = Statements :- ( Statement = statement(ml_stmt_block([], BlockStatements), _) -> Statements = BlockStatements ; Statements = [Statement] ). %-----------------------------------------------------------------------------% % % This code implements the --optimize-initializations option. % It converts MLDS code using assignments, e.g. % % { % int v1; // or any other type -- it doesn't have to be int % int v2; % int v3; % int v4; % int v5; % % v1 = 1; % v2 = 2; % v3 = 3; % foo(); % v4 = 4; % ... % } % % into code that instead uses initializers, e.g. % % { % int v1 = 1; % int v2 = 2; % int v3 = 3; % int v4; % % foo(); % v4 = 4; % ... % } % % Note that if there are multiple initializations of the same variable, % then we'll apply the optimization successively, replacing the existing % initializers as we go, and keeping only the last, e.g. % % int v = 1; % v = 2; % v = 3; % ... % % will get replaced with % % int v = 3; % ... % % We need to watch out for some tricky cases that can't be safely optimized. % If the RHS of the assignment refers to a variable which was declared after % the variable whose initialization we're optimizing, e.g. % % int v1 = 1; % int v2 = 0; % v1 = v2 + 1; // RHS refers to variable declared after v1 % % then we can't do the optimization because it would cause a forward reference % % int v1 = v2 + 1; // error -- v2 not declared yet! % int v2 = 0; % % Likewise if the RHS refers to the variable itself % % int v1 = 1; % v1 = v1 + 1; % % then we can't optimize it, because that would be bogus: % % int v1 = v1 + 1; // error -- v1 not initialized yet! % % Similarly, if the initializers of the variables that follow % the one we're trying to optimize refer to it, e.g. % % int v1 = 1; % int v2 = v1 + 1; // here v2 == 2 % v1 = 0; % ... % % then we can't eliminate the assignment, because that would produce % different results: % % int v1 = 0; % int v2 = v1 + 1; // wrong -- v2 == 1 % ... :- pred maybe_convert_assignments_into_initializers(opt_info::in, list(mlds_defn)::in, list(mlds_defn)::out, list(statement)::in, list(statement)::out) is det. maybe_convert_assignments_into_initializers(OptInfo, !Defns, !Statements) :- Globals = OptInfo ^ oi_globals, % Check if --optimize-initializations is enabled. ( globals.lookup_bool_option(Globals, optimize_initializations, yes) -> convert_assignments_into_initializers(OptInfo, !Defns, !Statements) ; true ). :- pred convert_assignments_into_initializers(opt_info::in, list(mlds_defn)::in, list(mlds_defn)::out, list(statement)::in, list(statement)::out) is det. convert_assignments_into_initializers(OptInfo, !Defns, !Statements) :- ( % Check if the first statement in the block is an assignment to one % of the variables declared in the block. !.Statements = [AssignStatement | !:Statements], AssignStatement = statement(ml_stmt_atomic(assign(LHS, RHS)), _), LHS = ml_var(ThisVar, _ThisType), ThisVar = qual(Qualifier, QualKind, VarName), ThisData = qual(Qualifier, QualKind, mlds_data_var(VarName)), % We must check that the value being assigned doesn't refer to the % variable itself. rval_contains_var(RHS, ThisData) = no, % We must check that the value being assign doesn't refer to any % of the variables which are declared after this one. We must also % check that the initializers (if any) of the variables that follow % this one don't refer to this variable. Qualifier = OptInfo ^ oi_module_name, list.takewhile(isnt(var_defn(VarName)), !.Defns, _PrecedingDefns, [_VarDefn | FollowingDefns]), Filter = (pred(OtherDefn::in) is semidet :- OtherDefn = mlds_defn(entity_data(OtherVarName), _, _, mlds_data(_Type, OtherInitializer, _GC)), ( QualOtherVar = qual(Qualifier, QualKind, OtherVarName), rval_contains_var(RHS, QualOtherVar) = yes ; initializer_contains_var(OtherInitializer, ThisData) = yes ) ), \+ list.find_first_match(Filter, FollowingDefns, _) -> % Replace the assignment statement with an initializer % on the variable declaration. set_initializer(VarName, RHS, !Defns), % Now try to apply the same optimization again. convert_assignments_into_initializers(OptInfo, !Defns, !Statements) ; % No optimization possible -- leave the block unchanged. true ). :- pred var_defn(mlds_var_name::in, mlds_defn::in) is semidet. var_defn(VarName, Defn) :- Defn = mlds_defn(entity_data(mlds_data_var(VarName)), _, _, _). % set_initializer(VarName, Rval, Defns0, Defns): % % Finds the first definition of the specified variable in Defns0, % and replaces the initializer of that definition with init_obj(Rval). % :- pred set_initializer(mlds_var_name::in, mlds_rval::in, list(mlds_defn)::in, list(mlds_defn)::out) is det. set_initializer(_, _, [], _) :- unexpected(this_file, "set_initializer: var not found!"). set_initializer(VarName, Rval, [Defn0 | Defns0], [Defn | Defns]) :- Defn0 = mlds_defn(Name, Context, Flags, DefnBody0), ( Name = entity_data(mlds_data_var(VarName)), DefnBody0 = mlds_data(Type, _OldInitializer, GCStatement) -> DefnBody = mlds_data(Type, init_obj(Rval), GCStatement), Defn = mlds_defn(Name, Context, Flags, DefnBody), Defns = Defns0 ; Defn = Defn0, set_initializer(VarName, Rval, Defns0, Defns) ). %-----------------------------------------------------------------------------% % % This is a pass to eliminate initialized local variable definitions, % by substituting the value of the initializer for occurrences of the variable. % % XXX This is quadratic in the number of variable definitions, since we do % one pass over the block per variable definition. A more efficient algorithm % would be to do one pass to figure out which variables could be eliminated, % and then do another pass to actually eliminate them. :- pred maybe_eliminate_locals(opt_info::in, list(mlds_defn)::in, list(mlds_defn)::out, list(statement)::in, list(statement)::out) is det. maybe_eliminate_locals(OptInfo, !Defns, !Statements) :- globals.lookup_bool_option(OptInfo ^ oi_globals, eliminate_local_vars, EliminateLocalVars), ( EliminateLocalVars = yes, eliminate_locals(OptInfo, !Defns, !Statements) ; EliminateLocalVars = no ). :- pred eliminate_locals(opt_info::in, list(mlds_defn)::in, list(mlds_defn)::out, list(statement)::in, list(statement)::out) is det. eliminate_locals(_OptInfo, [], [], !Statements). eliminate_locals(OptInfo, [Defn0 | Defns0], Defns, !Statements) :- ( try_to_eliminate_defn(OptInfo, Defn0, Defns0, Defns1, !Statements) -> eliminate_locals(OptInfo, Defns1, Defns, !Statements) ; eliminate_locals(OptInfo, Defns0, Defns2, !Statements), Defns = [Defn0 | Defns2] ). % This data structure holds information that we use % in this pass to eliminate initialized local variable definitions. :- type var_elim_info ---> var_elim_info( % These fields remain constant. var_name :: mlds_var, % The name of the variable to eliminate. var_value :: mlds_rval, % The value to replace the eliminated variable % with. % These get updated as we go along. replace_count :: int, % The number of occurrences of the variable. invalidated :: bool % `yes' if the optimization can't be applied, % e.g. because the variable was assigned to, % or because its address was taken. ). % Check if this definition is a variable that we can eliminate. % If so, replace uses of this variable with the variable's value. % This will fail if the definition is not a variable definition, % or if any of the statements or definitions take the address % of the variable, or assign to it. % :- pred try_to_eliminate_defn(opt_info::in, mlds_defn::in, list(mlds_defn)::in, list(mlds_defn)::out, list(statement)::in, list(statement)::out) is semidet. try_to_eliminate_defn(OptInfo, Defn0, Defns0, Defns, !Statements) :- Defn0 = mlds_defn(Name, _Context, Flags, DefnBody), % Check if this definition is a local variable definition... Name = entity_data(mlds_data_var(VarName)), Flags = ml_gen_local_var_decl_flags, DefnBody = mlds_data(_Type, Initializer, _GCStatement), % ... with a known initial value. QualVarName = qual(OptInfo ^ oi_module_name, module_qual, VarName), ( Initializer = init_obj(Rval) ; Initializer = no_initializer, find_initial_val_in_statements(QualVarName, Rval, !Statements) ), % It's only safe to do this transformation if the variable's value % is constant, otherwise we might end up moving the rvalue across % a statement which modifies it. rval_will_not_change(Rval), % This transformation moves evaluation of the rvalue later in the % computation. If the rvalue is something which might loop, throw an % exception, or abort (e.g. for division by zero), then this might change % the behaviour of the program. In such cases, we can only do the % transformation if reordering of both conjunctions and disjunctions % (we can't tell here whether this MLDS code came from a conjunction % or a disjunction) is allowed. ( rval_cannot_throw(Rval) ; Globals = OptInfo ^ oi_globals, globals.lookup_bool_option(Globals, reorder_conj, yes), globals.lookup_bool_option(Globals, reorder_disj, yes) ), % Replace uses of this variable with the variable's value, % checking that none of the statements or definitions took the % address of the variable, or assigned to it. eliminate_var(QualVarName, Rval, Defns0, Defns, !Statements, Count, Invalidated), Invalidated = no, % Make sure that we didn't duplicate the rval, unless it is just a constant % or a variable, because duplicating any real operation would be % a pessimization. ( Count =< 1 ; rval_is_cheap_enough_to_duplicate(Rval) ). :- pred rval_is_cheap_enough_to_duplicate(mlds_rval::in) is semidet. rval_is_cheap_enough_to_duplicate(Rval) :- ( Rval = ml_const(_) ; Rval = ml_lval(ml_var(_, _)) ; Rval = ml_mem_addr(_) ; Rval = ml_self(_) ). % Succeed only if the specified rval definitely won't change in value. % :- pred rval_will_not_change(mlds_rval::in) is semidet. rval_will_not_change(ml_const(_)). rval_will_not_change(ml_mkword(_Tag, Rval)) :- rval_will_not_change(Rval). rval_will_not_change(ml_unop(_Op, Rval)) :- rval_will_not_change(Rval). rval_will_not_change(ml_binop(_Op, Rval1, Rval2)) :- rval_will_not_change(Rval1), rval_will_not_change(Rval2). rval_will_not_change(ml_mem_addr(ml_var(_, _))). rval_will_not_change(ml_mem_addr(ml_mem_ref(Address, _Type))) :- rval_will_not_change(Address). rval_will_not_change(ml_mem_addr(ml_field(_, Address, _, _, _))) :- rval_will_not_change(Address). % Succeed only if the given rval definitely can't loop, % throw an exception, or abort. % We use a pretty conservative approximation... % :- pred rval_cannot_throw(mlds_rval::in) is semidet. rval_cannot_throw(ml_const(_)). rval_cannot_throw(ml_mkword(_Tag, Rval)) :- rval_cannot_throw(Rval). rval_cannot_throw(ml_mem_addr(_)). rval_cannot_throw(ml_self(_)). % Search through a list of statements, trying to find the first assignment % to the specified variable. Return the initial value, and a modified list % of statements with the initial assignment deleted. Fail if the first % value can't be determined. % :- pred find_initial_val_in_statements(mlds_var::in, mlds_rval::out, list(statement)::in, list(statement)::out) is semidet. find_initial_val_in_statements(VarName, Rval, [Statement0 | Statements0], Statements) :- ( find_initial_val_in_statement(VarName, Rval1, Statement0, Statement1) -> Rval = Rval1, ( Statement1 = statement(ml_stmt_block([], []), _) -> Statements = Statements0 ; Statements = [Statement1 | Statements0] ) ; % Check that Statement0 doesn't modify the value of the variable % -- this includes checking that there are no labels via which code % could branch into the middle of Statement0. Only if we are sure % that Statement0 can't modify the variable's value is it safe to go % on and look for the initial value in Statements0. VarName = qual(Mod, QualKind, UnqualVarName), DataName = qual(Mod, QualKind, mlds_data_var(UnqualVarName)), statement_contains_var(Statement0, DataName) = no, \+ ( statement_contains_statement(Statement0, Label), Label = statement(ml_stmt_label(_), _) ), find_initial_val_in_statements(VarName, Rval, Statements0, Statements1), Statements = [Statement0 | Statements1] ). :- pred find_initial_val_in_statement(mlds_var::in, mlds_rval::out, statement::in, statement::out) is semidet. find_initial_val_in_statement(Var, Rval, Statement0, Statement) :- Statement0 = statement(Stmt0, Context), Statement = statement(Stmt, Context), ( Stmt0 = ml_stmt_atomic(assign(ml_var(Var, _Type), Rval0)) -> Rval = Rval0, % Delete the assignment, by replacing it with an empty block. Stmt = ml_stmt_block([], []) ; Stmt0 = ml_stmt_block(Defns0, SubStatements0) -> Var = qual(Mod, QualKind, UnqualVarName), Data = qual(Mod, QualKind, mlds_data_var(UnqualVarName)), defns_contains_var(Defns0, Data) = no, find_initial_val_in_statements(Var, Rval, SubStatements0, SubStatements), Stmt = ml_stmt_block(Defns0, SubStatements) ; fail ). % Replace uses of this variable with the variable's value in the specified % definitions and statements. This will return a count of how many % occurrences of the variable there were. It will also return % Invalidated = yes if any of the statements or definitions take % the address of the variable, or assign to it; in that case, the % transformation should not be performed. % :- pred eliminate_var(mlds_var::in, mlds_rval::in, list(mlds_defn)::in, list(mlds_defn)::out, list(statement)::in, list(statement)::out, int::out, bool::out) is det. eliminate_var(QualVarName, VarRval, !Defns, !Statements, Count, Invalidated) :- Count0 = 0, Invalidated0 = no, VarElimInfo0 = var_elim_info(QualVarName, VarRval, Count0, Invalidated0), eliminate_var_in_block(!Defns, !Statements, VarElimInfo0, VarElimInfo), Count = VarElimInfo ^ replace_count, Invalidated = VarElimInfo ^ invalidated. % eliminate_var_in_*: % % Process the specified construct, replacing all rvalue occurences of the % variable (^var_name) with its value (^var_value), incrementing the % ^replace_count field for each occurrence as an rvalue, and setting % ^invalidated to yes if the variable occurs as an lvalue. :- pred eliminate_var_in_block(list(mlds_defn)::in, list(mlds_defn)::out, list(statement)::in, list(statement)::out, var_elim_info::in, var_elim_info::out) is det. eliminate_var_in_block(!Defns, !Statements, !VarElimInfo) :- eliminate_var_in_defns(!Defns, !VarElimInfo), eliminate_var_in_statements(!Statements, !VarElimInfo). :- pred eliminate_var_in_defns(list(mlds_defn)::in, list(mlds_defn)::out, var_elim_info::in, var_elim_info::out) is det. eliminate_var_in_defns(!Defns, !VarElimInfo) :- list.map_foldl(eliminate_var_in_defn, !Defns, !VarElimInfo). :- pred eliminate_var_in_defn(mlds_defn::in, mlds_defn::out, var_elim_info::in, var_elim_info::out) is det. eliminate_var_in_defn(Defn0, Defn, !VarElimInfo) :- Defn0 = mlds_defn(Name, Context, Flags, DefnBody0), ( DefnBody0 = mlds_data(Type, Initializer0, GCStatement), eliminate_var_in_initializer(Initializer0, Initializer, !VarElimInfo), DefnBody = mlds_data(Type, Initializer, GCStatement) ; DefnBody0 = mlds_class(_), % We assume that nested classes don't refer to local variables % in the containing scope. DefnBody = DefnBody0 ; DefnBody0 = mlds_function(Id, Params, Body0, Attributes, EnvVarNames), eliminate_var_in_function_body(Body0, Body, !VarElimInfo), DefnBody = mlds_function(Id, Params, Body, Attributes, EnvVarNames) ), Defn = mlds_defn(Name, Context, Flags, DefnBody). :- pred eliminate_var_in_function_body( mlds_function_body::in, mlds_function_body::out, var_elim_info::in, var_elim_info::out) is det. eliminate_var_in_function_body(body_external, body_external, !VarElimInfo). eliminate_var_in_function_body(body_defined_here(Stmt0), body_defined_here(Stmt), !VarElimInfo) :- eliminate_var_in_statement(Stmt0, Stmt, !VarElimInfo). :- pred eliminate_var_in_initializer( mlds_initializer::in, mlds_initializer::out, var_elim_info::in, var_elim_info::out) is det. eliminate_var_in_initializer(no_initializer, no_initializer, !VarElimInfo). eliminate_var_in_initializer(init_obj(Rval0), init_obj(Rval), !VarElimInfo) :- eliminate_var_in_rval(Rval0, Rval, !VarElimInfo). eliminate_var_in_initializer(init_array(Elements0), init_array(Elements), !VarElimInfo) :- list.map_foldl(eliminate_var_in_initializer, Elements0, Elements, !VarElimInfo). eliminate_var_in_initializer(init_struct(Type, Members0), init_struct(Type, Members), !VarElimInfo) :- list.map_foldl(eliminate_var_in_initializer, Members0, Members, !VarElimInfo). :- pred eliminate_var_in_rvals(list(mlds_rval)::in, list(mlds_rval)::out, var_elim_info::in, var_elim_info::out) is det. eliminate_var_in_rvals(!Rvals, !VarElimInfo) :- list.map_foldl(eliminate_var_in_rval, !Rvals, !VarElimInfo). :- pred eliminate_var_in_maybe_rval( maybe(mlds_rval)::in, maybe(mlds_rval)::out, var_elim_info::in, var_elim_info::out) is det. eliminate_var_in_maybe_rval(no, no, !VarElimInfo). eliminate_var_in_maybe_rval(yes(Rval0), yes(Rval), !VarElimInfo) :- eliminate_var_in_rval(Rval0, Rval, !VarElimInfo). :- pred eliminate_var_in_rval(mlds_rval::in, mlds_rval::out, var_elim_info::in, var_elim_info::out) is det. eliminate_var_in_rval(Rval0, Rval, !VarElimInfo) :- ( Rval0 = ml_lval(Lval0), VarName = !.VarElimInfo ^ var_name, ( Lval0 = ml_var(VarName, _) -> % We found an rvalue occurrence of the variable -- replace it % with the rval for the variable's value, and increment the counter % for the number of occurrences that we have replaced. Rval = !.VarElimInfo ^ var_value, Count0 = !.VarElimInfo ^ replace_count, !:VarElimInfo = !.VarElimInfo ^ replace_count := Count0 + 1 ; eliminate_var_in_lval(Lval0, Lval, !VarElimInfo), Rval = ml_lval(Lval) ) ; Rval0 = ml_mkword(Tag, ArgRval0), eliminate_var_in_rval(ArgRval0, ArgRval, !VarElimInfo), Rval = ml_mkword(Tag, ArgRval) ; Rval0 = ml_unop(Op, ArgRval0), eliminate_var_in_rval(ArgRval0, ArgRval, !VarElimInfo), Rval = ml_unop(Op, ArgRval) ; Rval0 = ml_binop(Op, Arg1Rval0, Arg2Rval0), eliminate_var_in_rval(Arg1Rval0, Arg1Rval, !VarElimInfo), eliminate_var_in_rval(Arg2Rval0, Arg2Rval, !VarElimInfo), Rval = ml_binop(Op, Arg1Rval, Arg2Rval) ; Rval0 = ml_mem_addr(Lval0), eliminate_var_in_lval(Lval0, Lval, !VarElimInfo), Rval = ml_mem_addr(Lval) ; Rval0 = ml_vector_common_row(VectorCommon, RowRval0), eliminate_var_in_rval(RowRval0, RowRval, !VarElimInfo), Rval = ml_vector_common_row(VectorCommon, RowRval) ; ( Rval0 = ml_const(_) ; Rval0 = ml_scalar_common(_) ; Rval0 = ml_self(_) ), Rval = Rval0 ). :- pred eliminate_var_in_lvals(list(mlds_lval)::in, list(mlds_lval)::out, var_elim_info::in, var_elim_info::out) is det. eliminate_var_in_lvals(!Lvals, !VarElimInfo) :- list.map_foldl(eliminate_var_in_lval, !Lvals, !VarElimInfo). :- pred eliminate_var_in_lval(mlds_lval::in, mlds_lval::out, var_elim_info::in, var_elim_info::out) is det. eliminate_var_in_lval(Lval0, Lval, !VarElimInfo) :- ( Lval0 = ml_field(MaybeTag, Rval0, FieldId, FieldType, PtrType), eliminate_var_in_rval(Rval0, Rval, !VarElimInfo), Lval = ml_field(MaybeTag, Rval, FieldId, FieldType, PtrType) ; Lval0 = ml_mem_ref(Rval0, Type), eliminate_var_in_rval(Rval0, Rval, !VarElimInfo), Lval = ml_mem_ref(Rval, Type) ; Lval0 = ml_global_var_ref(_Ref), Lval = Lval0 ; Lval0 = ml_var(VarName, _Type), ( VarName = !.VarElimInfo ^ var_name -> % We found an lvalue occurrence of the variable -- if the variable % that we are trying to eliminate has its address is taken, % or is assigned to, or in general if it is used as an lvalue, % then it's not safe to eliminate it !VarElimInfo ^ invalidated := yes ; true ), Lval = Lval0 ). :- pred eliminate_var_in_statements( list(statement)::in, list(statement)::out, var_elim_info::in, var_elim_info::out) is det. eliminate_var_in_statements(!Statements, !VarElimInfo) :- list.map_foldl(eliminate_var_in_statement, !Statements, !VarElimInfo). :- pred eliminate_var_in_maybe_statement( maybe(statement)::in, maybe(statement)::out, var_elim_info::in, var_elim_info::out) is det. eliminate_var_in_maybe_statement(no, no, !VarElimInfo). eliminate_var_in_maybe_statement(yes(Statement0), yes(Statement), !VarElimInfo) :- eliminate_var_in_statement(Statement0, Statement, !VarElimInfo). :- pred eliminate_var_in_statement(statement::in, statement::out, var_elim_info::in, var_elim_info::out) is det. eliminate_var_in_statement(Statement0, Statement, !VarElimInfo) :- Statement0 = statement(Stmt0, Context), eliminate_var_in_stmt(Stmt0, Stmt, !VarElimInfo), Statement = statement(Stmt, Context). :- pred eliminate_var_in_stmt(mlds_stmt::in, mlds_stmt::out, var_elim_info::in, var_elim_info::out) is det. eliminate_var_in_stmt(Stmt0, Stmt, !VarElimInfo) :- ( Stmt0 = ml_stmt_block(Defns0, Statements0), eliminate_var_in_block(Defns0, Defns, Statements0, Statements, !VarElimInfo), Stmt = ml_stmt_block(Defns, Statements) ; Stmt0 = ml_stmt_while(Kind, Rval0, Statement0), eliminate_var_in_rval(Rval0, Rval, !VarElimInfo), eliminate_var_in_statement(Statement0, Statement, !VarElimInfo), Stmt = ml_stmt_while(Kind, Rval, Statement) ; Stmt0 = ml_stmt_if_then_else(Cond0, Then0, MaybeElse0), eliminate_var_in_rval(Cond0, Cond, !VarElimInfo), eliminate_var_in_statement(Then0, Then, !VarElimInfo), eliminate_var_in_maybe_statement(MaybeElse0, MaybeElse, !VarElimInfo), Stmt = ml_stmt_if_then_else(Cond, Then, MaybeElse) ; Stmt0 = ml_stmt_switch(Type, Val0, Range, Cases0, Default0), eliminate_var_in_rval(Val0, Val, !VarElimInfo), list.map_foldl(eliminate_var_in_case, Cases0, Cases, !VarElimInfo), eliminate_var_in_default(Default0, Default, !VarElimInfo), Stmt = ml_stmt_switch(Type, Val, Range, Cases, Default) ; Stmt0 = ml_stmt_label(_), Stmt = Stmt0 ; Stmt0 = ml_stmt_goto(_), Stmt = Stmt0 ; Stmt0 = ml_stmt_computed_goto(Rval0, Labels), eliminate_var_in_rval(Rval0, Rval, !VarElimInfo), Stmt = ml_stmt_computed_goto(Rval, Labels) ; Stmt0 = ml_stmt_call(Sig, Func0, Obj0, Args0, RetLvals0, TailCall), eliminate_var_in_rval(Func0, Func, !VarElimInfo), eliminate_var_in_maybe_rval(Obj0, Obj, !VarElimInfo), eliminate_var_in_rvals(Args0, Args, !VarElimInfo), eliminate_var_in_lvals(RetLvals0, RetLvals, !VarElimInfo), Stmt = ml_stmt_call(Sig, Func, Obj, Args, RetLvals, TailCall) ; Stmt0 = ml_stmt_return(Rvals0), eliminate_var_in_rvals(Rvals0, Rvals, !VarElimInfo), Stmt = ml_stmt_return(Rvals) ; Stmt0 = ml_stmt_do_commit(Ref0), eliminate_var_in_rval(Ref0, Ref, !VarElimInfo), Stmt = ml_stmt_do_commit(Ref) ; Stmt0 = ml_stmt_try_commit(Ref0, Statement0, Handler0), eliminate_var_in_lval(Ref0, Ref, !VarElimInfo), eliminate_var_in_statement(Statement0, Statement, !VarElimInfo), eliminate_var_in_statement(Handler0, Handler, !VarElimInfo), Stmt = ml_stmt_try_commit(Ref, Statement, Handler) ; Stmt0 = ml_stmt_atomic(AtomicStmt0), eliminate_var_in_atomic_stmt(AtomicStmt0, AtomicStmt, !VarElimInfo), Stmt = ml_stmt_atomic(AtomicStmt) ). :- pred eliminate_var_in_case(mlds_switch_case::in, mlds_switch_case::out, var_elim_info::in, var_elim_info::out) is det. eliminate_var_in_case(Case0, Case, !VarElimInfo) :- Case0 = mlds_switch_case(FirstCond0, LaterConds0, Statement0), eliminate_var_in_case_cond(FirstCond0, FirstCond, !VarElimInfo), list.map_foldl(eliminate_var_in_case_cond, LaterConds0, LaterConds, !VarElimInfo), eliminate_var_in_statement(Statement0, Statement, !VarElimInfo), Case = mlds_switch_case(FirstCond, LaterConds, Statement). :- pred eliminate_var_in_default( mlds_switch_default::in, mlds_switch_default::out, var_elim_info::in, var_elim_info::out) is det. eliminate_var_in_default(Default0, Default, !VarElimInfo) :- ( ( Default0 = default_is_unreachable ; Default0 = default_do_nothing ), Default = Default0 ; Default0 = default_case(Statement0), eliminate_var_in_statement(Statement0, Statement, !VarElimInfo), Default = default_case(Statement) ). :- pred eliminate_var_in_atomic_stmt( mlds_atomic_statement::in, mlds_atomic_statement::out, var_elim_info::in, var_elim_info::out) is det. eliminate_var_in_atomic_stmt(Stmt0, Stmt, !VarElimInfo) :- ( ( Stmt0 = comment(_) ; Stmt0 = gc_check ), Stmt = Stmt0 ; Stmt0 = assign(Lval0, Rval0), eliminate_var_in_lval(Lval0, Lval, !VarElimInfo), eliminate_var_in_rval(Rval0, Rval, !VarElimInfo), Stmt = assign(Lval, Rval) ; Stmt0 = assign_if_in_heap(Lval0, Rval0), eliminate_var_in_lval(Lval0, Lval, !VarElimInfo), eliminate_var_in_rval(Rval0, Rval, !VarElimInfo), Stmt = assign_if_in_heap(Lval, Rval) ; Stmt0 = delete_object(Rval0), eliminate_var_in_rval(Rval0, Rval, !VarElimInfo), Stmt = delete_object(Rval) ; Stmt0 = new_object(Target0, MaybeTag, ExplicitSecTag, Type, MaybeSize, MaybeCtorName, Args0, ArgTypes, MayUseAtomic), eliminate_var_in_lval(Target0, Target, !VarElimInfo), eliminate_var_in_rvals(Args0, Args, !VarElimInfo), Stmt = new_object(Target, MaybeTag, ExplicitSecTag, Type, MaybeSize, MaybeCtorName, Args, ArgTypes, MayUseAtomic) ; Stmt0 = mark_hp(Lval0), eliminate_var_in_lval(Lval0, Lval, !VarElimInfo), Stmt = mark_hp(Lval) ; Stmt0 = restore_hp(Rval0), eliminate_var_in_rval(Rval0, Rval, !VarElimInfo), Stmt = restore_hp(Rval) ; Stmt0 = trail_op(TrailOp0), eliminate_var_in_trail_op(TrailOp0, TrailOp, !VarElimInfo), Stmt = trail_op(TrailOp) ; Stmt0 = inline_target_code(Lang, Components0), list.map_foldl(eliminate_var_in_target_code_component, Components0, Components, !VarElimInfo), Stmt = inline_target_code(Lang, Components) ; Stmt0 = outline_foreign_proc(Lang, Vs, Lvals0, Code), eliminate_var_in_lvals(Lvals0, Lvals, !VarElimInfo), Stmt = outline_foreign_proc(Lang, Vs, Lvals, Code) ). :- pred eliminate_var_in_case_cond( mlds_case_match_cond::in, mlds_case_match_cond::out, var_elim_info::in, var_elim_info::out) is det. eliminate_var_in_case_cond(Cond0, Cond, !VarElimInfo) :- ( Cond0 = match_value(Rval0), eliminate_var_in_rval(Rval0, Rval, !VarElimInfo), Cond = match_value(Rval) ; Cond0 = match_range(Low0, High0), eliminate_var_in_rval(Low0, Low, !VarElimInfo), eliminate_var_in_rval(High0, High, !VarElimInfo), Cond = match_range(Low, High) ). :- pred eliminate_var_in_target_code_component( target_code_component::in, target_code_component::out, var_elim_info::in, var_elim_info::out) is det. eliminate_var_in_target_code_component(Component0, Component, !VarElimInfo) :- ( ( Component0 = raw_target_code(_Code, _Attrs) ; Component0 = user_target_code(_Code, _Context, _Attrs) ; Component0 = target_code_type(_Type) ; Component0 = target_code_name(_Name) ), Component = Component0 ; Component0 = target_code_input(Rval0), eliminate_var_in_rval(Rval0, Rval, !VarElimInfo), Component = target_code_input(Rval) ; Component0 = target_code_output(Lval0), eliminate_var_in_lval(Lval0, Lval, !VarElimInfo), Component = target_code_output(Lval) ). :- pred eliminate_var_in_trail_op(trail_op::in, trail_op::out, var_elim_info::in, var_elim_info::out) is det. eliminate_var_in_trail_op(Op0, Op, !VarElimInfo) :- ( Op0 = store_ticket(Lval0), eliminate_var_in_lval(Lval0, Lval, !VarElimInfo), Op = store_ticket(Lval) ; Op0 = reset_ticket(Rval0, Reason), eliminate_var_in_rval(Rval0, Rval, !VarElimInfo), Op = reset_ticket(Rval, Reason) ; ( Op0 = discard_ticket ; Op0 = prune_ticket ), Op = Op0 ; Op0 = mark_ticket_stack(Lval0), eliminate_var_in_lval(Lval0, Lval, !VarElimInfo), Op = mark_ticket_stack(Lval) ; Op0 = prune_tickets_to(Rval0), eliminate_var_in_rval(Rval0, Rval, !VarElimInfo), Op = prune_tickets_to(Rval) ). %-----------------------------------------------------------------------------% :- func this_file = string. this_file = "ml_optimize.m". %-----------------------------------------------------------------------------% :- end_module ml_optimize. %-----------------------------------------------------------------------------%