%---------------------------------------------------------------------------% % vim: ft=mercury ts=4 sw=4 et %---------------------------------------------------------------------------% % Copyright (C) 2007-2011 The University of Melbourne. % Copyright (C) 2014-2018, 2020-2021 The Mercury team. % This file is distributed under the terms specified in COPYING.LIB. %---------------------------------------------------------------------------% % % File: backjump.m % Author: Mark Brown % Stability: medium % % This module defines the Mercury interface for backjumping. % % An application can use this module to add backjumping to their search % algorithms in the following way: % % - At points in the search tree where you wish to backjump *to*, add a % call to get_choice_id/1. % % - When in the search tree you discover that there are no further solutions % between the current execution point and the failure port of an earlier % call to get_choice_id/1, call backjump/1 with the relevant choice_id. % This takes execution immediately to that failure port. % % It is important to avoid backjumping to a choicepoint that has already % been pruned away, either on failure or due to a commit. In the former case % (which can occur if the choice_id is stored in a non-trailed mutable, for % example) the implementation throws an exception. In the latter case, % behaviour is undefined but most likely will be a seg fault. This can occur % if multi/nondet code that returns a choice_id is called from a % cc_multi/cc_nondet context, for example. % % Note that the definition of "solution" in the above means solution with % respect to the search algorithm, and doesn't necessarily mean solution of % the immediate parent or any particular ancestor. % %---------------------------------------------------------------------------% %---------------------------------------------------------------------------% :- module backjump. :- interface. %---------------------------------------------------------------------------% % Abstract type representing points in the search tree which can be % backjumped to. % :- type choice_id. % Returns a single unused choice_id, then fails. We make this nondet % rather than det, however, so that: % a) it leaves a nondet stack frame behind which can later be used % by backjump/1, and % b) the calling context is forced to deal with the case of no % solutions, which may occur even if later conjuncts can never fail % (since they may call backjump/1, which counts as an exceptional % rather than a logical answer). % :- impure pred get_choice_id(choice_id::out) is nondet. % Backjump to the point where the choice_id was created. That is, % jump immediately to the FAIL event of the corresponding call to % get_choice_id. % :- impure pred backjump(choice_id::in) is erroneous. %---------------------------------------------------------------------------% %---------------------------------------------------------------------------% :- implementation. %---------------------------------------------------------------------------% :- interface. % Debugging support. % :- func to_int(choice_id) = int. %---------------------------------------------------------------------------% :- implementation. :- import_module io. :- import_module list. :- import_module string. :- type choice_id == int. get_choice_id(Id) :- impure builtin_choice_id(Id). backjump(Id) :- impure builtin_backjump(Id). %---------------------------------------------------------------------------% :- impure pred builtin_choice_id(choice_id::out) is nondet. :- pragma terminates(pred(builtin_choice_id/1)). :- impure pred builtin_backjump(choice_id::in) is erroneous. :- pragma terminates(pred(builtin_backjump/1)). % builtin_choice_id and builtin_backjump are implemented below using % hand-coded low-level C code. % % IMPORTANT: any changes or additions to external predicates should be % reflected in the definition of pred_is_external in % mdbcomp/program_representation.m. The debugger needs to know what predicates % are defined externally, so that it knows not to expect events for those % predicates. :- pragma external_pred(builtin_choice_id/1). :- pragma external_pred(builtin_backjump/1). %---------------------------------------------------------------------------% :- pragma foreign_decl("C", "#include \"mercury_backjump.h\""). %---------------------------------------------------------------------------% % % The --high-level-code implementation. % :- pragma foreign_decl("C", " // protect against multiple inclusion #ifndef ML_BACKJUMP_GUARD #define ML_BACKJUMP_GUARD #ifdef MR_HIGHLEVEL_CODE #include #define MR_CONT_PARAMS MR_Cont cont, void *cont_env #define MR_CONT_PARAM_TYPES MR_Cont, void * #define MR_CONT_ARGS cont, cont_env #define MR_CONT_CALL() cont(cont_env) void MR_CALL mercury__backjump__builtin_choice_id_1_p_0( MR_BackJumpChoiceId *id, MR_CONT_PARAMS); void MR_CALL mercury__backjump__builtin_backjump_1_p_0(MR_BackJumpChoiceId id); #endif // MR_HIGHLEVEL_CODE #endif // ML_BACKJUMP_GUARD "). :- pragma foreign_code("C", " #ifdef MR_HIGHLEVEL_CODE #ifdef MR_NATIVE_GC // XXX code is needed to trace the local variables // in the builtin_choice_id predicate for accurate GC. #endif // MR_NATIVE_GC void MR_CALL mercury__backjump__builtin_choice_id_1_p_0(MR_Integer *id, MR_CONT_PARAMS) { MR_BackJumpHandler this_handler; this_handler.MR_bjh_prev = MR_GET_BACKJUMP_HANDLER(); this_handler.MR_bjh_id = MR_GET_NEXT_CHOICE_ID(); MR_SET_BACKJUMP_HANDLER(&this_handler); if (setjmp(this_handler.MR_bjh_handler) == 0) { #ifdef MR_DEBUG_JMPBUFS fprintf(stderr, ""choice setjmp %p\\n"", this_handler.MR_bjh_handler); #endif *id = this_handler.MR_bjh_id; MR_CONT_CALL(); } else { #ifdef MR_DEBUG_JMPBUFS fprintf(stderr, ""choice caught jmp %p\\n"", this_handler.MR_bjh_handler); #endif } MR_SET_BACKJUMP_HANDLER(this_handler.MR_bjh_prev); } void MR_CALL mercury__backjump__builtin_backjump_1_p_0(MR_BackJumpChoiceId id) { MR_BackJumpHandler *backjump_handler; backjump_handler = MR_GET_BACKJUMP_HANDLER(); // XXX when we commit and prune away nondet stack frames, we leave the // backjump handlers on the stack. If the caller tries to backjump to // a frame that has been pruned away, the handler may still be on the // stack and we won't detect the problem. // // We could avoid this problem by adding a trailing function which // prunes back the handler stack on a commit, which would mean that in // this case we will reach the bottom of the stack and call // ML_report_invalid_backjump rather than seg faulting. But that would // require trailing to be always available. Instead, we just rely on // the caller only backjumping to frames that actually do exist. // // (The same problem would occur if the caller tries to backjump to a // frame that has already failed. In this case, though, the choice ID // will also no longer be live since the call to get_choice_id would have // been backtracked over, so this isn't as much of a problem as with // commits.) while (backjump_handler != NULL) { if (backjump_handler->MR_bjh_id == id) { break; } backjump_handler = backjump_handler->MR_bjh_prev; } if (backjump_handler == NULL) { ML_report_invalid_backjump(id); exit(EXIT_FAILURE); } else { #ifdef MR_DEBUG_JMPBUFS fprintf(stderr, ""backjump longjmp %p\\n"", backjump_handler->MR_bjh_handler); #endif longjmp(backjump_handler->MR_bjh_handler, 1); } } #endif // MR_HIGHLEVEL_CODE "). %---------------------------------------------------------------------------% % % The --no-high-level-code implementation. % :- pragma foreign_decl("C", " #ifndef MR_HIGHLEVEL_CODE #include ""mercury_stacks.h"" #include ""mercury_stack_trace.h"" #include ""mercury_trace_base.h"" #include ""mercury_layout_util.h"" #include ""mercury_deep_profiling_hand.h"" #endif // !MR_HIGHLEVEL_CODE "). :- pragma foreign_code("C", " void mercury_sys_init_backjumps_init(void); void mercury_sys_init_backjumps_init_type_tables(void); #ifdef MR_DEEP_PROFILING void mercury_sys_init_backjumps_write_out_proc_statics(FILE *deep_fp, FILE *procrep_fp); #endif #ifndef MR_HIGHLEVEL_CODE #define ML_DUMMY_LINE 0 #define ML_BACKJUMP_STRUCT \ (((MR_BackJumpHandler *) (MR_curfr + 1 - MR_NONDET_FIXED_SIZE)) - 1) #ifdef ML_DEBUG_BACKJUMPS #define ML_BACKJUMP_CHECKPOINT(s, p) \ do { \ fflush(stdout); \ fprintf(stderr, ""backjumps (%s): "" \ ""loc %p, prev %p, id %d, sp %p, fr %p\\n"", \ s, p, p->MR_bjh_prev, p->MR_bjh_id, p->MR_bjh_saved_sp, \ p->MR_bjh_saved_fr); \ } while (0) #else #define ML_BACKJUMP_CHECKPOINT(s, p) #endif MR_define_extern_entry(mercury__backjump__builtin_choice_id_1_0); MR_define_extern_entry(mercury__backjump__builtin_backjump_1_0); MR_declare_label(mercury__backjump__builtin_choice_id_1_0_i1); MR_proc_static_user_no_site(backjump, builtin_choice_id, 1, 0, ""backjump.m"", ML_DUMMY_LINE, MR_TRUE); MR_proc_static_user_no_site(backjump, builtin_backjump, 1, 0, ""backjump.m"", ML_DUMMY_LINE, MR_TRUE); MR_EXTERN_USER_PROC_STATIC_PROC_LAYOUT(MR_DETISM_NON, MR_PROC_NO_SLOT_COUNT, -1, MR_PREDICATE, backjump, builtin_choice_id, 1, 0); MR_EXTERN_USER_PROC_STATIC_PROC_LAYOUT(MR_DETISM_DET, 1, MR_LONG_LVAL_STACKVAR_INT(1), MR_PREDICATE, backjump, builtin_backjump, 1, 0); MR_MAKE_USER_INTERNAL_LAYOUT(backjump, builtin_choice_id, 1, 0, 1); MR_BEGIN_MODULE(hand_written_backjump_module) MR_init_entry_sl(mercury__backjump__builtin_choice_id_1_0); MR_init_entry_sl(mercury__backjump__builtin_backjump_1_0); MR_init_label_sl(mercury__backjump__builtin_choice_id_1_0_i1); MR_BEGIN_CODE MR_define_entry(mercury__backjump__builtin_choice_id_1_0); { MR_mkpragmaframe(""builtin_choice_id/1"", 0, MR_BackJumpHandler_Struct, MR_LABEL(mercury__backjump__builtin_choice_id_1_0_i1)); #if defined(MR_DEEP_PROFILING) MR_fatal_error( ""builtin_choice_id: NYI backjumping and deep profiling""); #endif ML_BACKJUMP_STRUCT->MR_bjh_prev = MR_GET_BACKJUMP_HANDLER(); ML_BACKJUMP_STRUCT->MR_bjh_id = MR_GET_NEXT_CHOICE_ID(); ML_BACKJUMP_STRUCT->MR_bjh_saved_sp = MR_sp; ML_BACKJUMP_STRUCT->MR_bjh_saved_fr = MR_curfr; MR_SET_BACKJUMP_HANDLER(ML_BACKJUMP_STRUCT); ML_BACKJUMP_CHECKPOINT(""create"", ML_BACKJUMP_STRUCT); MR_r1 = (MR_Word) ML_BACKJUMP_STRUCT->MR_bjh_id; MR_succeed(); } MR_define_label(mercury__backjump__builtin_choice_id_1_0_i1); { // Restore the previous handler. MR_SET_BACKJUMP_HANDLER(ML_BACKJUMP_STRUCT->MR_bjh_prev); MR_fail(); } MR_define_entry(mercury__backjump__builtin_backjump_1_0); { MR_BackJumpChoiceId id = MR_r1; MR_BackJumpHandler *backjump_handler; backjump_handler = MR_GET_BACKJUMP_HANDLER(); #if defined(MR_DEEP_PROFILING) MR_fatal_error( ""builtin_backjump: NYI backjumping and deep profiling""); #endif // XXX see comments in the high-level implementation. while (backjump_handler != NULL) { if (backjump_handler->MR_bjh_id == id) { break; } ML_BACKJUMP_CHECKPOINT(""scan"", backjump_handler); backjump_handler = backjump_handler->MR_bjh_prev; } if (backjump_handler == NULL) { ML_report_invalid_backjump(id); exit(EXIT_FAILURE); } else { ML_BACKJUMP_CHECKPOINT(""found"", backjump_handler); // XXX we should produce some EXCP trace events here, to give // the user an opportunity to retry a goal that calculated a // (possibly incorrect) backjump. MR_SET_BACKJUMP_HANDLER(backjump_handler->MR_bjh_prev); MR_sp_word = (MR_Word) backjump_handler->MR_bjh_saved_sp; MR_maxfr_word = (MR_Word) backjump_handler->MR_bjh_saved_fr; MR_fail(); } } MR_END_MODULE #endif // !MR_HIGHLEVEL_CODE /* INIT mercury_sys_init_backjumps */ void mercury_sys_init_backjumps_init(void) { #ifndef MR_HIGHLEVEL_CODE hand_written_backjump_module(); #endif } void mercury_sys_init_backjumps_init_type_tables(void) { // no types to register } #ifdef MR_DEEP_PROFILING void mercury_sys_init_backjumps_write_out_proc_statics(FILE *deep_fp, FILE *procrep_fp) { MR_write_out_user_proc_static(deep_fp, procrep_fp, &MR_proc_layout_user_name(backjump, builtin_choice_id, 1, 0)); MR_write_out_user_proc_static(deep_fp, procrep_fp, &MR_proc_layout_user_name(backjump, builtin_backjump, 1, 0)); } #endif // MR_DEEP_PROFILING "). %---------------------------------------------------------------------------% :- pragma foreign_code("C#", " public static void builtin_choice_id_1_p_0(object cont, /* env_ptr */ object cont_env_ptr) { throw new System.Exception(""builtin_choice_id/1 not implemented""); } public static void builtin_backjump_1_p_0(int Id_2) { throw new System.Exception(""builtin_backjump/1 not implemented""); } "). %---------------------------------------------------------------------------% :- pragma foreign_code("Java", " public static void builtin_choice_id_1_p_0(jmercury.runtime.MethodPtr cont, /* env_ptr */ java.lang.Object cont_env_ptr) { throw new java.lang.Error(""builtin_choice_id/1 not implemented""); } public static void builtin_backjump_1_p_0(int Id_2) { throw new java.lang.Error(""builtin_backjump/1 not implemented""); } "). %---------------------------------------------------------------------------% :- pragma foreign_export("C", report_invalid_backjump(in, di, uo), "ML_report_invalid_backjump"). :- pred report_invalid_backjump(int::in, io::di, io::uo) is det. report_invalid_backjump(Id, !IO) :- io.output_stream(CurOutStream, !IO), io.flush_output(CurOutStream, !IO), io.stderr_stream(StdErrStream, !IO), io.format(StdErrStream, "Uncaught Mercury backjump (%d)\n", [i(Id)], !IO), io.flush_output(StdErrStream, !IO). %---------------------------------------------------------------------------% to_int(P) = P. %---------------------------------------------------------------------------% :- end_module backjump. %---------------------------------------------------------------------------%