1/* This file is generated by the genmloop script. DO NOT EDIT! */ 2 3/* Enable switch() support in cgen headers. */ 4#define SEM_IN_SWITCH 5 6#define WANT_CPU sh64 7#define WANT_CPU_SH64 8 9#include "sim-main.h" 10#include "bfd.h" 11#include "cgen-mem.h" 12#include "cgen-ops.h" 13#include "sim-assert.h" 14 15/* Fill in the administrative ARGBUF fields required by all insns, 16 virtual and real. */ 17 18static INLINE void 19sh64_media_fill_argbuf (const SIM_CPU *cpu, ARGBUF *abuf, const IDESC *idesc, 20 PCADDR pc, int fast_p) 21{ 22#if WITH_SCACHE 23 SEM_SET_CODE (abuf, idesc, fast_p); 24 ARGBUF_ADDR (abuf) = pc; 25#endif 26 ARGBUF_IDESC (abuf) = idesc; 27} 28 29/* Fill in tracing/profiling fields of an ARGBUF. */ 30 31static INLINE void 32sh64_media_fill_argbuf_tp (const SIM_CPU *cpu, ARGBUF *abuf, 33 int trace_p, int profile_p) 34{ 35 ARGBUF_TRACE_P (abuf) = trace_p; 36 ARGBUF_PROFILE_P (abuf) = profile_p; 37} 38 39#if WITH_SCACHE_PBB 40 41/* Emit the "x-before" handler. 42 x-before is emitted before each insn (serial or parallel). 43 This is as opposed to x-after which is only emitted at the end of a group 44 of parallel insns. */ 45 46static INLINE void 47sh64_media_emit_before (SIM_CPU *current_cpu, SCACHE *sc, PCADDR pc, int first_p) 48{ 49 ARGBUF *abuf = &sc[0].argbuf; 50 const IDESC *id = & CPU_IDESC (current_cpu) [SH64_MEDIA_INSN_X_BEFORE]; 51 52 abuf->fields.before.first_p = first_p; 53 sh64_media_fill_argbuf (current_cpu, abuf, id, pc, 0); 54 /* no need to set trace_p,profile_p */ 55} 56 57/* Emit the "x-after" handler. 58 x-after is emitted after a serial insn or at the end of a group of 59 parallel insns. */ 60 61static INLINE void 62sh64_media_emit_after (SIM_CPU *current_cpu, SCACHE *sc, PCADDR pc) 63{ 64 ARGBUF *abuf = &sc[0].argbuf; 65 const IDESC *id = & CPU_IDESC (current_cpu) [SH64_MEDIA_INSN_X_AFTER]; 66 67 sh64_media_fill_argbuf (current_cpu, abuf, id, pc, 0); 68 /* no need to set trace_p,profile_p */ 69} 70 71#endif /* WITH_SCACHE_PBB */ 72 73 74static INLINE const IDESC * 75extract (SIM_CPU *current_cpu, PCADDR pc, CGEN_INSN_INT insn, ARGBUF *abuf, 76 int fast_p) 77{ 78 const IDESC *id = sh64_media_decode (current_cpu, pc, insn, insn, abuf); 79 80 sh64_media_fill_argbuf (current_cpu, abuf, id, pc, fast_p); 81 if (! fast_p) 82 { 83 int trace_p = PC_IN_TRACE_RANGE_P (current_cpu, pc); 84 int profile_p = PC_IN_PROFILE_RANGE_P (current_cpu, pc); 85 sh64_media_fill_argbuf_tp (current_cpu, abuf, trace_p, profile_p); 86 } 87 return id; 88} 89 90static INLINE SEM_PC 91execute (SIM_CPU *current_cpu, SCACHE *sc, int fast_p) 92{ 93 SEM_PC vpc; 94 95 if (fast_p) 96 { 97#if ! WITH_SEM_SWITCH_FAST 98#if WITH_SCACHE 99 vpc = (*sc->argbuf.semantic.sem_fast) (current_cpu, sc); 100#else 101 vpc = (*sc->argbuf.semantic.sem_fast) (current_cpu, &sc->argbuf); 102#endif 103#else 104 abort (); 105#endif /* WITH_SEM_SWITCH_FAST */ 106 } 107 else 108 { 109#if ! WITH_SEM_SWITCH_FULL 110 ARGBUF *abuf = &sc->argbuf; 111 const IDESC *idesc = abuf->idesc; 112#if WITH_SCACHE_PBB 113 int virtual_p = CGEN_ATTR_VALUE (NULL, idesc->attrs, CGEN_INSN_VIRTUAL); 114#else 115 int virtual_p = 0; 116#endif 117 118 if (! virtual_p) 119 { 120 /* FIXME: call x-before */ 121 if (ARGBUF_PROFILE_P (abuf)) 122 PROFILE_COUNT_INSN (current_cpu, abuf->addr, idesc->num); 123 /* FIXME: Later make cover macros: PROFILE_INSN_{INIT,FINI}. */ 124 if (PROFILE_MODEL_P (current_cpu) 125 && ARGBUF_PROFILE_P (abuf)) 126 sh64_media_model_insn_before (current_cpu, 1 /*first_p*/); 127 TRACE_INSN_INIT (current_cpu, abuf, 1); 128 TRACE_INSN (current_cpu, idesc->idata, 129 (const struct argbuf *) abuf, abuf->addr); 130 } 131#if WITH_SCACHE 132 vpc = (*sc->argbuf.semantic.sem_full) (current_cpu, sc); 133#else 134 vpc = (*sc->argbuf.semantic.sem_full) (current_cpu, abuf); 135#endif 136 if (! virtual_p) 137 { 138 /* FIXME: call x-after */ 139 if (PROFILE_MODEL_P (current_cpu) 140 && ARGBUF_PROFILE_P (abuf)) 141 { 142 int cycles; 143 144 cycles = (*idesc->timing->model_fn) (current_cpu, sc); 145 sh64_media_model_insn_after (current_cpu, 1 /*last_p*/, cycles); 146 } 147 TRACE_INSN_FINI (current_cpu, abuf, 1); 148 } 149#else 150 abort (); 151#endif /* WITH_SEM_SWITCH_FULL */ 152 } 153 154 return vpc; 155} 156 157 158/* Record address of cti terminating a pbb. */ 159#define SET_CTI_VPC(sc) do { _cti_sc = (sc); } while (0) 160/* Record number of [real] insns in pbb. */ 161#define SET_INSN_COUNT(n) do { _insn_count = (n); } while (0) 162 163/* Fetch and extract a pseudo-basic-block. 164 FAST_P is non-zero if no tracing/profiling/etc. is wanted. */ 165 166INLINE SEM_PC 167sh64_media_pbb_begin (SIM_CPU *current_cpu, int FAST_P) 168{ 169 SEM_PC new_vpc; 170 PCADDR pc; 171 SCACHE *sc; 172 int max_insns = CPU_SCACHE_MAX_CHAIN_LENGTH (current_cpu); 173 174 pc = GET_H_PC (); 175 176 new_vpc = scache_lookup_or_alloc (current_cpu, pc, max_insns, &sc); 177 if (! new_vpc) 178 { 179 /* Leading '_' to avoid collision with mainloop.in. */ 180 int _insn_count = 0; 181 SCACHE *orig_sc = sc; 182 SCACHE *_cti_sc = NULL; 183 int slice_insns = CPU_MAX_SLICE_INSNS (current_cpu); 184 185 /* First figure out how many instructions to compile. 186 MAX_INSNS is the size of the allocated buffer, which includes space 187 for before/after handlers if they're being used. 188 SLICE_INSNS is the maxinum number of real insns that can be 189 executed. Zero means "as many as we want". */ 190 /* ??? max_insns is serving two incompatible roles. 191 1) Number of slots available in scache buffer. 192 2) Number of real insns to execute. 193 They're incompatible because there are virtual insns emitted too 194 (chain,cti-chain,before,after handlers). */ 195 196 if (slice_insns == 1) 197 { 198 /* No need to worry about extra slots required for virtual insns 199 and parallel exec support because MAX_CHAIN_LENGTH is 200 guaranteed to be big enough to execute at least 1 insn! */ 201 max_insns = 1; 202 } 203 else 204 { 205 /* Allow enough slop so that while compiling insns, if max_insns > 0 206 then there's guaranteed to be enough space to emit one real insn. 207 MAX_CHAIN_LENGTH is typically much longer than 208 the normal number of insns between cti's anyway. */ 209 max_insns -= (1 /* one for the trailing chain insn */ 210 + (FAST_P 211 ? 0 212 : (1 + MAX_PARALLEL_INSNS) /* before+after */) 213 + (MAX_PARALLEL_INSNS > 1 214 ? (MAX_PARALLEL_INSNS * 2) 215 : 0)); 216 217 /* Account for before/after handlers. */ 218 if (! FAST_P) 219 slice_insns *= 3; 220 221 if (slice_insns > 0 222 && slice_insns < max_insns) 223 max_insns = slice_insns; 224 } 225 226 new_vpc = sc; 227 228 /* SC,PC must be updated to point passed the last entry used. 229 SET_CTI_VPC must be called if pbb is terminated by a cti. 230 SET_INSN_COUNT must be called to record number of real insns in 231 pbb [could be computed by us of course, extra cpu but perhaps 232 negligible enough]. */ 233 234/* begin extract-pbb */ 235{ 236 const IDESC *idesc; 237 int icount = 0; 238 239 while (max_insns > 0) 240 { 241 USI insn = GETIMEMUSI (current_cpu, pc); 242 243 idesc = extract (current_cpu, pc, insn, &sc->argbuf, FAST_P); 244 SEM_SKIP_COMPILE (current_cpu, sc, 1); 245 ++sc; 246 --max_insns; 247 ++icount; 248 pc += idesc->length; 249 250 if (IDESC_CTI_P (idesc)) 251 { 252 SET_CTI_VPC (sc - 1); 253 254 if (CGEN_ATTR_VALUE (NULL, idesc->attrs, CGEN_INSN_DELAY_SLOT)) 255 { 256 USI insn = GETIMEMUSI (current_cpu, pc); 257 idesc = extract (current_cpu, pc, insn, &sc->argbuf, FAST_P); 258 259 ++sc; 260 --max_insns; 261 ++icount; 262 pc += idesc->length; 263 } 264 break; 265 } 266 } 267 268 Finish: 269 SET_INSN_COUNT (icount); 270} 271/* end extract-pbb */ 272 273 /* The last one is a pseudo-insn to link to the next chain. 274 It is also used to record the insn count for this chain. */ 275 { 276 const IDESC *id; 277 278 /* Was pbb terminated by a cti? */ 279 if (_cti_sc) 280 { 281 id = & CPU_IDESC (current_cpu) [SH64_MEDIA_INSN_X_CTI_CHAIN]; 282 } 283 else 284 { 285 id = & CPU_IDESC (current_cpu) [SH64_MEDIA_INSN_X_CHAIN]; 286 } 287 SEM_SET_CODE (&sc->argbuf, id, FAST_P); 288 sc->argbuf.idesc = id; 289 sc->argbuf.addr = pc; 290 sc->argbuf.fields.chain.insn_count = _insn_count; 291 sc->argbuf.fields.chain.next = 0; 292 sc->argbuf.fields.chain.branch_target = 0; 293 ++sc; 294 } 295 296 /* Update the pointer to the next free entry, may not have used as 297 many entries as was asked for. */ 298 CPU_SCACHE_NEXT_FREE (current_cpu) = sc; 299 /* Record length of chain if profiling. 300 This includes virtual insns since they count against 301 max_insns too. */ 302 if (! FAST_P) 303 PROFILE_COUNT_SCACHE_CHAIN_LENGTH (current_cpu, sc - orig_sc); 304 } 305 306 return new_vpc; 307} 308 309/* Chain to the next block from a non-cti terminated previous block. */ 310 311INLINE SEM_PC 312sh64_media_pbb_chain (SIM_CPU *current_cpu, SEM_ARG sem_arg) 313{ 314 ARGBUF *abuf = SEM_ARGBUF (sem_arg); 315 316 PBB_UPDATE_INSN_COUNT (current_cpu, sem_arg); 317 318 SET_H_PC (abuf->addr | 1); 319 320 /* If not running forever, exit back to main loop. */ 321 if (CPU_MAX_SLICE_INSNS (current_cpu) != 0 322 /* Also exit back to main loop if there's an event. 323 Note that if CPU_MAX_SLICE_INSNS != 1, events won't get processed 324 at the "right" time, but then that was what was asked for. 325 There is no silver bullet for simulator engines. 326 ??? Clearly this needs a cleaner interface. 327 At present it's just so Ctrl-C works. */ 328 || STATE_EVENTS (CPU_STATE (current_cpu))->work_pending) 329 CPU_RUNNING_P (current_cpu) = 0; 330 331 /* If chained to next block, go straight to it. */ 332 if (abuf->fields.chain.next) 333 return abuf->fields.chain.next; 334 /* See if next block has already been compiled. */ 335 abuf->fields.chain.next = scache_lookup (current_cpu, abuf->addr); 336 if (abuf->fields.chain.next) 337 return abuf->fields.chain.next; 338 /* Nope, so next insn is a virtual insn to invoke the compiler 339 (begin a pbb). */ 340 return CPU_SCACHE_PBB_BEGIN (current_cpu); 341} 342 343/* Chain to the next block from a cti terminated previous block. 344 BR_TYPE indicates whether the branch was taken and whether we can cache 345 the vpc of the branch target. 346 NEW_PC is the target's branch address, and is only valid if 347 BR_TYPE != SEM_BRANCH_UNTAKEN. */ 348 349INLINE SEM_PC 350sh64_media_pbb_cti_chain (SIM_CPU *current_cpu, SEM_ARG sem_arg, 351 SEM_BRANCH_TYPE br_type, PCADDR new_pc) 352{ 353 SEM_PC *new_vpc_ptr; 354 355 PBB_UPDATE_INSN_COUNT (current_cpu, sem_arg); 356 357 /* If we have switched ISAs, exit back to main loop. 358 Set idesc to 0 to cause the engine to point to the right insn table. */ 359 if ((new_pc & 1) == 0) 360 { 361 /* Switch to SHcompact. */ 362 CPU_IDESC_SEM_INIT_P (current_cpu) = 0; 363 CPU_RUNNING_P (current_cpu) = 0; 364 } 365 366 /* If not running forever, exit back to main loop. */ 367 if (CPU_MAX_SLICE_INSNS (current_cpu) != 0 368 /* Also exit back to main loop if there's an event. 369 Note that if CPU_MAX_SLICE_INSNS != 1, events won't get processed 370 at the "right" time, but then that was what was asked for. 371 There is no silver bullet for simulator engines. 372 ??? Clearly this needs a cleaner interface. 373 At present it's just so Ctrl-C works. */ 374 || STATE_EVENTS (CPU_STATE (current_cpu))->work_pending) 375 CPU_RUNNING_P (current_cpu) = 0; 376 377 /* Restart compiler if we branched to an uncacheable address 378 (e.g. "j reg"). */ 379 if (br_type == SEM_BRANCH_UNCACHEABLE) 380 { 381 SET_H_PC (new_pc); 382 return CPU_SCACHE_PBB_BEGIN (current_cpu); 383 } 384 385 /* If branch wasn't taken, update the pc and set BR_ADDR_PTR to our 386 next chain ptr. */ 387 if (br_type == SEM_BRANCH_UNTAKEN) 388 { 389 ARGBUF *abuf = SEM_ARGBUF (sem_arg); 390 new_pc = abuf->addr; 391 /* Set bit 0 to stay in SHmedia mode. */ 392 SET_H_PC (new_pc | 1); 393 new_vpc_ptr = &abuf->fields.chain.next; 394 } 395 else 396 { 397 ARGBUF *abuf = SEM_ARGBUF (sem_arg); 398 SET_H_PC (new_pc); 399 new_vpc_ptr = &abuf->fields.chain.branch_target; 400 } 401 402 /* If chained to next block, go straight to it. */ 403 if (*new_vpc_ptr) 404 return *new_vpc_ptr; 405 /* See if next block has already been compiled. */ 406 *new_vpc_ptr = scache_lookup (current_cpu, new_pc); 407 if (*new_vpc_ptr) 408 return *new_vpc_ptr; 409 /* Nope, so next insn is a virtual insn to invoke the compiler 410 (begin a pbb). */ 411 return CPU_SCACHE_PBB_BEGIN (current_cpu); 412} 413 414/* x-before handler. 415 This is called before each insn. */ 416 417void 418sh64_media_pbb_before (SIM_CPU *current_cpu, SCACHE *sc) 419{ 420 SEM_ARG sem_arg = sc; 421 const ARGBUF *abuf = SEM_ARGBUF (sem_arg); 422 int first_p = abuf->fields.before.first_p; 423 const ARGBUF *cur_abuf = SEM_ARGBUF (sc + 1); 424 const IDESC *cur_idesc = cur_abuf->idesc; 425 PCADDR pc = cur_abuf->addr; 426 427 if (ARGBUF_PROFILE_P (cur_abuf)) 428 PROFILE_COUNT_INSN (current_cpu, pc, cur_idesc->num); 429 430 /* If this isn't the first insn, finish up the previous one. */ 431 432 if (! first_p) 433 { 434 if (PROFILE_MODEL_P (current_cpu)) 435 { 436 const SEM_ARG prev_sem_arg = sc - 1; 437 const ARGBUF *prev_abuf = SEM_ARGBUF (prev_sem_arg); 438 const IDESC *prev_idesc = prev_abuf->idesc; 439 int cycles; 440 441 /* ??? May want to measure all insns if doing insn tracing. */ 442 if (ARGBUF_PROFILE_P (prev_abuf)) 443 { 444 cycles = (*prev_idesc->timing->model_fn) (current_cpu, prev_sem_arg); 445 sh64_media_model_insn_after (current_cpu, 0 /*last_p*/, cycles); 446 } 447 } 448 449 TRACE_INSN_FINI (current_cpu, cur_abuf, 0 /*last_p*/); 450 } 451 452 /* FIXME: Later make cover macros: PROFILE_INSN_{INIT,FINI}. */ 453 if (PROFILE_MODEL_P (current_cpu) 454 && ARGBUF_PROFILE_P (cur_abuf)) 455 sh64_media_model_insn_before (current_cpu, first_p); 456 457 TRACE_INSN_INIT (current_cpu, cur_abuf, first_p); 458 TRACE_INSN (current_cpu, cur_idesc->idata, cur_abuf, pc); 459} 460 461/* x-after handler. 462 This is called after a serial insn or at the end of a group of parallel 463 insns. */ 464 465void 466sh64_media_pbb_after (SIM_CPU *current_cpu, SCACHE *sc) 467{ 468 SEM_ARG sem_arg = sc; 469 const ARGBUF *abuf = SEM_ARGBUF (sem_arg); 470 const SEM_ARG prev_sem_arg = sc - 1; 471 const ARGBUF *prev_abuf = SEM_ARGBUF (prev_sem_arg); 472 473 /* ??? May want to measure all insns if doing insn tracing. */ 474 if (PROFILE_MODEL_P (current_cpu) 475 && ARGBUF_PROFILE_P (prev_abuf)) 476 { 477 const IDESC *prev_idesc = prev_abuf->idesc; 478 int cycles; 479 480 cycles = (*prev_idesc->timing->model_fn) (current_cpu, prev_sem_arg); 481 sh64_media_model_insn_after (current_cpu, 1 /*last_p*/, cycles); 482 } 483 TRACE_INSN_FINI (current_cpu, prev_abuf, 1 /*last_p*/); 484} 485 486#define FAST_P 0 487 488void 489sh64_media_engine_run_full (SIM_CPU *current_cpu) 490{ 491 SIM_DESC current_state = CPU_STATE (current_cpu); 492 SCACHE *scache = CPU_SCACHE_CACHE (current_cpu); 493 /* virtual program counter */ 494 SEM_PC vpc; 495#if WITH_SEM_SWITCH_FULL 496 /* For communication between cti's and cti-chain. */ 497 SEM_BRANCH_TYPE pbb_br_type; 498 PCADDR pbb_br_npc; 499#endif 500 501 502 if (! CPU_IDESC_SEM_INIT_P (current_cpu)) 503 { 504 /* ??? 'twould be nice to move this up a level and only call it once. 505 On the other hand, in the "let's go fast" case the test is only done 506 once per pbb (since we only return to the main loop at the end of 507 a pbb). And in the "let's run until we're done" case we don't return 508 until the program exits. */ 509 510#if WITH_SEM_SWITCH_FULL 511#if defined (__GNUC__) 512/* ??? Later maybe paste sem-switch.c in when building mainloop.c. */ 513#define DEFINE_LABELS 514#include "sem-media-switch.c" 515#endif 516#else 517 sh64_media_sem_init_idesc_table (current_cpu); 518#endif 519 520 /* Initialize the "begin (compile) a pbb" virtual insn. */ 521 vpc = CPU_SCACHE_PBB_BEGIN (current_cpu); 522 SEM_SET_FULL_CODE (SEM_ARGBUF (vpc), 523 & CPU_IDESC (current_cpu) [SH64_MEDIA_INSN_X_BEGIN]); 524 vpc->argbuf.idesc = & CPU_IDESC (current_cpu) [SH64_MEDIA_INSN_X_BEGIN]; 525 526 CPU_IDESC_SEM_INIT_P (current_cpu) = 1; 527 } 528 529 CPU_RUNNING_P (current_cpu) = 1; 530 /* ??? In the case where we're returning to the main loop after every 531 pbb we don't want to call pbb_begin each time (which hashes on the pc 532 and does a table lookup). A way to speed this up is to save vpc 533 between calls. */ 534 vpc = sh64_media_pbb_begin (current_cpu, FAST_P); 535 536 do 537 { 538/* begin full-exec-pbb */ 539{ 540#if (! FAST_P && WITH_SEM_SWITCH_FULL) || (FAST_P && WITH_SEM_SWITCH_FAST) 541#define DEFINE_SWITCH 542#define WITH_ISA_COMPACT 543#include "sem-media-switch.c" 544#else 545 vpc = execute (current_cpu, vpc, FAST_P); 546#endif 547} 548/* end full-exec-pbb */ 549 } 550 while (CPU_RUNNING_P (current_cpu)); 551} 552 553#undef FAST_P 554 555 556#define FAST_P 1 557 558void 559sh64_media_engine_run_fast (SIM_CPU *current_cpu) 560{ 561 SIM_DESC current_state = CPU_STATE (current_cpu); 562 SCACHE *scache = CPU_SCACHE_CACHE (current_cpu); 563 /* virtual program counter */ 564 SEM_PC vpc; 565#if WITH_SEM_SWITCH_FAST 566 /* For communication between cti's and cti-chain. */ 567 SEM_BRANCH_TYPE pbb_br_type; 568 PCADDR pbb_br_npc; 569#endif 570 571 572 if (! CPU_IDESC_SEM_INIT_P (current_cpu)) 573 { 574 /* ??? 'twould be nice to move this up a level and only call it once. 575 On the other hand, in the "let's go fast" case the test is only done 576 once per pbb (since we only return to the main loop at the end of 577 a pbb). And in the "let's run until we're done" case we don't return 578 until the program exits. */ 579 580#if WITH_SEM_SWITCH_FAST 581#if defined (__GNUC__) 582/* ??? Later maybe paste sem-switch.c in when building mainloop.c. */ 583#define DEFINE_LABELS 584#include "sem-media-switch.c" 585#endif 586#else 587 sh64_media_semf_init_idesc_table (current_cpu); 588#endif 589 590 /* Initialize the "begin (compile) a pbb" virtual insn. */ 591 vpc = CPU_SCACHE_PBB_BEGIN (current_cpu); 592 SEM_SET_FAST_CODE (SEM_ARGBUF (vpc), 593 & CPU_IDESC (current_cpu) [SH64_MEDIA_INSN_X_BEGIN]); 594 vpc->argbuf.idesc = & CPU_IDESC (current_cpu) [SH64_MEDIA_INSN_X_BEGIN]; 595 596 CPU_IDESC_SEM_INIT_P (current_cpu) = 1; 597 } 598 599 CPU_RUNNING_P (current_cpu) = 1; 600 /* ??? In the case where we're returning to the main loop after every 601 pbb we don't want to call pbb_begin each time (which hashes on the pc 602 and does a table lookup). A way to speed this up is to save vpc 603 between calls. */ 604 vpc = sh64_media_pbb_begin (current_cpu, FAST_P); 605 606 do 607 { 608/* begin fast-exec-pbb */ 609{ 610#if (! FAST_P && WITH_SEM_SWITCH_FULL) || (FAST_P && WITH_SEM_SWITCH_FAST) 611#define DEFINE_SWITCH 612#define WITH_ISA_COMPACT 613#include "sem-media-switch.c" 614#else 615 vpc = execute (current_cpu, vpc, FAST_P); 616#endif 617} 618/* end fast-exec-pbb */ 619 } 620 while (CPU_RUNNING_P (current_cpu)); 621} 622 623#undef FAST_P 624 625