Vitja Makarov, 05.12.2010 20:12:
> I've started woring on generators on friday.

Cool. This sounds like a larger code change, though. Try not to submit it 
as a single patch as that would likely be hard to review. Instead, put up a 
repo somewhere that others can look at, or submit bundles (as hg calls 
them) of separate commits.


> Now it can generate some code, but it doesn't work yet, what is done:
>   - switch() for resuming and argument parsing
>   - yield stuff
>   - closure temp allocation
>
> I'm going to put all the generator stuff into closure
>
> Generator function:
>
> Creates closure with all temps, args, variables, and all the internal stuff:
> Also closure have all the required generator methods __iter__(),
> __nextiter__() and so on.

Ok, why not.


> def my_generator(self, args, kwargs):
>     closure  = my_generator_closure_new()
>     closure.is_running = 0
>     closure.resume_label  = 0
>     closure.args = args
>     closure.kwargs = kwargs
>     closure.outer_scope = self
>     closure.generator_body = my_generator_body
>     return closure
>
> generator body:
>
> def my_generator_body(closure, value, is_exception):

Shouldn't that be a cdef function?


>       # switch case
>       switch (closure.resume_lable):
>           case 0:
>                  goto parse_arguments;
>           case 1:
>                  goto resume_from_yield1;
>       parse_arguments:
>       # function body here

Ok, sure, as in the CEP.


> About temps:
> now temps are allocated in closure using declare_var() and it works fine.

I think that could seriously slow down things inside of the generator, 
though. It means that it needs to jump through the closure indirection for 
every little value that it keeps around temporarily, including lots of 
intermediate C results. Also, I would expect that a lot more temps are used 
inside of the generator body than what actually needs to be kept alive 
during yields, so a single yield inside of a lengthy generator body could 
have a tremendous impact on the overall runtime and closure size.

If you only store away the live temp values before a yield, you don't even 
need to do any reference counting for them. It's just a quick bunch of C 
assignments on yield and resume. That's about the smallest impact you can get.

BTW, if you change the object struct of the generator closure anyway, maybe 
a dynamically sized PyVarObject would be a way to incorporate the temps?


> I'm not sure about manage_ref flag I guess that temps with
> manage_ref=False shouldn't be allocated on closure (am I right?)

"manage_ref=False" (for a Python object value) just means that the 
reference is either borrowed or will be stolen at some point. I'm not sure 
that stealing the reference may not happen *after* a yield, but I guess 
that would produce other problems if the reference isn't kept alive in 
another way. Nothing to worry about now, carefully crafted tests will 
eventually catch that.

In any case, all live temps (Python refs, borrowed refs and C values) may 
be needed after the yield, so they must *all* end up in the closure.


> TODO:
>   - create C function that returns closure
>   - generator utility code

At least for generator expressions, the latter wouldn't be needed for now.


> I have problem with closures and generators.
>
> Now I want to implement closure structure this way:
> struct abstract_generator {
>      PY_OBJECT_HEAD
>      /* generator internal vars */
>      PyObject *(*body)(struct closure *, PyObject *, int);
>      int resume_label;
> }

I hope you're not trying to do it manually at that level, are you? 
Declaring the fields on the closure class should just work.


> struct closure {
>      /* abstract_generator */
>      PY_OBJECT_HEAD
>      /* generator internal vars */
>      PyObject *(*body)(struct closure *, PyObject *, int);
>      int resume_label;
>      ,,,,
>      /* Closure stuff */
> } ;
>
> and generator utility function will work like this:
>
> PyObject *abstract_generator_next(PyObject *self, PyObject *args)
> {
>      struct abstract_generator *generator = (struct abstract_generator *) 
> self;
>      PyObject *retval;
>      // check running
>      retval =  generator->body(self, None, 0);
>      // unlock running
>      return retval;
> }
>
> But this way should be much better, don't know if that is possible btw.
>
> struct closure {
>      struct abstract_generator generator;
>      /* closure stuff here */
>      ...
> } ;

That's called inheritance in Cython. Just declare a subtype of the specific 
closure class.


> I add MarkGenerator visitor, that sets is_generator flag on DefNode
> and checks for returns/yields.

Why do you need a new class here? Can't the MarkClosureVisitor handle this? 
It's not like transform runs over the whole AST come for free...

Stefan
_______________________________________________
Cython-dev mailing list
Cython-dev@codespeak.net
http://codespeak.net/mailman/listinfo/cython-dev

Reply via email to