How Duff’s Device Works


I like C, but I have to admit that, sometimes, “The Old Man of Programming” can be a bit of a killjoy. This is one of the most exciting eras in computer history, but lately, C’s acting like he doesn’t even want to have a good time. While the cool kids like Ruby and Haskell are living it up, C’s over in the corner obsessing over bits and bytes and memory alignment and pointers and the stack and machine architecture and unreachable allocations and multiple indirections…and it’s…kind of a buzzkill. You want to tell him, “Dude! Lighten up! This is supposed to be fun!”

But of course C can’t relax. He’s holding the entire computing universe on his shoulders, after all. It’s not turtles all the way down — it’s turtles all the way down, then C. Not a lot of room for fun underneath that, is there?

Is there?

Well. Here’s the secret: C does let loose sometimes. And after being bottled up for so long, when the release finally does come, it’s pretty much an F5 of crazy.

Take for example a little thing called Duff’s Device:

send(short *to, short *from, int count)
{
	int n=(count+7)/8;
	switch(count%8){
	case 0:	do{	*to = *from++;
	case 7:		*to = *from++;
	case 6:		*to = *from++;
	case 5:		*to = *from++;
	case 4:		*to = *from++;
	case 3:		*to = *from++;
	case 2:		*to = *from++;
	case 1:		*to = *from++;
		}while( --n>0);
	}
}

Okay, what you have here is your basic switch statement merged with a do loop. Pretty messed up, right? And just to be clear: this is totally valid C.

Huh?

If we look at the Jargon File entry, we see that the purpose of Duff’s Device is to continuously copy data into a memory-mapped video register. Unfortunately, there’s no explanation of how it works, other than a brief mention of C’s default fall-through property. Call me crazy, but fall-through is about the only part of Duff’s Device that actually makes sense. What about the fact that there’s a friggin do loop embedded in a switch statement? Or the fact that case labels are, in turn, embedded inside the do? This thing is so caught up in itself it makes a teenager seem reflective.

However, since I started working on Tenacious C, I’ve gotten real intimate with the C grammar, and with C in general, and I can report to you that Duff’s Device isn’t as horrific as it appears. In fact, the reason it’s so shocking is because most people don’t really understand switch statements in C.

Switch Statements In General

If you’re like me, you’ve always visualized a switch as a glorified if-then-else block.

It makes sense to say that…

        switch(x) {
        case 1:
                printf("something1\n");
                break;
        case 2:
                printf("something2\n");
        case 3:
                printf("something3\n");
                break;
        case 4:
                printf("something4\n");
                break;
        }

…is pretty much equivalent to:

        if(x == 1)
                printf("something1\n");
        else if(x == 2 || x == 3)
        {
                if(x == 2)
                        printf("something2\n");
                 printf("something3\n");
        }
        else if(x == 4)
                printf("something4\n");

Right? You can see with your own eyes that the two statements produce the same result. I even omitted a break to ensure that our old friend Mr. Fall-Through didn’t break (har, har) the model. For workaday programming, this is a perfectly valid way of thinking about a switch.

But it breaks down in Duff’s Device.

If you’re in the “switch as if-then-else” frame of mind when you see Duff’s Device, your brain will try to overlay a do loop over an if-then-else block. But your brain will resist, because what it’s trying to do is impossible…and that’s where a lot of the confusion over Duff’s Device arises from.

Switch Statements In C

Here’s how a switch really works.

You type this…

        switch(x) {
        case 1:
                printf("something1\n");
                break;
        case 2:
                printf("something2\n");
        case 3:
                printf("something3\n");
                break;
        case 4:
                printf("something4\n");
                break;
        }

…and C sees this:

        if(x==1) goto label_case1;
        if(x==2) goto label_case2;
        if(x==3) goto label_case3;
        if(x==4) goto label_case4;
        goto label_finish;

        label_case1:
                printf("something1\n");
                goto label_finish;
        label_case2:
                printf("something2\n");
        label_case3:
                printf("something3\n");
                goto label_finish;
        label_case4:
                printf("something4\n");
                goto label_finish;

        label_finish:
                /* continue with the rest of the program */

See how that works? To C, a switch is really a bank of cascading gotos. That’s why case labels have to be constant, because C generates goto labels from them at compile time. And unlike an if-then-else block, goto labels can easily be intertwined with a do loop. In fact, let’s go ahead and rewrite Duff’s Device using this model:

send(short *to, short *from, int count)
{
	int n=(count+7)/8;

        if(count%8 == 0) goto label_case0;
        if(count%8 == 1) goto label_case1;
        if(count%8 == 2) goto label_case2;
        if(count%8 == 3) goto label_case3;
        if(count%8 == 4) goto label_case4;
        if(count%8 == 5) goto label_case5;
        if(count%8 == 6) goto label_case6;
        if(count%8 == 7) goto label_case7;

	label_case0:	do{	*to = *from++;
	label_case7:		*to = *from++;
	label_case6:		*to = *from++;
	label_case5:		*to = *from++;
	label_case4:		*to = *from++;
	label_case3:		*to = *from++;
	label_case2:		*to = *from++;
	label_case1:		*to = *from++;
		}while( --n>0);
}

Sweet! The switch is gone, the control flows in a way that makes sense, and the do loop runs without any problems. So…case closed, right?

Almost.

C — An Old Man, But A Liberal At Heart

You remember at the top, I had two questions about Duff’s Device:

    1. How do you embed a do loop inside a switch statement?
    2. How are case labels, in turn, embedded inside the do?

Well, we just answered question two. (They’re not case labels per se, but goto labels.)

Question one is trickier. Question one depends on familiarity with a certain quirk of the C language. As far as I know, no other high level language in existence, with the exception of C++, gives you the freedom C does to cram whatever the hell you want inside a switch. The only demand C makes is that the switch statement be followed by a statement. What’s a statement? Let’s look at the grammar:

statement
	: labeled_statement
	| compound_statement
	| expression_statement
	| selection_statement
	| iteration_statement
	| jump_statement
	;

So you can have a labeled statement (a single case or goto label), a compound statement (any number of statements enclosed by braces. This is by far the most common), an expression statement (any random expression, for instance an assignment), a selection statement (an if statement), an iteration statement (for, while, do), or a jump statement (goto, break, continue, return).

Duff’s device employs a do loop inside a switch.

switch(x)        /* notice that enclosing braces aren't necessary */
do{
        ....
}while(x > 100);

But just for fun we can try a for loop…

int y;
switch(x)        /* notice that enclosing braces aren't necessary */
for(y=x; y<10; y++{
        ....
}

...or an if statement.

int a;
int b;
switch(x)        /* notice that enclosing braces aren't necessary */
        if(a == b)
        {
        ....
        }

Try compiling those examples. You'll see they work. (Obviously, you'll have to replace the ellipses with real code.) Now, is this something you'll ever use in practice? Well, Tom Duff found a use for it. So who knows? But I'm leaning toward...nah.

Conclusion

So there you have it. Duff's Device is a truly bizarre programming construct, made possible by C's leniency when it comes to switch statements, and by the cascading-goto nature of the switch itself. Any questions, feel free to ask in the comments.

Hat tip: thanks to Mike Gleen for catching a small bug in the example code.

20 thoughts on “How Duff’s Device Works”

  1. So the ‘{ case: … }’ structure in the ‘switch’ statement is treated as ‘just another statement’.

    Is there any other way to use ‘case’ statements other than in a ‘switch’ statement?

  2. Yes and no. Yes, the case keyword is part of the labeled_statement production. And yes, a labeled_statement can go (almost) anywhere in the program. But if you try to use the case keyword outside of a switch, you’ll get an error.

    So, grammatically it’s ok, but semantically it’s not.

    labeled_statement
    	: IDENTIFIER ‘:’ statement
    	| CASE constant_expression ‘:’ statement
    	| DEFAULT ‘:’ statement
    	;
    

    The only labeled_statement you can use outside of a switch statement is ‘IDENTIFIER: statement’, which is a goto label.

  3. Great article but in the interest of programmer knowledge and efficiency I wish you had mentioned how switch statements really work which at the assembly level is with a jump table. So if properly constructed a switch statement will result in the jump to the correct case in one step as opposed to a whole series of decisions like the nested if else construct.

  4. Thanks for reading!

    You’re right about the jump table. I debated including a paragraph or two on the topic. However, I was really targeting more of a “lay” audience with this article, and I didn’t want to get too caught up in implementation details. It took a lot more words than I figured it would just to untangle the do and the switch.

    I’m planning a followup article which will go more in depth (and actually describe what the code itself does. As you probably noticed, I skirted that issue.)

  5. I’ve always loved Duff’s Device.

    You should probably mention some of the reasoning behind it. Namely, it is a device used to manually unwind a memory copy loop, reducing the number of loop branches by roughly a factor of eight with the ability to handle arrays with a length that was not a multiple of eight. It utilizes the quirk of C that lets you enter a do/while loop at any point.

    This is also the point behind the order of the case statements. They’re set up to, on the first pass of the do/while, truncate the remaining memory to copy into an even multiple of eight before continuing on with the do/while which copies eight [shorts/ints/chars] at a time.

    Also, it was originally used in C’s memcpy function (IIRC)

  6. Fascinating explanation — count me among those that did not know switches worked like gotos in C. There’s one thing I still don’t understand, though. Let’s say count % 0 is 1. Why doesn’t it freak out when it sees the tail end of the do/while statement, but not the head? Or does the compiler just not care at that stage of things? The way you explained it makes sense if the do/while begins before the first case statement.

  7. C will let you enter a code block at any point. Consider:


    do {
    ... } while (expression);

    This is more or less identical in behavior to:


    loop_start:
    ...
    if(expression) goto loop_start;

    It makes sense, then, that if you were to jump into the middle of that loop, it would still function the exact same. The only difference is that you are able to skip over some of the loop on the first iteration.

  8. Hey,

    Really interested in this IDE, it looks amazing!

    I had a question or two you might be able to answer.

    1) Is this freeware?
    2) Will this be able to be easily integratable into an embedded development environment, for, say, different Atmel chips (via plugins or config files or whatever)

    Thanks!

  9. Hello Alex,

    First, thank you very much for the positive support.

    Regarding question 2, one of our main goals is to target the embedded engineering market, specifically, develop plugins for many of the main uC/DSP manufacturers such as TI, Atmel, Microchip, etc. So yes, we definitely want to integrate this for Atmel chips.

    As for freeware, we are going to charge for Tenacious C. However, if you sign up for the beta version, that will be free, and our beta users will receive a nice discount when the full version is released, if said user fills out a survey about the IDE.

    Once again, thank you for your interest.

  10. i believe Duff’s Device is also useful for building cooperative multitasking frameworks … try using a static variable in the switch, and use it to control the state of some stepwise task. the return value could be a completion code. have fun!

  11. Awesome! Seems to me that this program has the potential to be a necessity for any C programming course. It is exciting to think of its potential applications in embedded programming. I look forward to using it.

  12. Call me stupid but what’s difference between Duff’s Device and just simple loop like this:

    while (count– > 0)
    *to = *from++;

  13. In your example, the end condition is checked with every iteration.
    This is time consuming.
    Duff’s device is a particularly elaborate form of loop unwinding, which allows one to avoid performing that check with each copy.
    In Duff’s case, if I recall correctly, he was interfacing with a video display device, and there were critical timing constraints involved.

  14. Think about what the while loop looks like in terms of gotos:

    label_start:
    if ( count <= 0) goto label_finish;
    *to = *from++;
    count--;
    goto label_start;
    label_finish:

    So for every memory write, we have to execute a test, an increment, and a jump statement. With Duff’s Device, these statements are only executed every 8 memory writes (after the first cycle, at least).

  15. Duff’s Device is really hideous C, but it wouldn’t look all that weird in assembly. Do compilers do any loop unraveling like this during optimization? Since code size is rarely limiting, I would think that a compiler could do this at a much larger scale. For instance, a compiler could try to unravel the loop such that the function just fits in the L1 instruction cache (assuming count is typically large).

  16. …love the name! Well, we have a tentative beta release for Dec. 1 with worst-case beta release of Jan 1. Also, we want to receive feedback from our beta users. So to answer your question, a full release would probably be available somewhere between Jan 15th and Feb 15th. Thank you for your interest.

Comments are closed.