|
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] Re: [PATCH 3/4] x86/hvm: Rework hpet_write() for improved code generation
On 28/08/2024 9:13 am, Jan Beulich wrote:
> On 27.08.2024 15:57, Andrew Cooper wrote:
>> In the HPET_STATUS handling, the use of __clear_bit(i, &new_val) is the only
>> thing causing it to be spilled to the stack. Furthemore we only care about
>> the bottom 3 bits, so rewrite it to be a plain for loop.
>>
>> For the {start,stop}_timer variables, these are spilled to the stack despite
>> the __{set,clear}_bit() calls.
> That's an observation from what the compiler happens to do? I don't see any
> other reason why they would need spilling; I expect it's merely a matter of
> registers better be used for other variables.
It is a consequence of how our helpers are written. I do expect it to
improve when I get around to reworking them.
For example, the Linux helpers have enough constant folding capabilities
to allow the compiler to turn:
{
int foo = 0;
...
__set_bit(1, &foo);
into:
{
int foo = 1;
as well as being able to emit LOCK AND/OR/XOR in place of LOCK BT{C,S,R}
for a constant bit position.
One thing I want to do, which I haven't figured out how to do yet, is to
allow the arch form to emit BT?Q forms.
Right now, code generation for PGC_* and PGT_* suffers quite a lot. We
mix between reg/imm logic, then spill to the stack because top bits
aren't within range for the "I" constraint on 32-bit instructions, issue
a BT?L reg/mem (which has much higher latency than any other form), then
pick it back off the stack to do more reg/imm logic.
I was wondering if, because of the always_inline, I could do something
like __builtin_constant_p(bit) && __builtin_object_size(addr, 0) >= 8
and emitting long-granular logic, which will be able to pick the imm/reg
form rather than turning into reg/mem.
But, I've not had time to experiment here, and I doubt I'll get around
to it soon.
Another optimisation we're lacking vs Linux is that our test_bit() has a
volatile pointer where Linux's is non-volatile. This makes a massive
difference for the ability to optimise looking at multiple bits.
> If we ever meant to build Xen
> with APX fully in use, that might change. IOW may I at least ask for
> s/are/happen to be/? I'm also a little irritated by "despite", but you're
> the native speaker. It would have seemed to me that e.g. "irrespective of"
> would better express what (I think) is meant.
"despite" isn't really the right term, but I also wouldn't have said it
was something to be irritated over.
What I was trying to say was "they're spilled to the stack even with the
__set_bit() calls removed". Which makes sense; they're values held for
almost the full duration of the function, that are not used in ~every
step of logic.
Interestingly, given that they're spilled to the stack, the __set_bit()
form is more efficient than the plain C "|= (1u << i);", but I'd still
like an implementation which could make that determination itself.
>
>> Again we only care about the bottom 3 bits, so
>> shrink the variables from long to int. Use for_each_set_bit() rather than
>> opencoding it at the end which amongst other things means the loop predicate
>> is no longer forced to the stack by the loop body.
>>
>> Signed-off-by: Andrew Cooper <andrew.cooper3@xxxxxxxxxx>
>> ---
>> CC: Jan Beulich <JBeulich@xxxxxxxx>
>> CC: Roger Pau Monné <roger.pau@xxxxxxxxxx>
>>
>> All in all, it's modest according to bloat-o-meter:
>>
>> add/remove: 0/0 grow/shrink: 0/1 up/down: 0/-29 (-29)
>> Function old new delta
>> hpet_write 2225 2196 -29
>>
>> but we have shrunk the stack frame by 8 bytes; 0x28 as opposed to 0x30
>> before.
> However, on the negative side all the first of the loops you touch now always
> takes 3 iterations, when previously we may have got away with as little as
> none. Is there a reason not to use
>
> for_each_set_bit ( i, new_val & ((1U << HPET_TIMER_NUM) - 1) )
>
> there (with the masking of the low bit possibly pulled out)?
There are multiple angles here.
First, I got an unexpected surprise on ARM with an expression, and while
this one won't pick up pointer const-ness, I can never remember what
MISRA's view on this is.
Second, this is the odd-loop-out compared to rest of the function, which
are all of the form "for ( i = 0; i < HPET_TIMER_NUM ;".
But perhaps most importantly, OSes don't touch this register. Xen not
at all, and Linux only in _hpet_print_config(). Neither bother
preserving/clearing it on suspend/resume, even when running the HPET in
legacy replacement mode.
I haven't checked windows behaviour, but I don't expect it to differ
here. This register simply isn't interesting for the preferred type of
interrupts (edge), and also isn't useful for an ISR handling a line
interrupt.
So my choice was based on which produced the smallest code, because it's
an dead-in-practice codepath.
>
>> @@ -533,19 +528,11 @@ static int cf_check hpet_write(
>> }
>>
>> /* stop/start timers whos state was changed by this write. */
>> - while (stop_timers)
>> - {
>> - i = ffsl(stop_timers) - 1;
>> - __clear_bit(i, &stop_timers);
>> + for_each_set_bit ( i, stop_timers )
>> hpet_stop_timer(h, i, guest_time);
>> - }
>>
>> - while (start_timers)
>> - {
>> - i = ffsl(start_timers) - 1;
>> - __clear_bit(i, &start_timers);
>> + for_each_set_bit ( i, start_timers )
>> hpet_set_timer(h, i, guest_time);
>> - }
> To avoid variable shadowing, I think you don't want to use i in these two
> loops. Alternatively the function scope i would need constraining to the
> individual loops.
Yeah, I was bitten by that on one of the ARM patches. I'll adjust.
~Andrew
|
![]() |
Lists.xenproject.org is hosted with RackSpace, monitoring our |