Hi Both,<br><br><div class="gmail_quote">On Tue, Nov 16, 2010 at 9:00 PM, Andreas Raab <span dir="ltr">&lt;<a href="mailto:andreas.raab@gmx.de">andreas.raab@gmx.de</a>&gt;</span> wrote:<br><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex;">
<div class="im">On 11/16/2010 8:05 PM, Levente Uzonyi wrote:<br>
<blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
I wasn&#39;t clear when I said atomic code. I expected #= (and #&lt;, #&gt;, etc)<br>
to _not_ be a real message send when both the receiver and the argument<br>
are SmallIntegers. Otherwise what&#39;s the point of having separate<br>
bytecodes for them?<br>
</blockquote>
<br></div>
Space. It makes a big difference for the most common selectors (#at:, #at:put:, #size, #+ etc) to be be encoded as bytecodes. It avoids having to allocate a literal every time you see the selector. Often, the special selector bytecodes look like this:<br>

<br>
Interpreter&gt;&gt;bytecodePrimNextPut<br>
        messageSelector := self specialSelector: 20.<br>
        argumentCount := 1.<br>
        self normalSend.<br>
<br>
I.e., it just dispatches to normalSend where the regular lookup takes place. Of course, that also means it&#39;s a prime place for an optimization that will evaluate eagerly for known receiver types and so (over time) optimizations were added, but many of the optimizations that may make sense in an interpreter have very different tradeoffs in the jit. For a jit to generate the level of optimization makes no sense because the code size simply explodes at no benefit if the inline caches are any good (ours *are* the best Eliot knows how to do and that is a meaningful statement).<br>

<br>
On to a finer point. The terminology &quot;real message send&quot; is misleading. Generally, we (the VM hackers) mean by &quot;real&quot; send a send that requires a method activation, i.e., the creation of a context, but *not* the lookup of the method. That excludes for example all (successful) primitives from being &quot;real sends&quot;, and as a consequence writing &quot;1 + 2&quot; is not a real send by that measure (with or without the bytecode present) since the primitive will be executed successfully and no &quot;real&quot; send (method activation) has taken place.\<br>
</blockquote><div><br></div><div>I want to disagree slightly.  For me the special selector bytecodes are both a space optimization and a performance opimization, important enough to have acquired its own term, static type prediction, essentially optimizing by implementing without lookup the highest dynamic frequency type, which is what&#39;s done for the arithmetic special selector bytecodes, #+, #-, #&lt; #&gt; et al.  What exactly do we mean here?  We mean that if the types are of a particular small set then there is an attempt to perform the operation that would be performed by a send without actually performing the send, and falling back to the full send if either an error occurs in the operation or if the types are invalid. e.g. for #+ we can attempt to perform the operation if receiver and argument are both SmallIntegers but the result may overflow.  So we send if the types are wrong or if the primitive operation can&#39;t be performed, and the machine falls back on a real send.</div>
<div><br></div><div>Interestingly there is a cost to static type prediction which can make it not worth-while.  If you look at VisualWorks&#39; HPS VM you&#39;ll find that there is no static type prediction for #= and #~= because its dynamic frequency for SmallInteger is too small.  There /is/ static type prediction for #+ #- #&lt; #&lt;= et al because these selectors are used so frequently in to:do: loops (unlike #= &amp; ~=).</div>
<div><br></div><div>So I distinguish between the avoidance of the send, the actual send and the operation the send binds to.  The special selector bytecode for #+ can avoid a real send if the receiver and argument are both SmallIntegers (or in the interpreter SmallIntegers and/or Floats) /and/ if the result does not overflow.  But it then does a real send to whatever the receiver is.  However that send might bind to a method that has a primitive (SmallInteger, LargeInteger Float) or not (Fraction, a non numeric object).  The primitive may perform the operation and avoid building an activation.  For example in the Cog JIT it might take too much space to implement static type prediction for #+ and (SmallInteger | Float) x (SmallInteger | Float) and instead just implement it for SmallInteger x SmallInteger, but the primitive for SmallInteger&gt;#+ might quite happily implement SmallInteger x (SmallInteger | Float).  Hence a particular special selector #+ invocation could end up in either avoiding the send, causing a real send that invokes the SmallInteger&gt;#+ primitive that succeeds, or a real send that invokes the SmallInteger&gt;#+ primtiive that fails and builds a frame, or a real send that invokes a method without a primitive that builds a frame.  But all the last three are real sends.</div>
<div><br></div><div><br></div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex;">
<br>
To make matters more complicated, when we talk about &quot;real&quot; sends in the context of thread switches, semaphores and critical sections, what we mean is whether there is a suspension point in the send or not. Obviously, some primitives (#suspend, #yield) must have suspension points so not all activation-free methods are also suspension-point-free. I am not entirely sure what the current set of rules for suspension points in Cog is; in the interpreter it was part of the activation sequence so any primitive that isn&#39;t process related would not have a suspension point but I don&#39;t know if that&#39;s still true in Cog.<br>
</blockquote><div><br></div><div>Right, I agree.  Basically the situation in Cog is that the suspension points are frame-building sends /not/ marked with primitive 221 or 222 (BlockClosure&gt;valueNoContextSwitch &amp; BlockClosure&gt;valueNoContextSwitch:), backward branches (i.e. at the end of a while or to:do: loop), and the Process/Semaphore/Mutex primitives (suspend, yield, wait, signal, enterCriticalSection, exitCriticalSection).  So at backward branches and at frame build the VM checks for external events which may cause process switches, but certain primitives may cause suspensions directly.</div>
<div><br></div><div><br></div><div>I suppose my real point is that sends which invoke primitives are still real sends, but special selector bytecodes that return a result short-circuit sends.</div><div><br></div><div>best</div>
<div>Eliot</div><div><br></div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex;">
<br>
Cheers,<br><font color="#888888">
  - Andreas<br>
<br>
</font></blockquote></div><br>