mirror of
https://github.com/nzp-team/fteqw.git
synced 2024-11-27 14:12:25 +00:00
d71843d2e4
git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@3018 fc73d0e0-1445-4013-8a0c-d673dee63da5
1233 lines
26 KiB
C
1233 lines
26 KiB
C
/*
|
|
===========================================================================
|
|
Copyright (C) 1999-2005 Id Software, Inc.
|
|
|
|
This file is part of the fteqw tools.
|
|
|
|
FTEQW and the Quake III Arena source code are free software; you can redistribute them
|
|
and/or modify them under the terms of the GNU General Public License as
|
|
published by the Free Software Foundation; either version 2 of the License,
|
|
or (at your option) any later version.
|
|
|
|
FTEQW and the Quake III Arena source code are distributed in the hope that it will be
|
|
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with Foobar; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
===========================================================================
|
|
*/
|
|
|
|
/*
|
|
Copyright (C) 2007 David Walton
|
|
|
|
GPL.
|
|
|
|
This file is the core of an assembler/linker to generate q3-compatable qvm files. It is based on the version in the Q3 source code, but rewritten from the ground up.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <stdarg.h>
|
|
#include "../engine/qclib/hash.h"
|
|
|
|
|
|
//from qfiles.h
|
|
#define VM_MAGIC 0x12721444
|
|
struct vmheader {
|
|
int vmMagic;
|
|
|
|
int instructionCount;
|
|
|
|
int codeOffset;
|
|
int codeLength;
|
|
|
|
int dataOffset; // data then lit are here
|
|
int dataLength;
|
|
int litLength; // ( dataLength - litLength ) should be byteswapped on load
|
|
int bssLength; // zero filled memory appended to datalength
|
|
};
|
|
//end of qfiles.h
|
|
|
|
|
|
|
|
typedef enum {
|
|
OP_UNDEF,
|
|
|
|
OP_IGNORE,
|
|
|
|
OP_BREAK,
|
|
|
|
OP_ENTER,
|
|
OP_LEAVE,
|
|
OP_CALL,
|
|
OP_PUSH,
|
|
OP_POP,
|
|
|
|
OP_CONST,
|
|
OP_LOCAL,
|
|
|
|
OP_JUMP,
|
|
|
|
//-------------------
|
|
|
|
OP_EQ,
|
|
OP_NE,
|
|
|
|
OP_LTI,
|
|
OP_LEI,
|
|
OP_GTI,
|
|
OP_GEI,
|
|
|
|
OP_LTU,
|
|
OP_LEU,
|
|
OP_GTU,
|
|
OP_GEU,
|
|
|
|
OP_EQF,
|
|
OP_NEF,
|
|
|
|
OP_LTF,
|
|
OP_LEF,
|
|
OP_GTF,
|
|
OP_GEF,
|
|
|
|
//-------------------
|
|
|
|
OP_LOAD1,
|
|
OP_LOAD2,
|
|
OP_LOAD4,
|
|
OP_STORE1,
|
|
OP_STORE2,
|
|
OP_STORE4, // *(stack[top-1]) = stack[yop
|
|
OP_ARG,
|
|
OP_BLOCK_COPY,
|
|
|
|
//-------------------
|
|
|
|
OP_SEX8,
|
|
OP_SEX16,
|
|
|
|
OP_NEGI,
|
|
OP_ADD,
|
|
OP_SUB,
|
|
OP_DIVI,
|
|
OP_DIVU,
|
|
OP_MODI,
|
|
OP_MODU,
|
|
OP_MULI,
|
|
OP_MULU,
|
|
|
|
OP_BAND,
|
|
OP_BOR,
|
|
OP_BXOR,
|
|
OP_BCOM,
|
|
|
|
OP_LSH,
|
|
OP_RSHI,
|
|
OP_RSHU,
|
|
|
|
OP_NEGF,
|
|
OP_ADDF,
|
|
OP_SUBF,
|
|
OP_DIVF,
|
|
OP_MULF,
|
|
|
|
OP_CVIF,
|
|
OP_CVFI
|
|
} opcode_t;
|
|
|
|
|
|
typedef struct {
|
|
char *name;
|
|
int opcode;
|
|
} sourceOps_t;
|
|
|
|
sourceOps_t sourceOps[] = {
|
|
#include "opstrings.h"
|
|
};
|
|
|
|
|
|
enum segs {
|
|
SEG_CODE,
|
|
SEG_DATA,
|
|
SEG_LIT,
|
|
SEG_BSS,
|
|
|
|
SEG_MAX,
|
|
SEG_UNKNOWN
|
|
};
|
|
|
|
#define SYMBOLBUCKETS 4096
|
|
#define LOCALSYMBOLBUCKETS 1024
|
|
#define USEDATBLOCK 64
|
|
#define STACKSIZE 0x10000
|
|
|
|
#ifdef WIN32
|
|
#define snprintf _snprintf
|
|
#endif
|
|
|
|
struct usedat {
|
|
struct usedat *next;
|
|
int count;
|
|
|
|
char seg[USEDATBLOCK];
|
|
int offset[USEDATBLOCK];
|
|
};
|
|
|
|
struct symbol {
|
|
struct symbol *next;
|
|
|
|
int inseg; //this is where the actual value is defined
|
|
int atoffset;
|
|
|
|
struct usedat usedat; //this is where it is used
|
|
//to help with cpu cache, the first is embeded
|
|
|
|
bucket_t bucket; //for the hash tables
|
|
|
|
char name[1]; //a common C hack
|
|
};
|
|
|
|
struct segment {
|
|
void *data;
|
|
unsigned int base;
|
|
unsigned int length;
|
|
unsigned int available; //before it needs a realloc
|
|
|
|
int isdummy; //set if there's no real data assosiated with this segment
|
|
};
|
|
|
|
struct assembler {
|
|
struct segment segs[SEG_MAX];
|
|
struct segment *curseg;
|
|
|
|
struct symbol *symbols;
|
|
|
|
hashtable_t symboltable;
|
|
bucket_t symboltablebuckets[SYMBOLBUCKETS];
|
|
|
|
hashtable_t localsymboltable;
|
|
bucket_t localsymboltablebuckets[LOCALSYMBOLBUCKETS];
|
|
|
|
struct symbol *lastsym; //so we can move symbols that lcc put into the wrong place.
|
|
|
|
int numinstructions;
|
|
|
|
int errorcount;
|
|
|
|
int funclocals;
|
|
int funcargs;
|
|
int funcargoffset;
|
|
|
|
int verbose;
|
|
};
|
|
|
|
struct workload {
|
|
char output[256];
|
|
|
|
int numsrcfiles;
|
|
char **srcfilelist;
|
|
|
|
int verbose;
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void EmitData(struct segment *seg, void *data, int bytes)
|
|
{
|
|
unsigned char *d, *s=data;
|
|
|
|
if (seg->isdummy)
|
|
{
|
|
seg->length += bytes;
|
|
return;
|
|
}
|
|
|
|
if (seg->length + bytes > seg->available)
|
|
{
|
|
unsigned int newlen = (seg->length + bytes + 0x10000) & ~0xffff;
|
|
void *newseg;
|
|
newseg = realloc(seg->data, newlen);
|
|
if (!newseg)
|
|
{
|
|
printf("out of memory\n");
|
|
seg->length += bytes;
|
|
seg->isdummy = 1;
|
|
return;
|
|
}
|
|
memset((char*)newseg+seg->available, 0, newlen - seg->available);
|
|
seg->data = newseg;
|
|
seg->available = newlen;
|
|
}
|
|
|
|
d = (unsigned char*)seg->data + seg->length;
|
|
seg->length += bytes;
|
|
while(bytes-- > 0)
|
|
*d++ = *s++;
|
|
}
|
|
|
|
void EmitByte(struct segment *seg, unsigned char data)
|
|
{
|
|
EmitData(seg, &data, 1);
|
|
}
|
|
|
|
void SkipData(struct segment *seg, int bytes)
|
|
{
|
|
seg->length += bytes;
|
|
}
|
|
|
|
void AlignSegment(struct segment *seg, int alignment)
|
|
{
|
|
//alignment must always be power of 2
|
|
alignment--;
|
|
seg->length = (seg->length + alignment) & ~(alignment);
|
|
}
|
|
|
|
void AssembleError(struct assembler *w, char *message, ...)
|
|
{
|
|
va_list va;
|
|
|
|
va_start(va, message);
|
|
vprintf(message, va);
|
|
va_end(va);
|
|
w->errorcount++;
|
|
}
|
|
|
|
void SegmentHack(struct assembler *w, int seg)
|
|
{
|
|
//like id, I don't see any way around this
|
|
//plus compatability. :/
|
|
|
|
if (w->curseg != &w->segs[seg])
|
|
{
|
|
w->curseg = &w->segs[seg];
|
|
|
|
if (w->lastsym)
|
|
{
|
|
// if (*w->lastsym->name != '$')
|
|
// printf("symbol %s segment-switch hack\n", w->lastsym->name);
|
|
w->lastsym->inseg = seg;
|
|
w->lastsym->atoffset = w->curseg->length;
|
|
}
|
|
else
|
|
printf("segment-switch hack\n");
|
|
}
|
|
}
|
|
|
|
void DoSegmentHack(struct assembler *w, int bytes)
|
|
{
|
|
if (bytes == 4)
|
|
{
|
|
SegmentHack(w, SEG_DATA);
|
|
}
|
|
else if (bytes == 1)
|
|
{
|
|
SegmentHack(w, SEG_LIT);
|
|
}
|
|
else
|
|
AssembleError(w, "%ibit variables are not supported\n", bytes*8);
|
|
}
|
|
|
|
|
|
int ParseToken(char *buffer, int bufferlen, char **input)
|
|
{
|
|
unsigned char *in = (unsigned char*)*input;
|
|
*buffer = 0;
|
|
while (*in <= ' ')
|
|
{
|
|
if (!*in++)
|
|
return 0;
|
|
}
|
|
|
|
if (*in == ';') //';' is taken as a comment
|
|
return 0;
|
|
|
|
do
|
|
{
|
|
if (!--bufferlen)
|
|
{
|
|
printf("name too long\n");
|
|
break;
|
|
}
|
|
*buffer++ = *in++;
|
|
} while (*in > ' ');
|
|
*buffer = 0;
|
|
*input = (char*)in;
|
|
return 1;
|
|
}
|
|
|
|
void AddReference(struct usedat *s, int segnum, int segofs)
|
|
{
|
|
if (s->count == USEDATBLOCK)
|
|
{ //we're getting a lot of references for this var
|
|
if (!s->next)
|
|
{
|
|
s->next = malloc(sizeof(*s));
|
|
if (!s->next)
|
|
{
|
|
printf("out of memory\n");
|
|
// AssembleError(w, "out of memory\n");
|
|
return;
|
|
}
|
|
s->next->next = NULL;
|
|
s->next->count = 0;
|
|
}
|
|
AddReference(s->next, segnum, segofs);
|
|
return;
|
|
}
|
|
s->seg[s->count] = segnum;
|
|
s->offset[s->count] = segofs;
|
|
s->count++;
|
|
}
|
|
|
|
struct symbol *GetSymbol(struct assembler *w, char *name)
|
|
{
|
|
struct symbol *s;
|
|
hashtable_t *t;
|
|
if (*name == '$')
|
|
t = &w->localsymboltable;
|
|
else
|
|
t = &w->symboltable;
|
|
|
|
s = Hash_Get(t, name);
|
|
if (!s)
|
|
{
|
|
s = malloc(sizeof(*s) + strlen(name));
|
|
if (!s)
|
|
{
|
|
AssembleError(w, "out of memory\n");
|
|
return NULL;
|
|
}
|
|
memset(s, 0, sizeof(*s));
|
|
s->next = w->symbols;
|
|
w->symbols = s;
|
|
strcpy(s->name, name);
|
|
Hash_Add(t, s->name, s, &s->bucket);
|
|
s->inseg = SEG_UNKNOWN;
|
|
s->atoffset = 0;
|
|
}
|
|
|
|
return s;
|
|
}
|
|
|
|
void DefineSymbol(struct assembler *w, char *symname, int segnum, int segofs)
|
|
{
|
|
struct symbol *s;
|
|
hashtable_t *t;
|
|
if (*symname == '$')
|
|
t = &w->localsymboltable;
|
|
else
|
|
t = &w->symboltable;
|
|
|
|
if (!*symname) //bail out
|
|
*(int*)NULL = -3;
|
|
|
|
s = Hash_Get(t, symname);
|
|
if (s)
|
|
{
|
|
if (s->inseg != SEG_UNKNOWN)
|
|
AssembleError(w, "symbol %s is already defined!\n", symname);
|
|
}
|
|
else
|
|
{
|
|
s = malloc(sizeof(*s) + strlen(symname));
|
|
if (!s)
|
|
{
|
|
printf ("out of memory\n");
|
|
w->errorcount++;
|
|
return;
|
|
}
|
|
memset(s, 0, sizeof(*s));
|
|
s->next = w->symbols;
|
|
w->symbols = s;
|
|
strcpy(s->name, symname);
|
|
Hash_Add(t, s->name, s, &s->bucket);
|
|
}
|
|
w->lastsym = s;
|
|
s->inseg = segnum;
|
|
s->atoffset = segofs;
|
|
}
|
|
|
|
int CalculateExpression(struct assembler *w, char *line)
|
|
{
|
|
char *o;
|
|
char valstr[64];
|
|
int val;
|
|
int segnum = w->curseg - w->segs;
|
|
int segoffs = w->curseg->length;
|
|
int isnum;
|
|
struct symbol *s;
|
|
//this is expressed as CONST+NAME-NAME
|
|
//many instructions need to add an additional base before emitting it
|
|
//so set the references to the place that we expect to be
|
|
|
|
val = 0;
|
|
|
|
while (*line)
|
|
{
|
|
o = valstr;
|
|
if (*line == '-' || *line == '+')
|
|
{
|
|
*o++ = *line++;
|
|
}
|
|
while (*(unsigned char*)line > ' ')
|
|
{
|
|
if (*line == '+' || *line == '-')
|
|
break;
|
|
*o++ = *line++;
|
|
}
|
|
*o = '\0';
|
|
|
|
if (*valstr == '-')
|
|
isnum = 1;
|
|
else if (*valstr == '+')
|
|
isnum = (valstr[1] >= '0' && valstr[1] <= '9');
|
|
else
|
|
isnum = (valstr[0] >= '0' && valstr[0] <= '9');
|
|
|
|
if (isnum)
|
|
val += atoi(valstr);
|
|
else
|
|
{
|
|
if (*valstr == '-' || *valstr == '+')
|
|
s = GetSymbol(w, valstr+1);
|
|
else
|
|
s = GetSymbol(w, valstr);
|
|
if (s) //yeah, doesn't make sense to have two symbols in one expression
|
|
AddReference(&s->usedat, segnum, segoffs); //but we do it anyway
|
|
}
|
|
}
|
|
|
|
return val;
|
|
}
|
|
|
|
void AssembleLine(struct assembler *w, char *line)
|
|
{
|
|
int i;
|
|
int opcode;
|
|
char instruction[64];
|
|
if (!ParseToken(instruction, sizeof(instruction), &line))
|
|
return; //nothing on this line
|
|
|
|
//try and get our special instructions first
|
|
switch (instruction[0])
|
|
{
|
|
case 'a':
|
|
if (!strcmp(instruction+1, "lign"))
|
|
{ //align
|
|
//theoretically, this should never make a difference. if it does, the segment hack stuff screwed something up.
|
|
if (ParseToken(instruction, sizeof(instruction), &line))
|
|
AlignSegment(w->curseg, atoi(instruction));
|
|
else
|
|
AssembleError(w, "align without argument\n");
|
|
return;
|
|
}
|
|
if (!strcmp(instruction+1, "ddress"))
|
|
{ //address
|
|
// w->lastsym = NULL;
|
|
if (ParseToken(instruction, sizeof(instruction), &line))
|
|
{
|
|
int expression;
|
|
SegmentHack(w, SEG_DATA);
|
|
expression = CalculateExpression(w, instruction);
|
|
EmitData(w->curseg, &expression, 4);
|
|
}
|
|
return;
|
|
}
|
|
break;
|
|
case 'b':
|
|
if (!strcmp(instruction+1, "ss"))
|
|
{ //bss
|
|
w->curseg = &w->segs[SEG_BSS];
|
|
return;
|
|
}
|
|
if (!strcmp(instruction+1, "yte"))
|
|
{ //byte
|
|
unsigned int value = 0;
|
|
unsigned int bytes = 0;
|
|
|
|
if (ParseToken(instruction, sizeof(instruction), &line))
|
|
bytes = atoi(instruction);
|
|
else
|
|
AssembleError(w, "byte without count\n");
|
|
if (ParseToken(instruction, sizeof(instruction), &line))
|
|
value = atoi(instruction);
|
|
else
|
|
AssembleError(w, "byte without value\n");
|
|
|
|
if (bytes == 4)
|
|
{
|
|
SegmentHack(w, SEG_DATA);
|
|
EmitData(w->curseg, &value, 4);
|
|
}
|
|
else if (bytes == 1)
|
|
{
|
|
SegmentHack(w, SEG_LIT);
|
|
EmitByte(w->curseg, value);
|
|
}
|
|
else
|
|
AssembleError(w, "%ibit variables are not supported\n", bytes*8);
|
|
return;
|
|
}
|
|
break;
|
|
case 'c':
|
|
if (!strcmp(instruction+1, "ode"))
|
|
{ //code
|
|
w->curseg = &w->segs[SEG_CODE];
|
|
return;
|
|
}
|
|
break;
|
|
case 'd':
|
|
if (!strcmp(instruction+1, "ata"))
|
|
{ //data
|
|
w->curseg = &w->segs[SEG_DATA];
|
|
return;
|
|
}
|
|
break;
|
|
case 'e':
|
|
if (!strcmp(instruction+1, "qu"))
|
|
{ //equ
|
|
char value[32];
|
|
if (ParseToken(instruction, sizeof(instruction), &line))
|
|
{
|
|
if (ParseToken(value, sizeof(value), &line))
|
|
DefineSymbol(w, instruction, w->curseg - w->segs, atoi(value));
|
|
else
|
|
|
|
AssembleError(w, "equ without value\n");
|
|
}
|
|
else
|
|
AssembleError(w, "equ without name\n");
|
|
return;
|
|
}
|
|
if (!strcmp(instruction+1, "xport"))
|
|
{ //export (ignored)
|
|
return;
|
|
}
|
|
if (!strcmp(instruction+1, "ndproc"))
|
|
{ //endproc
|
|
int locals = 0, args = 0;
|
|
if (ParseToken(instruction, sizeof(instruction), &line))
|
|
locals = atoi(instruction);
|
|
if (ParseToken(instruction, sizeof(instruction), &line))
|
|
args = atoi(instruction);
|
|
|
|
EmitByte(&w->segs[SEG_CODE], OP_PUSH); //we must return something
|
|
w->numinstructions++;
|
|
|
|
//disregard the two vars from above (matches q3asm...)
|
|
locals = 8 + w->funclocals + w->funcargs;
|
|
EmitByte(&w->segs[SEG_CODE], OP_LEAVE);
|
|
EmitData(&w->segs[SEG_CODE], &locals, 4);
|
|
w->numinstructions++;
|
|
return;
|
|
}
|
|
break;
|
|
case 'f':
|
|
if (!strcmp(instruction+1, "ile"))
|
|
{ //file (ignored)
|
|
return;
|
|
}
|
|
break;
|
|
case 'i':
|
|
if (!strcmp(instruction+1, "mport"))
|
|
{ //import (ignored)
|
|
return;
|
|
}
|
|
break;
|
|
case 'l':
|
|
if (!strcmp(instruction+1, "ine"))
|
|
{ //line (ignored)
|
|
return;
|
|
}
|
|
if (!strcmp(instruction+1, "it"))
|
|
{ //lit
|
|
w->curseg = &w->segs[SEG_LIT];
|
|
return;
|
|
}
|
|
break;
|
|
case 'p':
|
|
if (!strcmp(instruction+1, "roc"))
|
|
{ //proc
|
|
int v;
|
|
if (!ParseToken(instruction, sizeof(instruction), &line))
|
|
AssembleError(w, "unnamed function\n");
|
|
DefineSymbol(w, instruction, SEG_CODE, w->numinstructions);
|
|
if (ParseToken(instruction, sizeof(instruction), &line))
|
|
w->funclocals = (atoi(instruction) + 3) & ~3;
|
|
else
|
|
AssembleError(w, "proc without locals\n");
|
|
if (ParseToken(instruction, sizeof(instruction), &line))
|
|
w->funcargs = (atoi(instruction) + 3) & ~3;
|
|
else
|
|
AssembleError(w, "proc without args\n");
|
|
|
|
v = 8 + w->funclocals + w->funcargs;
|
|
if (v > 32767)
|
|
AssembleError(w, "function with an aweful lot of args\n");
|
|
|
|
EmitByte(&w->segs[SEG_CODE], OP_ENTER);
|
|
EmitData(&w->segs[SEG_CODE], &v, 4);
|
|
w->numinstructions++;
|
|
return;
|
|
}
|
|
if (!strcmp(instruction+1, "op"))
|
|
{ //pop
|
|
EmitByte(&w->segs[SEG_CODE], OP_POP);
|
|
w->numinstructions++;
|
|
return;
|
|
}
|
|
break;
|
|
case 's':
|
|
if (!strcmp(instruction+1, "kip"))
|
|
{ //skip
|
|
if (ParseToken(instruction, sizeof(instruction), &line))
|
|
SkipData(w->curseg, atoi(instruction));
|
|
else
|
|
AssembleError(w, "skip without argument\n");
|
|
return;
|
|
}
|
|
break;
|
|
case 'L':
|
|
if (!strncmp(instruction+1, "ABEL", 4))
|
|
{
|
|
if (!ParseToken(instruction, sizeof(instruction), &line))
|
|
{
|
|
printf("label with no name\n");
|
|
return;
|
|
}
|
|
//some evilness here (symbols in the instruction segment are instruction indexes not byte offsets)
|
|
if (w->curseg == &w->segs[SEG_CODE])
|
|
DefineSymbol(w, instruction, w->curseg - w->segs, w->numinstructions);
|
|
else
|
|
DefineSymbol(w, instruction, w->curseg - w->segs, w->curseg->length);
|
|
return;
|
|
}
|
|
break;
|
|
case 'A':
|
|
if (!strncmp(instruction+1, "RG", 2))
|
|
{ //ARG*
|
|
EmitByte(&w->segs[SEG_CODE], OP_ARG);
|
|
w->numinstructions++;
|
|
if (8 + w->funcargoffset > 255)
|
|
AssembleError(w, "too many arguments in fuction\n");
|
|
EmitByte(&w->segs[SEG_CODE], 8 + w->funcargoffset);
|
|
w->funcargoffset += 4; //reset by a following CALL instruction
|
|
return;
|
|
}
|
|
if (!strncmp(instruction+1, "DDRF", 4))
|
|
{ //ADDRF*
|
|
int exp = 0;
|
|
w->curseg = &w->segs[SEG_CODE]; //this differs, but not for lcc
|
|
EmitByte(&w->segs[SEG_CODE], OP_LOCAL);
|
|
if (ParseToken(instruction, sizeof(instruction), &line))
|
|
exp = CalculateExpression(w, instruction);
|
|
exp += 16 + w->funcargs + w->funclocals;
|
|
EmitData(&w->segs[SEG_CODE], &exp, 4);
|
|
w->numinstructions++;
|
|
return;
|
|
}
|
|
if (!strncmp(instruction+1, "DDRL", 4))
|
|
{ //ADDRL
|
|
int exp = 0;
|
|
w->curseg = &w->segs[SEG_CODE]; //this differs, but not for lcc
|
|
EmitByte(&w->segs[SEG_CODE], OP_LOCAL);
|
|
if (ParseToken(instruction, sizeof(instruction), &line))
|
|
exp = CalculateExpression(w, instruction);
|
|
exp += 8 + w->funcargs;
|
|
EmitData(&w->segs[SEG_CODE], &exp, 4);
|
|
w->numinstructions++;
|
|
return;
|
|
}
|
|
break;
|
|
case 'C':
|
|
if (!strncmp(instruction+1, "ALL", 3))
|
|
{ //CALL*
|
|
EmitByte(&w->segs[SEG_CODE], OP_CALL);
|
|
w->numinstructions++;
|
|
w->funcargoffset = 0;
|
|
return;
|
|
}
|
|
break;
|
|
case 'R':
|
|
if (!strncmp(instruction+1, "ET", 2))
|
|
{
|
|
int v = 8 + w->funclocals + w->funcargs;
|
|
EmitByte(&w->segs[SEG_CODE], OP_LEAVE);
|
|
EmitData(&w->segs[SEG_CODE], &v, 4);
|
|
w->numinstructions++;
|
|
return;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
//okay, we don't know what it is yet, try generic instructions
|
|
|
|
for (i = 0; i < sizeof(sourceOps) / sizeof(sourceOps[0]); i++)
|
|
{
|
|
if (*(unsigned int*)sourceOps[i].name == *(unsigned int*)instruction && !strcmp(sourceOps[i].name+4, instruction+4))
|
|
{
|
|
opcode = sourceOps[i].opcode;
|
|
if (opcode == OP_IGNORE)
|
|
return;
|
|
|
|
if (opcode == OP_SEX8)
|
|
{ //sex is special, apparently
|
|
|
|
if (ParseToken(instruction, sizeof(instruction), &line))
|
|
{
|
|
if (*instruction == '2')
|
|
opcode = OP_SEX16;
|
|
else if (*instruction != '1')
|
|
AssembleError(w, "bad sign extension\n");
|
|
}
|
|
}
|
|
|
|
if (opcode == OP_CVIF || opcode == OP_CVFI)
|
|
line = ""; //so we don't fall afoul looking for aguments (float/int conversions are always 4 anyway)
|
|
|
|
if (ParseToken(instruction, sizeof(instruction), &line))
|
|
{
|
|
int expression;
|
|
w->curseg = &w->segs[SEG_CODE]; //this differs, but not for lcc
|
|
EmitByte(&w->segs[SEG_CODE], opcode);
|
|
expression = CalculateExpression(w, instruction);
|
|
|
|
if (opcode == OP_BLOCK_COPY) //string initialisations are block copys too (this could still be wrong, but it matches)
|
|
expression = (expression+3)&~3;
|
|
EmitData(&w->segs[SEG_CODE], &expression, 4);
|
|
}
|
|
else
|
|
EmitByte(&w->segs[SEG_CODE], opcode);
|
|
|
|
w->numinstructions++;
|
|
return;
|
|
}
|
|
}
|
|
|
|
AssembleError(w, "Cannot handle %s\n", instruction);
|
|
}
|
|
|
|
void AssembleFile(struct assembler *w, int fnum, char *filename)
|
|
{
|
|
char linebuffer[1024];
|
|
FILE *f;
|
|
if (w->verbose)
|
|
{
|
|
if (fnum)
|
|
printf("file: %i %s\n", fnum, filename);
|
|
else
|
|
printf("file: %s\n", filename);
|
|
}
|
|
|
|
w->curseg = &w->segs[SEG_CODE];
|
|
|
|
f = fopen(filename, "rt");
|
|
if (!f)
|
|
{
|
|
snprintf(linebuffer, sizeof(linebuffer)-1, "%s.asm", filename);
|
|
f = fopen(linebuffer, "rt");
|
|
}
|
|
|
|
if (f)
|
|
{
|
|
while(fgets(linebuffer, sizeof(linebuffer), f))
|
|
AssembleLine(w, linebuffer);
|
|
fclose(f);
|
|
}
|
|
else
|
|
{
|
|
printf("couldn't open %s\n", filename);
|
|
w->errorcount++;
|
|
}
|
|
|
|
//reset the local symbol hash, so we don't find conflicting vars
|
|
memset(w->localsymboltablebuckets, 0, sizeof(w->localsymboltablebuckets));
|
|
}
|
|
|
|
|
|
|
|
int FixupSymbolReferencesOne(struct assembler *w, struct symbol *s, struct usedat *u)
|
|
{
|
|
int i;
|
|
int val;
|
|
unsigned int temp;
|
|
unsigned char *ptr;
|
|
val = w->segs[s->inseg].base + s->atoffset;
|
|
for (i = 0; i < u->count; i++)
|
|
{
|
|
ptr = (unsigned char*)w->segs[(int)u->seg[i]].data;
|
|
ptr += u->offset[i];
|
|
temp = (unsigned int)(ptr[0] | (ptr[1]<<8) | (ptr[2]<<16) | (ptr[3]<<24));
|
|
*(int*)&temp += val;
|
|
ptr[0] = (temp&0x000000ff)>>0;
|
|
ptr[1] = (temp&0x0000ff00)>>8;
|
|
ptr[2] = (temp&0x00ff0000)>>16;
|
|
ptr[3] = (temp&0xff000000)>>24;
|
|
}
|
|
if (u->next)
|
|
i += FixupSymbolReferencesOne(w, s, u->next);
|
|
|
|
return i;
|
|
}
|
|
|
|
|
|
void FixupSymbolReferences(struct assembler *w)
|
|
{ //this is our 'second pass'
|
|
struct symbol *s;
|
|
int numsyms = 0;
|
|
int numsymrefs = 0;
|
|
|
|
if (w->verbose)
|
|
printf("second 'pass'\n");
|
|
|
|
for (s = w->symbols; s; s = s->next)
|
|
{
|
|
if (s->inseg == SEG_UNKNOWN)
|
|
{
|
|
AssembleError(w, "Symbol %s was not defined\n", s->name);
|
|
continue;
|
|
}
|
|
|
|
if (w->verbose && !s->usedat.count && *s->name != '$') //don't ever mention the compiler generated 'static' vars
|
|
printf("%s was never used\n", s->name);
|
|
numsymrefs += FixupSymbolReferencesOne(w, s, &s->usedat);
|
|
numsyms++;
|
|
}
|
|
|
|
|
|
s = GetSymbol(w, "vmMain");
|
|
if (s)
|
|
{
|
|
if (s->atoffset != 0 || s->inseg != SEG_CODE)
|
|
AssembleError(w, "vmMain *MUST* be the first symbol in the qvm\nReorder your files\n");
|
|
}
|
|
|
|
if (w->verbose)
|
|
{
|
|
printf("Found %i symbols\n", numsyms);
|
|
printf("Found %i symbol references\n", numsymrefs);
|
|
}
|
|
}
|
|
|
|
void WriteMapFile(struct assembler *w, char *fname)
|
|
{
|
|
int segnum;
|
|
struct symbol *s;
|
|
FILE *f;
|
|
f = fopen(fname, "wt");
|
|
if (!f)
|
|
return;
|
|
//if we sorted these, we'd have better compatability with id's q3asm
|
|
//(their linker only defines variables in the second pass, their first pass is only to get table sizes)
|
|
//our symbols are in the order they were referenced, rather than used.
|
|
for (segnum = 0; segnum < SEG_MAX; segnum++)
|
|
{
|
|
for (s = w->symbols; s; s = s->next)
|
|
{
|
|
if (*s->name == '$') //don't show locals
|
|
continue;
|
|
if (s->inseg != segnum)
|
|
continue;
|
|
|
|
fprintf(f, "%i %8x %s\n", s->inseg, s->atoffset, s->name);
|
|
}
|
|
}
|
|
|
|
fclose(f);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void InitialiseAssembler(struct assembler *w)
|
|
{
|
|
int i;
|
|
memset(w, 0, sizeof(*w));
|
|
|
|
Hash_InitTable(&w->symboltable, SYMBOLBUCKETS, w->symboltablebuckets);
|
|
Hash_InitTable(&w->localsymboltable, LOCALSYMBOLBUCKETS, w->localsymboltablebuckets);
|
|
|
|
w->segs[SEG_BSS].isdummy = 1;
|
|
|
|
i = 0;
|
|
EmitData(&w->segs[SEG_DATA], &i, 4); //so NULL really is NULL
|
|
|
|
}
|
|
|
|
void WriteOutput(struct assembler *w, char *outputname)
|
|
{
|
|
FILE *f;
|
|
struct vmheader h;
|
|
|
|
int i;
|
|
for (i = 0; i < SEG_MAX; i++)
|
|
{ //align the segments (yes, even the code segment)
|
|
w->segs[i].length = (w->segs[i].length + 3) & ~3;
|
|
}
|
|
|
|
//add the stack to the end of the bss. I don't know if q3 even uses this
|
|
DefineSymbol(w, "_stackStart", SEG_BSS, w->segs[SEG_BSS].length);
|
|
w->segs[SEG_BSS].length += STACKSIZE;
|
|
DefineSymbol(w, "_stackEnd", SEG_BSS, w->segs[SEG_BSS].length);
|
|
|
|
w->segs[SEG_CODE].base = 0;
|
|
w->segs[SEG_DATA].base = 0;
|
|
w->segs[SEG_LIT].base = w->segs[SEG_DATA].base + w->segs[SEG_DATA].length;
|
|
w->segs[SEG_BSS].base = w->segs[SEG_LIT].base + w->segs[SEG_LIT].length;
|
|
|
|
FixupSymbolReferences(w);
|
|
|
|
if (w->segs[SEG_CODE].isdummy || w->segs[SEG_DATA].isdummy || w->segs[SEG_LIT].isdummy)
|
|
w->errorcount++; //one of the segments failed to allocate mem
|
|
|
|
printf("code bytes: %i\n", w->segs[SEG_CODE].length);
|
|
printf("data bytes: %i\n", w->segs[SEG_DATA].length);
|
|
printf(" lit bytes: %i\n", w->segs[SEG_LIT ].length);
|
|
printf(" bss bytes: %i\n", w->segs[SEG_BSS ].length);
|
|
printf("instruction count: %i\n", w->numinstructions);
|
|
|
|
if (w->errorcount)
|
|
{
|
|
printf("%i errors\n", w->errorcount);
|
|
return;
|
|
}
|
|
|
|
|
|
h.vmMagic = VM_MAGIC;
|
|
h.instructionCount = w->numinstructions;
|
|
h.codeLength = w->segs[SEG_CODE].length;
|
|
h.dataLength = w->segs[SEG_DATA].length;
|
|
h.litLength = w->segs[SEG_LIT].length;
|
|
h.bssLength = w->segs[SEG_BSS].length;
|
|
|
|
h.codeOffset = sizeof(h);
|
|
h.dataOffset = h.codeOffset + h.codeLength;
|
|
f = fopen(outputname, "wb");
|
|
if (f)
|
|
{
|
|
fwrite(&h, 1, sizeof(h), f);
|
|
fwrite(w->segs[SEG_CODE].data, 1, w->segs[SEG_CODE].length, f);
|
|
fwrite(w->segs[SEG_DATA].data, 1, w->segs[SEG_DATA].length, f);
|
|
fwrite(w->segs[SEG_LIT ].data, 1, w->segs[SEG_LIT ].length, f);
|
|
//logically the bss comes here (but doesn't, cos its bss)
|
|
fclose(f);
|
|
}
|
|
else
|
|
printf("error writing to file: %s\n", outputname);
|
|
|
|
|
|
WriteMapFile(w, "q3asm2.map");
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void Assemble(struct workload *w)
|
|
{
|
|
int i;
|
|
|
|
struct assembler a;
|
|
|
|
InitialiseAssembler(&a);
|
|
a.verbose = w->verbose;
|
|
|
|
for (i = 0; i < w->numsrcfiles; i++)
|
|
AssembleFile(&a, i+1, w->srcfilelist[i]);
|
|
|
|
WriteOutput(&a, w->output);
|
|
}
|
|
|
|
int ParseCommandList(struct workload *w, int numcmds, char **cmds)
|
|
{
|
|
char **t;
|
|
|
|
FILE *f;
|
|
char *buffer, *pp, *pps;
|
|
unsigned int len, numextraargs;
|
|
char *suppargs[64];
|
|
|
|
while (numcmds)
|
|
{
|
|
if ((*cmds)[0] == '-')
|
|
{
|
|
if ((*cmds)[1] == 'f')
|
|
{
|
|
numcmds--;
|
|
cmds++;
|
|
|
|
f = fopen(*cmds, "rt");
|
|
if (!f)
|
|
{
|
|
char blah[256];
|
|
snprintf(blah, sizeof(blah)-1, "%s.q3asm", *cmds);
|
|
f = fopen(blah, "rt");
|
|
}
|
|
if (f)
|
|
{
|
|
fseek(f, 0, SEEK_END);
|
|
len = ftell(f);
|
|
fseek(f, 0, SEEK_SET);
|
|
buffer = malloc((len+9));
|
|
if (buffer)
|
|
{
|
|
if (fread(buffer, 1, len, f) == len)
|
|
{
|
|
buffer[len] = 0;
|
|
numextraargs = 0;
|
|
pp = buffer;
|
|
do
|
|
{
|
|
if (numextraargs == sizeof(suppargs)/sizeof(suppargs[0]))
|
|
break;
|
|
while (*pp == ' ' || *pp == '\r' || *pp == '\n')
|
|
{
|
|
pp++;
|
|
}
|
|
if (*pp == '\"')
|
|
{
|
|
pp++;
|
|
pps = pp;
|
|
while (*pp && *pp != '\"')
|
|
pp++;
|
|
}
|
|
else
|
|
{
|
|
pps = pp;
|
|
while (*pp && !(*pp == ' ' || *pp == '\r' || *pp == '\n'))
|
|
{
|
|
pp++;
|
|
}
|
|
}
|
|
if (*pp)
|
|
*pp++ = 0;
|
|
suppargs[numextraargs] = pps;
|
|
numextraargs++;
|
|
} while (*pp);
|
|
ParseCommandList(w, numextraargs, suppargs);
|
|
}
|
|
else
|
|
{
|
|
printf("failure reading source file\n");
|
|
}
|
|
free(buffer);
|
|
}
|
|
else
|
|
{
|
|
printf("out of memory\n");
|
|
}
|
|
fclose(f);
|
|
}
|
|
else
|
|
{
|
|
printf("couldn't open \"%s\"\n", *cmds);
|
|
}
|
|
}
|
|
else if ((*cmds)[1] == 'o')
|
|
{
|
|
numcmds--;
|
|
cmds++;
|
|
|
|
if (!strchr(*cmds, '.'))
|
|
{
|
|
snprintf(w->output, sizeof(w->output)-1, "%s.qvm", *cmds);
|
|
}
|
|
else
|
|
{
|
|
strncpy(w->output, *cmds, sizeof(w->output)-1);
|
|
w->output[sizeof(w->output)-1] = 0;
|
|
}
|
|
}
|
|
else if ((*cmds)[1] == 'v')
|
|
w->verbose = 1;
|
|
else
|
|
{
|
|
printf("Unrecognised command\n");
|
|
return 1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//just a src file
|
|
t = realloc(w->srcfilelist, sizeof(char*)*(w->numsrcfiles+1));
|
|
if (!t)
|
|
{ //if realloc fails, the old pointer is still valid
|
|
printf("out of memory\n");
|
|
return 1;
|
|
}
|
|
w->srcfilelist = t;
|
|
w->srcfilelist[w->numsrcfiles] = malloc(strlen(*cmds)+1);
|
|
if (!w->srcfilelist[w->numsrcfiles])
|
|
{
|
|
printf("out of memory\n");
|
|
return 1;
|
|
}
|
|
strcpy(w->srcfilelist[w->numsrcfiles], *cmds);
|
|
w->numsrcfiles++;
|
|
}
|
|
numcmds--;
|
|
cmds++;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
int i;
|
|
struct workload *w;
|
|
|
|
if (!(w = malloc(sizeof(*w))))
|
|
{
|
|
printf("out of memory (REALLY EARLY!!!)\n");
|
|
}
|
|
else
|
|
{
|
|
w->numsrcfiles = 0;
|
|
w->srcfilelist = NULL;
|
|
strcpy(w->output, "q3asm2.qvm"); //fill in the default options
|
|
|
|
if (ParseCommandList(w, argc-1, argv+1))
|
|
{
|
|
printf("Syntax is: q3asm2 [-o <output>] <files>\n");
|
|
printf(" or: q3asm2 -f <listfile>\n");
|
|
printf("or any crazy recursive mixture of the two\n");
|
|
}
|
|
else
|
|
{
|
|
printf("output file: %s\n", w->output);
|
|
printf("%i files\n", w->numsrcfiles);
|
|
|
|
|
|
Assemble(w);
|
|
}
|
|
|
|
for (i = 0; i < w->numsrcfiles; i++)
|
|
{
|
|
free(w->srcfilelist[i]);
|
|
}
|
|
if (w->srcfilelist)
|
|
free(w->srcfilelist);
|
|
|
|
free(w);
|
|
}
|
|
return 0;
|
|
}
|
|
|