Oppolzer - Informatik / Stanford Pascal Compiler


Home       Lebenslauf       Schwerpunkte       Kenntnisse       Seminare       Kunden       Projekte       Produkte       Blog       Stanford Pascal       Kontakt

The Stanford Pascal Compiler / Evolution Steps

Back to Compiler main page

New P-Code instructions to support inline MEMCPY and MEMSET

Compiler version: 12.2017

MEMCPY and MEMSET were introduced in 2016 as new standard procedures to support block moves and block initializations in the same way as C does it. The arguments are the same as in their C counterparts. Because we have the ADDR function, too, which supports getting the address of every variable (and SIZEOF to get the size of every variable or type), MEMCPY and MEMSET are very easy to use. Of course, there are some security problems, when using these procedures, and they have to be used with care.

The initial versions of MEMCPY and MEMSET were implemented using the library call facility of Stanford Pascal, that is: the compiler generates calls to an external Pascal procedure which implements the standard function (in this case, the external procedure was located in PASLIBX.PAS). This was a bit costly, because the procedure prologue has to be executed on every MEMCPY and MEMSET call.

What made things worse: MEMCPY and MEMSET were implemented using Pascal loops, which in fact moved one character at a time. This was, of course, the easiest way to do it, and for a proof of concept it was sufficient, but not for production work.

This is the library function that implemented MEMCPY and MEMSET, BTW; at least the PTR2INT and PRTADD function calls are implemented by inline code (simple P-Code instuctions). If they were true function calls, performance would be a real nightmare.


procedure $PASSTR ( FUNCCODE : INTEGER ; X1 : CHARPTR ; X2 : CHARPTR ; L : INTEGER ) ; (**************************************) (* Verteiler fuer String-Funktionen *) (**************************************) var CH : CHAR ; begin (* $PASSTR *) case FUNCCODE of /*********************************/ /* MEMSET */ /*********************************/ 1 : begin CH := CHR ( PTR2INT ( X2 ) ) ; while L > 0 do begin X1 -> := CH ; L := L - 1 ; X1 := PTRADD ( X1 , 1 ) end (* while *) end (* tag/ca *) ; /*********************************/ /* MEMCPY */ /*********************************/ 2 : begin while L > 0 do begin X1 -> := X2 -> ; L := L - 1 ; X1 := PTRADD ( X1 , 1 ) ; X2 := PTRADD ( X2 , 1 ) end (* while *) end (* tag/ca *) ; otherwise EXIT ( 1120 ) ; end (* case *) ; end (* $PASSTR *) ;

It was clear to me from the start that I would have to find a better solution later.

With the 12.2017 compiler release, I introduced some new P-Code instructions to support inline translation of MEMCPY and MEMSET:

MCP - for MEMCPY, if length is variable (MOV is sufficient for MEMCPY, if length is fixed)

MSE - for MEMSET, if length is variable (MFI is sufficient for MEMSET, if length is fixed;
MFI was introduced recently to support pre-formatting of longer target strings, when assigning shorter strings)

MZE - special case for MEMSET, if length is fixed and init pattern is zero
(some platforms have special instructions for this case, for example XC on the mainframe)

For a detailed description of the new P-Code instructions, you may look at the new P-Code documentain (2017 update):

P-Code Description - 2017 release

Implementation of the new P-Code instructions

On the mainframe, the P-Code translator PASCAL2.PAS generates MVCs for P-Codes MOV and MFI, if the length is less or equal to 256, and MVCLs otherwise. For variable length transfers (like MSE and MCP), MVCL is generated. MZE generates XC, if the length is less or equal to 256.

Example: some lines of a test program containing MEMSET and MEMCPY (variable lengths); the numbers on the left are the line numbers of the source.


55: L := 2000 ; 58: P := ADDR ( BUF ) ; 59: P := PTRADD ( P , L ) ; 60: MEMSET ( P , CH , L ) ; 64: L := 21 ; 65: MEMCPY ( ADDR ( F21 ) , ADDR ( F30 ) , L ) ;

This is the P-Code that the compiler generated:


LOC 55 LDC I,2000 STR I,1,416 LOC 58 LDA 1,421 STR A,1,4424 LOC 59 LOD A,1,4424 LOD I,1,416 ADA STR A,1,4424 LOC 60 LOD A,1,4424 LOD C,1,420 LOD I,1,416 MSE 0 LOC 64 LDC I,21 STR I,1,416 LOC 65 LDA 1,392 LDA 1,362 LOD I,1,416 MCP

and here you can see the 370 instructions that PASCAL2 generates from this P-Code (Pseudo ASSEMBLER):


-------------------- LOC 55 ------------------ 0298: LDC I,2000 0298: STR I,1,416 @@ 0298: LA 2,2000 @@ 029C: ST 2,416(13) -------------------- LOC 58 ------------------ 02EE: LDA 1,421 02EE: STR A,1,4424 @@ 02EE: LA 2,421(13) @@ 02F2: LA 14,4088(13) @@ 02F6: ST 2,336(0,14) -------------------- LOC 59 ------------------ 02FA: LOD A,1,4424 02FA: LOD I,1,416 02FA: ADA @@ 02FA: A 2,416(13) 02FE: STR A,1,4424 @@ 02FE: ST 2,336(0,14) -------------------- LOC 60 ------------------ 0302: LOD A,1,4424 0302: LOD C,1,420 0302: LOD I,1,416 0302: MSE 0 @@ 0302: LA 6,0(0,2) @@ 0306: L 7,416(13) @@ 030A: LTR 7,7 @@ 030C: BC 13,0 ## BNP @NOMV @@ 0310: XR 4,4 @@ 0312: IC 5,420(13) @@ 0316: SLL 5,24 @@ 031A: MVCL 6,4 ## @NOMV DS 0H -------------------- LOC 64 ------------------ 0372: LDC I,21 0372: STR I,1,416 @@ 0372: LA 2,21 @@ 0376: ST 2,416(13) -------------------- LOC 65 ------------------ 037A: LDA 1,392 037A: LDA 1,362 037A: LOD I,1,416 037A: MCP @@ 037A: LA 6,392(13) @@ 037E: LA 4,362(13) @@ 0382: LR 7,2 @@ 0384: LTR 5,7 @@ 0386: BC 13,0 ## BNP @NOMV @@ 038A: MVCL 6,4 ## @NOMV DS 0H -------------------- LOC 66 ------------------

The P-Code interpreter PCINT also runs much better with the new P-Codes, of course. Without them, procedure calls MEMSET and MEMCPY had to be done by running (interpreting) the Pascal loops in PASLIBX.PAS (see above). Now the new P-Codes are interpreted directly by PCINT (that is, it uses C function calls memset and memcpy to interpret them, which are often implemented by block moves using native code instructions). This should make a big difference.

Back to Compiler main page