68285831e56c833820768276e7f590637194f6cc
[reactos.git] / lib / fast486 / common.inl
1 /*
2 * Fast486 386/486 CPU Emulation Library
3 * common.inl
4 *
5 * Copyright (C) 2014 Aleksandar Andrejevic <theflash AT sdf DOT lonestar DOT org>
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 */
21
22 #include "common.h"
23 #include "fpu.h"
24
25 /* PUBLIC FUNCTIONS ***********************************************************/
26
27 #if defined (__GNUC__)
28 #define CountLeadingZeros64(x) __builtin_clzll(x)
29
30 /*
31 #elif (_MSC_VER >= 1500) && defined(_WIN64)
32 #define CountLeadingZeros64(x) __lzcnt64(x)
33 #elif (_MSC_VER >= 1500)
34 #define CountLeadingZeros64(x) ((x) > 0xFFFFFFFFULL) ? __lzcnt((x) >> 32) \
35 : (__lzcnt(x) + 32)
36 */
37
38 #else
39 FORCEINLINE
40 ULONG
41 CountLeadingZeros64(ULONGLONG Value)
42 {
43 ULONG Count = 0;
44 Value = ~Value;
45 while ((LONGLONG)Value < 0)
46 {
47 Count++;
48 Value <<= 1;
49 }
50 return Count;
51 }
52 #endif
53
54 FORCEINLINE
55 INT
56 FASTCALL
57 Fast486GetCurrentPrivLevel(PFAST486_STATE State)
58 {
59 /* Return the CPL, or 3 if we're in virtual 8086 mode */
60 return (!State->Flags.Vm) ? State->Cpl : 3;
61 }
62
63 FORCEINLINE
64 ULONG
65 FASTCALL
66 Fast486GetPageTableEntry(PFAST486_STATE State,
67 ULONG VirtualAddress,
68 BOOLEAN MarkAsDirty)
69 {
70 ULONG PdeIndex = GET_ADDR_PDE(VirtualAddress);
71 ULONG PteIndex = GET_ADDR_PTE(VirtualAddress);
72 FAST486_PAGE_DIR DirectoryEntry;
73 FAST486_PAGE_TABLE TableEntry;
74 ULONG PageDirectory = State->ControlRegisters[FAST486_REG_CR3];
75
76 if ((State->Tlb != NULL)
77 && (State->Tlb[VirtualAddress >> 12] != INVALID_TLB_FIELD))
78 {
79 /* Return the cached entry */
80 return State->Tlb[VirtualAddress >> 12];
81 }
82
83 /* Read the directory entry */
84 State->MemReadCallback(State,
85 PageDirectory + PdeIndex * sizeof(ULONG),
86 &DirectoryEntry.Value,
87 sizeof(DirectoryEntry));
88
89 /* Make sure it is present */
90 if (!DirectoryEntry.Present) return 0;
91
92 /* Was the directory entry accessed before? */
93 if (!DirectoryEntry.Accessed)
94 {
95 /* Well, it is now */
96 DirectoryEntry.Accessed = TRUE;
97
98 /* Write back the directory entry */
99 State->MemWriteCallback(State,
100 PageDirectory + PdeIndex * sizeof(ULONG),
101 &DirectoryEntry.Value,
102 sizeof(DirectoryEntry));
103 }
104
105 /* Read the table entry */
106 State->MemReadCallback(State,
107 (DirectoryEntry.TableAddress << 12)
108 + PteIndex * sizeof(ULONG),
109 &TableEntry.Value,
110 sizeof(TableEntry));
111
112 /* Make sure it is present */
113 if (!TableEntry.Present) return 0;
114
115 if (MarkAsDirty) TableEntry.Dirty = TRUE;
116
117 /* Was the table entry accessed before? */
118 if (!TableEntry.Accessed)
119 {
120 /* Well, it is now */
121 TableEntry.Accessed = TRUE;
122
123 /* Write back the table entry */
124 State->MemWriteCallback(State,
125 (DirectoryEntry.TableAddress << 12)
126 + PteIndex * sizeof(ULONG),
127 &TableEntry.Value,
128 sizeof(TableEntry));
129 }
130
131 /*
132 * The resulting permissions depend on the permissions
133 * in the page directory table too
134 */
135 TableEntry.Writeable &= DirectoryEntry.Writeable;
136 TableEntry.Usermode &= DirectoryEntry.Usermode;
137
138 if (State->Tlb != NULL)
139 {
140 /* Set the TLB entry */
141 State->Tlb[VirtualAddress >> 12] = TableEntry.Value;
142 }
143
144 /* Return the table entry */
145 return TableEntry.Value;
146 }
147
148 FORCEINLINE
149 BOOLEAN
150 FASTCALL
151 Fast486ReadLinearMemory(PFAST486_STATE State,
152 ULONG LinearAddress,
153 PVOID Buffer,
154 ULONG Size)
155 {
156 /* Check if paging is enabled */
157 if (State->ControlRegisters[FAST486_REG_CR0] & FAST486_CR0_PG)
158 {
159 ULONG Page;
160 FAST486_PAGE_TABLE TableEntry;
161 INT Cpl = Fast486GetCurrentPrivLevel(State);
162 ULONG BufferOffset = 0;
163
164 for (Page = PAGE_ALIGN(LinearAddress);
165 Page <= PAGE_ALIGN(LinearAddress + Size - 1);
166 Page += FAST486_PAGE_SIZE)
167 {
168 ULONG PageOffset = 0, PageLength = FAST486_PAGE_SIZE;
169
170 /* Get the table entry */
171 TableEntry.Value = Fast486GetPageTableEntry(State, Page, FALSE);
172
173 if (!TableEntry.Present || (!TableEntry.Usermode && (Cpl > 0)))
174 {
175 /* Exception */
176 Fast486ExceptionWithErrorCode(State,
177 FAST486_EXCEPTION_PF,
178 TableEntry.Present | (State->Cpl ? 0x04 : 0));
179 return FALSE;
180 }
181
182 /* Check if this is the first page */
183 if (Page == PAGE_ALIGN(LinearAddress))
184 {
185 /* Start reading from the offset from the beginning of the page */
186 PageOffset = PAGE_OFFSET(LinearAddress);
187 PageLength -= PageOffset;
188 }
189
190 /* Check if this is the last page */
191 if (Page == PAGE_ALIGN(LinearAddress + Size - 1))
192 {
193 /* Read only a part of the page */
194 PageLength = PAGE_OFFSET(LinearAddress + Size - 1) - PageOffset + 1;
195 }
196
197 /* Read the memory */
198 State->MemReadCallback(State,
199 (TableEntry.Address << 12) | PageOffset,
200 (PVOID)((ULONG_PTR)Buffer + BufferOffset),
201 PageLength);
202
203 BufferOffset += PageLength;
204 }
205 }
206 else
207 {
208 /* Read the memory */
209 State->MemReadCallback(State, LinearAddress, Buffer, Size);
210 }
211
212 return TRUE;
213 }
214
215 FORCEINLINE
216 BOOLEAN
217 FASTCALL
218 Fast486WriteLinearMemory(PFAST486_STATE State,
219 ULONG LinearAddress,
220 PVOID Buffer,
221 ULONG Size)
222 {
223 /* Check if paging is enabled */
224 if (State->ControlRegisters[FAST486_REG_CR0] & FAST486_CR0_PG)
225 {
226 ULONG Page;
227 FAST486_PAGE_TABLE TableEntry;
228 INT Cpl = Fast486GetCurrentPrivLevel(State);
229 ULONG BufferOffset = 0;
230
231 for (Page = PAGE_ALIGN(LinearAddress);
232 Page <= PAGE_ALIGN(LinearAddress + Size - 1);
233 Page += FAST486_PAGE_SIZE)
234 {
235 ULONG PageOffset = 0, PageLength = FAST486_PAGE_SIZE;
236
237 /* Get the table entry */
238 TableEntry.Value = Fast486GetPageTableEntry(State, Page, TRUE);
239
240 if ((!TableEntry.Present || (!TableEntry.Usermode && (Cpl > 0)))
241 || ((State->ControlRegisters[FAST486_REG_CR0] & FAST486_CR0_WP)
242 && !TableEntry.Writeable))
243 {
244 /* Exception */
245 Fast486ExceptionWithErrorCode(State,
246 FAST486_EXCEPTION_PF,
247 TableEntry.Present | 0x02 | (State->Cpl ? 0x04 : 0));
248 return FALSE;
249 }
250
251 /* Check if this is the first page */
252 if (Page == PAGE_ALIGN(LinearAddress))
253 {
254 /* Start writing from the offset from the beginning of the page */
255 PageOffset = PAGE_OFFSET(LinearAddress);
256 PageLength -= PageOffset;
257 }
258
259 /* Check if this is the last page */
260 if (Page == PAGE_ALIGN(LinearAddress + Size - 1))
261 {
262 /* Write only a part of the page */
263 PageLength = PAGE_OFFSET(LinearAddress + Size - 1) - PageOffset + 1;
264 }
265
266 /* Write the memory */
267 State->MemWriteCallback(State,
268 (TableEntry.Address << 12) | PageOffset,
269 (PVOID)((ULONG_PTR)Buffer + BufferOffset),
270 PageLength);
271
272 BufferOffset += PageLength;
273 }
274 }
275 else
276 {
277 /* Write the memory */
278 State->MemWriteCallback(State, LinearAddress, Buffer, Size);
279 }
280
281 return TRUE;
282 }
283
284 FORCEINLINE
285 VOID
286 FASTCALL
287 Fast486Exception(PFAST486_STATE State,
288 FAST486_EXCEPTIONS ExceptionCode)
289 {
290 /* Call the internal function */
291 Fast486ExceptionWithErrorCode(State, ExceptionCode, 0);
292 }
293
294 FORCEINLINE
295 BOOLEAN
296 FASTCALL
297 Fast486StackPush(PFAST486_STATE State,
298 ULONG Value)
299 {
300 BOOLEAN Size = State->SegmentRegs[FAST486_REG_CS].Size;
301
302 /* The OPSIZE prefix toggles the size */
303 if (State->PrefixFlags & FAST486_PREFIX_OPSIZE) Size = !Size;
304
305 if (Size)
306 {
307 /* 32-bit size */
308
309 /* Check if ESP is between 1 and 3 */
310 if (State->GeneralRegs[FAST486_REG_ESP].Long >= 1
311 && State->GeneralRegs[FAST486_REG_ESP].Long <= 3)
312 {
313 Fast486Exception(State, FAST486_EXCEPTION_SS);
314 return FALSE;
315 }
316
317 /* Subtract ESP by 4 */
318 State->GeneralRegs[FAST486_REG_ESP].Long -= sizeof(ULONG);
319
320 /* Store the value in SS:ESP */
321 return Fast486WriteMemory(State,
322 FAST486_REG_SS,
323 State->GeneralRegs[FAST486_REG_ESP].Long,
324 &Value,
325 sizeof(ULONG));
326 }
327 else
328 {
329 /* 16-bit size */
330 USHORT ShortValue = LOWORD(Value);
331
332 /* Check if SP is 1 */
333 if (State->GeneralRegs[FAST486_REG_ESP].LowWord == 1)
334 {
335 Fast486Exception(State, FAST486_EXCEPTION_SS);
336 return FALSE;
337 }
338
339 /* Subtract SP by 2 */
340 State->GeneralRegs[FAST486_REG_ESP].LowWord -= sizeof(USHORT);
341
342 /* Store the value in SS:SP */
343 return Fast486WriteMemory(State,
344 FAST486_REG_SS,
345 State->GeneralRegs[FAST486_REG_ESP].LowWord,
346 &ShortValue,
347 sizeof(USHORT));
348 }
349 }
350
351 FORCEINLINE
352 BOOLEAN
353 FASTCALL
354 Fast486StackPop(PFAST486_STATE State,
355 PULONG Value)
356 {
357 BOOLEAN Size = State->SegmentRegs[FAST486_REG_CS].Size;
358
359 /* The OPSIZE prefix toggles the size */
360 TOGGLE_OPSIZE(Size);
361
362 if (Size)
363 {
364 /* 32-bit size */
365 ULONG LongValue;
366
367 /* Check if ESP is 0xFFFFFFFF */
368 if (State->GeneralRegs[FAST486_REG_ESP].Long == 0xFFFFFFFF)
369 {
370 Fast486Exception(State, FAST486_EXCEPTION_SS);
371 return FALSE;
372 }
373
374 /* Read the value from SS:ESP */
375 if (!Fast486ReadMemory(State,
376 FAST486_REG_SS,
377 State->GeneralRegs[FAST486_REG_ESP].Long,
378 FALSE,
379 &LongValue,
380 sizeof(LongValue)))
381 {
382 /* An exception occurred */
383 return FALSE;
384 }
385
386 /* Increment ESP by 4 */
387 State->GeneralRegs[FAST486_REG_ESP].Long += sizeof(ULONG);
388
389 /* Store the value in the result */
390 *Value = LongValue;
391 }
392 else
393 {
394 /* 16-bit size */
395 USHORT ShortValue;
396
397 /* Check if SP is 0xFFFF */
398 if (State->GeneralRegs[FAST486_REG_ESP].LowWord == 0xFFFF)
399 {
400 Fast486Exception(State, FAST486_EXCEPTION_SS);
401 return FALSE;
402 }
403
404 /* Read the value from SS:SP */
405 if (!Fast486ReadMemory(State,
406 FAST486_REG_SS,
407 State->GeneralRegs[FAST486_REG_ESP].LowWord,
408 FALSE,
409 &ShortValue,
410 sizeof(ShortValue)))
411 {
412 /* An exception occurred */
413 return FALSE;
414 }
415
416 /* Increment SP by 2 */
417 State->GeneralRegs[FAST486_REG_ESP].LowWord += sizeof(USHORT);
418
419 /* Store the value in the result */
420 *Value = ShortValue;
421 }
422
423 return TRUE;
424 }
425
426 FORCEINLINE
427 BOOLEAN
428 FASTCALL
429 Fast486ReadDescriptorEntry(PFAST486_STATE State,
430 USHORT Selector,
431 PBOOLEAN EntryValid,
432 PFAST486_GDT_ENTRY Entry)
433 {
434 if (!(Selector & SEGMENT_TABLE_INDICATOR))
435 {
436 /* Make sure the GDT contains the entry */
437 if (GET_SEGMENT_INDEX(Selector) >= (State->Gdtr.Size + 1))
438 {
439 *EntryValid = FALSE;
440 return TRUE;
441 }
442
443 /* Read the GDT */
444 if (!Fast486ReadLinearMemory(State,
445 State->Gdtr.Address
446 + GET_SEGMENT_INDEX(Selector),
447 Entry,
448 sizeof(*Entry)))
449 {
450 /* Exception occurred */
451 *EntryValid = FALSE;
452 return FALSE;
453 }
454 }
455 else
456 {
457 /* Make sure the LDT contains the entry */
458 if (GET_SEGMENT_INDEX(Selector) >= (State->Ldtr.Limit + 1))
459 {
460 *EntryValid = FALSE;
461 return TRUE;
462 }
463
464 /* Read the LDT */
465 if (!Fast486ReadLinearMemory(State,
466 State->Ldtr.Base
467 + GET_SEGMENT_INDEX(Selector),
468 Entry,
469 sizeof(*Entry)))
470 {
471 /* Exception occurred */
472 *EntryValid = FALSE;
473 return FALSE;
474 }
475 }
476
477 *EntryValid = TRUE;
478 return TRUE;
479 }
480
481 FORCEINLINE
482 BOOLEAN
483 FASTCALL
484 Fast486LoadSegmentInternal(PFAST486_STATE State,
485 FAST486_SEG_REGS Segment,
486 USHORT Selector,
487 FAST486_EXCEPTIONS Exception)
488 {
489 PFAST486_SEG_REG CachedDescriptor;
490 BOOLEAN Valid;
491 FAST486_GDT_ENTRY GdtEntry;
492
493 ASSERT(Segment < FAST486_NUM_SEG_REGS);
494
495 /* Get the cached descriptor */
496 CachedDescriptor = &State->SegmentRegs[Segment];
497
498 /* Check for protected mode */
499 if ((State->ControlRegisters[FAST486_REG_CR0] & FAST486_CR0_PE) && !State->Flags.Vm)
500 {
501 if (!Fast486ReadDescriptorEntry(State, Selector, &Valid, &GdtEntry))
502 {
503 /* Exception occurred */
504 return FALSE;
505 }
506
507 if (!Valid)
508 {
509 /* Invalid selector */
510 Fast486ExceptionWithErrorCode(State, Exception, Selector);
511 }
512
513 if (Segment == FAST486_REG_SS)
514 {
515 /* Loading the stack segment */
516
517 if (GET_SEGMENT_INDEX(Selector) == 0)
518 {
519 Fast486Exception(State, Exception);
520 return FALSE;
521 }
522
523 if (!GdtEntry.SystemType)
524 {
525 /* This is a special descriptor */
526 Fast486ExceptionWithErrorCode(State, Exception, Selector);
527 return FALSE;
528 }
529
530 if (GdtEntry.Executable || !GdtEntry.ReadWrite)
531 {
532 Fast486ExceptionWithErrorCode(State, Exception, Selector);
533 return FALSE;
534 }
535
536 if ((GET_SEGMENT_RPL(Selector) != Fast486GetCurrentPrivLevel(State))
537 || (GET_SEGMENT_RPL(Selector) != GdtEntry.Dpl))
538 {
539 Fast486ExceptionWithErrorCode(State, Exception, Selector);
540 return FALSE;
541 }
542
543 if (!GdtEntry.Present)
544 {
545 Fast486ExceptionWithErrorCode(State, FAST486_EXCEPTION_SS, Selector);
546 return FALSE;
547 }
548 }
549 else if (Segment == FAST486_REG_CS)
550 {
551 /* Loading the code segment */
552
553 #ifndef FAST486_NO_PREFETCH
554 /* Invalidate the prefetch */
555 State->PrefetchValid = FALSE;
556 #endif
557
558 if (GET_SEGMENT_INDEX(Selector) == 0)
559 {
560 Fast486Exception(State, Exception);
561 return FALSE;
562 }
563
564 if (!GdtEntry.SystemType)
565 {
566 /* Must be a segment descriptor */
567 Fast486ExceptionWithErrorCode(State, Exception, Selector);
568 }
569
570 if (!GdtEntry.Present)
571 {
572 Fast486ExceptionWithErrorCode(State, Exception, Selector);
573 return FALSE;
574 }
575
576 if (!GdtEntry.Executable)
577 {
578 Fast486ExceptionWithErrorCode(State, Exception, Selector);
579 return FALSE;
580 }
581
582 if (GdtEntry.DirConf)
583 {
584 /* Conforming Code Segment */
585
586 if (GdtEntry.Dpl > Fast486GetCurrentPrivLevel(State))
587 {
588 /* Must be accessed from lower-privileged code */
589 Fast486ExceptionWithErrorCode(State, Exception, Selector);
590 return FALSE;
591 }
592 }
593 else
594 {
595 /* Regular code segment */
596
597 if ((GET_SEGMENT_RPL(Selector) > Fast486GetCurrentPrivLevel(State))
598 || (Fast486GetCurrentPrivLevel(State) != GdtEntry.Dpl))
599 {
600 Fast486ExceptionWithErrorCode(State, Exception, Selector);
601 return FALSE;
602 }
603 }
604
605 /* Update CPL */
606 State->Cpl = GET_SEGMENT_RPL(Selector);
607 }
608 else
609 {
610 /* Loading a data segment */
611
612 if (GET_SEGMENT_INDEX(Selector) != 0)
613 {
614 if (!GdtEntry.SystemType)
615 {
616 /* This is a special descriptor */
617 Fast486ExceptionWithErrorCode(State, Exception, Selector);
618 return FALSE;
619 }
620
621 if ((GET_SEGMENT_RPL(Selector) > GdtEntry.Dpl)
622 || (Fast486GetCurrentPrivLevel(State) > GdtEntry.Dpl))
623 {
624 Fast486ExceptionWithErrorCode(State, Exception, Selector);
625 return FALSE;
626 }
627
628 if (!GdtEntry.Present)
629 {
630 Fast486ExceptionWithErrorCode(State, Exception, Selector);
631 return FALSE;
632 }
633 }
634 else
635 {
636 /* This is a NULL selector */
637 RtlZeroMemory(&GdtEntry, sizeof(GdtEntry));
638 }
639 }
640
641 /* Update the cache entry */
642 CachedDescriptor->Selector = Selector;
643 CachedDescriptor->Base = GdtEntry.Base | (GdtEntry.BaseMid << 16) | (GdtEntry.BaseHigh << 24);
644 CachedDescriptor->Limit = GdtEntry.Limit | (GdtEntry.LimitHigh << 16);
645 CachedDescriptor->Accessed = GdtEntry.Accessed;
646 CachedDescriptor->ReadWrite = GdtEntry.ReadWrite;
647 CachedDescriptor->DirConf = GdtEntry.DirConf;
648 CachedDescriptor->Executable = GdtEntry.Executable;
649 CachedDescriptor->SystemType = GdtEntry.SystemType;
650 CachedDescriptor->Rpl = GET_SEGMENT_RPL(Selector);
651 CachedDescriptor->Dpl = GdtEntry.Dpl;
652 CachedDescriptor->Present = GdtEntry.Present;
653 CachedDescriptor->Size = GdtEntry.Size;
654
655 /* Check for page granularity */
656 if (GdtEntry.Granularity) CachedDescriptor->Limit <<= 12;
657 }
658 else
659 {
660 /* Update the selector and base */
661 CachedDescriptor->Selector = Selector;
662 CachedDescriptor->Base = Selector << 4;
663 }
664
665 return TRUE;
666 }
667
668 FORCEINLINE
669 BOOLEAN
670 FASTCALL
671 Fast486LoadSegment(PFAST486_STATE State,
672 FAST486_SEG_REGS Segment,
673 USHORT Selector)
674 {
675 return Fast486LoadSegmentInternal(State,
676 Segment,
677 Selector,
678 FAST486_EXCEPTION_GP);
679 }
680
681 FORCEINLINE
682 BOOLEAN
683 FASTCALL
684 Fast486ProcessGate(PFAST486_STATE State, USHORT Selector, ULONG Offset, BOOLEAN Call)
685 {
686 BOOLEAN Valid;
687 FAST486_SYSTEM_DESCRIPTOR Descriptor;
688
689 if (!Fast486ReadDescriptorEntry(State,
690 Selector,
691 &Valid,
692 (PFAST486_GDT_ENTRY)&Descriptor))
693 {
694 /* Exception occurred */
695 return FALSE;
696 }
697
698 if (!Valid)
699 {
700 /* Invalid selector */
701 Fast486ExceptionWithErrorCode(State, FAST486_EXCEPTION_GP, Selector);
702 return FALSE;
703 }
704
705 switch (Descriptor.Signature)
706 {
707 case FAST486_TASK_GATE_SIGNATURE:
708 {
709 Fast486TaskSwitch(State,
710 Call ? FAST486_TASK_CALL : FAST486_TASK_JUMP,
711 ((PFAST486_IDT_ENTRY)&Descriptor)->Selector);
712
713 return FALSE;
714 }
715
716 case FAST486_TSS_SIGNATURE:
717 {
718 Fast486TaskSwitch(State,
719 Call ? FAST486_TASK_CALL : FAST486_TASK_JUMP,
720 Selector);
721
722 return FALSE;
723 }
724
725 case FAST486_CALL_GATE_SIGNATURE:
726 {
727 // TODO: NOT IMPLEMENTED
728 UNIMPLEMENTED;
729 }
730
731 default:
732 {
733 return TRUE;
734 }
735 }
736 }
737
738 FORCEINLINE
739 BOOLEAN
740 FASTCALL
741 Fast486FetchByte(PFAST486_STATE State,
742 PUCHAR Data)
743 {
744 PFAST486_SEG_REG CachedDescriptor;
745 ULONG Offset;
746 #ifndef FAST486_NO_PREFETCH
747 ULONG LinearAddress;
748 #endif
749
750 /* Get the cached descriptor of CS */
751 CachedDescriptor = &State->SegmentRegs[FAST486_REG_CS];
752
753 Offset = (CachedDescriptor->Size) ? State->InstPtr.Long
754 : State->InstPtr.LowWord;
755 #ifndef FAST486_NO_PREFETCH
756 LinearAddress = CachedDescriptor->Base + Offset;
757
758 if (State->PrefetchValid
759 && (LinearAddress >= State->PrefetchAddress)
760 && ((LinearAddress + sizeof(UCHAR)) <= (State->PrefetchAddress + FAST486_CACHE_SIZE)))
761 {
762 *Data = *(PUCHAR)&State->PrefetchCache[LinearAddress - State->PrefetchAddress];
763 }
764 else
765 #endif
766 {
767 /* Read from memory */
768 if (!Fast486ReadMemory(State,
769 FAST486_REG_CS,
770 Offset,
771 TRUE,
772 Data,
773 sizeof(UCHAR)))
774 {
775 /* Exception occurred during instruction fetch */
776 return FALSE;
777 }
778 }
779
780 /* Advance the instruction pointer */
781 if (CachedDescriptor->Size) State->InstPtr.Long++;
782 else State->InstPtr.LowWord++;
783
784 return TRUE;
785 }
786
787 FORCEINLINE
788 BOOLEAN
789 FASTCALL
790 Fast486FetchWord(PFAST486_STATE State,
791 PUSHORT Data)
792 {
793 PFAST486_SEG_REG CachedDescriptor;
794 ULONG Offset;
795 #ifndef FAST486_NO_PREFETCH
796 ULONG LinearAddress;
797 #endif
798
799 /* Get the cached descriptor of CS */
800 CachedDescriptor = &State->SegmentRegs[FAST486_REG_CS];
801
802 Offset = (CachedDescriptor->Size) ? State->InstPtr.Long
803 : State->InstPtr.LowWord;
804
805 #ifndef FAST486_NO_PREFETCH
806 LinearAddress = CachedDescriptor->Base + Offset;
807
808 if (State->PrefetchValid
809 && (LinearAddress >= State->PrefetchAddress)
810 && ((LinearAddress + sizeof(USHORT)) <= (State->PrefetchAddress + FAST486_CACHE_SIZE)))
811 {
812 *Data = *(PUSHORT)&State->PrefetchCache[LinearAddress - State->PrefetchAddress];
813 }
814 else
815 #endif
816 {
817 /* Read from memory */
818 // FIXME: Fix byte order on big-endian machines
819 if (!Fast486ReadMemory(State,
820 FAST486_REG_CS,
821 Offset,
822 TRUE,
823 Data,
824 sizeof(USHORT)))
825 {
826 /* Exception occurred during instruction fetch */
827 return FALSE;
828 }
829 }
830
831 /* Advance the instruction pointer */
832 if (CachedDescriptor->Size) State->InstPtr.Long += sizeof(USHORT);
833 else State->InstPtr.LowWord += sizeof(USHORT);
834
835 return TRUE;
836 }
837
838 FORCEINLINE
839 BOOLEAN
840 FASTCALL
841 Fast486FetchDword(PFAST486_STATE State,
842 PULONG Data)
843 {
844 PFAST486_SEG_REG CachedDescriptor;
845 ULONG Offset;
846 #ifndef FAST486_NO_PREFETCH
847 ULONG LinearAddress;
848 #endif
849
850 /* Get the cached descriptor of CS */
851 CachedDescriptor = &State->SegmentRegs[FAST486_REG_CS];
852
853 Offset = (CachedDescriptor->Size) ? State->InstPtr.Long
854 : State->InstPtr.LowWord;
855
856 #ifndef FAST486_NO_PREFETCH
857 LinearAddress = CachedDescriptor->Base + Offset;
858
859 if (State->PrefetchValid
860 && (LinearAddress >= State->PrefetchAddress)
861 && ((LinearAddress + sizeof(ULONG)) <= (State->PrefetchAddress + FAST486_CACHE_SIZE)))
862 {
863 *Data = *(PULONG)&State->PrefetchCache[LinearAddress - State->PrefetchAddress];
864 }
865 else
866 #endif
867 {
868 /* Read from memory */
869 // FIXME: Fix byte order on big-endian machines
870 if (!Fast486ReadMemory(State,
871 FAST486_REG_CS,
872 Offset,
873 TRUE,
874 Data,
875 sizeof(ULONG)))
876 {
877 /* Exception occurred during instruction fetch */
878 return FALSE;
879 }
880 }
881
882 /* Advance the instruction pointer */
883 if (CachedDescriptor->Size) State->InstPtr.Long += sizeof(ULONG);
884 else State->InstPtr.LowWord += sizeof(ULONG);
885
886 return TRUE;
887 }
888
889 FORCEINLINE
890 BOOLEAN
891 FASTCALL
892 Fast486CalculateParity(UCHAR Number)
893 {
894 // See http://graphics.stanford.edu/~seander/bithacks.html#ParityLookupTable too...
895 return (0x9669 >> ((Number & 0x0F) ^ (Number >> 4))) & 1;
896 }
897
898 FORCEINLINE
899 BOOLEAN
900 FASTCALL
901 Fast486ParseModRegRm(PFAST486_STATE State,
902 BOOLEAN AddressSize,
903 PFAST486_MOD_REG_RM ModRegRm)
904 {
905 UCHAR ModRmByte, Mode, RegMem;
906
907 /* Fetch the MOD REG R/M byte */
908 if (!Fast486FetchByte(State, &ModRmByte))
909 {
910 /* Exception occurred */
911 return FALSE;
912 }
913
914 /* Unpack the mode and R/M */
915 Mode = ModRmByte >> 6;
916 RegMem = ModRmByte & 0x07;
917
918 /* Set the register operand */
919 ModRegRm->Register = (ModRmByte >> 3) & 0x07;
920
921 /* Check the mode */
922 if (Mode == 3)
923 {
924 /* The second operand is also a register */
925 ModRegRm->Memory = FALSE;
926 ModRegRm->SecondRegister = RegMem;
927
928 /* Done parsing */
929 return TRUE;
930 }
931
932 /* The second operand is memory */
933 ModRegRm->Memory = TRUE;
934
935 if (AddressSize)
936 {
937 if (RegMem == FAST486_REG_ESP)
938 {
939 UCHAR SibByte;
940 ULONG Scale, Index, Base;
941
942 /* Fetch the SIB byte */
943 if (!Fast486FetchByte(State, &SibByte))
944 {
945 /* Exception occurred */
946 return FALSE;
947 }
948
949 /* Unpack the scale, index and base */
950 Scale = 1 << (SibByte >> 6);
951 Index = (SibByte >> 3) & 0x07;
952 if (Index != FAST486_REG_ESP) Index = State->GeneralRegs[Index].Long;
953 else Index = 0;
954
955 if (((SibByte & 0x07) != FAST486_REG_EBP) || (Mode != 0))
956 {
957 /* Use the register a base */
958 Base = State->GeneralRegs[SibByte & 0x07].Long;
959 }
960 else
961 {
962 /* Fetch the base */
963 if (!Fast486FetchDword(State, &Base))
964 {
965 /* Exception occurred */
966 return FALSE;
967 }
968 }
969
970 if ((SibByte & 0x07) == FAST486_REG_ESP)
971 {
972 /* Check if there is no segment override */
973 if (!(State->PrefixFlags & FAST486_PREFIX_SEG))
974 {
975 /* Add a SS: prefix */
976 State->PrefixFlags |= FAST486_PREFIX_SEG;
977 State->SegmentOverride = FAST486_REG_SS;
978 }
979 }
980
981 /* Calculate the address */
982 ModRegRm->MemoryAddress = Base + Index * Scale;
983 }
984 else if (RegMem == FAST486_REG_EBP)
985 {
986 if (Mode) ModRegRm->MemoryAddress = State->GeneralRegs[FAST486_REG_EBP].Long;
987 else ModRegRm->MemoryAddress = 0;
988 }
989 else
990 {
991 /* Get the base from the register */
992 ModRegRm->MemoryAddress = State->GeneralRegs[RegMem].Long;
993 }
994
995 /* Check if there is no segment override */
996 if (!(State->PrefixFlags & FAST486_PREFIX_SEG))
997 {
998 /* Check if the default segment should be SS */
999 if ((RegMem == FAST486_REG_EBP) && Mode)
1000 {
1001 /* Add a SS: prefix */
1002 State->PrefixFlags |= FAST486_PREFIX_SEG;
1003 State->SegmentOverride = FAST486_REG_SS;
1004 }
1005 }
1006
1007 if (Mode == 1)
1008 {
1009 CHAR Offset;
1010
1011 /* Fetch the byte */
1012 if (!Fast486FetchByte(State, (PUCHAR)&Offset))
1013 {
1014 /* Exception occurred */
1015 return FALSE;
1016 }
1017
1018 /* Add the signed offset to the address */
1019 ModRegRm->MemoryAddress += (LONG)Offset;
1020 }
1021 else if ((Mode == 2) || ((Mode == 0) && (RegMem == FAST486_REG_EBP)))
1022 {
1023 LONG Offset;
1024
1025 /* Fetch the dword */
1026 if (!Fast486FetchDword(State, (PULONG)&Offset))
1027 {
1028 /* Exception occurred */
1029 return FALSE;
1030 }
1031
1032 /* Add the signed offset to the address */
1033 ModRegRm->MemoryAddress += Offset;
1034 }
1035 }
1036 else
1037 {
1038 /* Check the operand */
1039 switch (RegMem)
1040 {
1041 case 0:
1042 {
1043 /* [BX + SI] */
1044 ModRegRm->MemoryAddress = State->GeneralRegs[FAST486_REG_EBX].LowWord
1045 + State->GeneralRegs[FAST486_REG_ESI].LowWord;
1046 break;
1047 }
1048
1049 case 1:
1050 {
1051 /* [BX + DI] */
1052 ModRegRm->MemoryAddress = State->GeneralRegs[FAST486_REG_EBX].LowWord
1053 + State->GeneralRegs[FAST486_REG_EDI].LowWord;
1054 break;
1055 }
1056
1057 case 2:
1058 {
1059 /* SS:[BP + SI] */
1060 ModRegRm->MemoryAddress = State->GeneralRegs[FAST486_REG_EBP].LowWord
1061 + State->GeneralRegs[FAST486_REG_ESI].LowWord;
1062 break;
1063 }
1064
1065 case 3:
1066 {
1067 /* SS:[BP + DI] */
1068 ModRegRm->MemoryAddress = State->GeneralRegs[FAST486_REG_EBP].LowWord
1069 + State->GeneralRegs[FAST486_REG_EDI].LowWord;
1070 break;
1071 }
1072
1073 case 4:
1074 {
1075 /* [SI] */
1076 ModRegRm->MemoryAddress = State->GeneralRegs[FAST486_REG_ESI].LowWord;
1077 break;
1078 }
1079
1080 case 5:
1081 {
1082 /* [DI] */
1083 ModRegRm->MemoryAddress = State->GeneralRegs[FAST486_REG_EDI].LowWord;
1084 break;
1085 }
1086
1087 case 6:
1088 {
1089 if (Mode)
1090 {
1091 /* [BP] */
1092 ModRegRm->MemoryAddress = State->GeneralRegs[FAST486_REG_EBP].LowWord;
1093 }
1094 else
1095 {
1096 /* [constant] (added later) */
1097 ModRegRm->MemoryAddress = 0;
1098 }
1099
1100 break;
1101 }
1102
1103 case 7:
1104 {
1105 /* [BX] */
1106 ModRegRm->MemoryAddress = State->GeneralRegs[FAST486_REG_EBX].LowWord;
1107 break;
1108 }
1109 }
1110
1111 /* Check if there is no segment override */
1112 if (!(State->PrefixFlags & FAST486_PREFIX_SEG))
1113 {
1114 /* Check if the default segment should be SS */
1115 if ((RegMem == 2) || (RegMem == 3) || ((RegMem == 6) && Mode))
1116 {
1117 /* Add a SS: prefix */
1118 State->PrefixFlags |= FAST486_PREFIX_SEG;
1119 State->SegmentOverride = FAST486_REG_SS;
1120 }
1121 }
1122
1123 if (Mode == 1)
1124 {
1125 CHAR Offset;
1126
1127 /* Fetch the byte */
1128 if (!Fast486FetchByte(State, (PUCHAR)&Offset))
1129 {
1130 /* Exception occurred */
1131 return FALSE;
1132 }
1133
1134 /* Add the signed offset to the address */
1135 ModRegRm->MemoryAddress += (LONG)Offset;
1136 }
1137 else if ((Mode == 2) || ((Mode == 0) && (RegMem == 6)))
1138 {
1139 SHORT Offset;
1140
1141 /* Fetch the word */
1142 if (!Fast486FetchWord(State, (PUSHORT)&Offset))
1143 {
1144 /* Exception occurred */
1145 return FALSE;
1146 }
1147
1148 /* Add the signed offset to the address */
1149 ModRegRm->MemoryAddress += (LONG)Offset;
1150 }
1151
1152 /* Clear the top 16 bits */
1153 ModRegRm->MemoryAddress &= 0x0000FFFF;
1154 }
1155
1156 return TRUE;
1157 }
1158
1159 FORCEINLINE
1160 BOOLEAN
1161 FASTCALL
1162 Fast486ReadModrmByteOperands(PFAST486_STATE State,
1163 PFAST486_MOD_REG_RM ModRegRm,
1164 PUCHAR RegValue,
1165 PUCHAR RmValue)
1166 {
1167 FAST486_SEG_REGS Segment = FAST486_REG_DS;
1168
1169 if (RegValue)
1170 {
1171 /* Get the register value */
1172 if (ModRegRm->Register & 0x04)
1173 {
1174 /* AH, CH, DH, BH */
1175 *RegValue = State->GeneralRegs[ModRegRm->Register & 0x03].HighByte;
1176 }
1177 else
1178 {
1179 /* AL, CL, DL, BL */
1180 *RegValue = State->GeneralRegs[ModRegRm->Register & 0x03].LowByte;
1181 }
1182 }
1183
1184 if (RmValue)
1185 {
1186 if (!ModRegRm->Memory)
1187 {
1188 /* Get the second register value */
1189 if (ModRegRm->SecondRegister & 0x04)
1190 {
1191 /* AH, CH, DH, BH */
1192 *RmValue = State->GeneralRegs[ModRegRm->SecondRegister & 0x03].HighByte;
1193 }
1194 else
1195 {
1196 /* AL, CL, DL, BL */
1197 *RmValue = State->GeneralRegs[ModRegRm->SecondRegister & 0x03].LowByte;
1198 }
1199 }
1200 else
1201 {
1202 /* Check for the segment override */
1203 if (State->PrefixFlags & FAST486_PREFIX_SEG)
1204 {
1205 /* Use the override segment instead */
1206 Segment = State->SegmentOverride;
1207 }
1208
1209 /* Read memory */
1210 if (!Fast486ReadMemory(State,
1211 Segment,
1212 ModRegRm->MemoryAddress,
1213 FALSE,
1214 RmValue,
1215 sizeof(UCHAR)))
1216 {
1217 /* Exception occurred */
1218 return FALSE;
1219 }
1220 }
1221 }
1222
1223 return TRUE;
1224 }
1225
1226 FORCEINLINE
1227 BOOLEAN
1228 FASTCALL
1229 Fast486ReadModrmWordOperands(PFAST486_STATE State,
1230 PFAST486_MOD_REG_RM ModRegRm,
1231 PUSHORT RegValue,
1232 PUSHORT RmValue)
1233 {
1234 FAST486_SEG_REGS Segment = FAST486_REG_DS;
1235
1236 if (RegValue)
1237 {
1238 /* Get the register value */
1239 *RegValue = State->GeneralRegs[ModRegRm->Register].LowWord;
1240 }
1241
1242 if (RmValue)
1243 {
1244 if (!ModRegRm->Memory)
1245 {
1246 /* Get the second register value */
1247 *RmValue = State->GeneralRegs[ModRegRm->SecondRegister].LowWord;
1248 }
1249 else
1250 {
1251 /* Check for the segment override */
1252 if (State->PrefixFlags & FAST486_PREFIX_SEG)
1253 {
1254 /* Use the override segment instead */
1255 Segment = State->SegmentOverride;
1256 }
1257
1258 /* Read memory */
1259 if (!Fast486ReadMemory(State,
1260 Segment,
1261 ModRegRm->MemoryAddress,
1262 FALSE,
1263 RmValue,
1264 sizeof(USHORT)))
1265 {
1266 /* Exception occurred */
1267 return FALSE;
1268 }
1269 }
1270 }
1271
1272 return TRUE;
1273 }
1274
1275 FORCEINLINE
1276 BOOLEAN
1277 FASTCALL
1278 Fast486ReadModrmDwordOperands(PFAST486_STATE State,
1279 PFAST486_MOD_REG_RM ModRegRm,
1280 PULONG RegValue,
1281 PULONG RmValue)
1282 {
1283 FAST486_SEG_REGS Segment = FAST486_REG_DS;
1284
1285 if (RegValue)
1286 {
1287 /* Get the register value */
1288 *RegValue = State->GeneralRegs[ModRegRm->Register].Long;
1289 }
1290
1291 if (RmValue)
1292 {
1293 if (!ModRegRm->Memory)
1294 {
1295 /* Get the second register value */
1296 *RmValue = State->GeneralRegs[ModRegRm->SecondRegister].Long;
1297 }
1298 else
1299 {
1300 /* Check for the segment override */
1301 if (State->PrefixFlags & FAST486_PREFIX_SEG)
1302 {
1303 /* Use the override segment instead */
1304 Segment = State->SegmentOverride;
1305 }
1306
1307 /* Read memory */
1308 if (!Fast486ReadMemory(State,
1309 Segment,
1310 ModRegRm->MemoryAddress,
1311 FALSE,
1312 RmValue,
1313 sizeof(ULONG)))
1314 {
1315 /* Exception occurred */
1316 return FALSE;
1317 }
1318 }
1319 }
1320
1321 return TRUE;
1322 }
1323
1324 FORCEINLINE
1325 BOOLEAN
1326 FASTCALL
1327 Fast486WriteModrmByteOperands(PFAST486_STATE State,
1328 PFAST486_MOD_REG_RM ModRegRm,
1329 BOOLEAN WriteRegister,
1330 UCHAR Value)
1331 {
1332 FAST486_SEG_REGS Segment = FAST486_REG_DS;
1333
1334 if (WriteRegister)
1335 {
1336 /* Store the value in the register */
1337 if (ModRegRm->Register & 0x04)
1338 {
1339 /* AH, CH, DH, BH */
1340 State->GeneralRegs[ModRegRm->Register & 0x03].HighByte = Value;
1341 }
1342 else
1343 {
1344 /* AL, CL, DL, BL */
1345 State->GeneralRegs[ModRegRm->Register & 0x03].LowByte = Value;
1346 }
1347 }
1348 else
1349 {
1350 if (!ModRegRm->Memory)
1351 {
1352 /* Store the value in the second register */
1353 if (ModRegRm->SecondRegister & 0x04)
1354 {
1355 /* AH, CH, DH, BH */
1356 State->GeneralRegs[ModRegRm->SecondRegister & 0x03].HighByte = Value;
1357 }
1358 else
1359 {
1360 /* AL, CL, DL, BL */
1361 State->GeneralRegs[ModRegRm->SecondRegister & 0x03].LowByte = Value;
1362 }
1363 }
1364 else
1365 {
1366 /* Check for the segment override */
1367 if (State->PrefixFlags & FAST486_PREFIX_SEG)
1368 {
1369 /* Use the override segment instead */
1370 Segment = State->SegmentOverride;
1371 }
1372
1373 /* Write memory */
1374 if (!Fast486WriteMemory(State,
1375 Segment,
1376 ModRegRm->MemoryAddress,
1377 &Value,
1378 sizeof(UCHAR)))
1379 {
1380 /* Exception occurred */
1381 return FALSE;
1382 }
1383 }
1384 }
1385
1386 return TRUE;
1387 }
1388
1389 FORCEINLINE
1390 BOOLEAN
1391 FASTCALL
1392 Fast486WriteModrmWordOperands(PFAST486_STATE State,
1393 PFAST486_MOD_REG_RM ModRegRm,
1394 BOOLEAN WriteRegister,
1395 USHORT Value)
1396 {
1397 FAST486_SEG_REGS Segment = FAST486_REG_DS;
1398
1399 if (WriteRegister)
1400 {
1401 /* Store the value in the register */
1402 State->GeneralRegs[ModRegRm->Register].LowWord = Value;
1403 }
1404 else
1405 {
1406 if (!ModRegRm->Memory)
1407 {
1408 /* Store the value in the second register */
1409 State->GeneralRegs[ModRegRm->SecondRegister].LowWord = Value;
1410 }
1411 else
1412 {
1413 /* Check for the segment override */
1414 if (State->PrefixFlags & FAST486_PREFIX_SEG)
1415 {
1416 /* Use the override segment instead */
1417 Segment = State->SegmentOverride;
1418 }
1419
1420 /* Write memory */
1421 if (!Fast486WriteMemory(State,
1422 Segment,
1423 ModRegRm->MemoryAddress,
1424 &Value,
1425 sizeof(USHORT)))
1426 {
1427 /* Exception occurred */
1428 return FALSE;
1429 }
1430 }
1431 }
1432
1433 return TRUE;
1434 }
1435
1436 FORCEINLINE
1437 BOOLEAN
1438 FASTCALL
1439 Fast486WriteModrmDwordOperands(PFAST486_STATE State,
1440 PFAST486_MOD_REG_RM ModRegRm,
1441 BOOLEAN WriteRegister,
1442 ULONG Value)
1443 {
1444 FAST486_SEG_REGS Segment = FAST486_REG_DS;
1445
1446 if (WriteRegister)
1447 {
1448 /* Store the value in the register */
1449 State->GeneralRegs[ModRegRm->Register].Long = Value;
1450 }
1451 else
1452 {
1453 if (!ModRegRm->Memory)
1454 {
1455 /* Store the value in the second register */
1456 State->GeneralRegs[ModRegRm->SecondRegister].Long = Value;
1457 }
1458 else
1459 {
1460 /* Check for the segment override */
1461 if (State->PrefixFlags & FAST486_PREFIX_SEG)
1462 {
1463 /* Use the override segment instead */
1464 Segment = State->SegmentOverride;
1465 }
1466
1467 /* Write memory */
1468 if (!Fast486WriteMemory(State,
1469 Segment,
1470 ModRegRm->MemoryAddress,
1471 &Value,
1472 sizeof(ULONG)))
1473 {
1474 /* Exception occurred */
1475 return FALSE;
1476 }
1477 }
1478 }
1479
1480 return TRUE;
1481 }
1482
1483 #ifndef FAST486_NO_FPU
1484
1485 FORCEINLINE
1486 VOID
1487 FASTCALL
1488 Fast486FpuNormalize(PFAST486_STATE State,
1489 PFAST486_FPU_DATA_REG Data)
1490 {
1491 UINT LeadingZeros;
1492
1493 if (FPU_IS_NORMALIZED(Data)) return;
1494 if (FPU_IS_ZERO(Data))
1495 {
1496 Data->Exponent = 0;
1497 return;
1498 }
1499
1500 LeadingZeros = CountLeadingZeros64(Data->Mantissa);
1501
1502 if (LeadingZeros < Data->Exponent)
1503 {
1504 Data->Mantissa <<= LeadingZeros;
1505 Data->Exponent -= LeadingZeros;
1506 }
1507 else
1508 {
1509 /* Make it denormalized */
1510 Data->Mantissa <<= Data->Exponent - 1;
1511 Data->Exponent = 1;
1512
1513 /* Underflow */
1514 State->FpuStatus.Ue = TRUE;
1515 }
1516 }
1517
1518 FORCEINLINE
1519 USHORT
1520 FASTCALL
1521 Fast486GetValueTag(PFAST486_FPU_DATA_REG Data)
1522 {
1523 if (FPU_IS_ZERO(Data)) return FPU_TAG_ZERO;
1524 else if (FPU_IS_NAN(Data)) return FPU_TAG_SPECIAL;
1525 else return FPU_TAG_VALID;
1526 }
1527
1528 FORCEINLINE
1529 VOID
1530 FASTCALL
1531 Fast486FpuPush(PFAST486_STATE State,
1532 PFAST486_FPU_DATA_REG Data)
1533 {
1534 State->FpuStatus.Top--;
1535
1536 if (FPU_GET_TAG(0) == FPU_TAG_EMPTY)
1537 {
1538 FPU_ST(0) = *Data;
1539 FPU_SET_TAG(0, Fast486GetValueTag(Data));
1540 }
1541 else State->FpuStatus.Ie = TRUE;
1542 }
1543
1544 FORCEINLINE
1545 VOID
1546 FASTCALL
1547 Fast486FpuPop(PFAST486_STATE State)
1548 {
1549 if (FPU_GET_TAG(0) != FPU_TAG_EMPTY)
1550 {
1551 FPU_SET_TAG(0, FPU_TAG_EMPTY);
1552 State->FpuStatus.Top++;
1553 }
1554 else State->FpuStatus.Ie = TRUE;
1555 }
1556
1557 #endif
1558
1559 /* EOF */