GCC后端及汇编发布(27)

来源:互联网 发布:vb status() 编辑:程序博客网 时间:2024/06/09 19:38

9.5.8.4.    构建有限状态自动机

有限状态自动机(FSA)被分类为确定性(DFA)及非确定性(NDFA)。对于确定性的自动机,通过初始状态及触发事件,我们可以确切地知道将要迁移到的状态;而对于非确定性自动机,则将通向多个目标。在机器描述文件里,对于指令而言,包含替代(“|”操作符)的单元预订,将引入非确定性FSA

不过,我们可以在产生这个自动机的过程中,通过使用“-ndfa”选项来控制这些替代的输出。默认的,没有这个选项,替代以确定性的方式来处理,仅考虑到第一个合格替代的迁移。而使用这个选项,所有合格的替代都被用于构造非确定性自动机。不过在这之后,执行一个NDFADFA的转换,使自动机成为DFA。显然,由ndfa_flag0所生成的自动机,效率上不如由ndfa_flag1所生成的自动机,不过前者可以更快地生成,而且效率的损失没有想象的那么大。

 

6401 static void

6402 build_automaton (automaton_tautomaton)                                                  in genautomata.c

6403 {

6404   int states_num;

6405   int arcs_num;

6406

6407   ticker_on (&NDFA_time);

6408   if (progress_flag)

6409   {

6410     if (automaton->corresponding_automaton_decl== NULL)

6411       fprintf (stderr, "Create anonymousautomaton");

6412     else

6413       fprintf (stderr, "Create automaton`%s'",

6414            automaton->corresponding_automaton_decl->name);

6415       fprintf (stderr, " (1 dot is 100 newstates):");

6416   }

6417   make_automaton(automaton);

 

make_automaton被调用来构建自动机,它将根据“-ndfa”选项,依据出现的替代(在define_insn_reservation中的操作符“|”)构建非确定性或确定性自动机。

 

5693 static void

5694 make_automaton (automaton_tautomaton)                                                  in genautomata.c

5695 {

5696   ainsn_t ainsn;

5697   structinsn_reserv_decl *insn_reserv_decl;

5698   alt_state_talt_state;

5699   state_t state;

5700   state_t start_state;

5701   state_t state2;

5702   ainsn_t advance_cycle_ainsn;

5703   arc_t added_arc;

5704   vla_ptr_t state_stack;

5705   int states_n;

5706   reserv_sets_t reservs_matter = form_reservs_matter (automaton);

5707

5708   VLA_PTR_CREATE (state_stack, 150, "statestack");

5709   /* Create the startstate (empty state).  */

5710   start_state = insert_state (get_free_state(1, automaton));

5711   automaton->start_state = start_state;

5712  start_state->it_was_placed_in_stack_for_NDFA_forming = 1;

5713   VLA_PTR_ADD (state_stack, start_state);

5714   states_n = 1;

 

在这里,form_reserv_matter将再次填充一个单元预订位图。不过,这一次的位图是与自动机一一对应的。在DEFINE_INSN_RESERVATION模式的概览一节,我们看到功能单元是需要分配给自动机的(通过define_cpu_unit模式分配,每个功能单元只能属于一个自动机),而同一个自动机的不同功能单元之间,又存在依存或互斥的关系(通过EXCLUSION_SET等模式定义)。

另外,define_insn_reservation描述了每个指令类别(注意,严格来讲,名字中insn代表的是指令类别)对功能单元的占用情况,汇总每个类别的信息,就能大致知道每个单元的总体预订的情况,这正是前一节所做的事情。

前面反映每个指令类别CPU周期中单元预订情况的位图,无法显示这些信息。这里需要把这些信息收集到与自动机对应的位图。

 

5669 static reserv_sets_t

5670 form_reservs_matter (automaton_t automaton)                                      ingenautomata.c

5671 {

5672   int cycle, unit;

5673   reserv_sets_t reservs_matter =alloc_empty_reserv_sets();

5674

5675   for (cycle =0; cycle < max_cycles_num;cycle++)

5676     for (unit =0; unit < description->units_num;unit++)

5677       if (units_array [unit]->automaton_decl

5678             ==automaton->corresponding_automaton_decl

5679           && (cycle >= units_array[unit]->min_occ_cycle_num

5680               /* Wecan not remove queried unit from reservations. */

5681               || units_array [unit]->query_p

5682               /* Wecan not remove units which are used

5683                 `exclusion_set', `presence_set',

5684                 `final_presence_set', `absence_set', and

5685                 `final_absence_set'.  */

5686               || units_array [unit]->in_set_p))

5687         set_unit_reserv(reservs_matter, cycle, unit);

5688   returnreservs_matter;

5689 }

 

5679min_occ_cycle_num反映了该单元所有预订中,最小的预订周期。在这里的处理中,之后的周期,该单元都将被所属自动机视为已预订。这样做显然很保守,但足够简单并且正确。

接着在make_automaton5710一个新的start_state被保存入state_stack。它将是这个构建中的自动机的起始点。因此5715行条件一开始就满足了。

 

make_automaton (continued)

 

5715  while(VLA_PTR_LENGTH (state_stack) != 0)

5716   {

5717     state = VLA_PTR (state_stack,VLA_PTR_LENGTH (state_stack) - 1);

5718     VLA_PTR_SHORTEN (state_stack, 1);

5719    advance_cycle_ainsn = NULL;

5720     for (ainsn = automaton->ainsn_list;

5721        ainsn != NULL;

5722         ainsn = ainsn->next_ainsn)

5723       if(ainsn->first_insn_with_same_reservs)

5724       {

5725         insn_reserv_decl =ainsn->insn_reserv_decl;

5726         if (insn_reserv_decl !=DECL_INSN_RESERV (advance_cycle_insn_decl))

5727         {

5728           /* Weprocess alt_states in the same order as they are

5729             present in the description.  */

5730           added_arc = NULL;

5731          for (alt_state = ainsn->alt_states;

5732               alt_state != NULL;

5733               alt_state =alt_state->next_alt_state)

5734           {

5735             state2 = alt_state->state;

5736             if (!intersected_state_reservs_p (state,state2))

5737             {

5738               state2 = states_union(state, state2, reservs_matter);

5739               if(!state2->it_was_placed_in_stack_for_NDFA_forming)

5740               {

5741                 state2->it_was_placed_in_stack_for_NDFA_forming

5742                         = 1;

5743                 VLA_PTR_ADD (state_stack,state2);

5744                 states_n++;

5745                 if (progress_flag && states_n %100 == 0)

5746                   fprintf (stderr, ".");

5747               }

5748               added_arc = add_arc(state, state2, ainsn, 1);

5749               if (!ndfa_flag)

5750                 break;

5751             }

5752           }

5753           if (!ndfa_flag && added_arc !=NULL)

5754           {

5755             added_arc->state_alts = 0;

5756            for (alt_state = ainsn->alt_states;

5757                 alt_state != NULL;

5758                 alt_state =alt_state->next_alt_state)

5759             {

5760               state2 = alt_state->state;

5761               if (!intersected_state_reservs_p (state,state2))

5762                 added_arc->state_alts++;

5763             }

5764           }

5765         }

5766         else

5767           advance_cycle_ainsn = ainsn;

5768       }

5769       /* Addtransition to advance cycle.  */

5770       state2 = state_shift (state,reservs_matter);

5771       if(!state2->it_was_placed_in_stack_for_NDFA_forming)

5772       {

5773        state2->it_was_placed_in_stack_for_NDFA_forming = 1;

5774         VLA_PTR_ADD (state_stack, state2);

5775         states_n++;

5776         if (progress_flag && states_n % 100 == 0)

5777           fprintf (stderr, ".");

5778       }

5779       if (advance_cycle_ainsn == NULL)

5780         abort ();

5781       add_arc (state,state2, advance_cycle_ainsn, 1);

5782   }

5783   VLA_PTR_DELETE (state_stack);

5784 }

 

在这个自动机中,我们使用状态来追踪资源的使用,状态的迁移显示指令的发布。有两种状态迁移。第一种,某些指令可以在这个状态下发布(即,该指令不会与已发布的指令争夺资源)。另一种,如果没有指令可以被发布,我们不能做任何事,等待CPU前进一个周期。

在前面的小节中,已经看到ainsn如果设置了first_insn_with_same_reservs,表明它是使用该单元预订的在机器描述文件中第一个出现的define_insn_reservation模式。这意味着这个模式所预订的单元可能可用,值得做一次查找。而如果first_insn_with_same_reservs0,则意味着已经安排了使用同样单元的其它的指令类别,只能等下一个周期了(跳到5770行)。

上面在5761行,state代表源状态,state2则是可能的目标状态,注意这两个状态在同一个周期中,它们必须不争夺相同的CPU单元,迁移才有可能。intersected_state_reservs_p就是检查这两个状态是否会争夺相同的资源,它返回0,如果没有发现资源的争夺。

 

4168 static int

4169 intersected_state_reservs_p (state_t state1, state_t state2)                        in genautomata.c

4170 {

4171   if (state1->automaton !=state2->automaton)

4172     abort ();

4173   return reserv_sets_are_intersected(state1->reservs, state2->reservs);

4174 }

 

显而易见,这两个状态必须属于同一个自动机。在statereservs域是记录这个状态下各个周期里对功能单元的占用情况的位图(周期数取决于所有define_insn_reservation模式中出现的最大周期数),因此满足3881行条件,表示这两个状态有冲突。

 

3867 static int

3868 reserv_sets_are_intersected(reserv_sets_t operand_1,                             ingenautomata.c

3869                           reserv_sets_t operand_2)

3870 {

3871  set_el_t *el_ptr_1;

3872  set_el_t *el_ptr_2;

3873  set_el_t *cycle_ptr_1;

3874  set_el_t *cycle_ptr_2;

3875

3876   if(operand_1 == NULL || operand_2 == NULL)

3877    abort ();

3878   for (el_ptr_1 = operand_1, el_ptr_2 = operand_2;

3879      el_ptr_1 < operand_1 + els_in_reservs;

3880      el_ptr_1++, el_ptr_2++)

3881    if (*el_ptr_1 & *el_ptr_2)

3882       return 1;

3883   reserv_sets_or (temp_reserv, operand_1, operand_2);

3884   for(cycle_ptr_1 = operand_1, cycle_ptr_2 = operand_2;

3885       cycle_ptr_1 < operand_1 + els_in_reservs;

3886       cycle_ptr_1 += els_in_cycle_reserv, cycle_ptr_2 +=els_in_cycle_reserv)

3887   {

3888     for(el_ptr_1 = cycle_ptr_1, el_ptr_2 = get_excl_set(cycle_ptr_2);

3889         el_ptr_1 < cycle_ptr_1 + els_in_cycle_reserv;

3890        el_ptr_1++, el_ptr_2++)

3891    if (*el_ptr_1 & *el_ptr_2)

3892       return 1;

3893     if (!check_presence_pattern_sets(cycle_ptr_1, cycle_ptr_2, FALSE))

3894       return 1;

3895     if (!check_presence_pattern_sets (temp_reserv+ (cycle_ptr_2

3896                                  - operand_2),

3897                                  cycle_ptr_2,TRUE))

3898       return 1;

3899     if (!check_absence_pattern_sets(cycle_ptr_1, cycle_ptr_2, FALSE))

3900       return 1;

3901     if (!check_absence_pattern_sets (temp_reserv+ (cycle_ptr_2 - operand_2),

3902                                  cycle_ptr_2,TRUE))

3903       return 1;

3904   }

3905   return 0;

3906 }

 

如果这两个状态不竞争资源,那么离下结论还为时过早,我们需要进一步检查功能单元之间是否由于依存、互斥关系导致冲突。

首先检查单元互斥的状况。在initiate_excl_sets中根据出现的EXCLUDE_SET模式(参见数据初始化一节)设置unit_excl_set_table。这也是个位图,它的实现是一个unit_num * unit_num的二维数组(因为互斥是对称关系,它实际上是一个对称矩阵),互斥的单元12,在数组1*2处为1

 

4582 static reserv_sets_t

4583 get_excl_set (reserv_sets_t in_set)

4584 {

4585   int excl_char_num;

4586  int chars_num;

4587   int i;

4588   int start_unit_num;

4589   int unit_num;

4590

4591   chars_num = els_in_cycle_reserv * sizeof (set_el_t);

4592   memset (excl_set, 0, chars_num);

4593   for(excl_char_num = 0; excl_char_num < chars_num; excl_char_num++)

4594     if (((unsigned char *) in_set)[excl_char_num])

4595      for (i = CHAR_BIT - 1; i >= 0; i--)

4596         if ((((unsigned char *) in_set)[excl_char_num] >> i) & 1)

4597         {

4598           start_unit_num = excl_char_num * CHAR_BIT +i;

4599           if (start_unit_num >= description->units_num)

4600             return excl_set;

4601           for(unit_num = 0; unit_num < els_in_cycle_reserv; unit_num++)

4602           {

4603             excl_set [unit_num]

4604               |= unit_excl_set_table[start_unit_num] [unit_num];

4605           }

4606         }

4607   return excl_set;

4608 }

 

45944596行,只有被预订的单元才去查找与其互斥的单元,结果放在数组excl_set中,这个数组的元素也是位图。这个位图是编号为其索引的单元与其它单元的互斥位图。显然如果相斥的单元出现在另一个状态中,这两个状态是不可以合并的。

除了互斥关系,还要检查依赖关系。在3883行,首先获取这两个状态的合并状态,结果保存在temp_reserv中。在DEFINE_INSN_RESERVATION式的概览一节里,我们知道presenceabsence依赖关系有final及非final的区别。Final版本的要求在目的状态进行检查,非final则在源状态进行检查,temp_reserv就作为假定的目的状态。上面表达式“temp_reserv + (cycle_ptr_2 - operand_2)”是相应周期假定的目的状态对单元占用位图。

check_presence_pattern_sets的任务相当清楚,根据在original_set中被预订的单元,在unit_final_presence_set_tableunit_presence_set_table中查找其所依赖的单元。如果所依赖的单元都出现在checked_set中,那么这两个状态可能是相容的。

 

4692 static int

4693 check_presence_pattern_sets (reserv_sets_tchecked_set,

4694                         reserv_sets_t origional_set,

4695                         int final_p)

4696 {

4697   int char_num;

4698   int chars_num;

4699   int i;

4700   int start_unit_num;

4701   int unit_num;

4702   int presence_p;

4703   pattern_reserv_t pat_reserv;

4704

4705   chars_num = els_in_cycle_reserv * sizeof (set_el_t);

4706   for (char_num= 0; char_num < chars_num; char_num++)

4707     if (((unsigned char *) origional_set)[char_num])

4708       for (i =CHAR_BIT - 1; i >= 0; i--)

4709         if ((((unsigned char *) origional_set)[char_num] >> i) & 1)

4710         {

4711           start_unit_num = char_num * CHAR_BIT + i;

4712           if (start_unit_num >= description->units_num)

4713             break;

4714           if ((final_p

4715                && unit_final_presence_set_table[start_unit_num] == NULL)

4716              || (!final_p

4717                 && unit_presence_set_table[start_unit_num] == NULL))

4718             continue;

4719           presence_p = FALSE;

4720           for (pat_reserv = (final_p

4721                         ? unit_final_presence_set_table [start_unit_num]

4722                          : unit_presence_set_table [start_unit_num]);

4723               pat_reserv != NULL;

4724               pat_reserv =pat_reserv->next_pattern_reserv)

4725           {

4726             for(unit_num = 0; unit_num < els_in_cycle_reserv; unit_num++)

4727               if((checked_set [unit_num] & pat_reserv->reserv [unit_num])

4728                   != pat_reserv->reserv [unit_num])

4729                 break;

4730             presence_p = presence_p || unit_num>= els_in_cycle_reserv;

4731          }

4732           if (!presence_p)

4733             returnFALSE;

4734         }

4735   return TRUE;

4736 }

 

check_absence_pattern_sets的处理亦如是,只有所有不能出现的单元都不在checked_set中出现,这两个状态才可能相容。

 

4741 static int

4742 check_absence_pattern_sets (reserv_sets_tchecked_set,

4743                         reserv_sets_t origional_set,

4744                         int final_p)

4745 {

4746   int char_num;

4747   int chars_num;

4748   int i;

4749   int start_unit_num;

4750   int unit_num;

4751   pattern_reserv_t pat_reserv;

4752

4753   chars_num = els_in_cycle_reserv * sizeof (set_el_t);

4754   for (char_num= 0; char_num < chars_num; char_num++)

4755     if (((unsigned char *) origional_set)[char_num])

4756       for (i =CHAR_BIT - 1; i >= 0; i--)

4757         if ((((unsigned char *) origional_set)[char_num] >> i) & 1)

4758         {

4759           start_unit_num = char_num * CHAR_BIT + i;

4760           if (start_unit_num >= description->units_num)

4761             break;

4762           for (pat_reserv= (final_p

4763                         ? unit_final_absence_set_table [start_unit_num]

4764                          : unit_absence_set_table [start_unit_num]);

4765               pat_reserv != NULL;

4766               pat_reserv = pat_reserv->next_pattern_reserv)

4767           {

4768             for(unit_num = 0; unit_num < els_in_cycle_reserv; unit_num++)

4769               if ((checked_set [unit_num] &pat_reserv->reserv [unit_num])

4770                    != pat_reserv->reserv [unit_num]

4771                       && pat_reserv->reserv[unit_num])

4772                 break;

4773             if (unit_num >= els_in_cycle_reserv)

4774               return FALSE;

4775           }

4776         }

4777   return TRUE;

4778 }

 

如果状态可以合并,在5738行,state_union把它们合并为一个新状态。然后这个新状态放入state_stack,作为下一步的起点。

 

4179 static state_t

4180 states_union (state_t state1, state_tstate2, reserv_sets_t reservs)

4181 {

4182   state_t result;

4183   state_t state_in_table;

4184

4185   if (state1->automaton !=state2->automaton)

4186     abort ();

4187   result = get_free_state (1,state1->automaton);

4188   reserv_sets_or (result->reservs,state1->reservs, state2->reservs);

4189   reserv_sets_and (result->reservs,result->reservs, reservs);

4190   state_in_table = insert_state (result);

4191   if (result != state_in_table)

4192   {

4193     free_state (result);

4194     result = state_in_table;

4195   }

4196   returnresult;

4197 }

 

上面参数reservs是由form_reservs_matter构建的,自动机对功能单元的占用情况。两个状态的合集可能超出了自动机占用的单元,所以要在4189行进行与操作。

接着,我们需要记录到这个新状态的迁移。

 

4319 static arc_t

4320 add_arc (state_tfrom_state, state_t to_state, ainsn_t ainsn,                      in genautomata.c

4321        int state_alts)

4322 {

4323   arc_t new_arc;

4324

4325   new_arc = find_arc(from_state, to_state, ainsn);

4326   if (new_arc != NULL)

4327     returnnew_arc;

4328   if (first_free_arc == NULL)

4329   {

4330 #ifndef NDEBUG

4331     allocated_arcs_num++;

4332 #endif

4333     new_arc = create_node (sizeof (struct arc));

4334     new_arc->to_state = NULL;

4335     new_arc->insn = NULL;

4336    new_arc->next_out_arc = NULL;

4337   }

4338   else

4339   {

4340     new_arc = first_free_arc;

4341     first_free_arc = first_free_arc->next_out_arc;

4342   }

4343   new_arc->to_state = to_state;

4344   new_arc->insn = ainsn;

4345   ainsn->arc_exists_p = 1;

4346   new_arc->next_out_arc =from_state->first_out_arc;

4347   from_state->first_out_arc = new_arc;

4348   new_arc->next_arc_marked_by_insn = NULL;

4349   new_arc->state_alts = state_alts;

4350   returnnew_arc;

4351 }

 

Arc被定义为如下。对于所有外出的to_state,它们由next_out_arc链接起来,而to_state代表迁移的目标。

 

1140 struct arc                                                                                            in genautomata.c

1141 {

1142   /* The followingfield refers for the state into which given arc

1143     enters. */

1144   state_t to_state;

1145   /* The followingfield describes that the insn issue (with cycle

1146     advancing for special insn `cycleadvancing' and without cycle

1147     advancing for others) makes transition fromgiven state to

1148     another given state.  */

1149   ainsn_t insn;

1150   /* The followingfield value is the next arc output from the same

1151     state. */

1152   arc_t next_out_arc;

1153   /* List of arcsmarked given insn is formed with the following

1154     field. The field is used in transformationNDFA -> DFA.  */

1155   arc_t next_arc_marked_by_insn;

1156   /* The followingfield is defined if NDFA_FLAG is zero. The member

1157     value is number of alternative reservationswhich can be used for

1158     transition for given state by giveninsn.  */

1159   int state_alts;

1160 };

 

Arc的对象必须与(sourcestatetarget stateinsn)对严格的一一对应。这一决定对后面自动机最小化有深刻的影响。这个唯一性由find_arc保证。

 

4305 static arc_t

4306 find_arc (state_t from_state, state_t to_state, ainsn_t insn)

4307 {

4308   arc_t arc;

4309

4310   for (arc =first_out_arc (from_state); arc != NULL; arc = next_out_arc (arc))

4311     if (arc->to_state == to_state &&arc->insn == insn)

4312       returnarc;

4313   return NULL;

4314 }

 

上面在5749行,ndfa_flag如果非0,表明使用“-ndfa”选项(以非确定性方式处理替代)。在非确定性方式中,对于一个define_insn_reservation的所有替代,将记录每个可能迁移;而在确定性方式中,对于一个define_insn_reservation的所有替代,仅记录第一个合格的替代,及可能迁移的数目(不过第一个以外的迁移是不会被采用的)。例如,假定一个define_insn_reservation具有三个替代:ABC。从开始状态S开始,我们得到以下的arcs

S à A(可以迁移,构建状态SA,压入state_stack

S à B(可以迁移,构建状态SB,压入state_stack

S à C(可以迁移,构建状态SC,压入state_stack

而在确定性自动机中,将为第一个可能的迁移创建一个arc。在这个例子中,我们可以得到:

S à A(可以迁移,构建状态SA,压入state_stack),及arcstate_alts的值为3

假定接下来的define_insn_reservation具有两个替代:EF。对于非确定性自动机,我们得到:

S à E(可以迁移,构建状态SE,压入state_stack

S à F(可以迁移,构建状态SF,压入state_stack

而对于确定性自动机,我们得到:

S à E(可以迁移,构建状态SE,压入state_stack),及arcstate_alts的值为2

5720行的FOR循环尝试找出所有可能的迁移。那么在make_automaton5769行,state_shift将通过移动reservs域的内容,把当前状态推进一个周期——当然,应该为这个迁移构建一个新状态及arc对象。一旦建立,这个状态(这里是state2)将被加入state_stack,作为后面的开始状态(现在在state_stack中有:对非确定性自动机,SASBSCSESF;对确定性自动机,SASE。注意这里没有state2,因为start_stateSA = SA)。

5715WHILE循环的第二次运行中,从获取state_stack状态SF 对于非确定性自动机,我们得到以下迁移,并且记住,替代必须在有效的自动机声明中共享某些CPU单元:

F à E(不可能,intersected_state_reservs_p返回1

F à A(构建状态FA,压入state_stack,假定没有CPU单元冲突)

F à B(构建状态FB,压入state_stack,假定没有CPU单元冲突)

F à C(构建状态FC,压入state_stack,假定没有CPU单元冲突)

F à F1(前进一个周期,构建状态F1,压入state_stack

而对于确定性自动机我们得到以下迁移:

F à E(不可能,intersected_state_reservs_p返回1

F à A(构建状态FA,压入state_stack,假定没有CPU单元冲突)

F à F1(前进一个周期,构建状态F1,压入state_stack

那么在下一次循环中,获取了F1状态,我们将得到如下迁移:

F1 à A(不可能,假定intersected_state_reservs_p返回1

F1 à B(不可能,假定intersected_state_reservs_p返回1

F1 à C(构建状态F1C,压入state_stack,假定没有CPU单元冲突)

F1 à E(不可能,假定intersected_state_reservs_p返回1

F1 à F(不可能,假定intersected_state_reservs_p返回1

F1 à F2(推进F1一个周期,构建状态F2,压入state_stack

而对于确定性自动机,获取状态E,我们将得到如下迁移:

F1 à A(不可能,假定intersected_state_reservs_p返回1

F1 à B(不可能,假定intersected_state_reservs_p返回1

F1 à C(构建状态F1C,压入state_stack,假定没有CPU单元冲突)

F1 à E(不可能,假定intersected_state_reservs_p返回1

F1 à F(不可能,假定intersected_state_reservs_p返回1

F1 à F2(推进F1一个周期,构建状态F2,压入state_stack

此时,arc被由add_arc链接入state,我们可以得到如下视图。在这个图形中,arc*记录了迁移的信息,域next_out_arc链接起所有从相同状态出发的迁移,而域to_state指向目的状态。

t73

73:构建DFA,阶段1

通过这个方式,找出,并由arc记录所有的迁移机会。就通常的认识而言,我们已经构建了这个自动机。不过,如果出现的替代,NDFA可能被通过使用“-ndfa”选项构建了。NDFA不是我们所希望的,接着我们需要把它转换为DFA

原创粉丝点击