VMS - a 64 bit OS with both 32 and 64 bit pointers

Content:

  1. Introduction
  2. Virtual memory address space
  3. Applications
  4. VMS status
  5. Examples general
  6. Examples C
  7. Examples other languages

Introduction:

This article discusses how VMS has both 32 and 64 bit pointers.

It consist of:

It should hopefully explain to the reader how it works and what to look out for when programming on VMS.

It assumes the reader knows a little about C, VMS calling convention and VMS system services and RTL functions.

To really benefit from it then the reader will have to read code and look at the output.

I hope everything is correct, but I can easily have missed a few things - this is complicated stuff.

Virtual memory address space:

First some history.

VMS VAX had the virtual memory address space like (32 bit OS on 32 bit HW):

VMS VAX virtual address space

VMS Alpha 1.x and 6.x had the virtual memory address space like (32 bit OS on 64 bit HW):

old VMS Alpha virtual address space

VMS Alpha 7.0+, VMS Itanium and VMS x86-64 has the virtual memory address space like (64 bit OS on 64 bit HW):

current VMS virtual address space

The rest of the article will only cover 64 bit VMS aka VMS Alpha 7.0+, VMS Itanium and VMS x86-64.

Applications:

So VMS has a 64 bit virtual address space.

But VMS has both 32 and 64 bit pointers.

Pointers in C/C++ terminology. In other languages it could be "references to variables". But we will use the term pointer in this article also for other languages than C/C++.

A 64 bit pointer simply contains a 64 bit virtual address.

A 32 bit pointer get converted to a 64 bit virtual address by sign extension.

Like:

0x00012345 -> 0x0000000000012345
0x80012345 -> 0xFFFFFFFF80012345

Note that VMS is not like Windows and Linux on x86-64.

Windows and Linux on x86-64 have 32 and 64 bit modes with 32 and 64 bit instruction sets using 32 and 64 bit virtual addresses - and applications are either 32 or 64 bit.

VMS on Alpha, Itanium and x86-64 has only one mode and always use 64 bit instructions using 64 bit virtual addresses - but on a per pointer basis sometimes only the lower 32 bit of an address is stored in the pointer variable in memory.

To repeat: VMS does not have 32 bit mode and VMS does not have 32 bit applications - a VMS application may have some (maybe none, maybe all) addresses stored in 32 bit pointers.

The closest similar functionality I am aware of is that Sun/Oracle/OpenJDK JVM on x86-64 when heap space is <= 32 GB (unless -XX:-UseCompressedOops is specified on the command line) actually store 64 bit references (Java term for pointers) in 32 bit (because Java referencs are always allocated in 8 byte boundaries, then they can store bit 3-34 instead of 0-31).

What 32 and 64 bit pointer can access:

pointer size reachable address spaces unreachable address spaces
32 bit P0, P1, S0 and S1 P2 and S2
64 bit P0, P1, P2, S0, S1 and S2

Or if we turn it around:

data in space accessible by 32 bit pointer accessible by 64 bit pointer
P0 yes yes
P1 yes yes
P2 no yes
S0 yes yes
S1 yes yes
S2 no yes

Which from a user application and API perspective means:

data in space API expected pointer size result
P0 32 bit works
P0 64 bit works
P1 32 bit works
P1 64 bit works
P2 32 bit problem
P2 64 bit works

and:

application pointer size API expected size result
32 bit 32 bit works
64 bit 64 bit works
32 bit 64 bit works
64 bit 32 bit may work (P0 and P1 space)
may not work (P2 space)

Bottom line: data in P2 space and 32 bit pointers does not work together!

So if one has data in P2 space and need to call an API expecting a 32 bit pointer, then one need to:

  1. allocate space in P0 space
  2. copy data from P2 space to P0 space
  3. make API call
  4. if data is modified copy it back
  5. deallocate

That is a hassle!

VMS status:

One may think that 32 bit pointers would be a rare thing on VMS, but actually it is 64 bit pointers that are rare.

Status is:

Most applications on VMS use almost entirely 32 bit pointers.

Many have asked the question why did VMS end up this way instead of going all 64 bit pointers.

I don't *know* - you will have to ask someone in the VMS group in DEC in the early 1990's to get a first hand explanation.

I can *guess* though:

*) a lot of the VMS libraries are written in Macro-32 or Bliss, so it is not just a recompile telling the C compiler to use 64 bit pointers

**) most early Alpha's came with just 128, 256 or 512 MB of RAM so memory usage did matter

Was it a good technical design? No - the resulting situation is messy - it would have been much cleaner to go all 64 bit pointers (like OSF/1 aka DUNIX aka Tru64).

Was it a good business decision? Likely yes - the ability for customers to upgrade from VMS 5.x (on something labeled VAX XXX) to VMS 6.x (on something labeled Alpha YYY) simply by recompiling kept customers on VMS - if customers would have had to port code from VMS VAX 5.x to VMS Alpha 6.x then many would have chosen to port to Solaris SPARC or AIX Power or Windows NT x86 instead.

No matter the explanation for the decision and ones opinion about the decision then we are where we are!

Examples general:

The most relevant cases on VMS are:

Pointers can be 32 bit:

address (32 bit)

or 64 bit:

address (64 bit)

Descriptors can use 32 bit pointer:

class (8 bit) type (8 bit) length (16 bit)
pointer (32 bit)

or 64 bit pointer:

-1 (32 bit) class (8 bit) type (8 bit) 1 (16 bit)
length (64 bit)
pointer (64 bit)

Itemlists use 32 bit pointers:

buffer length (16 bit) code (16 bit)
buffer address (32 bit)
return length address (32 bit)
(more items)
buffer length (16 bit) code (16 bit)
buffer address (32 bit)
return length address (32 bit)
0 (32 bit)

To inspect addresses the following C function will be used:

#ifndef  INSPECTA_H
#define INSPECTA_H

#define INSPECTA(lbl, p) inspecta(lbl, p, sizeof(p))

void inspecta();

#endif
#include <stdio.h>

#pragma pointer_size save
#pragma pointer_size 32
typedef void *void_ptr32;
#pragma pointer_size 64
typedef void *void_ptr64;
#pragma pointer_size restore

#include "inspecta.h"

static void inspecta32(void_ptr32 a32)
{
   printf(" 32 bit API see         %08p\n", a32);
   printf("  extend it to  %016llp\n", (void_ptr64)a32);
}

static void inspecta64(void_ptr64 a64)
{
   printf(" 64 bit API see %016llp\n", a64);
}

void inspecta(const char *lbl, void *p, int len)
{
   printf("-- %s --\n", lbl);
   if(len == 4)
   {
       printf("real address is         %08p (%d bits)\n", p, len * 8);
   }
   else
   {
       printf("real address is %016llp (%d bits)\n", p, len * 8);
   }
   inspecta32((void_ptr32)p);
   inspecta64((void_ptr64)p);
   printf(" first char is %c\n", *((char *)p));
}

To inspect descriptors the following C function will be used:

#ifndef INSPECTD_H
#define INSPECTD_H

#define INSPECTD(lbl, p) inspectd(lbl, p, sizeof(p))

void inspectd();

#endif
#include <stdio.h>
#include <descrip.h>

#include "inspectd.h"

static char *d32class(struct dsc$descriptor *d32)
{
    switch(d32->dsc$b_class)
    {
        case DSC$K_CLASS_S : return "S";
        case DSC$K_CLASS_D : return "D";
        case DSC$K_CLASS_VS : return "VS";
        default : return "-";
    }
}

static char *d32dtype(struct dsc$descriptor *d32)
{
    switch(d32->dsc$b_dtype)
    {
        case DSC$K_DTYPE_T : return "T";
        case DSC$K_DTYPE_VT : return "VT";
        default : return "-";
    }
}

static char *d64class(struct dsc64$descriptor *d64)
{
    switch(d64->dsc64$b_class)
    {
        case DSC64$K_CLASS_S : return "S";
        case DSC64$K_CLASS_D : return "D";
        case DSC64$K_CLASS_VS : return "VS";
        default : return "-";
    }
}

static char *d64dtype(struct dsc64$descriptor *d64)
{
    switch(d64->dsc64$b_dtype)
    {
        case DSC64$K_DTYPE_T : return "T";
        case DSC64$K_DTYPE_VT : return "VT";
        default : return "-";
    }
}

void inspectd(const char *lbl, void *p, int len)
{
    struct dsc$descriptor *d32;
    struct dsc64$descriptor *d64;
    int offset;
    printf("-- %s --\n", lbl);
    d32 = p;
    d64 = p;
    if(d64->dsc64$w_mbo == 1 & d64->dsc64$l_mbmo == -1) 
    {
        printf("%d bit pointer to 64 bit descriptor\n", len * 8);
        printf(" class=%s dtype=%s\n", d64class(d64), d64dtype(d64));
        printf(" length=%lld\n", d64->dsc64$q_length);
        if(d64->dsc64$b_class == DSC64$K_CLASS_VS && d64->dsc64$b_dtype == DSC64$K_DTYPE_VT)
        {
            printf(" actual=%d\n", *((short int *)d64->dsc64$pq_pointer));
            offset = 2;
        }
        else
        {
            offset = 0;
        }
        printf(" pointer=%016llp\n", d64->dsc64$pq_pointer);
        printf(" first char is %c\n", *(d64->dsc64$pq_pointer + offset));
    }
    else
    {
        printf("%d bit pointer to 32 bit descriptor\n", len * 8);
        printf(" class=%s dtype=%s\n", d32class(d32), d32dtype(d32));
        printf(" length=%d\n", d32->dsc$w_length);
        if(d32->dsc$b_class == DSC$K_CLASS_VS && d32->dsc$b_dtype == DSC$K_DTYPE_VT)
        {
            printf(" actual=%d\n", *((short int *)d32->dsc$a_pointer));
            offset = 2;
        }
        else
        {
            offset = 0;
        }
        printf(" pointer=%08p\n", d32->dsc$a_pointer);
        printf(" first char is %c\n", *(d32->dsc$a_pointer + offset));
    }
}

Examples C:

Pointers:

In VMS C the application developer can either:

#include <stdio.h>
#include <stdlib.h>

#include "inspecta.h"

static char glob[] = "X";

int main(int argc, char *argv[])
{
    char loc[] = "X";
    char *p;
    p = &loc[0];
    INSPECTA("char * (local)", p);
    p = &glob[0];
    INSPECTA("char * (global)", p);
    p = malloc(1);
    p[0] = 'X';
    INSPECTA("char * (dynamic)", p);
    return 0; 
}
$ cc/pointer=64 inspecta
$ write sys$output "** default compile **"
$ cc p1
$ link p1 + inspecta
$ run p1
$ write sys$output "** /pointer=32 compile **"
$ cc/pointer=32 p1
$ link p1 + inspecta
$ run p1
$ write sys$output "** /pointer=64 compile **"
$ cc/pointer=64 p1
$ link p1 + inspecta
$ run p1
$ exit
** default compile **
-- char * (local) --
real address is         7AE27A58 (32 bits)
 32 bit API see         7AE27A58
  extend it to  000000007AE27A58
 64 bit API see 000000007AE27A58
 first char is X
-- char * (global) --
real address is         00020000 (32 bits)
 32 bit API see         00020000
  extend it to  0000000000020000
 64 bit API see 0000000000020000
 first char is X
-- char * (dynamic) --
real address is         0005B828 (32 bits)
 32 bit API see         0005B828
  extend it to  000000000005B828
 64 bit API see 000000000005B828
 first char is X
** /pointer=32 compile **
-- char * (local) --
real address is         7AE27A58 (32 bits)
 32 bit API see         7AE27A58
  extend it to  000000007AE27A58
 64 bit API see 000000007AE27A58
 first char is X
-- char * (global) --
real address is         00020000 (32 bits)
 32 bit API see         00020000
  extend it to  0000000000020000
 64 bit API see 0000000000020000
 first char is X
-- char * (dynamic) --
real address is         0005B828 (32 bits)
 32 bit API see         0005B828
  extend it to  000000000005B828
 64 bit API see 000000000005B828
 first char is X
** /pointer=64 compile **
-- char * (local) --
real address is 000000007AE27A58 (64 bits)
 32 bit API see         7AE27A58
  extend it to  000000007AE27A58
 64 bit API see 000000007AE27A58
 first char is X
-- char * (global) --
real address is 0000000000020000 (64 bits)
 32 bit API see         00020000
  extend it to  0000000000020000
 64 bit API see 0000000000020000
 first char is X
-- char * (dynamic) --
real address is 0000000080000010 (64 bits)
 32 bit API see         80000010
  extend it to  FFFFFFFF80000010
 64 bit API see 0000000080000010
 first char is X
#include <stdio.h>
#include <stdlib.h>

#include "inspecta.h"

typedef char *char_ptrdef;
#pragma pointer_size save
#pragma pointer_size 32
typedef char *char_ptr32;
#pragma pointer_size 64
typedef char *char_ptr64;
#pragma pointer_size restore

static char glob[] = "X";

int main(int argc, char *argv[])
{
    char loc[] = "X";
    char_ptrdef pdef;
    char_ptr32 p32;
    char_ptr64 p64;
    pdef = &loc[0];
    INSPECTA("char * (default, local)", pdef);
    pdef = &glob[0];
    INSPECTA("char * (default, global)", pdef);
    pdef = malloc(1);
    pdef[0] = 'X';
    INSPECTA("char * (default, dynamic)", pdef);
    p32 = &loc[0];
    INSPECTA("char * (32 bit, local)", p32);
    p32 = &glob[0];
    INSPECTA("char * (32 bit, global)", p32);
    p32 = _malloc32(1);
    p32[0] = 'X';
    INSPECTA("char * (32 bit, dynamic)", p32);
    p64 = &loc[0];
    INSPECTA("char * (64 bit, local)", p64);
    p64 = &glob[0];
    INSPECTA("char * (64 bit, global)", p64);
    p64 = _malloc64(1);
    p64[0] = 'X';
    INSPECTA("char * (64 bit, dynamic)", p64);
    for(int i = 0; i < 16; i++) _malloc64(0x10000000); // force next allocation up in memory
    p64 = _malloc64(1);
    p64[0] = 'X';
    INSPECTA("char * (64 bit, high, dynamic)", p64);
    return 0; 
}
$ cc/pointer=64 inspecta
$ write sys$output "** /pointer=32 compile **"
$ cc/pointer=32 p2
$ link p2 + inspecta
$ run p2
$ write sys$output "** /pointer=64 compile **"
$ cc/pointer=64 p2
$ link p2 + inspecta
$ run p2
$ exit
** /pointer=32 compile **
-- char * (default, local) --
real address is         7AE27A48 (32 bits)
 32 bit API see         7AE27A48
  extend it to  000000007AE27A48
 64 bit API see 000000007AE27A48
 first char is X
-- char * (default, global) --
real address is         00020000 (32 bits)
 32 bit API see         00020000
  extend it to  0000000000020000
 64 bit API see 0000000000020000
 first char is X
-- char * (default, dynamic) --
real address is         0005B828 (32 bits)
 32 bit API see         0005B828
  extend it to  000000000005B828
 64 bit API see 000000000005B828
 first char is X
-- char * (32 bit, local) --
real address is         7AE27A48 (32 bits)
 32 bit API see         7AE27A48
  extend it to  000000007AE27A48
 64 bit API see 000000007AE27A48
 first char is X
-- char * (32 bit, global) --
real address is         00020000 (32 bits)
 32 bit API see         00020000
  extend it to  0000000000020000
 64 bit API see 0000000000020000
 first char is X
-- char * (32 bit, dynamic) --
real address is         0005F868 (32 bits)
 32 bit API see         0005F868
  extend it to  000000000005F868
 64 bit API see 000000000005F868
 first char is X
-- char * (64 bit, local) --
real address is 000000007AE27A48 (64 bits)
 32 bit API see         7AE27A48
  extend it to  000000007AE27A48
 64 bit API see 000000007AE27A48
 first char is X
-- char * (64 bit, global) --
real address is 0000000000020000 (64 bits)
 32 bit API see         00020000
  extend it to  0000000000020000
 64 bit API see 0000000000020000
 first char is X
-- char * (64 bit, dynamic) --
real address is 0000000080000010 (64 bits)
 32 bit API see         80000010
  extend it to  FFFFFFFF80000010
 64 bit API see 0000000080000010
 first char is X
-- char * (64 bit, high, dynamic) --
real address is 00000001A0000030 (64 bits)
 32 bit API see         A0000030
  extend it to  FFFFFFFFA0000030
 64 bit API see 00000001A0000030
 first char is X
** /pointer=64 compile **
-- char * (default, local) --
real address is 000000007AE27A48 (64 bits)
 32 bit API see         7AE27A48
  extend it to  000000007AE27A48
 64 bit API see 000000007AE27A48
 first char is X
-- char * (default, global) --
real address is 0000000000020000 (64 bits)
 32 bit API see         00020000
  extend it to  0000000000020000
 64 bit API see 0000000000020000
 first char is X
-- char * (default, dynamic) --
real address is 0000000080000010 (64 bits)
 32 bit API see         80000010
  extend it to  FFFFFFFF80000010
 64 bit API see 0000000080000010
 first char is X
-- char * (32 bit, local) --
real address is         7AE27A48 (32 bits)
 32 bit API see         7AE27A48
  extend it to  000000007AE27A48
 64 bit API see 000000007AE27A48
 first char is X
-- char * (32 bit, global) --
real address is         00020000 (32 bits)
 32 bit API see         00020000
  extend it to  0000000000020000
 64 bit API see 0000000000020000
 first char is X
-- char * (32 bit, dynamic) --
real address is         0005B828 (32 bits)
 32 bit API see         0005B828
  extend it to  000000000005B828
 64 bit API see 000000000005B828
 first char is X
-- char * (64 bit, local) --
real address is 000000007AE27A48 (64 bits)
 32 bit API see         7AE27A48
  extend it to  000000007AE27A48
 64 bit API see 000000007AE27A48
 first char is X
-- char * (64 bit, global) --
real address is 0000000000020000 (64 bits)
 32 bit API see         00020000
  extend it to  0000000000020000
 64 bit API see 0000000000020000
 first char is X
-- char * (64 bit, dynamic) --
real address is 0000000080000030 (64 bits)
 32 bit API see         80000030
  extend it to  FFFFFFFF80000030
 64 bit API see 0000000080000030
 first char is X
-- char * (64 bit, high, dynamic) --
real address is 00000001A0000050 (64 bits)
 32 bit API see         A0000050
  extend it to  FFFFFFFFA0000050
 64 bit API see 00000001A0000050
 first char is X

Everything is as expected - everything works except 64 bit pointers and an API expecting 32 bit pointers.

Descriptors:

VMS C does not have builtin support for descriptors. Instead descriptors are a visible struct.

That is usually a hassle, but it does give explicit control over whether descriptors are using 32 bit pointers or 64 bit pointers.

#include <stdio.h>
#include <string.h>

#include <descrip.h>

#include "inspectd.h"

int main(int argc, char *argv[])
{
    $DESCRIPTOR(d1, "X");
    struct dsc$descriptor_s d2 = { strlen("X"), DSC$K_DTYPE_T, DSC$K_CLASS_S, "X" };
    $DESCRIPTOR64(d3, "X");
    struct dsc64$descriptor_s d4 = { 1, DSC64$K_DTYPE_T, DSC64$K_CLASS_S, -1, strlen("X"), "X" };
    INSPECTD("$DESCRIPTOR", &d1);
    INSPECTD("struct dsc$descriptor_s", &d2);
    INSPECTD("$DESCRIPTOR64", &d3);
    INSPECTD("struct dsc64$descriptor_s", &d4);
    return 0;
}
$ cc/pointer=64 inspectd
$ write sys$output "** default compile **"
$ cc d1
$ link d1 + inspectd
$ run d1
$ write sys$output "** /pointer=32 compile **"
$ cc/pointer=32 d1
$ link d1 + inspectd
$ run d1
$ write sys$output "** /pointer=64 compile **"
$ cc/pointer=64 d1
$ link d1 + inspectd
$ run d1
$ exit
** default compile **
-- $DESCRIPTOR --
32 bit pointer to 32 bit descriptor
 class=S dtype=T
 length=1
 pointer=00010030
 first char is X
-- struct dsc$descriptor_s --
32 bit pointer to 32 bit descriptor
 class=S dtype=T
 length=1
 pointer=00010030
 first char is X
-- $DESCRIPTOR64 --
32 bit pointer to 64 bit descriptor
 class=S dtype=T
 length=1
 pointer=0000000000010030
 first char is X
-- struct dsc64$descriptor_s --
32 bit pointer to 64 bit descriptor
 class=S dtype=T
 length=1
 pointer=0000000000010030
 first char is X
** /pointer=32 compile **
-- $DESCRIPTOR --
32 bit pointer to 32 bit descriptor
 class=S dtype=T
 length=1
 pointer=00010030
 first char is X
-- struct dsc$descriptor_s --
32 bit pointer to 32 bit descriptor
 class=S dtype=T
 length=1
 pointer=00010030
 first char is X
-- $DESCRIPTOR64 --
32 bit pointer to 64 bit descriptor
 class=S dtype=T
 length=1
 pointer=0000000000010030
 first char is X
-- struct dsc64$descriptor_s --
32 bit pointer to 64 bit descriptor
 class=S dtype=T
 length=1
 pointer=0000000000010030
 first char is X
** /pointer=64 compile **
-- $DESCRIPTOR --
32 bit pointer to 32 bit descriptor
 class=S dtype=T
 length=1
 pointer=00010030
 first char is X
-- struct dsc$descriptor_s --
32 bit pointer to 32 bit descriptor
 class=S dtype=T
 length=1
 pointer=00010030
 first char is X
-- $DESCRIPTOR64 --
32 bit pointer to 64 bit descriptor
 class=S dtype=T
 length=1
 pointer=0000000000010030
 first char is X
-- struct dsc64$descriptor_s --
32 bit pointer to 64 bit descriptor
 class=S dtype=T
 length=1
 pointer=0000000000010030
 first char is X
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include <descrip.h>

#include "inspectd.h"

typedef struct dsc$descriptor_s *dsc_s_ptrdef;
typedef struct dsc64$descriptor_s *dsc64_s_ptrdef;
#pragma pointer_size save
#pragma pointer_size 32
typedef struct dsc$descriptor_s *dsc_s_ptr32;
#pragma pointer_size 64
typedef struct dsc64$descriptor_s *dsc64_s_ptr64;
#pragma pointer_size restore

int main(int argc, char *argv[])
{
    struct dsc$descriptor_s loc32 = { strlen("X"), DSC$K_DTYPE_T, DSC$K_CLASS_S, "X" };
    dsc_s_ptrdef d1 = &loc32;
    dsc_s_ptr32 d2;
    struct dsc64$descriptor_s loc64 = { 1, DSC64$K_DTYPE_T, DSC64$K_CLASS_S, -1, strlen("X"), "X" };
    dsc64_s_ptrdef d3 = &loc64;
    dsc64_s_ptr64 d4;
    INSPECTD("struct dsc$descriptor_s (default, static)", d1);
    d2 = _malloc32(sizeof(*d2));
    d2->dsc$w_length = 1;
    d2->dsc$b_dtype = DSC$K_DTYPE_T;
    d2->dsc$b_class = DSC$K_CLASS_S;
    d2->dsc$a_pointer = _malloc32(1);
    d2->dsc$a_pointer[0] = 'X';
    INSPECTD("struct dsc$descriptor_s * (32 bit, dynamic)", d2);
    INSPECTD("struct dsc64$descriptor_s (default, static)", d3);
    d4 = _malloc64(sizeof(*d4));
    d4->dsc64$w_mbo = 1;
    d4->dsc64$b_dtype = DSC64$K_DTYPE_T;
    d4->dsc64$b_class = DSC64$K_CLASS_S;
    d4->dsc64$l_mbmo = -1;
    d4->dsc64$q_length = 1;
    d4->dsc64$pq_pointer = _malloc64(1);
    d4->dsc64$pq_pointer[0] = 'X';
    INSPECTD("struct dsc64$descriptor_s * (64 bit, dynamic)", d4);
    return 0;
}
$ cc/pointer=64 inspectd
$ write sys$output "** /pointer=32 compile **"
$ cc/pointer=32 d2
$ link d2 + inspectd
$ run d2
$ write sys$output "** /pointer=64 compile **"
$ cc/pointer=64 d2
$ link d2 + inspectd
$ run d2
$ exit
** /pointer=32 compile **
-- struct dsc$descriptor_s (default, static) --
32 bit pointer to 32 bit descriptor
 class=S dtype=T
 length=1
 pointer=00010040
 first char is X
-- struct dsc$descriptor_s * (32 bit, dynamic) --
32 bit pointer to 32 bit descriptor
 class=S dtype=T
 length=1
 pointer=0004F868
 first char is X
-- struct dsc64$descriptor_s (default, static) --
32 bit pointer to 64 bit descriptor
 class=S dtype=T
 length=1
 pointer=0000000000010040
 first char is X
-- struct dsc64$descriptor_s * (64 bit, dynamic) --
64 bit pointer to 64 bit descriptor
 class=S dtype=T
 length=1
 pointer=0000000080000040
 first char is X
** /pointer=64 compile **
-- struct dsc$descriptor_s (default, static) --
64 bit pointer to 32 bit descriptor
 class=S dtype=T
 length=1
 pointer=00010040
 first char is X
-- struct dsc$descriptor_s * (32 bit, dynamic) --
32 bit pointer to 32 bit descriptor
 class=S dtype=T
 length=1
 pointer=0004F868
 first char is X
-- struct dsc64$descriptor_s (default, static) --
64 bit pointer to 64 bit descriptor
 class=S dtype=T
 length=1
 pointer=0000000000010040
 first char is X
-- struct dsc64$descriptor_s * (64 bit, dynamic) --
64 bit pointer to 64 bit descriptor
 class=S dtype=T
 length=1
 pointer=0000000080000040
 first char is X

Everything as expected. I suspect that in the real world practically all descriptors are using 32 bit pointers.

Examples other languages:

Other languages will not be handled at the same depth as C.

Fortran:

In VMS Fortran the application developer can put variables in P2 space aka use 64 bit references using the attribute mechanism. This applies to both statically and dynamically allocated variables.

Pointers:

      program fp
      implicit none
      integer*1::sta(1)
      integer*1::sta64(1)
      integer*1,automatic::auto(1)
      integer*1,allocatable::dyn(:)
      integer*1,allocatable::dyn64(:)
      integer*1,allocatable::dummy(:,:)
      integer*1,allocatable::dyn64x(:)
cDEC$ ATTRIBUTES address64::sta64
cDEC$ ATTRIBUTES address64::dyn64
cDEC$ ATTRIBUTES address64::dummy
cDEC$ ATTRIBUTES address64::dyn64x
      sta = 'X'
      call inspecta(%ref('static'//char(0)), %ref(sta), %val(4))
      sta64 = 'X'
      call inspecta(%ref('static (64 bit)'//char(0)), %ref(sta64), %val(8))
      auto = 'X'
      call inspecta(%ref('automatic'//char(0)), %ref(auto), %val(4))
      allocate(dyn(1))
      dyn = 'X'
      call inspecta(%ref('dynamic'//char(0)), %ref(dyn), %val(4))
      allocate(dyn64(1))
      dyn64 = 'X'
      call inspecta(%ref('dynamic (64 bit)'//char(0)), %ref(dyn64), %val(8))
      allocate(dummy(65536,65536))
      allocate(dyn64x(1))
      dyn64x = 'X'
      call inspecta(%ref('dynamic (64 bit,high)'//char(0)), %ref(dyn64x), %val(8))
      end
$ cc/pointer=64 inspecta
$ for/ext fp
$ lin fp + inspecta
$ run fp
$ exit
-- static --
real address is         00040000 (32 bits)
 32 bit API see         00040000
  extend it to  0000000000040000
 64 bit API see 0000000000040000
 first char is X
-- static (64 bit) --
real address is 0000000080000000 (64 bits)
 32 bit API see         80000000
  extend it to  FFFFFFFF80000000
 64 bit API see 0000000080000000
 first char is X
-- automatic --
real address is         7AE27A98 (32 bits)
 32 bit API see         7AE27A98
  extend it to  000000007AE27A98
 64 bit API see 000000007AE27A98
 first char is X
-- dynamic --
real address is         0006ACE8 (32 bits)
 32 bit API see         0006ACE8
  extend it to  000000000006ACE8
 64 bit API see 000000000006ACE8
 first char is X
-- dynamic (64 bit) --
real address is 0000000080014010 (64 bits)
 32 bit API see         80014010
  extend it to  FFFFFFFF80014010
 64 bit API see 0000000080014010
 first char is X
-- dynamic (64 bit,high) --
real address is 0000000180016030 (64 bits)
 32 bit API see         80016030
  extend it to  FFFFFFFF80016030
 64 bit API see 0000000180016030
 first char is X

Fortran provides excellent control over what gets in P2 space. Obviously the problem with data in P2 space and API's expecting 32 bit pointers also applies to Fortran.

I have not covered Fortran POINTER type as I did not find it a good fit for the context.

For more detail on VMS Fortran and 64 bit pointers I can recommend JoukJ's 64-bit Fortran on OpenVMS systems.

Descriptors:

      program fd
      implicit none
      character*5 s
      character*5 s64
cDEC$ ATTRIBUTES address64::s64
      s = 'ABC'
      call inspectd(%ref('character*n'//char(0)), %descr(s), %val(4))
      s64 = 'ABC'
      call inspectd(%ref('character*n (64 bit)'//char(0)), %descr(s64), %val(8))
      s = 'ABCDE'
      call inspectd(%ref('character*n'//char(0)), %descr(s), %val(4))
      s64 = 'ABCDE'
      call inspectd(%ref('character*n (64 bit)'//char(0)), %descr(s64), %val(8))
      end
$ cc/pointer=64 inspectd
$ for/ext fd
$ lin fd + inspectd
$ run fd
$ exit
-- character*n --
32 bit pointer to 32 bit descriptor
 class=S dtype=T
 length=5
 pointer=00030000
 first char is A
-- character*n (64 bit) --
64 bit pointer to 64 bit descriptor
 class=S dtype=T
 length=5
 pointer=0000000080000000
 first char is A
-- character*n --
32 bit pointer to 32 bit descriptor
 class=S dtype=T
 length=5
 pointer=00030000
 first char is A
-- character*n (64 bit) --
64 bit pointer to 64 bit descriptor
 class=S dtype=T
 length=5
 pointer=0000000080000000
 first char is A

Even descriptors works fine with P2 space.

Pascal:

In VMS Pascal the only way to use P2 space is to use pointers with the [quad] attribute.

Pointers:

program pp(input,output);

[external('INSPECTA')]
procedure inspecta_ptr(%REF lbl : packed array [l1..u1:integer] of char;
                       %IMMED p : [quad] pointer;
                       %IMMED len : integer); external;

[external('INSPECTA')]
procedure inspecta_addr(%REF lbl : packed array [l1..u1:integer] of char;
                        %IMMED p : integer64;
                        %IMMED len : integer); external;

type
   charptr = ^char;
   qcharptr = [quad] ^char;

var
   loc : char;
   sta : [static] char;
   p : charptr;
   qp : qcharptr;

begin
   loc := 'X';
   inspecta_addr('local' + chr(0), iaddress(loc), 4);
   inspecta_addr('local' + chr(0), iaddress64(loc), 8);
   sta := 'X';
   inspecta_addr('static' + chr(0), iaddress(sta), 4);
   inspecta_addr('static' + chr(0), iaddress64(sta), 8);
   new(p);
   p^ := 'X';
   inspecta_ptr('dynamic' + chr(0), p, size(p));
   new(qp);
   qp^ := 'X';
   inspecta_ptr('dynamic (64 bit)' + chr(0), qp, size(qp));
end.
$ cc/pointer=64 inspecta
$ pas pp
$ lin pp + inspecta
$ run pp
$ exit
-- local --
real address is         7AE27A88 (32 bits)
 32 bit API see         7AE27A88
  extend it to  000000007AE27A88
 64 bit API see 000000007AE27A88
 first char is X
-- local --
real address is 000000007AE27A88 (64 bits)
 32 bit API see         7AE27A88
  extend it to  000000007AE27A88
 64 bit API see 000000007AE27A88
 first char is X
-- static --
real address is         00030000 (32 bits)
 32 bit API see         00030000
  extend it to  0000000000030000
 64 bit API see 0000000000030000
 first char is X
-- static --
real address is 0000000000030000 (64 bits)
 32 bit API see         00030000
  extend it to  0000000000030000
 64 bit API see 0000000000030000
 first char is X
-- dynamic --
real address is         00064008 (32 bits)
 32 bit API see         00064008
  extend it to  0000000000064008
 64 bit API see 0000000000064008
 first char is X
-- dynamic (64 bit) --
real address is 0000000080002010 (64 bits)
 32 bit API see         80002010
  extend it to  FFFFFFFF80002010
 64 bit API see 0000000080002010
 first char is X

Descriptors:

program pd(input,output);

type
   string = varying [255] of char;

[external('INSPECTD')]
procedure inspectd_fix(%REF lbl : packed array [l1..u1:integer] of char;
                       %STDESCR d : packed array [l2..u2:integer] of char;
                       %IMMED len : integer); external;


[external('INSPECTD')]
procedure inspectd_var(%REF lbl : packed array [l1..u1:integer] of char;
                       %DESCR d : string;
                       %IMMED len : integer); external;


var
   d1 : packed array [1..5] of char;
   d2 : string;

begin
   d1 := 'ABC';
   inspectd_fix('packed array' + chr(0), d1, 4);
   d2 := 'ABC';
   inspectd_var('varying' + chr(0), d2, 4);
   d1 := 'ABCDE';
   inspectd_fix('packed array' + chr(0), d1, 4);
   d2 := 'ABCDE';
   inspectd_var('varying' + chr(0), d2, 4);
end.
$ cc/pointer=64 inspectd
$ pas pd
$ lin pd + inspectd
$ run pd
$ exit
-- packed array --
32 bit pointer to 32 bit descriptor
 class=S dtype=T
 length=5
 pointer=7AE27988
 first char is A
-- varying --
32 bit pointer to 32 bit descriptor
 class=VS dtype=VT
 length=255
 actual=3
 pointer=7AE279D0
 first char is A
-- packed array --
32 bit pointer to 32 bit descriptor
 class=S dtype=T
 length=5
 pointer=7AE27988
 first char is A
-- varying --
32 bit pointer to 32 bit descriptor
 class=VS dtype=VT
 length=255
 actual=5
 pointer=7AE279D0
 first char is A

Just like the other languages when 64 bit is possible.

Pascal on VMS x86-64 has a new qualifier option /USAGE=64BIT_TO_DESCR that allow programs to pass an address of lowest 2 GB of P2 space 0x0000000080000000-0x00000000FFFFFFFF via a 32 bit descriptor using %STDESCR (it is up to the called code to get the 32 bit pointer over in a 64 bit pointer zero extended instead of sign extended - Pascal can do it by first casting to unsigned and then casting to 64 bit pointer).

Basic:

VMS Basic does not support 64 bit.

Pointers:

program bd

option type = explicit

declare string norm
map (m) string glob = 1
external sub inspecta(string by ref, string by ref, integer by value)

norm = "X"
call inspecta("normal" + chr$(0), norm, 4)
glob = "X"
call inspecta("map" + chr$(0), glob, 4)

end program
$ cc/pointer=64 inspecta
$ bas bp
$ lin bp + inspecta
$ run bp
$ exit
-- normal --
real address is         00044804 (32 bits)
 32 bit API see         00044804
  extend it to  0000000000044804
 64 bit API see 0000000000044804
 first char is X
-- map --
real address is         00030000 (32 bits)
 32 bit API see         00030000
  extend it to  0000000000030000
 64 bit API see 0000000000030000
 first char is X

Descriptors:

program bd

option type = explicit

declare string d1
map (m) string d2 = 5
external sub inspectd(string by ref, string by desc, integer by value)

d1 = "ABC"
call inspectd("dynamic string" + chr$(0), d1, 4)
d2 = "ABC"
call inspectd("static string" + chr$(0), d2, 4)
d1 = "ABCDE"
call inspectd("dynamic string" + chr$(0), d1, 4)
d2 = "ABCDE"
call inspectd("static string" + chr$(0), d2, 4)
d1 = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
call inspectd("dynamic string (moved)" + chr$(0), d1, 4)

end program
$ cc/pointer=64 inspectd
$ bas bd
$ lin bd + inspectd
$ run bd
$ exit
-- dynamic string --
32 bit pointer to 32 bit descriptor
 class=D dtype=T
 length=3
 pointer=00044804
 first char is A
-- static string --
32 bit pointer to 32 bit descriptor
 class=S dtype=T
 length=5
 pointer=00030000
 first char is A
-- dynamic string --
32 bit pointer to 32 bit descriptor
 class=D dtype=T
 length=5
 pointer=00044804
 first char is A
-- static string --
32 bit pointer to 32 bit descriptor
 class=S dtype=T
 length=5
 pointer=00030000
 first char is A
-- dynamic string (moved) --
32 bit pointer to 32 bit descriptor
 class=D dtype=T
 length=32
 pointer=000448B4
 first char is X

Cobol:

VMS Cobol does not support 64 bit except that it has a POINTER-64 data type that can be used to pass through 64 bit pointers.

Pointers:

identification division.
program-id.cp.
*
data division.
working-storage section.
01 lbl pic x(255).
01 v   pic x(1) value "X".
01 p   pointer.
01 p64 pointer-64.
*
procedure division.
main-paragraph.
    string "variable" delimited by size
           function char(1) delimited by size into lbl
    call "inspecta"
        using
            by reference lbl
            by reference v
            by value 4
    end-call
    set p to reference v
    string "pointer" delimited by size
           function char(1) delimited by size into lbl
    call "inspecta"
        using
            by reference lbl
            by value p
            by value 4
    set p64 to reference v
    string "pointer (64 bit)" delimited by size
           function char(1) delimited by size into lbl
    call "inspecta"
        using
            by reference lbl
            by value p64
            by value 8
    stop run.
$ cc/pointer=64 inspecta
$ cob cp
$ lin cp + inspecta
$ run cp
$ exit
-- variable --
real address is         00020110 (32 bits)
 32 bit API see         00020110
  extend it to  0000000000020110
 64 bit API see 0000000000020110
 first char is X
-- pointer --
real address is         00020110 (32 bits)
 32 bit API see         00020110
  extend it to  0000000000020110
 64 bit API see 0000000000020110
 first char is X
-- pointer (64 bit) --
real address is 0000000000020110 (64 bits)
 32 bit API see         00020110
  extend it to  0000000000020110
 64 bit API see 0000000000020110
 first char is X

Descriptors:

identification division.
program-id.xcd.
*
data division.
working-storage section.
01 lbl pic x(255).
01 s   pic x(255).
*
procedure division.
main-paragraph.
    string "string" delimited by size
           function char(1) delimited by size into lbl
    move "ABC" to s
    call "inspectd"
        using
            by reference lbl
            by descriptor s
            by value 4
    end-call
    move "ABCDE" to s
    call "inspectd"
        using
            by reference lbl
            by descriptor s
            by value 4
    end-call
    stop run.
$ cc/pointer=64 inspectd
$ cob cd
$ lin cd + inspectd
$ run cd
$ exit
-- string --
32 bit pointer to 32 bit descriptor
 class=S dtype=T
 length=255
 pointer=00020110
 first char is A
-- string --
32 bit pointer to 32 bit descriptor
 class=S dtype=T
 length=255
 pointer=00020110
 first char is A

C++:

C++ is very much like C.

32 bit pointers are default on Alpha and Itanium. But the new clang based C++ compiler on x86-64 has 64 bit pointers as default.

Pointers:

#include <iostream>

using namespace std;

#define INSPECTA(lbl, p) inspecta(lbl, p, sizeof(p))

extern "C"
{
void inspecta(char *lbl, char *p, int len);
}

static char glob[] = "X";

int main(int argc, char *argv[])
{
    char loc[] = "X";
    char *p;
    p = &loc[0];
    INSPECTA("char * (local)", p);
    p = &glob[0];
    INSPECTA("char * (global)", p);
    p = new char[1];
    p[0] = 'X';
    INSPECTA("char * (dynamic)", p);
    return 0; 
}
$ cc/pointer=64 inspecta
$ write sys$output "** default compile **"
$ cxx op
$ cxxl op + inspecta
$ run op
$ write sys$output "** /pointer=32 compile **"
$ cxx/pointer=32 op
$ cxxl op + inspecta
$ run op
$ write sys$output "** /pointer=64 compile **"
$ cxx/pointer=64 op
$ cxxl op + inspecta
$ run op
$ exit
** default compile **
-- char * (local) --
real address is         7AE279F8 (32 bits)
 32 bit API see         7AE279F8
  extend it to  000000007AE279F8
 64 bit API see 000000007AE279F8
 first char is X
-- char * (global) --
real address is         00020000 (32 bits)
 32 bit API see         00020000
  extend it to  0000000000020000
 64 bit API see 0000000000020000
 first char is X
-- char * (dynamic) --
real address is         0007F368 (32 bits)
 32 bit API see         0007F368
  extend it to  000000000007F368
 64 bit API see 000000000007F368
 first char is X
** /pointer=32 compile **
-- char * (local) --
real address is         7AE279F8 (32 bits)
 32 bit API see         7AE279F8
  extend it to  000000007AE279F8
 64 bit API see 000000007AE279F8
 first char is X
-- char * (global) --
real address is         00020000 (32 bits)
 32 bit API see         00020000
  extend it to  0000000000020000
 64 bit API see 0000000000020000
 first char is X
-- char * (dynamic) --
real address is         0007F368 (32 bits)
 32 bit API see         0007F368
  extend it to  000000000007F368
 64 bit API see 000000000007F368
 first char is X
** /pointer=64 compile **
-- char * (local) --
real address is 000000007AE279F8 (64 bits)
 32 bit API see         7AE279F8
  extend it to  000000007AE279F8
 64 bit API see 000000007AE279F8
 first char is X
-- char * (global) --
real address is 0000000000020000 (64 bits)
 32 bit API see         00020000
  extend it to  0000000000020000
 64 bit API see 0000000000020000
 first char is X
-- char * (dynamic) --
real address is 0000000080000010 (64 bits)
 32 bit API see         80000010
  extend it to  FFFFFFFF80000010
 64 bit API see 0000000080000010
 first char is X

Descriptors:

#include <iostream>

using namespace std;

#include <descrip.h>

#define INSPECTD(lbl, p) inspectd(lbl, p, sizeof(p))

extern "C"
{
void inspectd(char *lbl, void *d, int len);
}

int main(int argc, char *argv[])
{
    $DESCRIPTOR(d1, "X");
    struct dsc$descriptor_s d2 = { strlen("X"), DSC$K_DTYPE_T, DSC$K_CLASS_S, "X" };
    $DESCRIPTOR64(d3, "X");
    struct dsc64$descriptor_s d4 = { 1, DSC64$K_DTYPE_T, DSC64$K_CLASS_S, -1, strlen("X"), "X" };
    INSPECTD("$DESCRIPTOR", &d1);
    INSPECTD("struct dsc$descriptor_s", &d2);
    INSPECTD("$DESCRIPTOR64", &d3);
    INSPECTD("struct dsc64$descriptor_s", &d4);
    return 0;
}
$ cc/pointer=64 inspectd
$ write sys$output "** default compile **"
$ cxx od
$ cxxl od + inspectd
$ run od
$ write sys$output "** /pointer=32 compile **"
$ cxx/pointer=32 od
$ cxxl od + inspectd
$ run od
$ write sys$output "** /pointer=64 compile **"
$ cxx/pointer=64 od
$ cxxl od + inspectd
$ run od
$ exit
** default compile **
-- $DESCRIPTOR --
32 bit pointer to 32 bit descriptor
 class=S dtype=T
 length=1
 pointer=00010AF0
 first char is X
-- struct dsc$descriptor_s --
32 bit pointer to 32 bit descriptor
 class=S dtype=T
 length=1
 pointer=00010AF0
 first char is X
-- $DESCRIPTOR64 --
32 bit pointer to 64 bit descriptor
 class=S dtype=T
 length=1
 pointer=0000000000010AF0
 first char is X
-- struct dsc64$descriptor_s --
32 bit pointer to 64 bit descriptor
 class=S dtype=T
 length=1
 pointer=0000000000010AF0
 first char is X
** /pointer=32 compile **
-- $DESCRIPTOR --
32 bit pointer to 32 bit descriptor
 class=S dtype=T
 length=1
 pointer=00010AF0
 first char is X
-- struct dsc$descriptor_s --
32 bit pointer to 32 bit descriptor
 class=S dtype=T
 length=1
 pointer=00010AF0
 first char is X
-- $DESCRIPTOR64 --
32 bit pointer to 64 bit descriptor
 class=S dtype=T
 length=1
 pointer=0000000000010AF0
 first char is X
-- struct dsc64$descriptor_s --
32 bit pointer to 64 bit descriptor
 class=S dtype=T
 length=1
 pointer=0000000000010AF0
 first char is X
** /pointer=64 compile **
-- $DESCRIPTOR --
64 bit pointer to 32 bit descriptor
 class=S dtype=T
 length=1
 pointer=00010AF0
 first char is X
-- struct dsc$descriptor_s --
64 bit pointer to 32 bit descriptor
 class=S dtype=T
 length=1
 pointer=00010AF0
 first char is X
-- $DESCRIPTOR64 --
64 bit pointer to 64 bit descriptor
 class=S dtype=T
 length=1
 pointer=0000000000010AF0
 first char is X
-- struct dsc64$descriptor_s --
64 bit pointer to 64 bit descriptor
 class=S dtype=T
 length=1
 pointer=0000000000010AF0
 first char is X

Java:

Pure Java runs identical on 32 and 64 bit JVM's and pointer (reference in Java terminology) size is totally transparent.

But if the Java code calls native code using JNI then pointer size becomes very relevant.

Java 1.3, 1.4, 1.5 and 1.6 on VMS pass 32 bit pointers via JNI.

Java 1.8 (and probably future versions) on VMS pass 64 bit pointers via JNI.

And the C code being called need to be build accordingly.

And it is really a hassle to get a 64 bit pointer from Java and needing to call a VMS API requiring 32 bit pointers.

Macro-32:

Pointers:

The example is not a main program in Macro-32 but some passthrough routines in Macro-32. I considered that more relevant.

#include <stdlib.h>

typedef char *char_ptrdef;
#pragma pointer_size save
#pragma pointer_size 32
typedef char *char_ptr32;
#pragma pointer_size 64
typedef char *char_ptr64;
#pragma pointer_size restore

void r32(char_ptr32 lbl, char_ptr32 p);
void r64(char_ptr64 lbl, char_ptr64 p);

int main(int argc, char *argv[])
{
    char_ptr32 p32;
    char_ptr64 p64;
    p32 = _malloc32(1);
    p32[0] = 'X';
    r32("32 bit", p32);
    p64 = _malloc64(1);
    p64[0] = 'X';
    r64("64 bit", p64);
    return 0; 
}

        .title  mp
        .psect  $CODE quad,pic,con,lcl,shr,exe,nowrt
r32::   .call_entry home_args=TRUE,max_args=2
        pushl   #4
        pushab  @8(ap)
        pushab  @4(ap)
        calls   #3, inspecta
        ret
r64::   .call_entry quad_args=TRUE,max_args=2
        EVAX_LDQ       r2,8(ap)
        EVAX_LDQ       r1,4(ap)
        $SETUP_CALL64  3
        $PUSH_ARG64    #8
        $PUSH_ARG64    r2
        $PUSH_ARG64    r1
        $CALL64        inspecta
        ret
        .end
$ cc/pointer=64 inspecta
$ macro mp
$ cc/pointer=64 mp0
$ link/exe=mp mp0 + mp + inspecta
$ run  mp
$ exit
-- 32 bit --
real address is         00044C08 (32 bits)
 32 bit API see         00044C08
  extend it to  0000000000044C08
 64 bit API see 0000000000044C08
 first char is X
-- 64 bit --
real address is 0000000080000010 (64 bits)
 32 bit API see         80000010
  extend it to  FFFFFFFF80000010
 64 bit API see 0000000080000010
 first char is X

The 64 bit version does not share much resemblance with traditional VAX Macro-32.

Article history:

Version Date Description
0.9 March 10th 2023 Pre-release
1.0 March 15th 2023 Updates (thanks to Craig, Andreas, Jouk, Jan-Erik, Simon and John for comments)

Other articles:

See list of all articles here

Comments:

Please send comments to Arne Vajhøj