diff mbox series

[v1,2/2] cleanup.h: Introduce DEFINE_INACTIVE_GUARD and activate_guard

Message ID 20240828143719.828968-3-mathieu.desnoyers@efficios.com (mailing list archive)
State Not Applicable
Headers show
Series cleanup.h: Introduce DEFINE_INACTIVE_GUARD()/activate_guard() | expand

Checks

Context Check Description
netdev/tree_selection success Not a local patch, async

Commit Message

Mathieu Desnoyers Aug. 28, 2024, 2:37 p.m. UTC
To cover scenarios where the scope of the guard differs from the scope
of its activation, introduce DEFINE_INACTIVE_GUARD() and activate_guard().

Here is an example use for a conditionally activated guard variable:

void func(bool a)
{
	DEFINE_INACTIVE_GUARD(preempt_notrace, myguard);

	[...]
	if (a) {
		might_sleep();
		activate_guard(preempt_notrace, myguard)();
	}
	[ protected code ]
}

Signed-off-by: Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
Cc: Peter Zijlstra (Intel) <peterz@infradead.org>
Cc: Ingo Molnar <mingo@kernel.org>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Kees Cook <keescook@chromium.org>
Cc: Greg KH <gregkh@linuxfoundation.org>
Cc: Sean Christopherson <seanjc@google.com>
---
 include/linux/cleanup.h | 23 ++++++++++++++++++++++-
 1 file changed, 22 insertions(+), 1 deletion(-)

Comments

Peter Zijlstra Sept. 2, 2024, 3:43 p.m. UTC | #1
On Wed, Aug 28, 2024 at 10:37:19AM -0400, Mathieu Desnoyers wrote:
> To cover scenarios where the scope of the guard differs from the scope
> of its activation, introduce DEFINE_INACTIVE_GUARD() and activate_guard().
> 
> Here is an example use for a conditionally activated guard variable:
> 
> void func(bool a)
> {
> 	DEFINE_INACTIVE_GUARD(preempt_notrace, myguard);
> 
> 	[...]
> 	if (a) {
> 		might_sleep();
> 		activate_guard(preempt_notrace, myguard)();
> 	}
> 	[ protected code ]
> }

So... I more or less proposed this much earlier:

  https://lore.kernel.org/all/20230919131038.GC39346@noisy.programming.kicks-ass.net/T/#mb7b84212619ac743dfe4d2cc81decce451586b27

and Linus took objection to similar patterns. But perhaps my naming
wasn't right.
Mathieu Desnoyers Sept. 2, 2024, 6:08 p.m. UTC | #2
On 2024-09-02 11:43, Peter Zijlstra wrote:
> On Wed, Aug 28, 2024 at 10:37:19AM -0400, Mathieu Desnoyers wrote:
>> To cover scenarios where the scope of the guard differs from the scope
>> of its activation, introduce DEFINE_INACTIVE_GUARD() and activate_guard().
>>
>> Here is an example use for a conditionally activated guard variable:
>>
>> void func(bool a)
>> {
>> 	DEFINE_INACTIVE_GUARD(preempt_notrace, myguard);
>>
>> 	[...]
>> 	if (a) {
>> 		might_sleep();
>> 		activate_guard(preempt_notrace, myguard)();
>> 	}
>> 	[ protected code ]
>> }
> 
> So... I more or less proposed this much earlier:
> 
>    https://lore.kernel.org/all/20230919131038.GC39346@noisy.programming.kicks-ass.net/T/#mb7b84212619ac743dfe4d2cc81decce451586b27
> 
> and Linus took objection to similar patterns. But perhaps my naming
> wasn't right.

Then you suggested something like a "guard_if()":

https://lore.kernel.org/lkml/20231120221524.GD8262@noisy.programming.kicks-ass.net/

which I find really odd because it requires to evaluate the same
condition twice within the function if it is used as guard_if
expression and needed as expression within the rest of the function
flow. I find the original patch with labels and gotos less ugly
than the guard_if().

Hence my proposal to optionally separate the definition from the activation,
which nicely integrates with the existing code flow.

If Linus' objections were mainly about naming, perhaps what I am
suggestion here may be more to his liking ?

Thanks,

Mathieu
Linus Torvalds Sept. 2, 2024, 6:10 p.m. UTC | #3
On Mon, 2 Sept 2024 at 08:43, Peter Zijlstra <peterz@infradead.org> wrote:
>
> and Linus took objection to similar patterns. But perhaps my naming
> wasn't right.

Well, more of a "this stuff is new, let's start very limited and very clear".

I'm not loving the inactive guard, but I did try to think of a better
model for it, and I can't.  I absolutely hate the *example*, though:

  void func(bool a)
  {
        DEFINE_INACTIVE_GUARD(preempt_notrace, myguard);

        [...]
        if (a) {
                might_sleep();
                activate_guard(preempt_notrace, myguard)();
        }
        [ protected code ]
  }

because that "protected code" obviously is *NOT* protected code. It's
conditionally protected only in one situation.

Honestly, I still think the guard macros are new enough that we should
strive to avoid them in complicated cases like this. And this *is*
complicated. It *looks* simple, but when even the example that was
given was pure and utter garbage, it's clearly not *actually* simple.

Once some code is sometimes protected, and sometimes isn't, and you
have magic compiler stuff that *hides* it, I'm not sure we should use
the magic compiler stuff.

                Linus
Mathieu Desnoyers Sept. 2, 2024, 6:14 p.m. UTC | #4
On 2024-09-02 14:10, Linus Torvalds wrote:
> On Mon, 2 Sept 2024 at 08:43, Peter Zijlstra <peterz@infradead.org> wrote:
>>
>> and Linus took objection to similar patterns. But perhaps my naming
>> wasn't right.
> 
> Well, more of a "this stuff is new, let's start very limited and very clear".
> 
> I'm not loving the inactive guard, but I did try to think of a better
> model for it, and I can't.  I absolutely hate the *example*, though:
> 
>    void func(bool a)
>    {
>          DEFINE_INACTIVE_GUARD(preempt_notrace, myguard);
> 
>          [...]
>          if (a) {
>                  might_sleep();
>                  activate_guard(preempt_notrace, myguard)();
>          }
>          [ protected code ]

Fair. I should have written something more like

   [ conditionally protected code ]

>    }
> 
> because that "protected code" obviously is *NOT* protected code. It's
> conditionally protected only in one situation.
> 
> Honestly, I still think the guard macros are new enough that we should
> strive to avoid them in complicated cases like this. And this *is*
> complicated. It *looks* simple, but when even the example that was
> given was pure and utter garbage, it's clearly not *actually* simple.
> 
> Once some code is sometimes protected, and sometimes isn't, and you
> have magic compiler stuff that *hides* it, I'm not sure we should use
> the magic compiler stuff.

I've tried my best to come up with a scheme which would be cleaner
than the "guard_if()" proposed by Peter, because I really hate it.

I'm perfectly fine going back to goto/labels for that function if we
cannot agree on a clean way to express what is needed there.

Thanks,

Mathieu
Linus Torvalds Sept. 2, 2024, 6:46 p.m. UTC | #5
On Mon, 2 Sept 2024 at 11:08, Mathieu Desnoyers
<mathieu.desnoyers@efficios.com> wrote:
>
> If Linus' objections were mainly about naming, perhaps what I am
> suggestion here may be more to his liking ?

Most of my objections were about having subtle features that then had
very subtle syntax and weren't that clear.

And yes, a small part of it was naming (I absolutely hated the initial
"everything is a guard" thing, when one of the main uses were about
automatic freeing of variables).

But a much larger part was about making the new use greppable and have
really simple syntax.

And the conditional case was never that simple syntax, and I feel it
really violates the whole "this scope is protected".

And no, I do not like Peter's "if_guard()" either.

Honestly, I get the feeling that this is all wrong.

For example, I searched for a few of the places where you wanted to
use this, and see code like this:

    #define __BPF_DECLARE_TRACE(call, proto, args, tp_flags)            \
    static notrace void                                                 \
    __bpf_trace_##call(void *__data, proto)                             \
    {                                                                   \
        DEFINE_INACTIVE_GUARD(preempt_notrace, bpf_trace_guard);        \
                                                                        \
        if ((tp_flags) & TRACEPOINT_MAY_FAULT) {                        \
                might_fault();                                          \
                activate_guard(preempt_notrace, bpf_trace_guard)();     \
        }                                                               \
                                                                        \
        CONCATENATE(bpf_trace_run, COUNT_ARGS(args))(__data,
CAST_TO_U64(args)); \
    }

and it makes me just go "that's just *WRONG*".

That code should never EVER use a conditional guard.

I find *two* uses of this in your patches, and both of them look
com,pletely wrong to me, because you could have written the code
*without* that conditional activation, and it would have been *better*
that way.

IOW, that code should just have been something like this:

    #define __BPF_DECLARE_TRACE(call, proto, args, tp_flags)    \
    static notrace void                                         \
    __bpf_trace_##call(void *__data, proto)                     \
    {                                                           \
                                                                \
        if ((tp_flags) & TRACEPOINT_MAY_FAULT) {                \
                might_fault();                                  \
                guard(preempt_notrace)();                       \
                CONCATENATE(bpf_trace_run, ...                  \
                return;                                         \
        }                                                       \
        CONCATENATE(bpf_trace_run, ...                          \
    }

instead.

Sure, it duplicates the code inside the guard, but what's the
downside? In both of the cases I saw, the "duplicated" code was
trivial.

And the *upside* is that you don't have any conditional locking or
guarding, and you don't have to make up ridiculous and meaningless
temporary names for the guard etc. So you get to use the *LEGIBLE*
code.

And you don't have a patch that just renames all our existing uses.
Which was also wrong.

So no, I don't like Peter's "if_guard()",  but I find your conditional
activation to be absolutely wrogn and horrible too.

                 Linus
Mathieu Desnoyers Sept. 3, 2024, 1:42 p.m. UTC | #6
On 2024-09-02 14:46, Linus Torvalds wrote:
[...]
> IOW, that code should just have been something like this:
> 
>      #define __BPF_DECLARE_TRACE(call, proto, args, tp_flags)    \
>      static notrace void                                         \
>      __bpf_trace_##call(void *__data, proto)                     \
>      {                                                           \
>                                                                  \
>          if ((tp_flags) & TRACEPOINT_MAY_FAULT) {                \
>                  might_fault();                                  \
>                  guard(preempt_notrace)();                       \
>                  CONCATENATE(bpf_trace_run, ...                  \
>                  return;                                         \
>          }                                                       \
>          CONCATENATE(bpf_trace_run, ...                          \
>      }
> 
> instead.

If we look at perf_trace_##call(), with the conditional guard, it looks
like the following. It is not clear to me that code duplication would
be acceptable here.

I agree with you that the conditional guard is perhaps not something we
want at this stage, but in this specific case perhaps we should go back
to goto and labels ?

One alternative is to add yet another level of macros to handle the
code duplication.

#define _DECLARE_EVENT_CLASS(call, proto, args, tstruct, assign, print, tp_flags) \
static notrace void                                                     \
perf_trace_##call(void *__data, proto)                                  \
{                                                                       \
         struct trace_event_call *event_call = __data;                   \
         struct trace_event_data_offsets_##call __maybe_unused __data_offsets;\
         struct trace_event_raw_##call *entry;                           \
         struct pt_regs *__regs;                                         \
         u64 __count = 1;                                                \
         struct task_struct *__task = NULL;                              \
         struct hlist_head *head;                                        \
         int __entry_size;                                               \
         int __data_size;                                                \
         int rctx;                                                       \
                                                                         \
         DEFINE_INACTIVE_GUARD(preempt_notrace, trace_event_guard);      \
                                                                         \
         if ((tp_flags) & TRACEPOINT_MAY_FAULT) {                        \
                 might_fault();                                          \
                 activate_guard(preempt_notrace, trace_event_guard)();   \
         }                                                               \
                                                                         \
         __data_size = trace_event_get_offsets_##call(&__data_offsets, args); \
                                                                         \
         head = this_cpu_ptr(event_call->perf_events);                   \
         if (!bpf_prog_array_valid(event_call) &&                        \
             __builtin_constant_p(!__task) && !__task &&                 \
             hlist_empty(head))                                          \
                 return;                                                 \
                                                                         \
         __entry_size = ALIGN(__data_size + sizeof(*entry) + sizeof(u32),\
                              sizeof(u64));                              \
         __entry_size -= sizeof(u32);                                    \
                                                                         \
         entry = perf_trace_buf_alloc(__entry_size, &__regs, &rctx);     \
         if (!entry)                                                     \
                 return;                                                 \
                                                                         \
         perf_fetch_caller_regs(__regs);                                 \
                                                                         \
         tstruct                                                         \
                                                                         \
         { assign; }                                                     \
                                                                         \
         perf_trace_run_bpf_submit(entry, __entry_size, rctx,            \
                                   event_call, __count, __regs,          \
                                   head, __task);                        \
}

Thanks,

Mathieu
Linus Torvalds Sept. 3, 2024, 7 p.m. UTC | #7
On Tue, 3 Sept 2024 at 06:42, Mathieu Desnoyers
<mathieu.desnoyers@efficios.com> wrote:
>
> If we look at perf_trace_##call(), with the conditional guard, it looks
> like the following. It is not clear to me that code duplication would
> be acceptable here.

Sure it is.

Admittedly, I think it would look a *lot* better off with that macro
creating a helper function for the common part, and then just call
that helper function in two places.

In fact, I think the existing "perf_trace_##call()" function *is* that
helper function already, and all it needs is

 (a) add the traditional double underscore (or "do_") to that function

 (b) create the wrapper function.

IOW, I think the diff would look something like this:

--- a/include/trace/perf.h
+++ b/include/trace/perf.h
@@ -15,7 +15,7 @@
 #undef DECLARE_EVENT_CLASS
 #define DECLARE_EVENT_CLASS(call, proto, args, tstruct, assign, print) \
 static notrace void                                                    \
-perf_trace_##call(void *__data, proto)                                 \
+__perf_trace_##call(void *__data, proto)                               \
 {                                                                      \
        struct trace_event_call *event_call = __data;                   \
        struct trace_event_data_offsets_##call __maybe_unused __data_offsets;\
@@ -53,6 +53,17 @@ perf_trace_##call(void *__data, proto)
                         \
        perf_trace_run_bpf_submit(entry, __entry_size, rctx,            \
                                  event_call, __count, __regs,          \
                                  head, __task);                        \
+}                                                                      \
+static notrace void                                                    \
+perf_trace_##call(void *__data, proto)                                 \
+{                                                                      \
+        if ((tp_flags) & TRACEPOINT_MAY_FAULT) {                        \
+                might_fault();                                          \
+                guard(preempt_notrace)();                              \
+                __perf_trace_##call(__data, proto);                    \
+                return;
         \
+        }                                                              \
+        __perf_trace_##call(__data, proto);                            \
 }

 /*

and notice how there is no actual duplicated code on a source level
now (although the compiler may obviously do the inlining and
duplication - although I think that the tp_flags are always constant,
so what actually hopefully happens is that the compiler doesn't
duplicate anything at all, and all the conditionals just go away.).

NOTE! The above patch is garbage. I did it as a "like this" patch on
my current tree, and it doesn't even have the "tp_class" thing at all,
so the patch is obviously not functional. It's just meant as a "look,
wrapper function".

In fact, I think the wrapping might even be done at a higher level (ie
maybe the whole "may_fault" shouldn't be an argument at all, but a
static part of the define, in case some event classes can take faults
and others cannot?)

I dunno. I actually think the whole "TRACEPOINT_MAY_FAULT" thing is an
incredibly ugly hack, and SHOULD NOT EXIST.

In other words - why make these incredibly ugly macros EVEN MORE UGLY
- when there is a single use-case and we sure as hell don't want to
add any more?

IOW - why not get rid of the stupid TRACE_EVENT_FN_MAY_FAULT thing
entirely, and do this trivial wrapping in the *one* place that wants
it, instead of making it look like some generic thing with an
allegedly generic test, when it is anything but generic, and is in
fact just a single special case for system call.

Yes, I know. Computer Science classes say that "generic programming is
good". Those CS classes are garbage. You want to make your code as
specialized as humanly possible, and *not* make some complicated "this
handles all cases" code that is complicated and fragile.

                            Linus
Mathieu Desnoyers Sept. 6, 2024, 10:05 p.m. UTC | #8
On 2024-09-03 15:00, Linus Torvalds wrote:
[...]
> 
> IOW - why not get rid of the stupid TRACE_EVENT_FN_MAY_FAULT thing
> entirely, and do this trivial wrapping in the *one* place that wants
> it, instead of making it look like some generic thing with an
> allegedly generic test, when it is anything but generic, and is in
> fact just a single special case for system call.
> 
> Yes, I know. Computer Science classes say that "generic programming is
> good". Those CS classes are garbage. You want to make your code as
> specialized as humanly possible, and *not* make some complicated "this
> handles all cases" code that is complicated and fragile.

Thank you for looking into this. I'm redoing the series based on your
feedback, and it is indeed much cleaner. Let me know if you are
interested to be CC'd on the next version of the series changing
syscall tracepoints.

Mathieu
diff mbox series

Patch

diff --git a/include/linux/cleanup.h b/include/linux/cleanup.h
index 1247e67a6161..5f031cce97ca 100644
--- a/include/linux/cleanup.h
+++ b/include/linux/cleanup.h
@@ -149,12 +149,20 @@  static inline class_##_name##_t class_##_name##ext##_constructor(_init_args) \
  *      similar to scoped_guard(), except it does fail when the lock
  *      acquire fails.
  *
+ * DEFINE_INACTIVE_GUARD(name, var):
+ *      define an inactive guard variable in a given scope, initialized to NULL.
+ *
+ * activate_guard(name, var)(args...):
+ *      activate a guard variable with its constructor, if it is not already
+ *      activated.
  */
 
 #define DECLARE_GUARD(_name, _type, _lock, _unlock) \
 	DECLARE_CLASS(_name, _type, if (_T) { _unlock; }, ({ _lock; _T; }), _type _T); \
 	static inline void * class_##_name##_lock_ptr(class_##_name##_t *_T) \
-	{ return *_T; }
+	{ return *_T; } \
+	static inline class_##_name##_t class_##_name##_null(void) \
+	{ return NULL; }
 
 #define DECLARE_GUARD_COND(_name, _ext, _condlock) \
 	EXTEND_CLASS(_name, _ext, \
@@ -178,6 +186,14 @@  static inline class_##_name##_t class_##_name##ext##_constructor(_init_args) \
 		if (!__guard_ptr(_name)(&scope)) _fail; \
 		else
 
+#define DEFINE_INACTIVE_GUARD(_name, _var) \
+	class_##_name##_t _var __cleanup(class_##_name##_destructor) = \
+		class_##_name##_null()
+
+#define activate_guard(_name, _var) \
+	if (!class_##_name##_lock_ptr(&(_var))) \
+		_var = class_##_name##_constructor
+
 /*
  * Additional helper macros for generating lock guards with types, either for
  * locks that don't have a native type (eg. RCU, preempt) or those that need a
@@ -212,6 +228,11 @@  static inline void class_##_name##_destructor(class_##_name##_t *_T)	\
 static inline void *class_##_name##_lock_ptr(class_##_name##_t *_T)	\
 {									\
 	return _T->lock;						\
+}									\
+static inline class_##_name##_t class_##_name##_null(void)		\
+{									\
+	class_##_name##_t _t = { .lock = NULL };			\
+	return _t;							\
 }