GCC后端及汇编发布(12)

5.3. define_split 产生代码

对于我们的例子,分解后的模式,同样有一个如下的 gen_split 模式。

t33

33 genouput - define_insn_and_split 模式的例子 – split 部分

 

929  static void

930  gen_split (rtx split, int lineno)                                                              in genoutput.c

931  {

932    struct data *d = xmalloc (sizeof (struct data));

933    int i;

934 

935    d->code_number = next_code_number ;

936    d->index_number = next_index_number ;

937    d->lineno = lineno;

938    d->name = 0;

939 

940    /* Build up the list in the same order as the insns are seen

941      i n the machine description.  */

942    d->next = 0;

943    *idata_end = d;

944    idata_end = &d->next;

945 

946    max_opno = -1;

947    num_dups = 0;

948    memset (d->operand, 0, sizeof (d->operand));

949 

950    /* Get the number of operands by scanning all the patterns of the

951      split patterns. But ignore all the rest of the information thus

952      obtained.  */

953    for (i = 0; i < XVECLEN (split, 0); i++)

954      scan_operands (d, XVECEXP (split, 0, i), 0, 0);

955 

956    d->n_operands = max_opno + 1;

957    d->n_dups = 0;

958    d->n_alternatives = 0;

959    d->template = 0;

960    d->output_format = INSN_OUTPUT_FORMAT_NONE;

961 

962    place_operands (d);

963  }

 

对于 define_split 模式,虽然它具有约束,现在这里没有检查,正如 gccinfo 所提及的那样。没有任何检查,操作数将被放入由 odata 管理的链表。

 

main (continued)

 

1006   printf("/n/n");

1007   output_operand_data ();

1008   output_insn_data ();

1009   output_get_insn_name ();

1010

1011   fflush (stdout);

1012   return (ferror (stdout) != 0 || have_error

1013        ? FATAL_EXIT_CODE : SUCCESS_EXIT_CODE);

1014 }

5.4. define_peephole 产生代码

5.4.1. 概览 关于 define_peephole

在编译过程中,组合器( combiner )不会注意到某些窥孔优化,如果程序中的数据流没有启发它应该做这个尝试。例如,有时两条相邻的,目的一致的指令可以被合并,即便第二条看起来不使用一个保存第一条计算结果的寄存器。一个机器特定的窥孔优化器可以检测出这样的机会。

有两种窥孔定义可能被使用。原始的 define_peephole 运行在汇编输出时刻,来匹配指令并替换汇编代码。 define_peephole 的使用已经过时。

一个更新的 define_peephole2 匹配指令,并替换为新指令。 peephole2 遍,在寄存器分配之后,但指令调度之前运行,它可能会,为执行指令调度的目标,产生好得多的代码。

一个定义看起来就像这样:

     (define_peephole

       [INSN-PATTERN-1

        INSN-PATTERN-2

       ...]

       "CONDITION"

       "TEMPLATE"

       "OPTIONAL-INSN-ATTRIBUTES")

最后的字符串操作数可能被忽略 如果在这个机器描述中 你不使用任何机器特定的信息。如果出现,它必须遵循在 define_insn 中相同的规则。

在这个概要中, INSN-PATTERN-1 ,诸如此类的,是匹配连续指令的模式。当 INSN-PATTERN-1 匹配第一条指令, INSN-PATTERN-2 匹配第二条指令,依此类推时,这个优化应用于这个指令序列。

每个被一个窥孔匹配的指令必须也要匹配一个 define_insn 。窥孔只在代码产生之前的最后阶段被检查,并且是可选的。因此,任意匹配一个窥孔但不匹配 define_insn 的指令将导致,一个非优化编译在代码产生阶段崩溃,或在不同的优化阶段崩溃。

指令的操作数,如常,由 match_operands match_operator match_dup 来匹配。不寻常的是,操作数的数目应用到定义中所有的指令模式里。因此,你可以在两条指令中,通过使用在一条指令中使用 match_operand ,在另一条中使用 match_dup 来检查同一个的操作数。

用在 match_operand 模式中的操作数约束,对窥孔的应用,没有直接的影响,但是它们将在后面进行有效性检查,因此确认你的约束足够通用于窥孔匹配。如果窥孔匹配了,但约束不能满足,编译器将崩溃。

忽略窥孔所有操作数的约束是安全的;或者你可以写出,作为之前测试准则的二次检查的约束。

一旦一个指令序列匹配了这些模式, CONDITION 将被检查。这是一个 C 表达式,它进行最后的判断,是否执行这个优化(如果这个表达式不是 0 ,我们执行优化)。如果 CONDITION 被忽略(换而言之,这个字符串是空的),那么优化被应用到每个匹配这些模式的指令序列。

所定义的窥孔优化在寄存器分配之后应用。因此,仅通过查看这个操作数,窥孔定义可以检查操作数最终在哪个类别的寄存器中。

引用在 CONDITION 中的操作数的方式是,对于操作数 I ,使用 operands[I] (由 (match_operand I ...) 匹配)。使用变量 insn 引用最后一个被匹配的指令;使用 prev_active_insn 找出前面的指令。

当优化使用中间结果的计算时,你可以使,仅当这个中间结果不在别处使用时, CONDITION 得到匹配。 使用 C 表达式 dead_or_set_p (INSN, OP) ,其中 INSN 是,你所期望的,上一次这个值所被使用的指令(从 insn 值,连同使用 prev_nonnote_insn ),而 OP 是这个中间值(从 operands[I] )。

应用这个优化意味着用一个新指令替换这个指令序列。 TEMPLATE 控制着这个合并指令的汇编代码的最终输出。它就像 define_insn 的模板那样工作。在这个模板中操作数的数目,与用在匹配原始指令序列的操作数数目相同。

一个被定义的窥孔优化器的结果不需要匹配,在这个机器描述中的,任一个指令模式;它甚至没有机会去匹配它们。这个窥孔优化器定义本身被用作指令模式,来控制如何输出这个指令。

所定义的窥孔优化器被运行做将要输出的汇编代码,因此它们产生的指令不会以任何方式合并或重排。

这里是一个例子,取自 68000 的机器描述。

     (define_peephole

       [(set (reg:SI 15) (plus:SI (reg:SI 15) (const_int 4)))

        (set (match_operand:DF 0 "register_operand" "=f")

           (match_operand:DF 1 "register_operand" "ad"))]

       "FP_REG_P (operands[0]) && ! FP_REG_P (operands[1])"

     {

        rtx xoperands[2];

       xoperands[1] = gen_rtx (REG, SImode, REGNO (operands[1]) + 1);

     #ifdef MOTOROLA

       output_asm_insn ("move.l %1,(sp)", xoperands);

       output_asm_insn ("move.l %1,-(sp)", operands);

       return "fmove.d (sp)+,%0";

      #else

       output_asm_insn ("movel %1,sp@", xoperands);

       output_asm_insn ("movel %1,sp@-", operands);

       return "fmoved sp@+,%0";

     #endif

    })

   这个优化的效果是改变

     jbsr _foobar

     addql #4,sp

     movel d1,sp@-

     movel d0,sp@-

     fmoved sp@+,fp0

     jbsr _foobar

     movel d1,sp@

     movel d0,sp@-

     fmoved sp@+,fp0

INSN-PATTERN-1 等等看起来就像 define_insn 的第二个操作数。这儿有一个重要的区别: define_insn 的第二个操作数包含一个或多个封闭在方括号里的 RTX 对象。通常,只有一个:那么相同的行为可以被写作一个 define_peephole 的一个元素。不过当在一个 define_insn 中有多个行为时,它们被隐含地包括在一个 parallel 中。那么在这个 define_peephole 里,你必须显式地写出这个 parallel ,及其中的方括号。这样如果一个指令模式看起来像这样,

     (define_insn "divmodsi4"

       [(set (match_operand:SI 0 "general_operand" "=d")

           (div:SI (match_operand:SI 1 "general_operand" "0")

                 (match_operand:SI 2 "general_operand" "dmsK")))

         (set (match_operand:SI 3 "general_operand" "=d")

           (mod:SI (match_dup 1) (match_dup 2)))]

       "TARGET_68020"

       "divsl%.l %2,%3:%0")

那么在一个 peephole 中如下提及这个指令:

     (define_peephole

       [...

        (parallel

         [(set (match_operand:SI 0 "general_operand" "=d")

             (div:SI (match_operand:SI 1 "general_operand" "0")

                   (match_operand:SI 2 "general_operand" "dmsK")))

          (set (match_operand:SI 3 "general_operand" "=d")

             (mod:SI (match_dup 1) (match_dup 2)))])

       ...]

      ...)

这里的处理也是非常类似:把模式中的操作数提取出来,生成唯一实例保存在 odata 中。

 

848  static void

849  gen_peephole (rtx peep, int lineno)                                                       in genoutput.c

850  {

851    struct data *d = xmalloc (sizeof (struct data));

852    int i;

853 

854    d->code_number = next_code_number ;

855    d->index_number = next_index_number ;

856    d->lineno = lineno;

857    d->name = 0;

858 

859    /* Build up the list in the same order as the insns are seen

860      i n the machine description.  */

861    d->next = 0;

862    *idata_end = d;

863    idata_end = &d->next;

864 

865    max_opno = -1;

866    num_dups = 0;

867    memset (d->operand, 0, sizeof (d->operand));

868 

869    /* Get the number of operands by scanning all the patterns of the

870      peephole optimizer. But ignore all the rest of the information

871      thus obtained.  */

872    for (i = 0; i < XVECLEN (peep, 0); i++)

873      scan_operands (d, XVECEXP (peep, 0, i), 0, 0);

874 

875    d->n_operands = max_opno + 1;

876    d->n_dups = 0;

877 

878    validate_insn_alternatives (d);

879    place_operands (d);

880    process_template (d, XTMPL (peep, 2));

881  }

5.5. define_peephole2 产生代码

5.5.1. define_peephole2 的概览

define_peephole2 定义告诉编译器,如何用另一个指令序列来代替一个指令序列,那个额外的草稿( scratch )寄存器可能需要,及它们的生命周期。

  (define_peephole2

     [INSN-PATTERN-1

      INSN-PATTERN-2

     ...]

     "CONDITION"

     [NEW-INSN-PATTERN-1

      NEW-INSN-PATTERN-2

     ...]

     "PREPARATION-STATEMENTS")

这个定义几乎与 define_split 相同,除了匹配的模式不是单个指令,而是一个指令序列。

在输出模板中要求额外的草稿寄存器是可能的。如果适合的寄存器没有释放,这个模式将简单地视作不匹配。

由在输入模式的最上层的一个 match_scratch 模式来要求草稿寄存器。被分配的寄存器(一开始)在原始序列中,在要求的这一点上将死去。如果这个草稿寄存器在多个点上使用,在输入模式的最上层,一个 match_dup 模式标记输入序列中的最后位置,在那点上这个寄存器必须可用。

这里是一个来自 IA-32 机器描述的例子:

   (define_peephole2

       [(match_scratch:SI 2 "r")

        (parallel [(set (match_operand:SI 0 "register_operand" "")

                   (match_operator:SI 3 "arith_or_logical_operator"

                       [(match_dup 0)

                        (match_operand:SI 1 "memory_operand" "")]))

                    (clobber (reg:CC 17))])]

       "! optimize_size && ! TARGET_READ_MODIFY"

       [(set (match_dup 2) (match_dup 1))

        (parallel [(set (match_dup 0)

                    (match_op_dup 3 [(match_dup 0) (match_dup 2)]))

                (clobber (reg:CC 17))])]

    "")

这个模式尝试分解一个载入,期望这样我们可以在内存载入延迟前后安排指令。它分配了一个类别为 GENERAL_REGS Simode 寄存器( r ),它只需要存活到算术运算之前。

一个真正要求扩展草稿寄存器生命期的例子是很难得到的,因此这里是一个愚蠢的、捏造的例子:

  (define_peephole2

      [(match_scratch:SI 4 "r")

       (set (match_operand:SI 0 "" "") (match_operand:SI 1 "" ""))

       (set (match_operand:SI 2 "" "") (match_dup 1))

       (match_dup 4)

       (set (match_operand:SI 3 "" "") (match_dup 1))]

       "/* determine 1 does not overlap 0 and 2 */"

      [(set (match_dup 4) (match_dup 1))

       (set (match_dup 0) (match_dup 4))

       (set (match_dup 2) (match_dup 4))]

       (set (match_dup 3) (match_dup 4))]

   "")

如果我们没有在输入序列的中间加入 (match_dup 4) ,它可能是这样的情形:我们在序列开头选择的寄存器被第一或第二个 set 所杀死。

在工具 genrecog genrecog工具 )中, define_peephole2 模式被用于产生识别树。也就是说这个模式的识别由 recog 相关函数完成,而 define_peephole ,正如我们在 genpeep工具 所见,则通过 peephole

 

929  static void

930  gen_split (rtx split, int lineno)                                                                     in genoutput.c

931  {

932    struct data *d = xmalloc (sizeof (struct data));

933    int i;

934 

935    d->code_number = next_code_number ;

936    d->index_number = next_index_number ;

937    d->lineno = lineno;

938    d->name = 0;

939 

940    /* Build up the list in the same order as the insns are seen

941      i n the machine description.  */

942    d->next = 0;

943    *idata_end = d;

944    idata_end = &d->next;

945 

946    max_opno = -1;

947    num_dups = 0;

948    memset (d->operand, 0, sizeof (d->operand));

949 

950    /* Get the number of operands by scanning all the patterns of the

951      split patterns. But ignore all the rest of the information thus

952      obtained.  */

953    for (i = 0; i < XVECLEN (split, 0); i++)

954      scan_operands (d, XVECEXP (split, 0, i), 0, 0);

955 

956    d->n_operands = max_opno + 1;

957    d->n_dups = 0;

958    d->n_alternatives = 0;

959    d->template = 0;

960    d->output_format = INSN_OUTPUT_FORMAT_NONE;

961 

962    place_operands (d);

963  }

 

注意到在这个函数中仅保存了操作数,并随后输出。 define_peephole2 的转移函数( transformation function )在 define_peephole2产生代码 中产生。

5.6. 输出数据

回到 main , 接下来输出代码,记住 odata 保存了操作数,而 idata 保存了在 gen_insn gen_split gen_peephole 等模式处理函数中产生的 data 。这些余下的函数相当简单。

 

245  static void

246  output_operand_data (void)                                                                 in genoutput.c

247  {

248    struct operand_data *d;

249 

250    printf ("/nstatic const struct insn_operand_data operand_data[] = /n{/n");

251 

252    for (d = odata ; d; d = d->next)

253    {

254      printf ("  {/n");

255 

256      printf ("    %s,/n",

257          d->predicate && d->predicate[0] ? d->predicate : "0");

258 

259      printf ("    /"%s/",/n", d->constraint ? d->constraint : "");

260 

261      printf ("    %smode,/n", GET_MODE_NAME (d->mode));

262 

263      printf ("    %d,/n", d->strict_low);

264 

265      printf ("    %d/n", d->eliminable);

266 

267      printf("  },/n");

268    }

269    printf("};/n/n/n");

270  }

 

output_operand_data 将输出类型为 insn_operand_data 的数组,它定义在 recog.h 中,其类型如下。

 

218  struct insn_operand_data                                                                              in recog.h

219  {

220    const insn_operand_predicate_fn predicate;

221 

222    const char *const constraint;

223 

224    ENUM_BITFIELD(machine_mode) const mode : 16;

225 

226    const char strict_low;

227 

228    const char eliminable;

229  };

 

注意到在 recog.h 中很重要的数组 operand_data 在上面输出了。它记录了机器描述中所有不相同的操作数。

 

272  static void

273  output_insn_data (void)                                                                       in genoutput.c

274  {

275    struct data *d;

276    int name_offset = 0;

277    int next_name_offset;

278    const char * last_name = 0;

279    const char * next_name = 0;

280    struct data *n;

281 

282    for (n = idata , next_name_offset = 1; n; n = n->next, next_name_offset++)

283      if (n->name)

284      {

285        next_name = n->name;

286        break ;

287      }

288 

289    printf ("#if GCC_VERSION >= 2007/n__extension__/n#endif/n");

290    printf ("/nconst struct insn_data insn_data[] = /n{/n");

291 

292    for (d = idata ; d; d = d->next)

293    {

294      printf ("  {/n");

295 

296      if (d->name)

297      {

298        printf ("    /"%s/",/n", d->name);

299        name_offset = 0;

300        last_name = d->name;

301        next_name = 0;

302        for (n = d->next, next_name_offset = 1; n;

303            n = n->next, next_name_offset++)

304        {

305          if (n->name)

306          {

307            next_name = n->name;

308            break ;

309          }

310        }

311      }

312      else

313      {

314        name_offset++;

315        if (next_name && (last_name == 0

316           || name_offset > next_name_offset / 2))

317          printf ("    /"%s-%d/",/n", next_name,

318               next_name_offset - name_offset);

319        else

320          printf ("    /"%s+%d/",/n", last_name, name_offset);

321      }

322 

323      switch (d->output_format)

324      {

325        case INSN_OUTPUT_FORMAT_NONE:

326          printf ("#if HAVE_DESIGNATED_INITIALIZERS/n");

327          printf ("    { 0 },/n");

328            printf ("#else/n");

329           printf ("    { 0, 0, 0 },/n");

330           printf ("#endif/n");

331           break ;

332        case INSN_OUTPUT_FORMAT_SINGLE:

333        {

334          const char *p = d->template;

335          char prev = 0;

336 

337           printf ("#if HAVE_DESIGNATED_INITIALIZERS/n");

338          printf ("    { .single =/n");

339           printf ("#else/n");

340           printf ("    {/n");

341           printf ("#endif/n");

342          printf ("    /"");

343          while (*p)

344          {

345            if (IS_VSPACE (*p) && prev != '//')

346            {

347              /* Preserve two consecutive /n's or /r's, but treat /r/n

348                as a single newline.  */

349              if (*p == '/n' && prev != '/r')

350                printf ("//n///n");

351            }

352            else

353              putchar (*p);

354            prev = *p;

355            ++p;

356          }

357          printf ("/",/n");

358          printf ("#if HAVE_DESIGNATED_INITIALIZERS/n");

359          printf ("    },/n");

360           printf ("#else/n");

361           printf ("    0, 0 },/n");

362           printf ("#endif/n");

363         }

364        break ;

365        case INSN_OUTPUT_FORMAT_MULTI:

366          printf ("#if HAVE_DESIGNATED_INITIALIZERS/n");

367          printf ("     { .multi = output_%d },/n", d->code_number);

368          printf ("#else/n");

369          printf ("    { 0, output_%d, 0 },/n", d->code_number);

370          printf ("#endif/n");

371          break ;

372        case INSN_OUTPUT_FORMAT_FUNCTION:

373          printf ("#if HAVE_DESIGNATED_INITIALIZERS/n");

374          printf ("    { .function = output_%d },/n", d->code_number);

375          printf ("#else/n");

376          printf ("    { 0, 0, output_%d },/n", d->code_number);

377          printf ("#endif/n");

378           break ;

379        default :

380          abort ();

381      }

382 

383      if (d->name && d->name[0] != '*')

384        printf ("    (insn_gen_fn) gen_%s,/n", d->name);

385      else

386        printf ("    0,/n");

387 

388      printf ("    &operand_data[%d],/n", d->operand_number);

389      printf ("    %d,/n", d->n_operands);

390      printf ("    %d,/n", d->n_dups);

391      printf ("    %d,/n", d->n_alternatives);

392      printf ("    %d/n", d->output_format);

393 

394      printf("  },/n");

395    }

396    printf ("};/n/n/n");

397  }

 

另一个在 recog.h 中重要的数组 insn_data output_insn_data 输出。注意到 operand_data 是依次输出的,因此在 388 行, operand_number 可以被用作所有。其类型定义如下:

 

238  struct insn_data

239  {

240    const char *const name;

241  #if HAVE_DESIGNATED_INITIALIZERS

242    union {

243      const char *single;

244      const char *const *multi;

245      insn_output_fn function;

246    } output;

247   #else

248    struct {

249      const char *single;

250      const char *const *multi;

251      insn_output_fn function;

252    } output;

253  #endif

254    const insn_gen_fn genfun;

255    const struct insn_operand_data *const operand;

256 

257    const char n_operands;

258    const char n_dups;

259    const char n_alternatives;

260    const char output_format;

261  };

 

显然,从这个数组,我们可以知道为一个模式产生汇编代码的输出代码。而 254 行的 genfun 实际上将指向由 genemit 工具产生的 gen_* 函数,注意其类型是:

typedef rtx (*insn_gen_fn) (rtx, ...);

可以适配所有的 gen_* 函数。下面的函数则是生成返回指令名的函数。

 

399  static void

400  output_get_insn_name (void)                                                               in genoutput.c

401  {

402    printf ("const char */n");

403    printf ("get_insn_name (int code)/n");

404    printf ("{/n");

405    printf ("  if (code == NOOP_MOVE_INSN_CODE)/n");

406    printf ("    return /"NOOP_MOVE/";/n");

407    printf ("  else/n");

408    printf ("    return insn_data[code].name;/n");

409    printf ("}/n");

410  }

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值