> -----Original Message-----
> From: Anthony Ferrara [mailto:ircmax...@gmail.com]
> Sent: Monday, February 23, 2015 4:38 AM
> To: Zeev Suraski
> Cc: PHP internals
> Subject: Re: [PHP-DEV] JIT (was RE: [PHP-DEV] Coercive Scalar Type Hints
> RFC)
>
> Well, yes and no.
>
> In this simple example, you could generate the division as float division,
> then
> checking the mantissa to determine if it's an int.
>
> long bar(long something) {
>     double x = something / 2;
>     if (x != (double)(long)x) {
>         raise_error();
>     }
>     return foo((long) x);
> }
>
> You're still doubling the number of CPU ops and adding at least one branch
> at
> runtime, but not a massive difference.

To be honest, I missed an important part in the semantics of the sample
code, the fact that the result of the division in bar() is sent to function
with an integer type hint, which means it may with Coercive STH just as it
would with Strict STH (in retrospect I now understand you alluded to that in
your replies to Stas, but was too tired to realize that).  That means that
we could conduct identical static analysis, alerting the developer to the
exact same possible type mismatch in both Coercive STH and Strict STH.  I
actually fail now to see how the process would be any different at all
between the two modes.  This particular code requires changes in order to
work in all cases - semantic changes - probably either explicit casting to
int or - more likely - changing the type hints to float.  In either of these
cases, we'd now have fully known types for the entire flow and could
optimize it to machine code equally well and equally easily, and with the
same number of resulting CPU ops.

> However in general you'd have to use something like div_function and use a
> union type of some sort. You mention this (about checking zval.type at
> runtime). My goal would be to avoid using unions at all (and hence no
> zval).
> Because that drastically simplifies both compiler and code generator
> design.

That would be our goal as well.  And in most cases, the success ratio will
be the same between coercive and strict implementations.  In that snippet
(which again, I misanalysed) - a static analyzer will be able to alert to
the same issue, prompting the developer to fix it (equally in both Coercive
and Strict).  With the two probable fixes - an explicit cast to int, or the
more likely change of type hints across the sample from int to float - we'd
be able to conduct the same compile-time analysis and generate optimal,
ZVAL-free code in both Coercive and Strict STH.

> It's very much not about impossible.

I'm happy we have that clearly stated, as based on emails here and
elsewhere, it wasn't clear to a lot of people beforehand.  Given identical
input source code, in all the cases where it's possible to generate C-level
calls with Strict, it will also be possible to generate C-level calls with
Coercive.

By the way, that misperception that it's possible to do optimizations with
Strict that aren't possible with Dynamic/Coercive doesn't have to do just
with assertions that were stated here or elsewhere.  I've heard people
assuming  that many years ago, effectively guessing that's the case without
having any meaningful understanding about execution engines or JIT, deducing
that strict type hints would somehow turn PHP into C in terms of our ability
to optimize.

> It's about complexity.  Strict code is
> easier to reason about, it's easier to analyze and it's easier to
> code-generate
> because all of the reduced amount that you need to support. And we're not
> talking about making users change their code drastically. We're talking
> about
> -in many cases- minor tweaks.

The explicit casting risk has been beaten to death so I won't dive into it
yet again.

I think it boils down to strict STH accepting fewer inputs, which a static
analyzer can sometimes pick up, thereby prompting developers to be more
explicit in their choices of types - in turn providing more compile-time
type information and more restrictive at that - thereby making the code
easier for AOT to work on.  It still holds that given the same
explicitly-typed code, AOT/JIT can do an identical job between Strict STH
and Coercive STH.  The difference is that the Static Analyzer would be able
to alert you to some more rejections - because there would be more
rejections with Strict than there would be with Coercive - rejections that
would prompt you to change your code.  I still think that the cases where a
static analyzer can provide more insight in Strict vs. Coercive are
relatively rare.  Our ability to infer the type in compile time is identical
between both;  In cases where we can clearly know with absolute confidence
that the type we have is the type the function wants - we can optimize that
into a C call - in both cases.  If we can't infer the type in compile-time,
then the code we'd generate would be the same in both cases.  The main
difference is in cases where we can infer possible types, which would be
rejected in Strict but accepted in Coercive.  With Strict, you could simply
warn about it, pushing the developer in the direction of changing his code
(potentially making it .  With Coercive, you could generate optimized code
for the likely case, and catch-all code for the less likely cases.

> Minor tweaks that would need to be done with your proposal as well. So if
> we're going to require users change their code, why not make it opt-in and
> give them the predictability that we can?

That's off topic for the JIT discussion.  I explained why I think having two
modes would be have negative implications in the RFC.

> > Let me describe here too how it may look with coercive hints.  Instead
> > of beginning with the assertion that it must be an int, we make no
> > guess as to what it may be(*).  We would use the very same methods you
> > would use to prove or refute that it's an int, to determine whether
> > it's an int.  Our ability to deduce that it's an int is going to be
> > identical to your ability to prove that it's an int.  If we see that
> > it comes from an int type hint, from an int typed function, etc. -
> > we'd be able to generate the same ultra optimized C-level call.  If we
> > manage to deduce that it may be an int or a float, we can still create
> > an ultra-optimized calling code that would deal with just these two
> > cases, or call coerce_to_int().  If we deduce that it's a type that
> > cannot be
> converted to an int (e.g. array or resource) - we can
> > emit a compile-time error.   And if we have no idea what it is, we emit
> > a
> > regular function call.  Going back to that (*) from earlier, even if
> > we're unable to deduce what it is, we can actually assume/hope that
> > it'll be an integer and if it is - pass it on directly to the C
> > implementation with a C level function call;  And if not, go with the
> > regular
> function call.
> >
> > The machine code you're left with is pretty much equivalent in case we
> > reached the conclusion that the variable is an integer (which would be
> > roughly in the same cases you're able to prove it that it is).  The
> > difference would be that it allows for the non-integer types to be
> > accepted according to the coercion rules, which is a functional
> > difference, not performance difference.
>
> Well, the end result is pretty much equivalent. But only pretty much.
> In the example above, the few CPU ops and extra branch will very likely
> slow
> down the code significantly (more than a factor of 2).

If we manage to conclude that the value is an integer - the code would not
only be pretty much identical, but completely identical.
If we manage to conclude that the value is either an integer or a float
(which I don't believe is a very common scenario, pretty unique to the
division operator) - then in both cases a static analyzer can alert the
developer his code is potentially unsafe given certain inputs.  If the
developer decides to change his code to an explicit cast - we're back to the
first scenario.  If not - then the generated code would still be similar
between Strict and Coercive, with the difference being Strict flat out
rejecting floats, while Coercive performing some check on them to see if
they can be converted with no data loss.  In both Strict and Coercive, the
ZVAL structure will have to stick around, you'd have to check the type and
perform different actions depending on it.  It's true that if the value is
the result of the division operator, then pretty much by definition a float
fed to the hint would always fail to convert to an int without data loss,
but that's really, really a very specialized property of the division
operator.

It boils down to what semantics the developer is after, not whether Strict
can generate more efficient code.  In both cases, the static analyzer can
alert us to the same issue;  Whether we can emit more efficient int-only
code would depend on whether the developer changes his code so that the
input can clearly be inferred as an int during compile-time.

> Again, not saying this is major enough to be concerned about, but it's not
> identical. There are small differences.

I agree, but they stem from the difference in functionality, not because we
can optimize Strict code better.  Given identical input source code, Strict
and Coercive can be optimized to exactly the same code.


> The big difference though is the complexity of the analyzer and compiler
> (time, resources, programmer skill) to build one for the coercive case.
> With
> strict types, its drastically easier (simply less cases to be concerned
> about).

There doesn't have to be any difference in the complexity of the analyzer,
not in terms of developing it and not in terms of the time or runtime
resources it would consume.  Cases where you can infer the type with
confidence would be identical between Strict and Coercive.  For cases where
we don't know anything - we won't be able to optimize much in either case.
For the few (IMHO uncommon) cases where we know the type could be one of
several, we're not obligated to optimize every possible branch.  We could
opt to not optimize it at all and treat it like 'unknown', or optimize the
path for the most likely type, while keeping the rest unoptimized.

Another thought.  If we end going in the direction of the Dual Mode RFC,
we'd probably want to create JIT/AOT for both modes, given both would be
used extensively, and that the differences between the two don't justify a
position where we say JIT/AOT cannot be done for dynamic/coercive.  Having
to develop two modes for JIT is not a happy thought.

Zeev

-- 
PHP Internals - PHP Runtime Development Mailing List
To unsubscribe, visit: http://www.php.net/unsub.php

Reply via email to