%-----------------------------------------------------------------------------% % Copyright (C) 1999 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: rl_relops % Main author: stayl % % Generate intermediate code for relational operations. %-----------------------------------------------------------------------------% :- module rl_relops. :- interface. :- import_module hlds_goal, instmap, prog_data, rl, rl_info. :- import_module bool, list, std_util. /* % Perform projections and selections before joins. In some cases % this may worsen performance (reducing tuple size by removing % a single integer from each tuple is not likely to improve the % perforance of a join by much, but requires an extra pass over % the data). There's not really any way to decide at compile time % what the performance difference will be, so we do it anyway. % % XXX this is a bad idea, since the relation being projected % may have an index built for it, which will have to be rebuilt % after the projection. This can be done later as an optimization. :- pred rl_relops__pre_select_and_project(relation_id::in, relation_id::out, list(prog_var)::in, list(prog_var)::in, instmap::in, list(hlds_goal)::in, rl_tree::out, rl_info::rl_info_di, rl_info::rl_info_uo) is det. */ % Compute a projection for a single call rule. If the % projection condition is semidet, generate a select first. :- pred rl_relops__select_and_project(relation_id::in, relation_id::out, list(prog_var)::in, list(prog_var)::in, instmap::in, list(hlds_goal)::in, rl_tree::out, rl_info::rl_info_di, rl_info::rl_info_uo) is det. % rl_relops__join(InputRel1, InputRel2, InputArgs1, InputArgs2, % InstMap, JoinCondGoals, OutputArgs, OutputSchema, OutputRel, % Code). :- pred rl_relops__join(relation_id::in, relation_id::in, list(prog_var)::in, list(prog_var)::in, instmap::in, list(hlds_goal)::in, list(prog_var)::in, relation_schema::in, relation_id::out, rl_tree::out, rl_info::rl_info_di, rl_info::rl_info_uo) is det. % rl_relops__join(FullRel1, DiffRel1, FullRel2, DiffRel2, % InputArgs1, InputArgs2, InstMap, JoinCondGoals, % OutputArgs, OutputSchema, OutputRel, Code). % % Generate (DiffRel1 |X| FullRel2) U (FullRel1 |X| DiffRel2) for a % rule with two recursive calls. :- pred rl_relops__diff_diff_join(relation_id::in, relation_id::in, relation_id::in, relation_id::in, list(prog_var)::in, list(prog_var)::in, instmap::in, list(hlds_goal)::in, list(prog_var)::in, relation_schema::in, relation_id::out, rl_tree::out, rl_info::rl_info_di, rl_info::rl_info_uo) is det. % rl_relops__subtract(InputRel1, InputRel2, InputArgs1, InputArgs2, % InstMap, SubtractCond, ProjectCond, OutputArgs, OutputSchema, % OutputRelation, Code). :- pred rl_relops__subtract(relation_id::in, relation_id::in, list(prog_var)::in, list(prog_var)::in, instmap::in, list(hlds_goal)::in, list(hlds_goal)::in, list(prog_var)::in, relation_schema::in, relation_id::out, rl_tree::out, rl_info::rl_info_di, rl_info::rl_info_uo) is det. % rl_relops__difference(InputRel1, InputRel2, OutputRel, Code). % % A difference is just a special case of subtract % (InputRel1 - InputRel2 = OutputRel). % The inputs to the difference must be sorted. :- pred rl_relops__difference(relation_id::in, relation_id::in, relation_id::in, rl_tree::out, rl_info::rl_info_di, rl_info::rl_info_uo) is det. % rl_relops__union(MustSortOutput, Schema, InputRels, MaybeOutputRel, % OutputRel, Code). :- pred rl_relops__union(bool::in, relation_schema::in, list(relation_id)::in, maybe(relation_id)::in, relation_id::out, rl_tree::out, rl_info::rl_info_di, rl_info::rl_info_uo) is det. % Generate code to sort a relation on all its arguments. :- pred rl_relops__sort(relation_id::in, relation_id::out, rl_tree::out, rl_info::rl_info_di, rl_info::rl_info_uo) is det. % Package up a list of top-down goals to execute as a condition % or projection for a relational operation. :- pred rl_relops__goal(instmap::in, rl_goal_inputs::in, rl_goal_outputs::in, list(hlds_goal)::in, rl_goal::out, rl_info::rl_info_di, rl_info::rl_info_uo) is det. :- implementation. :- import_module code_aux, hlds_data, hlds_module, hlds_pred, mode_util. :- import_module tree, rl_key, globals, options. :- import_module int, map, require, set. %-----------------------------------------------------------------------------% %-----------------------------------------------------------------------------% /* rl_relops__pre_select_and_project(Relation, ProjectRel, OutputArgs, NonLocalOutputArgs, InstMap0, CondGoals, Code) --> rl_info_write_message("Generating pre-project\n", []), { set__list_to_set(NonLocalOutputArgs, NonLocalVars) }, rl_relops__get_dependent_goals(NonLocalVars, InstMap0, CondGoals, ThisCallGoals), rl_relops__maybe_select(Relation, SelectRel, OutputArgs, InstMap0, ThisCallGoals, SelectCode), ( { OutputArgs = NonLocalOutputArgs } -> { ProjectRel = SelectRel }, { ProjectCode = empty } , rl_info_write_message("No project\n", []) ; rl_relops__project(SelectRel, ProjectRel, OutputArgs, NonLocalOutputArgs, InstMap0, [], ProjectCode), rl_info_write_message("Generated project\n", []) ), { Code = tree(SelectCode, ProjectCode) }. */ % Get the goals from a list of goals that depend only on the % given variables. This is used to select out the goals that % can be evaluated with only one of the calls to be joined. :- pred rl_relops__get_dependent_goals(set(prog_var)::in, instmap::in, list(hlds_goal)::in, list(hlds_goal)::out, rl_info::rl_info_di, rl_info::rl_info_uo) is det. rl_relops__get_dependent_goals(_, _, [], []) --> []. rl_relops__get_dependent_goals(Vars, InstMap0, [Goal | Goals], DependentGoals) --> % Get the input vars for this goal. { Goal = _ - GoalInfo }, { goal_info_get_nonlocals(GoalInfo, NonLocals) }, { set__to_sorted_list(NonLocals, NonLocalsList) }, { goal_info_get_instmap_delta(GoalInfo, InstMapDelta) }, { instmap__apply_instmap_delta(InstMap0, InstMapDelta, InstMap) }, rl_info_get_module_info(ModuleInfo), { IsInput = lambda([Var::in] is semidet, ( instmap__lookup_var(InstMap0, Var, Inst0), instmap__lookup_var(InstMap, Var, Inst), mode_is_input(ModuleInfo, (Inst0 -> Inst)) )) }, { list__filter(IsInput, NonLocalsList, InputNonLocalsList) }, { set__sorted_list_to_set(InputNonLocalsList, InputNonLocals) }, { set__difference(InputNonLocals, Vars, OtherCallInputs) }, ( { set__empty(OtherCallInputs) } -> { set__union(Vars, NonLocals, Vars1) }, rl_relops__get_dependent_goals(Vars1, InstMap, Goals, DependentGoals1), { DependentGoals = [Goal | DependentGoals1] } ; rl_relops__get_dependent_goals(Vars, InstMap, Goals, DependentGoals) ). %-----------------------------------------------------------------------------% rl_relops__select_and_project(InputRel, OutputRel, InputArgs, OutputArgs, InstMap0, CondGoals, Code) --> rl_info_write_message("Generating single call select+project\n", []), { goal_list_determinism(CondGoals, Detism) }, { determinism_components(Detism, CanFail, _) }, ( { InputArgs = OutputArgs -> % If the outputs are the same as the inputs, we % only need to do the operation if it can fail. CanFail = can_fail, MaybeOutputs = no ; MaybeOutputs = yes(OutputArgs) } -> rl_relops__goal(InstMap0, one_input(InputArgs), MaybeOutputs, CondGoals, Cond), ( { MaybeOutputs = yes(_) } -> rl_info_get_proc_info(ProcInfo), { proc_info_vartypes(ProcInfo, VarTypes) }, { map__apply_to_list(OutputArgs, VarTypes, OutputTypes) }, { OutputSchema = schema(OutputTypes) } ; { OutputSchema = same_as_relation(InputRel) } ), rl_info_get_new_temporary(OutputSchema, OutputRel), rl_info__comment(Comment), { ProjectInstr = project(output_rel(OutputRel, []), InputRel, Cond, [], filter) - Comment }, { Code = node([ProjectInstr]) } ; { OutputRel = InputRel }, { Code = empty } ). %-----------------------------------------------------------------------------% rl_relops__join(InputRel1, InputRel2, Args1, Args2, InstMap, JoinCondGoals, OutputVars, OutputSchema, OutputRel, Code) --> rl_relops__join_2(InputRel1, InputRel2, Args1, Args2, InstMap, JoinCondGoals, OutputVars, OutputSchema, JoinOutputRel, JoinType, JoinOutputs, JoinCode), % Semi-join doesn't create an output tuple, so we may need % to do a projection on the output relation. ( { JoinType = semi } -> rl_relops__select_and_project(JoinOutputRel, OutputRel, JoinOutputs, OutputVars, InstMap, JoinCondGoals, ProjectCode) ; { ProjectCode = empty }, { OutputRel = JoinOutputRel } ), { Code = tree(JoinCode, ProjectCode) }. :- pred rl_relops__join_2(relation_id::in, relation_id::in, list(prog_var)::in, list(prog_var)::in, instmap::in, list(hlds_goal)::in, list(prog_var)::in, relation_schema::in, relation_id::out, join_type::out, list(prog_var)::out, rl_tree::out, rl_info::rl_info_di, rl_info::rl_info_uo) is det. rl_relops__join_2(InputRel1, InputRel2, Args1, Args2, InstMap, JoinCondGoals, OutputVars, OutputSchema, OutputRel, JoinType, JoinOutputs, Code) --> rl_relops__classify_join_condition(Args1, Args2, OutputVars, JoinCondGoals, JoinType, JoinOutputs, ReverseInputs), { rl_relops__maybe_reverse_inputs(ReverseInputs, InputRel1, InputRel2, ReorderedInput1, ReorderedInput2) }, { rl_relops__maybe_reverse_inputs(ReverseInputs, Args1, Args2, ReorderedArgs1, ReorderedArgs2) }, ( { JoinType = semi } -> rl_info_get_proc_info(ProcInfo), { proc_info_vartypes(ProcInfo, VarTypes) }, { map__apply_to_list(JoinOutputs, VarTypes, JoinOutputTypes) }, { JoinOutputSchema = schema(JoinOutputTypes) }, { MaybeOutputs = no } ; { JoinOutputSchema = OutputSchema }, { MaybeOutputs = yes(OutputVars) } ), rl_relops__goal(InstMap, two_inputs(ReorderedArgs1, ReorderedArgs2), MaybeOutputs, JoinCondGoals, JoinCond), rl_info__comment(Comment), rl_info_get_new_temporary(JoinOutputSchema, OutputRel), { Code = node([join(output_rel(OutputRel, []), ReorderedInput1, ReorderedInput2, JoinType, JoinCond) - Comment]) }. rl_relops__diff_diff_join(DiffRel1, FullRel1, DiffRel2, FullRel2, Args1, Args2, InstMap, JoinCondGoals, RuleOutputs, RuleSchema, RuleResult, JoinCode) --> rl_relops__join_2(DiffRel1, FullRel2, Args1, Args2, InstMap, JoinCondGoals, RuleOutputs, RuleSchema, OutputRel1, JoinType, JoinOutputs, JoinCode1), rl_relops__join_2(FullRel1, DiffRel2, Args1, Args2, InstMap, JoinCondGoals, RuleOutputs, RuleSchema, OutputRel2, JoinType2, JoinOutputs2, JoinCode2), % We should get the same join type back from both joins. { JoinType = JoinType2, JoinOutputs = JoinOutputs2 -> true ; error("rl_relops__diff_diff_join: different join types") }, rl_relops__union(yes, RuleSchema, [OutputRel1, OutputRel2], no, UnionResult, UnionCode), % Delay the projection until after the union so that the % projection isn't run on duplicates tuples in each relation. ( { JoinType = semi } -> rl_relops__select_and_project(UnionResult, RuleResult, JoinOutputs, RuleOutputs, InstMap, JoinCondGoals, ProjectCode) ; { RuleResult = UnionResult }, { ProjectCode = empty } ), { JoinCode = tree(JoinCode1, tree(JoinCode2, tree(UnionCode, ProjectCode ))) }. % If the outputs of a join are the same as the attributes % of an input relation, we can do a semi-join. If so, the % inputs may need to be reversed so that the relation from % which the outputs are to be taken is the first argument % of the join. % XXX Aditi's implementation of semi-join is buggy. :- pred rl_relops__classify_join_condition(list(prog_var)::in, list(prog_var)::in, list(prog_var)::in, list(hlds_goal)::in, join_type::out, list(prog_var)::out, bool::out, rl_info::rl_info_di, rl_info::rl_info_uo) is det. rl_relops__classify_join_condition(Args1, Args2, Outputs, _Goals, Type, JoinOutputs, ReverseInputRels) --> { Args1 = Outputs, semidet_fail -> Type = semi, ReverseInputRels = no, JoinOutputs = Args1 ; Args2 = Outputs, semidet_fail -> Type = semi, ReverseInputRels = yes, JoinOutputs = Args2 ; Type = nested_loop, ReverseInputRels = no, JoinOutputs = Outputs }. :- pred rl_relops__maybe_reverse_inputs(bool::in, T::in, T::in, T::out, T::out) is det. rl_relops__maybe_reverse_inputs(yes, A, B, B, A). rl_relops__maybe_reverse_inputs(no, A, B, A, B). %-----------------------------------------------------------------------------% rl_relops__subtract(Rel1, Rel2, OutputArgs1, OutputArgs2, InstMap, SubtractGoals, ProjectGoals, OutputVars, _Schema, Result, SubtractCode) --> rl_relops__classify_subtract_condition(SubtractGoals, SubtractType), rl_relops__goal(InstMap, two_inputs(OutputArgs1, OutputArgs2), no, SubtractGoals, SubtractCond), rl_info_get_new_temporary(same_as_relation(Rel1), TempRel), { Subtract = subtract(output_rel(TempRel, []), Rel1, Rel2, SubtractType, SubtractCond) - Comment }, rl_info__comment(Comment), % The output projection for subtracts must be done as a separate % operation, unlike for nested loop joins. rl_relops__select_and_project(TempRel, Result, OutputArgs1, OutputVars, InstMap, ProjectGoals, ProjectCode), { SubtractCode = tree(node([Subtract]), ProjectCode ) }. :- pred rl_relops__classify_subtract_condition(list(hlds_goal)::in, subtract_type::out, rl_info::rl_info_di, rl_info::rl_info_uo) is det. rl_relops__classify_subtract_condition(_, semi) --> []. rl_relops__difference(OldRel, NewRel, DiffRel, Code) --> rl_info_get_relation_schema(OldRel, Schema), { rl__ascending_sort_spec(Schema, SortSpec) }, { Code = node([difference(output_rel(DiffRel, []), NewRel, OldRel, sort_merge(attributes(SortSpec))) - ""]) }. %-----------------------------------------------------------------------------% rl_relops__union(SortNeeded, Schema, RelsToUnion, MaybeUnionRel, UnionRel, UnionCode) --> ( { RelsToUnion = [] }, { error("rl_relops__union: no relations to union") } ; { RelsToUnion = [RelToUnion | RelsToUnion1] }, ( { RelsToUnion1 = [] }, ( { SortNeeded = yes } -> rl_relops__sort(RelToUnion, UnionRel, UnionCode) ; { UnionRel = RelToUnion }, { UnionCode = empty } ) ; { RelsToUnion1 = [_|_] }, ( { MaybeUnionRel = yes(UnionRel) } ; { MaybeUnionRel = no }, rl_info_get_new_temporary(Schema, UnionRel) ), ( { SortNeeded = yes } -> rl_relops__sort_rels(RelsToUnion, SortedRelsToUnion, SortCode) ; { SortCode = empty }, { SortedRelsToUnion = RelsToUnion } ), rl_info_relation_schema_to_type_list(Schema, Types), { rl__ascending_sort_spec(Types, SortAttrs) }, { Union = union(output_rel(UnionRel, []), SortedRelsToUnion, sort_merge(attributes(SortAttrs))) - "" }, { UnionCode = tree(SortCode, node([Union])) } ) ). %-----------------------------------------------------------------------------% rl_relops__sort(InputRel, SortedRel, Code) --> rl_info_get_relation_schema(InputRel, Schema), ( { Schema = [] } -> % Optimize for a zero arity relation, which occurs % for procedures with no input arguments. { SortedRel = InputRel }, { Code = empty } ; rl_info_get_new_temporary(schema(Schema), SortedRel), { rl__ascending_sort_spec(Schema, Spec) }, { Code = node([ sort(output_rel(SortedRel, []), InputRel, Spec) - "" ]) } ). :- pred rl_relops__sort_rels(list(relation_id)::in, list(relation_id)::out, rl_tree::out, rl_info::rl_info_di, rl_info::rl_info_uo) is det. rl_relops__sort_rels([], [], empty) --> []. rl_relops__sort_rels([InputRel | InputRels], [OutputRel | OutputRels], Code) --> rl_relops__sort(InputRel, OutputRel, SortCode), rl_relops__sort_rels(InputRels, OutputRels, Code1), { Code = tree(SortCode, Code1) }. %-----------------------------------------------------------------------------% rl_relops__goal(InstMap, Inputs, Outputs, Goals, RLGoal) --> rl_info_get_module_info(ModuleInfo), rl_info_get_pred_proc_id(PredProcId), rl_info_get_proc_info(ProcInfo), { proc_info_varset(ProcInfo, VarSet) }, { proc_info_vartypes(ProcInfo, VarTypes) }, { rl_key__extract_indexing(Inputs, Goals, ModuleInfo, VarTypes, Bounds) }, { RLGoal = rl_goal(yes(PredProcId), VarSet, VarTypes, InstMap, Inputs, Outputs, Goals, Bounds) }. %-----------------------------------------------------------------------------% %-----------------------------------------------------------------------------%