I've just come across the following signature for which I want to write
NCI bindings:
/* Get the dimensions of a rendered string of text */
int SDLCALL TTF_SizeText(TTF_Font *font, const char *text, int *w, int
*h);
*w and *h are out parameters.
Per my reading of the NCI PDD and the code, this isn't yet supported.
I *could* fake it up by passing in an unmanaged struct and hoping that
the alignment all works out. That's really not a long-term solution.
It may also have portability issues, besides the general ugliness. (I
do love tweaking people who think that C has a working type system,
though.)
Ideally, there'd be an NCI signature that told the NCI wrapper to
generate and pass in the appropriate out parameters and then store them
in the appropriate registers, so I could call this from Parrot with:
(width, height) = SizeText(font, text);
It does mean revamping PDD16 a bit though. Dan?
-- c
> *w and *h are out parameters.
> Per my reading of the NCI PDD and the code, this isn't yet supported.
Then the POD is missing some items:
,--[ src/call_list.txt ]
| # 2 - pointer to short
| # 3 - pointer to int
| # 4 - pointer to long
`-----------------------
> (width, height) = SizeText(font, text);
This signature doesn't match the call's signature. Shuffling return
values around like that isn't really pleasant and you don't get it that
way from C.
> It does mean revamping PDD16 a bit though. Dan?
leo
> > Per my reading of the NCI PDD and the code, this isn't yet supported.
>
> Then the POD is missing some items:
>
> ,--[ src/call_list.txt ]
> | # 2 - pointer to short
> | # 3 - pointer to int
> | # 4 - pointer to long
> `-----------------------
Patch attached for those.
> > (width, height) = SizeText(font, text);
>
> This signature doesn't match the call's signature. Shuffling return
> values around like that isn't really pleasant and you don't get it that
> way from C.
That's true. Still, there has to be some extra support for out
parameters, or else NCI will walk all over the newly-updated contents of
the registers with the return results.
I patched pcf_i_pt33() manually to prevent it from assigning to I5 and
the following call does exactly what I want:
(width, height) = SizeText( font, text, width, height )
I don't particularly mind if I have to give it a signature of 'ipt3!3!'
or whatever to signify that I want the NCI sub to write into the
appropriate registers, but I do want to be able to fetch the values it
writes.
-- c
> ... Still, there has to be some extra support for out
> parameters, or else NCI will walk all over the newly-updated contents of
> the registers with the return results.
Ah yep. That's a problem.
> I patched pcf_i_pt33()
missing test?
> ... manually to prevent it from assigning to I5 and
> the following call does exactly what I want:
> (width, height) = SizeText( font, text, width, height )
Well, that's ok for this one. Bu, what if we have:
int foo(int *i, int *j) {}
in I5 in I6
out I5
We could fake that as:
(i, j, retval) = foo(i, j)
and count each pointer type as retval too. Or something...
Or, we forget about these special cased pointer to int and pass a
managed struct.
leo
> > I patched pcf_i_pt33()
>
> missing test?
That's an autogenerated file and my patch was only a local proof of
concept. I'm not sure exactly how the interface should look, so I
haven't written the test yet.
> Well, that's ok for this one. Bu, what if we have:
>
> int foo(int *i, int *j) {}
>
> in I5 in I6
> out I5
>
> We could fake that as:
>
> (i, j, retval) = foo(i, j)
>
> and count each pointer type as retval too. Or something...
That seems the easiest to me. It's clearer, Parrot-wise, to follow with
the calling conventions. I'd rather expose those return conventions
than the C out parameter interface. Yuck.
> Or, we forget about these special cased pointer to int and pass a
> managed struct.
That's cleaner from the C side, but it's enough of a pain to set up
managed structs on the calling side that I'd rather do it only when it's
absolutely necessary.
Provided we pick a convention that makes sense (that is, out parameters
go in I5, I6, ... In, and then the actual returned value goes at the end
of the list in In+1) and stick with it, I like option A a little better.
If that's the case, we need some way of marking parameters as having
important return values in src/call_list.txt and a bit of modification
to save those values in the generated src/nci.c.
-- c
> > Or, we forget about these special cased pointer to int and pass a
> > managed struct.
>
> That's cleaner from the C side, but it's enough of a pain to set up
> managed structs on the calling side that I'd rather do it only when it's
> absolutely necessary.
>
> Provided we pick a convention that makes sense (that is, out parameters
> go in I5, I6, ... In, and then the actual returned value goes at the end
> of the list in In+1) and stick with it, I like option A a little better.
Is there a good reason not to put the returned value first?
Makes more sense to me.
Tim.
While it's much nicer to reorder the parameters, I left them in that
way on purpose. Once you start reordering in and out parameters then
you get into multiple joined parameters (buffer pointers and lengths)
and then it starts (well, continues) to get really nasty.
I'm not opposed to a scheme, mind, but I wonder if maybe we'd be
better off leaving NCI what it is (really primitive) and building a
tool to do the cleanup of the parameters that generates bytecode to
do it instead.
--
Dan
--------------------------------------"it's like this"-------------------
Dan Sugalski even samurai
d...@sidhe.org have teddy bears and even
teddy bears get drunk
> While it's much nicer to reorder the parameters, I left them in that
> way on purpose. Once you start reordering in and out parameters then
> you get into multiple joined parameters (buffer pointers and lengths)
> and then it starts (well, continues) to get really nasty.
Would following Tim's suggestion to put retval in the first return
register and the out parameters in subsequent registers be more to your
liking?
> I'm not opposed to a scheme, mind, but I wonder if maybe we'd be
> better off leaving NCI what it is (really primitive) and building a
> tool to do the cleanup of the parameters that generates bytecode to
> do it instead.
Unless we go with Leo's other idea of passing in a struct containing out
parameters, there's currently no way to do this from bytecode. By the
time you're back to bytecode from the thunk in nci.c, one of your out
parameters is gone forever, if a collision occurred.
The struct approach has merit in that it keeps the simple case simple
and requires few, simple changes to nci.c (if at all). It has a
drawbacks though. It increases the differences between the function
signatures for the native calls and the setup for Parrot calls in a way
that's visible to users. (Oh, it wants an int pointer and a string
pointer, but I have to pass one thing, a pointer to a struct containing
an int and a string.)
Building structs to pass around is currently very verbose and my least
favorite part of NCI. (Again, it's not nearly as tricky as XS, for
which I'm very grateful.)
On the other hand, this is likely an odd case. It only shows up when
there's a return value of the same register type as an out parameter in
the first parameter register. That's a bit of a coincidence.
It still would be nice to have that work deliberately, not accidentally.
-- c
> > Provided we pick a convention that makes sense (that is, out parameters
> > go in I5, I6, ... In, and then the actual returned value goes at the end
> > of the list in In+1) and stick with it, I like option A a little better.
>
> Is there a good reason not to put the returned value first?
> Makes more sense to me.
Nope, no reason. I flipped a coin and described the scheme for
'heads'. After thinking a little more, I agree with you.
-- c
> Building structs to pass around is currently very verbose and my least
> favorite part of NCI. (Again, it's not nearly as tricky as XS, for
> which I'm very grateful.)
Building a struct is still the right thing. We currently already have
some more (unimplemented?) pointer stuff:
# 2 - pointer to short
# 3 - pointer to int
# 4 - pointer to long
# P - void * <<< that's probably 'b'
# B - void **
# L - Long array
# T - Array of string pointers (Converted to cstrings)
Now put in some permutations of these to generated more signatures and
src/nci.c will fill the disc ;) All that stuff above is just one
signature 'p' plus an accompanied struct PMC describing the contents.
What about the managed struct generator that was discussed some time
ago?
leo
Nah, those work. And I'm using quite a number of them as part of the
postgres wrapper. (One of the reasons I have a non-jit build, since
they're not implemented in it)
> Nah, those work. And I'm using quite a number of them as part of the
> postgres wrapper. (One of the reasons I have a non-jit build, since
> they're not implemented in it)
If JIT can't build a signature like 'L' or whatnot, it falls back to
hard-wired constructed functions. Or at least it should. Did you actually
test it. A propos test ... you know it ;)
I'm still thinking that we should reduce the amount of signatures. Some
special pointer types could be ok, though, *if* we can find something
for/against the in/out problem.
leo
Yes, of course. Parrot built with the JIT throws a fatal exception
when loading up the postgres.imc library.
>I'm still thinking that we should reduce the amount of signatures. Some
>special pointer types could be ok, though, *if* we can find something
>for/against the in/out problem.
I'm fine with that, if we can.
> I'm still thinking that we should reduce the amount of signatures. Some
> special pointer types could be ok, though, *if* we can find something
> for/against the in/out problem.
Brainstorming again, I had a bit of insight.
The real problem isn't the impedence mismatch between stack calls and
register calls. It's that low-level values in Parrot I, N, and S
registers are too transient.
Would it be easier to require that all out parameters use PMCs instead
that are converted to their low-level types? For example, for my
signature of:
int SDLCALL TTF_SizeText(TTF_Font *font, const char *text, int *w, int
*h);
... instead of passing values in I5 and I6, I'd pass two PerlInt (or
ParrotInt or whatever) PMCs in P6 and P7. If the NCI thunk could
convert these, I wouldn't need parameter reordering or anything as I'd
still have some way to get at the value.
I think that'd also be shorter than setting up a temporary struct.
I'm at work now (writing about video games -- yeah, you guys should hate
me today), so I don't really have time to wire up a test, but I don't
see any drawbacks at the moment.
-- c
> Yes, of course. Parrot built with the JIT throws a fatal exception
> when loading up the postgres.imc library.
Should be fixed now.
leo
Cool, thanks.
> Would it be easier to require that all out parameters use PMCs instead
> that are converted to their low-level types? For example, for my
> signature of:
> int SDLCALL TTF_SizeText(TTF_Font *font, const char *text, int *w, int
> *h);
A PMC doesn't work for int constants (or not w/o overhead). But for the
"int* w" types, a PMC wouldn't hurt - or better, yes, sounds good.
Any integer typed PMC with PMC_int_val(pmc) holding the INTVAL would do
it.
leo