Trailing-Edge
-
PDP-10 Archives
-
SRI_NIC_PERM_FS_1_19910112
-
c/kcc/ccout.c
There are 8 other files named ccout.c in the archive. Click here to see a list.
/* CCOUT.C - Output pseudo-code to assembler file
**
** (c) Copyright Ken Harrenstien 1989
** All changes after v.259, 11-Aug-1988
** (c) Copyright Ken Harrenstien, SRI International 1985, 1986
** All changes after v.77, 8-Aug-1985
**
** Original version (C) 1981 K. Chen
*/
#include "cc.h"
#include "ccgen.h"
#include "ccchar.h"
#include <string.h>
/* Imported functions */
extern SYMBOL *symfidstr(); /* CCSYM */
extern int foldtrna(); /* CCJSKP */
/* Exported functions (defined here and used externally) */
void outinit(), outdone(),
outlab(), outscon(), outmpdbl(),
outid(), outmidef(), outmiref(), outptr(),
outstr(), outnum(), outpnum(), outc(), outnl(), outtab(), outsix();
void realcode();
int codeseg(), dataseg(), prevseg();
int outflt(); /* CCGEN */
int binexp(); /* CCEVAL */
int makprefile();
int adjboffset();
#if SYS_CSI
void outprolog(), outepilog(); /* profiler functions: 9/15/89 by MVS */
#endif
/* Internal Functions */
static void simptrcnv(), simsmove(), simufltr(), simuidiv(),
simsubbp(), simdfix(), simdsngl();
static void outpreamble(), outpostamble(),
outinstr(), outop(), outreg(),
outpti(), outaddress(), outasmh();
static int directop(), fltpow2(), outdecl(), bigfloat(), obplh();
static whichseg; /* 1 = code, -1 = data, 0 = unknown */
/* Tables to handle references to built-in C RunTime symbols without
** going through the overhead of using the symbol table.
** Currently this is only used to determine which CRT symbols need to
** be declared EXTERN in the assembly-language output file.
** Note that $$CVER is always defined and declared in every KCC module,
** as are the appropriate $$CPxx symbols.
*/
#define crtsyms \
crtsym(CRT_,"") /* avoid zero index */\
crtsym(CRT_CRT, "$$$CRT") /* CRT module "entry point" */\
crtsym(CRT_CPU, "$$$CPU") /* CPU module "entry point" */\
crtsym(CRT_CVER, "$$CVER") /* KCC code & library version # */\
crtsym(CRT_CPUKA, "$$CPKA") /* KCC/CPU type KA-10 */\
crtsym(CRT_CPUKI, "$$CPKI") /* KCC/CPU type KI-10 */\
crtsym(CRT_CPUKS, "$$CPKS") /* KCC/CPU type KS-10 and KL-10A */\
crtsym(CRT_CPUKL0,"$$CPKL") /* KCC/CPU type KL-10B, section 0 */\
crtsym(CRT_CPUKLX,"$$CPKX") /* KCC/CPU type KL-10B, non-0 sect */\
crtsym(CRT_SECT, "$$SECT") /* CPU Section # being loaded into */\
crtsym(CRT_BPH0, "$$BPH0") /* CPU Byte ptr LHs for 18-bit bytes*/\
crtsym(CRT_BPH1, "$$BPH1") /* CPU */\
crtsym(CRT_BP90, "$$BP90") /* CPU Byte ptr LHs for 9-bit bytes */\
crtsym(CRT_BP91, "$$BP91") /* CPU */\
crtsym(CRT_BP92, "$$BP92") /* CPU */\
crtsym(CRT_BP93, "$$BP93") /* CPU */\
crtsym(CRT_BP80, "$$BP80") /* CPU Byte ptr LHs for 8-bit bytes */\
crtsym(CRT_BP81, "$$BP81") /* CPU */\
crtsym(CRT_BP82, "$$BP82") /* CPU */\
crtsym(CRT_BP83, "$$BP83") /* CPU */\
crtsym(CRT_BP70, "$$BP70") /* CPU Byte ptr LHs for 7-bit bytes */\
crtsym(CRT_BP71, "$$BP71") /* CPU */\
crtsym(CRT_BP72, "$$BP72") /* CPU */\
crtsym(CRT_BP73, "$$BP73") /* CPU */\
crtsym(CRT_BP74, "$$BP74") /* CPU */\
crtsym(CRT_BP60, "$$BP60") /* CPU Byte ptr LHs for 6-bit bytes */\
crtsym(CRT_BP61, "$$BP61") /* CPU */\
crtsym(CRT_BP62, "$$BP62") /* CPU */\
crtsym(CRT_BP63, "$$BP63") /* CPU */\
crtsym(CRT_BP64, "$$BP64") /* CPU */\
crtsym(CRT_BP65, "$$BP65") /* CPU */\
crtsym(CRT_BPPS, "$$BPPS") /* CPU Mask for BP P+S field */\
crtsym(CRT_BPSZ, "$$BPSZ") /* CPU BP LH to get BP size field */\
crtsym(CRT_BSHF, "$$BSHF") /* CPU # bits to shift in P_SUBBP */\
crtsym(CRT_BMP6, "$$BMP6") /* CPU val for 6bit MULI in P_SUBBP */\
crtsym(CRT_BMP7, "$$BMP7") /* CPU " " 7-bit " */\
crtsym(CRT_BMP8, "$$BMP8") /* CPU " " 8-bit " */\
crtsym(CRT_BMP9, "$$BMP9") /* CPU " " 9-bit " */\
crtsym(CRT_BMPH, "$$BMPH") /* CPU " " 18-bit " */\
crtsym(CRT_PH90, "$$PH90") /* CPU Instr #0 for 18->9 cnv */\
crtsym(CRT_PH91, "$$PH91") /* CPU Instr #1 for 18->9 cnv */\
crtsym(CRT_P9H0, "$$P9H0") /* CPU Instr #0 for 9->18 cnv */\
crtsym(CRT_P9H1, "$$P9H1") /* CPU Instr #1 for 9->18 cnv */\
crtsym(CRT_P9H2, "$$P9H2") /* CPU Instr #2 for 9->18 cnv */\
crtsym(CRT_START,"$START") /* CRT start location */\
crtsym(CRT_RET, "$RET") /* CRT Convenient return label */\
crtsym(CRT_RETZ, "$RETZ") /* CRT ditto, returns 0 */\
crtsym(CRT_RETF, "$RETF") /* CRT ditto, returns 0 */\
crtsym(CRT_RETP, "$RETP") /* CRT ditto, returns 1 */\
crtsym(CRT_RETT, "$RETT") /* CRT ditto, returns 1 */\
crtsym(CRT_RETN, "$RETN") /* CRT ditto, returns -1 */\
crtsym(CRT_ZERO, "$ZERO") /* CRT Double 0.0 constant */\
crtsym(CRT_ADJBP, "$ADJBP") /* CRT ADJBP simulation rtn (unused)*/\
crtsym(CRT_BPMUL, "$BPMUL") /* CRT BP mul table (for P_SUBBP) */\
crtsym(CRT_BPADT, "$BPADT") /* CRT BP table of $BPADn (for ") */\
crtsym(CRT_BPAD6, "$BPAD6") /* CRT BP 6-bit add table (for ") */\
crtsym(CRT_BPAD7, "$BPAD7") /* CRT BP 7-bit add table (for ") */\
crtsym(CRT_BPAD8, "$BPAD8") /* CRT BP 8-bit add table (for ") */\
crtsym(CRT_BPAD9, "$BPAD9") /* CRT BP 9-bit add table (for ") */\
crtsym(CRT_BPADH, "$BPADH") /* CRT BP 18-bit add table (for ") */\
crtsym(CRT_BPCNT, "$BPCNT") /* CRT Byte ptr auxiliary */\
crtsym(CRT_SUBBP, "$SUBBP") /* CRT Byte ptr subtraction aux */\
crtsym(CRT_DFIX, "$DFIX") /* CRT Aux for (int)(double) cast */\
crtsym(CRT_DFIXS, "$DFIXS") /* CRT Aux for (int)(double) cast */\
crtsym(CRT_DFLTS, "$DFLTS") /* CRT Aux for (double)(int) cast */\
crtsym(CRT_SPUSH, "$NSPUSH") /* CRT Aux to put struct on stack */\
crtsym(CRT_SPOP, "$NSPOP") /* CRT Aux to get struct from stack */
#define crtsym(idx, sym) idx,
enum { crtsyms CRT_N }; /* Define the CRT_ indices plus count */
#undef crtsym
static int crtref[CRT_N]; /* Table of CRT sym reference counts */
#define crtsym(idx, sym) sym,
static char *crtsnam[CRT_N] = { crtsyms }; /* Table of CRT symbol names */
#undef crtsym
/*
** OUTINIT - Initialize assembled code output
** Called once for each file compiled.
*/
void
outinit()
{
register int i;
for(i = CRT_N; --i >= 0;) /* Reset reference counts */
crtref[i] = 0;
/* These two runtime symbols are ALWAYS implicitly referenced;
** they refer to the CRT and CPU runtime modules.
** See outpostamble() for a special reset check, though.
*/
++crtref[CRT_CRT];
++crtref[CRT_CPU];
outpreamble(); /* Output assembler preamble */
whichseg = 1; /* Now starting in code section */
}
/* OUTDONE - Finalize assembled code output.
*/
void
outdone(mainf)
{
outpostamble(mainf);
}
/*
** assembler preamble and postamble
*/
static void
outpreamble()
{
if (tgasm == TGASM_MIDAS) /* This MIDAS pseudo */
outstr(".SYMTAB 8111.,8003.\n"); /* must come before TITLE. */
outstr("\tTITLE\t"); /* Make TITLE pseudo-op */
outstr(inpfmodule); /* with name of input file as title */
outnl();
/* Now output the Assembler Header, either by generating it
** ourself or copying it from a user-specified file.
*/
outasmh();
}
static void
outpostamble(mainf)
int mainf; /* TRUE if main() was defined in file */
{
register int i, c;
register SYMBOL *s;
outnl();
codeseg(); /* make sure in code segment */
/* Specify machine types usable with this module */
for (c = TGCPU_N; --c >= 0;)
if (tgcpu[c] == 0) { /* Impossible CPUs have their symbol set 0 */
switch(c) {
case TGCPU_KA: i = CRT_CPUKA; break;
case TGCPU_KI: i = CRT_CPUKI; break;
case TGCPU_KS: i = CRT_CPUKS; break;
case TGCPU_KL0: i = CRT_CPUKL0; break;
case TGCPU_KLX: i = CRT_CPUKLX; break;
default: int_error("outpostamble: bad CPU");
i = CRT_CPUKL0;
}
outstr(crtsnam[i]);
outstr("==0\n\t");
outstr(tgasm == TGASM_MIDAS ? ".GLOBAL " : "INTERN ");
outstr(crtsnam[i]);
outnl();
}
if (tgasm == TGASM_MIDAS) { /* output literals and finish */
dataseg();
outstr("\tVARIABLES\n"); /* Put any accumulated vars here */
codeseg(); /* Then back to code seg */
outstr("\n\tCONSTANTS\n"); /* for accumulated literals */
} else outstr("\n\tLIT\n");
/* "main" module needs entry vector set. This crock is necessary
** because FAIL requires that the arg to END be defined in the current
** module!
*/
if (mainf) {
outstr("$$STRT: JRST $START\n");
outstr("\tJRST $START+1\n");
outstr("\t0\n"); /* 3rd wd for T20 ver info */
++crtref[CRT_START];
}
/* Output EXTERN declarations for any C runtime symbols we used */
/* Normally the CPU and CRT modules are always reffed, but just
** to make life easier for us KCC implementors, we check here
** for compiling those modules themselves, and avoid sending out
** an EXTERN if so!
*/
if ((s = symfidstr("`$$$CRT")) && s->Sclass==SC_EXTDEF)
crtref[CRT_CRT] = 0; /* Is defined, pretend not reffed */
if ((s = symfidstr("`$$$CPU")) && s->Sclass==SC_EXTDEF)
crtref[CRT_CPU] = 0; /* Is defined, pretend not reffed */
/* Emit symbol requests for all referenced C runtime support stuff */
for (i = CRT_N; --i >= 0; )
if (crtref[i]) {
outstr(tgasm == TGASM_MIDAS ? "\t.GLOBAL " : "\tEXTERN\t");
outstr(crtsnam[i]);
outnl();
}
#if 0
if (bytsym.Svalue) outstr("\tEXTERN\t$BYTE\n");
#endif
outdecl(); /* Output user-defined INTERN and EXTERNs */
/* Finally, output entry vector if any */
if (mainf)
outstr("\tEND <3,,$$STRT>\n"); /* 3 wds in entry vector */
else
outstr("\tEND\n");
}
/* OUTDECL - Output assembler external & internal declarations at end
** of compiling a module (translation unit).
*/
static int
outdecl()
{
SYMBOL *s = symbol; /* Start of global sym list */
int num = 0;
while (s = s->Snext) {
switch (s->Sclass) {
default: continue;
case SC_EXTDEF: /* External linkage sym defined */
num++;
outstr(tgasm == TGASM_MIDAS ? "\t.GLOBAL\t " : "\tINTERN\t");
break;
case SC_EXTREF: /* External linkage sym referenced */
case SC_XEXTREF:
/* Symbol not defined. Ask for it if needed */
if (s->Srefs == 0) { /* If never used, don't ask. */
if (!delete) /* If keeping asm file, */
outc(';'); /* output it as a comment. */
else continue; /* Otherwise just ignore */
}
outstr(tgasm == TGASM_MIDAS ? "\t.GLOBAL\t" : "\tEXTERN\t");
break;
}
outmiref(s);
outnl();
}
return num;
}
/* MAKPREFILE - determines whether a prefix assembler file is needed
** and creates it if so. Returns non-zero if one was created.
** Always generates one for MIDAS since an .INSRT was already put
** in the output MID file!
*/
int
makprefile(fname)
char *fname;
{
SYMBOL *sym;
FILE *savout, *fpre;
int nexfs = 0;
/* Scan symbol table to see if there are any names
** we should export as library entry points.
*/
if (tgasm == TGASM_MIDAS) nexfs++; /* Fake out */
else {
for (sym = symbol; sym = sym->Snext;)
if (sym->Sclass == SC_EXTDEF) /* An external definition? */
break; /* Yup */
if (sym == NULL) return 0; /* If none, just return */
}
if ((fpre = fopen(prefname, "w")) == NULL)
return errfopen("prefix output", prefname), 0;
savout = out;
out = fpre; /* Set this so can use outstr and outmiref */
/* OK, now output entry statements for each exportable symbol */
for(; sym; sym = sym->Snext)
if (sym->Sclass == SC_EXTDEF) { /* For each external def */
outstr(tgasm == TGASM_MIDAS ? ".GLOBAL " : "ENTRY ");
outmiref(sym);
outnl();
++nexfs;
}
out = savout; /* Restore old pointer if any */
fclose(fpre);
return(nexfs);
}
/*
** KCC Assembler Header output
**
** This section contains the code required to generate the assembler header
** text that KCC puts at the beginning of all its assembler
** output files, just after any ENTRY or TITLE statements, and before
** any generated code.
**
** The header for both FAIL and MACRO is identical.
** However, MIDAS requires a somewhat different syntax.
*/
/* Imported functions */
extern char *malloc();
extern char *estrcpy(), *fstrcpy(); /* CCASMB */
/* Internal functions and data */
static char *ahmacs();
static char *ahmacdef();
/* OUTASMH - Output assembler header file.
**
** Copy Assembler Header File from its default or user-specified location.
** This should end up in the code segment, and supply macros
** %%CODE and %%DATA for switching between the two.
*/
static char *asmhdr = NULL;
static void
outasmh()
{
FILE *hdrf;
register int c;
/* If user explicitly specified an assembler header file, use it. */
if (asmhfile) {
if ((hdrf = fopen(asmhfile, "r")) == NULL) /* open file */
errfopen("preamble", asmhfile); /* no luck, give up */
else {
while ((c = getc(hdrf)) != EOF) outc(c); /* copy file */
if (ferror(hdrf) || !feof(hdrf))
jerr("I/O error while reading preamble \"%s\"", asmhfile);
fclose(hdrf);
return;
}
}
/* No file specified, or couldn't open. Use self-generated header. */
outstr(asmhdr ? asmhdr : ahmacs()); /* Make or use existing */
}
/* Macro definition tables */
static char * macdmv[] = { "DMOVE",
"MOVE A,M",
"MOVE A+1,1+M",
NULL
};
static char * macdmm[] = { "DMOVEM",
"MOVEM A,M",
"MOVEM A+1,1+M",
NULL
};
static char * macjsp[] = { "ADJSP",
"ADD A,[<M,,M>+<M&<-1,,0>>]",
NULL
};
/*
** Byte pointer operations.
**
** ADJBP AC,MEM
** takes a byte count in AC and a byte pointer of any shape in MEM,
** and leaves a byte pointer shifted over by the count in the AC.
**
** %%CHBP AC,MEM
** backs up the byte pointer in MEM by one and leaves it in AC.
** This was sometimes used in old runtimes to produce an ILDB pointer
** from an IBP pointer. The result may not quite be the same as
** corresponding AC -- it may be a (POINT n,X) pointer i.e. have
** a P field of 44 -- so only do this once and don't index through
** the result or compare it to another non-%%CHBP pointer.
*/
static char * macdb1[] = { "%%CHBP", /* For machines with ADJBP */
"SETO A,",
"ADJBP A,M",
NULL
};
static char * macjbp[] = { "ADJBP",
"EXTERN $ADJBP", /* Declare extern routine to simulate it */
"MOVE 16,M", /* Do this before EXCH. Get byte pointer */
"EXCH 15,A", /* Put count into expected reg, save AC15 */
"PUSHJ 17,$ADJBP", /* Call $ADJBP to simulate byte ptr add */
"EXCH 15,A", /* restore AC15 and get adjusted bp in reg */
NULL
};
static char * macdb2[] = { "%%CHBP",
"MOVE A,M", /* Get byte pointer */
"ADD A,[110000,,0]", /* Adjust assuming 9-bit bytes */
"TLNN A,007000", /* Was it 7-bit byte pointer? */
" SUB A,[020000,,0]", /* Yes, fix up for that case */
NULL,
};
/* General-purpose FLTR instruction for KA-10.
** This correctly handles integers larger than 27 significant bits.
** When the code generator knows that the integer will be small enough, it
** can produce a simple FSC R,233 instead. The simulation for UFLTR
** (unsigned FLTR) may also use a slightly better sequence since in that
** case the number will assuredly be positive.
*/
static char * macfltr[] = { "FLTR",
"MOVE A,M",
"MOVE 16,A+1", /* Must save A+1 */
"ASHC A,-10", /* Shift valid bits out of exponent field */
"FSC A,233+10", /* Now can float the high bits of integer */
"ASH A+1,-32", /* Right-justify low 8 bits of integer */
"FSC A,233", /* Float them too */
"FADR A,A+1", /* Add together to produce result! */
"MOVE A+1,16", /* Restore A+1 */
NULL
};
/*
** KA-10 Double-Precision Floating Point (software format) Macros
**
** Copied from:
** DEC Assembly Language Handbook, 1st edition, 1972
** PDP-10 System Reference Manual DEC-10-HGAC-D
** 3rd printing August 1969
** Page 2-67, "Programming Examples".
**
** NOTE! These macros require 3 registers, not just 2!
** We can save things in AC16 and be sure that A+2 is free
** (KCC reserves AC16 as a temporary for just such purposes).
** We never use doublewords beyond 14/15 so A+2 always exists.
**
** Some of these sequences are less than efficient, but they
** have to be sufficiently general to cover all possible abuses
** (e.g. unexpected registers used as indices in addresses).
*/
/* %%DSAV A,M - Set up A+2/free, M into -2(17)/-1(17): */
static char * macdsv[] = { "%%DSAV",
"MOVE 16,1+M", /* Get low word (push here would mess up M) */
"PUSH 17,M", /* Now safe to push high word */
"PUSH 17,16", /* Followed by low word (careful if A+2==16) */
"PUSH 17,A+2", /* Followed by AC to be freed */
NULL
};
/* %%DRST A, - Restore at end of double op after %%DSAV: */
static char * macdrs[] = { "%%DRST",
"POP 17,A+2", /* Get saved register back */
"ADJSP 17,-2", /* Forget about pushed copy of doubleword */
NULL
};
/* DMOVN A,M - Get double from memory and negate: */
static char * macdnv[] = { "DMOVN",
"MOVE A,M", /* Get high part of double */
"MOVE A+1,1+M", /* Then low part */
"DFN A,A+1", /* Do double register negate */
NULL
};
/* DMOVNM A,M - Save negated doubleword into memory: */
static char * macdnm[] = { "DMOVNM",
"DFN A,A+1", /* Negate register doubleword */
"MOVEM A,M", /* Store high part of negated word into mem */
"MOVEM A+1,1+M", /* And low part */
"DFN A,A+1", /* Negate reg again to leave the way it was */
NULL
};
/* DFAD A,M - Add two doubles: */
static char * macdfad[] = { "DFAD",
"%%DSAV A,M", /* Set up for doubleword op */
"UFA A+1,-1(17)", /* Sum of low parts to A+2 */
"FADL A,-2(17)", /* Sum of high parts to A,A+1 */
"UFA A+1,A+2", /* Add low part of high sum to A+2 */
"FADL A,A+2", /* Add low sum to high sum */
"%%DRST A,0", /* Fix up after doubleword op */
NULL
};
/* DFSB A,M - Subtract double in mem from one in acs: */
static char * macdfsb[] = { "DFSB",
"DFN A,A+1", /* Negate double length operand */
"DFAD A,M", /* Perform normal add */
"DFN A,A+1", /* -(M - AC) = AC - M */
NULL
};
/* DFMP A,M - Multiply two doubles: */
static char * macdfmp[] = { "DFMP",
"%%DSAV A,M", /* Set up for doubleword op */
"MOVEM A,A+2", /* Copy high AC operand in A+2 */
"FMPR A+2,-1(17)", /* High reg times low mem to A+2 */
"FMPR A+1,-2(17)", /* Low reg times high mem to A+1 */
"UFA A+1,A+2", /* Add cross products into A+2 */
"FMPL A,-2(17)", /* High product to A,A+1 */
"UFA A+1,A+2", /* Add low part to cross sum in A+2 */
"FADL A,A+2", /* Add low sum to high part of product */
"%%DRST A,0", /* Fix up after doubleword op */
NULL
};
/* DFDV A,M - Divide double in acs by one in mem: */
static char * macdfdv[] = { "DFDV",
"%%DSAV A,M", /* Set up for complicated double op */
"FDVL A,-2(17)", /* Get high part of quotient */
"MOVN A+2,A", /* Copy negative of quotient in A+2 */
"FMPR A+2,-1(17)", /* Multiply by low part of divisor */
"UFA A+1,A+2", /* Add remainder */
"FDVR A+2,-2(17)", /* Divide sum by high part of divisor */
"FADL A,A+2", /* Add result to original quotient */
"%%DRST A,0", /* Fix up after doubleword op */
NULL
};
/* Segmentation macros. See codeseg() and dataseg() for more comments. */
static char segfai[] = "\
TWOSEG 400000 \n\
RELOC 0 \n\
RELOC 400000 \n\
DEFINE %%CODE <RELOC>\n\
DEFINE %%DATA <RELOC>\n";
/* For MIDAS the code/data macros use .M" to ensure that the definitions they
** make are valid even if used within a .BEGIN symbol block.
*/
static char segmid[] = "\
.DECTWO 400000 \n\
$$RLO==. \n\
.=.+400000 \n\
$$RHI==. \n\
DEFINE %%CODE \n\
.M\"$$RLO==. ? .=.M\"$$RHI \n\
TERMIN \n\
DEFINE %%DATA \n\
.M\"$$RHI==. ? .=.M\"$$RLO \n\
TERMIN\n";
static char *
ahmacs()
{
register char *cp;
char *beg;
int size;
/* To construct the possibly large header string, we steal space
** by re-using the pcode buffer temporarily. Check when done to ensure
** it wasn't overflowed!
*/
beg = cp = (char *)codes;
#if SYS_CSI
if (profbliss) /* profiling switch for CSI */
cp = estrcpy(cp, "\t.REQUIRE BLI:PROFIL.REL\n");
#endif /* inserted for BLISS profiler, 9/29/89 MVS */
/* Make request for standard C library (unless using MIDAS).
** Eventually flush this request and rely on KCC linker invocation.
*/
if (tgasm != TGASM_MIDAS)
cp = estrcpy(fstrcpy(estrcpy(cp, "\t.REQUEST "), libpath, "c"), "\n");
else
/* If MIDAS, take the opportunity to .INSRT the prefix file here. */
cp = estrcpy(estrcpy(estrcpy(cp, ".INSRT "), prefname), "\n");
/* Specify version of compiler and library used with this module */
sprintf(cp,"\t$$CVER==<%o,,%o>\n\t%s $$CVER\n",
cvercode, cverlib,
(tgasm == TGASM_MIDAS) ? ".GLOBAL" : "INTERN");
cp += strlen(cp); /* Sigh, update ptr */
/* Add machine-dependent macro definitions */
if (!tgmachuse.dmovx) {
cp = ahmacdef(cp, macdmv); /* DMOVE */
cp = ahmacdef(cp, macdmm); /* DMOVEM */
}
if (!tgmachuse.adjsp)
cp = ahmacdef(cp, macjsp); /* ADJSP */
if (tgmachuse.adjbp) {
/* FAIL and MACRO need to have ADJBP defined! */
if (tgasm == TGASM_FAIL || tgasm == TGASM_MACRO)
cp = estrcpy(cp, "\tOPDEF ADJBP [IBP]\n");
cp = ahmacdef(cp, macdb1); /* %%CHBP */
} else {
cp = ahmacdef(cp, macjbp); /* ADJBP */
cp = ahmacdef(cp, macdb2); /* %%CHBP */
}
if (!tgmachuse.fixflt)
cp = ahmacdef(cp, macfltr); /* FLTR */
#if SYS_CSI /* PROF. for profiling */
if (profbliss) /* added 09/15/89 by MVS */
cp = estrcpy(cp, "\tOPDEF PROF. [37B8]\n");
#endif
if (tgmachuse.dfl_s) { /* KA-10 Software double-precision instrs */
cp = ahmacdef(cp, macdsv); /* %%DSAV */
cp = ahmacdef(cp, macdrs); /* %%DRST */
cp = ahmacdef(cp, macdnv); /* DMOVN */
cp = ahmacdef(cp, macdnm); /* DMOVNM */
cp = ahmacdef(cp, macdfad); /* DFAD */
cp = ahmacdef(cp, macdfsb); /* DFSB */
cp = ahmacdef(cp, macdfmp); /* DFMP */
cp = ahmacdef(cp, macdfdv); /* DFDV */
}
/* Miscellaneous op-code definitions.
** ERJMP and ERJMPA are for TOPS-20 and TENEX systems only, for
** the benefit of library (or user) #asm code.
** ERJMPA should be used instead of ERJMP when the JSYS normally skips.
**
** XMOVEI and IFIW are used with extended addressing code.
** XBLT likewise; this is actually an EXTEND sub-operation.
*/
if (tgasm == TGASM_FAIL || tgasm == TGASM_MACRO) {
if (tgsys == TGSYS_TOPS20 || tgsys == TGSYS_TENEX) {
cp = estrcpy(cp, "IFNDEF ERJMP,< OPDEF ERJMP [JUMP 16,] >\n");
if (tgsys == TGSYS_TOPS20)
cp = estrcpy(cp, "OPDEF ERJMPA [ERJMP]\n");
else cp = estrcpy(cp, "OPDEF ERJMPA [JUMPA 16,]\n");
}
cp = estrcpy(cp, "OPDEF XMOVEI [SETMI]\n\tDEFINE IFIW <SETZ >\n");
} else if (tgasm == TGASM_MIDAS) {
cp = estrcpy(cp, (tgsys == TGSYS_TOPS20
? "ERJMPA==<ERJMP>\n" /* T20 */
: "ERJMPA==<JUMPA 16,>\n")); /* otherwise */
cp = estrcpy(cp, "IFIW==<SETZ>\n");
}
if (tgasm == TGASM_FAIL)
cp = estrcpy(cp, "OPDEF XBLT [020000,,0]\n");
else if (tgasm == TGASM_MIDAS)
cp = estrcpy(cp, "XBLT==<020000,,0>\n");
/* Code and data segmentation setup and %%DATA/%%CODE macro definition */
if (tgasm == TGASM_FAIL || tgasm == TGASM_MACRO)
cp = estrcpy(cp, segfai);
else if (tgasm == TGASM_MIDAS) cp = estrcpy(cp, segmid);
/*
** Finally, we try to purge symbols which may conflict with user-defined syms.
** However, there is a bug in FAIL which causes SEARCH MONSYM to barf
** ("FAIL BUG IN SEARCH") if the SEARCH comes after the IF's are purged!
** But if we put the SEARCH here, before the PURGE, we are including it in ALL
** KCC output, which is not a good idea; there is more overhead, and
** otherwise undefined symbols might be unexpectedly satisfied from MONSYM!
** For the time being, since FAIL will output a .REL file anyway even after
** barfing about the SEARCH, we will continue doing this, and include a
** note in the doc to ignore those barfs, and hope someone fixes FAIL someday.
**
** Note that MACRO will not complain, and consequently MACRO is preferred
** when compiling modules with #asm code (such as the C library!)
*/
if (tgasm == TGASM_FAIL || tgasm == TGASM_MACRO)
cp = estrcpy(cp, "\
PURGE IFE,IFN,IFG,IFGE,IFL,IFLE,IFDEF,IFNDEF,IFIDN,IFDIF\n");
else if (tgasm == TGASM_MIDAS)
cp = estrcpy(cp, "\
IF1 [EQUALS $IFE,IFE?EQUALS $IFN,IFN?EQUALS $IFG,IFG?EQUALS $IFL,IFL\n\
EQUALS $IFGE,IFGE?EQUALS $IFLE,IFLE?EQUALS $IFSE,IFSE?EQUALS $IFSN,IFSN\n\
EQUALS $IFDEF,IFDEF?EQUALS $IFNDEF,IFNDEF?EQUALS $IRP,IRP\n\
EXPUNGE IFE,IFN,IFG,IFGE,IFL,IFLE,IFSE,IFSN,IFDEF,IFNDEF,IRP\n\
]\n");
size = strlen(beg)+1; /* Find size of entire header */
if (size > sizeof(codes)) /* Paranoia check */
fatal("Assembler header table overflow");
if (asmhdr = malloc(size)) { /* Now try to relocate it */
strcpy(asmhdr, beg); /* Won, copy header to save it! */
return asmhdr;
}
return beg; /* No luck, just re-generate each time */
}
static char *
ahmacdef(cp, macro)
char *cp;
char **macro;
{
switch (tgasm) {
case TGASM_FAIL:
case TGASM_MACRO:
cp = estrcpy(estrcpy(estrcpy(cp,"DEFINE "),macro[0]),"(A,M)\n<");
while (*++macro)
cp = estrcpy(estrcpy(estrcpy(cp, "\t"), *macro), "\n");
cp = estrcpy(cp, ">\n");
break;
case TGASM_MIDAS:
cp = estrcpy(estrcpy(estrcpy(cp,"DEFINE "),macro[0])," A,M\n");
while (*++macro)
cp = estrcpy(estrcpy(estrcpy(cp, "\t"), *macro), "\n");
cp = estrcpy(cp, "TERMIN\n");
break;
}
return cp;
}
/*
** Data segmentation.
**
** KCC emits code in two sections: code and data. The header sets things up
** so that code and data are in the high and low segments, which typically
** correspond to the high and low halves of their section but which can be
** rearranged by the appropriate LINK directives.
**
** Code and data are separated in the assembly output by the %%CODE and %%DATA
** macros. %%CODE will never be called from any context other than the data
** segment, and vice versa. KCC expects the header to initially be in code.
**
** During code generation, codeseg() and dataseg() are used to switch
** between the two segments.
*/
/* CODESEG - Start using code (pure) segment
*/
int
codeseg()
{
int oseg;
if ((oseg = whichseg) < 0) { /* if in data */
outstr("\n\t%%CODE\n"); /* put in code instead */
whichseg = 1; /* and remember in code */
}
return oseg;
}
/* DATASEG - Start using data (impure) segment
*/
int
dataseg()
{
int oseg;
if ((oseg = whichseg) > 0) { /* if in code */
outstr("\n\t%%DATA\n"); /* put in data instead */
whichseg = -1; /* and remember in data */
}
return oseg;
}
/* PREVSEG - restore to previous segment
*/
int
prevseg(seg)
{
return (seg < 0) ? dataseg() : codeseg();
}
/* REALCODE(p) - Generate real code for given pseudo-instruction.
** This is the primary assembler output function.
*/
void
realcode(p)
PCODE *p;
{
int i, typ;
typ = (p->Ptype &~ PTF_SKIPPED); /* Get sanitized addressing type */
switch (p->Pop & POF_OPCODE) {
case P_NOP: /* Ignore deleted pseudo-code */
return;
/* Handle the various simulated ops, which do not correspond
** directly to a real PDP-10 instruction.
*/
case P_PTRCNV: simptrcnv(p);return; /* "Pointer Conversion" */
case P_SMOVE: simsmove(p); return; /* "Structure Move" */
case P_UFLTR: simufltr(p); return; /* "Unsigned Float & Round" */
case P_UIDIV: simuidiv(p); return; /* "Unsigned Integer Divide" */
case P_SUBBP: simsubbp(p); return; /* "Subtract Byte Pointer" */
case P_DFIX: simdfix(p); return; /* "Double Fix" */
case P_DSNGL: simdsngl(p); return; /* "Double to Single" */
/* End of simulated ops; switch continued on next page! */
/* Switch continued from previous page! Start of "real" ops. */
/*
** If we are doing a test or comparison with a number, and the number
** is larger than 18 bits, we have to use a literal.
** We also have to perform a similar transformation if we want
** to compare the register with an immediate address.
** This entails changing the opcode from TRN to TDN, CAI to CAM, etc.
** Note this is NOT an optimization -- it is needed to avoid generating
** impossible instructions.
*/
case P_TRN:
if (foldtrna(p)) return;
case P_TRC:
case P_TRZ:
case P_TRO:
case P_CAI:
if (typ != PTA_RCONST || (p->Pvalue &~ 0777777) == 0) break;
if ((p->Pvalue & 0777777) == 0) {
switch (p->Pop & POF_OPCODE) {
case P_TRN: i = P_TRN ^ P_TLN; break;
case P_TRO: i = P_TRO ^ P_TLO; break;
case P_TRZ: i = P_TRZ ^ P_TLZ; break;
case P_TRC: i = P_TRC ^ P_TLC; break;
default: i = 0; break;
}
if (i) {
p->Pop ^= i; /* Flip from RH to LH */
p->Pvalue = ((unsigned) p->Pvalue) >> 18;
break;
}
}
if (!(p->Pvalue &~ 0777777)) break; /* still too big? */
p->Pop = directop(p->Pop); /* Turn into memory version */
typ |= PTF_IMM; /* Make PTV_IMMED so outinstr hacks bigness */
break;
/* Optimization for non-test bitwise operations (IOR, AND, XOR).
** We try turning these into tests to avoid referencing memory
** for a literal constant.
*/
case P_IOR:
if (!optobj || typ != PTV_IMMED || (p->Pvalue &~ 0777777) == 0)
break; /* Can't or don't optimize */
if ((p->Pvalue & 0777777) == 0) { /* x,,0 */
p->Pop = P_TLO;
typ = PTA_RCONST;
p->Pvalue = ((unsigned) p->Pvalue) >> 18;
} else if (((unsigned) p->Pvalue >> 18) == 0777777) { /* 777777,,x */
p->Pop = P_ORCM;
p->Pvalue = (~p->Pvalue) & 0777777;
}
break;
case P_XOR:
if (!optobj || typ != PTV_IMMED || (p->Pvalue &~ 0777777) == 0)
break; /* Can't or don't optimize */
if ((p->Pvalue & 0777777) == 0) { /* x,,0 */
p->Pop = P_TLC;
typ = PTA_RCONST;
p->Pvalue = ((unsigned) p->Pvalue) >> 18;
} else if (((unsigned) p->Pvalue >> 18) == 0777777) { /* 777777,,x */
p->Pop = P_EQV;
p->Pvalue = (~p->Pvalue) & 0777777;
}
break;
case P_AND:
if (!optobj || typ != PTV_IMMED || (p->Pvalue &~ 0777777) == 0)
break; /* Can't or don't optimize */
if ((p->Pvalue & 0777777) == 0777777) { /* x,,777777 */
p->Pop = P_TLZ;
typ = PTA_RCONST;
p->Pvalue = (unsigned)(~p->Pvalue) >> 18;
} else if (((unsigned)p->Pvalue >> 18) == 0777777) { /* 777777,,x */
p->Pop = P_TRZ;
typ = PTA_RCONST;
p->Pvalue = (~p->Pvalue) & 0777777;
}
break;
/*
** Signed integer multiplication by a power of two is better done as an ASH
** (arithmetic shift - not the same as LSH). Don't try this
** for division, as the result is incorrect for negative numbers!
** Note that unsigned multiplication will use P_MUL, not P_IMUL.
** Note the subtle test for power-of-two-ness which relies on
** twos-complement arithmetic.
**
** Similarly, floating multiply by two becomes FSC (floating scale).
** Division is OK, unlike the integer case.
** This can only work for single-precision floats (not doubles).
** Otherwise we fall through to the other floating code.
*/
case P_IMUL:
if (optobj && typ == PTV_IMMED && (p->Pvalue & (p->Pvalue - 1)) == 0) {
p->Pvalue = binexp(p->Pvalue); /* get # of bits to shift */
p->Pop = P_ASH; /* and opcode */
typ = PTA_RCONST; /* and code type */
}
break;
case P_FMPR: /* If doing single-precision mult or div */
case P_FDVR:
if (optobj
&& typ == PTA_FCONST /* If operand is a float constant */
&& (i = fltpow2(p->Pfloat))) { /* and is a power of 2 */
p->Pvalue = (p->Pop == P_FDVR) ? -i : i;
p->Pop = P_FSC;
typ = PTA_RCONST;
break;
}
/*
** Optimize moves of constants to use immediate form if possible.
** A check for "float" constants, which can be
** MOVSI'd into the LH if their RH is zero, is made in outinstr().
** "double" constants
** normally do not come here as they use DMOVx, but it is possible
** as part of a code sequence that zeros the second AC separately.
*/
case P_MOVN:
if (!optobj || typ != PTV_IMMED || (p->Pvalue &~ 0777777) == 0) break;
p->Pop = P_MOVE; /* re-invert P_MOVN */
p->Pvalue = - p->Pvalue; /* for fixup into P_MOVSI */
case P_MOVE:
if (!optobj || typ != PTV_IMMED) break;
if (p->Pvalue && (p->Pvalue & 0777777) == 0) {
p->Pop = P_MOVS; /* MOVEI of left half quantity */
p->Pvalue = ((unsigned) p->Pvalue) >> 18; /* becomes MOVSI */
}
break;
}
/* End of moby switch on instruction opcode! */
typ |= p->Ptype & PTF_SKIPPED;
p->Ptype = typ; /* Put back type in case it was changed. */
outinstr(p); /* Output the instruction! */
}
/* OUTINSTR(p) - Output instruction.
**
** We give a skipped-over op an extra space of indentation, to make it look
** more like human code (big deal) and to make debugging KCC easier.
*/
static void
outinstr(p)
PCODE *p;
{
int big, i, opr;
outtab(); /* Start instruction output w/ tab */
if (p->Ptype & PTF_SKIPPED) /* Indent skipped-over instr */
outc(' ');
switch (p->Ptype & PTF_ADRMODE) {
case PTA_ONEREG:
outop(p->Pop);
outreg(p->Preg);
break;
case PTA_REGIS:
outop(p->Pop);
outreg(p->Preg);
outnum(p->Pr2);
break;
/* PTA_MINDEXED operands require special hackery when the operand is
** being used in an immediate fashion, i.e. the address value itself
** is needed. This can happen either if the operand is marked
** immediate (PTF_IMM set) or if the opcode is one that treats E as
** an immediate operand (only CAIx and TRx are checked). This special
** handling typically involves using an XMOVEI to get the real address
** if the target machine uses extended addressing.
*/
case PTA_MINDEXED:
if (!(p->Ptype & PTF_IMM)) {
/* Not using immediate address, only need to check for
** instrs that are always immediate.
*/
if (i = directop(p->Pop)) { /* Converting CAI, etc to CAM etc? */
p->Pop = i; /* Ugh, convert to direct form */
/* Fall past to do XMOVEI */
} else {
/* Normal operand, easy! */
outop(p->Pop); /* Normal op, just output it */
outreg(p->Preg); /* use given reg */
outaddress(p); /* and address */
break; /* Done! */
}
}
/* Instruction is using an immediate address.
** The only instruction to correctly implement this addressing
** mode in extended sections is XMOVEI. Others must be simulated
** by an XMOVEI into a scratch register followed by reg-to-reg
** operation.
*/
if (p->Pop == P_MOVE || p->Pop == P_XMOVEI) {
outop(P_XMOVEI); /* Substitute XMOVEI for MOVEI */
outreg(p->Preg); /* Use given reg */
outaddress(p); /* and address */
break;
}
/* Instruction needs an XMOVEI preceding it. Barf if we
** generated a skip over this instr in the mistaken belief it
** only occupied 1 word, which means that oneinstr() was either
** wrong or wasn't called. In particular, this should never happen
** for the second of a cascaded skip.
*/
if (p->Ptype & PTF_SKIPPED)
int_error("outinstr: XMOVEI skipped");
outop(P_XMOVEI);
outreg(R_SCRREG); /* simulate by XMOVEI into scratch */
outaddress(p); /* of desired address */
outnl();
outtab();
outop(p->Pop); /* followed by real instruction */
outreg(p->Preg); /* into desired reg */
outnum(R_SCRREG); /* from scratch reg */
break;
/* PTA_PCONST requires some special checks for immediate pointer
** operands, which are distinguished by the lack of a word address
** symbol (Pptr is NULL).
** These should only be seen for TDZ, TDO, and IOR.
** TDZ uses them to mask out the P+S fields of a ptr (converting to wd ptr)
** IOR uses them to generate a byte ptr from a word ptr.
** TDOx is sometimes seen due to conversion from an IOR.
** Not clear if anything else can ever have a null word address.
*/
case PTA_PCONST:
i = 0;
if (p->Pptr == NULL) switch (p->Pop & POF_OPCODE) {
case P_TDZ: i = P_TDZ ^ P_TLZ; break; /* Change TDZ to TLZ */
case P_TDO: i = P_TDO ^ P_TLO; break; /* Change TDO to TLO */
case P_IOR: i = P_IOR ^ P_TLO; break; /* Change IOR to TLO */
}
if (i) { /* If can use immediate form, */
outop(p->Pop ^ i); /* change opcode and output it! */
outreg(p->Preg);
outpti(p->Pbsize, p->Poffset); /* Output immediate P+S sym */
} else {
outop(p->Pop); /* Normal full-word pointer constant */
outreg(p->Preg);
outc('[');
outptr(p->Pptr, p->Pbsize, p->Poffset);
outc(']');
}
break;
case PTA_BYTEPOINT:
outop(p->Pop);
outreg(p->Preg);
outc('[');
outnum(p->Pbsize); /* Output P+S field */
outstr(",,");
outaddress(p); /* Now add right half addr+offset(index) */
outc(']');
break;
case PTA_RCONST:
outop(opr = p->Pop);
big = 1; /* Assume full-word operand */
if ((popflg[opr&POF_OPCODE]&PF_EIMM) /* If op takes E as immed op */
|| !(p->Ptype & PTF_IMM)) /* or optype not marked "imm"*/
big = 0; /* then just output OP R,val */
else {
/* Op is not an E-immediate type, but is marked as "immediate".
** So either we generate an opI form,
** or make the operand into a memory literal as in OP R,[val].
*/
if ((popflg[opr&POF_OPCODE] & PF_OPI) /* If op can be opI,*/
&& (p->Pvalue &~ 0777777) == 0) { /* and operand has zero LH, */
outc('I'); /* make immediate op! */
big = 0; /* and say small operand */
}
}
outreg(p->Preg); /* Output reg and comma */
if (big) {
outc('[');
outnum(p->Pvalue);
outc(']');
}
else outnum(p->Pvalue);
break;
case PTA_FCONST:
switch(p->Pop & POF_OPCODE) {
case P_FADR: case P_FSBR: /* Single-precision operations */
case P_FMPR: case P_FDVR: /* can be optimized sometimes */
case P_MOVS:
big = bigfloat(p); /* Set flag 0 if small */
break;
case P_MOVE: /* Check for quick setup of float */
if (!(big = bigfloat(p)))
p->Pop = P_MOVS; /* MOVE R,[flt] into MOVSI R,(flt) */
break;
default: /* Always big otherwise */
big = 1;
break;
}
outop(p->Pop);
if (!big) outc('I');
outreg(p->Preg);
outc(big ? '[' : '(');
outnum(*(int *)(&p->Pfloat)); /* Pass float as int val */
outc(big ? ']' : ')');
/* Add a readable comment so humans can understand what the floating
* constant is. However, because this is slow, only do it when
* we know the assembler output is going to stay around.
*/
if(!delete) /* If keeping asm file around, */
fprintf(out,"\t; %.20g",p->Pfloat); /* add comment for humans */
break;
case PTA_DCONST1: /* Double-word floating constant */
case PTA_DCONST2:
i = ((p->Ptype)&PTF_ADRMODE) == PTA_DCONST1 ? 1 : 2;
outop(p->Pop);
outreg(p->Preg);
outc('[');
if (tgmachuse.mapdbl) /* If target mach fmt is different */
outmpdbl(&p->Pdouble, i); /* Output part of mapped double */
else
outnum(i == 1 ? p->Pdouble1 : p->Pdouble2);
outc(']');
if(!delete) /* If keeping asm file around, */
fprintf(out,"\t; %.20g", p->Pdouble); /* add comment for humans */
break;
case PTA_DCONST: /* Double-word floating constant */
outop(p->Pop);
outreg(p->Preg);
if (p->Pdouble == 0) { /* Hack: if a double zero, */
outstr(crtsnam[CRT_ZERO]); /* Use universal constant location. */
crtref[CRT_ZERO]++;
} else if (tgmachuse.mapdbl) { /* If target mach fmt is different */
outc('[');
outmpdbl(&p->Pdouble, 3); /* Output mapped double */
outc(']');
} else {
outc('[');
outnum(p->Pdouble1);
outnl(); outtab(); outtab();
outnum(p->Pdouble2);
outc(']');
}
if(!delete) /* If keeping asm file around, */
fprintf(out,"\t; %.20g",p->Pdouble); /* add comment for humans */
break;
default:
int_error("outinstr: bad adrmode %d", (p->Ptype & PTF_ADRMODE));
}
outnl(); /* Done with instruction, finish off! */
}
/* ONEINSTR(p) - See if an instruction is safe to skip over.
**
** Takes an instruction as argument and returns true if that
** instruction will expand to one machine code word.
** This routine is in CCOUT because it must accurately reflect what
** CCOUT will actually do for a given pseudo-instruction.
*/
int
oneinstr(p)
PCODE *p;
{
switch (p->Pop & POF_OPCODE) {
/* Simulated ops always expand out. */
case P_PTRCNV:
case P_SMOVE:
case P_UIDIV:
case P_UFLTR:
case P_SUBBP:
case P_DFIX:
case P_DSNGL:
return 0;
/* These instructions may or may not expand out, depending on the
** target machine. If unsupported, they are actually macros defined
** by the assembler header.
** None of them have an "immediate" form, so PTV_IINDEXED can never happen.
*/
case P_DMOVE: case P_DMOVN: case P_DMOVEM:
return tgmachuse.dmovx; /* TRUE if machine has DMOVx */
case P_ADJBP:
return tgmachuse.adjbp; /* TRUE if machine has ADJBP */
case P_DFAD: case P_DFSB: case P_DFMP: case P_DFDV:
return tgmachuse.dfl_h; /* TRUE if machine has hardware dbls */
case P_FLTR:
return tgmachuse.fixflt; /* TRUE if machine has hardware FLTR */
case P_TRN: case P_TRO: case P_TRC: case P_TRZ: /* RH tests */
case P_TLN: case P_TLO: case P_TLC: case P_TLZ: /* LH tests */
case P_CAI:
case P_LSH: case P_ASH:
return (p->Ptype != PTA_MINDEXED); /* PTV_IINDEXED for implicit immed op */
/* All other (standard) instructions.
** If target machine may use extended addressing, any
** reference to immediate object addrs (immediate MINDEXED) will
** be turned into a XMOVEI plus a register op.
*/
default: /* OPI R,addr expands to */
return (p->Ptype != PTV_IINDEXED); /* XMOVEI 16,addr ? OP r,16 */
/* Special case of above, which always wins */
case P_MOVE:
case P_XMOVEI:
return 1; /* OK even for PTV_IINDEXED */
}
}
static int
directop(op)
{
switch (op & POF_OPCODE) {
case P_CAI: return op ^ (P_CAI ^ P_CAM);
case P_TRN: return op ^ (P_TRN ^ P_TDN);
case P_TRO: return op ^ (P_TRO ^ P_TDO);
case P_TRC: return op ^ (P_TRC ^ P_TDC);
case P_TRZ: return op ^ (P_TRZ ^ P_TDZ);
default: return 0;
}
}
/* SIMUFLTR - Output expansion of P_UFLTR unsigned float "instruction".
** UFLTR R,M
** R = register to leave single-precision float in.
** M = any memory reference to single-word integer operand
**
** Note that for machines without the FLTR instruction, we don't even
** bother to get things right since the usual substitute (FSC R,233) won't
** work. Need a fancy KA-10 float routine.
**
** Expands into:
** if R == M if R != M
** SKIPGE 16,M SKIPGE R,M ; Fetch or copy the float
** LSH R,-1 LSH R,-1 ; If sign bit set, shift down
** FLTR R,R FLTR R,R ; Float the positive integer!
** CAIGE 16, SKIPGE M ; If number was shifted,
** FSC R,1 FSC R,1 ; adjust exponent accordingly.
*/
static void
simufltr(p)
PCODE *p;
{
int r = p->Preg;
int mr = ((p->Ptype&PTF_ADRMODE) == PTA_REGIS) ? p->Pr2 : 0;
p->Pop = P_SKIP+POF_ISSKIP+POS_SKPGE;
if (r == mr) p->Preg = R_SCRREG; /* 16 */
outinstr(p); /* SKIPGE R,M or SKIPGE 16,R */
fprintf(out,"\t LSH %o,-1\n", r); /* LSH R,-1 */
if (tgmachuse.fixflt) /* If have hardware FLTR */
fprintf(out,"\tFLTR %o,%o\n", r, r); /* FLTR R,R */
else
fprintf(out,"\tFSC %o,233\n", r); /* FSC R,233 (sigh) */
if (r == mr)
fprintf(out,"\tCAIGE %o,\n", R_SCRREG); /* CAIGE 16, */
else { /* or */
if (mr)
fprintf(out,"\tCAIGE %o,\n", R_SCRREG); /* CAIGE R, */
else {
outstr("\tSKIPGE "); /* SKIPGE M */
outaddress(p);
outnl();
}
}
fprintf(out,"\t FSC %o,1\n", r); /* FSC R,1 */
}
/* SIMDFIX - Output expansion of P_DFIX double-to-integer conversion "instr"
** P_DFIX reg,M
** reg = register pair, integer will be left in 1st reg.
** M = any memory ref to double-word float operand.
**
** Note that reg+1 is clobbered!
** Expands into:
** DMOVE R,M
** HLRE 16,R ;This mattered when shifts were bit-at-a-time
** ASH 16,-11 ;Get just exponent (9 bits)
** JUMPGE 16,.+3 ;Positive?
** DMOVN R,R ;No, negate, orig sign still in 1B0[A]
** ; For KA-10 format this is DFN R,R+1.
** TRC 16,777777 ;Watch for diff between twos and ones comp
** TLZ R,777000 ;Bash exponent and sign ... now positive
** ; For KA-10 format, LSH R+1,10 goes here.
** ASHC R,-233(16) ;Make an integer (may overflow)
** CAIGE 16, ;Original negative? Check its sign.
** MOVN R,R ;Yup, negate result.
*/
static void
simdfix(p)
PCODE *p;
{
int r = p->Preg;
/* Check for DFIX R,R and skip the DMOVE if that's what we have */
if ((p->Ptype&PTF_ADRMODE) != PTA_REGIS || r != p->Pr2) {
p->Pop = P_DMOVE; /* Make DMOVE R,M to get double */
outinstr(p);
}
fprintf(out, "\tHLRE 16,%o\n\tASH 16,-11\n\tJUMPGE 16,.+3\n", r);
if (tgmachuse.dfl_s) /* Use right kind of double negation */
fprintf(out, "\tDFN %o,%o\n", r, r+1);
else fprintf(out, "\tDMOVN %o,%o\n", r, r);
outstr("\tTRC 16,-1\n");
fprintf(out, "\tTLZ %o,777000\n", r);
if (tgmachuse.dfl_s) /* Account for exponent in 2nd wd */
fprintf(out, "\tLSH %o,10\n", r+1);
fprintf(out, "\tASHC %o,-233(16)\n", r);
outstr("\tCAIGE 16,\n"); /* Check sign bit of original # */
fprintf(out, "\t MOVNS %o\n", r); /* Negate result */
}
/* SIMDSNGL - Output expansion of P_DSNGL double-to-float conversion "instr"
** P_DSNGL reg,M
** reg = register pair, float will be left in 1st reg.
** M = any memory ref to double-word float operand.
**
** Note that reg+1 is clobbered!
** Expands into:
** DMOVE R,M ; Get double value
** SKIPGE 16,R ; Check sign, save indicator
** DMOVN R,R ; Negative, make positive temporarily.
** TLNE R+1,200000 ; If low word >= .5
** TRON R,1 ; then must round high wd, try fast hack
** JRST .+4 ; Won!
** MOVE R+1,R ; Ugh, have to do it hard way. Copy high wd
** AND R+1,[777000000001] ; Zap all but exp and low-order bit
** FADR R,R+1 ; Add low bit to effect rounding
** CAIGE 16, ; Now if original was negative,
** MOVN R,R ; make it negative again.
*/
static void
simdsngl(p)
PCODE *p;
{
int r = p->Preg;
/* Check for DSNGL R,R and skip the DMOVE if that's what we have */
if ((p->Ptype&PTF_ADRMODE) != PTA_REGIS || r != p->Pr2) {
p->Pop = P_DMOVE; /* Make DMOVE R,M to get double */
outinstr(p);
}
fprintf(out, "\tSKIPGE 16,%o\n", r);
if (tgmachuse.dfl_s) { /* Use right kind of double negation */
fprintf(out, "\t DFN %o,%o\n", r, r+1);
fprintf(out, "\tTLNE %o,400\n", r+1);
} else {
fprintf(out, "\t DMOVN %o,%o\n", r, r);
fprintf(out, "\tTLNE %o,200000\n", r+1);
}
fprintf(out, "\t TRON %o,1\n\t JRST .+4\n", r);
fprintf(out, "\tMOVE %o,%o\n\tAND %o,[777000,,1]\n\tFADR %o,%o\n",
r+1, r,
r+1,
r, r+1);
outstr("\tCAIGE 16,\n"); /* Check sign bit of original # */
fprintf(out, "\t MOVNS %o\n", r); /* Negate result */
}
/* SIMSUBBP - Output expansion of P_SUBBP byte-pointer subtraction "instr".
** P_SUBBP reg,M [plus Pbsize set to bytesize if known]
** reg = register pair, 1st reg contains minuend BP
** M = any memory ref to subtrahend BP
** Pbsize = bytesize of pointers (> 0 if known)
** This is currently only used if M operand is P_CONST.
** Leaves resulting # in 2nd reg!
**
** This makes use of 2 special symbols from CPU.C and
** some tables in CRT.C, and expands into:
** Known size Unknown size
** LDB 16,[$$BPSZ,,R] ; get PS from R
** SUB R,M SUB R,M
** MULI R,$$BMPn MUL R,$BPMUL(16)
** ASH R+1,$$BSHF ASH R+1,$$BSHF
** ADD R,$BPADT(16)
** ADD R+1,$BPADn(A) ADD R+1,(A)
*/
static void
simsubbp(p)
PCODE *p;
{
int siz, typ, tbidx;
if ((typ = (p->Ptype&PTF_ADRMODE)) == PTA_PCONST)
siz = p->Pbsize; /* Aha, size is known! */
else siz = 0;
switch (siz) {
case 6: tbidx = 0; break;
case 7: tbidx = 1; break;
case 8: tbidx = 2; break;
case 9: tbidx = 3; break;
case 18: tbidx = 4; break;
default:
int_error("simsubbp: bad Pbsize: %d", siz);
siz = 0;
case 0:
fprintf(out, "\tLDB 16,[%s,,%o]\n", crtsnam[CRT_BPSZ], p->Preg);
crtref[CRT_BPSZ]++;
}
/* Simple check to verify addressing mode is OK */
switch (typ) {
case PTA_PCONST:
case PTA_REGIS:
case PTA_MINDEXED:
break;
default:
int_error("simsubbp: bad adrmode: %d", typ);
}
p->Pop = P_SUB; /* Make SUB R,M */
outinstr(p);
if (siz) {
fprintf(out, "\tMULI %o,%s\n", p->Preg, crtsnam[CRT_BMP6+tbidx]);
crtref[CRT_BMP6+tbidx]++;
} else {
fprintf(out, "\tMUL %o,%s(16)\n", p->Preg, crtsnam[CRT_BPMUL]);
crtref[CRT_BPMUL]++;
}
fprintf(out, "\tASH %o,-%s\n", p->Preg+1, crtsnam[CRT_BSHF]);
crtref[CRT_BSHF]++;
if (!siz) {
fprintf(out, "\tADD %o,%s(16)\n", p->Preg, crtsnam[CRT_BPADT]);
crtref[CRT_BPADT]++;
fprintf(out, "\tADD %o,(%o)\n", p->Preg+1, p->Preg);
} else {
fprintf(out, "\tADD %o,%s(%o)\n",
p->Preg+1, crtsnam[CRT_BPAD6+tbidx], p->Preg);
crtref[CRT_BPAD6+tbidx]++;
}
}
/*
** NOTE:
** Byte pointer comparison used to be simulated too but now is done
** with explicit instructions by CCGEN2.
*/
/* NOTE:
** Some PDP-10 architecture machines have no ADJBP instruction.
** We used to expand a sequence for P_ADJBP similar to that for
** P_SUBBP, but now we always just output P_ADJBP
** as ADJBP R,X and it is up to the C-HDR preamble file to redefine
** ADJBP as a macro (in a form similar to the above) if the machine
** does not support ADJBP. This allows machine-language library routines
** to use ADJBP in their code.
*/
/* SIMPTRCNV - Output expansion of P_PTRCNV pointer conversion "instruction".
** P_PTRCNV reg,offset [plus Pbsize set to desired bytesize]
** reg = register containing pointer to convert
** offset = bytesize of old pointer
** Pbsize = desired bytesize of new pointer
**
** Currently this only supports 9<->18 bit conversions, and the
** code generator shouldn't give us anything else.
** The interaction between the special instruction symbols defined in the
** library $$$CPU module and the code here has been carefully arranged.
** In particular the instructions must take up the same amount of space
** whether being loaded for zero-section or multi-section operation, and
** they must NOT change the value of a NULL (zero) pointer.
**
** The conversions to and from word pointers are done by the code
** generator (with TDZ/TLZ or SKIPE+IOR/TLO) because it may be possible
** for the peephole optimizer to do something with those instructions.
*/
static void
simptrcnv(p)
PCODE *p;
{
int *ip;
static int cvH_9[] = {CRT_PH90, CRT_PH91, 0};
static int cv9_H[] = {CRT_P9H0, CRT_P9H1, CRT_P9H2, 0};
/* Switch depending on original size plus desired size */
switch (TGSIZ_WORD* p->Poffset + p->Pbsize) {
case TGSIZ_WORD* 9 + 18: /* 9-bit to 18-bit */
if (!tgmachuse.exadr) { /* Coding for local-section only? */
fprintf(out, "\tTLZE %o,117700\n", p->Preg);
fprintf(out, "\t TLO %o,002200\n", p->Preg);
return;
}
fprintf(out,"\tJUMPE %o,.+4\n", p->Preg); /* Skip if ptr NULL */
ip = cv9_H;
break;
case TGSIZ_WORD* 18 + 9: /* 18-bit to 9-bit */
if (!tgmachuse.exadr) { /* Coding for local-section only? */
fprintf(out, "\tTLZE %o,007700\n", p->Preg);
fprintf(out, "\t TLO %o,111100\n", p->Preg);
return;
}
ip = cvH_9;
break;
default:
int_error("simptrcnv: bad bsize: %d", p->Pbsize);
return;
}
/* Wrap up with special instructions the value of which depends
** on whether the code is being loaded for a zero-section or
** multi-section program. The specified register is added into
** the symbols in such a way that it fits into the AC field.
*/
for (; *ip; ++ip) {
fprintf(out,"\t%s+<%o>\n", crtsnam[*ip], p->Preg<<23);
++crtref[*ip];
}
}
/* SIMSMOVE - Output expansion of P_SMOVE structure copy "instruction".
** P_SMOVE reg,addr+offset(idx) [plus Pbsize set to # words]
** reg = register containing destination word address
** addr+offset(idx) = source address
** Pbsize = # words to copy
*/
static void
simsmove(p)
PCODE *p;
{
int size;
if ((p->Ptype&PTF_ADRMODE) != PTA_MINDEXED) {
int_error("simsmove: bad adrmode: %o", p->Ptype);
return;
}
switch (size = p->Pbsize) {
case 1: case 2: case 3:
fprintf(out,"\tXMOVEI 16,-1(%o)\n", p->Preg); /* Get dest <addr-1> */
while (--size >= 0) {
outstr("\tPUSH 16,");
outaddress(p);
outnl();
++(p->Poffset);
}
return;
default:
if (size <= 0) {
int_error("simsmove: bad size %d", size);
return;
}
/* Code to do local-section BLT */
if (tgmachuse.exadr) /* If mach has ext addressing */
outstr("\tJUMPGE 17,.+5\n"); /* Must check at runtime. */
fprintf(out,"\tMOVEI 16,(%o)\n\tHRLI 16,",p->Preg);
outaddress(p); /* for HRLI 16,<source-addr> */
fprintf(out,"\n\tBLT 16,%o(%o)\n", size-1, p->Preg);
/* Code to do global-section XBLT */
if (!tgmachuse.exadr) /* If mach doesn't have ext addressing */
return; /* then that's all! */
/* There is so much setup overhead that we check the size and
** use PUSHes if there are less than 10 words, which seems to
** be the breakeven point (paper calculation).
*/
if (size < 10) {
fprintf(out,"\tJRST .+%o\n", size+2);
fprintf(out,"\tXMOVEI 16,-1(%o)\n", p->Preg); /* dest <addr-1> */
while (--size >= 0) {
outstr("\tPUSH 16,");
outaddress(p);
outnl();
++(p->Poffset);
}
return;
}
/* Too big for cleverness, just spit out the XBLT code */
fprintf(out,"\tJRST .+11\n\tPUSH 17,14\n\tPUSH 17,15\n");
if (p->Pindex == R_SP) /* Must adjust stack reference? */
p->Poffset -= 2; /* Yeah, account for our 2 PUSHes */
if (size <= 0777777)
fprintf(out,"\tMOVEI 14,%o\n", size);
else fprintf(out,"\tMOVE 14,[%o]\n", size);
outstr("\tXMOVEI 15,"); outaddress(p);
fprintf(out,"\n\tMOVE 16,%o\n\tEXTEND 14,[XBLT]\n", p->Preg);
outstr("\tPOP 17,15\n\tPOP 17,14\n");
}
}
/* SIMUIDIV - Output expansion of P_UIDIV unsigned division "instruction".
** P_UIDIV reg,<addr>
** reg = register containing dividend (two-word register)
** addr = divisor
** This sequence was derived from one suggested by Peter Samson
** at Systems Concepts.
*/
static void
simuidiv(p)
PCODE *p;
{
register int rq = p->Preg; /* RQ - Quotient register */
register int rr = rq+1; /* RR - Remainder register, RQ+1 */
register int divisor;
/* First, try to use optimized sequences if the divisor is a constant. */
if ((p->Ptype&PTF_ADRMODE) == PTA_RCONST) {
divisor = p->Pvalue;
if (divisor == 1 || divisor == 0) { /* Div by 1 (or 0??) */
fprintf(out,"\tSETZ %o,\n", rr); /* just clears rem */
return;
}
if ((divisor & (divisor-1)) == 0) { /* If divisor is power of 2 */
fprintf(out,"\tLSHC %o,-%o\n", rq, binexp(divisor));
fprintf(out,"\tLSH %o,-%o\n",rr, (TGSIZ_WORD - binexp(divisor)));
return;
}
if (divisor > 0) { /* High bit not set? */
fprintf(out, "\tSKIPL %o,%o\n", rr, rq);
fprintf(out, "\t TDZA %o,%o\n", rq, rq);
fprintf(out, "\t MOVEI %o,1\n", rq);
if (!(p->Pvalue & ~0777777)) /* Constant fits in RH? */
fprintf(out, "\tDIVI %o,%o\n", rq, divisor);
else fprintf(out, "\tDIV %o,[%o]\n", rq, divisor);
return;
}
/* Constant divisor has high bit set, ugh!
** This case is pretty unlikely so don't bother optimizing it.
** Just drop through to general-purpose division algorithm.
*/
p->Ptype |= PTF_IMM; /* Ensure outinstr checks bigness */
}
/* Sigh, cannot avoid doing full-fledged hairy unsigned division.
** Output entire algorithm -- see following comments for more explanation.
** Note that divisor is fetched into reg 16 immediately to avoid any
** addressing conflicts with regs RQ or RQ+1. We temporarily set
** Preg to this scratch reg so that we can take advantage of outinstr()
** and have it use the right register.
*/
p->Pop = P_SKIP+POF_ISSKIP+POS_SKPGE; /* Modify instr to fake out */
p->Preg = R_SCRREG; /* call to outinstr() */
outinstr(p); /* SKIPGE 16,MEM Get divisor */
outstr( "\t JRST .+10\n"); /* JRST $1 if divisor neg */
fprintf(out,"\tJUMPGE %o,.+17\n", rq); /* JUMPGE RQ,$3 Both +? Win! */
/* Divisor is positive, but dividend isn't.
** Must check for special case of 1, which leaves high-order (sign) bit
** still set! (All other values zero it). Might as well include 0 here.
*/
fprintf(out,"\tCAIG %o,1\n", R_SCRREG); /* CAIG 16,1 Check divisor */
outstr( "\t JRST .+14\n"); /* JRST $2 if 0 or 1 */
/* Dividend is neg (has high bit set), divisor doesn't.
** We know that divisor is at least 2 so the quotient will always
** lose at least 1 high bit and thus we can win by doing a DIV without
** any fixup. The DIV is needed rather than IDIV because we have to
** divide a 2-word value; the high bit becomes the low bit of the
** high-order word.
*/
fprintf(out,"\tMOVE %o,%o\n", rr, rq); /* MOVE RR,RQ Set up */
fprintf(out,"\tMOVEI %o,1\n", rq); /* MOVEI RQ,1 Get 1 ? dvdend */
fprintf(out,"\tDIV %o,%o\n", rq, R_SCRREG); /* DIV RQ,16 Do the div! */
outstr( "\tJRST .+12\n"); /* JRST $4 Done! */
/* Label $1: Divisor is negative (high bit is set) */
/* Because divisor's high bit is set, there's no way the dividend
** can be more than twice the magnitude of the divisor. So the
** quotient must be either 0 or 1, with the remainder being respectively
** either the dividend or the dividend less 1 times the divisor.
*/
fprintf(out,"\tMOVE %o,%o\n", rr, rq); /* MOVE RR,RQ Make dblwd */
fprintf(out,"\tMOVEI %o,0\n", rq); /* MOVEI RQ,0 with high wd 0 */
fprintf(out,"\tJUMPGE %o,.+7\n", rr); /* JUMPGE RR,$4 Maybe done */
fprintf(out,"\tCAMGE %o,%o\n",rr,R_SCRREG); /* CAMGE RR,16 */
fprintf(out,"\t JRST .+5\n"); /* JRST $4 */
fprintf(out,"\tSUB %o,%o\n", rr, R_SCRREG); /* SUB RR,16 */
fprintf(out,"\tAOJA %o,.+3\n", rq); /* AOJA RQ,$4 */
/* Label $2: Divisor is 0 or 1, dividend is neg */
fprintf(out,"\tTDZA %o,%o\n", rr, rr); /* TDZA RR,RR Clear rem */
/* and skip next instr */
/* Label $3: Divisor and dividend both positive */
fprintf(out,"\t IDIV %o,%o\n",rq,R_SCRREG); /* IDIV RQ,16 */
/* Label $4: Done! */
}
/* OUTOP - Emit opcode and condition-test fields
**
*/
static void
outop(opr)
{
if (opr == 0) int_error("outop: null op");
outstr(popostr[opr & POF_OPCODE]); /* Output assembler opcode mnemonic */
switch (opr & POF_OPSKIP) {
case POS_SKPA: outc('A'); break;
case POS_SKPE: outc('E'); break;
case POS_SKPN: outc('N'); break;
case POS_SKPL: outc('L'); break;
case POS_SKPG: outc('G'); break;
case POS_SKPLE: outstr("LE"); break;
case POS_SKPGE: outstr("GE"); break;
}
if (opr & POF_BOTH) switch (opr) {
case P_MOVN+POF_BOTH:
case P_MOVM+POF_BOTH: outc('S'); break;
default: outc('B'); break;
}
}
/* OUTREG - Output register field
*/
static void
outreg(n)
{
outc(' '); /* Separate from opcode with space */
if (n > 0) {
outnum(n);
outc(',');
}
}
/* OUTADDRESS - Output address (E) field
*/
static void
outaddress(p)
PCODE *p;
{
if (p->Ptype & PTF_IND) outc('@'); /* if indirect, say so with atsign */
if (p->Pptr != NULL) { /* now right half: */
outmiref(p->Pptr); /* symbol */
if (p->Poffset) { /* with offset */
/* Do trick here of multiplying by 1. This is only needed for
** FAIL to force it into making Polish, to avoid wrong-seg
** relocation bug (see FAIL manual doc for the TWOSEG pseudo).
** MACRO and MIDAS aren't bothered by it.
*/
if (tgasm == TGASM_FAIL)
outstr("*1"); /* Hack hack */
if (p->Poffset > 0) outc ('+');
outnum(p->Poffset);
}
} else outnum(p->Poffset); /* no sym, just give offset */
if (p->Pindex) { /* now output index register */
if (p->Pindex == R_SP /* Simple check - if stack index */
&& p->Poffset > 0 /* ensure it points to valid stuff */
&& (p->Ptype&PTF_IMM)==0) /* or if not, that we only want addr */
/* Else making bad stack reference! */
int_error("outaddress: + stk offset");
outc('(');
outnum(p->Pindex);
outc(')');
}
}
/* OUTPTI - output immediate pointer operand.
** This is used for TLZ and TLO.
** We already know that the instruction's operand is a PTA_PCONST
** with no symbol, so the RH is zero and only the LH is needed.
** Note $$SECT should not be used as we are masking BP position bits rather
** than address bits. Error if byte offset is so large that a word offset
** becomes necessary.
*/
static void
outpti(bsize, offset)
int bsize; /* Bytesize of pointer (0 for word ptr is illegal) */
int offset; /* # bytes offset from start of word */
{
int i, woff = 0;
if ((i = obplh(offset, &woff, bsize)) == 0
|| woff) {
int_error("outpti: bad args");
i = CRT_BPPS;
}
outstr(crtsnam[i]); /* Output byte pointer bits */
++crtref[i];
}
/* OUTPTR - output pointer value, for word or byte pointer.
** sym - address identifier (if non-NULL)
** offset - offset in bytes from given address
** bsize - size of bytes in bits.
** 0 = word pointer, no P+S in left half.
** If the address identifier is NULL, no $$SECT is output for the LH.
** This could be used to create absolute pointers.
*/
void
outptr(sym, bsize, offset)
SYMBOL *sym; /* Identifier to point at */
int bsize; /* Bytesize of pointer (0 = word) */
int offset; /* # units offset from identifier */
{
register int i = 0;
if (bsize && (i = obplh(offset, &offset, bsize))) {
outstr(crtsnam[i]); /* Output byte pointer bits */
++crtref[i];
}
/* Now do rest of pointer (the word address) */
if (sym) {
if (i) outstr("+"); /* Combine with P+S if any */
outstr(crtsnam[CRT_SECT]); /* Make global word address */
++crtref[CRT_SECT];
}
outstr(",,");
if (sym) {
outmiref(sym); /* Either internal static, or normal extern */
} else outstr("0");
if (offset) { /* If non-zero word offset, */
outstr("*1"); /* See outaddress() for crock explanation */
if (offset > 0) outc('+'); /* Add or subtract it */
outnum(offset); /* note outnum prefixes '-' if negative */
}
}
/* OBPLH - Find BP left-half value
** If bsize = -1, use P+S mask. This is meaningless for most all
** ops except P_TDZ or P_TLZ, however.
*/
static int
obplh(boff, awoff, bsize)
int boff; /* Byte offset */
int *awoff; /* Addr of place to return word-offset to */
int bsize; /* Byte size (0 if word) */
{
switch (bsize) {
default:
int_error("obplh: bad bsize: %d", bsize);
case -1: /* Return P+S mask */
return CRT_BPPS;
case 0: /* Word pointer */
return 0;
case 6:
return CRT_BP60 + adjboffset(boff, awoff, 6);
case 7:
return CRT_BP70 + adjboffset(boff, awoff, 5);
case 8:
return CRT_BP80 + adjboffset(boff, awoff, 4);
case 9:
return CRT_BP90 + adjboffset(boff, awoff, 4);
case 18:
return CRT_BPH0 + adjboffset(boff, awoff, 2);
}
}
/* ADJBOFFSET - Adjust byte offset. Auxiliary for obplh() and foldadjbp().
*/
int
adjboffset(boff, awoff, bpw)
int boff; /* Byte offset to adjust */
int *awoff; /* Place to deposit word offset */
int bpw; /* # bytes per word */
{
if (boff < 0) {
/* Negative increment, need to go back far enough
** that boff can become a positive offset within a word.
*/
*awoff = -((-boff) / bpw); /* Find -# words */
if (boff = (-boff) % bpw) { /* If any bytes, */
(*awoff)--; /* must bump back 1 more word */
return bpw - boff; /* and return positive offset. */
} else return 0;
} else { /* Positive increment, simple. */
*awoff = boff / bpw; /* Find # words */
return boff %= bpw; /* and remaining # bytes */
}
}
/* Floating-point auxiliary functions */
/* OUTFLT - Output floating-point constant value.
** Handles float, double, long double.
** If no flags given, assumes outputting all of value as a static data
** constant (used by CCGEN), else a code literal constant.
** Returns the number of words emitted.
*/
#define OF_CONST 0100 /* Code literal constant */
#define OF_WD1 01 /* Emit word 1 only */
#define OF_WD2 02 /* Emit word 2 only */
int
outflt(typ, ptr, flags)
int typ; /* Tspec of value */
int *ptr; /* Generic pointer to object */
int flags;
{
if (!flags) flags = OF_WD1|OF_WD2;
if (flags & OF_CONST)
outc('['); /* Set up as literal constant ] */
else outtab();
if (typ == TS_FLOAT) {
outpnum(*ptr);
typ = 1;
} else if (tgmachuse.mapdbl) { /* If target mach fmt is different */
outmpdbl(ptr, 3); /* output mapped double */
typ = 2;
} else {
outpnum(ptr[0]);
outnl();
outtab();
if (flags & OF_CONST)
outtab();
outpnum(ptr[1]);
typ = 2;
}
if (flags & OF_CONST)
outc(']');
/* Add a readable comment so humans can understand what the floating
** constant is. However, because this is slow, only do it when
** we know the assembler output is going to stay around.
*/
if (!delete) /* If keeping asm file around, add comment */
fprintf(out, "\t; %.20g", *(double *)ptr);
if (!(flags & OF_CONST))
outnl();
return typ; /* Return # wds emitted */
}
/* OUTMPDBL - Output mapped double-format constant
** This is also used by CCGEN for data.
*/
void
outmpdbl(ip, which)
int *ip;
int which; /* 1 = 1st wd, 2 = 2nd wd, 3 = both wds */
{
int second; /* 2nd word is the different one */
int exp; /* Gotta derive new exponent */
if (second = ip[1]) /* Only if low order word is non-zero */
switch (tgmachuse.mapdbl) {
case 1: /* Internal format is hardware, output in software format */
exp = (*ip < 0 ? -*ip : *ip) & ~((1<<27)-1); /* Mask off pos exp */
second = (((unsigned)second) >> 8) | (exp - (27<<27));
break;
case -1: /* Internal format is software, output in hardware format */
second = (second << 8) & ~(1<<35); /* Just flush 2nd exp! */
break;
default:
int_error("outmpdbl: bad map");
}
if (which&01) outpnum(*ip); /* 1st wd always output as is */
if (which==03) outstr("\n\t\t");
if (which&02) outpnum(second); /* 2nd word has been mapped */
}
/* BINEXP - count zero bits to right of rightmost 1 in word.
** Used for converting a power-of-2 value into a shift count.
** Also called by CCEVAL for constant folding.
*/
int
binexp(n)
unsigned int n;
{
int e;
e = -1; /* init count of bits to shift */
do {
n >>= 1; /* logical shift over one */
e++; /* and count a zero */
} while (n != 0); /* until that was the last bit */
return e; /* return number of bits */
}
/* FLTPOW2(d) - See if arg is positive and a power of 2
* Returns zero if not, else non-zero integer exponent.
* Very machine-dependent, only works for standard single-precision
* PDP-10 floating point format, and assumes a normalized number.
* If the number is a power of 2, only bit 9 will be set in
* the mantissa (the rest will be 0) and the exponent field (bits 1-8)
* can tell us what we want to know - it is in excess 128 code, so
* that an exponent X for the fractional mantissa is represented by X+128.
* Subtracting 129 then gives us the power of 2 for the integer.
* Minor screw: 1.0 (2 to the 0th) returns 0 which is the same as
* an error return. However, the two cases of using 0.0 or 1.0 as operand
* should have been caught earlier in optimization.
*/
static int
fltpow2(d)
double d;
{
if (d > 0.0 && ((*(int *)(&d)) & 03777777777) == 0)
return ((unsigned)(*(int *)(&d)) >> 27) - 129;
return 0;
}
/* BIGFLOAT - See whether 1st word of a floating-point number is too big
** to fit into a halfword (i.e. RH has some non-zero bits).
** Note this only checks floats, not doubles!
*/
static int
bigfloat(p)
PCODE *p;
{
return(((*(int *)(&p->Pfloat))
& 0777777) != 0); /* If 1st wd RH has bits, big. */
}
/* OUTSCON - Output string constant.
** Used to always produce an ASCIZ string, but now uses
** whatever bytesize is specified.
** The major hack to be noted here is that 6-bit characters are
** converted to PDP-10 SIXBIT!
*/
void
outscon(s, l, bsiz)
char *s; /* Char string */
int l; /* Length (may include nulls!) */
int bsiz; /* Byte size to use. */
{
int i, sepchar = ',';
char *opstr = "BYTE\t(%d) ";
if (tgasm == TGASM_MIDAS)
sepchar = '?', opstr = ".BYTE %d ? ";
--s; /* Set up for preincrement */
while (l > 0) { /* For each word */
fprintf(out, opstr, bsiz); /* Start bytes of given size */
for (i = (TGSIZ_WORD/bsiz);;) {
if (bsiz == 6) outnum(*++s ? (toupper(*s)-040) : 0);
else outnum(*++s); /* output char value */
if (--l <= 0) break;
if (--i <= 0) break;
outc(sepchar);
}
outstr("\n\t");
}
if (tgasm == TGASM_MIDAS) outstr(" ? .BYTE");
outnl();
}
/* OUTLAB - Output label
** Note: the ==. syntax is used for local labels (without linkage)
** because that is
** acceptable to all of FAIL, MACRO, and MIDAS. The "proper" half-killed label
** syntax for MACRO is "foo:!", but it is "foo::" for the others.
** We cleverly avoid the issue with ==.
*/
void
outlab(s)
SYMBOL *s;
{
outid(s->Sname); /* Output the actual label name */
if (s->Sname[0] == '$') /* Local label? */
outstr("==.\n"); /* Yes, define it as half-killed. See note. */
else outstr(":\n"); /* No, normal label. */
}
/* OUTID - Output identifier to assembler file
** Does minor mapping.
*/
void
outid(s)
char *s;
{
register int n, ch;
n = 6; /* Max # chars to output */
if ((ch = *s) == SPC_IDQUOT) /* Handle quoted identifier syms */
ch = *++s;
while (ch) {
if (ch == '_') ch = '.';
putc(ch, out);
if (--n <= 0) break;
ch = *++s;
}
}
#if SYS_CSI
/* OUTPROLOG - Output Profiler call from Function Prolog
** added 09/15/89 by MVS
*/
void
outprolog(s)
SYMBOL *s;
{
fputs("\tPROF.\t0,", out);
outmiref(s);
}
/* OUTEPILOG - Output Profiler call from Function Epilog
** added 09/15/89 by MVS
*/
void
outepilog(s)
SYMBOL *s;
{
fputs("\tPROF.\t1,", out);
outmiref(s);
putc('\n', out);
}
#endif
/* OUTMIDEF - Output Mapped Identifier Definition
*/
void
outmidef(s)
SYMBOL *s;
{
outmiref(s);
putc(':', out);
}
/* OUTMIREF - Output Mapped Identifier Reference
*/
void
outmiref(s)
SYMBOL *s;
{
switch (s->Sclass) {
case SC_ISTATIC: outid(s->Ssym->Sname); break;
case SC_LABEL: case SC_ULABEL: case SC_ILABEL:
outid(s->Sname); break;
default:
if (s->Smaplab) outsix(s->Smaplab); /* Output SIXBIT */
else int_error("outmiref: no map for \"%s\"", s->Sname);
}
}
/* Low-level assembler file output functions */
/* OUTSTR - Output string
*/
void
outstr(s)
char *s;
{
if (*s) {
do putc(*s, out);
while (*++s);
}
}
/* OUTC - Output character
** OUTNL - Output NewLine
** OUTTAB - Output Tab
*/
void outc(c) { putc(c, out); }
void outnl() { putc('\n', out); }
void outtab(){ putc('\t', out); }
/* OUTNUM - Output value as a signed octal number (with minus sign if negative)
*/
void
outnum(n)
{
if (n < 0) {
n = -n;
putc('-', out);
}
if (n &~ 07) outpnum((unsigned) n >> 3);
putc((n & 07) + '0', out);
}
/* OUTPNUM - Output value as a positive unsigned octal number
*/
void
outpnum(n)
unsigned n;
{
if (n &~ 07) outpnum(n >> 3);
putc((n & 07) + '0', out);
}
/* OUTSIX - Output SIXBIT word, ignoring trailing blanks
*/
static void outrj6();
void
outsix(wd)
unsigned wd;
{
while (!(wd & 077)) wd >>= 6; /* Right-justify the sixbit */
outrj6(wd);
}
static void
outrj6(ms)
unsigned ms;
{
int ch6;
ch6 = (ms&077) + ' ';
if (ms >>= 6) outrj6(ms);
putc(ch6, out);
}