The the code is being translated, we statically know offset of
Continuaton from its ActRec. Use this knowledge to emit data fetches
relative to ActRec rather than relative to Continuation loaded from
local 0.
Flow control commands should not evaluate the conditions of conditional breakpoints when enabling and disabling breakpoints during stepping. Also, all breakpoints, rather than just the first matching breakpoint should be enabled/disabled.
This diff creates IRTranslator, which creates and uses an
HhbcTranslator to implement the translate* methods. It can be used
independently of Translator or TranslatorX64 (it isn't yet but my next
region compiler diff uses it). I also moved a bunch of methods out of
inappropriate classes and changed the type guard/assert methods in
HhbcTranslator to use RegionDesc::Location instead of Transl::Location
and fixes a local tracking issue in translateRegion.
This is an incremental step towards moving it all the way
to hphp/server. This flattens base but doesn't untangle
the server files from lib_hphp_runtime
In the region JIT, we may end up with a CheckType on a source with a
known type that is boxed but different from the boxed type parameter
of CheckType. We still want to keep such CheckType instruction to
communicate the type of its dest to the following code. However, that
instruction doesn't need to generate any code since the inner types
are checked at the uses (via LdRef).
Added IR opcodes to perform MIter* instructions in JIT. Added IterBreakV bytecode
operation to break out of multiple loops containing iterators. Emitter and assembler
were modified to support such use.
It was asserting that the input to a SpillStack is
a subtype of one of Gen, Cls or None, rather than subtype of
Gen|Cls or None.
Added a new type Type::StackElem, and used it.
Added some test cases. Also now permit caller=>func() type of breakpoints. To make this work, I added a method, getCallingSite, to class InterruptSite and use this to walk the stack when checking if a site matches a break point that has more than one function name.
Updates continuations to allow yielding of a key-value
pair from a generator. Adds bytecode instructions (PackContK,
ContKey) for using the new feature, and adds IR instructions
(ContUpdateIdx, ContIncKey) to help get it down to the metal
(in particular, ContIncKey attempts to keep the current use-cases
as fast as possible).
This diff removes the Marker opcode, replacing it with a BCMarker
struct in each IRInstruction. This gives us fewer redundant lines in IRTrace
dumps and allows for more straightforward control of which IRInstructions are
associated with which bytecodes. I took this opportunity to do some more
cleanup of ir dumps as well, and it's now possible to interpOne every codegen
punt.
PackCont opcode is always followed by ContExit so let's join them into ContSuspend. ContResume counterpart may come later (replacing ContNext/Send/Raise/Enter).
Continuation's m_received field is used to transfer values and
exceptions to the Continuation by send() and raise() methods. It has a
valid value only for a short duration of time, between
ContNext/ContSend/ContRaise and UnpackCont opcodes, when no other
operations could possibly occur. Let's free these 16 bytes of memory and
pass the value thru the VM stack.
To achieve this goal, ContEnter was modified to accept a value to be
transferred on the stack. The existence of the value on the stack is
then acknowledged by the UnpackCont opcode, which is the first opcode
executed after ContEnter enters the continuation body.
ContNext then becomes a trivial Null and is thus removed, ContSend just
teleports the value from local 0 to the stack (I will experiment in a
follow up diff with replacing ContSent by CGetL+UnsetL). ContRaise has
to update the label, but the eventual plan is to enter into unwinder
directly from the raise() method.
Add ContCheck opcode that moves away responsibility of checking
Continuation status from Cont{Next,Send,Raise} opcodes.
This check needs to run outside of try/catch in next()/send()/raise()
methods and moving it to a separate opcode lets us merge
Cont{Next,Send,Raise} with ContEnter.
This was going to be easy, but then it wasn't. The existing
interpOne relied heavily on the outputs of NormalizedInstruction, and
those are only populated by code that we want to kill soon. It also
didn't properly deal with bytecodes that wrote to more than one local,
or more than one stack cell. So the bulk of this diff is rewriting
interpOne to not rely on the NI outputs, then it was simple to hook it
into the region translator. I also cleaned things up so we don't
attempt to translate instructions that have no hope of succeeding; we
just interp them on the first try now.
Apparently VarEnv's m_previous is not used for anything. Let's kill its
maintenance and avoid 2 native calls per Continuation
next()/send()/raise() calls.
Introducing `ZendParamMode` to as a idl flag. We are not consistent with zend on how they do their params for builtins. We cast to the expected data type. They do some checks, and if the checks don't pass they issue a warning and return (usually) `null`. This diff starts us down that path.
I'm introducing the param and using it in the places where we were emulating the calling convention in the `f_foo` functions. I'm going to follow up with converting as many as I can and then eventually this becomes the default. I also want this to be applied to php files in systemlib.
Many of the conversions are from https://github.com/php/php-src/blob/master/Zend/zend_API.c#L305
Instead, just check the class is defined and is the class we
expected at translation time (side exiting if not), and just use the
scalar value directly. If the class is Persistent or a parent of the
current context we don't need to check the Class* either.
Adding some developer docs for the debugger. The idea is to give an overview of the major implementation points so we can refer to them from the source when necessary.
This converter enables much more thorough testing of the
region translator. It currently passes all tests, though it does punt
on one or more Tracelets in roughly 1/6th of them. The two big
unimplemented features for the whole pipeline are interpOne (should be
pretty easy) and parameter reffiness checks (probably
nontrivial). I'll attack those in separate diffs next.
Cleanup a lot of hangs with either the debugger client or server in a variety of error conditions, mostly related to communication errors or the client or server exiting unexpectedly. One of the biggest fixes is that all cases where the client was left in a state where Ctrl-C wouldn't work have been fixed.
Remove lots of little snippets of dead code. If you see a function (or small set of functions/fields) deleted then it was actually dead.
I debated whether to keep throwing DebuggerClientExitException on the server, and I decided to keep it. I think it's reasonable that if you've got the server stopped and you quit the debugger that the request gets terminated rather than continuing to run.
I also considered a big change to the way Ctrl-C works, but ended up staying with what was there with just a bit of cleanup. We need to guard against people banging on Ctrl-C, which is a reasonable behavior, and I think it feels pretty reasonable with the updated message.
Finally, added many comments about how this stuff works.
Remove unnecessary optimization that can be achieved in a more general
way and simplify continuation creation code.
VMExecutionContext::createContinuationHelper was renamed to
VMExecutionContext::createCont{Func,Meth}. The createContFunc no longer
takes this/class arguments, the createContMeth transfers them in one
Type::Ctx pointer that is used natively by ActRec's m_this. Since the
whole logic of this function is to set this single field, the logic is
now simpler.
Interpreter: iopCreateCont() just passes the m_this field of the parent
ActRec.
Translator: CreateCont opcode loads m_this field using LdCtx opcode.
This opcode optimizes into LdThis, which optimizes into
DefInlineFP->SpillFrame->object and allows frame to be eliminated, a
case previously covered by InlineCreateCont.
This diff uncovered a bug in trace builder, where LdCtx in static
methods could be optimized into LdThis, if the method was called thru
object. Fixed.
This diff adds a very, very basic region translator. Together with
-vEval.JitRegionSelector=method, it is capable of translating very simple
methods without using a Tracelet. It takes in a RegionDesc struct and creates
NormalizedInstructions on the stack to drive the translate* methods in
irtranslator.cpp. I started trying to get all tests passing with
JitRegionSelector=method (by replacing lots of asserts with punts) but decided
it's not worth it right now.
I'm planning on writing a Tracelet -> RegionDesc converter next and expanding
translateRegion to handle everything that throws at it.
While debugging a sandbox crash, I spent some time looking at a huge
sequnce of conditional masks, compares and branches that didnt seem
to belong in the code I was debugging. Finally realized that it was
a reffiness check. It looked way too complicated, so I investigated.
Part of the problem was that we were avoiding a malloc in the case
of a zero param function at the expense of an extra check.
Instead, this diff always sets up at least 64 bits worth of m_refBitVec.
But by using the space set aside for the pointer in Func::m_shared it
avoids a malloc for any function with fewer than 65 arguments, and
avoids the numParams check for the first 64 parameters.
In addition, the existing code was spitting out a generic test for
the guard condition - (mask & bits) == value - where mask and value
are known constants. Since the most common case is that value == 0
(all the parameters are expected to be by value), we can usually omit
the compare. In addition, since most functions only have a small
number of parameters, we can usually get away with 8 bit, or 32
bit operations.
The result is that for a typical function (fewer than 64 args, args
expected to be by value) the reffiness guard is now
test <mask>, Func::m_refBitVec[0]
jne exit
Rather than:
move <mask>, reg1
xor reg2, reg2
cmp 1, Func::m_numParams
jnl ok
test AttrVarArgs, Func::m_attrs
jne exit
jmp done
ok:
load reg3, Func::m_refBitVec[0]
and mask, reg3
cmp reg3, reg2
jne exit
done:
Save 8 bytes of m_args and its initialization for Continuations without
func_get_args() call (does not save real memory due to 16-byte alignment).
Store variable arguments in optional local.
specialized the IR instruction CGetM/SetM/IssetM for Vector and Map. IR now generates specialized code in those situation similar to what is done with Array.
Users of m_localsOffset used this offset to calculate pointer where
local variables starts. Since they are stored in the reverse order, the
equation was:
cont_ptr + m_localsOffset + num_locals - local_id - 1
We are already storing pointer to ActRec (m_arPtr), which is equal to:
cont_ptr + m_localsOffset + num_locals
This diffs kills m_localsOffset and uses m_arPtr to simplify
calculations. Continuation fields were rearranged so that
sizeof(c_Continuation) is reduced by 16 bytes.
I've been linking people to the README in this directory, but it is hard to see with all these files in here. What do people think about it moving a subdirectory? I don't love the name.
Change LdClsCns to side exit when the type is uninitialized.
On the exit path, do a LookupClsCns and ReqBindJmp for the next srckey
so forward progress is still made. Add a predictionopts case for the
common case of a LdClsCns; CheckInit being followed again by a
CheckType---in this case, hoist all the checks into LdClsCns.
Previously it returned a bool to say whether or not it had
succeeded, but always initialized the iter. The other iterators
branch on failure.
This adds the branch target which brings it closer to the other
iterators, and avoids the need to do a (pointless) CIterFree on
the failure path just to keep the validator happy.
I've also added a translation for it.
Most of the SpillStacks we do are just to keep the in-memory VM stack
clean in case a helper call throws an exception. Many of these exceptions are
vanishingly rare, so let's stop making the fast path slower for them. This diff
adds support for catch traces, which are just like normal exit traces with one
major exception: they're never jumped to from translated code. If we're
unwinding a TC frame and discover that the current rip has a catch trace
registered, the unwinder will execute it before resuming unwinding.
The unwinder parts were fairly straightfoward, then I ran into a bunch of
issues with SetM. The SetElem instruction sometimes consumes a reference to one
of its inputs, which is bad news for our optimizations. To make everything work
again, I changed SetElem to throw an exception in cases where it would've
previously decreffed the value input. This is a special exception that the new
unwinding code recognizes. When one of these is caught, the catch trace
executed for that TC frame will finish up the vector instruction and push the
value provided by the exception on the stack. This allows us to optimize most
traces under the assumption that SetM's output is the same as its input,
letting the catch trace clean things up if the helper decides that's not the
case and throws.
There is one common case where SetM's output isn't the same as its input:
setting an offset in a string base returns a new String. Luckily, we can detect
most of these at compile time so SetElem returns a new StringData* when the
base is known to be a string. If the base might be a string, SetElem will
return nullptr if the base wasn't a string, and a StringData* if it was. We
test the output in these cases and side exit if the return value of SetElem is
non-null (I have yet to see this side exit happen outside of artificially
created test cases).
Once I got things working in the vector translator, I went through every other
call to spillStack and exceptionBarrier, replacing them with catch traces as
appropriate.
Functions like array_filter and array_map need "withRef" semantics,
and while it can be simulated in php via copy-on-write, doing so
is very inefficient.
This adds
SetWithRefLM
SetWithRefRM
- similar to SetM but binds the value if it was a reference. L reads a local, R reads a return value
WIterInit
WIterInitK
WIterNext
WIterNextK
- essentially the same as the corresponding opcodes without W, but the value local is set by reference if the array element was a reference.
I was looking over the instruction table to try to understand
what may need to happen to make it easier for a memelim mark 2 to
track memory effects (on object properties and other memory
locations), and I made the mistake of documenting StProp and StPropNT
(currently unused). This led to finishing the remaining missing
entries...
Allow RuntimeType to specify a more specific Type (by adding a Class*) and transfer that Class* to Type. Extended API to allow discovering when a Type is a subclass of Type::Obj