A cookie for anyone who can solve this C/macro problem...

Lawrence Stewart lstewart at freebsd.org
Fri Apr 16 08:16:55 UTC 2010


On 04/15/10 23:17, Lawrence Stewart wrote:
> Hi All,
>
> I've hit a road block that I'm hoping people with better C/macro fu can
> help me with. It also may be the case that what I'm trying to do can't
> be done, so I welcome alternative suggestions as well.
>
> The problem:
>
> I'm working on a KPI to modularise congestion control support in the
> kernel (overview of KPI is visible in [1]). So far, I've been focused
> solely on TCP, but I want to have the framework generalise to be able to
> share congestion control algorithms between other congestion aware
> transports e.g. SCTP, DCCP.
>
> To achieve this, I need to pass congestion control information into the
> hook functions in a protocol agnostic way. The current KPI passes the
> tcpcb ptr and this works nicely, but obviously doesn't help.
>
> The cleanest option as I see it is to embed a new CC specific struct in
> the appropriate TCP and SCTP structs, and pass this in to the KPI. I
> started down this road and quickly stopped - the amount of code churn
> this will cause in the TCP and SCTP stacks is to my mind far to
> intrusive, at least for an initial import of the framework.
>
> So the next idea I've been trying to prototype was to pass a union of
> the various protocol struct types into the KPI and then use macro magic
> to allow the CC algorithms to access commonly named variables across the
> different transport struct types. It would require vars to have the same
> name, which would still mean some code churn, but less than the first
> option. Here are the basic bits I'm currently working with:
>
> struct cc_var {
> uint16_t type;
> union {
> struct tcpcb *tcp;
> struct sctp_nets *sctp;
> } ccvc;
> };
>
> All function ptrs in struct cc_algo [1] are modified to accept a "struct
> cc_var *ccv".
>
> In an algorithm implementation e.g. NewReno [2], I then want to be able
> to do things like this with the fictitious CCVC() macro:
>
> void
> newreno_ack_received(struct cc_var *ccv)
> {
> u_int cw = CCVC(ccv)->snd_cwnd;
> ...
> CCVC(ccv)->snd_cwnd = blah;
> }
>
>
> So far I haven't found a way to achieve the above, and the best I've
> come up (with help) is this:
>
> #define CCV_DO(ccv, what) \
> ( \
> (ccv)->type == IPPROTO_TCP ? (ccv)->ccvc.tcp->what : \
> (ccv)->ccvc.sctp->what \
> )
>
> which can be used like this:
>
> void
> newreno_ack_received(struct cc_var *ccv)
> {
> u_int cw = CCV_DO(ccv, snd_cwnd);
> ...
> CCVC(ccv, snd_cwnd = blah);
> }
>
> Of course, this falls apart if you try do this for example:
>
> CCVC(ccv, snd_cwnd = min(blah, bleh));
>
>
> So... I'm sure there are some good ideas out there and would really
> appreciate hearing about them.

Thanks to those who replied on and off list. The cookie reward goes to 
Hans and Daniel. The key insight I was missing is that the C ternary 
statement doesn't return something suitable for use as an lvalue. 
Dereferencing a ptr returned by the ternary does though, and this 
provides me with a fairly neat way of dealing with the problem.

For posterity's sake, relevant snippets of working code that I'm now 
using are as follows:

struct cc_var {
         union ccv_container {
                 struct tcpcb *tcp;
                 struct sctp_nets *sctp;
         } ccvc;
         int type;
};

#define CCV(ccv, what)                                                 \
(*(                                                                    \
         (ccv)->type == IPPROTO_TCP ?    &(ccv)->ccvc.tcp->what :       \
                                         &(ccv)->ccvc.sctp->what        \
))

In future, the CCV macro could also grow support for DCCP using a nested 
ternary e.g. (untested)

#define CCV(ccv, what)                                                 \
(*(                                                                    \
         (ccv)->type == IPPROTO_TCP   ?  &(ccv)->ccvc.tcp->what :       \
	((ccv)->type == IPPROTO_SCTP ?	&(ccv)->ccvc.sctp->what :      \
				        &(ccv)->ccvc.dccp->what)       \
))


And finally, an example use of the current code in the NewReno CC module:

void
newreno_ack_received(struct cc_var *ccv)
{
         u_int cw = CCV(ccv, snd_cwnd);
         u_int incr = CCV(ccv, t_maxseg);
         if (cw > CCV(ccv, snd_ssthresh))
                 incr = max((incr * incr / cw), 1);

         CCV(ccv, snd_cwnd) = min(cw+incr, TCP_MAXWIN <<
	    CCV(ccv, snd_scale));
}

Whilst we're here and on an educational roll, does anyone have thoughts 
on what the compiler actually does with "*(blah ? &1 : &2)"? As this TCP 
code is called in the fast path, I'm curious to know if there is a 
runtime penalty for taking the ref and then immediately derefing it like 
this, or if the only overhead is the test+branch and the compiler is 
smart enough to collapse *(&1 or &2) to (1 or 2). I don't have a good 
mental model of how this stuff works under the hood...

Thanks again.

Cheers,
Lawrence


More information about the freebsd-hackers mailing list