Macro Operation x86 Opcode Notes
macro_252D NOT [mem+8] F7 /2 Bitwise complement
macro_2535 NEG [mem+8] F7 /3 Two's complement negate
macro_2509 MOV [mem+8], reg 89 Register to memory
macro_251C MOV [mem+8], imm C7 /0 Immediate to memory
macro_2512 CMOVcc synthesis or SETcc 0F 40-4F or 0F 90-9F Conditional with register
macro_2524 Conditional with imm Synthetic No direct x86 equivalent
macro_12A5 MOV [mem+8], 1 (?) C7 /0 or test/bit op Unclear, needs more context
FP Transcendental Operation Analysis (msrom-612, 0x1FFE-0x21E1)
Overview
This code block implements a floating-point transcendental function (likely FPATAN or similar) using polynomial approximation. The code demonstrates critical patterns for transferring data between TMP registers (computational domain) and ST registers (architectural FP stack).
UOP.020(source_constant, value_register, U2_flags)
- Transfer value to FP stack register
- Examples from code:
- 0x2001: UOP.020(CONST_00+024, ST0, U2.80) - prepare ST0
- 0x2002: ST7 = UOP.020(CONST_00+03C, ST7, U2.80) - update ST7
- 0x20C6: ST7 = UOP.020(CONST_00+014, TMP1, U2.80) - write TMP to ST7
- 0x20CD: TMP0 = UOP.020(CONST_0, ST0) - read ST0 to TMP
- 0x20D1: ST0 = UOP.020(CONST_00+032, TMP0, U2.80) - write TMP to ST0
- CONST values likely specify conversion mode or precision control
- Can operate bidirectionally: TMP to ST or ST to TMP
- U2.80 flag present when writing to architectural ST registers
UOP.220(constant, ST_register, U2_flags)
- Prepare FP stack register for operation
- Examples from code:
- 0x2004: UOP.220(CONST_0, ST0) - prepare ST0
- 0x20D8: UOP.220(CONST_00+024, ST0, U2.80) - prepare with cross-domain flag
- Appears before reading ST into TMP registers
- May mark ST register as readable or lock it for operation
UOP.7EE(operand1, operand2, operation_code, U2_flags)
- Complex FP operation producing ST result
- Examples from code:
- 0x207A: ST0 = UOP.7EE(TMP5, TMP6, CONST_00+032, U2.C9) - with EOM_Fl3
- 0x2086: ST0 = UOP.7EE(TMP6, TMP5, CONST_00+032, U2.C9) - with EOM_Fl3
- Takes two TMP operands, produces ST result
- CONST_00+032 appears to be operation/mode selector
- Always appears with U2.C9 flag at subroutine exit
- Always marked with EOM_Fl3 (subroutine return)
UFPOP_7X8(operand1, operand2, U2_flags)
- FP operation with stack pop
- Examples from code:
- 0x2139: ST0 = UFPOP_7X8(TMP0, TMP5, U2.49) - with EOM_Fl3
- 0x215C: ST0 = UFPOP_7X8(TMP7, TMP5, U2.49) - with EOM_Fl3
- 0x219E: ST0 = UFPOP_7X8(TMP3, TMP2, U2.49) - with EOM_Fl3
- 0x21AE: ST0 = UFPOP_7X8(TMP4, TMP2, U2.49) - with EOM_Fl3
- Takes TMP register inputs
- Produces ST0 result AND pops FP stack
- Always uses U2.49 flag
- Always marked with EOM_Fl3 (subroutine return)
UOP.262(operand1, operand2)
- Simple TMP to ST transfer or merge
- Examples from code:
- 0x21B8: ST0 = UOP.262(TMPA, ST0) - with EOM_Fl3
- 0x21C2: ST0 = UOP.262(TMPA, ST0) - with EOM_Fl3
- Used in special case handling (denormals, infinities)
- Appears to merge or conditionally update ST0
UOP.029(ST_register, ST_register)
- Extract field from FP register
- Example from code:
- 0x200A: TMPD = UOP.029(ST0, ST0)
- 0x20DE: TMPD = UOP.029(ST0, ST0)
- Result immediately masked with AND (0x004 in examples)
- Likely extracts FP classification bits (NaN, Inf, denormal flags)
UOP.060(FP_value, CONST_0)
- Extract FP field to integer
- Examples from code:
- 0x2019: TMPE = UOP.060(TMP0, CONST_0)
- 0x2020: TMPD = UOP.060(TMPA, CONST_0)
- 0x20ED: TMPE = UOP.060(TMP0, CONST_0)
- Extracts exponent or other FP fields for range reduction
UOP.061(FP_value, CONST_0)
- Extract FP field (variant of UOP.060)
- Examples from code:
- 0x2009: TMPC = UOP.061(ST0, CONST_0)
- 0x2036: TMPC = UOP.061(TMP0, CONST_0)
- 0x20DD: TMPC = UOP.061(TMP0, CONST_0)
- Result used for range comparisons
- Possibly extracts biased exponent
UOP.063(FP_value, CONST_0)
- Extract FP field (another variant)
- Example from code:
- 0x2035: TMPE = UOP.063(TMP3, CONST_0)
- Used after FXORS operation
UOP.064(FP_value, CONST_0)
- Extract FP field (exponent?)
- Examples from code:
- 0x2006: TMPC = UOP.064(ST0, CONST_0)
- 0x202E: TMPD = UOP.064(TMP0, CONST_0)
- 0x20DA: TMPC = UOP.064(ST0, CONST_0)
- Result often shifted left by 3 or 4 bits
- Likely extracts exponent for classification
UOP.0A1(CONST_0, FP_value)
- FP operation on value
- Example from code:
- 0x2034: TMP4 = UOP.0A1(CONST_0, TMP3)
- Purpose unclear - possibly absolute value or normalize
UOP.223(operand1, operand2)
- FP arithmetic operation
- Example from code:
- 0x201C: TMPA = UOP.223(TMP2, TMP0)
- Used in range reduction sequence
UOP.227(CONST_0, operand)
- FP operation
- Example from code:
- 0x201A: TMP0 = UOP.227(CONST_0, TMP0)
- Part of argument reduction
UOP.228(operand1, operand2)
- FP operation
- Example from code:
- 0x2018: TMP0 = UOP.228(TMP0, TMP1)
- Used before range reduction
UOP.267(operand1, operand2)
- FP operation
- Example from code:
- 0x2024: TMP0 = UOP.267(TMP2, TMP1)
- Part of computation sequence
Integer Operations on FP Exponents
UOP.124(operand1, operand2)
- Integer operation on FP exponent/sign
- Examples from code:
- 0x2090: TMPB = UOP.124(TMPB, TMPD)
- 0x20BE: TMPB = UOP.124(CONST_0, TMPD)
- 0x2134: TMPB = UOP.124(TMPB, TMPE)
- Result used with FXORS to apply sign changes
- Likely constructs sign/exponent bits for result
Based on observed patterns in code:
- U2.08 - Read architectural state flag
- 0x2005: TMP0 = FXORS(ST0, ST0, U2.08)
- 0x20D9: TMP0 = FXORS(ST0, ST0, U2.08)
- Allows reading ST registers in computational domain
- U2.20 - Write preparation or intermediate result flag
- 0x2072: TMP9 = ADD.DSZ32(EIP_30, REG.31, U2.20)
- 0x2191: TMP9 = ADD.DSZ32(EIP_30, REG.31, U2.20)
- Marks operations that prepare for architectural commit
- U2.49 - FP stack pop with result commit
- Always used with UFPOP_7X8
- Combines result commit with stack management
- Bit pattern: 0100 1001
- U2.4A - Exception or special completion flag
- 0x20D5: UOP.120(CONST_16+004, CONST_16+004, U2.4A) - with EOM_Fl3
- 0x21C6: UOP.120(CONST_16+004, CONST_16+004, U2.4A) - with EOM_Fl3
- Used at error/exception exits
- U2.4B - Normal setup/initialization flag
- 0x2000: UOP.120(CONST_0, CONST_0, U2.4B)
- 0x20D6: UOP.120(CONST_0, CONST_0, U2.4B)
- Appears at start of computation sequences
- U2.50 - Precision or mode control flag
- 0x201D: UOP.120(CONST_16+010, CONST_16+010, U2.50)
- 0x20CC: UOP.120(CONST_16+010, CONST_16+010, U2.50)
- Used before final result computation
- U2.80 - Cross-domain write enable (architectural commit)
- Used extensively with UOP.020 when writing to ST registers
- This is the key "make visible" flag
- Allows computational results to affect architectural state
- Bit pattern: 1000 0000
- U2.C9 - Complex cross-domain operation with commit
- Always used with UOP.7EE at subroutine returns
- Bit pattern: 1100 1001 (includes U2.80 bit)
- Indicates full architectural state update
- Step 1: Read ST registers into TMP domain
- UOP.220 prepares ST register
- FXORS, UOP.064, UOP.029 extract fields with U2.08 flag
- Exponent and special case checks
- Step 2: Range reduction and argument preparation
- UOP.061 extracts exponent
- Compare against ROM constants (CONSTROM.03D, CONSTROM.047, CONSTROM.03E)
- Branch to special case handlers if needed
- Step 3: Polynomial approximation in TMP domain
- Multiple FREADROM to load coefficients
- UOP.6E9 (FP multiply) and UOP.768 (FP add) for Horner's method
- All computation stays in TMP registers (invisible to architecture)
- Step 4: Result finalization
- UOP.120 operations with various U2 flags for mode setup
- FXORS with TMPB to apply final sign
- Step 5: Commit to architectural state
- UOP.0D8 updates next instruction pointer
- UOP.0D4 synchronizes pipeline
- UOP.7EE or UFPOP_7X8 writes result to ST0 with U2.C9 or U2.49
- EOM_Fl3 marks subroutine return
- Denormals: Check UOP.029 result & 0x004 at 0x200D, 0x20E1
- Underflow range: Compare exponent < CONSTROM.03D at 0x200E, 0x20E2
- Overflow range: Compare exponent >= CONSTROM.047 at 0x2012, 0x20E6
- Large arguments: Compare exponent >= CONSTROM.03E at 0x2038, 0x2110
- Each case has dedicated exit path with appropriate result handling
The U2.80 bit is the "architectural visibility" flag. Operations without this bit execute in a shadow computational domain where:
- TMP registers can be freely modified
- FP operations compute intermediate results
- No architectural state is changed
- Exceptions cannot occur (computation is speculative)
Only operations with U2.80 (or composite flags like U2.C9 containing it) can:
- Write to architectural ST registers
- Update FP status flags
- Trigger FP exceptions
- Make results visible to subsequent instructions
The Pentium Pro implemented SYSENTER and SYSEXIT before Intel documented them. Operating systems such as Linux 2.6 later enabled these instructions based on the later documented Pentium II behavior, and Pentium Pro systems crashed.
SYSENTER in Pentium Pro is identical in effect on both chips: CS/SS/EIP/ESP load from the SYSENTER MSRs, CPL becomes 0, and it faults #GP(0) if IA32_SYSENTER_CS is 0. The two chips only disagree on SYSEXIT.
- EIP is loaded from ESI
- ESP is loaded from ECX
- CS is loaded from DI, taken as-is
- SS is loaded from (BX & 0x1FC) | CPL
- CPL is set to 3
- Faults #GP(0) if IA32_SYSENTER_CS == 0
- Faults #GP(0) if (DI & 0x1FC) == 0, i.e. DI is a null selector
- No check exists anywhere on BX
- The DI-null check happens before CS, ESP, or CPL are written, so a null DI faults
The Pentium II SYSEXIT does not read DI, BX, or ESI at all. EIP is loaded from EDX, ESP from ECX, CS from (SYSENTER_CS & 0xFFFC) + 16, and SS from (SYSENTER_CS & 0xFFFC) + 24; CPL is set to 3. So: EIP comes from EDX on the Pentium II versus ESI on the Pentium Pro; CS and SS are computed from IA32_SYSENTER_CS on the Pentium II versus read directly from DI and BX on the Pentium Pro; ESP from ECX is the same on both. The Pentium II cannot load a null CS/SS unless IA32_SYSENTER_CS itself is misconfigured, since both are derived from it; the Pentium Pro has no such protection for SS. The Pentium II's SYSEXIT also ANDs an internal "SystemFlags" word with 0x1FF; the Pentium Pro's SYSEXIT never touches it.
Because a normal kernel leaves DI/BX/ESI full of leftover values from syscall argument handling, the Pentium Pro version silently uses that leftover data as CS/SS/EIP. This matches Intel's Pentium Pro erratum #82, "SYSENTER/SYSEXIT instructions can implicitly load null segment selector to SS and CS registers."
For CPUID detection: checking "family 6, model < 3, stepping < 3" under-excludes affected chips. Comparing the combined CPUID signature and requiring it to be >= 0x633 before trusting the documented behavior excludes every Pentium Pro in one comparison.
Pentium Pro checks DI for null and faults before committing CS/ESP/CPL; no equivalent check exists for BX on the Pentium Pro
Summary: Pentium Pro reads EIP/CS/SS from ESI/DI/BX; Pentium II computes CS/SS from IA32_SYSENTER_CS and reads EIP from EDX; ESP comes from ECX on both
Pentium II's SYSEXIT ANDs "SystemFlags" with 0x1FF; Pentium Pro's SYSEXIT never touches it; Pentium II's SYSENTER also masks it, with a different mask
- "SystemFlags/ArithFlags" ? architectural EFLAGS register or an internal bookkeeping word, how internal flags work.
- The exact fault sequence that produced the observed crash (double fault vs. triple fault vs. hang), and under what register-garbage conditions. Two sequential #GP faults are not automatically a double fault under the exception-classification rules, so a chain of "faults on first user-mode fetch, then faults again on IRET" needs a mechanism beyond just "#GP followed by #GP"
- Whether a half-committed state (CPL/CS updated, SS not) can actually occur on the Pentium Pro via the unchecked BX/SS path, or whether some later, undecoded commit step also validates BX before it takes effect
- Whether an Event or other intrruption can arrive in a way that exposes partially-updated CPL/CS/SS state from outside the instruction
- Exact semantics of several microcode opcodes (UOP.203, UOP.208, USEGOP4's immediate operands) are not decoded yet