I am working on code to speed up short sentences, to reduce the penalty
for writing FORTRAN-like J code. So far, so good. As part of this
work, I had to grapple with in-place operations, to allow
y =. y + 1
and
t =. 2 * 1 + z
to execute without allocating/freeing memory unduly [the second sentence
still requires one alloc/free].
I had an epiphany: it is not a BLOCK that is eligible for in-placing,
but an ARGUMENT. The same block may be inplaceable or not, depending on
the context. The question then was, How to get the in-placing info to
the verb? and I found the answer to be, In the 2 LSBs of jt. This
allows flags to be passed without changing the arguments to anything.
This works pretty well in my testbed. Here are the details.
Henry
// In-place operations
//
// The key point is that an in-place operation is connected to a
FUNCTION CALL, not an DATA BLOCK.
// For example, in (+/ % #) <: y the result of <: y has a usecount of 1
and is
// not used anywhere else, so it can be passed into the fork as an
inplaceable argument.
// This same block is not inplaceable when it is passed into # (because
it will be needed later by +/)
// but it can be inplaceable when passed into +/.
// Thus, it is up to the caller to decide whether it can tolerate having
an argument overwritten;
// if so, it marks the argument as inplaceable for that call only.
//
// Generally an argument is eligible for inplacing if its usecount is 1,
but there are exceptions.
// We can't inplace any argument that came from the queue, because the
sentence may
// be reexecuted later. [This is actually two different cases. Names in
the queue are replaced,
// by either their value or a reference, so there is no issue of
overwriting the queue; but the value
// of a noun must be protected since it is attached to the name. It
would be permissible to modify
// a reference but there is no advantage in doing so. Unnamed noun
values in the queue must be protected
// because they are stored in the saved sentence, which might be in an
explicit definition that will
// be run again later. It would be OK to modify values coming from ".
or immex, but that is not
// in a critical-performance path.]
// Individual verbs may also decide not to allow inplacing, as in the
example above.
// And, if a non-inplaceable argument is returned from a verb (such as
]), we need to make sure
// that that result is also marked non-inplaceable.
//
// The upshot is that results of verbs are (usually) inplaceable. When
one of these has usecount 1,
// we indicate its inplaceability by setting an LSB of jt: bit 0 to
indicate inplaceable w, bit 1 for inplaceable a.
// It is our responsibility not to set these flags unless we know that
the receiving verb
// can handle inplace arguments (if it can, it needs to execute F?PREFIP
as its first act, to preserve
// the flags in jt and cleanse jt before it is used). For primitive
verbs, the verb flags
// VINPLACEOK? indicate whether the monad and dyad can handle inplace calls.
//
// We use stack[].ipt.ip to indicate that the stacked noun block is an
argument that supports inplace calls.
//
// There is one more piece to the inplace system: reassigned names.
When there is an
// assignment to a name, the block being reassigned can be reused for
the output if:
// the usecount is 1
// the current execution is the only thing on the stack
// the assignment is to a local name
// We detect these cases only for local assignments, to avoid worries
about aliasing
// with other uses of the names in sentences in execution at higher
stack levels; and only when
// the verb about to be executed is the only thing on the stack (but
there may have been previous
// executions in the sentence before the final execution whose result is
about to be assigned).
// The name jt->assignsym is set to point to the symbol-table entry
whenever a local assignment is detected
// (note that some such assignments, coming from ". or (name)=. might go
undetected), as a time-saving maneuver
// because such an assignment can complete quickly.
// When a symbol is being reassigned and the data area can be reused,
the name jt->zombieval is set
// to point to the data block for the name.
//
// Note that if jt->zombieval is set, any argument that is equal to
jt->zombieval will be
// ipso facto inplaceable, and moreover the flag in jt corresponding to such
// an argument WILL NOT be set because it came from evaluating a name.
So, a verb supporting inplace
// operation must check for (jtinplace&1||jt->zombiesym==w) for example
// if it wants to inplace the w argument.
// Note kludge: the stack bits to indicate inplaceability could be
replaced by performing
// ra();tpush(); when a word is moved to the stack; but until we
// get nonrecursive usecounts we don't do that.
----------------------------------------------------------------------
For information about J forums see http://www.jsoftware.com/forums.htm