Skip site navigation (1)Skip section navigation (2)
Date:      Wed, 24 Jan 2007 17:11:47 +1100 (EST)
From:      Bruce Evans <bde@zeta.org.au>
To:        Tijl Coosemans <tijl@ulyssis.org>
Cc:        freebsd-emulation@FreeBSD.org, Jung-uk Kim <jkim@FreeBSD.org>
Subject:   Re: linuxolator: tls_test results amd64
Message-ID:  <20070124160934.V36320@delplex.bde.org>
In-Reply-To: <200701232343.47316.tijl@ulyssis.org>
References:  <790a9fff0701211041j1176d00gd6dd75d0989cf4ec@mail.gmail.com> <200701232113.50766.tijl@ulyssis.org> <200701231555.57521.jkim@FreeBSD.org> <200701232343.47316.tijl@ulyssis.org>

next in thread | previous in thread | raw e-mail | index | archive | help
On Tue, 23 Jan 2007, Tijl Coosemans wrote:

> On Tuesday 23 January 2007 21:55, Jung-uk Kim wrote:
>> On Tuesday 23 January 2007 03:13 pm, Tijl Coosemans wrote:
>>> On Tuesday 23 January 2007 20:00, Jung-uk Kim wrote:
>>>> Second problem is MSR_KGSBASE is scrubbed by something during
>>>> context switch, i.e., it becomes 0 some times.
>>>
>>> You mean:
>>>
>>> *kernel sets gsbase and switches back to user mode
>>> *user program does things
>>
>> Yes.  BTW, glibc seems to use movw instead of movl to load %gs.  I
>> don't know if that makes difference, though.  It may have some
>> effect when glibc is built with -mtls-direct-seg-refs flag.  Need
>> confirmation.
>
> I think there's no difference between movw and movl. The first stores
> a 16 bit value in %gs, the second stores the lower 16 bits of a 32 bit
> value.

movl is a workaround for pessimizations and/or bugs in old versions of
gas.  Old versions of gas didn't understand operand sizes so they
generated pessimal operand size prefixes and in general (for other
instructions) made a mess when the source and target operands have
different sizes.  Gas now detects mismatched sizes but allows them
in this case:

%%%
GAS LISTING gas.s 			page 1


    1 0000 8EE8     		movl	%eax,%gs
    2 0002 8EE8     		mov	%eax,%gs
    3 0004 8EE8     		movw	%ax,%gs
    4 0006 8EE8     		mov	%ax,%gs
    5
    6 0008 8E2D0000 		movl	0,%gs
    6      0000
    7 000e 8E2D0000 		mov	0,%gs
    7      0000
    8 0014 8E2D0000 		movw	0,%gs
    8      0000
%%%

so the old workaround has no effect.

When I wrote my i386 assembler, I thought that the 32-bit source value
couldn't even exist for the case of move from memory.  It makes a
difference whether the load is a 16-bit or 32-bit one.  If it is 32-bit,
despite there not being an operand size prefix in 32-bit mode, then it
might trap where a 16-bit load would not, because only the 32-bit load
crosses a segment or page boundary.  I think it is always 16-bit.  Thus
asking for a 32-bit move to a segment register is just bogus.  My
assembler enforces this:

%%%
...
00001                                           	mov	dword ds,eax
*****                                                                   ^mismatched size
*****                                                                       ^mismatched size
00002                                           	mov	ds,eax
*****                                                                 ^mismatched size
00003 0000           8ED8                       	mov	word ds,ax
00004 0002           8ED8                       	mov	ds,ax
00005 
00006 0004   67      8E1E       0000            	mov	dword ds,[0]
*****                                                                   ^mismatched size
00007 0009   67      8E1E       0000            	mov	ds,[0]
00008 000E   67      8E1E       0000            	mov	ds,[0]
...
%%%

My assembler doesn't generate an operand size prefix, but generates an
address size prefix to optimize the loads from address 0 for space.
This is possible because these address is known to fit in 16 bits.  I
don't know how to make gas do this (cc -Os doesn't give it).

So if a 32-bit store is different and you actually want it, neither
assembler supports it directly (you have to manually add an operand
size prefix explicitly).

Moves _from_ segment registers are completely different.  For push
of a segment register, you normally want a 32-bit access to keep the
stack aligned and perhaps to avoid leaving garbage on the stack.
mov of a segment register to memory is little different.  Even pop
of a segment is little different -- it increments the stack pointer
by 4 for compatibility with push, but I think it doesn't access the
upper 16 bits.  IIRC, 32-bit writes of segment registers to another
register or memory write CPU-dependent garbage to the top 32 bits.
Newer CPUs are more likely to write 0's and older CPUs are more likely
to write pure garbage.  The garbage is most visible in debugger output.

Gas now gets this right by determining the operand sizes correctly
and generating the operand size prefix iff it is necessary.  It
generates the operand size prefix as necessary for popw in 32-bit mode,
so the prefix is not completely unnecessary for loads -- it is necessary
to do the correct stack adjustment.  Gas doesn't generate any operand
sizes for mov's to memory, and I think the memory access size is always
16 despite this, the same as for mov's from memoery.

FreeBSD still uses movl from segment registers in most places to ensure
not getting an operand size prefixes.  Unnecessary operand size prefixes
cost more on older CPUs.  On newer CPUs they get decoded into nothing
before reaching final pipeline stages.

My assembler has always got this completely wrong (except probably in
Linux versions that I haven't looked at), at least for push (and pop too)
and for mov's to registers.  It enforces a 16-bit operand size for
everything.  This is especially broken for push/pop in 32-bit mode.

Some of the CPU behaviours (especially for movl from a segreg to a general
register) seem to be only documented in newer CPU manuals.  I've never
seen or wrote documentation for the asm behaviours and have to write
the above programs to remember what they are.

Bruce



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?20070124160934.V36320>