Files
mercury/compiler/ml_commit_gen.m
Zoltan Somogyi d00ea69529 Switch from using set(prog_var), which is represented using set_ordlist,
Estimated hours taken: 12
Branches: main

Switch from using set(prog_var), which is represented using set_ordlist,
to set_of_progvar, which is represented using tree_bitset, for most sets
of variables in the compiler, including the nonlocals sets in goal_infos.

This diff yields about a 5% speedup when compiling the training_cars_full.m
stress test, but also about a 1% slowdown on tools/speedtest. Both of these
are with the current default state in which tree_bitset is compiled with
a whole bunch of sanity checks. If these are disabled, we get roughly a 1%
speedup on tools/speedtest. I intend to disable those sanity checks after
a shakedown period of a week or two in which the updated version of the
compiler is installed on our platforms.

compiler/hlds_goal.m:
	Replace almost all occurrences of set(prog_var) with set_of_progvar.
	The main exceptions are the types supporting rbmm.

compiler/set_of_var.m:
	Add some more predicates and functions that previous existed on sets
	but not yet on set_of_vars.

compiler/*.m:
	Conform to the change in hlds_goal.m, and make similar changes
	in set representations.

library/bag.m:
	Add a predicate and function for creating a bag from a sorted list.
	We already had them for creating a bag from a set, but a set_of_progvar
	shouldn't have to be converted to a set.

library/robdd.m:
	Fix deviations from our programming style.
2011-08-16 03:26:40 +00:00

532 lines
20 KiB
Mathematica

%-----------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%-----------------------------------------------------------------------------%
% Copyright (C) 2009, 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_commit_gen.m.
% Main author: fjh.
%
% This module handles code generation for commits.
%
% There's several different ways of handling commits:
% - using catch/throw
% - using setjmp/longjmp
% - using GCC's __builtin_setjmp/__builtin_longjmp
% - exiting nested functions via gotos to
% their containing functions
%
% The MLDS data structure abstracts away these differences using the
% `try_commit' and `do_commit' instructions. The comments below show
% the MLDS try_commit/do_commit version first, but for clarity I've also
% included sample code using each of the three different techniques.
% This shows how the MLDS->target back-end can map mlds_commit_type,
% do_commit and try_commit into target language constructs.
%
% Note that if we're using GCC's __builtin_longjmp(), then it is important
% that the call to __builtin_longjmp() be put in its own function, to ensure
% that it is not in the same function as the __builtin_setjmp(). The code
% generation schema below does that automatically. We will need to be careful
% with MLDS optimizations to ensure that we preserve that invariant, though.
% (Alternatively, we could just call a function that calls __builtin_longjmp()
% rather than calling it directly. But that would be a little less efficient.)
%
% If those methods turn out to be too inefficient, another alternative would be
% to change the generated code so that after every function call, it would
% check a flag, and if that flag was set, it would return. Then MR_DO_COMMIT
% would just set the flag and return. The flag could be in a global
% (or thread-local) variable, or it could be an additional value returned
% from each function.
%
% model_non in semi context: (using try_commit/do_commit)
% <succeeded = Goal>
% ===>
% MR_COMMIT_TYPE ref;
% void success() {
% MR_DO_COMMIT(ref);
% }
% MR_TRY_COMMIT(ref, {
% <Goal && success()>
% succeeded = MR_FALSE;
% }, {
% succeeded = MR_TRUE;
% })
%
% model_non in semi context: (using catch/throw)
% <succeeded = Goal>
% ===>
% void success() {
% throw COMMIT();
% }
% try {
% <Goal && success()>
% succeeded = MR_FALSE;
% } catch (COMMIT) {
% succeeded = MR_TRUE;
% }
%
% The above is using C++ syntax. Here COMMIT is an exception type, which
% can be defined trivially (e.g. "class COMMIT {};"). Note that when using
% catch/throw, we don't need the "ref" argument at all; the target language's
% exception handling implementation keeps track of all the information needed
% to unwind the stack.
%
% model_non in semi context: (using setjmp/longjmp)
% <succeeded = Goal>
% ===>
% jmp_buf ref;
% void success() {
% longjmp(ref, 1);
% }
% if (setjmp(ref)) {
% succeeded = MR_TRUE;
% } else {
% <Goal && success()>
% succeeded = MR_FALSE;
% }
%
% model_non in semi context: (using GNU C nested functions,
% GNU C local labels, and exiting
% the nested function by a goto
% to a label in the containing function)
% <succeeded = Goal>
% ===>
% __label__ commit;
% void success() {
% goto commit;
% }
% <Goal && success()>
% succeeded = MR_FALSE;
% goto commit_done;
% commit:
% succeeded = MR_TRUE;
% commit_done:
% ;
%
% model_non in det context: (using try_commit/do_commit)
% <do Goal>
% ===>
% MR_COMMIT_TYPE ref;
% void success() {
% MR_DO_COMMIT(ref);
% }
% MR_TRY_COMMIT(ref, {
% <Goal && success()>
% }, {})
%
% model_non in det context (using GNU C nested functions,
% GNU C local labels, and exiting
% the nested function by a goto
% to a label in the containing function)
% <do Goal>
% ===>
% __label__ done;
% void success() {
% goto done;
% }
% <Goal && success()>
% done: ;
%
% model_non in det context (using catch/throw):
% <do Goal>
% ===>
% void success() {
% throw COMMIT();
% }
% try {
% <Goal && success()>
% } catch (COMMIT) {}
%
% model_non in det context (using setjmp/longjmp):
% <do Goal>
% ===>
% jmp_buf ref;
% void success() {
% longjmp(ref, 1);
% }
% if (setjmp(ref) == 0) {
% <Goal && success()>
% }
%
% Note that for all of these versions, we must hoist any static declarations
% generated for <Goal> out to the top level; this is needed so that such
% declarations remain in scope for any following goals.
:- module ml_backend.ml_commit_gen.
:- interface.
:- import_module hlds.code_model.
:- import_module hlds.hlds_goal.
:- import_module ml_backend.ml_gen_info.
:- import_module ml_backend.mlds.
:- import_module parse_tree.prog_data.
:- import_module list.
% Generate code for a commit.
%
:- pred ml_gen_commit(hlds_goal::in, code_model::in, prog_context::in,
list(mlds_defn)::out, list(statement)::out,
ml_gen_info::in, ml_gen_info::out) is det.
%-----------------------------------------------------------------------------%
%-----------------------------------------------------------------------------%
:- implementation.
:- import_module check_hlds.type_util.
:- import_module libs.globals.
:- import_module libs.options.
:- import_module ml_backend.ml_accurate_gc.
:- import_module ml_backend.ml_code_gen.
:- import_module ml_backend.ml_code_util.
:- import_module parse_tree.set_of_var.
:- import_module bool.
:- import_module map.
:- import_module maybe.
:- import_module set.
:- import_module string.
%-----------------------------------------------------------------------------%
%-----------------------------------------------------------------------------%
ml_gen_commit(Goal, CodeModel, Context, Decls, Statements, !Info) :-
Goal = hlds_goal(_, GoalInfo),
GoalCodeModel = goal_info_get_code_model(GoalInfo),
GoalContext = goal_info_get_context(GoalInfo),
(
GoalCodeModel = model_non,
CodeModel = model_semi
->
% model_non in semi context: (using try_commit/do_commit)
% <succeeded = Goal>
% ===>
% MR_bool succeeded;
% #ifdef NONDET_COPY_OUT
% <local var decls>
% #endif
% #ifdef PUT_COMMIT_IN_OWN_FUNC
% /*
% ** to avoid problems with setjmp() and non-volatile
% ** local variables, we need to put the call to
% ** setjmp() in its own nested function
% */
% void commit_func()
% {
% #endif
% MR_COMMIT_TYPE ref;
%
% void success() {
% MR_DO_COMMIT(ref);
% }
%
% MR_TRY_COMMIT(ref, {
% <Goal && success()>
% succeeded = MR_FALSE;
% }, {
% #ifdef NONDET_COPY_OUT
% <copy local vars to output args>
% #endif
% succeeded = MR_TRUE;
% })
% #ifdef PUT_COMMIT_IN_OWN_FUNC
%
% commit_func();
% #endif
ml_gen_maybe_make_locals_for_output_args(GoalInfo, LocalVarDecls,
CopyLocalsToOutputArgs, OrigVarLvalMap, !Info),
% Generate the `success()' function.
ml_gen_new_func_label(no, SuccessFuncLabel, SuccessFuncLabelRval,
!Info),
% push nesting level
MLDS_Context = mlds_make_context(Context),
ml_gen_info_new_aux_var_name("commit", CommitRef, !Info),
ml_gen_var_lval(!.Info, CommitRef, mlds_commit_type, CommitRefLval),
CommitRefDecl = ml_gen_commit_var_decl(MLDS_Context, CommitRef),
DoCommitStmt = ml_stmt_do_commit(ml_lval(CommitRefLval)),
DoCommitStatement = statement(DoCommitStmt, MLDS_Context),
% Pop nesting level.
ml_gen_nondet_label_func(!.Info, SuccessFuncLabel, Context,
DoCommitStatement, SuccessFunc),
ml_get_env_ptr(!.Info, EnvPtrRval),
SuccessCont = success_cont(SuccessFuncLabelRval, EnvPtrRval, [], []),
ml_gen_info_push_success_cont(SuccessCont, !Info),
ml_gen_goal(model_non, Goal, GoalDecls, GoalStatements, !Info),
GoalStatement = ml_gen_block(GoalDecls, GoalStatements, GoalContext),
ml_gen_info_pop_success_cont(!Info),
ml_gen_set_success(!.Info, ml_const(mlconst_false), Context,
SetSuccessFalse),
ml_gen_set_success(!.Info, ml_const(mlconst_true), Context,
SetSuccessTrue),
TryCommitStmt = ml_stmt_try_commit(CommitRefLval,
ml_gen_block([], [GoalStatement, SetSuccessFalse], Context),
ml_gen_block([], CopyLocalsToOutputArgs ++ [SetSuccessTrue],
Context)
),
TryCommitStatement = statement(TryCommitStmt, MLDS_Context),
CommitFuncLocalDecls = [CommitRefDecl, SuccessFunc],
maybe_put_commit_in_own_func(CommitFuncLocalDecls,
[TryCommitStatement], Context, CommitFuncDecls, Statements, !Info),
Decls = LocalVarDecls ++ CommitFuncDecls,
ml_gen_info_set_var_lvals(OrigVarLvalMap, !Info)
;
GoalCodeModel = model_non,
CodeModel = model_det
->
% model_non in det context: (using try_commit/do_commit)
% <do Goal>
% ===>
% #ifdef NONDET_COPY_OUT
% <local var decls>
% #endif
% #ifdef PUT_COMMIT_IN_NESTED_FUNC
% /*
% ** to avoid problems with setjmp() and non-volatile
% ** local variables, we need to put the call to
% ** setjmp() in its own nested functions
% */
% void commit_func()
% {
% #endif
% MR_COMMIT_TYPE ref;
% void success() {
% MR_DO_COMMIT(ref);
% }
% MR_TRY_COMMIT(ref, {
% <Goal && success()>
% }, {
% #ifdef NONDET_COPY_OUT
% <copy local vars to output args>
% #endif
% })
% #ifdef PUT_COMMIT_IN_NESTED_FUNC
%
% commit_func();
% #endif
ml_gen_maybe_make_locals_for_output_args(GoalInfo, LocalVarDecls,
CopyLocalsToOutputArgs, OrigVarLvalMap, !Info),
% Generate the `success()' function.
ml_gen_new_func_label(no, SuccessFuncLabel, SuccessFuncLabelRval,
!Info),
% push nesting level
MLDS_Context = mlds_make_context(Context),
ml_gen_info_new_aux_var_name("commit", CommitRef, !Info),
ml_gen_var_lval(!.Info, CommitRef, mlds_commit_type, CommitRefLval),
CommitRefDecl = ml_gen_commit_var_decl(MLDS_Context, CommitRef),
DoCommitStmt = ml_stmt_do_commit(ml_lval(CommitRefLval)),
DoCommitStatement = statement(DoCommitStmt, MLDS_Context),
% pop nesting level
ml_gen_nondet_label_func(!.Info, SuccessFuncLabel, Context,
DoCommitStatement, SuccessFunc),
ml_get_env_ptr(!.Info, EnvPtrRval),
SuccessCont = success_cont(SuccessFuncLabelRval, EnvPtrRval, [], []),
ml_gen_info_push_success_cont(SuccessCont, !Info),
ml_gen_goal(model_non, Goal, GoalDecls, GoalStatements, !Info),
% Hoist any static constant declarations for Goal out to the top level.
GoalStatement = ml_gen_block(GoalDecls, GoalStatements, GoalContext),
ml_gen_info_pop_success_cont(!Info),
TryCommitStmt = ml_stmt_try_commit(CommitRefLval, GoalStatement,
ml_gen_block([], CopyLocalsToOutputArgs, Context)),
TryCommitStatement = statement(TryCommitStmt, MLDS_Context),
CommitFuncLocalDecls = [CommitRefDecl, SuccessFunc],
maybe_put_commit_in_own_func(CommitFuncLocalDecls,
[TryCommitStatement], Context, CommitFuncDecls, Statements, !Info),
Decls = LocalVarDecls ++ CommitFuncDecls,
ml_gen_info_set_var_lvals(OrigVarLvalMap, !Info)
;
% No commit required.
ml_gen_goal(CodeModel, Goal, Decls, Statements, !Info)
).
% maybe_put_commit_in_own_func(Defns0, Stmts0, Defns, Stmts):
%
% If the --put-commit-in-own-func option is set, put the commit in its
% own function. This is needed for the high-level C back-end, to handle
% problems with setjmp()/longjmp() clobbering non-volatile local variables.
%
% Detailed explanation:
%
% For the high-level C back-end, we implement commits using
% setjmp()/longjmp(). Unfortunately for us, ANSI/ISO C says that longjmp()
% is allowed to clobber the values of any non-volatile local variables
% in the function that called setjmp() which have been modified between
% the setjmp() and the longjmp().
%
% To avoid this, whenever we generate a commit, we put it in its own
% nested function, with the local variables (e.g. `succeeded', plus any
% outputs from the goal that we're committing over) remaining in the
% containing function. This ensures that none of the variables which
% get modified between the setjmp() and the longjmp() and which get
% referenced after the longjmp() are local variables in the function
% containing the setjmp().
%
% [The obvious alternative of declaring the local variables in the function
% containing setjmp() as `volatile' doesn't work, since the assignments
% to those output variables may be deep in some function called indirectly
% from the goal that we're committing across, and assigning to a
% volatile-qualified variable via a non-volatile pointer is undefined
% behaviour. The only way to make it work would be to be to declare
% *every* output argument that we pass by reference as `volatile T *'.
% But that would impose distributed fat and would make interoperability
% difficult.]
%
:- pred maybe_put_commit_in_own_func(list(mlds_defn)::in, list(statement)::in,
prog_context::in, list(mlds_defn)::out, list(statement)::out,
ml_gen_info::in, ml_gen_info::out) is det.
maybe_put_commit_in_own_func(CommitFuncLocalDecls, TryCommitStatements,
Context, Decls, Statements, !Info) :-
ml_gen_info_put_commit_in_own_func(!.Info, PutCommitInOwnFunc),
(
PutCommitInOwnFunc = yes,
% Generate the `void commit_func() { ... }' wrapper
% around the main body that we generated above
ml_gen_new_func_label(no, CommitFuncLabel, CommitFuncLabelRval, !Info),
% push nesting level
CommitFuncBody = ml_gen_block(CommitFuncLocalDecls,
TryCommitStatements, Context),
% pop nesting level
ml_gen_nondet_label_func(!.Info, CommitFuncLabel, Context,
CommitFuncBody, CommitFunc),
% Generate the call to `commit_func();'
ml_gen_info_use_gcc_nested_functions(!.Info, UseNestedFuncs),
(
UseNestedFuncs = yes,
ArgRvals = [],
ArgTypes = []
;
UseNestedFuncs = no,
ml_get_env_ptr(!.Info, EnvPtrRval),
ArgRvals = [EnvPtrRval],
ArgTypes = [mlds_generic_env_ptr_type]
),
RetTypes = [],
Signature = mlds_func_signature(ArgTypes, RetTypes),
CallKind = ordinary_call,
CallStmt = ml_stmt_call(Signature, CommitFuncLabelRval, no, ArgRvals,
[], CallKind),
CallStatement = statement(CallStmt, mlds_make_context(Context)),
% Package it all up.
Statements = [CallStatement],
Decls = [CommitFunc]
;
PutCommitInOwnFunc = no,
Statements = TryCommitStatements,
Decls = CommitFuncLocalDecls
).
% In commits, you have model_non code called from a model_det or model_semi
% context. With --nondet-copy-out, when generating code for commits,
% if the context is a model_det or model_semi procedure with output
% arguments passed by reference, then we need to introduce local variables
% corresponding to those output arguments, and at the end of the commit
% we'll copy the local variables into the output arguments.
%
:- pred ml_gen_maybe_make_locals_for_output_args(hlds_goal_info::in,
list(mlds_defn)::out, list(statement)::out,
map(prog_var, mlds_lval)::out,
ml_gen_info::in, ml_gen_info::out) is det.
ml_gen_maybe_make_locals_for_output_args(GoalInfo, LocalVarDecls,
CopyLocalsToOutputArgs, OrigVarLvalMap, !Info) :-
ml_gen_info_get_var_lvals(!.Info, OrigVarLvalMap),
ml_gen_info_get_globals(!.Info, Globals),
globals.lookup_bool_option(Globals, nondet_copy_out, NondetCopyOut),
(
NondetCopyOut = yes,
Context = goal_info_get_context(GoalInfo),
NonLocals = goal_info_get_nonlocals(GoalInfo),
ml_gen_info_get_byref_output_vars(!.Info, ByRefOutputVars),
VarsToCopy = set_of_var.intersect(
set_of_var.list_to_set(ByRefOutputVars), NonLocals),
ml_gen_make_locals_for_output_args(
set_of_var.to_sorted_list(VarsToCopy),
Context, LocalVarDecls, CopyLocalsToOutputArgs, !Info)
;
NondetCopyOut = no,
LocalVarDecls = [],
CopyLocalsToOutputArgs = []
).
:- pred ml_gen_make_locals_for_output_args(list(prog_var)::in,
prog_context::in, list(mlds_defn)::out, list(statement)::out,
ml_gen_info::in, ml_gen_info::out) is det.
ml_gen_make_locals_for_output_args([], _, [], [], !Info).
ml_gen_make_locals_for_output_args([Var | Vars], Context,
LocalDefns, Assigns, !Info) :-
ml_gen_make_locals_for_output_args(Vars, Context, LocalDefns0, Assigns0,
!Info),
ml_gen_info_get_module_info(!.Info, ModuleInfo),
ml_variable_type(!.Info, Var, Type),
IsDummy = check_dummy_type(ModuleInfo, Type),
(
IsDummy = is_dummy_type,
LocalDefns = LocalDefns0,
Assigns = Assigns0
;
IsDummy = is_not_dummy_type,
ml_gen_make_local_for_output_arg(Var, Type, Context,
LocalDefn, Assign, !Info),
LocalDefns = [LocalDefn | LocalDefns0],
Assigns = [Assign | Assigns0]
).
:- pred ml_gen_make_local_for_output_arg(prog_var::in, mer_type::in,
prog_context::in, mlds_defn::out, statement::out,
ml_gen_info::in, ml_gen_info::out) is det.
ml_gen_make_local_for_output_arg(OutputVar, Type, Context,
LocalVarDefn, Assign, !Info) :-
% Look up the name of the output variable.
ml_gen_info_get_varset(!.Info, VarSet),
OutputVarName = ml_gen_var_name(VarSet, OutputVar),
% Generate a declaration for a corresponding local variable.
OutputVarName = mlds_var_name(OutputVarNameStr, MaybeNum),
LocalVarName = mlds_var_name(
string.append("local_", OutputVarNameStr), MaybeNum),
ml_gen_type(!.Info, Type, MLDS_Type),
ml_gen_gc_statement(LocalVarName, Type, Context, GCStatement,
!Info),
LocalVarDefn = ml_gen_mlds_var_decl(mlds_data_var(LocalVarName), MLDS_Type,
GCStatement, mlds_make_context(Context)),
% Generate code to assign from the local var to the output var.
ml_gen_var(!.Info, OutputVar, OutputVarLval),
ml_gen_var_lval(!.Info, LocalVarName, MLDS_Type, LocalVarLval),
Assign = ml_gen_assign(OutputVarLval, ml_lval(LocalVarLval), Context),
% Update the lval for this variable so that any references to it inside
% the commit refer to the local variable rather than to the output
% argument. (Note that we reset all the var lvals at the end of the
% commit.)
ml_gen_info_set_var_lval(OutputVar, LocalVarLval, !Info).
% Generate the declaration for the `commit' variable.
%
:- func ml_gen_commit_var_decl(mlds_context, mlds_var_name) = mlds_defn.
ml_gen_commit_var_decl(Context, VarName) =
ml_gen_mlds_var_decl(mlds_data_var(VarName), mlds_commit_type, gc_no_stmt,
Context).
%-----------------------------------------------------------------------------%
:- end_module ml_backend.ml_commit_gen.
%-----------------------------------------------------------------------------%