Hi all,
A while ago Joe came to me with an interesting question that seems
simple on the surface. He just wanted to be able to create new
instances of struct classes in cases where the class itself is not
known at compile time, and is a runtime parameter. Since all struct
classes are just tuple classes with a single slot named (underlying),
the obvious approach would be
: memory>struct ( ptr class -- struct )
new swap >>(underlying) ; inline
The 'new' word has a static stack effect ( class -- tuple ) and can be
called with a non-literal class symbol; if the class is literal, the
compiler optimizes it further, but that's not a requirement.
However, this doesn't actually work in Joe's case, because the
struct's (underlying) slot is declared read-only, and read-only slots
don't have setter words, so you can't actually call >>(underlying).
Read only slots can only be set by calling boa. However, boa cannot be
used directly in this case, because it is a macro which must have its
inputs be literal values in order to compile:
: memory>struct ( ptr class -- struct )
boa ; inline
Note that technically its possible to set read-only slots by calling
the set-slot primitive:
: memory>struct ( ptr class -- struct )
new 2 set-slot ; inline
However this inhibits tuple unboxing, which is the whole point of
making the slot read-only in the first place.
We quickly figured out that none of these solutions were satisfactory.
Eventually we came up with a two-tiered design; the 'slow' case where
the class really is dynamic is handled by calling slots>tuple on a
one-element array; slots>tuple does all the relevant checking to make
sure the slots have the right type, and the right number of slots is
given:
: memory>struct ( ptr class -- struct )
[ 1array ] dip slots>tuple ;
Then, to make the 'fast' case, where the class is a literal value
known at compile time, Joe used the define-partial-eval word in the
compiler.tree.propagation.transforms vocabulary; this word is intended
specifically for cases such as this (it is also used to expand member?
out into a case statement if the sequence is literal, and other
things):
\ memory>struct [
dup struct-class? [ '[ _ boa ] ] [ drop f ] if
] 1 define-partial-eval
However this approach still had issues. First of all, it is quite a
bit of essentially meaningless boilerplate to just make a
single-slotted tuple where the class may or may not be known at
compile time. Second, it doesn't work in deployed apps where
reflection support has been stripped out, because slots>tuple is
disabled in that case. Indeed, slots>tuple is only intended for things
such as the literal tuple syntax in the parser, and so on.
Just now though, I came up with a more elegant solution. It is still a
workaround, but it is much simpler. We can use the 'escape hatch'
afforded by call( to call boa and force the call to take one argument:
: memory>struct ( ptr class -- struct )
'[ _ boa ] call( ptr -- struct ) ; inline
We don't need define-partial-eval anymore; instead, memory>struct is
declared inline. If the class is literal, the compiler will fold the
curry construction, as well as call(, and generate efficient code with
no indirection at all.
Or at least, it's supposed to :-) I noticed that the call( inlining
didn't handle the case where there was a curry built from a quotation
that does not have a static stack effect by itself, but becomes static
if the curry's parameter is supplied. Fixing this was pretty easy and
required minor changes to compiler.tree.propagation.call-effect.
This is all pretty obscure stuff, and even the final solution is a bit
of a workaround; its only necessary because Factor's 'boa'
constructors really want you to specify the class as a literal value.
However I'm pretty happy that this code path inlines and optimizes
fully without any explicit macros, compiler transforms, partial evals,
etc. Daniel Ehrenberg implemented both the partial eval stuff as well
as the call( and execute( inlining optimizations in the compiler, and
they're quite useful for low-level, behind the scenes stuff like this.
Slava
------------------------------------------------------------------------------
Let Crystal Reports handle the reporting - Free Crystal Reports 2008 30-Day
trial. Simplify your report design, integration and deployment - and focus on
what you do best, core application coding. Discover what's new with
Crystal Reports now. http://p.sf.net/sfu/bobj-july
_______________________________________________
Factor-talk mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/factor-talk