Deutsch   English   Français   Italiano  
<vrvukp$n29$1@reader1.panix.com>

View for Bookmarking (what is this?)
Look up another Usenet article

Path: ...!weretis.net!feeder9.news.weretis.net!panix!.POSTED.spitfire.i.gajendra.net!not-for-mail
From: cross@spitfire.i.gajendra.net (Dan Cross)
Newsgroups: comp.arch
Subject: Re: MSI interrupts
Date: Wed, 26 Mar 2025 04:08:57 -0000 (UTC)
Organization: PANIX Public Access Internet and UNIX, NYC
Message-ID: <vrvukp$n29$1@reader1.panix.com>
References: <vqto79$335c6$1@dont-email.me> <e4ecfb53ef4f33f10b6d2548d5930159@www.novabbs.org> <vrva4r$9ka$1@reader1.panix.com> <b1c74762d01f71cc1b8ac838dcf6d4fa@www.novabbs.org>
Injection-Date: Wed, 26 Mar 2025 04:08:57 -0000 (UTC)
Injection-Info: reader1.panix.com; posting-host="spitfire.i.gajendra.net:166.84.136.80";
	logging-data="23625"; mail-complaints-to="abuse@panix.com"
X-Newsreader: trn 4.0-test77 (Sep 1, 2010)
Originator: cross@spitfire.i.gajendra.net (Dan Cross)
Bytes: 10697
Lines: 250

In article <b1c74762d01f71cc1b8ac838dcf6d4fa@www.novabbs.org>,
MitchAlsup1 <mitchalsup@aol.com> wrote:
>On Tue, 25 Mar 2025 22:19:07 +0000, Dan Cross wrote:
>
>> In article <e4ecfb53ef4f33f10b6d2548d5930159@www.novabbs.org>,
>> MitchAlsup1 <mitchalsup@aol.com> wrote:
>>
>>>> Your stuff sounds fine for getting into the critsec; but it
>>>> doesn't help you once you're in it, and it seems like it may
>>>> hurt you if some higher-priority thing comes along and yanks
>>>> your lock out from under you.
>>>
>>>I conceded that point at least 2 days ago.
>>
>> Ok, now we're back to the question then: what purpose does it
>> serve?
>
>You (well Chris) can write critical sections where a low priority
>holder can give way to the higher priority requestor such that
>the low priority holder reverts back to before it entered CS,
>and does not see the lock being stolen.

You still have not defined what you mean by, "the lock being
stolen."

Tell you what.  Earlier, you posted some code for a critical
section that inserts a node into a linked list.  That code is
written in terms of some primitives (presumably intrinsics,
though you call them macros); `esmLOCKload`, `esmLOCKprefetch`,
`esmINTERFERENCE` and `esmLOCKstore`.  Here's that code, slightly
reformatted:

// Removes the element `from` from a doubly linked list, and
// inserts it into a (possibly different) doubly linked list,
// after the element pointed to be `to`.
// Assumes <stdbool.h>
bool
MoveElement(Element *from, Element *to)
{
	assert(from != NULL);
	assert(to != NULL);

	Element *from_next = esmLOCKload(from->next);
	Element *from_prev = esmLOCKload(from->prev);
	Element *to_next = esmLOCKload(to->next);
	esmLOCKprefetch(from_next);
	esmLOCKprefetch(from_prev);
	esmLOCKprefetch(to_next);
	if (!esmINTERFERENCE()) {
		// Unlink the "from" node from its list.
		from_prev->next = from_next;  // XXX: `from_prev` may be nil?
		from_next->prev = from_prev;  // XXX: `from_next` may be nil?
		// Insert the node after "to".
		to->next = from;
		to_next->prev = from;         // XXX: `to_net` may be nil?
		from->prev = to;
		esmLOCKstore(from->next, to_next);
		return true;
	}
	return false;
}

Leaving aside the unchecked boundary conditions I highlighted
above, there are other questions that this raises.  You
mentioned that the ESM stuff can use up to 8 cache lines in a
single atomic "event"; let's count how many this code may
encounter:

1. `from` may point to a cache line
2. `to` may point to a different cache line.
3. Does your architecture use a stack?  What sort of alignment
   criteria do you impose?   At first blush, it seems to me that
   the pointers `from_next`, `from_prev`, and `to_next` could be
   on the stack and if so, will be on at least one cache line,
   and possibly 2, if the call frame for `MoveElement` spans
   more than one.  Of course, it's impossible to say when this
   is compiled, but maybe you require that the stack is always
   aligned to a cache line boundary or something.  Or maybe
   these are all in registers and it's fine.
4. Depending on the size and alignment criteria of the the
   `Element` structure and the placement of the `next` and
   `prev` elements in the struct (unknowable from this example),
   `to->next` may be on another cache line.
5. Similarly, `from->next`
6. And `from->prev`
7. And `from_prev->next`
8. And `from_next->prev`
9. And `to_next->prev`.

So potentially this code _could_ touch 9 or 10 cache lines, more
than you said are supported for a single atomic event.  What
happens in that case?  Does the code just always return false?
The programmer best make sure the optimizer is keeping those
temporary pointers off the stack; good luck with a debug build.

Anyway, let's assume that's not the case, and we don't hit the
pathological case with cache lines, and we're not dealing with
edge cases at the front or end of the lists (so various next or
prev pointers are not NULL).  Why don't I try to explain what I
think this code is doing, and you tell me whether I'm right or
wrong.

If I understand this correctly, the `esmLOCKload`s are simply
loads, but they make sure that the compiler emits instructions
that tie into your atomic support framework.  Earlier you wrote,
"esmINTERFERENCE is a query on the state of th event and whether
a 2nd party has performed a memory action that kills the event."
Ok, I take this to mean _at the time of the store_, which
appears later in the program.  If it were simply a query at the
time it was written in the code, then it seems like it opens a
TOCTOU bug (what if something interfers after the check, but
before the the locked store at the end?).  Or perhaps it signals
to the architecture that this is the point to which the
processor should rollback, if anything subsequently fails; sort
of a `setjmp` kind of thing that conceptually returns twice.

Assuming nothing has poisoned the event, the various pointer
stores inside the conditional are conditionally performed; the
`esmLOCKstore` to `from->next` commits the event, at which
point all side effects are exposed and durable.

Is that correct?  Let's suppose that it is.

But later you wrote,

>So, if you want the property whereby the lock disappears on any
>control transfer out of the event {exception, interrupt, SVC, SVR, ...};
>then you want to use my ATOMIC stuff; otherwise, you can use the
>normal ATOMIC primitives everyone and his brother provide.

Well, what precisely do you mean here when you say, "if you want
the property whereby the lock disappears on any control transfer
out of the event"?

What I _hope_ you mean is that if you transfer "out of the
event" before the `esmLOCKstore` then anything that's been done
since the "event" started is rolled back, but NOT if control
transfer happens _after_ the `esmLOCKstore`.  If THAT were the
case, then this entire mechanism is useless.

Perhaps you would clarify?

But for the time being, let's assume that `esmLOCKstore` is
conceptually a commit that ends the atomic event, and the store
is durable and observable after that.  Let's see if we can
construct a simple spinlock from these primitives.

typedef struct Spinlock Spinlock;
struct Spinlock {
	uint32_t lock;
};

uint32_t
xcas(volatile Spinlock *lk, uint32_t expected, uint32_t val)
{
	uint32_t v = esmLOCKload(&lk->lock);
	if (v == expected && !esmINTERFERENCE()) {
		esmLOCKstore(&lk->lock, val)
	}
	return v;
}

void
spin_acquire(volatile Spinlock *lk)
{
	splhi();
	while (xcas(lk, 0, 1) != 0)
		relax();	// Spin loop intrinsic.
}

void
spin_release(volatile Spinlock *lk)
{
	xcas(lk, 1, 0);
	// Maybe we should `spllo` here, maybe not.  Either choice
	// reflects lots of assumptions about whether or not we might
	// take and hold multiple locks concurrently, etc.
	spllo();
}

Does this seem correct?  It is not immediately clear to me
whether `esmINTERFERENCE` is actually needed here, but
avoiding the store seemed fine.  One could imagine shorting
`xcas` to simply:
========== REMAINDER OF ARTICLE TRUNCATED ==========