I will post some of the relevant algorithms to see what
can be done. If you missed it: there's an issue with
service calls (needed to do synchronous channel I/O)
in generators: in principle functions use the machine
stack for return addresses and the C model of returning
values, which conflicts with the ability to read or write
an schannel because service calls currently require 
returning control to the scheduler (with an actual C level
return). You cannot return to the scheduler unless you were
called by the scheduler!

This has been an ongoing thorn. The problem is created by a
conflict between the desire for flexibility the spaghetti stack
provides versus the performance and compatibility provided
by the machine stack. Machine stack swapping cannot be
done portably other than be use of pre-emptive threading.

Inlining functions gets rid of the call/return boundary causing
the machine stack barrier to evaporate. Here is the inlining
rule for functions:

          let inline_choice =
            can_inline &&
              not (mem `NoInline props) &&
              (
                mem `Inline props ||
                length exes <= syms.compiler_options.max_inline_length
              ) &&
             (
                (* inline a non-recursive function *)
                not (Flx_call.is_recursive_call uses callee callee)
                ||

                (* inline a recursive call to a recursive child *)
                Flx_call.is_recursive_call uses caller callee &&
                Flx_bsym_table.is_child bsym_table caller callee
             )
          in

The algorithm first checks if it is "possible" to inline the function.
If it is, and inlining is not explicitly excluded by the programmer,
then,  if inlining is explicitly requested by the programmer OR
the current size of the callee is under the inline limit THEN
if the function call is not recursive inline it, if it is recursive
but the call is to a child, inline it, otherwise don't inline it.

The can_inline flag checks is set by two things.
If the call is to a virtual, it checks if there is a matching instance.
[If there isn't, the call may get inlined later after further specialisation]

It also checks if there is a yield instruction in the function.
If there is, the function cannot be inlined.

Note no conflicts with the "inline" tag put there by the programmer
are reported as errors. Perhaps they should be, since inline
has actual semantics.

However all the above is only part the story!
If the application was of a generator, none of the above is done
at this point. Instead, the application is just replaced by a variable
and the variable assigned the application. The variable gets the name
"_genout_urv" + a unique integer.

Elsewhere, we look for initialisations of the form:

        v = f a;

and do the "can_inline" check, and then call "inline_check" which says:

  let inline_check caller callee props exes =
    not (mem `NoInline props) &&
    (
        mem `Inline props ||
        length exes <= syms.compiler_options.max_inline_length
    ) &&
    (
      (* only inline a recursive call to a child *)
      not (Flx_call.is_recursive_call uses caller callee) ||
      Flx_bsym_table.is_child bsym_table caller callee
    )
  in

which is slightly different (not sure why).

So the bottom line is, if you put "inline" on a function, then a call to it will
not be inlined if:

(a) it contains "yield"
(b) the call is recursive and it is not a child of the caller

Condition (b) is suboptimal! Sibling calls are excluded.
Ideally, recursive functions should be inlined once.
However I just couldn't make either of these work.

The reason for all this explanation: the following implementation of futures:


var ch = mk_schannel[int]();
spawn_fthread { write (ch,42); };
var x:int;
var flag = false;
proc fetch() {
  x = read ch;
  flag = true;
}

gen get() = { if not flag do fetch; done return x; }

println$ #get;

actually works but only by "luck" that the #get call
is inlined. For this to work reliably, the call must be
inlined (I made a closure to prove it and sure enough it
segfaulted). As you can see from the compiler code above
adding "inline" to the definition greatly improves the
chances of inlining -- for this definition it is forced.

However there's no diagnostic if it isn't, and it never is
if a closure is stored in a variable: inline adjective only applies to direct 
calls.

The situation is more complex: not only must get() be inlined,
but any "function like thing" it is contained in must be too,
up to the closest enclosing top level procedure (meaning
a procedure not called from inside a function).

One must be conscious of the fact that C primitives like:

        fun sin: double -> double = "sin($1)";

are NOT functions, they're actually (typesafe) macros: the arguments
replace the parameters as written. Most of Felix is built on such C bindings.
Scrapping functions using the machine stack would force the hard distinction
between functions and procedures to be moved from Felix abstract semantics
to the binding level.

Bindings could be fixed too: we could ban functional bindings: instead write:

        proc sin : double * &double = "*$2 = sin ($1); ";

Given a whole procedural language the only use of the machine stack
would be in leaf calls to C bindings, and only transient: perfectly safe
provided we never need closures for C binding arguments (since those
closures would have to be functional because that's the kind of callback
C expects).

Of course -- you would not write proc sin. Felix would just translate
your functional macro.

The result would be safer -- the problem is that Felix actually does
the translations now, just not for closures, that is, a variable of
function type A -> B is not translated to A * &B -> 0.

One major disadvantage of the procedural form is that because it
uses pointers it is extremely hard to optimise: you need data flow
analysis on sequences of instructions instead of recursive
pattern matching on expressions.


--
john skaller
skal...@users.sourceforge.net
http://felix-lang.org




------------------------------------------------------------------------------
Monitor your physical, virtual and cloud infrastructure from a single
web console. Get in-depth insight into apps, servers, databases, vmware,
SAP, cloud infrastructure, etc. Download 30-day Free Trial.
Pricing starts from $795 for 25 servers or applications!
http://p.sf.net/sfu/zoho_dev2dev_nov
_______________________________________________
Felix-language mailing list
Felix-language@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/felix-language

Reply via email to