public inbox for passt-dev@passt.top
 help / color / mirror / code / Atom feed
From: Stefano Brivio <sbrivio@redhat.com>
To: David Gibson <david@gibson.dropbear.id.au>
Cc: passt-dev@passt.top
Subject: Re: [PATCH 1/1] iov: iov tail helpers
Date: Wed, 6 Nov 2024 11:06:18 +0100	[thread overview]
Message-ID: <20241106110618.333e3c8e@elisabeth> (raw)
In-Reply-To: <ZyrWrshUBgHOGfWo@zatzit>

On Wed, 6 Nov 2024 13:38:38 +1100
David Gibson <david@gibson.dropbear.id.au> wrote:

> On Wed, Nov 06, 2024 at 01:56:31AM +0100, Stefano Brivio wrote:
> > On Tue,  5 Nov 2024 13:32:22 +1100
> > David Gibson <david@gibson.dropbear.id.au> wrote:
> >   
> > > In the vhost-user code we have a number of places where we need to locate
> > > a particular header within the guest-supplied IO vector.  We need to work
> > > out which buffer the header is in, and verify that it's contiguous and
> > > aligned as we need.  At the moment this is open-coded, but introduce a
> > > helper to make this more straightforward.
> > > 
> > > We add a new datatype 'struct iov_tail' representing an IO vector from
> > > which we've logically consumed some number of headers.  The IOV_PULL_HEADER
> > > macro consumes a new header from the vector, returning a pointer and
> > > updating the iov_tail.  
> > 
> > The interfaces look usable and straightforward to me. I find some names
> > and comments a bit obscure, though.  
> 
> Yeah, I don't love the names either.
> 
> > First off, I would intuitively say that the "tail" is always at the
> > end, and if we already consumed something, that's always at the "head".  
> 
> Right.. which is true in a sense.  The idea is you'd set one of these
> up, to cover a whole (say) frame, then pull bits off the front as you
> need it.  So, the iov_tail does represent the "tail", as in the
> unprocessed bit of the frame at each point...
> 
> > If we call the whole abstraction "tail", we risk ending up talking about
> > the tail of the tail, and the head of the tail. Consider this part from
> > the cover letter:  
> 
> .. but, yeah, heads of tails and tails of tails gets confusing.
> Unless we rewrite in LISP, I guess.
> 
> > > "iov tail", that is an iov from which you've
> > > already consumed (in some sense) some data from the beginning.   
> > 
> > ...in other words, that's an IO vector called tail, and we
> > already consumed some data from its head.
> > 
> > What about (iov-based) "batch"?  
> 
> I don't think "batch" is really any better, it's just unclear on a
> different set of axes.
> 
> Would "iov remainder" be any better?

Now that I read your reply, the "tail" name becomes clearer as it's
clear that we just use it for left-overs. I don't think "remainder"
makes it much clearer, so I would rather stick to "tail" and try to
explain the purpose of struct iov_tail a bit more specifically (see
below).

> > > Signed-off-by: David Gibson <david@gibson.dropbear.id.au>
> > > ---
> > >  iov.c | 83 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
> > >  iov.h | 24 +++++++++++++++++
> > >  2 files changed, 107 insertions(+)
> > > 
> > > diff --git a/iov.c b/iov.c
> > > index 3f9e229..3d384ae 100644
> > > --- a/iov.c
> > > +++ b/iov.c
> > > @@ -156,3 +156,86 @@ size_t iov_size(const struct iovec *iov, size_t iov_cnt)
> > >  
> > >  	return len;
> > >  }
> > > +
> > > +/**
> > > + * iov_tail_shorten() - Remove any buffers from an IOV tail that are wholly consumed  
> > 
> > "Remove" is a bit difficult to interpret (does it deallocate? Throw
> > data away^), I would rather say that we... detach (?) those buffers from
> > the batch/tail.  
> 
> Yeah, I'm not sure how to express this either.  In a sense this
> operation is a logical no-op: it shouldn't change the results of any
> future operation, but it might make them slightly faster.

Oh, this wasn't clear to me. I guess that "reduce", "minimise", or
"prune" would convey this better.

> > This operation itself raises a question though: if the batch already
> > carries the information that some buffers were completely consumed,
> > should it ever be in a state where we want to drop these buffers from
> > it?
> > 
> > That is, it sounds like we have some other operation that allows it to
> > be in an inconsistent state.  
> 
> Sort of, yes, but I think this is the right design choice.  It means
> we can trivially construct one of these things with an arbitrary
> offset, and we only do the work of stepping through the buffers when
> we actually have to.  We can discard bytes simply by adding to the
> offset, without having to look at the actual buffers until later.
> 
> Basically if we want to ensure that the representation is always
> "minimal", in the sense that the offset lies within the first buffer,
> then a bunch more operations need to do work to maintain that.  Plus,
> this approach is naturally robust: if we somehow get an iov_tail in
> non-minimal form, peek/pull will just handle it with no extra logic.
> 
> > > + * @tail:	IO vector tail (modified)
> > > + *
> > > + * Return:	true if the tail still contains any bytes, otherwise false
> > > + */
> > > +bool iov_tail_shorten(struct iov_tail *tail)
> > > +{
> > > +	size_t i;
> > > +
> > > +	i = iov_skip_bytes(tail->iov, tail->cnt, tail->off, &tail->off);
> > > +	tail->iov += i;
> > > +	tail->cnt -= i;
> > > +
> > > +	return !!tail->cnt;
> > > +}
> > > +
> > > +/**
> > > + * iov_tail_size - Calculate the total size of an IO vector tail
> > > + * @tail:	IO vector tail
> > > + *
> > > + * Returns:    The total size in bytes.
> > > + */
> > > +/* cppcheck-suppress unusedFunction */
> > > +size_t iov_tail_size(struct iov_tail *tail)
> > > +{
> > > +	iov_tail_shorten(tail);
> > > +	return iov_size(tail->iov, tail->cnt) - tail->off;
> > > +}
> > > +
> > > +/**
> > > + * iov_peek_header_() - Get pointer to header from an IOV tail  
> > 
> > I think that this needs to be more generic than "header", because yes,
> > we're using it for headers, but that word doesn't really help in this
> > context.  
> 
> Hm.  Well, we are using it for headers, and we're also always pulling
> from the "head" of whatever we have left.
> 
> > What about "aligned block", or just "block"?  
> 
> Maybe... but it really does have to be from the start of the current
> tail.

"Head block"? Or forget about it, "header" is actually fine.

> >   
> > > + * @tail:	IO vector tail to get header from
> > > + * @len:	Length of header to remove in bytes  
> > 
> > to remove, in bytes
> >   
> > > + * @align:	Required alignment of header in bytes  
> > 
> > Judging from this comment alone, it's not clear if 0 or 1 should be
> > used to get freely aligned blocks.
> >   
> > > + *
> > > + * @tail may be modified, but will be semantically equivalent.
> > > + *
> > > + * Returns:	Pointer to the removed header, NULL if it overruns the IO
> > > + *		vector, is not contiguous or is misaligned.
> > > + */
> > > +void *iov_peek_header_(struct iov_tail *tail, size_t len, size_t align)
> > > +{
> > > +	char *p;
> > > +
> > > +	if (!iov_tail_shorten(tail))
> > > +		return NULL; /* Nothing left */
> > > +
> > > +	if (tail->off + len < tail->off)
> > > +		return NULL; /* Overflow */
> > > +
> > > +	if (tail->off + len > tail->iov[0].iov_len)
> > > +		return NULL; /* Not contiguous */  
> > 
> > I'm not sure if this observation is useful in some cases, but this
> > doesn't necessarily mean that the header/block is not contiguous: if
> > tail->iov[0].iov_base + tail->iov[0].iov_len == tail->iov[1].iov_base,
> > it actually is.  
> 
> Hm, true.  Not sure if it's worth the tests handle that case though.

I guess it's worth it if there's any chance we'll ever want to support
split headers.

> > > +	p = (char *)tail->iov[0].iov_base + tail->off;
> > > +	if ((uintptr_t)p % align)
> > > +		return NULL; /* not aligned */
> > > +
> > > +	return p;
> > > +}
> > > +/**
> > > + * iov_pull_header_() - Remove a header from an IOV tail  
> > 
> > I know that "pulling" is widely used, but it's sometimes ambiguous (I
> > guess we already had a discussion about that in the past). What about
> > "remove", or "drop"?  
> 
> "remove" might work.  I don't like "drop" because that implies to me
> it's just gone, rather than returned.
> 
> > > + * @tail:	IO vector tail to remove header from (modified)
> > > + * @len:	Length of header to remove in bytes
> > > + * @align:	Required alignment of header in bytes
> > > + *
> > > + * @tail is updated so that it no longer includes the extracted header
> > > + *
> > > + * Returns:	Pointer to the removed header, NULL if it overruns the IO
> > > + *		vector, is not contiguous or is misaligned.
> > > + */
> > > +/* cppcheck-suppress unusedFunction */
> > > +void *iov_pull_header_(struct iov_tail *tail, size_t len, size_t align)
> > > +{
> > > +	char *p = iov_peek_header_(tail, len, align);
> > > +
> > > +	if (!p)
> > > +		return NULL;
> > > +
> > > +	tail->off = tail->off + len;  
> > 
> > This could just be += I guess.  
> 
> True.
> 
> > > +	return p;
> > > +}
> > > diff --git a/iov.h b/iov.h
> > > index a9e1722..a2f449c 100644
> > > --- a/iov.h
> > > +++ b/iov.h
> > > @@ -28,4 +28,28 @@ size_t iov_from_buf(const struct iovec *iov, size_t iov_cnt,
> > >  size_t iov_to_buf(const struct iovec *iov, size_t iov_cnt,
> > >                    size_t offset, void *buf, size_t bytes);
> > >  size_t iov_size(const struct iovec *iov, size_t iov_cnt);
> > > +
> > > +/**
> > > + * struct iov_tail - Represents the fail portion of an IO vector  

...so, it's not just a tail portion, it's a tail portion in a rather
specific and constrained context, and once that's revealed these
helpers are easier (for me) to understand. What about:

 * struct iov_tail - Remaining, not consumed portion (tail) of IO vector

?

> > s/fail/tail/
> >   
> > > + * @iov:	IO vector
> > > + * @cnt:	Number of entries in @iov
> > > + * @off:	Current offset in @iov
> > > + */
> > > +struct iov_tail {
> > > +	const struct iovec *iov;
> > > +	size_t cnt, off;
> > > +};
> > > +
> > > +#define IOV_TAIL(iov_, cnt_, off_) \
> > > +	(struct iov_tail){ .iov = (iov_), .cnt = (cnt_), .off = (off_) }
> > > +
> > > +bool iov_tail_shorten(struct iov_tail *tail);
> > > +size_t iov_tail_size(struct iov_tail *tail);
> > > +void *iov_peek_header_(struct iov_tail *tail, size_t len, size_t align);
> > > +#define IOV_PEEK_HEADER(tail_, ty_) \
> > > +	((ty_ *)(iov_peek_header_((tail_), sizeof(ty_), __alignof__(ty_))))  
> > 
> > I guess 'x' would be as clear as 'ty_' (actually, I'm failing to guess
> > what it stands for).  
> 
> "type"

Oh. That's kind of obscure. I guess it would be better to actually
spell that out and define this on multiple lines.

> > > +void *iov_pull_header_(struct iov_tail *tail, size_t len, size_t align);
> > > +#define IOV_PULL_HEADER(tail_, ty_) \
> > > +	((ty_ *)(iov_pull_header_((tail_), sizeof(ty_), __alignof__(ty_))))
> > > +
> > >  #endif /* IOVEC_H */  
> > 
> > I would have expected some functions to add data or build those
> > tails... or we don't need them for some reason?  
> 
> The IOV_TAIL() macro builds one.  Adding data doesn't make sense; the
> idea is this is a view into part of an existing IO vector, which
> doesn't require copying the struct iovecs of that original vector.

I see now, I didn't pay much attention to that. Perhaps some one-liner
function-like comments around IOV_TAIL(), IOV_PEEK_HEADER() and
IOV_PULL_HEADER() would help.

-- 
Stefano


  reply	other threads:[~2024-11-06 10:06 UTC|newest]

Thread overview: 6+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2024-11-05  2:32 [PATCH 0/1] RFC: IOV tail helpers David Gibson
2024-11-05  2:32 ` [PATCH 1/1] iov: iov " David Gibson
2024-11-06  0:56   ` Stefano Brivio
2024-11-06  2:38     ` David Gibson
2024-11-06 10:06       ` Stefano Brivio [this message]
2024-11-08  4:18         ` David Gibson

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20241106110618.333e3c8e@elisabeth \
    --to=sbrivio@redhat.com \
    --cc=david@gibson.dropbear.id.au \
    --cc=passt-dev@passt.top \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this public inbox

	https://passt.top/passt

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for IMAP folder(s).