0

I have tried to play a bit with a couple of macros to try to implement yield syntactic sugar in C. Having such a convoluted transformation, my context is not being saved.

#include<stdio.h>
#include<stdlib.h>
#include<setjmp.h>

struct yield_ctx__{jmp_buf fw,bw;long last_line;};
typedef struct yield_ctx__* yield_ctx;

#define with(x)\
    for(volatile yield_ctx yield_requires_with_block__=(yield_ctx)x;\
        ((yield_requires_with_block__==NULL)?\
            (yield_requires_with_block__=(yield_ctx)malloc(sizeof(struct yield_ctx__)),\
             yield_requires_with_block__->last_line=__LINE__,\
             (x=yield_requires_with_block__)!=NULL)\
          :(x!=NULL))\
        &&((yield_requires_with_block__->last_line=setjmp(yield_requires_with_block__->bw))?\
            (longjmp(yield_requires_with_block__->fw,yield_requires_with_block__->last_line),\
            (x!=NULL))\
          :(x!=NULL))\
      ;free(yield_requires_with_block__))

#define yield \
    if(\
       (yield_requires_with_block__->last_line<__LINE__+4)?\
        (setjmp(yield_requires_with_block__->fw)==__LINE__+3)\
        :1)\
    {if(yield_requires_with_block__->last_line<__LINE__+1)\
     longjmp(yield_requires_with_block__->bw,__LINE__);\
    } else return

int func_test(yield_ctx ctx, int nr)
{
    if (ctx==NULL) printf("No context!\n");
    with(ctx)
    {
        printf("Context created!\n");
        yield 1*nr;
        printf("Called back!\n");
        yield 2*nr;
        printf("Finished!\n");
    }
    return 3*nr;
}

int main()
{
    yield_ctx ctx=NULL;
    printf("%i.Welcome to yield!! Happy Coding :)\n",func_test(ctx,2));
    printf("%i.Welcome to yield!! Happy Coding :)\n",func_test(ctx,2));
    printf("%i.Welcome to yield!! Happy Coding :)\n",func_test(ctx,2));
    return 0;
}

when I run it it always runs the first part of the function, even though there is a save point at first yield.

The example is split in the following components:

  1. with macro is hiding a for loop that creates a local variable called yield_requires_with_block out of provided x parameter, naming is selected to provide a useful error if yield is used without with
  2. the second part of the for loop tests various scenarios of contexts, creates setjmp return point and jumps to saved point in the function. Line numbers are passed to prevent repeating the same setjmp, this condition should in most cases resolve to true
  3. in case we exit the with statement, free the context memory. The compiler will force a final return after the with statement.
  4. the second macro, yield tests and sets a jump exchange

I am an amateur and like to play with different concepts in code, I'm sure it's something I'm missing. Edit: per observations I have renamed the types and the inner variable. I also split the macros in multiple lines and added explicit testing of pointers to use in boolean expressions.

  • 1
    Note that you should not, in general, create function, variable, tag or macro names that start with an underscore. Part of [C11 §7.1.3 Reserved identifiers](https://port70.net/~nsz/c/c11/n1570.html#7.1.3) says: — _All identifiers that begin with an underscore and either an uppercase letter or another underscore are always reserved for any use._ — _All identifiers that begin with an underscore are always reserved for use as identifiers with file scope in both the ordinary and tag name spaces._ See also [What does double underscore (`__const`) mean in C?](https://stackoverflow.com/q/1449181) – Jonathan Leffler May 02 '22 at 20:16
  • 1
    Maybe expand those macros in a readable manner, otherwise it's impossible to understand what they do... – Marco Bonelli May 02 '22 at 20:20
  • This can _not_ work. In `main`, you do: `yield_ctx ctx=NULL;` Then, you pass to the function. But, `with` will `malloc` the pointer if it is NULL. But, the updated pointer value is _not_ propagated to the caller (e.g. `main`). So, each time the context is created anew. This is also a memory leak. – Craig Estey May 02 '22 at 20:28
  • Since the caller (e.g. `main`) has the definition it would be better with: `struct _yield_ctx ctx = { 0 };` Then, call with: `&ctx` and remove the `malloc` from `with`. – Craig Estey May 02 '22 at 20:31
  • But, using `setjmp/longjmp` for something that can be replaced with (e.g.)`switch (ctx->state++) { case 1: return 88; break; case 2: return 99; break; }` is way too complex. – Craig Estey May 02 '22 at 20:34
  • 2
    longjmp'ing into a function that has already returned is not allowed. "[If the function that called setjmp has exited (whether by return or by a different longjmp higher up the stack), the behavior is undefined](https://en.cppreference.com/w/c/program/longjmp)." – Raymond Chen May 02 '22 at 22:33
  • I see the problem, it seems setjmp/longjmp does not save and restore the stack, so going forward the stack after a new call does not find the correct content. If only I could select and save the stack beginning with the function call it might work... Thank you for your remarks, I will reflect on this further. – Mihai-Gabriel Vasile May 03 '22 at 11:02

0 Answers0