GCC's bacl-end & assemble emission (28)

9.5.8.5.    Minimize finite state automaton

The algorithm used to transform NDFA to DFA does not promise to produce the minimized DFA. So, it needs try to minimize the automaton created. The minimization algorithm here is, first assuming all states are same, and then visits these states one by one, and selects out those are not the same as the first state. Puts those states in another set and again assumes them are same. Then visits these states repeatly by above steps, until no different state can be found out in the set assuming equivalent set. Next merges the equivalent states to produce the minimized automaton.

 

6357 static void

6358 minimize_DFA (automaton_t automaton)                                              in genautomata.c

6359 {

6360   vla_ptr_t equiv_classes;

6361

6362   VLA_PTR_CREATE (equiv_classes, 1500, "equivalence classes");

6363   evaluate_equiv_classes (automaton, &equiv_classes);

6364   merge_states (automaton, &equiv_classes);

6365   pass_states (automaton, set_new_cycle_flags);

6366   VLA_PTR_DELETE (equiv_classes);

6367 }

 

evaluate_equiv_class at line 6363, groups the states into equivalence set as above describing.

 

6201 static void

6202 evaluate_equiv_classes (automaton_t automaton, vla_ptr_t *equiv_classes) in genautomata.c

6203 {

6204   state_t new_equiv_class;

6205   int new_equiv_class_num;

6206   int odd_iteration_flag;

6207   int finish_flag;

6208   vla_ptr_t next_iteration_classes;

6209   state_t *equiv_class_ptr;

6210   state_t *state_ptr;

6211

6212   VLA_PTR_CREATE (all_achieved_states , 1500, "all achieved states");

6213   pass_states (automaton, add_achieved_state );

6214   new_equiv_class = init_equiv_class (VLA_PTR_BEGIN (all_achieved_states ),

6215                                 VLA_PTR_LENGTH (all_achieved_states ));

6216   odd_iteration_flag = 0;

6217   new_equiv_class_num = 1;

6218   VLA_PTR_CREATE (next_iteration_classes, 150, "next iteration classes");

6219   VLA_PTR_ADD (next_iteration_classes, new_equiv_class);

6220   do

6221   {

6222     odd_iteration_flag = !odd_iteration_flag;

6223     finish_flag = 1;

6224     copy_equiv_class (equiv_classes, &next_iteration_classes);

6225     /* Transfer equiv numbers for the next iteration.  */

6226     for (state_ptr = VLA_PTR_BEGIN (all_achieved_states );

6227         state_ptr <= (state_t *) VLA_PTR_LAST (all_achieved_states );

6228         state_ptr++)

6229       if (odd_iteration_flag)

6230         (*state_ptr)->equiv_class_num_2 = (*state_ptr)->equiv_class_num_1;

6231       else

6232         (*state_ptr)->equiv_class_num_1 = (*state_ptr)->equiv_class_num_2;

6233     for (equiv_class_ptr = VLA_PTR_BEGIN (*equiv_classes);

6234         equiv_class_ptr <= (state_t *) VLA_PTR_LAST (*equiv_classes);

6235         equiv_class_ptr++)

6236       if (partition_equiv_class (equiv_class_ptr, odd_iteration_flag,

6237                   &next_iteration_classes,

6238                   &new_equiv_class_num))

6239         finish_flag = 0;

6240   }

6241   while (!finish_flag);

6242   VLA_PTR_DELETE (next_iteration_classes);

6243   VLA_PTR_DELETE (all_achieved_states );

6244 }

 

In pass_states at line 6213, it will traverse the automaton by states, and add states into all_achieved_states by add_achieved_state .

 

5998 static void

5999 add_achieved_state (state_t state)                                                         in genautomata.c

6000 {

6001   VLA_PTR_ADD (all_achieved_states , state);

6002 }

 

Then init_equiv_class at line 6214 links these states by next_equiv_class_state in reverse order. It just assumes all the states are equivalent, as next_equiv_class_state should links equivalent state. At line 6131, result_equiv_class points to the last state visited by pass_states . This value is saved in next_iteration_classes at line 6214.

 

6118 static state_t

6119 init_equiv_class (state_t *states, int states_num)                                           in genautomata.c

6120 {

6121   state_t *state_ptr;

6122   state_t result_equiv_class;

6123

6124   result_equiv_class = NULL;

6125   for (state_ptr = states; state_ptr < states + states_num; state_ptr++)

6126   {

6127     (*state_ptr)->equiv_class_num_1 = 1;

6128     (*state_ptr)->next_equiv_class_state = result_equiv_class;

6129     result_equiv_class = *state_ptr;

6130   }

6131   return result_equiv_class;

6132 }

 

At line 6214, copy_equiv_class copies equivalence sets from next_iteration_classes .

 

6049 static void

6050 copy_equiv_class (vla_ptr_t *to, const vla_ptr_t *from)                         in genautomata.c

6051 {

6052   state_t *class_ptr;

6053

6054   VLA_PTR_NULLIFY (*to);

6055   for (class_ptr = VLA_PTR_BEGIN (*from);

6056        class_ptr <= (state_t *) VLA_PTR_LAST (*from);

6057        class_ptr++)

6058     VLA_PTR_ADD (*to, *class_ptr);

6059 }

 

At line 6216 in evaluate_equiv_classes , variable odd_iteration_flag is used to indicate if it is the odd-th time of iteration. This flag controls filling and switching of equiv_class_num_1 and equiv_class_num_2 . equiv_class_num_1 and equiv_class_num_2 are used to record the No. of equivalent states. This No. is unique for every equivalent states set, and is used as differentiation for equivalent states sets. We will see later why needs the two variables.

partition_equiv_class at line 6236 in evaluate_equiv_classes , splits the original assuming equivalent states set into real equivalent states sets.

 

6141 static int

6142 partition_equiv_class (state_t *equiv_class_ptr, int odd_iteration_flag,     in genautomata.c

6143                      vla_ptr_t *next_iteration_classes,

6144                      int *new_equiv_class_num_ptr)

6145 {

6146   state_t new_equiv_class;

6147   int partition_p;

6148   state_t first_state;

6149   state_t curr_state;

6150   state_t prev_state;

6151   state_t next_state;

6152   int out_arcs_num;

6153

6154   partition_p = 0;

6155   if (*equiv_class_ptr == NULL)

6156     abort ();

6157   for (first_state = *equiv_class_ptr;

6158       first_state != NULL;

6159       first_state = new_equiv_class)

6160   {

6161     new_equiv_class = NULL;

6162     if (first_state->next_equiv_class_state != NULL)

6163     {

6164        /* There are more one states in the class equivalence.  */

6165       out_arcs_num = set_out_arc_insns_equiv_num (first_state,

6166                            odd_iteration_flag);

6167       for (prev_state = first_state,

6168               curr_state = first_state->next_equiv_class_state;

6169           curr_state != NULL;

6170           curr_state = next_state)

6171       {

6172         next_state = curr_state->next_equiv_class_state;

6173         if (state_is_differed (curr_state, first_state, out_arcs_num,

6174                          odd_iteration_flag))

6175         {

6176            /* Remove curr state from the class equivalence.  */

6177           prev_state->next_equiv_class_state = next_state;

6178           /* Add curr state to the new class equivalence.  */

6179           curr_state->next_equiv_class_state = new_equiv_class;

6180           if (new_equiv_class == NULL)

6181             (*new_equiv_class_num_ptr)++;

6182           if (odd_iteration_flag)

6183             curr_state->equiv_class_num_2 = *new_equiv_class_num_ptr;

6184           else

6185             curr_state->equiv_class_num_1 = *new_equiv_class_num_ptr;

6186           new_equiv_class = curr_state;

6187           partition_p = 1;

6188         }

6189         else

6190            prev_state = curr_state;

6191       }

6192       clear_arc_insns_equiv_num (first_state);

6193     }

6194     if (new_equiv_class != NULL)

6195       VLA_PTR_ADD (*next_iteration_classes, new_equiv_class);

6196   }

6197   return partition_p;

6198 }

 

In algorithm mentioned brefore, rest states of the assuming equivlant states set will be compared with first state to find out the different ones. As a short cut, if the outlet numbers are different between two states, the states are considered as different. set_out_arc_insns_equiv_num finds out the outlet number for the first state at line 6165 in partition_equiv_class . At the same time arc->insn->insn_reserv_decl->equiv_class_num, arc->insn->insn_reserv_decl->state_alts are set to indicate arc been processed. Note, at here, arc->insn doesn’t represent any specified insn, its meaning is only demonstrated in here treatment.

 

6008 static int

6009 set_out_arc_insns_equiv_num (state_t state, int odd_iteration_flag)        in genautomata.c

6010 {

6011   int state_out_arcs_num;

6012   arc_t arc;

6013

6014   state_out_arcs_num = 0;

6015   for (arc = first_out_arc (state); arc != NULL; arc = next_out_arc (arc))

6016   {

6017     if (arc->insn->insn_reserv_decl->equiv_class_num != 0

6018        || arc->insn->insn_reserv_decl->state_alts != 0)

6019       abort ();

6020     state_out_arcs_num++;

6021     arc->insn->insn_reserv_decl->equiv_class_num

6022           = (odd_iteration_flag

6023                ? arc->to_state->equiv_class_num_1

6024                : arc->to_state->equiv_class_num_2);

6025     arc->insn->insn_reserv_decl->state_alts = arc->state_alts;

6026     if (arc->insn->insn_reserv_decl->equiv_class_num == 0

6027         || arc->insn->insn_reserv_decl->state_alts <= 0)

6028       abort ();

6029   }

6030   return state_out_arcs_num;

6031 }

 

The automaton now is DFA, so in the FOR loop at line 6015, every arc must associate with different state. We set equiv_class_num of all arcs associating the state at line 6021, which will be one of proofs for state equivalence.

 

6082 static int

6083 state_is_differed (state_t state, state_t another_state,                               in genautomata.c

6084                int another_state_out_arcs_num, int odd_iteration_flag)

6085 {

6086   arc_t arc;

6087   int state_out_arcs_num;

6088   int i, presence1_p, presence2_p;

6089

6090   state_out_arcs_num = 0;

6091   for (arc = first_out_arc (state); arc != NULL; arc = next_out_arc (arc))

6092   {

6093     state_out_arcs_num++;

6094     if ((odd_iteration_flag

6095           ? arc->to_state->equiv_class_num_1

6096            : arc->to_state->equiv_class_num_2)

6097              != arc->insn->insn_reserv_decl->equiv_class_num

6098        || (arc->insn->insn_reserv_decl->state_alts != arc->state_alts))

6099       return 1;

6100   }

6101   if (state_out_arcs_num != another_state_out_arcs_num)

6102     return 1;

6103   /* Now we are looking at the states with the point of view of query

6104     units.  */

6105   for (i = 0; i < description ->units_num; i++)

6106     if (units_array [i]->query_p)

6107     {

6108       presence1_p = first_cycle_unit_presence (state, i);

6109       presence2_p = first_cycle_unit_presence (another_state, i);

6110       if ((presence1_p && !presence2_p) || (!presence1_p && presence2_p))

6111         return 1;

6112     }

6113   return 0;

6114 }

 

If state and another_state is equivalent, that means:

1)    They reaches same sets of equivalent target states

2)    They use the same units’ reservation

3)    They have same sets of equivalent source states

state_is_differed guarantees the first two conditions, while the iteration of the split sets in evaluate_equiv_classes makes the second condition satisfied. That is why in init_equiv_class rerrange states in reverse order, which can make the latest target state at beginning, and backtracks to its source states.

If states are judged as not equivalent, after back partition_equiv_class , all states not equivalent with first_state are put in the same set, and share a same equiv_class_num. Note assignment from line 6182 to 6185, when odd_iteration_flag is true, equiv_class_num_2 is assigned (the initial value is 2), just opposite to above (at that time, it is equiv_class_num_1 that in used.

That set is then processed in FOR loop at line 6175 in partition_equiv_class (it is assigned to first_state at line 6159. The set is further split by the first state (note the invocation of set_out_arc_insns_equiv_num ), as during which odd_iteration_flag is unchanged, so every separate sets has the increasing equiv_class_num_2 (assuming equiv_class_num_1 is in used, which is unchanged here too).

Back evaluate_equiv_classes , it updates equiv_class_num_1 or equiv_class_num_2 (depends on odd_iteration_flag , which is used to distinguish sets) for all new sets. Then call partition_equiv_class for every new set again for the partitiion.

The procedure is repeated unitl no new set is generated.

At line 6192 in partition_equiv_class , when certain equivalent set has been handled, related fields of the arc should be restored to separate the set from the rest (state_is_differed always returns at line 6099).

 

6035 static void

6036 clear_arc_insns_equiv_num (state_t state)                                             in genautomata.c

6037 {

6038   arc_t arc;

6039

6040   for (arc = first_out_arc (state); arc != NULL; arc = next_out_arc (arc))

6041   {

6042     arc->insn->insn_reserv_decl->equiv_class_num = 0;

6043     arc->insn->insn_reserv_decl->state_alts = 0;

6044   }

6045 }

 

After finding out the equivalent classes, at line 6364 in minimize_DFA , we can merge the equivalent states tegother to produce the minimized automaton by merge_states .

 

6247 static void

6248 merge_states (automaton_t automaton, vla_ptr_t *equiv_classes)             in genautomata.

6249 {

6250   state_t *equiv_class_ptr;

6251   state_t curr_state;

6252   state_t new_state;

6253   state_t first_class_state;

6254   alt_state_t alt_states;

6255   alt_state_t alt_state, new_alt_state;

6256   arc_t curr_arc;

6257   arc_t next_arc;

6258

6259   /* Create states corresponding to equivalence classes containing two

6260     or more states.  */

6261   for (equiv_class_ptr = VLA_PTR_BEGIN (*equiv_classes);

6262       equiv_class_ptr <= (state_t *) VLA_PTR_LAST (*equiv_classes);

6263       equiv_class_ptr++)

6264     if ((*equiv_class_ptr)->next_equiv_class_state != NULL)

6265     {

6266       /* There are more one states in the class equivalence.  */

6267       /* Create new compound state.  */

6268       new_state = get_free_state (0, automaton);

6269       alt_states = NULL;

6270       first_class_state = *equiv_class_ptr;

6271       for (curr_state = first_class_state;

6272           curr_state != NULL;

6273           curr_state = curr_state->next_equiv_class_state)

6274       {

6275         curr_state->equiv_class_state = new_state;

6276         if (curr_state->component_states == NULL)

6277         {

6278           new_alt_state = get_free_alt_state ();

6279           new_alt_state->state = curr_state;

6280           new_alt_state->next_alt_state = alt_states;

6281           alt_states = new_alt_state;

6282         }

6283         else

6284           for (alt_state = curr_state->component_states;

6285               alt_state != NULL;

6286               alt_state = alt_state->next_sorted_alt_state)

6287           {

6288             new_alt_state = get_free_alt_state ();

6289             new_alt_state->state = alt_state->state;

6290             new_alt_state->next_alt_state = alt_states;

6291             alt_states = new_alt_state;

6292           }

6293       }

6294       /* Its is important that alt states were sorted before and

6295         after merging to have the same querying results.  */

6296       new_state->component_states = uniq_sort_alt_states (alt_states);

6297     }

6298     else

6299       (*equiv_class_ptr)->equiv_class_state = *equiv_class_ptr;

6300     for (equiv_class_ptr = VLA_PTR_BEGIN (*equiv_classes);

6301         equiv_class_ptr <= (state_t *) VLA_PTR_LAST (*equiv_classes);

6302         equiv_class_ptr++)

6303       if ((*equiv_class_ptr)->next_equiv_class_state != NULL)

6304       {

6305         first_class_state = *equiv_class_ptr;

6306         /* Create new arcs output from the state corresponding to

6307           equiv class.  */

6308         for (curr_arc = first_out_arc (first_class_state);

6309             curr_arc != NULL;

6310             curr_arc = next_out_arc (curr_arc))

6311           add_arc (first_class_state->equiv_class_state,

6312                   curr_arc->to_state->equiv_class_state,

6313                   curr_arc->insn, curr_arc->state_alts);

6314         /* Delete output arcs from states of given class equivalence.  */

6315         for (curr_state = first_class_state;

6316             curr_state != NULL;

6317             curr_state = curr_state->next_equiv_class_state)

6318         {

6319           if (automaton->start_state == curr_state)

6320             automaton->start_state = curr_state->equiv_class_state;

6321           /* Delete the state and its output arcs.  */

6322           for (curr_arc = first_out_arc (curr_state);

6323               curr_arc != NULL;

6324               curr_arc = next_arc)

6325           {

6326             next_arc = next_out_arc (curr_arc);

6327             free_arc (curr_arc);

6328           }

6329         }

6330       }

6331       else

6332       {

6333           /* Change `to_state' of arcs output from the state of given

6334            equivalence class.  */

6335         for (curr_arc = first_out_arc (*equiv_class_ptr);

6336             curr_arc != NULL;

6337             curr_arc = next_out_arc (curr_arc))

6338           curr_arc->to_state = curr_arc->to_state->equiv_class_state;

6339       }

6340 }

 

merge_states works very similar as create_composed_state . Above in FOR loop at line 6261, for every equivalent class, a new state is created, and all states belonging to the class are sorted and linked into component_states . Pay attention to line 6275 and 6299, for every state, its equiv_class_state field is set with new state.

Following in the FOR loop at line 6300, first creating arc for transition to/from new states (FOR loop at line 6308 handles it, notice that add_arc will not create arc if it has been created), then removes arcs for transition to/from obsolete states.

After merging, build_automaton returns back to create_automata , enumerate_states finds out the total number of states.

 

6480 static void

6481 enumerate_states (automaton_t automaton)                                                  in genautomata.c

6482 {

6483   curr_state_order_num = 0;

6484   pass_states (automaton, set_order_state_num );

6485   automaton->achieved_states_num = curr_state_order_num ;

6486 }

 

6472 static void

6473 set_order_state_num ( state_t state)                                                        in genautomata.c

6474 {

6475   state->order_state_num = curr_state_order_num ;

6476   curr_state_order_num ++;

6477 }

 

Now we have built the minimized DFA, and all equivalent states have been merged. We need to group instruction classes according to the equivalent state classes, as instruction class belonging to the same equivalent class can be handled in the same way. It is done at line 6893 in create_automata by set_insn_equiv_classes .

 

6584 static void

6585 set_insn_equiv_classes (automaton_t automaton)                                   in genautomata.c

6586 {

6587   ainsn_t ainsn;

6588   ainsn_t first_insn;

6589   ainsn_t curr_insn;

6590   ainsn_t cyclic_insn_list;

6591   ainsn_t insn_with_same_reservs;

6592   int equiv_classes_num;

6593

6594   /* All insns are included in one equivalence class.  */

6595   cyclic_insn_list = NULL;

6596   for (ainsn = automaton->ainsn_list; ainsn != NULL; ainsn = ainsn->next_ainsn)

6597     if (ainsn->first_insn_with_same_reservs)

6598       cyclic_insn_list = insert_ainsn_into_equiv_class (ainsn,

6599                                                  cyclic_insn_list);

6600   /* Process insns in order to make equivalence partition.  */

6601   pass_states (automaton, process_state_for_insn_equiv_partition );

6602   /* Enumerate equiv classes.   */

6603   for (ainsn = automaton->ainsn_list; ainsn != NULL; ainsn = ainsn->next_ainsn)

6604      /* Set undefined value.  */

6605     ainsn->insn_equiv_class_num = -1;

6606   equiv_classes_num = 0;

6607   for (ainsn = automaton->ainsn_list; ainsn != NULL; ainsn = ainsn->next_ainsn)

6608     if (ainsn->insn_equiv_class_num < 0)

6609     {

6610       first_insn = ainsn;

6611       if (!first_insn->first_insn_with_same_reservs)

6612         abort ();

6613       first_insn->first_ainsn_with_given_equialence_num = 1;

6614       curr_insn = first_insn;

6615       do

6616       {

6617         for (insn_with_same_reservs = curr_insn;

6618             insn_with_same_reservs != NULL;

6619             insn_with_same_reservs

6620                    = insn_with_same_reservs->next_same_reservs_insn)

6621           insn_with_same_reservs->insn_equiv_class_num = equiv_classes_num;

6622         curr_insn = curr_insn->next_equiv_class_insn;

6623       }

6624       while (curr_insn != first_insn);

6625       equiv_classes_num++;

6626     }

6627   automaton->insn_equiv_classes_num = equiv_classes_num;

6628 }

 

See in form_ainsn_with_same_reservs , first_insn_with_same_reservs field is set if the ainsn is the first of those with same unit reservation or if the instruction is advance_cycle_insn_decl which forms a set only containing itself. At line 6598, insert_ainsn_into_equiv_class links these instruction classes tegother via next_equiv_class_insn .

 

6495 static ainsn_t

6496 insert_ainsn_into_equiv_class (ainsn_t ainsn,                                         in genautomata.c

6497                      ainsn_t cyclic_equiv_class_insn_list)

6498 {

6499   if (cyclic_equiv_class_insn_list == NULL)

6500     ainsn->next_equiv_class_insn = ainsn;

6501   else

6502   {

6503      ainsn->next_equiv_class_insn

6504          = cyclic_equiv_class_insn_list->next_equiv_class_insn;

6505     cyclic_equiv_class_insn_list->next_equiv_class_insn = ainsn;

6506   }

6507   return ainsn;

6508 }

 

At this step, we get following data structure. In the figure, in the list in red, its nodes represents different classes of equivalent instruction set, while the lists in blue are formed by equivalent instruction set individually.

t80

figure 80  : grouping instruction into equivalent class, stage 2

As the list formed by next_same_reservs_insn is built by form_ainsn_with_same_reservs , after that by processing in NDFA_to_DFA and minimize_DFA , this list is obselete. So, next it needs process_state_for_insn_equiv_partition to repartite the equivalent instruction sets.

 

6562 static void

6563 pr ocess_state_for_insn_equiv_partition ( state_t state)                             in genautomata.c

6564 {

6565   arc_t arc;

6566   arc_t *insn_arcs_array;

6567   int i;

6568   vla_ptr_t insn_arcs_vect;

6569

6570   VLA_PTR_CREATE (insn_arcs_vect, 500, "insn arcs vector");

6571   VLA_PTR_EXPAND (insn_arcs_vect, description ->insns_num);

6572   insn_arcs_array = VLA_PTR_BEGIN (insn_arcs_vect);

6573   /* Process insns of the arcs.  */

6574   for (i = 0; i < description ->insns_num; i++)

6575     insn_arcs_array [i] = NULL;

6576   for (arc = first_out_arc (state); arc != NULL; arc = next_out_arc (arc))

6577     insn_arcs_array [arc->insn->insn_reserv_decl->insn_num] = arc;

6578   for (arc = first_out_arc (state); arc != NULL; arc = next_out_arc (arc))

6579     process_insn_equiv_class (arc->insn, insn_arcs_array);

6580   VLA_PTR_DELETE (insn_arcs_vect);

6581 }

 

Remember that first_out_arc and next_out_arc all return the transition out from the state and arc has one-to-one correspondence with (source state, target state, insn) pair, the checking is done by process_insn_equiv_class at line 6597 above per arc.

 

6531 static void

6532 process_insn_equiv_class (ainsn_t ainsn, arc_t *insn_arcs_array)

6533 {

6534   ainsn_t next_insn;

6535   ainsn_t curr_insn;

6536   ainsn_t cyclic_insn_list;

6537   arc_t arc;

6538

6539   if (insn_arcs_array [ainsn->insn_reserv_decl->insn_num] == NULL)

6540     abort ();

6541   curr_insn = ainsn;

6542   /* New class of ainsns which are not equivalent to given ainsn.  */

6543   cyclic_insn_list = NULL;

6544   do

6545   {

6546     next_insn = curr_insn->next_equiv_class_insn;

6547     arc = insn_arcs_array [curr_insn->insn_reserv_decl->insn_num];

6548     if (arc == NULL

6549         || (insn_arcs_array [ainsn->insn_reserv_decl->insn_num]->to_state

6550              != arc->to_state))

6551     {

6552       delete_ainsn_from_equiv_class (curr_insn);

6553       cyclic_insn_list = insert_ainsn_into_equiv_class (curr_insn,

6554                                      cyclic_insn_list);

6555     }

6556     curr_insn = next_insn;

6557   }

6558   while (curr_insn != ainsn);

6559 }

 

We know that using same unit reservation is the necessary condtion for being equivalent instruction class, so no class of equivalent instruction set gotten in minimize_DFA can span lists of next_same_reservs_insn constructed in form_ainsn_with_same_reservs , on the contractly, one of the lists may be split into several lists. Then in rest part of set_insn_equiv_classes , it reassigns new equiv_classes_num.

t81

figure 81  : grouping instruction into equivalent class, stage 3

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值