aboutsummaryrefslogtreecommitdiff
path: root/include/linux/file.h
diff options
context:
space:
mode:
authorAl Viro <viro@zeniv.linux.org.uk>2024-05-31 15:45:12 -0400
committerAl Viro <viro@zeniv.linux.org.uk>2024-08-12 22:01:05 -0400
commit88a2f6468d013ca1163490dbddfc95135d1c27a1 (patch)
tree1ec4ea78a040c38d05892e80a3ce23cb040172c6 /include/linux/file.h
parent1da91ea87aefe2c25b68c9f96947a9271ba6325d (diff)
struct fd: representation change
We want the compiler to see that fdput() on empty instance is a no-op. The emptiness check is that file reference is NULL, while fdput() is "fput() if FDPUT_FPUT is present in flags". The reason why fdput() on empty instance is a no-op is something compiler can't see - it's that we never generate instances with NULL file reference combined with non-zero flags. It's not that hard to deal with - the real primitives behind fdget() et.al. are returning an unsigned long value, unpacked by (inlined) __to_fd() into the current struct file * + int. The lower bits are used to store flags, while the rest encodes the pointer. Linus suggested that keeping this unsigned long around with the extractions done by inlined accessors should generate a sane code and that turns out to be the case. Namely, turning struct fd into a struct-wrapped unsinged long, with fd_empty(f) => unlikely(f.word == 0) fd_file(f) => (struct file *)(f.word & ~3) fdput(f) => if (f.word & 1) fput(fd_file(f)) ends up with compiler doing the right thing. The cost is the patch footprint, of course - we need to switch f.file to fd_file(f) all over the tree, and it's not doable with simple search and replace; there are false positives, etc. Note that the sole member of that structure is an opaque unsigned long - all accesses should be done via wrappers and I don't want to use a name that would invite manual casts to file pointers, etc. The value of that member is equal either to (unsigned long)p | flags, p being an address of some struct file instance, or to 0 for an empty fd. For now the new predicate (fd_empty(f)) has no users; all the existing checks have form (!fd_file(f)). We will convert to fd_empty() use later; here we only define it (and tell the compiler that it's unlikely to return true). This commit only deals with representation change; there will be followups. Reviewed-by: Christian Brauner <brauner@kernel.org> Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
Diffstat (limited to 'include/linux/file.h')
-rw-r--r--include/linux/file.h22
1 files changed, 16 insertions, 6 deletions
diff --git a/include/linux/file.h b/include/linux/file.h
index 0f3f369f2450..eb28469b1c16 100644
--- a/include/linux/file.h
+++ b/include/linux/file.h
@@ -35,18 +35,28 @@ static inline void fput_light(struct file *file, int fput_needed)
fput(file);
}
+/* either a reference to struct file + flags
+ * (cloned vs. borrowed, pos locked), with
+ * flags stored in lower bits of value,
+ * or empty (represented by 0).
+ */
struct fd {
- struct file *file;
- unsigned int flags;
+ unsigned long word;
};
#define FDPUT_FPUT 1
#define FDPUT_POS_UNLOCK 2
-#define fd_file(f) ((f).file)
+#define fd_file(f) ((struct file *)((f).word & ~(FDPUT_FPUT|FDPUT_POS_UNLOCK)))
+static inline bool fd_empty(struct fd f)
+{
+ return unlikely(!f.word);
+}
+
+#define EMPTY_FD (struct fd){0}
static inline void fdput(struct fd fd)
{
- if (fd.flags & FDPUT_FPUT)
+ if (fd.word & FDPUT_FPUT)
fput(fd_file(fd));
}
@@ -60,7 +70,7 @@ extern void __f_unlock_pos(struct file *);
static inline struct fd __to_fd(unsigned long v)
{
- return (struct fd){(struct file *)(v & ~3),v & 3};
+ return (struct fd){v};
}
static inline struct fd fdget(unsigned int fd)
@@ -80,7 +90,7 @@ static inline struct fd fdget_pos(int fd)
static inline void fdput_pos(struct fd f)
{
- if (f.flags & FDPUT_POS_UNLOCK)
+ if (f.word & FDPUT_POS_UNLOCK)
__f_unlock_pos(fd_file(f));
fdput(f);
}