In article <11*********************@z14g2000cwz.googlegroups. com>,
sl*******@yahoo.com <sl*******@gmail.com> wrote:
bluejack wrote: If C required the functionality, it could be translated in different
ways. Eg, instead of simply putting in a "call addy" assembly for every
function pointer, the function pointer could "translate" to a snippet of
assembly code. For a genuine function call that code might be "call addy",
whilst for a noop (with a return value) it might be a "mov 0x00 (addy)" code.
But in YOUR case, this won't work because the information is run-time
(you stated in earlier posts that macros won't work for you). So the
compiler MUST ALWAYS make a call and do a return. So, for the noop
function you wanted you'll be facint call and return overhead. Like you
said earlier, checking for NULL is much more lightweight and much
faster (compiles to a single instruction on a lot of platforms).
I think you missed bluejack's idea.
The usual implementation semantics for a function pointer is:
- set up any register preservation needed
- set up the parameters
- unconditionally perform a subroutine call to the address indicated
by the function pointer -- historically if not to the actual instruction
location whose value is stored in the pointer, then to an instruction
location determined by taking the pointer value to be an offset from
some register or some segment pointer
- save any expected return parameters
- undo any necessary register preservation
with this all handled inline.
A potential replacement semantics for a function pointer would be something
like this:
- construct a pointer to a block of code that would set up the
parameters, and another pointer to a block of code that would
retreive return parameters to the proper locatons, and put the address
of the structure containing both into a register reserved for this
purpose... or push the structure contents onto the stack
- unconditionally perform a lightweight call to the address indicated
by the function pointer, either as a direct value or as an offset
as per the above. A lightweight call would just record the return
address and then do the branch
- deallocate the above structure / pop the values from the stack
The process of building a function pointer would then involve writing
into memory one of two things:
- if the pointer is NULL, just write a lightweight jump back to the
return location, without having called the setup or teardown routine
- otherwise, write a sequence to retrieve the address of the
setup routine and to perform a lightweight call to it, followed by
a sequence to perform the subroutine call the the actual code
location, followed by a sequence to retrieve the address of the
teardown routine and to perform a lightweight call to it.
The compiler would, in this semantics, pre-compile the setup and
teardown routines according the current context of what registers were
used and where the arguments were to be retrieved from, so the
'construct a pointer' bit would simply be a matter of saving two
values known in advance to the compiler. The address saved in the
function pointer variable would still be dynamic, but only a light
dynamic call would be used, rather than a full "preserve all the registers,
save all the masks, change contexts. The code that was branched
-to- would do nothing, gracefully, for the no-op (NULL) case,
but for the non-NULL case, at the time the pointer was created the
compiler stored the code to deal with the parameters and perform the
"real" subroutine call.
There are three major differences between the above and the current
semantics.
The first is that I have created a new definition, that deliberately
taking the function pointer of NULL results in a something that is a no-op
rather than a crash-the-program-op.
The second is that I have -defined- this NULL case as not
even evaluating the arguments at all -- but that part of it is easily
redefined if you want to evaluate the arguments anyhow (in case of
side-effects.)
The third difference is that with the current traditional semantics,
*all* code is always in fixed memory and precompiled, so all aspects
of code itself can be stored in read-only (execute-only) memory, whereas
in the revised semantics, I have framed it in terms of the
function-pointer operation writing RAM that contains code that will
later be executed, thus needing a section that is writable and
executable, and (in a naive implementation) quite extendable
since one could potentially loop taking pointers to functions.
As C semantics do not allow for user creation of routines on the
fly, then an optimization of what I have described would be possible:
it would be possible for the program to take note of all routines
whose pointer is ever taken, and pre-write the code for calling
those, and write just those handlers to read-only (execute only)
memory, at locations that it knows about.
Indeed, this could be taken further as follows:
Instead of having the current semantics of inline parameter
setup and so on, followed by unconditional branch, pre-compile
the code that would be needed to do the parameter handling,
and do a lightweight branch to the address designated by the
stored function pointer. At compile time, the compiler is
able to detect a function pointer to NULL, and can return as
the pointer a block of code that does nothing gracefully and returns
with a light return; for all other calls, the address would be to the
be function code block, which would call the setup block, call
the real code just below, then call the teardown block, then do a
light return. All of this involves only precompiled entities and so
can go into read-only (execute-only) memory.
This should, I believe, work no matter what -legal- function pointer
manipulations were undertaken. It isn't possible to construct a
new function pointer such as by doing pointer arithmetic on an
existing one, and it isn't possible to construct a new NULL pointer
by subtracting a function pointer from itself. Therefore, no matter
how many levels one goes through, every operation on a
function pointer amounts to either passing a compile-(well, link-)
time constant around, storing such a constant, or taking the
address of a memory location that contains such a constant and working
with that. One should thus be able to implement this with just the minor
change to C semantics of permitting a no-op function pointer constructor,
which one might or might not choose to be accompanied by the semantics
of not evaluating the parameters.
Now, I don't imagine for a moment that anyone would bother to go
through the trouble to actually implement this, since clearly it
is only an optimization and the C standards stay well away from
matters of efficency. All I'm saying is that bluejack's proposal
is not "impossible" and could be made to work. But it'd only
-matter- for architectures and APIs on which there is a lightweight
call/return mechanism that is much less costly than the API's
full-call requirements.
Note: I cannot at the moment think of any implementation of the
idea that does not involve at least -one- call/return: to go
below that, one would need self-modifying code... or code that
just tests for NULL and branches around the call, which is of course
what the CALL_MAYBE_NOOP macro does...
(Yes, we've looped back to where we started, but now we know that
the idea wasn't impossible, but that the CALL_MAYBE_NOOP implementation
is probably much more efficient anyhow...)
--
I was very young in those days, but I was also rather dim.
-- Christopher Priest