Article 131395 of comp.os.vms:
Path: nntpd.lkg.dec.com!crl.dec.com!crl.dec.com!caen!kuhub.cc.ukans.edu!zippy.dct.ac.uk!dundee.ac.uk!sunsite.doc.ic.ac.uk!warwick!slxsys!plug.news.pipex.net!pipex!dish.news.pipex.net!pipex!wave.news.pipex.net!pipex!tank.news.pipex.net!pipex!news.mathworks.com!newsfeed.internetmci.com!inet-nntp-gw-1.us.oracle.com!nedc.us.oracle.com!us.oracle.com!raitto
Newsgroups: comp.os.vms
Subject: Re: Generating machine code on the fly and running it
Message-ID: <45gtfp$jff@nedc.us.oracle.com>
From: raitto@us.oracle.com (Jack Raitto)
Date: 11 Oct 1995 17:04:57 GMT
Reply-To: jraitto@us.oracle.com
Sender: raitto@tekoa.enet.dec.com (Jack Raitto)
References: <DG6pxr.MJJ@advent.co.uk> <45eu3b$kgb@gap.cco.caltech.edu>
Distribution: world
Organization: Oracle Corporation
NNTP-Posting-Host: TEKOA
X-Newsreader: dxrn 6.18-23
Lines: 102

This is not that hard to do.  You do need to get some memory mapped with
read, write, and execute enabled.  I did that with an EXE,RD,WRT psect
in MACRO-64:

        $code$ = "RW_CODE,OCTA,PIC,CON,REL,LCL,SHR,EXE,RD,WRT"
        $routine DYNO_SWITCH, kind=stack, saved_regs=r2
        $linkage_section
LF = 10
T1MSG:  .ascii  "Landed on TARGET1"
        .byte   LF, 0
T2MSG:  .ascii  "Landed on TARGET2"
        .byte   LF, 0
STMSG:  .ascii  "Landed on STATIC_TARGET"
        .byte   LF, 0
        $code_section
        mov     r27, r2
        .base   r2, $ls
SWITCH::
        br      STATIC_TARGET
TARGET1::
        lda     r16, T1MSG
        br      SPLAT
TARGET2::
        lda     r16, T2MSG
        br      SPLAT
STATIC_TARGET:
        lda     r16, STMSG
        br      SPLAT
SPLAT:
        $call   DECC$GPRINTF, args=r16
        $return
        $end_routine DYNO_SWITCH
        .end

Now I can use a C program to modify the MACRO-64 routine, specifically the
target of the branch instruction at SWITCH::

#include <stdio.h>
#include <builtins.h> /* need __PAL_IMB() */

extern void DYNO_SWITCH (void);
extern int SWITCH, TARGET1, TARGET2;

static void retarget
    (int *br_addr,
     int *addr)
{
    int br_instruction, new_displacement;

    br_instruction = *br_addr;

    br_instruction &= 0xFFF00000; /* clear old displacement */

    /*
    ** Compute new displacement -- remember that C uses scaled pointer
    ** arithmatic, so we don't need to divide by 4 (ok, so I forgot the
    ** 1st time around!).
    */
    new_displacement = (addr - br_addr) - 1;

    /* reassemble the branch instruction */
    br_instruction |= new_displacement & 0x000FFFFF;

    /* stuff the retargetted branch instruction */
    *br_addr = br_instruction;

    /*
    ** Make the AXP instruction cache see the world in the same was as the
    ** AXP data cache.  If you omit this step, your results may vary
    ** from one AXP machine to another, depending on their respective
    ** cache sizes and cache algorithms.
    */
    __PAL_IMB();
}


int main (void)
{
    printf ("calling DYNO_SWITCH prior to mods... ");
    DYNO_SWITCH();

    retarget (&SWITCH, &TARGET1);
    printf ("calling DYNO_SWITCH after targeting TARGET1... ");
    DYNO_SWITCH();

    retarget (&SWITCH, &TARGET2);
    printf ("calling DYNO_SWITCH after targeting TARGET2... ");
    DYNO_SWITCH();

}

It is a little more work to generate instructions into a pre-allocated 
EXE,RD,WRT psect (in my frail mind) and jumping to it.  But the example above
shows the basic idea.  Don't forget to use an IMB instruction after writing
your instructions and before jumping on them.  This brings the AXP instruction
cache into coherence with the AXP data cache.  The AXP instruction cache is
not otherwise invalidated by data writes.

/Jack

-- 
If it goes, it can go faster.


