# peek

Definition: | integer i = peek(atom addr) -- or -- sequence s = peek({addr, len})
Description: | Return a single byte value in the range 0 to 255 from machine address addr in memory, or return a sequence containing len consecutive byte values starting at address addr.
pwa/p2js: | Not supported.
Comments: | There are in fact eleven further variations of the peek function:
          peek1s  peek2s  peek4s  peek8s
          peek1u  peek2u  peek4u  peek8u
          peekNS  peekns  peeknu
The first two rows, from left to right retrieve bytes, words, dwords, and qwords, with the first row obtaining signed values and the second unsigned. The peekNS() function takes three parameters: (addr or {addr,len}), size, and signed. By way of illustration, peek4u(a)===peekNS(a,4,false). The peekns() function is a simple hll wrapper of peekNS() that defaults size to machine_word() and signed to true; more convenient but less performant (see Implementation notes below). The peeknu() function is identical to peekns() except that it defaults signed to false. The peek routine is actually an alias of peek1u, and when passed a parameter of {addr,len} returns a string, whereas when given the same parameter all the others, including peek1s, return a dword-sequence. The peek8s/u routines are not intended for use on 32-bit, see technicalia. The 32-bit values returned by peek4s/u() may be too large for the 32-bit phix integer type (31-bits), so you should use atom variables, likewise for 64-bit values returned by peek8s() on 64-bit phix. For similar reasons all variables that hold an address should also be declared as atoms. When passed an atom parameter, peek(1|2)(s|u) (and peek4(s|u) on 64-bit) are however guaranteed to return an integer, should that help any. Historically the peek({a,i}) form was faster than reading one byte at a time in a loop, however recent optimisations mean that is no longer necessarily (but will more often than not be) true. Specifically, when the overhead of subscripting to extract the individual elements of a sequence result that might otherwise not exist is taken into account, then individual peeks in a loop is likely to be faster. Of course if you are retreiving a string/sequence, and keeping a copy of it anyway, then whether or not you examine the individual characters/elements the peek({a,i}) form will almost certainly be faster. Remember that peek always takes just one argument, which in the second form is actually a 2-element sequence (except for peekNS, which takes 3 parameters).
Example: | The following are equivalent:
    -- method 1
    s = {peek(addr), peek(addr+1), peek(addr+2), peek(addr+3)}
    -- method 2
    s = peek({addr, 4})
Implementation: | via :%opPeekNx in builtins\VM\pMem.e (an autoinclude) - be warned however it is low-level complicated stuff that you do not need to know. The peekns and peeknu routines are (trivially) implemented in builtins\peekns.e (an autoinclude). In some future release that mapping may be taken over by the compiler front end (in pmain.e), thereby eliminating any performance overhead, or perhaps directly in VM\pMem.e, if I ever decide that having yet 2 more opcodes is worth it (and some real program arises that could really benefit from it). For the moment, at least, library code or anything that may be invoked millions of times should prefer a longhand peekNS() over peekns()/peeknu(), then again millions of peekNS() suggests you might be somewhat better off with a little #ilASM{}.
See Also: | poke, allocate, free, call, machine_bits
Technicalia | While somewhat flakey versions of peek8s and peek8u now exist in 32-bit phix they are not formally supported; any code that is required to run on both 32 and 64 bit should stick to using peek4s/u and below. Atoms in 64-bit phix use 80-bit floats that have a maximum precision of 63 bits+sign, perfect for peek8s. However, atoms in 32-bit phix use 64-bit floats that have a maximum precision of 53 bits, hence loss of data is avoided by making these routines return a two-element pair of unsigned dword-sized blocks when necessary, as the following example (which assumes little endian) shows. When peek8s or peek8u is used in a 32-bit application (not recommended and not officially supported), it checks for precision loss:
    poke(addr,{0,0,0,0,0,0,0,#80,1,0,0,0,0,0,0,#80})
    result = peek8u({addr,2})
On 64-bit result gets set to {#8000000000000000,#8000000000000001} On 32-bit result gets set to {#8000000000000000,{#80000000,#00000001}}. Internally, of course, result[1] is an atom value that happens to fit quite comfortably in 53 bits, whereas attempting to do the same for result[2] would simply throw away the trailing 1, along with 10 other bits. In hex, that translates as two-and-half nibbles, or hex characters, roughly speaking the trailing #03FF, so very loosly speaking this split occurs when any quadword does not end #x000, or start with #000x or #FFFx. (Internally it uses a simple and straightforward "is the reconstructed qword binary identical?" test. In decimal, the largest integer on 32 bit is 9,007,199,254,740,992 as per floats are not exact.) Note that when two dwords are returned, they are always unsigned, even for peek8s, and are in the correct order for `sprintf("#%08x%08x",result[2])` but the wrong order for `poke4(addr+8,result[2])`, whereas `poke8(addr,result[1])` together with `poke4(addr+8,reverse(result[2]))` make for the perfect bitwise copy, and obviously it would all be far easier to use peek4s/u and poke4s/u, as recommended in the first place. Likewise, on 32 bit, should you shift/add the results from peek8s/u together to form a quadword, you will get exactly the same precision loss all this was trying to avoid. Naturally poke8 would go hopelessly wrong should you pass it a pair of dwords obtained from peek8s/u, that is, when under 32-bit, and obviously poke8(addr,result) would fail over the nested subsequence. Also note that the 32-bit version of printf() is not designed to cope well with negative integer/hex values less than -#80000000, (arguably a bug, but hex values of more than 32 bits on a 32 bit system, one has to ask is it worth fixing?). In truth, there are two reasons why peek8s/u exist at all on 32-bit: peek8u is used (lightly and in a non-critical manner) when cross-compiling, ie when a 32-bit phix is asked, via a format directive, to create a 64-bit executable, and secondly so that code such as
    if machine_bits()=32 then
        r = peek4u(k)
    else -- machine_bits()=64
        r = peek8u(k)
    end if
compiles cleanly rather than complains that peek8u does not exist, even though you are not going to call it because you understand and accept that it can be a bit flakey on 32-bit. As above there is now a peeknu() routine that offers a one-line equivalent to the above. Also note that the 32-bit version of dir() uses a peek8 on linux on the assumption you will not have many files above 8192TB lying about. Since 64-bit phix uses 80-bit floats that have the required 64 bits of precision, peek8s/u does not need to do any of this, not that your code would suffer much from a few sequence() checks on the result. Note that the last two parameters to the peekNS routine must be integers: a `size` of 2.001 should (for performance reasons) trigger a run-time error, as it is not one of the integers (1|2|4|8), in contrast `sign` is just tested for non-zero rather than formally validated as being (0|1).