[PATCH v2] fs: make generic_fillattr() tail-callable and utilize it in ext2/ext4

Mateusz Guzik posted 1 patch 1 week, 2 days ago
fs/ext2/inode.c    | 3 +--
fs/ext4/inode.c    | 3 +--
fs/stat.c          | 6 +++++-
include/linux/fs.h | 2 +-
4 files changed, 8 insertions(+), 6 deletions(-)
[PATCH v2] fs: make generic_fillattr() tail-callable and utilize it in ext2/ext4
Posted by Mateusz Guzik 1 week, 2 days ago
Unfortunately the other filesystems I checked make adjustments after
their own call to generic_fillattr() and consequently can't benefit.

Signed-off-by: Mateusz Guzik <mjguzik@gmail.com>
---

v2:
- also patch vfs_getattr_nosec

There are weird slowdowns on fstat, this submission is a byproduct of
trying to straighten out the fast path.

Not benchmarked, but I did confirm the compiler jmps out to the routine
instead of emitting a call which is the right thing to do here.

that said I'm not going to argue, but I like to see this out of the way.

there are nasty things which need to be addressed separately

 fs/ext2/inode.c    | 3 +--
 fs/ext4/inode.c    | 3 +--
 fs/stat.c          | 6 +++++-
 include/linux/fs.h | 2 +-
 4 files changed, 8 insertions(+), 6 deletions(-)

diff --git a/fs/ext2/inode.c b/fs/ext2/inode.c
index 30f8201c155f..cf1f89922207 100644
--- a/fs/ext2/inode.c
+++ b/fs/ext2/inode.c
@@ -1629,8 +1629,7 @@ int ext2_getattr(struct mnt_idmap *idmap, const struct path *path,
 			STATX_ATTR_IMMUTABLE |
 			STATX_ATTR_NODUMP);
 
-	generic_fillattr(&nop_mnt_idmap, request_mask, inode, stat);
-	return 0;
+	return generic_fillattr(&nop_mnt_idmap, request_mask, inode, stat);
 }
 
 int ext2_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
index 1dc09ed5d403..3edd6e60dd9b 100644
--- a/fs/ext4/inode.c
+++ b/fs/ext4/inode.c
@@ -5687,8 +5687,7 @@ int ext4_getattr(struct mnt_idmap *idmap, const struct path *path,
 				  STATX_ATTR_NODUMP |
 				  STATX_ATTR_VERITY);
 
-	generic_fillattr(idmap, request_mask, inode, stat);
-	return 0;
+	return generic_fillattr(idmap, request_mask, inode, stat);
 }
 
 int ext4_file_getattr(struct mnt_idmap *idmap,
diff --git a/fs/stat.c b/fs/stat.c
index f13308bfdc98..581a95376e70 100644
--- a/fs/stat.c
+++ b/fs/stat.c
@@ -78,8 +78,11 @@ EXPORT_SYMBOL(fill_mg_cmtime);
  * take care to map the inode according to @idmap before filling in the
  * uid and gid filds. On non-idmapped mounts or if permission checking is to be
  * performed on the raw inode simply pass @nop_mnt_idmap.
+ *
+ * The routine always succeeds. We make it return a value so that consumers can
+ * tail-call it.
  */
-void generic_fillattr(struct mnt_idmap *idmap, u32 request_mask,
+int generic_fillattr(struct mnt_idmap *idmap, u32 request_mask,
 		      struct inode *inode, struct kstat *stat)
 {
 	vfsuid_t vfsuid = i_uid_into_vfsuid(idmap, inode);
@@ -110,6 +113,7 @@ void generic_fillattr(struct mnt_idmap *idmap, u32 request_mask,
 		stat->change_cookie = inode_query_iversion(inode);
 	}
 
+	return 0;
 }
 EXPORT_SYMBOL(generic_fillattr);
 
diff --git a/include/linux/fs.h b/include/linux/fs.h
index 016b0fe1536e..754893d8d2a8 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -3471,7 +3471,7 @@ extern int page_symlink(struct inode *inode, const char *symname, int len);
 extern const struct inode_operations page_symlink_inode_operations;
 extern void kfree_link(void *);
 void fill_mg_cmtime(struct kstat *stat, u32 request_mask, struct inode *inode);
-void generic_fillattr(struct mnt_idmap *, u32, struct inode *, struct kstat *);
+int generic_fillattr(struct mnt_idmap *, u32, struct inode *, struct kstat *);
 void generic_fill_statx_attr(struct inode *inode, struct kstat *stat);
 void generic_fill_statx_atomic_writes(struct kstat *stat,
 				      unsigned int unit_min,
-- 
2.43.0

 fs/ext2/inode.c    | 3 +--
 fs/ext4/inode.c    | 3 +--
 fs/stat.c          | 9 ++++++---
 include/linux/fs.h | 2 +-
 4 files changed, 9 insertions(+), 8 deletions(-)

diff --git a/fs/ext2/inode.c b/fs/ext2/inode.c
index 30f8201c155f..cf1f89922207 100644
--- a/fs/ext2/inode.c
+++ b/fs/ext2/inode.c
@@ -1629,8 +1629,7 @@ int ext2_getattr(struct mnt_idmap *idmap, const struct path *path,
 			STATX_ATTR_IMMUTABLE |
 			STATX_ATTR_NODUMP);
 
-	generic_fillattr(&nop_mnt_idmap, request_mask, inode, stat);
-	return 0;
+	return generic_fillattr(&nop_mnt_idmap, request_mask, inode, stat);
 }
 
 int ext2_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
index 1dc09ed5d403..3edd6e60dd9b 100644
--- a/fs/ext4/inode.c
+++ b/fs/ext4/inode.c
@@ -5687,8 +5687,7 @@ int ext4_getattr(struct mnt_idmap *idmap, const struct path *path,
 				  STATX_ATTR_NODUMP |
 				  STATX_ATTR_VERITY);
 
-	generic_fillattr(idmap, request_mask, inode, stat);
-	return 0;
+	return generic_fillattr(idmap, request_mask, inode, stat);
 }
 
 int ext4_file_getattr(struct mnt_idmap *idmap,
diff --git a/fs/stat.c b/fs/stat.c
index f13308bfdc98..7d390bcd74ab 100644
--- a/fs/stat.c
+++ b/fs/stat.c
@@ -78,8 +78,11 @@ EXPORT_SYMBOL(fill_mg_cmtime);
  * take care to map the inode according to @idmap before filling in the
  * uid and gid filds. On non-idmapped mounts or if permission checking is to be
  * performed on the raw inode simply pass @nop_mnt_idmap.
+ *
+ * The routine always succeeds. We make it return a value so that consumers can
+ * tail-call it.
  */
-void generic_fillattr(struct mnt_idmap *idmap, u32 request_mask,
+int generic_fillattr(struct mnt_idmap *idmap, u32 request_mask,
 		      struct inode *inode, struct kstat *stat)
 {
 	vfsuid_t vfsuid = i_uid_into_vfsuid(idmap, inode);
@@ -110,6 +113,7 @@ void generic_fillattr(struct mnt_idmap *idmap, u32 request_mask,
 		stat->change_cookie = inode_query_iversion(inode);
 	}
 
+	return 0;
 }
 EXPORT_SYMBOL(generic_fillattr);
 
@@ -209,8 +213,7 @@ int vfs_getattr_nosec(const struct path *path, struct kstat *stat,
 					    request_mask,
 					    query_flags);
 
-	generic_fillattr(idmap, request_mask, inode, stat);
-	return 0;
+	return generic_fillattr(idmap, request_mask, inode, stat);
 }
 EXPORT_SYMBOL(vfs_getattr_nosec);
 
diff --git a/include/linux/fs.h b/include/linux/fs.h
index 016b0fe1536e..754893d8d2a8 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -3471,7 +3471,7 @@ extern int page_symlink(struct inode *inode, const char *symname, int len);
 extern const struct inode_operations page_symlink_inode_operations;
 extern void kfree_link(void *);
 void fill_mg_cmtime(struct kstat *stat, u32 request_mask, struct inode *inode);
-void generic_fillattr(struct mnt_idmap *, u32, struct inode *, struct kstat *);
+int generic_fillattr(struct mnt_idmap *, u32, struct inode *, struct kstat *);
 void generic_fill_statx_attr(struct inode *inode, struct kstat *stat);
 void generic_fill_statx_atomic_writes(struct kstat *stat,
 				      unsigned int unit_min,
-- 
2.43.0
Re: [PATCH v2] fs: make generic_fillattr() tail-callable and utilize it in ext2/ext4
Posted by Christoph Hellwig 6 days, 10 hours ago
On Tue, Apr 01, 2025 at 06:52:52PM +0200, Mateusz Guzik wrote:
> Unfortunately the other filesystems I checked make adjustments after
> their own call to generic_fillattr() and consequently can't benefit.

This is in no way a useful commit message.

Why do you even do this change?  What's the point of it?  And why do you
think making a function tail callable for two callers, one of which is
basically irrelevant warrants adding a pointless return that now needs
to be generated and checked by all other callers (which this patch fails
to do)?
Re: [PATCH v2] fs: make generic_fillattr() tail-callable and utilize it in ext2/ext4
Posted by Mateusz Guzik 5 days, 22 hours ago
On Fri, Apr 4, 2025 at 10:33 AM Christoph Hellwig <hch@infradead.org> wrote:
>
> On Tue, Apr 01, 2025 at 06:52:52PM +0200, Mateusz Guzik wrote:
> > Unfortunately the other filesystems I checked make adjustments after
> > their own call to generic_fillattr() and consequently can't benefit.
>
> This is in no way a useful commit message.
>
> Why do you even do this change?  What's the point of it?  And why do you
> think making a function tail callable for two callers, one of which is
> basically irrelevant warrants adding a pointless return that now needs
> to be generated and checked by all other callers (which this patch fails
> to do)?
>

Callers don't need to check it because it is guaranteed to be 0. Also
returning 0 vs returning nothing makes virtually no difference to
anyone.

As for general context, there are several small slowdowns when issuing
fstat() and I'm tackling them bit by bit (and yes, tail calling vs
returning to the caller and that caller exiting is a small
optimization).
-- 
Mateusz Guzik <mjguzik gmail.com>
Re: [PATCH v2] fs: make generic_fillattr() tail-callable and utilize it in ext2/ext4
Posted by Christoph Hellwig 3 days, 9 hours ago
On Fri, Apr 04, 2025 at 10:14:32PM +0200, Mateusz Guzik wrote:
> Callers don't need to check it because it is guaranteed to be 0. Also
> returning 0 vs returning nothing makes virtually no difference to
> anyone.

Nom the return value very clearly states it can return something else
with your patch, and people will sooner or later do that.

Let's stop these silly things that just create nasty landmines for no
reason at all.

> As for general context, there are several small slowdowns when issuing
> fstat() and I'm tackling them bit by bit (and yes, tail calling vs
> returning to the caller and that caller exiting is a small
> optimization).

Please prove that it makes a difference.  And if it does do the
changes in a sane way by keeping the helper as is except for marking
it as inline candidate and a special _tail helper below that inlines
it.

But the bar for such micro-optimizations should be high, and so far you
have no managed to provide any rationale for it.
Re: [PATCH v2] fs: make generic_fillattr() tail-callable and utilize it in ext2/ext4
Posted by Christian Brauner 1 week ago
On Tue, Apr 01, 2025 at 06:52:52PM +0200, Mateusz Guzik wrote:
> Unfortunately the other filesystems I checked make adjustments after
> their own call to generic_fillattr() and consequently can't benefit.
> 
> Signed-off-by: Mateusz Guzik <mjguzik@gmail.com>
> ---

You need to resend this during -rc1 or at least when all fs trees have
been merged based on mainline.