It turned out a lot of the namespace stuff still worked. The biggest thing for the first pass is that we don't fallback to the global function or constant if there isn't a namespaced one.
Also, when a constant has a ##\## anywhere in it it throw an error when it isn't defined, instead of assuming the string.
PolicyArray splits the ArrayData implementation in two parts. ArrayShell implements the baroque ArrayData implementation in terms of a much smaller statically-bound core that concerns itself exclusively with the storage strategy for the array. This way the two aspects can be worked on separately, and different stores can be easily plugged into the given ArrayShell.
To give a better perspective, the featured SimpleArrayStore has about 340 lines all told (including solid documentation), whereas ArrayShell has some 1100 lines. The shell can be reused with other stores of unbounded sophistication. The store needs to implement only 22 primitives, some of which are trivial. Basically a new store implementation saves 1100 of difficult-to-get-right lines of code right off the bat, and only needs to focus on implementing 22 well-defined primitives.
Things to watch for when reviewing:
- Is the store API small/expressive enough? What should be added/removed?
- Are various forms of duplication present? If so, how could code be factored better?
- Any subtle change in semantics from HphpArray and friends that should be minded?
Known issues:
* Currently ArrayShell is defined as:
class ArrayShell : public ArrayData, private SimpleArrayStore { ... };
when in fact it should be:
template <class StorePolicy>
class ArrayShell : public ArrayData, private StorePolicy { ... };
Extenuating circumstances related to allocator design prevent use of templates at this point. I'll work on these in parallel with the review.
* growNoResize is almost always prefaced by a test-and-grow. This sequence should be encoded in a function.
* The growth policy is spread all over the place in a subtle form of duplication.
* The current store design makes almost no effort to be particularly efficient.
This func took 2 params and ignored the second one. While I was there I made the functions call eachother. Hopefully inlining is smart enough to let me clean this code.
I got a bit carried away, but I think this is better.
In order to ensure that we can continue to interpret code when setting up a step out, even if we're stepping out to jitted code, we set the saved RIP in each ActRec on the stack to the retFromInterpretedFrame() helper. However, this is wrong for generator functions. There is a different variant of the function for generators, so use that when appropriate. Also added a check to ensure we only changed return addresses to jitted code. Mark thought of a rare case where we could have a return address to a C++ helper in there, so leave those alone.
The debugger's Instrument command does not work with the JIT; any instrumentation requests will be ignored for jitted code. We believe that no one is using this at this time. I'm starting with a small diff to disable the command and remove the impact it has on the interpreter for now, just in case we're wrong. Once the command has been disabled for a few weeks I'll come back and remove all of the code (task 2376711).
InstHelpers was just pure dead code, so I nuked it.
Rather than test RuntimeOption::EvalJit and 5 thread locals to determine
whether or not to run the jit on each re-entry, maintain one thread local.
Make various RequestInjectionData fields private to ensure that the jit
flag is kept in sync.
This cleans up the code a lot, and takes it out of various hot
paths. It will impact perf for requests where intercepts are used,
but no longer penalizes requests that don't use intercepts.
We're paying for some null checks here each time we do a
backtrace. Inspected all usage sites of the other StaticStrings to
ensure adding "consts" shouldn't change behavior (similar to that
ternary operator issue @smith ran into). Also remove a bunch of
temporary smart pointers that we don't need anymore.
We already look up the PC for every frame as we're taking the
backtrace, so we can just skip them when going through a backtrace
frame. Also, there is currently a guarantee that if an exception
propagates through a frame where pc was pointing at RetC, all the
locals and the $this pointer have already been decref'd, so we don't
need to use a null check to determine this.
This improves both Next and Out to avoid interpreting and stepping everything between when they start and finish. Out now lets the program run free until a pseudo-breakpoint at the return site it hit. Next continues to single-step the source line being stepped over, but now lets the program run free under calls made from that line.
The logic in Next regarding "calls made from that line" is extremely generic. We don't look at, say, call opcodes and decide to do something special. Rather, when we find we're off the original source line and a frame deeper we setup a "step out" operation much like the Out command then let the program run free. When we reach our return point, we continue stepping like normal. This accounts for not just calls, but iterators, and anything else that causes more PHP to run under the original source line.
This change moves the flow control logic down in to the respective cmds: Next, Step, Out, Continue. These cmds get a crack at executing at various points in the interrupt/command processing path. These cmds now own setting up the last location filter, whether they need VM interrupts, and whether they're done or not.
There are two dynamic_cast<HphpArray*> instances in bytecode.cpp that cause code to fail when using other types of arrays. However, the necessity of those casts seems to have disappeared in the meantime because simply removing them compiles and runs. I have ran full 'fbmake runtests' after removing the first cast (all passed). Then I removed the second cast (in lexical order) and ran the tests again. This time test/zend/good/ext-hash/hash_file_basic.php failed, but when I ran it again in separation it passed.
I did an experiment turning off all stack checks, and it
looks like it does cost something. This seemed like an easy way to
turn it off some of the time, but maybe we should resurrect the SEGV
handler.
We store a bunch of data into an ActRec on the stack, then
call a php-level function that calls a C++ function to copy it into an
ActRec on the heap. This should hopefully be a little better.
We can't box a non-ref value for a ref param because
we could be modifying an array with refCount != 1.
zend warns, skips the call and returns null. For now, this
makes us warn, but do the call (without modifying the array).
A file scope flag controls whether to skip the call or not,
and should be changed (and eliminated) once all our tests pass.
With the flag turned on, we still dont match zend's behavior,
because its happy to go ahead and call the function with no
warning in the case of a literal array parameter
(cuf('foo', array(1))), even though it warns and skips it when
the array is in a variable ($a=array(1); cuf('foo', $a)).
Sets up the translator analyze pass to create a Tracelet for
the callee at every statically-known FCall. If the callee has an
appropriate shape (in this diff, it must be a function consisting of
"return $this->foo" for a declared property), we can inline it in
HHIR. Restructures the IR relating to frames some so we can eliminate
the stores relating to ActRec in this simple case (see the comments in
dce.cpp and hhbctranslator.cpp for details). Includes partial support
for inlining callees with locals, but it's disabled for now because
they will keep the frame live.
This is a partial step towards merging the HPHP::VM namespace
up into its parent. To keep it reviewable/mergeable I'm not doing
everything at once here, but most of the code I've touched seems
improved. I've drawn an invisible line around the jit, Unit and
its cohort (Class, Func, PreClass, etc.); we'll get back to them
soon.
Add a lot of comments to the debugger based on my current understanding of it. These may change in the future as we learn more, but they're helpful right now.
Also moved a few small things around in the code to clarify their purpose or scope. I.e., making a few things private, renaming a few functions, etc. No real logic changes, though. Also minor dead code removal. Also a few lint errors.
Make the targetcache the one true home for constants,
so we dont need to (also) insert them all into
VMExecutionContext::m_constants (which is now gone).
By also making "non-volatile" constants persistent, we save
initializing most of them at all in RepoAuthoritative mode.
Add Eval.EnableArgsInBacktraces, which defaults to !Repo.Authoritative. This allows us to test what happens when backtraces keep references to function arguments even when testing in authoritative mode. Modified the test harness to set this to true when using Repo.Authoritative.
The CreateCl translation dereferenced its targetcache handle
at compile time instead of in the TC, so we could get "class
undefined" errors if the targetcache slot in the thread that jitted it
didn't happen to have that closure defined. Fix that, combine the two
helper calls, and use nativecalls so we get register precoloring.
This diff addresses what we called "step 1" in the task: simply ensure that any C++ exceptions that escape a destructor get rethrown and can continue to propagate naturally. The exception is remembered on the thread, and rethrown when we check for surprises later. If multiple destructors let C++ exceptions escape the last one to escape will be the one rethrown at the next surprise check.
This also ensures that C++ exceptions prevent more PHP code from running, by omitting calls to __destruct methods as we unwind the stack.
Finally, this also enables surprise checks for OnFunctionExit unless we're unwinding, in which case surprises remain unchecked so they can propagate later.
This is different than Zend's behavior, where destructors do run as fatals unwind.
This is the bottleneck for closure perf. Instead of all the code in the prologue, it turned out the expensive thing about closures was constructing them. This bypasses all the roundabout copying involved with __construct and makes a new opcode to do the same thing.
Generators should specify function names as the name instead of the
full name for consistency with Zend 5.5 (and more importantly because
this breaks PHPUnit), this was exposed by the backtrace removal of
file and line info
Adds runtime support for non-class typehints. Typedefs are
introduced using type statements, and autoloaded via a new autoload
map entry. Shapes are parsed but the structure is currently thrown
away and treated as arrays at runtime. This extends the NamedEntity
structure to sometimes cache 'NameDefs', which are either Typedef*'s
or Class*'s. VerifyParamType now has to check for typedefs if an
object fails a class check, or when checking non-Object types against
a non-primitive type name that isn't a class.
If we failed to get the translation for a cloned closure, we didnt
clean the registers.
VMRegAnchor::VMRegAncor(ActRec*,bool) didnt know how to set things
up for continuations, and had an unused bool parameter.
RetFromInterpretedFrame isnt suitable for returning from a
generator - so just preserve m_savedRip across the doFCall.
unserialize() and call_user_func_array() were straightforward. They were
called from all over the runtime, but I renamed those implementations
and codemodded the runtime.
The is_* functions were only ever being called by the CVarRef signature,
so I deleted all the other ones (same for f_gettype). Only some of the
is_* functions were being called from the runtime, so I made inline
versions of those without the f_ prefix.
The late static bound class was being used for the context. This
was wrong, but before this change there was no context at all, so
it would have taken newly written code to expose the bug.
However, a given continuation can be instantiated from a large number
of different lsb classes (but only one context class) which meant that
we got an explosion of unnecessary translations.
Fixing various bugs all over the VM that make assumptions about RefData
and TypedValue layout. Here are the assumptions fixed by this diff:
offsetof(RefData, m_tv) == 0. Both JIT's assumed this in many subtle
ways, by punning RefData* as TypedValue* without adding an offset.
This assumption also causes RefData._count to overlap TypedValue.m_aux,
which constraints TypedValue layout.
offsetof(TypedValue, m_data) == 0. gen_ext_hhvm.php assumes you
can cast TypedValue* to Value*; the JITs often weren't using
offsetof(TypedValue, m_data) in their addressing calculations. HHIR
assumed return-by-value TV's have m_data/m_type in rax/rdx, which
can change when TV layout changes.
offsetof(TypedValue, m_type) > 8 is an assumption baked into the
pass-by-value register assignment logic in HHIR's codegen.cpp; if
the type is in the low word, register assignment is swapped.
sizeof(TypedValue::m_type) == 4. We used dword-sized operations
in both JIT's when accessing m_type. Now, we use helper functions
that are sensitive to sizeof(DataType)
Configuration:
DEBUG=: (opt) same layouts as trunk for RefData & TypedValue
DEBUG=1: (dbg) new RefData layout (m_tv doesn't overlap RefData::_count)
PACKED_TV=1, DEBUG=*: new RefData and TypedValue layout.
I've found several bugs recently due to the TypedValue plausible
check being too loose. Our DataType enum values are now sparse,
and we miss garbage that happens to fall inside the [-10, 127]
range, which is >50% of possible garbage values.
Merge ContDone+ContExit into ContRetC with added support of passing results. This variable passing mechanism is not exposed to the PHP as the ReturnStatements in generators do not contain result expression. However, this is exposed by restored hphp_continuation_done() built-in to allow experimentation.
The idea is that once we introduce ContYield opcode (merge of all opcodes used by YieldExpression), we could change ContRetC and ContYield to leave result and done-status on the stack and leave it up to the caller (ContNext/ContSend/ContRaise) to fill in Continuation fields. This will make these opcodes more generic and useful for other things, while allowing us to move some properties to the VM and kill opcodes like ContCurrent.
Continuation::send() uses m_received field to transfer the value inside
the continuation. This field remains set until the continuation is
iterated the next time.
Unset this field early by ContReceive so that the memory can be
reclaimed.
Of the four horsemen of the SmartAllocator, ArrayData was the only virtual
call. This meant an extra layer of indirection when coming from the TC
to allow the c++ compiler to emit its virtual call, and slightly larger
callsites when using non-generic paths.
While we're moving in this direction, consolidate ArrayData introspection
on its type enum. isSharedMap() was previously implemented with a vtable
slot, and we had no way of asking if an ArrayData was a NameValueTable.