From patchwork Sun Nov 5 18:50:22 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Richard Sandiford X-Patchwork-Id: 161766 Return-Path: Delivered-To: ouuuleilei@gmail.com Received: by 2002:a59:8f47:0:b0:403:3b70:6f57 with SMTP id j7csp2261539vqu; Sun, 5 Nov 2023 10:50:49 -0800 (PST) X-Google-Smtp-Source: AGHT+IEZnWpDHntrEyzL6lCT2+fcKKI04aiCYyF5PglDhtIptKITcwYSCltNh1YWpQuKHK8OLWBP X-Received: by 2002:a05:620a:4094:b0:77a:739b:322e with SMTP id f20-20020a05620a409400b0077a739b322emr7088080qko.57.1699210249457; Sun, 05 Nov 2023 10:50:49 -0800 (PST) ARC-Seal: i=2; a=rsa-sha256; t=1699210249; cv=pass; d=google.com; s=arc-20160816; b=CDXYQzSYbe8Kb/r/B62+b31Izr9x1sy6SMTuq8xvYAJpJV7pOVugRLCdDnI0J2X5+H 1HIfr/l7xH80N6bpp4Wcd+vq84IycvuzoTIWirM3hcbV8kSEqrgZf7ArTdO9jXU80hjv zmIGwcCupvh+4qhsjkkgJT2bwb/yyYSs3wXcoDRHKQvwtXdlpxepaaFJwcB2B2PMA9Wf ED9zmiwJunVVP84jjWSnZpL4TvFNB7q1AXorusCpaDBGnZfc3AZ2Utz15aaDI+qgXUY4 XCPjtJLjEgrPHOzrOWDcGiluyvKyrWCiIpauZRkhafzdIxqbMBKriH3zIBAzFMEH/KSM /yVw== ARC-Message-Signature: i=2; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=errors-to:list-subscribe:list-help:list-post:list-archive :list-unsubscribe:list-id:precedence:mime-version:user-agent :message-id:in-reply-to:date:references:subject:cc:mail-followup-to :to:from:arc-filter:dmarc-filter:delivered-to; bh=ocVzcsPKHQiOmUoIIk42d2FBhr2TS8sPuqXi3ZAIuLQ=; fh=+zKahnQ9dI9rCMQA/vTUSn1PPyLwgfSULGOjxoIZkos=; b=Wn6Go11H2SoQWKGRYuQkliRVnIAaxUqX496o+BmkCLg+ic6gfbv+0CaWf8AorvdzIZ Ytb8+o0WMagvRoQH4dRKB/++dskHLY9MIYYl6N7UiL06oAqnJs2+7SvIFSYJbsl2n7I8 SdS0BKgB/kHoc8XnMwPVyCe+L5gGuPnvzTKGO4XiKE7+5axZkbx3A/ApIofOPYrGVoN9 CdcM76AjPQgiNten+Aohx8zjl9JhQUTGwhWBwrGszNShsEW1vo1dSvG9N+8bGRSEChc0 eQGD0dLJBwYNLBFkCIkqMx3rMnEx6StV9LJU4tOGWuG7f/TLmB0sdc2anGunjSwT/Czs htFg== ARC-Authentication-Results: i=2; mx.google.com; arc=pass (i=1); spf=pass (google.com: domain of gcc-patches-bounces+ouuuleilei=gmail.com@gcc.gnu.org designates 2620:52:3:1:0:246e:9693:128c as permitted sender) smtp.mailfrom="gcc-patches-bounces+ouuuleilei=gmail.com@gcc.gnu.org"; dmarc=fail (p=NONE sp=NONE dis=NONE) header.from=arm.com Received: from server2.sourceware.org (server2.sourceware.org. [2620:52:3:1:0:246e:9693:128c]) by mx.google.com with ESMTPS id o9-20020a05622a044900b00403a7a5820fsi4722087qtx.142.2023.11.05.10.50.49 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 05 Nov 2023 10:50:49 -0800 (PST) Received-SPF: pass (google.com: domain of gcc-patches-bounces+ouuuleilei=gmail.com@gcc.gnu.org designates 2620:52:3:1:0:246e:9693:128c as permitted sender) client-ip=2620:52:3:1:0:246e:9693:128c; Authentication-Results: mx.google.com; arc=pass (i=1); spf=pass (google.com: domain of gcc-patches-bounces+ouuuleilei=gmail.com@gcc.gnu.org designates 2620:52:3:1:0:246e:9693:128c as permitted sender) smtp.mailfrom="gcc-patches-bounces+ouuuleilei=gmail.com@gcc.gnu.org"; dmarc=fail (p=NONE sp=NONE dis=NONE) header.from=arm.com Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 3CDE03856DE2 for ; Sun, 5 Nov 2023 18:50:49 +0000 (GMT) X-Original-To: gcc-patches@gcc.gnu.org Delivered-To: gcc-patches@gcc.gnu.org Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by sourceware.org (Postfix) with ESMTP id 23F723857C40 for ; Sun, 5 Nov 2023 18:50:24 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 23F723857C40 Authentication-Results: sourceware.org; dmarc=pass (p=none dis=none) header.from=arm.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=arm.com ARC-Filter: OpenARC Filter v1.0.0 sourceware.org 23F723857C40 Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=217.140.110.172 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1699210226; cv=none; b=rJRsWSGYj+gm6UTZkEcjKYMK1Ziegp75LGxroF5jjDdsOBAkvP0n0QKGkyVcN8WBXkztryttrI7hrhLqqgmS48egxEQ6u/6ZHxbd1xXIBTioBGbiy8T3XVTUwCNrS6SB6n25v8d5tlhWq5YQEvHzPMHGOPXnUv7YcPeJCVt3YvQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1699210226; c=relaxed/simple; bh=VPa5snu5ILkX1NAlKuaAbKw8Y9k4rKt0iyVtX0T/OtI=; h=From:To:Subject:Date:Message-ID:MIME-Version; b=VAWiNkNjgo+QS39ErOxBlO8+Q5pxsoBFKckgPPoBs5Gauw8WsPxInZpVyXpiivSUHeQXreKZgZacgN2mN7BnDMUgOB77KZuqysKm/w9YxB/8kOJLk9Y053C7oRNNI04Hyh7nNH13tVmNu50ybOuSSvQjCRf4iD1DUiSe1QDHANs= ARC-Authentication-Results: i=1; server2.sourceware.org Received: from usa-sjc-imap-foss1.foss.arm.com (unknown [10.121.207.14]) by usa-sjc-mx-foss1.foss.arm.com (Postfix) with ESMTP id B621DC15; Sun, 5 Nov 2023 10:51:07 -0800 (PST) Received: from localhost (e121540-lin.manchester.arm.com [10.32.110.72]) by usa-sjc-imap-foss1.foss.arm.com (Postfix) with ESMTPSA id 57C373F703; Sun, 5 Nov 2023 10:50:23 -0800 (PST) From: Richard Sandiford To: gcc-patches@gcc.gnu.org Mail-Followup-To: gcc-patches@gcc.gnu.org, jlaw@ventanamicro.com, richard.sandiford@arm.com Cc: jlaw@ventanamicro.com Subject: [PATCH 12/12] mode-switching: Add a backprop hook References: Date: Sun, 05 Nov 2023 18:50:22 +0000 In-Reply-To: (Richard Sandiford's message of "Sun, 05 Nov 2023 18:45:29 +0000") Message-ID: User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/26.3 (gnu/linux) MIME-Version: 1.0 X-Spam-Status: No, score=-23.3 required=5.0 tests=BAYES_00, GIT_PATCH_0, KAM_DMARC_NONE, KAM_DMARC_STATUS, KAM_LAZY_DOMAIN_SECURITY, SPF_HELO_NONE, SPF_NONE, TXREP, T_SCC_BODY_TEXT_LINE autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on server2.sourceware.org X-BeenThere: gcc-patches@gcc.gnu.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Gcc-patches mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: gcc-patches-bounces+ouuuleilei=gmail.com@gcc.gnu.org X-getmail-retrieved-from-mailbox: INBOX X-GMAIL-THRID: 1781751086395577204 X-GMAIL-MSGID: 1781751086395577204 This patch adds a way for targets to ask that selected mode changes be brought forward, through a combination of: (1) requiring a mode in blocks where the entity was previously transparent (2) pushing the transition at the head of a block onto incomging edges SME has two uses for this: - A "one-shot" entity that, for any given path of execution, either stays off or makes exactly one transition from off to on. This relies only on (1) above; see the hook description for more info. The main purpose of using mode-switching for this entity is to shrink-wrap the code that requires it. - A second entity for which all transitions must be from known modes, which is enforced using a combination of (1) and (2). More specifically, (1) looks for edges B1->B2 for which: - B2 requires a specific mode and - B1 does not guarantee a specific starting mode In this system, such an edge is only possible if the entity is transparent in B1. (1) then forces B1 to require some safe common mode. Applying this inductively means that all incoming edges are from known modes. If different edges give different starting modes, (2) pushes the transitions onto the edges themselves; this only happens if the entity is not transparent in some predecessor block. The patch also uses the back-propagation as an excuse to do a simple on-the-fly optimisation. Hopefully the comments in the patch explain things a bit better. gcc/ * target.def (mode_switching.backprop): New hook. * doc/tm.texi.in (TARGET_MODE_BACKPROP): New @hook. * doc/tm.texi: Regenerate. * mode-switching.cc (struct bb_info): Add single_succ. (confluence_info): Add transp field. (single_succ_confluence_n, single_succ_transfer): New functions. (backprop_confluence_n, backprop_transfer): Likewise. (optimize_mode_switching): Use them. Push mode transitions onto a block's incoming edges, if the backprop hook requires it. --- gcc/doc/tm.texi | 28 +++++ gcc/doc/tm.texi.in | 2 + gcc/mode-switching.cc | 272 ++++++++++++++++++++++++++++++++++++++++++ gcc/target.def | 29 +++++ 4 files changed, 331 insertions(+) diff --git a/gcc/doc/tm.texi b/gcc/doc/tm.texi index cd346538fe2..d83ca73b1af 100644 --- a/gcc/doc/tm.texi +++ b/gcc/doc/tm.texi @@ -10456,6 +10456,34 @@ The hook should return the number of modes if no suitable mode exists for the given arguments. @end deftypefn +@deftypefn {Target Hook} int TARGET_MODE_BACKPROP (int @var{entity}, int @var{mode1}, int @var{mode2}) +If defined, the mode-switching pass uses this hook to back-propagate mode +requirements through blocks that have no mode requirements of their own. +Specifically, @var{mode1} is the mode that @var{entity} has on exit +from a block B1 (say) and @var{mode2} is the mode that the next block +requires @var{entity} to have. B1 does not have any mode requirements +of its own. + +The hook should return the mode that it prefers or requires @var{entity} +to have in B1, or the number of modes if there is no such requirement. +If the hook returns a required mode for more than one of B1's outgoing +edges, those modes are combined as for @code{TARGET_MODE_CONFLUENCE}. + +For example, suppose there is a ``one-shot'' entity that, +for a given execution of a function, either stays off or makes exactly +one transition from off to on. It is safe to make the transition at any +time, but it is better not to do so unnecessarily. This hook allows the +function to manage such an entity without having to track its state at +runtime. Specifically. the entity would have two modes, 0 for off and +1 for on, with 2 representing ``don't know''. The system is forbidden from +transitioning from 2 to 1, since 2 represents the possibility that the +entity is already on (and the aim is to avoid having to emit code to +check for that case). This hook would therefore return 1 when @var{mode1} +is 2 and @var{mode2} is 1, which would force the entity to be on in the +source block. Applying this inductively would remove all transitions +in which the previous state is unknown. +@end deftypefn + @deftypefn {Target Hook} int TARGET_MODE_ENTRY (int @var{entity}) If this hook is defined, it is evaluated for every @var{entity} that needs mode switching. It should return the mode that @var{entity} is diff --git a/gcc/doc/tm.texi.in b/gcc/doc/tm.texi.in index ae23241ea1c..3d3ae12cc2f 100644 --- a/gcc/doc/tm.texi.in +++ b/gcc/doc/tm.texi.in @@ -6977,6 +6977,8 @@ mode or ``no mode'', depending on context. @hook TARGET_MODE_CONFLUENCE +@hook TARGET_MODE_BACKPROP + @hook TARGET_MODE_ENTRY @hook TARGET_MODE_EXIT diff --git a/gcc/mode-switching.cc b/gcc/mode-switching.cc index 87b23d2c050..720c30df72d 100644 --- a/gcc/mode-switching.cc +++ b/gcc/mode-switching.cc @@ -81,6 +81,7 @@ struct bb_info int computing; int mode_out; int mode_in; + int single_succ; }; /* Clear ode I from entity J in bitmap B. */ @@ -509,6 +510,9 @@ struct /* Information about each basic block, indexed by block id. */ struct bb_info *bb_info; + /* A bitmap of blocks for which the current entity is transparent. */ + sbitmap transp; + /* The entity that we're processing. */ int entity; @@ -580,6 +584,210 @@ forward_transfer (int bb_index) return true; } +/* A backwards confluence function. Update the the bb_info single_succ + field for E's source block, based on changes to E's destination block. + At the end of the dataflow problem, single_succ is the single mode + that all successors require (directly or indirectly), or no_mode + if there are conflicting requirements. + + Initially, a value of no_mode + 1 means "don't know". */ + +static bool +single_succ_confluence_n (edge e) +{ + /* The entry block has no associated mode information. */ + if (e->src->index == ENTRY_BLOCK) + return false; + + /* We don't control mode changes across abnormal edges. */ + if (e->flags & EDGE_ABNORMAL) + return false; + + /* Do nothing if we've already found a conflict. */ + struct bb_info *bb_info = confluence_info.bb_info; + int no_mode = confluence_info.no_mode; + int src_mode = bb_info[e->src->index].single_succ; + if (src_mode == no_mode) + return false; + + /* Work out what mode the destination block (or its successors) require. */ + int dest_mode; + if (e->dest->index == EXIT_BLOCK) + dest_mode = no_mode; + else if (bitmap_bit_p (confluence_info.transp, e->dest->index)) + dest_mode = bb_info[e->dest->index].single_succ; + else + dest_mode = bb_info[e->dest->index].seginfo->mode; + + /* Do nothing if the destination block has no new information. */ + if (dest_mode == no_mode + 1 || dest_mode == src_mode) + return false; + + /* Detect conflicting modes. */ + if (src_mode != no_mode + 1) + dest_mode = no_mode; + + bb_info[e->src->index].single_succ = dest_mode; + return true; +} + +/* A backward transfer function for computing the bb_info single_succ + fields, as described above single_succ_confluence. */ + +static bool +single_succ_transfer (int bb_index) +{ + /* We don't have any field to transfer to. Assume that, after the + first iteration, we are only called if single_succ has changed. + We should then process incoming edges if the entity is transparent. */ + return bitmap_bit_p (confluence_info.transp, bb_index); +} + +/* Check whether the target wants to back-propagate a mode change across + edge E, and update the source block's computed mode if so. Return true + if something changed. */ + +static bool +backprop_confluence_n (edge e) +{ + /* The entry and exit blocks have no useful mode information. */ + if (e->src->index == ENTRY_BLOCK || e->dest->index == EXIT_BLOCK) + return false; + + /* We don't control mode changes across abnormal edges. */ + if (e->flags & EDGE_ABNORMAL) + return false; + + /* We can only require a new mode in the source block if the entity + was originally transparent there. */ + if (!bitmap_bit_p (confluence_info.transp, e->src->index)) + return false; + + /* Exit now if there is no required mode, or if all paths into the + source block leave the entity in the required mode. */ + struct bb_info *bb_info = confluence_info.bb_info; + int no_mode = confluence_info.no_mode; + int src_mode = bb_info[e->src->index].mode_out; + int dest_mode = bb_info[e->dest->index].mode_in; + if (dest_mode == no_mode || src_mode == dest_mode) + return false; + + /* See what the target thinks about this transition. */ + int entity = confluence_info.entity; + int new_mode = targetm.mode_switching.backprop (entity, src_mode, + dest_mode); + if (new_mode == no_mode) + return false; + + /* The target doesn't like the current transition, but would be happy + with a transition from NEW_MODE. + + If we force the source block to use NEW_MODE, we might introduce a + double transition on at least one path through the function (one to + NEW_MODE and then one to DEST_MODE). Therefore, if all destination + blocks require the same mode, it is usually better to bring that + mode requirement forward. + + If that isn't possible, merge the preference for this edge with + the preferences for other edges. no_mode + 1 indicates that there + was no previous preference. */ + int old_mode = bb_info[e->src->index].computing; + if (bb_info[e->src->index].single_succ != no_mode) + new_mode = bb_info[e->src->index].single_succ; + else if (old_mode != no_mode + 1) + new_mode = mode_confluence (entity, old_mode, new_mode, no_mode); + + if (old_mode == new_mode) + return false; + + bb_info[e->src->index].computing = new_mode; + return true; +} + +/* If the current entity was originally transparent in block BB_INDEX, + update the incoming mode to match the outgoing mode. Register a mode + change if the entity is no longer transparent. + + Also, as an on-the-fly optimization, check whether the entity was + originally transparent in BB_INDEX and if all successor blocks require + the same mode. If so, anticipate the mode change in BB_INDEX if + doing it on the incoming edges would require no more mode changes than + doing it on the outgoing edges. The aim is to reduce the total number + of mode changes emitted for the function (and thus reduce code size and + cfg complexity) without increasing the number of mode changes on any + given path through the function. A typical case where it helps is: + + T + / \ + T M + \ / + M + + where the entity is transparent in the T blocks and is required to have + mode M in the M blocks. If there are no redundancies leading up to this, + there will be two mutually-exclusive changes to mode M, one on each of + the T->M edges. The optimization instead converts it to: + + T T M + / \ / \ / \ + T M -> M M -> M M + \ / \ / \ / + M M M + + which creates a single transition to M for both paths through the diamond. + + Return true if something changed. */ + +static bool +backprop_transfer (int bb_index) +{ + /* The entry and exit blocks have no useful mode information. */ + if (bb_index == ENTRY_BLOCK || bb_index == EXIT_BLOCK) + return false; + + /* We can only require a new mode if the entity was previously + transparent. */ + if (!bitmap_bit_p (confluence_info.transp, bb_index)) + return false; + + struct bb_info *bb_info = confluence_info.bb_info; + basic_block bb = BASIC_BLOCK_FOR_FN (cfun, bb_index); + int no_mode = confluence_info.no_mode; + int mode_in = bb_info[bb_index].mode_in; + int mode_out = bb_info[bb_index].computing; + if (mode_out == no_mode + 1) + { + /* The entity is still transparent for this block. See whether + all successor blocks need the same mode, either directly or + indirectly. */ + mode_out = bb_info[bb_index].single_succ; + if (mode_out == no_mode) + return false; + + /* Get a minimum bound on the number of transitions that would be + removed if BB itself required MODE_OUT. */ + unsigned int moved = 0; + for (edge e : bb->succs) + if (e->dest->index != EXIT_BLOCK + && mode_out == bb_info[e->dest->index].seginfo->mode) + moved += 1; + + /* See whether making the mode change on all incoming edges would + be no worse than making it on MOVED outgoing edges. */ + if (moved < EDGE_COUNT (bb->preds)) + return false; + + bb_info[bb_index].mode_out = mode_out; + bb_info[bb_index].computing = mode_out; + } + else if (mode_out == mode_in) + return false; + + bb_info[bb_index].mode_in = mode_out; + bb_info[bb_index].seginfo->mode = mode_out; + return true; +} + /* Find all insns that need a particular mode setting, and insert the necessary mode switches. Return true if we did work. */ @@ -685,6 +893,7 @@ optimize_mode_switching (void) } confluence_info.bb_info = info; + confluence_info.transp = nullptr; confluence_info.entity = entity; confluence_info.no_mode = no_mode; @@ -818,6 +1027,53 @@ optimize_mode_switching (void) } } + /* If the target requests it, back-propagate selected mode requirements + through transparent blocks. */ + if (targetm.mode_switching.backprop) + { + /* First work out the mode on entry to and exit from each block. */ + forwprop_mode_info (info, e, no_mode); + + /* Compute the single_succ fields, as described above + single_succ_confluence. */ + FOR_EACH_BB_FN (bb, cfun) + info[bb->index].single_succ = no_mode + 1; + + confluence_info.transp = transp_all; + bitmap_set_range (blocks, 0, last_basic_block_for_fn (cfun)); + df_simple_dataflow (DF_BACKWARD, NULL, NULL, + single_succ_confluence_n, + single_succ_transfer, blocks, + df_get_postorder (DF_BACKWARD), + df_get_n_blocks (DF_BACKWARD)); + + FOR_EACH_BB_FN (bb, cfun) + { + /* Repurpose mode_in as the first mode required by the block, + or the output mode if none. */ + if (info[bb->index].seginfo->mode != no_mode) + info[bb->index].mode_in = info[bb->index].seginfo->mode; + + /* In transparent blocks, use computing == no_mode + 1 + to indicate that no propagation has taken place. */ + if (info[bb->index].computing == no_mode) + info[bb->index].computing = no_mode + 1; + } + + bitmap_set_range (blocks, 0, last_basic_block_for_fn (cfun)); + df_simple_dataflow (DF_BACKWARD, NULL, NULL, backprop_confluence_n, + backprop_transfer, blocks, + df_get_postorder (DF_BACKWARD), + df_get_n_blocks (DF_BACKWARD)); + + /* Any block that now computes a mode is no longer transparent. */ + FOR_EACH_BB_FN (bb, cfun) + if (info[bb->index].computing == no_mode + 1) + info[bb->index].computing = no_mode; + else if (info[bb->index].computing != no_mode) + bitmap_clear_bit (transp_all, bb->index); + } + /* Set the anticipatable and computing arrays. */ for (i = 0; i < no_mode; i++) { @@ -899,6 +1155,22 @@ optimize_mode_switching (void) for (i = 0; i < no_mode; i++) if (mode_bit_p (del[bb->index], j, i)) info[bb->index].seginfo->mode = no_mode; + + /* See whether the target can perform the first transition. + If not, push it onto the incoming edges. The earlier backprop + pass should ensure that the resulting transitions are valid. */ + if (targetm.mode_switching.backprop) + { + int from_mode = info[bb->index].mode_in; + int to_mode = info[bb->index].seginfo->mode; + if (targetm.mode_switching.backprop (entity_map[j], from_mode, + to_mode) != no_mode) + { + for (edge e : bb->preds) + e->aux = (void *) (intptr_t) (to_mode + 1); + info[bb->index].mode_in = to_mode; + } + } } /* Now output the remaining mode sets in all the segments. */ diff --git a/gcc/target.def b/gcc/target.def index b08ede692f1..0996da0f71a 100644 --- a/gcc/target.def +++ b/gcc/target.def @@ -7070,6 +7070,35 @@ The hook should return the number of modes if no suitable mode exists\n\ for the given arguments.", int, (int entity, int mode1, int mode2), NULL) +DEFHOOK +(backprop, + "If defined, the mode-switching pass uses this hook to back-propagate mode\n\ +requirements through blocks that have no mode requirements of their own.\n\ +Specifically, @var{mode1} is the mode that @var{entity} has on exit\n\ +from a block B1 (say) and @var{mode2} is the mode that the next block\n\ +requires @var{entity} to have. B1 does not have any mode requirements\n\ +of its own.\n\ +\n\ +The hook should return the mode that it prefers or requires @var{entity}\n\ +to have in B1, or the number of modes if there is no such requirement.\n\ +If the hook returns a required mode for more than one of B1's outgoing\n\ +edges, those modes are combined as for @code{TARGET_MODE_CONFLUENCE}.\n\ +\n\ +For example, suppose there is a ``one-shot'' entity that,\n\ +for a given execution of a function, either stays off or makes exactly\n\ +one transition from off to on. It is safe to make the transition at any\n\ +time, but it is better not to do so unnecessarily. This hook allows the\n\ +function to manage such an entity without having to track its state at\n\ +runtime. Specifically. the entity would have two modes, 0 for off and\n\ +1 for on, with 2 representing ``don't know''. The system is forbidden from\n\ +transitioning from 2 to 1, since 2 represents the possibility that the\n\ +entity is already on (and the aim is to avoid having to emit code to\n\ +check for that case). This hook would therefore return 1 when @var{mode1}\n\ +is 2 and @var{mode2} is 1, which would force the entity to be on in the\n\ +source block. Applying this inductively would remove all transitions\n\ +in which the previous state is unknown.", + int, (int entity, int mode1, int mode2), NULL) + DEFHOOK (entry, "If this hook is defined, it is evaluated for every @var{entity} that\n\