mirror of
https://github.com/frida/tinycc
synced 2024-11-24 08:39:37 +03:00
8624 lines
249 KiB
C
8624 lines
249 KiB
C
/*
|
|
* TCC - Tiny C Compiler
|
|
*
|
|
* Copyright (c) 2001, 2002 Fabrice Bellard
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it 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.
|
|
*
|
|
* This program is 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 this program; if not, write to the Free Software
|
|
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
*/
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <stdarg.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <math.h>
|
|
#include <unistd.h>
|
|
#include <signal.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#ifndef WIN32
|
|
#include <sys/ucontext.h>
|
|
#include <sys/mman.h>
|
|
#endif
|
|
#include "elf.h"
|
|
#include "stab.h"
|
|
#ifndef CONFIG_TCC_STATIC
|
|
#include <dlfcn.h>
|
|
#endif
|
|
|
|
#include "libtcc.h"
|
|
|
|
//#define DEBUG
|
|
/* preprocessor debug */
|
|
//#define PP_DEBUG
|
|
|
|
/* target selection */
|
|
//#define TCC_TARGET_I386 /* i386 code generator */
|
|
//#define TCC_TARGET_IL /* .NET CLI generator */
|
|
|
|
/* default target is I386 */
|
|
#if !defined(TCC_TARGET_I386) && !defined(TCC_TARGET_IL)
|
|
#define TCC_TARGET_I386
|
|
#endif
|
|
|
|
#if !defined(WIN32) && !defined(TCC_UCLIBC) && !defined(TCC_TARGET_IL)
|
|
#define CONFIG_TCC_BCHECK /* enable bound checking code */
|
|
#endif
|
|
|
|
#ifndef CONFIG_TCC_PREFIX
|
|
#define CONFIG_TCC_PREFIX "/usr/local"
|
|
#endif
|
|
|
|
/* path to find crt1.o, crti.o and crtn.o. Only needed when generating
|
|
executables or dlls */
|
|
#define CONFIG_TCC_CRT_PREFIX "/usr/lib"
|
|
|
|
/* amount of virtual memory associated to a section (currently, we do
|
|
not realloc them) */
|
|
#define SECTION_VSIZE (1024 * 1024)
|
|
|
|
#define INCLUDE_STACK_SIZE 32
|
|
#define IFDEF_STACK_SIZE 64
|
|
#define VSTACK_SIZE 64
|
|
#define STRING_MAX_SIZE 1024
|
|
|
|
#define TOK_HASH_SIZE 2048 /* must be a power of two */
|
|
#define TOK_ALLOC_INCR 512 /* must be a power of two */
|
|
#define SYM_HASH_SIZE 1031
|
|
#define ELF_SYM_HASH_SIZE 2048
|
|
#define ELF_DYNSYM_HASH_SIZE 32
|
|
|
|
/* token symbol management */
|
|
typedef struct TokenSym {
|
|
struct TokenSym *hash_next;
|
|
int tok; /* token number */
|
|
int len;
|
|
char str[1];
|
|
} TokenSym;
|
|
|
|
/* constant value */
|
|
typedef union CValue {
|
|
long double ld;
|
|
double d;
|
|
float f;
|
|
int i;
|
|
unsigned int ui;
|
|
unsigned int ul; /* address (should be unsigned long on 64 bit cpu) */
|
|
long long ll;
|
|
unsigned long long ull;
|
|
struct TokenSym *ts;
|
|
struct Sym *sym;
|
|
void *ptr;
|
|
int tab[1];
|
|
} CValue;
|
|
|
|
/* value on stack */
|
|
typedef struct SValue {
|
|
int t; /* type */
|
|
unsigned short r; /* register + flags */
|
|
unsigned short r2; /* second register, used for 'long long'
|
|
type. If not used, set to VT_CONST */
|
|
CValue c; /* constant, if VT_CONST */
|
|
} SValue;
|
|
|
|
/* symbol management */
|
|
typedef struct Sym {
|
|
int v; /* symbol token */
|
|
int t; /* associated type */
|
|
int r; /* associated register */
|
|
int c; /* associated number */
|
|
struct Sym *next; /* next related symbol */
|
|
struct Sym *prev; /* prev symbol in stack */
|
|
struct Sym *hash_next; /* next symbol in hash table */
|
|
} Sym;
|
|
|
|
typedef struct SymStack {
|
|
struct Sym *top;
|
|
struct Sym *hash[SYM_HASH_SIZE];
|
|
} SymStack;
|
|
|
|
/* section definition */
|
|
/* XXX: use directly ELF structure for parameters ? */
|
|
/* special flag to indicate that the section should not be linked to
|
|
the other ones */
|
|
#define SHF_PRIVATE 0x80000000
|
|
|
|
typedef struct Section {
|
|
unsigned long data_offset; /* current data offset */
|
|
unsigned char *data; /* section data */
|
|
int sh_name; /* elf section name (only used during output) */
|
|
int sh_num; /* elf section number */
|
|
int sh_type; /* elf section type */
|
|
int sh_flags; /* elf section flags */
|
|
int sh_info; /* elf section info */
|
|
int sh_addralign; /* elf section alignment */
|
|
int sh_entsize; /* elf entry size */
|
|
unsigned long sh_size; /* section size (only used during output) */
|
|
unsigned long sh_addr; /* address at which the section is relocated */
|
|
unsigned long sh_offset; /* address at which the section is relocated */
|
|
struct Section *link; /* link to another section */
|
|
struct Section *reloc; /* corresponding section for relocation, if any */
|
|
struct Section *hash; /* hash table for symbols */
|
|
struct Section *next;
|
|
char name[64]; /* section name */
|
|
} Section;
|
|
|
|
typedef struct DLLReference {
|
|
int level;
|
|
char name[1];
|
|
} DLLReference;
|
|
|
|
/* GNUC attribute definition */
|
|
typedef struct AttributeDef {
|
|
int aligned;
|
|
Section *section;
|
|
unsigned char func_call; /* FUNC_CDECL or FUNC_STDCALL */
|
|
} AttributeDef;
|
|
|
|
#define SYM_STRUCT 0x40000000 /* struct/union/enum symbol space */
|
|
#define SYM_FIELD 0x20000000 /* struct/union field symbol space */
|
|
#define SYM_FIRST_ANOM (1 << (31 - VT_STRUCT_SHIFT)) /* first anonymous sym */
|
|
|
|
/* stored in 'Sym.c' field */
|
|
#define FUNC_NEW 1 /* ansi function prototype */
|
|
#define FUNC_OLD 2 /* old function prototype */
|
|
#define FUNC_ELLIPSIS 3 /* ansi function prototype with ... */
|
|
|
|
/* stored in 'Sym.r' field */
|
|
#define FUNC_CDECL 0 /* standard c call */
|
|
#define FUNC_STDCALL 1 /* pascal c call */
|
|
|
|
/* field 'Sym.t' for macros */
|
|
#define MACRO_OBJ 0 /* object like macro */
|
|
#define MACRO_FUNC 1 /* function like macro */
|
|
|
|
/* field 'Sym.t' for labels */
|
|
#define LABEL_FORWARD 1 /* label is forward defined */
|
|
|
|
/* type_decl() types */
|
|
#define TYPE_ABSTRACT 1 /* type without variable */
|
|
#define TYPE_DIRECT 2 /* type with variable */
|
|
|
|
#define IO_BUF_SIZE 8192
|
|
|
|
typedef struct BufferedFile {
|
|
unsigned char *buf_ptr;
|
|
unsigned char *buf_end;
|
|
int fd;
|
|
int line_num; /* current line number - here to simply code */
|
|
char filename[1024]; /* current filename - here to simplify code */
|
|
unsigned char buffer[IO_BUF_SIZE + 1]; /* extra size for CH_EOB char */
|
|
} BufferedFile;
|
|
|
|
#define CH_EOB 0 /* end of buffer or '\0' char in file */
|
|
#define CH_EOF (-1) /* end of file */
|
|
|
|
/* parsing state (used to save parser state to reparse part of the
|
|
source several times) */
|
|
typedef struct ParseState {
|
|
int *macro_ptr;
|
|
int line_num;
|
|
int tok;
|
|
CValue tokc;
|
|
} ParseState;
|
|
|
|
/* used to record tokens */
|
|
typedef struct TokenString {
|
|
int *str;
|
|
int len;
|
|
int last_line_num;
|
|
} TokenString;
|
|
|
|
/* parser */
|
|
struct BufferedFile *file;
|
|
int ch, ch1, tok, tok1;
|
|
CValue tokc, tok1c;
|
|
int return_linefeed; /* if true, line feed is returned as a token */
|
|
|
|
/* sections */
|
|
Section **sections;
|
|
int nb_sections; /* number of sections, including first dummy section */
|
|
Section *text_section, *data_section, *bss_section; /* predefined sections */
|
|
Section *cur_text_section; /* current section where function code is
|
|
generated */
|
|
/* bound check related sections */
|
|
Section *bounds_section; /* contains global data bound description */
|
|
Section *lbounds_section; /* contains local data bound description */
|
|
/* symbol sections */
|
|
Section *symtab_section, *strtab_section;
|
|
/* temporary dynamic symbol sections (for dll loading) */
|
|
Section *dynsymtab_section, *dynstrtab_section;
|
|
/* exported dynamic symbol section */
|
|
Section *dynsym;
|
|
/* got handling */
|
|
Section *got;
|
|
unsigned long *got_offsets;
|
|
int nb_got_offsets;
|
|
int nb_plt_entries;
|
|
|
|
|
|
/* array of all loaded dlls (including those referenced by loaded
|
|
dlls) */
|
|
DLLReference **loaded_dlls;
|
|
int nb_loaded_dlls;
|
|
|
|
/* debug sections */
|
|
Section *stab_section, *stabstr_section;
|
|
|
|
char **library_paths;
|
|
int nb_library_paths;
|
|
|
|
/* loc : local variable index
|
|
ind : output code index
|
|
rsym: return symbol
|
|
anon_sym: anonymous symbol index
|
|
*/
|
|
int rsym, anon_sym,
|
|
prog, ind, loc, const_wanted;
|
|
int global_expr; /* true if compound literals must be allocated
|
|
globally (used during initializers parsing */
|
|
int func_vt, func_vc; /* current function return type (used by
|
|
return instruction) */
|
|
int last_line_num, last_ind, func_ind; /* debug last line number and pc */
|
|
int tok_ident;
|
|
TokenSym **table_ident;
|
|
TokenSym *hash_ident[TOK_HASH_SIZE];
|
|
char token_buf[STRING_MAX_SIZE + 1];
|
|
char *funcname;
|
|
SymStack define_stack, global_stack, local_stack, label_stack;
|
|
|
|
SValue vstack[VSTACK_SIZE], *vtop;
|
|
int *macro_ptr, *macro_ptr_allocated;
|
|
BufferedFile *include_stack[INCLUDE_STACK_SIZE], **include_stack_ptr;
|
|
int ifdef_stack[IFDEF_STACK_SIZE], *ifdef_stack_ptr;
|
|
char **include_paths;
|
|
int nb_include_paths;
|
|
int char_pointer_type;
|
|
int func_old_type;
|
|
|
|
/* compile with debug symbol (and use them if error during execution) */
|
|
int do_debug = 0;
|
|
|
|
/* compile with built-in memory and bounds checker */
|
|
int do_bounds_check = 0;
|
|
|
|
/* display benchmark infos */
|
|
int do_bench = 0;
|
|
int total_lines;
|
|
int total_bytes;
|
|
|
|
/* use GNU C extensions */
|
|
int gnu_ext = 1;
|
|
|
|
/* use Tiny C extensions */
|
|
int tcc_ext = 1;
|
|
|
|
/* if true, static linking is performed */
|
|
int static_link = 0;
|
|
|
|
/* give the path of the tcc libraries */
|
|
static const char *tcc_lib_path = CONFIG_TCC_PREFIX "/lib/tcc";
|
|
|
|
struct TCCState {
|
|
int output_type;
|
|
};
|
|
|
|
/* The current value can be: */
|
|
#define VT_VALMASK 0x00ff
|
|
#define VT_CONST 0x00f0 /* constant in vc
|
|
(must be first non register value) */
|
|
#define VT_LLOCAL 0x00f1 /* lvalue, offset on stack */
|
|
#define VT_LOCAL 0x00f2 /* offset on stack */
|
|
#define VT_CMP 0x00f3 /* the value is stored in processor flags (in vc) */
|
|
#define VT_JMP 0x00f4 /* value is the consequence of jmp true (even) */
|
|
#define VT_JMPI 0x00f5 /* value is the consequence of jmp false (odd) */
|
|
#define VT_LVAL 0x0100 /* var is an lvalue */
|
|
#define VT_SYM 0x0200 /* a symbol value is added */
|
|
#define VT_MUSTCAST 0x0400 /* value must be casted to be correct (used for
|
|
char/short stored in integer registers) */
|
|
#define VT_MUSTBOUND 0x0800 /* bound checking must be done before
|
|
dereferencing value */
|
|
#define VT_BOUNDED 0x8000 /* value is bounded. The address of the
|
|
bounding function call point is in vc */
|
|
#define VT_LVAL_BYTE 0x1000 /* lvalue is a byte */
|
|
#define VT_LVAL_SHORT 0x2000 /* lvalue is a short */
|
|
#define VT_LVAL_UNSIGNED 0x4000 /* lvalue is unsigned */
|
|
#define VT_LVAL_TYPE (VT_LVAL_BYTE | VT_LVAL_SHORT | VT_LVAL_UNSIGNED)
|
|
|
|
/* types */
|
|
#define VT_STRUCT_SHIFT 12 /* structure/enum name shift (20 bits left) */
|
|
|
|
#define VT_INT 0 /* integer type */
|
|
#define VT_BYTE 1 /* signed byte type */
|
|
#define VT_SHORT 2 /* short type */
|
|
#define VT_VOID 3 /* void type */
|
|
#define VT_PTR 4 /* pointer */
|
|
#define VT_ENUM 5 /* enum definition */
|
|
#define VT_FUNC 6 /* function type */
|
|
#define VT_STRUCT 7 /* struct/union definition */
|
|
#define VT_FLOAT 8 /* IEEE float */
|
|
#define VT_DOUBLE 9 /* IEEE double */
|
|
#define VT_LDOUBLE 10 /* IEEE long double */
|
|
#define VT_BOOL 11 /* ISOC99 boolean type */
|
|
#define VT_LLONG 12 /* 64 bit integer */
|
|
#define VT_LONG 13 /* long integer (NEVER USED as type, only
|
|
during parsing) */
|
|
#define VT_BTYPE 0x000f /* mask for basic type */
|
|
#define VT_UNSIGNED 0x0010 /* unsigned type */
|
|
#define VT_ARRAY 0x0020 /* array type (also has VT_PTR) */
|
|
#define VT_BITFIELD 0x0040 /* bitfield modifier */
|
|
|
|
/* storage */
|
|
#define VT_EXTERN 0x00000080 /* extern definition */
|
|
#define VT_STATIC 0x00000100 /* static variable */
|
|
#define VT_TYPEDEF 0x00000200 /* typedef definition */
|
|
|
|
/* type mask (except storage) */
|
|
#define VT_TYPE (~(VT_EXTERN | VT_STATIC | VT_TYPEDEF))
|
|
|
|
/* token values */
|
|
|
|
/* warning: the following compare tokens depend on i386 asm code */
|
|
#define TOK_ULT 0x92
|
|
#define TOK_UGE 0x93
|
|
#define TOK_EQ 0x94
|
|
#define TOK_NE 0x95
|
|
#define TOK_ULE 0x96
|
|
#define TOK_UGT 0x97
|
|
#define TOK_LT 0x9c
|
|
#define TOK_GE 0x9d
|
|
#define TOK_LE 0x9e
|
|
#define TOK_GT 0x9f
|
|
|
|
#define TOK_LAND 0xa0
|
|
#define TOK_LOR 0xa1
|
|
|
|
#define TOK_DEC 0xa2
|
|
#define TOK_MID 0xa3 /* inc/dec, to void constant */
|
|
#define TOK_INC 0xa4
|
|
#define TOK_UDIV 0xb0 /* unsigned division */
|
|
#define TOK_UMOD 0xb1 /* unsigned modulo */
|
|
#define TOK_PDIV 0xb2 /* fast division with undefined rounding for pointers */
|
|
#define TOK_CINT 0xb3 /* number in tokc */
|
|
#define TOK_CCHAR 0xb4 /* char constant in tokc */
|
|
#define TOK_STR 0xb5 /* pointer to string in tokc */
|
|
#define TOK_TWOSHARPS 0xb6 /* ## preprocessing token */
|
|
#define TOK_LCHAR 0xb7
|
|
#define TOK_LSTR 0xb8
|
|
#define TOK_CFLOAT 0xb9 /* float constant */
|
|
#define TOK_LINENUM 0xba /* line number info */
|
|
#define TOK_CDOUBLE 0xc0 /* double constant */
|
|
#define TOK_CLDOUBLE 0xc1 /* long double constant */
|
|
#define TOK_UMULL 0xc2 /* unsigned 32x32 -> 64 mul */
|
|
#define TOK_ADDC1 0xc3 /* add with carry generation */
|
|
#define TOK_ADDC2 0xc4 /* add with carry use */
|
|
#define TOK_SUBC1 0xc5 /* add with carry generation */
|
|
#define TOK_SUBC2 0xc6 /* add with carry use */
|
|
#define TOK_CUINT 0xc8 /* unsigned int constant */
|
|
#define TOK_CLLONG 0xc9 /* long long constant */
|
|
#define TOK_CULLONG 0xca /* unsigned long long constant */
|
|
#define TOK_ARROW 0xcb
|
|
#define TOK_DOTS 0xcc /* three dots */
|
|
#define TOK_SHR 0xcd /* unsigned shift right */
|
|
|
|
#define TOK_SHL 0x01 /* shift left */
|
|
#define TOK_SAR 0x02 /* signed shift right */
|
|
|
|
/* assignement operators : normal operator or 0x80 */
|
|
#define TOK_A_MOD 0xa5
|
|
#define TOK_A_AND 0xa6
|
|
#define TOK_A_MUL 0xaa
|
|
#define TOK_A_ADD 0xab
|
|
#define TOK_A_SUB 0xad
|
|
#define TOK_A_DIV 0xaf
|
|
#define TOK_A_XOR 0xde
|
|
#define TOK_A_OR 0xfc
|
|
#define TOK_A_SHL 0x81
|
|
#define TOK_A_SAR 0x82
|
|
|
|
#define TOK_EOF (-1) /* end of file */
|
|
#define TOK_LINEFEED 10 /* line feed */
|
|
|
|
/* all identificators and strings have token above that */
|
|
#define TOK_IDENT 256
|
|
|
|
enum {
|
|
TOK_INT = TOK_IDENT,
|
|
TOK_VOID,
|
|
TOK_CHAR,
|
|
TOK_IF,
|
|
TOK_ELSE,
|
|
TOK_WHILE,
|
|
TOK_BREAK,
|
|
TOK_RETURN,
|
|
TOK_FOR,
|
|
TOK_EXTERN,
|
|
TOK_STATIC,
|
|
TOK_UNSIGNED,
|
|
TOK_GOTO,
|
|
TOK_DO,
|
|
TOK_CONTINUE,
|
|
TOK_SWITCH,
|
|
TOK_CASE,
|
|
|
|
/* ignored types Must have contiguous values */
|
|
TOK_CONST,
|
|
TOK_VOLATILE,
|
|
TOK_LONG,
|
|
TOK_REGISTER,
|
|
TOK_SIGNED,
|
|
TOK___SIGNED__, /* gcc keyword */
|
|
TOK_AUTO,
|
|
TOK_INLINE,
|
|
TOK___INLINE__, /* gcc keyword */
|
|
TOK_RESTRICT,
|
|
|
|
/* unsupported type */
|
|
TOK_FLOAT,
|
|
TOK_DOUBLE,
|
|
TOK_BOOL,
|
|
|
|
TOK_SHORT,
|
|
TOK_STRUCT,
|
|
TOK_UNION,
|
|
TOK_TYPEDEF,
|
|
TOK_DEFAULT,
|
|
TOK_ENUM,
|
|
TOK_SIZEOF,
|
|
TOK___ATTRIBUTE__,
|
|
|
|
/* preprocessor only */
|
|
TOK_UIDENT, /* first "user" ident (not keyword) */
|
|
TOK_DEFINE = TOK_UIDENT,
|
|
TOK_INCLUDE,
|
|
TOK_IFDEF,
|
|
TOK_IFNDEF,
|
|
TOK_ELIF,
|
|
TOK_ENDIF,
|
|
TOK_DEFINED,
|
|
TOK_UNDEF,
|
|
TOK_ERROR,
|
|
TOK_LINE,
|
|
TOK___LINE__,
|
|
TOK___FILE__,
|
|
TOK___DATE__,
|
|
TOK___TIME__,
|
|
TOK___VA_ARGS__,
|
|
|
|
/* special identifiers */
|
|
TOK___FUNC__,
|
|
TOK_MAIN,
|
|
#define DEF(id, str) id,
|
|
#include "tcctok.h"
|
|
#undef DEF
|
|
};
|
|
|
|
char *tcc_keywords =
|
|
"int\0void\0char\0if\0else\0while\0break\0return\0for\0extern\0static\0"
|
|
"unsigned\0goto\0do\0continue\0switch\0case\0const\0volatile\0long\0"
|
|
"register\0signed\0__signed__\0auto\0inline\0__inline__\0restrict\0"
|
|
"float\0double\0_Bool\0short\0struct\0union\0typedef\0default\0enum\0"
|
|
"sizeof\0__attribute__\0"
|
|
/* the following are not keywords. They are included to ease parsing */
|
|
"define\0include\0ifdef\0ifndef\0elif\0endif\0"
|
|
"defined\0undef\0error\0line\0"
|
|
"__LINE__\0__FILE__\0__DATE__\0__TIME__\0__VA_ARGS__\0"
|
|
"__func__\0main\0"
|
|
/* builtin functions */
|
|
#define DEF(id, str) str "\0"
|
|
#include "tcctok.h"
|
|
#undef DEF
|
|
;
|
|
|
|
#ifdef WIN32
|
|
#define snprintf _snprintf
|
|
#endif
|
|
|
|
#if defined(WIN32) || defined(TCC_UCLIBC)
|
|
/* currently incorrect */
|
|
long double strtold(const char *nptr, char **endptr)
|
|
{
|
|
return (long double)strtod(nptr, endptr);
|
|
}
|
|
float strtof(const char *nptr, char **endptr)
|
|
{
|
|
return (float)strtod(nptr, endptr);
|
|
}
|
|
#else
|
|
/* XXX: need to define this to use them in non ISOC99 context */
|
|
extern float strtof (const char *__nptr, char **__endptr);
|
|
extern long double strtold (const char *__nptr, char **__endptr);
|
|
#endif
|
|
|
|
static char *pstrcpy(char *buf, int buf_size, const char *s);
|
|
static char *pstrcat(char *buf, int buf_size, const char *s);
|
|
|
|
void sum(int l);
|
|
void next(void);
|
|
void next_nomacro(void);
|
|
int expr_const(void);
|
|
void expr_eq(void);
|
|
void gexpr(void);
|
|
void decl(int l);
|
|
void decl_initializer(int t, Section *sec, unsigned long c, int first, int size_only);
|
|
void decl_initializer_alloc(int t, AttributeDef *ad, int r, int has_init,
|
|
int v, int scope);
|
|
int gv(int rc);
|
|
void gv2(int rc1, int rc2);
|
|
void move_reg(int r, int s);
|
|
void save_regs(int n);
|
|
void save_reg(int r);
|
|
void vpop(void);
|
|
void vswap(void);
|
|
void vdup(void);
|
|
int get_reg(int rc);
|
|
|
|
void macro_subst(TokenString *tok_str,
|
|
Sym **nested_list, int *macro_str);
|
|
int save_reg_forced(int r);
|
|
void gen_op(int op);
|
|
void force_charshort_cast(int t);
|
|
void gen_cast(int t);
|
|
void vstore(void);
|
|
Sym *sym_find(int v);
|
|
Sym *sym_push(int v, int t, int r, int c);
|
|
|
|
/* type handling */
|
|
int type_size(int t, int *a);
|
|
int pointed_type(int t);
|
|
int pointed_size(int t);
|
|
int is_compatible_types(int t1, int t2);
|
|
int parse_btype(int *type_ptr, AttributeDef *ad);
|
|
int type_decl(AttributeDef *ad, int *v, int t, int td);
|
|
|
|
void error(const char *fmt, ...);
|
|
void rt_error(unsigned long pc, const char *fmt, ...);
|
|
void vpushi(int v);
|
|
void vset(int t, int r, int v);
|
|
void type_to_str(char *buf, int buf_size,
|
|
int t, const char *varstr);
|
|
char *get_tok_str(int v, CValue *cv);
|
|
Sym *external_sym(int v, int u, int r);
|
|
static Sym *get_sym_ref(int t, Section *sec,
|
|
unsigned long offset, unsigned long size);
|
|
|
|
/* section generation */
|
|
static void *section_ptr(Section *sec, unsigned long size);
|
|
static void *section_ptr_add(Section *sec, unsigned long size);
|
|
void put_extern_sym(Sym *sym, Section *section, unsigned long value);
|
|
void greloc(Section *s, Sym *sym, unsigned long addr, int type);
|
|
static int put_elf_str(Section *s, const char *sym);
|
|
static int put_elf_sym(Section *s,
|
|
unsigned long value, unsigned long size,
|
|
int info, int other, int shndx, const char *name);
|
|
static void put_elf_reloc(Section *symtab, Section *s, unsigned long offset,
|
|
int type, int symbol);
|
|
static void put_stabs(const char *str, int type, int other, int desc,
|
|
unsigned long value);
|
|
static void put_stabs_r(const char *str, int type, int other, int desc,
|
|
unsigned long value, Section *sec, int sym_index);
|
|
static void put_stabn(int type, int other, int desc, int value);
|
|
static void put_stabd(int type, int other, int desc);
|
|
static int tcc_add_dll(TCCState *s, const char *filename, int flags);
|
|
|
|
#define AFF_PRINT_ERROR 0x0001 /* print error if file not found */
|
|
#define AFF_REFERENCED_DLL 0x0002 /* load a referenced dll from another dll */
|
|
static int tcc_add_file_internal(TCCState *s, const char *filename, int flags);
|
|
|
|
/* true if float/double/long double type */
|
|
static inline int is_float(int t)
|
|
{
|
|
int bt;
|
|
bt = t & VT_BTYPE;
|
|
return bt == VT_LDOUBLE || bt == VT_DOUBLE || bt == VT_FLOAT;
|
|
}
|
|
|
|
#ifdef TCC_TARGET_I386
|
|
#include "i386-gen.c"
|
|
#endif
|
|
#ifdef TCC_TARGET_IL
|
|
#include "il-gen.c"
|
|
#endif
|
|
|
|
#ifdef CONFIG_TCC_STATIC
|
|
|
|
#define RTLD_LAZY 0x001
|
|
#define RTLD_NOW 0x002
|
|
#define RTLD_GLOBAL 0x100
|
|
|
|
/* dummy function for profiling */
|
|
void *dlopen(const char *filename, int flag)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
const char *dlerror(void)
|
|
{
|
|
return "error";
|
|
}
|
|
|
|
typedef struct TCCSyms {
|
|
char *str;
|
|
void *ptr;
|
|
} TCCSyms;
|
|
|
|
#define TCCSYM(a) { #a, &a, },
|
|
|
|
/* add the symbol you want here if no dynamic linking is done */
|
|
static TCCSyms tcc_syms[] = {
|
|
TCCSYM(printf)
|
|
TCCSYM(fprintf)
|
|
TCCSYM(fopen)
|
|
TCCSYM(fclose)
|
|
{ NULL, NULL },
|
|
};
|
|
|
|
void *dlsym(void *handle, const char *symbol)
|
|
{
|
|
TCCSyms *p;
|
|
p = tcc_syms;
|
|
while (p->str != NULL) {
|
|
if (!strcmp(p->str, symbol))
|
|
return p->ptr;
|
|
p++;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
#endif
|
|
|
|
/********************************************************/
|
|
|
|
/* we use our own 'finite' function to avoid potential problems with
|
|
non standard math libs */
|
|
/* XXX: endianness dependant */
|
|
int ieee_finite(double d)
|
|
{
|
|
int *p = (int *)&d;
|
|
return ((unsigned)((p[1] | 0x800fffff) + 1)) >> 31;
|
|
}
|
|
|
|
/* copy a string and truncate it. */
|
|
static char *pstrcpy(char *buf, int buf_size, const char *s)
|
|
{
|
|
char *q, *q_end;
|
|
int c;
|
|
|
|
if (buf_size > 0) {
|
|
q = buf;
|
|
q_end = buf + buf_size - 1;
|
|
while (q < q_end) {
|
|
c = *s++;
|
|
if (c == '\0')
|
|
break;
|
|
*q++ = c;
|
|
}
|
|
*q = '\0';
|
|
}
|
|
return buf;
|
|
}
|
|
|
|
/* strcat and truncate. */
|
|
static char *pstrcat(char *buf, int buf_size, const char *s)
|
|
{
|
|
int len;
|
|
len = strlen(buf);
|
|
if (len < buf_size)
|
|
pstrcpy(buf + len, buf_size - len, s);
|
|
return buf;
|
|
}
|
|
|
|
static void dynarray_add(void ***ptab, int *nb_ptr, void *data)
|
|
{
|
|
int nb, nb_alloc;
|
|
void **pp;
|
|
|
|
nb = *nb_ptr;
|
|
pp = *ptab;
|
|
/* every power of two we double array size */
|
|
if ((nb & (nb - 1)) == 0) {
|
|
if (!nb)
|
|
nb_alloc = 1;
|
|
else
|
|
nb_alloc = nb * 2;
|
|
pp = realloc(pp, nb_alloc * sizeof(void *));
|
|
if (!pp)
|
|
error("memory full");
|
|
*ptab = pp;
|
|
}
|
|
pp[nb++] = data;
|
|
*nb_ptr = nb;
|
|
}
|
|
|
|
Section *new_section(const char *name, int sh_type, int sh_flags)
|
|
{
|
|
Section *sec;
|
|
void *data;
|
|
|
|
sec = malloc(sizeof(Section));
|
|
if (!sec)
|
|
error("memory full");
|
|
memset(sec, 0, sizeof(Section));
|
|
pstrcpy(sec->name, sizeof(sec->name), name);
|
|
sec->sh_type = sh_type;
|
|
sec->sh_flags = sh_flags;
|
|
switch(sh_type) {
|
|
case SHT_HASH:
|
|
case SHT_REL:
|
|
case SHT_DYNSYM:
|
|
case SHT_SYMTAB:
|
|
case SHT_DYNAMIC:
|
|
sec->sh_addralign = 4;
|
|
break;
|
|
case SHT_STRTAB:
|
|
sec->sh_addralign = 1;
|
|
break;
|
|
default:
|
|
sec->sh_addralign = 32; /* default conservative alignment */
|
|
break;
|
|
}
|
|
#ifdef WIN32
|
|
/* XXX: currently, a single malloc */
|
|
data = malloc(SECTION_VSIZE);
|
|
if (data == NULL)
|
|
error("could not alloc section '%s'", name);
|
|
#else
|
|
data = mmap(NULL, SECTION_VSIZE,
|
|
PROT_EXEC | PROT_READ | PROT_WRITE,
|
|
MAP_PRIVATE | MAP_ANONYMOUS,
|
|
-1, 0);
|
|
if (data == (void *)(-1))
|
|
error("could not mmap section '%s'", name);
|
|
#endif
|
|
sec->data = data;
|
|
sec->data_offset = 0;
|
|
|
|
/* only add section if not private */
|
|
if (!(sh_flags & SHF_PRIVATE)) {
|
|
sec->sh_num = nb_sections;
|
|
dynarray_add((void ***)§ions, &nb_sections, sec);
|
|
}
|
|
return sec;
|
|
}
|
|
|
|
/* reserve at least 'size' bytes in section 'sec' from sec->data_offset */
|
|
static void *section_ptr(Section *sec, unsigned long size)
|
|
{
|
|
return sec->data + sec->data_offset;
|
|
}
|
|
|
|
static void *section_ptr_add(Section *sec, unsigned long size)
|
|
{
|
|
void *ptr;
|
|
ptr = sec->data + sec->data_offset;
|
|
sec->data_offset += size;
|
|
return ptr;
|
|
}
|
|
|
|
/* return a reference to a section, and create it if it does not
|
|
exists */
|
|
Section *find_section(const char *name)
|
|
{
|
|
Section *sec;
|
|
int i;
|
|
for(i = 1; i < nb_sections; i++) {
|
|
sec = sections[i];
|
|
if (!strcmp(name, sec->name))
|
|
return sec;
|
|
}
|
|
/* sections are created as PROGBITS */
|
|
return new_section(name, SHT_PROGBITS, SHF_ALLOC);
|
|
}
|
|
|
|
/* add a new relocation entry to symbol 'sym' in section 's' */
|
|
void greloc(Section *s, Sym *sym, unsigned long offset, int type)
|
|
{
|
|
if (!sym->c)
|
|
put_extern_sym(sym, NULL, 0);
|
|
/* now we can add ELF relocation info */
|
|
put_elf_reloc(symtab_section, s, offset, type, sym->c);
|
|
}
|
|
|
|
static inline int isid(int c)
|
|
{
|
|
return (c >= 'a' && c <= 'z') ||
|
|
(c >= 'A' && c <= 'Z') ||
|
|
c == '_';
|
|
}
|
|
|
|
static inline int isnum(int c)
|
|
{
|
|
return c >= '0' && c <= '9';
|
|
}
|
|
|
|
static inline int toup(int c)
|
|
{
|
|
if (ch >= 'a' && ch <= 'z')
|
|
return ch - 'a' + 'A';
|
|
else
|
|
return ch;
|
|
}
|
|
|
|
void printline(void)
|
|
{
|
|
BufferedFile **f;
|
|
|
|
if (file) {
|
|
for(f = include_stack; f < include_stack_ptr; f++)
|
|
fprintf(stderr, "In file included from %s:%d:\n",
|
|
(*f)->filename, (*f)->line_num);
|
|
if (file->line_num > 0) {
|
|
fprintf(stderr, "%s:%d: ", file->filename, file->line_num);
|
|
} else {
|
|
fprintf(stderr, "%s: ", file->filename);
|
|
}
|
|
} else {
|
|
fprintf(stderr, "tcc: ");
|
|
}
|
|
}
|
|
|
|
void error(const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
printline();
|
|
vfprintf(stderr, fmt, ap);
|
|
fprintf(stderr, "\n");
|
|
exit(1);
|
|
va_end(ap);
|
|
}
|
|
|
|
void expect(const char *msg)
|
|
{
|
|
error("%s expected", msg);
|
|
}
|
|
|
|
void warning(const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
|
|
va_start(ap, fmt);
|
|
printline();
|
|
fprintf(stderr, "warning: ");
|
|
vfprintf(stderr, fmt, ap);
|
|
fprintf(stderr, "\n");
|
|
va_end(ap);
|
|
}
|
|
|
|
void skip(int c)
|
|
{
|
|
if (tok != c)
|
|
error("'%c' expected", c);
|
|
next();
|
|
}
|
|
|
|
void test_lvalue(void)
|
|
{
|
|
if (!(vtop->r & VT_LVAL))
|
|
expect("lvalue");
|
|
}
|
|
|
|
TokenSym *tok_alloc(const char *str, int len)
|
|
{
|
|
TokenSym *ts, **pts, **ptable;
|
|
int h, i;
|
|
|
|
if (len <= 0)
|
|
len = strlen(str);
|
|
h = 1;
|
|
for(i=0;i<len;i++)
|
|
h = (h * 263 + ((unsigned char *)str)[i]) & (TOK_HASH_SIZE - 1);
|
|
|
|
pts = &hash_ident[h];
|
|
while (1) {
|
|
ts = *pts;
|
|
if (!ts)
|
|
break;
|
|
if (ts->len == len && !memcmp(ts->str, str, len))
|
|
return ts;
|
|
pts = &(ts->hash_next);
|
|
}
|
|
|
|
if (tok_ident >= SYM_FIRST_ANOM)
|
|
error("memory full");
|
|
|
|
/* expand token table if needed */
|
|
i = tok_ident - TOK_IDENT;
|
|
if ((i % TOK_ALLOC_INCR) == 0) {
|
|
ptable = realloc(table_ident, (i + TOK_ALLOC_INCR) * sizeof(TokenSym *));
|
|
if (!ptable)
|
|
error("memory full");
|
|
table_ident = ptable;
|
|
}
|
|
|
|
ts = malloc(sizeof(TokenSym) + len);
|
|
if (!ts)
|
|
error("memory full");
|
|
table_ident[i] = ts;
|
|
ts->tok = tok_ident++;
|
|
ts->len = len;
|
|
ts->hash_next = NULL;
|
|
memcpy(ts->str, str, len + 1);
|
|
*pts = ts;
|
|
return ts;
|
|
}
|
|
|
|
void add_char(char **pp, int c)
|
|
{
|
|
char *p;
|
|
p = *pp;
|
|
if (c == '\'' || c == '\"' || c == '\\') {
|
|
/* XXX: could be more precise if char or string */
|
|
*p++ = '\\';
|
|
}
|
|
if (c >= 32 && c <= 126) {
|
|
*p++ = c;
|
|
} else {
|
|
*p++ = '\\';
|
|
if (c == '\n') {
|
|
*p++ = 'n';
|
|
} else {
|
|
*p++ = '0' + ((c >> 6) & 7);
|
|
*p++ = '0' + ((c >> 3) & 7);
|
|
*p++ = '0' + (c & 7);
|
|
}
|
|
}
|
|
*pp = p;
|
|
}
|
|
|
|
/* XXX: buffer overflow */
|
|
char *get_tok_str(int v, CValue *cv)
|
|
{
|
|
static char buf[STRING_MAX_SIZE + 1];
|
|
TokenSym *ts;
|
|
char *p;
|
|
int i;
|
|
|
|
if (v == TOK_CINT || v == TOK_CUINT) {
|
|
sprintf(buf, "%u", cv->ui);
|
|
return buf;
|
|
} else if (v == TOK_CCHAR || v == TOK_LCHAR) {
|
|
p = buf;
|
|
*p++ = '\'';
|
|
add_char(&p, cv->i);
|
|
*p++ = '\'';
|
|
*p = '\0';
|
|
return buf;
|
|
} else if (v == TOK_STR || v == TOK_LSTR) {
|
|
ts = cv->ts;
|
|
p = buf;
|
|
*p++ = '\"';
|
|
for(i=0;i<ts->len;i++)
|
|
add_char(&p, ts->str[i]);
|
|
*p++ = '\"';
|
|
*p = '\0';
|
|
return buf;
|
|
} else if (v < TOK_IDENT) {
|
|
p = buf;
|
|
*p++ = v;
|
|
*p = '\0';
|
|
return buf;
|
|
} else if (v < tok_ident) {
|
|
return table_ident[v - TOK_IDENT]->str;
|
|
} else if (v >= SYM_FIRST_ANOM) {
|
|
/* special name for anonymous symbol */
|
|
sprintf(buf, "L.%u", v - SYM_FIRST_ANOM);
|
|
return buf;
|
|
} else {
|
|
/* should never happen */
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/* push, without hashing */
|
|
Sym *sym_push2(Sym **ps, int v, int t, int c)
|
|
{
|
|
Sym *s;
|
|
s = malloc(sizeof(Sym));
|
|
if (!s)
|
|
error("memory full");
|
|
s->v = v;
|
|
s->t = t;
|
|
s->c = c;
|
|
s->next = NULL;
|
|
/* add in stack */
|
|
s->prev = *ps;
|
|
*ps = s;
|
|
return s;
|
|
}
|
|
|
|
/* find a symbol and return its associated structure. 's' is the top
|
|
of the symbol stack */
|
|
Sym *sym_find2(Sym *s, int v)
|
|
{
|
|
while (s) {
|
|
if (s->v == v)
|
|
return s;
|
|
s = s->prev;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
#define HASH_SYM(v) ((unsigned)(v) % SYM_HASH_SIZE)
|
|
|
|
/* find a symbol and return its associated structure. 'st' is the
|
|
symbol stack */
|
|
Sym *sym_find1(SymStack *st, int v)
|
|
{
|
|
Sym *s;
|
|
|
|
s = st->hash[HASH_SYM(v)];
|
|
while (s) {
|
|
if (s->v == v)
|
|
return s;
|
|
s = s->hash_next;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
Sym *sym_push1(SymStack *st, int v, int t, int c)
|
|
{
|
|
Sym *s, **ps;
|
|
s = sym_push2(&st->top, v, t, c);
|
|
/* add in hash table */
|
|
if (v) {
|
|
ps = &st->hash[HASH_SYM(v)];
|
|
s->hash_next = *ps;
|
|
*ps = s;
|
|
}
|
|
return s;
|
|
}
|
|
|
|
/* find a symbol in the right symbol space */
|
|
Sym *sym_find(int v)
|
|
{
|
|
Sym *s;
|
|
s = sym_find1(&local_stack, v);
|
|
if (!s)
|
|
s = sym_find1(&global_stack, v);
|
|
return s;
|
|
}
|
|
|
|
/* push a given symbol on the symbol stack */
|
|
Sym *sym_push(int v, int t, int r, int c)
|
|
{
|
|
Sym *s;
|
|
if (local_stack.top)
|
|
s = sym_push1(&local_stack, v, t, c);
|
|
else
|
|
s = sym_push1(&global_stack, v, t, c);
|
|
s->r = r;
|
|
return s;
|
|
}
|
|
|
|
/* pop symbols until top reaches 'b' */
|
|
void sym_pop(SymStack *st, Sym *b)
|
|
{
|
|
Sym *s, *ss;
|
|
|
|
s = st->top;
|
|
while(s != b) {
|
|
ss = s->prev;
|
|
/* free hash table entry, except if symbol was freed (only
|
|
used for #undef symbols) */
|
|
if (s->v)
|
|
st->hash[HASH_SYM(s->v)] = s->hash_next;
|
|
free(s);
|
|
s = ss;
|
|
}
|
|
st->top = b;
|
|
}
|
|
|
|
/* undefined a hashed symbol (used for #undef). Its name is set to
|
|
zero */
|
|
void sym_undef(SymStack *st, Sym *s)
|
|
{
|
|
Sym **ss;
|
|
ss = &st->hash[HASH_SYM(s->v)];
|
|
while (*ss != NULL) {
|
|
if (*ss == s)
|
|
break;
|
|
ss = &(*ss)->hash_next;
|
|
}
|
|
*ss = s->hash_next;
|
|
s->v = 0;
|
|
}
|
|
|
|
/* I/O layer */
|
|
|
|
BufferedFile *tcc_open(const char *filename)
|
|
{
|
|
int fd;
|
|
BufferedFile *bf;
|
|
|
|
fd = open(filename, O_RDONLY);
|
|
if (fd < 0)
|
|
return NULL;
|
|
bf = malloc(sizeof(BufferedFile));
|
|
if (!bf) {
|
|
close(fd);
|
|
return NULL;
|
|
}
|
|
bf->fd = fd;
|
|
bf->buf_ptr = bf->buffer;
|
|
bf->buf_end = bf->buffer;
|
|
bf->buffer[0] = CH_EOB; /* put eob symbol */
|
|
pstrcpy(bf->filename, sizeof(bf->filename), filename);
|
|
bf->line_num = 1;
|
|
// printf("opening '%s'\n", filename);
|
|
return bf;
|
|
}
|
|
|
|
void tcc_close(BufferedFile *bf)
|
|
{
|
|
total_lines += bf->line_num;
|
|
close(bf->fd);
|
|
free(bf);
|
|
}
|
|
|
|
/* read one char. MUST call tcc_fillbuf if CH_EOB is read */
|
|
#define TCC_GETC(bf) (*(bf)->buf_ptr++)
|
|
|
|
/* fill input buffer and return next char */
|
|
int tcc_getc_slow(BufferedFile *bf)
|
|
{
|
|
int len;
|
|
/* only tries to read if really end of buffer */
|
|
if (bf->buf_ptr >= bf->buf_end) {
|
|
if (bf->fd != -1) {
|
|
len = read(bf->fd, bf->buffer, IO_BUF_SIZE);
|
|
if (len < 0)
|
|
len = 0;
|
|
} else {
|
|
len = 0;
|
|
}
|
|
total_bytes += len;
|
|
bf->buf_ptr = bf->buffer;
|
|
bf->buf_end = bf->buffer + len;
|
|
*bf->buf_end = CH_EOB;
|
|
}
|
|
if (bf->buf_ptr < bf->buf_end) {
|
|
return *bf->buf_ptr++;
|
|
} else {
|
|
bf->buf_ptr = bf->buf_end;
|
|
return CH_EOF;
|
|
}
|
|
}
|
|
|
|
/* no need to put that inline */
|
|
void handle_eob(void)
|
|
{
|
|
for(;;) {
|
|
ch1 = tcc_getc_slow(file);
|
|
if (ch1 != CH_EOF)
|
|
return;
|
|
|
|
if (include_stack_ptr == include_stack)
|
|
return;
|
|
/* add end of include file debug info */
|
|
if (do_debug) {
|
|
put_stabd(N_EINCL, 0, 0);
|
|
}
|
|
/* pop include stack */
|
|
tcc_close(file);
|
|
include_stack_ptr--;
|
|
file = *include_stack_ptr;
|
|
}
|
|
}
|
|
|
|
/* read next char from current input file */
|
|
static inline void inp(void)
|
|
{
|
|
ch1 = TCC_GETC(file);
|
|
/* end of buffer/file handling */
|
|
if (ch1 == CH_EOB)
|
|
handle_eob();
|
|
if (ch1 == '\n')
|
|
file->line_num++;
|
|
// printf("ch1=%c 0x%x\n", ch1, ch1);
|
|
}
|
|
|
|
/* input with '\\n' handling */
|
|
static inline void minp(void)
|
|
{
|
|
redo:
|
|
ch = ch1;
|
|
inp();
|
|
if (ch == '\\' && ch1 == '\n') {
|
|
inp();
|
|
goto redo;
|
|
}
|
|
//printf("ch=%c 0x%x\n", ch, ch);
|
|
}
|
|
|
|
|
|
/* same as minp, but also skip comments */
|
|
void cinp(void)
|
|
{
|
|
int c;
|
|
|
|
if (ch1 == '/') {
|
|
inp();
|
|
if (ch1 == '/') {
|
|
/* single line C++ comments */
|
|
inp();
|
|
while (ch1 != '\n' && ch1 != -1)
|
|
inp();
|
|
inp();
|
|
ch = ' '; /* return space */
|
|
} else if (ch1 == '*') {
|
|
/* C comments */
|
|
inp();
|
|
while (ch1 != -1) {
|
|
c = ch1;
|
|
inp();
|
|
if (c == '*' && ch1 == '/') {
|
|
inp();
|
|
ch = ' '; /* return space */
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
ch = '/';
|
|
}
|
|
} else {
|
|
minp();
|
|
}
|
|
}
|
|
|
|
void skip_spaces(void)
|
|
{
|
|
while (ch == ' ' || ch == '\t')
|
|
cinp();
|
|
}
|
|
|
|
/* skip block of text until #else, #elif or #endif. skip also pairs of
|
|
#if/#endif */
|
|
void preprocess_skip(void)
|
|
{
|
|
int a;
|
|
a = 0;
|
|
while (1) {
|
|
while (ch != '\n') {
|
|
if (ch == -1)
|
|
expect("#endif");
|
|
cinp();
|
|
}
|
|
cinp();
|
|
skip_spaces();
|
|
if (ch == '#') {
|
|
cinp();
|
|
next_nomacro();
|
|
if (a == 0 &&
|
|
(tok == TOK_ELSE || tok == TOK_ELIF || tok == TOK_ENDIF))
|
|
break;
|
|
if (tok == TOK_IF || tok == TOK_IFDEF || tok == TOK_IFNDEF)
|
|
a++;
|
|
else if (tok == TOK_ENDIF)
|
|
a--;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ParseState handling */
|
|
|
|
/* XXX: currently, no include file info is stored. Thus, we cannot display
|
|
accurate messages if the function or data definition spans multiple
|
|
files */
|
|
|
|
/* save current parse state in 's' */
|
|
void save_parse_state(ParseState *s)
|
|
{
|
|
s->line_num = file->line_num;
|
|
s->macro_ptr = macro_ptr;
|
|
s->tok = tok;
|
|
s->tokc = tokc;
|
|
}
|
|
|
|
/* restore parse state from 's' */
|
|
void restore_parse_state(ParseState *s)
|
|
{
|
|
file->line_num = s->line_num;
|
|
macro_ptr = s->macro_ptr;
|
|
tok = s->tok;
|
|
tokc = s->tokc;
|
|
}
|
|
|
|
/* return the number of additionnal 'ints' necessary to store the
|
|
token */
|
|
static inline int tok_ext_size(int t)
|
|
{
|
|
switch(t) {
|
|
/* 4 bytes */
|
|
case TOK_CINT:
|
|
case TOK_CUINT:
|
|
case TOK_CCHAR:
|
|
case TOK_LCHAR:
|
|
case TOK_STR:
|
|
case TOK_LSTR:
|
|
case TOK_CFLOAT:
|
|
case TOK_LINENUM:
|
|
return 1;
|
|
case TOK_CDOUBLE:
|
|
case TOK_CLLONG:
|
|
case TOK_CULLONG:
|
|
return 2;
|
|
case TOK_CLDOUBLE:
|
|
return LDOUBLE_SIZE / 4;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/* token string handling */
|
|
|
|
static inline void tok_str_new(TokenString *s)
|
|
{
|
|
s->str = NULL;
|
|
s->len = 0;
|
|
s->last_line_num = -1;
|
|
}
|
|
|
|
static void tok_str_add(TokenString *s, int t)
|
|
{
|
|
int len, *str;
|
|
|
|
len = s->len;
|
|
str = s->str;
|
|
if ((len & 63) == 0) {
|
|
str = realloc(str, (len + 64) * sizeof(int));
|
|
if (!str)
|
|
return;
|
|
s->str = str;
|
|
}
|
|
str[len++] = t;
|
|
s->len = len;
|
|
}
|
|
|
|
static void tok_str_add2(TokenString *s, int t, CValue *cv)
|
|
{
|
|
int n, i;
|
|
tok_str_add(s, t);
|
|
n = tok_ext_size(t);
|
|
for(i=0;i<n;i++)
|
|
tok_str_add(s, cv->tab[i]);
|
|
}
|
|
|
|
/* add the current parse token in token string 's' */
|
|
static void tok_str_add_tok(TokenString *s)
|
|
{
|
|
CValue cval;
|
|
|
|
/* save line number info */
|
|
if (file->line_num != s->last_line_num) {
|
|
s->last_line_num = file->line_num;
|
|
cval.i = s->last_line_num;
|
|
tok_str_add2(s, TOK_LINENUM, &cval);
|
|
}
|
|
tok_str_add2(s, tok, &tokc);
|
|
}
|
|
|
|
/* get a token from an integer array and increment pointer accordingly */
|
|
static int tok_get(int **tok_str, CValue *cv)
|
|
{
|
|
int *p, t, n, i;
|
|
|
|
p = *tok_str;
|
|
t = *p++;
|
|
n = tok_ext_size(t);
|
|
for(i=0;i<n;i++)
|
|
cv->tab[i] = *p++;
|
|
*tok_str = p;
|
|
return t;
|
|
}
|
|
|
|
/* eval an expression for #if/#elif */
|
|
int expr_preprocess(void)
|
|
{
|
|
int c, t;
|
|
TokenString str;
|
|
|
|
tok_str_new(&str);
|
|
while (tok != TOK_LINEFEED && tok != TOK_EOF) {
|
|
next(); /* do macro subst */
|
|
if (tok == TOK_DEFINED) {
|
|
next_nomacro();
|
|
t = tok;
|
|
if (t == '(')
|
|
next_nomacro();
|
|
c = sym_find1(&define_stack, tok) != 0;
|
|
if (t == '(')
|
|
next_nomacro();
|
|
tok = TOK_CINT;
|
|
tokc.i = c;
|
|
} else if (tok >= TOK_IDENT) {
|
|
/* if undefined macro */
|
|
tok = TOK_CINT;
|
|
tokc.i = 0;
|
|
}
|
|
tok_str_add_tok(&str);
|
|
}
|
|
tok_str_add(&str, -1); /* simulate end of file */
|
|
tok_str_add(&str, 0);
|
|
/* now evaluate C constant expression */
|
|
macro_ptr = str.str;
|
|
next();
|
|
c = expr_const();
|
|
macro_ptr = NULL;
|
|
free(str.str);
|
|
return c != 0;
|
|
}
|
|
|
|
#if defined(DEBUG) || defined(PP_DEBUG)
|
|
void tok_print(int *str)
|
|
{
|
|
int t;
|
|
CValue cval;
|
|
|
|
while (1) {
|
|
t = tok_get(&str, &cval);
|
|
if (!t)
|
|
break;
|
|
printf(" %s", get_tok_str(t, &cval));
|
|
}
|
|
printf("\n");
|
|
}
|
|
#endif
|
|
|
|
/* parse after #define */
|
|
void parse_define(void)
|
|
{
|
|
Sym *s, *first, **ps;
|
|
int v, t, varg, is_vaargs;
|
|
TokenString str;
|
|
|
|
v = tok;
|
|
/* XXX: should check if same macro (ANSI) */
|
|
first = NULL;
|
|
t = MACRO_OBJ;
|
|
/* '(' must be just after macro definition for MACRO_FUNC */
|
|
if (ch == '(') {
|
|
next_nomacro();
|
|
next_nomacro();
|
|
ps = &first;
|
|
while (tok != ')') {
|
|
varg = tok;
|
|
next_nomacro();
|
|
is_vaargs = 0;
|
|
if (varg == TOK_DOTS) {
|
|
varg = TOK___VA_ARGS__;
|
|
is_vaargs = 1;
|
|
} else if (tok == TOK_DOTS && gnu_ext) {
|
|
is_vaargs = 1;
|
|
next_nomacro();
|
|
}
|
|
if (varg < TOK_IDENT)
|
|
error("badly punctuated parameter list");
|
|
s = sym_push1(&define_stack, varg | SYM_FIELD, is_vaargs, 0);
|
|
*ps = s;
|
|
ps = &s->next;
|
|
if (tok != ',')
|
|
break;
|
|
next_nomacro();
|
|
}
|
|
t = MACRO_FUNC;
|
|
}
|
|
tok_str_new(&str);
|
|
while (1) {
|
|
skip_spaces();
|
|
if (ch == '\n' || ch == -1)
|
|
break;
|
|
next_nomacro();
|
|
tok_str_add2(&str, tok, &tokc);
|
|
}
|
|
tok_str_add(&str, 0);
|
|
#ifdef PP_DEBUG
|
|
printf("define %s %d: ", get_tok_str(v, NULL), t);
|
|
tok_print(str.str);
|
|
#endif
|
|
s = sym_push1(&define_stack, v, t, (int)str.str);
|
|
s->next = first;
|
|
}
|
|
|
|
void preprocess(void)
|
|
{
|
|
int size, i, c;
|
|
char buf[1024], *q, *p;
|
|
char buf1[1024];
|
|
BufferedFile *f;
|
|
Sym *s;
|
|
|
|
return_linefeed = 1; /* linefeed will be returned as a token */
|
|
cinp();
|
|
next_nomacro();
|
|
redo:
|
|
if (tok == TOK_DEFINE) {
|
|
next_nomacro();
|
|
parse_define();
|
|
} else if (tok == TOK_UNDEF) {
|
|
next_nomacro();
|
|
s = sym_find1(&define_stack, tok);
|
|
/* undefine symbol by putting an invalid name */
|
|
if (s)
|
|
sym_undef(&define_stack, s);
|
|
} else if (tok == TOK_INCLUDE) {
|
|
skip_spaces();
|
|
if (ch == '<') {
|
|
c = '>';
|
|
goto read_name;
|
|
} else if (ch == '\"') {
|
|
c = ch;
|
|
read_name:
|
|
minp();
|
|
q = buf;
|
|
while (ch != c && ch != '\n' && ch != -1) {
|
|
if ((q - buf) < sizeof(buf) - 1)
|
|
*q++ = ch;
|
|
minp();
|
|
}
|
|
*q = '\0';
|
|
} else {
|
|
next();
|
|
if (tok != TOK_STR)
|
|
error("#include syntax error");
|
|
pstrcpy(buf, sizeof(buf), get_tok_str(tok, &tokc));
|
|
c = '\"';
|
|
}
|
|
/* eat all spaces and comments after include */
|
|
/* XXX: slightly incorrect */
|
|
while (ch1 != '\n' && ch1 != -1)
|
|
inp();
|
|
|
|
if (include_stack_ptr >= include_stack + INCLUDE_STACK_SIZE)
|
|
error("memory full");
|
|
if (c == '\"') {
|
|
/* first search in current dir if "header.h" */
|
|
size = 0;
|
|
p = strrchr(file->filename, '/');
|
|
if (p)
|
|
size = p + 1 - file->filename;
|
|
if (size > sizeof(buf1) - 1)
|
|
size = sizeof(buf1) - 1;
|
|
memcpy(buf1, file->filename, size);
|
|
buf1[size] = '\0';
|
|
pstrcat(buf1, sizeof(buf1), buf);
|
|
f = tcc_open(buf1);
|
|
if (f)
|
|
goto found;
|
|
}
|
|
/* now search in standard include path */
|
|
for(i=nb_include_paths - 1;i>=0;i--) {
|
|
strcpy(buf1, include_paths[i]);
|
|
strcat(buf1, "/");
|
|
strcat(buf1, buf);
|
|
f = tcc_open(buf1);
|
|
if (f)
|
|
goto found;
|
|
}
|
|
error("include file '%s' not found", buf);
|
|
f = NULL;
|
|
found:
|
|
/* push current file in stack */
|
|
/* XXX: fix current line init */
|
|
*include_stack_ptr++ = file;
|
|
file = f;
|
|
/* add include file debug info */
|
|
if (do_debug) {
|
|
put_stabs(file->filename, N_BINCL, 0, 0, 0);
|
|
}
|
|
ch = '\n';
|
|
goto the_end;
|
|
} else if (tok == TOK_IFNDEF) {
|
|
c = 1;
|
|
goto do_ifdef;
|
|
} else if (tok == TOK_IF) {
|
|
c = expr_preprocess();
|
|
goto do_if;
|
|
} else if (tok == TOK_IFDEF) {
|
|
c = 0;
|
|
do_ifdef:
|
|
next_nomacro();
|
|
c = (sym_find1(&define_stack, tok) != 0) ^ c;
|
|
do_if:
|
|
if (ifdef_stack_ptr >= ifdef_stack + IFDEF_STACK_SIZE)
|
|
error("memory full");
|
|
*ifdef_stack_ptr++ = c;
|
|
goto test_skip;
|
|
} else if (tok == TOK_ELSE) {
|
|
if (ifdef_stack_ptr == ifdef_stack)
|
|
error("#else without matching #if");
|
|
if (ifdef_stack_ptr[-1] & 2)
|
|
error("#else after #else");
|
|
c = (ifdef_stack_ptr[-1] ^= 3);
|
|
goto test_skip;
|
|
} else if (tok == TOK_ELIF) {
|
|
if (ifdef_stack_ptr == ifdef_stack)
|
|
error("#elif without matching #if");
|
|
c = ifdef_stack_ptr[-1];
|
|
if (c > 1)
|
|
error("#elif after #else");
|
|
/* last #if/#elif expression was true: we skip */
|
|
if (c == 1)
|
|
goto skip;
|
|
c = expr_preprocess();
|
|
ifdef_stack_ptr[-1] = c;
|
|
test_skip:
|
|
if (!(c & 1)) {
|
|
skip:
|
|
preprocess_skip();
|
|
goto redo;
|
|
}
|
|
} else if (tok == TOK_ENDIF) {
|
|
if (ifdef_stack_ptr == ifdef_stack)
|
|
error("#endif without matching #if");
|
|
ifdef_stack_ptr--;
|
|
} else if (tok == TOK_LINE) {
|
|
next();
|
|
if (tok != TOK_CINT)
|
|
error("#line");
|
|
file->line_num = tokc.i;
|
|
skip_spaces();
|
|
if (ch != '\n') {
|
|
next();
|
|
if (tok != TOK_STR)
|
|
error("#line");
|
|
pstrcpy(file->filename, sizeof(file->filename),
|
|
get_tok_str(tok, &tokc));
|
|
}
|
|
} else if (tok == TOK_ERROR) {
|
|
error("#error");
|
|
}
|
|
/* ignore other preprocess commands or #! for C scripts */
|
|
while (tok != TOK_LINEFEED && tok != TOK_EOF)
|
|
next_nomacro();
|
|
the_end:
|
|
return_linefeed = 0;
|
|
}
|
|
|
|
/* read a number in base b */
|
|
static int getn(int b)
|
|
{
|
|
int n, t;
|
|
n = 0;
|
|
while (1) {
|
|
if (ch >= 'a' && ch <= 'f')
|
|
t = ch - 'a' + 10;
|
|
else if (ch >= 'A' && ch <= 'F')
|
|
t = ch - 'A' + 10;
|
|
else if (isnum(ch))
|
|
t = ch - '0';
|
|
else
|
|
break;
|
|
if (t < 0 || t >= b)
|
|
break;
|
|
n = n * b + t;
|
|
cinp();
|
|
}
|
|
return n;
|
|
}
|
|
|
|
/* read a character for string or char constant and eval escape codes */
|
|
static int getq(void)
|
|
{
|
|
int c;
|
|
|
|
c = ch;
|
|
minp();
|
|
if (c == '\\') {
|
|
if (isnum(ch)) {
|
|
/* at most three octal digits */
|
|
c = ch - '0';
|
|
minp();
|
|
if (isnum(ch)) {
|
|
c = c * 8 + ch - '0';
|
|
minp();
|
|
if (isnum(ch)) {
|
|
c = c * 8 + ch - '0';
|
|
minp();
|
|
}
|
|
}
|
|
return c;
|
|
} else if (ch == 'x') {
|
|
minp();
|
|
return getn(16);
|
|
} else {
|
|
if (ch == 'a')
|
|
c = '\a';
|
|
else if (ch == 'b')
|
|
c = '\b';
|
|
else if (ch == 'f')
|
|
c = '\f';
|
|
else if (ch == 'n')
|
|
c = '\n';
|
|
else if (ch == 'r')
|
|
c = '\r';
|
|
else if (ch == 't')
|
|
c = '\t';
|
|
else if (ch == 'v')
|
|
c = '\v';
|
|
else if (ch == 'e' && gnu_ext)
|
|
c = 27;
|
|
else if (ch == '\'' || ch == '\"' || ch == '\\' || ch == '?')
|
|
c = ch;
|
|
else
|
|
error("invalid escaped char");
|
|
minp();
|
|
}
|
|
}
|
|
return c;
|
|
}
|
|
|
|
/* we use 64 bit numbers */
|
|
#define BN_SIZE 2
|
|
|
|
/* bn = (bn << shift) | or_val */
|
|
void bn_lshift(unsigned int *bn, int shift, int or_val)
|
|
{
|
|
int i;
|
|
unsigned int v;
|
|
for(i=0;i<BN_SIZE;i++) {
|
|
v = bn[i];
|
|
bn[i] = (v << shift) | or_val;
|
|
or_val = v >> (32 - shift);
|
|
}
|
|
}
|
|
|
|
void bn_zero(unsigned int *bn)
|
|
{
|
|
int i;
|
|
for(i=0;i<BN_SIZE;i++) {
|
|
bn[i] = 0;
|
|
}
|
|
}
|
|
|
|
void parse_number(void)
|
|
{
|
|
int b, t, shift, frac_bits, s, exp_val;
|
|
char *q;
|
|
unsigned int bn[BN_SIZE];
|
|
double d;
|
|
|
|
/* number */
|
|
q = token_buf;
|
|
t = ch;
|
|
cinp();
|
|
*q++ = t;
|
|
b = 10;
|
|
if (t == '.') {
|
|
/* special dot handling */
|
|
if (ch >= '0' && ch <= '9') {
|
|
goto float_frac_parse;
|
|
} else if (ch == '.') {
|
|
cinp();
|
|
if (ch != '.')
|
|
expect("'.'");
|
|
cinp();
|
|
tok = TOK_DOTS;
|
|
} else {
|
|
/* dots */
|
|
tok = t;
|
|
}
|
|
return;
|
|
} else if (t == '0') {
|
|
if (ch == 'x' || ch == 'X') {
|
|
q--;
|
|
cinp();
|
|
b = 16;
|
|
} else if (tcc_ext && (ch == 'b' || ch == 'B')) {
|
|
q--;
|
|
cinp();
|
|
b = 2;
|
|
}
|
|
}
|
|
/* parse all digits. cannot check octal numbers at this stage
|
|
because of floating point constants */
|
|
while (1) {
|
|
if (ch >= 'a' && ch <= 'f')
|
|
t = ch - 'a' + 10;
|
|
else if (ch >= 'A' && ch <= 'F')
|
|
t = ch - 'A' + 10;
|
|
else if (isnum(ch))
|
|
t = ch - '0';
|
|
else
|
|
break;
|
|
if (t >= b)
|
|
break;
|
|
if (q >= token_buf + STRING_MAX_SIZE) {
|
|
num_too_long:
|
|
error("number too long");
|
|
}
|
|
*q++ = ch;
|
|
cinp();
|
|
}
|
|
if (ch == '.' ||
|
|
((ch == 'e' || ch == 'E') && b == 10) ||
|
|
((ch == 'p' || ch == 'P') && (b == 16 || b == 2))) {
|
|
if (b != 10) {
|
|
/* NOTE: strtox should support that for hexa numbers, but
|
|
non ISOC99 libcs do not support it, so we prefer to do
|
|
it by hand */
|
|
/* hexadecimal or binary floats */
|
|
/* XXX: handle overflows */
|
|
*q = '\0';
|
|
if (b == 16)
|
|
shift = 4;
|
|
else
|
|
shift = 2;
|
|
bn_zero(bn);
|
|
q = token_buf;
|
|
while (1) {
|
|
t = *q++;
|
|
if (t == '\0') {
|
|
break;
|
|
} else if (t >= 'a') {
|
|
t = t - 'a' + 10;
|
|
} else if (t >= 'A') {
|
|
t = t - 'A' + 10;
|
|
} else {
|
|
t = t - '0';
|
|
}
|
|
bn_lshift(bn, shift, t);
|
|
}
|
|
frac_bits = 0;
|
|
if (ch == '.') {
|
|
cinp();
|
|
while (1) {
|
|
t = ch;
|
|
if (t >= 'a' && t <= 'f') {
|
|
t = t - 'a' + 10;
|
|
} else if (t >= 'A' && t <= 'F') {
|
|
t = t - 'A' + 10;
|
|
} else if (t >= '0' && t <= '9') {
|
|
t = t - '0';
|
|
} else {
|
|
break;
|
|
}
|
|
if (t >= b)
|
|
error("invalid digit");
|
|
bn_lshift(bn, shift, t);
|
|
frac_bits += shift;
|
|
cinp();
|
|
}
|
|
}
|
|
if (ch != 'p' && ch != 'P')
|
|
error("exponent expected");
|
|
cinp();
|
|
s = 1;
|
|
exp_val = 0;
|
|
if (ch == '+') {
|
|
cinp();
|
|
} else if (ch == '-') {
|
|
s = -1;
|
|
cinp();
|
|
}
|
|
if (ch < '0' || ch > '9')
|
|
error("exponent digits expected");
|
|
while (ch >= '0' && ch <= '9') {
|
|
exp_val = exp_val * 10 + ch - '0';
|
|
cinp();
|
|
}
|
|
exp_val = exp_val * s;
|
|
|
|
/* now we can generate the number */
|
|
/* XXX: should patch directly float number */
|
|
d = (double)bn[1] * 4294967296.0 + (double)bn[0];
|
|
d = ldexp(d, exp_val - frac_bits);
|
|
t = toup(ch);
|
|
if (t == 'F') {
|
|
cinp();
|
|
tok = TOK_CFLOAT;
|
|
/* float : should handle overflow */
|
|
tokc.f = (float)d;
|
|
} else if (t == 'L') {
|
|
cinp();
|
|
tok = TOK_CLDOUBLE;
|
|
/* XXX: not large enough */
|
|
tokc.ld = (long double)d;
|
|
} else {
|
|
tok = TOK_CDOUBLE;
|
|
tokc.d = d;
|
|
}
|
|
} else {
|
|
/* decimal floats */
|
|
if (ch == '.') {
|
|
if (q >= token_buf + STRING_MAX_SIZE)
|
|
goto num_too_long;
|
|
*q++ = ch;
|
|
cinp();
|
|
float_frac_parse:
|
|
while (ch >= '0' && ch <= '9') {
|
|
if (q >= token_buf + STRING_MAX_SIZE)
|
|
goto num_too_long;
|
|
*q++ = ch;
|
|
cinp();
|
|
}
|
|
}
|
|
if (ch == 'e' || ch == 'E') {
|
|
if (q >= token_buf + STRING_MAX_SIZE)
|
|
goto num_too_long;
|
|
*q++ = ch;
|
|
cinp();
|
|
if (ch == '-' || ch == '+') {
|
|
if (q >= token_buf + STRING_MAX_SIZE)
|
|
goto num_too_long;
|
|
*q++ = ch;
|
|
cinp();
|
|
}
|
|
if (ch < '0' || ch > '9')
|
|
error("exponent digits expected");
|
|
while (ch >= '0' && ch <= '9') {
|
|
if (q >= token_buf + STRING_MAX_SIZE)
|
|
goto num_too_long;
|
|
*q++ = ch;
|
|
cinp();
|
|
}
|
|
}
|
|
*q = '\0';
|
|
t = toup(ch);
|
|
errno = 0;
|
|
if (t == 'F') {
|
|
cinp();
|
|
tok = TOK_CFLOAT;
|
|
tokc.f = strtof(token_buf, NULL);
|
|
} else if (t == 'L') {
|
|
cinp();
|
|
tok = TOK_CLDOUBLE;
|
|
tokc.ld = strtold(token_buf, NULL);
|
|
} else {
|
|
tok = TOK_CDOUBLE;
|
|
tokc.d = strtod(token_buf, NULL);
|
|
}
|
|
}
|
|
} else {
|
|
unsigned long long n, n1;
|
|
int lcount;
|
|
|
|
/* integer number */
|
|
*q = '\0';
|
|
q = token_buf;
|
|
if (b == 10 && *q == '0') {
|
|
b = 8;
|
|
q++;
|
|
}
|
|
n = 0;
|
|
while(1) {
|
|
t = *q++;
|
|
/* no need for checks except for base 10 / 8 errors */
|
|
if (t == '\0') {
|
|
break;
|
|
} else if (t >= 'a') {
|
|
t = t - 'a' + 10;
|
|
} else if (t >= 'A') {
|
|
t = t - 'A' + 10;
|
|
} else {
|
|
t = t - '0';
|
|
if (t >= b)
|
|
error("invalid digit");
|
|
}
|
|
n1 = n;
|
|
n = n * b + t;
|
|
/* detect overflow */
|
|
if (n < n1)
|
|
error("integer constant overflow");
|
|
}
|
|
|
|
/* XXX: not exactly ANSI compliant */
|
|
if ((n & 0xffffffff00000000LL) != 0) {
|
|
if ((n >> 63) != 0)
|
|
tok = TOK_CULLONG;
|
|
else
|
|
tok = TOK_CLLONG;
|
|
} else if (n > 0x7fffffff) {
|
|
tok = TOK_CUINT;
|
|
} else {
|
|
tok = TOK_CINT;
|
|
}
|
|
lcount = 0;
|
|
for(;;) {
|
|
t = toup(ch);
|
|
if (t == 'L') {
|
|
if (lcount >= 2)
|
|
error("three 'l' in integer constant");
|
|
lcount++;
|
|
if (lcount == 2) {
|
|
if (tok == TOK_CINT)
|
|
tok = TOK_CLLONG;
|
|
else if (tok == TOK_CUINT)
|
|
tok = TOK_CULLONG;
|
|
}
|
|
cinp();
|
|
} else if (t == 'U') {
|
|
if (tok == TOK_CINT)
|
|
tok = TOK_CUINT;
|
|
else if (tok == TOK_CLLONG)
|
|
tok = TOK_CULLONG;
|
|
cinp();
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
if (tok == TOK_CINT || tok == TOK_CUINT)
|
|
tokc.ui = n;
|
|
else
|
|
tokc.ull = n;
|
|
}
|
|
}
|
|
|
|
|
|
/* return next token without macro substitution */
|
|
void next_nomacro1(void)
|
|
{
|
|
int b;
|
|
char *q;
|
|
TokenSym *ts;
|
|
|
|
/* skip spaces */
|
|
while(1) {
|
|
while (ch == '\n') {
|
|
/* during preprocessor parsing, '\n' is a token */
|
|
if (return_linefeed) {
|
|
tok = TOK_LINEFEED;
|
|
return;
|
|
}
|
|
cinp();
|
|
while (ch == ' ' || ch == '\t')
|
|
cinp();
|
|
if (ch == '#') {
|
|
/* preprocessor command if # at start of line after
|
|
spaces */
|
|
preprocess();
|
|
}
|
|
}
|
|
if (ch != ' ' && ch != '\t' && ch != '\f')
|
|
break;
|
|
cinp();
|
|
}
|
|
if (isid(ch)) {
|
|
q = token_buf;
|
|
*q++ = ch;
|
|
cinp();
|
|
if (q[-1] == 'L') {
|
|
if (ch == '\'') {
|
|
tok = TOK_LCHAR;
|
|
goto char_const;
|
|
}
|
|
if (ch == '\"') {
|
|
tok = TOK_LSTR;
|
|
goto str_const;
|
|
}
|
|
}
|
|
while (isid(ch) || isnum(ch)) {
|
|
if (q >= token_buf + STRING_MAX_SIZE)
|
|
error("ident too long");
|
|
*q++ = ch;
|
|
cinp();
|
|
}
|
|
*q = '\0';
|
|
ts = tok_alloc(token_buf, q - token_buf);
|
|
tok = ts->tok;
|
|
} else if (isnum(ch) || ch == '.') {
|
|
parse_number();
|
|
} else if (ch == '\'') {
|
|
tok = TOK_CCHAR;
|
|
char_const:
|
|
minp();
|
|
b = getq();
|
|
/* this cast is needed if >= 128 */
|
|
if (tok == TOK_CCHAR)
|
|
b = (char)b;
|
|
tokc.i = b;
|
|
if (ch != '\'')
|
|
expect("\'");
|
|
minp();
|
|
} else if (ch == '\"') {
|
|
tok = TOK_STR;
|
|
str_const:
|
|
minp();
|
|
q = token_buf;
|
|
while (ch != '\"') {
|
|
b = getq();
|
|
if (ch == -1)
|
|
error("unterminated string");
|
|
if (q >= token_buf + STRING_MAX_SIZE)
|
|
error("string too long");
|
|
*q++ = b;
|
|
}
|
|
*q = '\0';
|
|
tokc.ts = tok_alloc(token_buf, q - token_buf);
|
|
minp();
|
|
} else {
|
|
q = "<=\236>=\235!=\225&&\240||\241++\244--\242==\224<<\1>>\2+=\253-=\255*=\252/=\257%=\245&=\246^=\336|=\374->\313..\250##\266";
|
|
/* two chars */
|
|
tok = ch;
|
|
cinp();
|
|
while (*q) {
|
|
if (*q == tok && q[1] == ch) {
|
|
cinp();
|
|
tok = q[2] & 0xff;
|
|
/* three chars tests */
|
|
if (tok == TOK_SHL || tok == TOK_SAR) {
|
|
if (ch == '=') {
|
|
tok = tok | 0x80;
|
|
cinp();
|
|
}
|
|
} else if (tok == TOK_DOTS) {
|
|
if (ch != '.')
|
|
error("parse error");
|
|
cinp();
|
|
}
|
|
return;
|
|
}
|
|
q = q + 3;
|
|
}
|
|
/* single char substitutions */
|
|
if (tok == '<')
|
|
tok = TOK_LT;
|
|
else if (tok == '>')
|
|
tok = TOK_GT;
|
|
}
|
|
}
|
|
|
|
/* return next token without macro substitution. Can read input from
|
|
macro_ptr buffer */
|
|
void next_nomacro()
|
|
{
|
|
if (macro_ptr) {
|
|
redo:
|
|
tok = *macro_ptr;
|
|
if (tok) {
|
|
tok = tok_get(¯o_ptr, &tokc);
|
|
if (tok == TOK_LINENUM) {
|
|
file->line_num = tokc.i;
|
|
goto redo;
|
|
}
|
|
}
|
|
} else {
|
|
next_nomacro1();
|
|
}
|
|
}
|
|
|
|
/* substitute args in macro_str and return allocated string */
|
|
int *macro_arg_subst(Sym **nested_list, int *macro_str, Sym *args)
|
|
{
|
|
int *st, last_tok, t, notfirst;
|
|
Sym *s;
|
|
TokenSym *ts;
|
|
CValue cval;
|
|
TokenString str;
|
|
|
|
tok_str_new(&str);
|
|
last_tok = 0;
|
|
while(1) {
|
|
t = tok_get(¯o_str, &cval);
|
|
if (!t)
|
|
break;
|
|
if (t == '#') {
|
|
/* stringize */
|
|
t = tok_get(¯o_str, &cval);
|
|
if (!t)
|
|
break;
|
|
s = sym_find2(args, t);
|
|
if (s) {
|
|
token_buf[0] = '\0';
|
|
st = (int *)s->c;
|
|
notfirst = 0;
|
|
while (*st) {
|
|
if (notfirst)
|
|
pstrcat(token_buf, sizeof(token_buf), " ");
|
|
t = tok_get(&st, &cval);
|
|
pstrcat(token_buf, sizeof(token_buf), get_tok_str(t, &cval));
|
|
notfirst = 1;
|
|
}
|
|
#ifdef PP_DEBUG
|
|
printf("stringize: %s\n", token_buf);
|
|
#endif
|
|
/* add string */
|
|
ts = tok_alloc(token_buf, 0);
|
|
cval.ts = ts;
|
|
tok_str_add2(&str, TOK_STR, &cval);
|
|
} else {
|
|
tok_str_add2(&str, t, &cval);
|
|
}
|
|
} else if (t >= TOK_IDENT) {
|
|
s = sym_find2(args, t);
|
|
if (s) {
|
|
st = (int *)s->c;
|
|
/* if '##' is present before or after, no arg substitution */
|
|
if (*macro_str == TOK_TWOSHARPS || last_tok == TOK_TWOSHARPS) {
|
|
/* special case for var arg macros : ## eats the
|
|
',' if empty VA_ARGS riable. */
|
|
/* XXX: test of the ',' is not 100%
|
|
reliable. should fix it to avoid security
|
|
problems */
|
|
if (gnu_ext && s->t && *st == 0 &&
|
|
last_tok == TOK_TWOSHARPS &&
|
|
str.len >= 2&& str.str[str.len - 2] == ',') {
|
|
/* suppress ',' '##' */
|
|
str.len -= 2;
|
|
} else {
|
|
while (*st)
|
|
tok_str_add(&str, *st++);
|
|
}
|
|
} else {
|
|
macro_subst(&str, nested_list, st);
|
|
}
|
|
} else {
|
|
tok_str_add(&str, t);
|
|
}
|
|
} else {
|
|
tok_str_add2(&str, t, &cval);
|
|
}
|
|
last_tok = t;
|
|
}
|
|
tok_str_add(&str, 0);
|
|
return str.str;
|
|
}
|
|
|
|
/* handle the '##' operator */
|
|
int *macro_twosharps(int *macro_str)
|
|
{
|
|
TokenSym *ts;
|
|
int *macro_ptr1;
|
|
int t;
|
|
char *p;
|
|
CValue cval;
|
|
TokenString macro_str1;
|
|
|
|
tok_str_new(¯o_str1);
|
|
tok = 0;
|
|
while (1) {
|
|
next_nomacro();
|
|
if (tok == 0)
|
|
break;
|
|
while (*macro_ptr == TOK_TWOSHARPS) {
|
|
macro_ptr++;
|
|
macro_ptr1 = macro_ptr;
|
|
t = *macro_ptr;
|
|
if (t) {
|
|
t = tok_get(¯o_ptr, &cval);
|
|
/* XXX: we handle only most common cases:
|
|
ident + ident or ident + number */
|
|
if (tok >= TOK_IDENT &&
|
|
(t >= TOK_IDENT || t == TOK_CINT)) {
|
|
p = get_tok_str(tok, &tokc);
|
|
pstrcpy(token_buf, sizeof(token_buf), p);
|
|
p = get_tok_str(t, &cval);
|
|
pstrcat(token_buf, sizeof(token_buf), p);
|
|
ts = tok_alloc(token_buf, 0);
|
|
tok = ts->tok; /* modify current token */
|
|
} else {
|
|
/* cannot merge tokens: skip '##' */
|
|
macro_ptr = macro_ptr1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
tok_str_add2(¯o_str1, tok, &tokc);
|
|
}
|
|
tok_str_add(¯o_str1, 0);
|
|
return macro_str1.str;
|
|
}
|
|
|
|
/* do macro substitution of macro_str and add result to
|
|
(tok_str,tok_len). If macro_str is NULL, then input stream token is
|
|
substituted. 'nested_list' is the list of all macros we got inside
|
|
to avoid recursing. */
|
|
void macro_subst(TokenString *tok_str,
|
|
Sym **nested_list, int *macro_str)
|
|
{
|
|
Sym *s, *args, *sa, *sa1;
|
|
int parlevel, *mstr, t, *saved_macro_ptr;
|
|
int mstr_allocated, *macro_str1;
|
|
CValue cval;
|
|
TokenString str;
|
|
|
|
saved_macro_ptr = macro_ptr;
|
|
macro_ptr = macro_str;
|
|
macro_str1 = NULL;
|
|
if (macro_str) {
|
|
/* first scan for '##' operator handling */
|
|
macro_str1 = macro_twosharps(macro_str);
|
|
macro_ptr = macro_str1;
|
|
}
|
|
|
|
while (1) {
|
|
next_nomacro();
|
|
if (tok == 0)
|
|
break;
|
|
/* special macros */
|
|
if (tok == TOK___LINE__) {
|
|
cval.i = file->line_num;
|
|
tok_str_add2(tok_str, TOK_CINT, &cval);
|
|
} else if (tok == TOK___FILE__) {
|
|
cval.ts = tok_alloc(file->filename, 0);
|
|
tok_str_add2(tok_str, TOK_STR, &cval);
|
|
} else if (tok == TOK___DATE__) {
|
|
cval.ts = tok_alloc("Jan 1 1970", 0);
|
|
tok_str_add2(tok_str, TOK_STR, &cval);
|
|
} else if (tok == TOK___TIME__) {
|
|
cval.ts = tok_alloc("00:00:00", 0);
|
|
tok_str_add2(tok_str, TOK_STR, &cval);
|
|
} else if ((s = sym_find1(&define_stack, tok)) != NULL) {
|
|
/* if symbol is a macro, prepare substitution */
|
|
/* if nested substitution, do nothing */
|
|
if (sym_find2(*nested_list, tok))
|
|
goto no_subst;
|
|
mstr = (int *)s->c;
|
|
mstr_allocated = 0;
|
|
if (s->t == MACRO_FUNC) {
|
|
/* NOTE: we do not use next_nomacro to avoid eating the
|
|
next token. XXX: find better solution */
|
|
if (macro_ptr) {
|
|
t = *macro_ptr;
|
|
} else {
|
|
while (ch == ' ' || ch == '\t' || ch == '\n')
|
|
cinp();
|
|
t = ch;
|
|
}
|
|
if (t != '(') /* no macro subst */
|
|
goto no_subst;
|
|
|
|
/* argument macro */
|
|
next_nomacro();
|
|
next_nomacro();
|
|
args = NULL;
|
|
sa = s->next;
|
|
/* NOTE: empty args are allowed, except if no args */
|
|
for(;;) {
|
|
/* handle '()' case */
|
|
if (!args && tok == ')')
|
|
break;
|
|
if (!sa)
|
|
error("macro '%s' used with too many args",
|
|
get_tok_str(s->v, 0));
|
|
tok_str_new(&str);
|
|
parlevel = 0;
|
|
/* NOTE: non zero sa->t indicates VA_ARGS */
|
|
while ((parlevel > 0 ||
|
|
(tok != ')' &&
|
|
(tok != ',' || sa->t))) &&
|
|
tok != -1) {
|
|
if (tok == '(')
|
|
parlevel++;
|
|
else if (tok == ')')
|
|
parlevel--;
|
|
tok_str_add2(&str, tok, &tokc);
|
|
next_nomacro();
|
|
}
|
|
tok_str_add(&str, 0);
|
|
sym_push2(&args, sa->v & ~SYM_FIELD, sa->t, (int)str.str);
|
|
sa = sa->next;
|
|
if (tok == ')') {
|
|
/* special case for gcc var args: add an empty
|
|
var arg argument if it is omitted */
|
|
if (sa && sa->t && gnu_ext)
|
|
continue;
|
|
else
|
|
break;
|
|
}
|
|
if (tok != ',')
|
|
expect(",");
|
|
next_nomacro();
|
|
}
|
|
if (sa) {
|
|
error("macro '%s' used with too few args",
|
|
get_tok_str(s->v, 0));
|
|
}
|
|
|
|
/* now subst each arg */
|
|
mstr = macro_arg_subst(nested_list, mstr, args);
|
|
/* free memory */
|
|
sa = args;
|
|
while (sa) {
|
|
sa1 = sa->prev;
|
|
free((int *)sa->c);
|
|
free(sa);
|
|
sa = sa1;
|
|
}
|
|
mstr_allocated = 1;
|
|
}
|
|
sym_push2(nested_list, s->v, 0, 0);
|
|
macro_subst(tok_str, nested_list, mstr);
|
|
/* pop nested defined symbol */
|
|
sa1 = *nested_list;
|
|
*nested_list = sa1->prev;
|
|
free(sa1);
|
|
if (mstr_allocated)
|
|
free(mstr);
|
|
} else {
|
|
no_subst:
|
|
/* no need to add if reading input stream */
|
|
if (!macro_str)
|
|
return;
|
|
tok_str_add2(tok_str, tok, &tokc);
|
|
}
|
|
/* only replace one macro while parsing input stream */
|
|
if (!macro_str)
|
|
return;
|
|
}
|
|
macro_ptr = saved_macro_ptr;
|
|
if (macro_str1)
|
|
free(macro_str1);
|
|
}
|
|
|
|
/* return next token with macro substitution */
|
|
void next(void)
|
|
{
|
|
Sym *nested_list;
|
|
TokenString str;
|
|
|
|
/* special 'ungettok' case for label parsing */
|
|
if (tok1) {
|
|
tok = tok1;
|
|
tokc = tok1c;
|
|
tok1 = 0;
|
|
} else {
|
|
redo:
|
|
if (!macro_ptr) {
|
|
/* if not reading from macro substituted string, then try
|
|
to substitute */
|
|
/* XXX: optimize non macro case */
|
|
tok_str_new(&str);
|
|
nested_list = NULL;
|
|
macro_subst(&str, &nested_list, NULL);
|
|
if (str.str) {
|
|
tok_str_add(&str, 0);
|
|
macro_ptr = str.str;
|
|
macro_ptr_allocated = str.str;
|
|
goto redo;
|
|
}
|
|
if (tok == 0)
|
|
goto redo;
|
|
} else {
|
|
next_nomacro();
|
|
if (tok == 0) {
|
|
/* end of macro string: free it */
|
|
free(macro_ptr_allocated);
|
|
macro_ptr = NULL;
|
|
goto redo;
|
|
}
|
|
}
|
|
}
|
|
#if defined(DEBUG)
|
|
printf("token = %s\n", get_tok_str(tok, &tokc));
|
|
#endif
|
|
}
|
|
|
|
void swap(int *p, int *q)
|
|
{
|
|
int t;
|
|
t = *p;
|
|
*p = *q;
|
|
*q = t;
|
|
}
|
|
|
|
void vsetc(int t, int r, CValue *vc)
|
|
{
|
|
if (vtop >= vstack + VSTACK_SIZE)
|
|
error("memory full");
|
|
/* cannot let cpu flags if other instruction are generated */
|
|
/* XXX: VT_JMP test too ? */
|
|
if ((vtop->r & VT_VALMASK) == VT_CMP)
|
|
gv(RC_INT);
|
|
vtop++;
|
|
vtop->t = t;
|
|
vtop->r = r;
|
|
vtop->r2 = VT_CONST;
|
|
vtop->c = *vc;
|
|
}
|
|
|
|
/* push integer constant */
|
|
void vpushi(int v)
|
|
{
|
|
CValue cval;
|
|
cval.i = v;
|
|
vsetc(VT_INT, VT_CONST, &cval);
|
|
}
|
|
|
|
/* Return a static symbol pointing to a section */
|
|
static Sym *get_sym_ref(int t, Section *sec,
|
|
unsigned long offset, unsigned long size)
|
|
{
|
|
int v;
|
|
Sym *sym;
|
|
|
|
v = anon_sym++;
|
|
sym = sym_push1(&global_stack, v, t | VT_STATIC, 0);
|
|
sym->r = VT_CONST | VT_SYM;
|
|
put_extern_sym(sym, sec, offset);
|
|
return sym;
|
|
}
|
|
|
|
/* push a reference to a section offset by adding a dummy symbol */
|
|
/* XXX: add size */
|
|
void vpush_ref(int t, Section *sec, unsigned long offset)
|
|
{
|
|
CValue cval;
|
|
|
|
cval.sym = get_sym_ref(t, sec, offset, 0);
|
|
vsetc(t, VT_CONST | VT_SYM, &cval);
|
|
}
|
|
|
|
/* push a reference to symbol v */
|
|
void vpush_sym(int t, int v)
|
|
{
|
|
Sym *sym;
|
|
CValue cval;
|
|
|
|
sym = external_sym(v, t, 0);
|
|
cval.sym = sym;
|
|
vsetc(t, VT_CONST | VT_SYM, &cval);
|
|
}
|
|
|
|
void vset(int t, int r, int v)
|
|
{
|
|
CValue cval;
|
|
|
|
cval.i = v;
|
|
vsetc(t, r, &cval);
|
|
}
|
|
|
|
void vswap(void)
|
|
{
|
|
SValue tmp;
|
|
|
|
tmp = vtop[0];
|
|
vtop[0] = vtop[-1];
|
|
vtop[-1] = tmp;
|
|
}
|
|
|
|
void vpushv(SValue *v)
|
|
{
|
|
if (vtop >= vstack + VSTACK_SIZE)
|
|
error("memory full");
|
|
vtop++;
|
|
*vtop = *v;
|
|
}
|
|
|
|
void vdup(void)
|
|
{
|
|
vpushv(vtop);
|
|
}
|
|
|
|
/* save r to the memory stack, and mark it as being free */
|
|
void save_reg(int r)
|
|
{
|
|
int l, i, saved, t, size, align;
|
|
SValue *p, sv;
|
|
|
|
/* modify all stack values */
|
|
saved = 0;
|
|
l = 0;
|
|
for(p=vstack;p<=vtop;p++) {
|
|
i = p->r & VT_VALMASK;
|
|
if ((p->r & VT_VALMASK) == r ||
|
|
(p->r2 & VT_VALMASK) == r) {
|
|
/* must save value on stack if not already done */
|
|
if (!saved) {
|
|
/* store register in the stack */
|
|
t = p->t;
|
|
if ((p->r & VT_LVAL) ||
|
|
(!is_float(t) && (t & VT_BTYPE) != VT_LLONG))
|
|
t = VT_INT;
|
|
size = type_size(t, &align);
|
|
loc = (loc - size) & -align;
|
|
sv.t = t;
|
|
sv.r = VT_LOCAL | VT_LVAL;
|
|
sv.c.ul = loc;
|
|
store(r, &sv);
|
|
#ifdef TCC_TARGET_I386
|
|
/* x86 specific: need to pop fp register ST0 if saved */
|
|
if (r == REG_ST0) {
|
|
o(0xd9dd); /* fstp %st(1) */
|
|
}
|
|
#endif
|
|
/* special long long case */
|
|
if ((t & VT_BTYPE) == VT_LLONG) {
|
|
sv.c.ul += 4;
|
|
store(p->r2, &sv);
|
|
}
|
|
l = loc;
|
|
saved = 1;
|
|
}
|
|
/* mark that stack entry as being saved on the stack */
|
|
if (p->r & VT_LVAL)
|
|
t = VT_LLOCAL;
|
|
else
|
|
t = VT_LOCAL;
|
|
p->r = VT_LVAL | t;
|
|
p->r2 = VT_CONST;
|
|
p->c.ul = l;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* find a free register of class 'rc'. If none, save one register */
|
|
int get_reg(int rc)
|
|
{
|
|
int r;
|
|
SValue *p;
|
|
|
|
/* find a free register */
|
|
for(r=0;r<NB_REGS;r++) {
|
|
if (reg_classes[r] & rc) {
|
|
for(p=vstack;p<=vtop;p++) {
|
|
if ((p->r & VT_VALMASK) == r ||
|
|
(p->r2 & VT_VALMASK) == r)
|
|
goto notfound;
|
|
}
|
|
return r;
|
|
}
|
|
notfound: ;
|
|
}
|
|
|
|
/* no register left : free the first one on the stack (VERY
|
|
IMPORTANT to start from the bottom to ensure that we don't
|
|
spill registers used in gen_opi()) */
|
|
for(p=vstack;p<=vtop;p++) {
|
|
r = p->r & VT_VALMASK;
|
|
if (r < VT_CONST && (reg_classes[r] & rc)) {
|
|
save_reg(r);
|
|
break;
|
|
}
|
|
}
|
|
return r;
|
|
}
|
|
|
|
/* save registers up to (vtop - n) stack entry */
|
|
void save_regs(int n)
|
|
{
|
|
int r;
|
|
SValue *p, *p1;
|
|
p1 = vtop - n;
|
|
for(p = vstack;p <= p1; p++) {
|
|
r = p->r & VT_VALMASK;
|
|
if (r < VT_CONST) {
|
|
save_reg(r);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* move register 's' to 'r', and flush previous value of r to memory
|
|
if needed */
|
|
void move_reg(int r, int s)
|
|
{
|
|
SValue sv;
|
|
|
|
if (r != s) {
|
|
save_reg(r);
|
|
sv.t = VT_INT;
|
|
sv.r = s;
|
|
sv.c.ul = 0;
|
|
load(r, &sv);
|
|
}
|
|
}
|
|
|
|
/* get address of vtop (vtop MUST BE an lvalue) */
|
|
void gaddrof(void)
|
|
{
|
|
vtop->r &= ~VT_LVAL;
|
|
/* tricky: if saved lvalue, then we can go back to lvalue */
|
|
if ((vtop->r & VT_VALMASK) == VT_LLOCAL)
|
|
vtop->r = (vtop->r & ~VT_VALMASK) | VT_LOCAL | VT_LVAL;
|
|
}
|
|
|
|
#ifdef CONFIG_TCC_BCHECK
|
|
/* generate lvalue bound code */
|
|
void gbound(void)
|
|
{
|
|
int lval_type;
|
|
|
|
vtop->r &= ~VT_MUSTBOUND;
|
|
/* if lvalue, then use checking code before dereferencing */
|
|
if (vtop->r & VT_LVAL) {
|
|
/* if not VT_BOUNDED value, then make one */
|
|
if (!(vtop->r & VT_BOUNDED)) {
|
|
lval_type = vtop->r & (VT_LVAL_TYPE | VT_LVAL);
|
|
gaddrof();
|
|
vpushi(0);
|
|
gen_bounded_ptr_add();
|
|
vtop->r |= lval_type;
|
|
}
|
|
/* then check for dereferencing */
|
|
gen_bounded_ptr_deref();
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/* store vtop a register belonging to class 'rc'. lvalues are
|
|
converted to values. Cannot be used if cannot be converted to
|
|
register value (such as structures). */
|
|
int gv(int rc)
|
|
{
|
|
int r, r2, rc2, bit_pos, bit_size, size, align, i;
|
|
unsigned long long ll;
|
|
|
|
/* NOTE: get_reg can modify vstack[] */
|
|
if (vtop->t & VT_BITFIELD) {
|
|
bit_pos = (vtop->t >> VT_STRUCT_SHIFT) & 0x3f;
|
|
bit_size = (vtop->t >> (VT_STRUCT_SHIFT + 6)) & 0x3f;
|
|
/* remove bit field info to avoid loops */
|
|
vtop->t &= ~(VT_BITFIELD | (-1 << VT_STRUCT_SHIFT));
|
|
/* generate shifts */
|
|
vpushi(32 - (bit_pos + bit_size));
|
|
gen_op(TOK_SHL);
|
|
vpushi(32 - bit_size);
|
|
/* NOTE: transformed to SHR if unsigned */
|
|
gen_op(TOK_SAR);
|
|
r = gv(rc);
|
|
} else {
|
|
if (is_float(vtop->t) &&
|
|
(vtop->r & (VT_VALMASK | VT_LVAL)) == VT_CONST) {
|
|
int v;
|
|
Sym *sym;
|
|
int *ptr;
|
|
|
|
/* XXX: unify with initializers handling ? */
|
|
/* CPUs usually cannot use float constants, so we store them
|
|
generically in data segment */
|
|
size = type_size(vtop->t, &align);
|
|
data_section->data_offset = (data_section->data_offset +
|
|
align - 1) & -align;
|
|
/* XXX: not portable yet */
|
|
ptr = section_ptr(data_section, size);
|
|
size = size >> 2;
|
|
for(i=0;i<size;i++)
|
|
ptr[i] = vtop->c.tab[i];
|
|
|
|
v = anon_sym++;
|
|
sym = sym_push1(&global_stack, v, vtop->t | VT_STATIC, 0);
|
|
sym->r = VT_CONST | VT_SYM;
|
|
put_extern_sym(sym, data_section, data_section->data_offset);
|
|
|
|
vtop->r |= VT_LVAL | VT_SYM;
|
|
vtop->c.sym = sym;
|
|
data_section->data_offset += size << 2;
|
|
}
|
|
#ifdef CONFIG_TCC_BCHECK
|
|
if (vtop->r & VT_MUSTBOUND)
|
|
gbound();
|
|
#endif
|
|
|
|
r = vtop->r & VT_VALMASK;
|
|
/* need to reload if:
|
|
- constant
|
|
- lvalue (need to dereference pointer)
|
|
- already a register, but not in the right class */
|
|
if (r >= VT_CONST ||
|
|
(vtop->r & VT_LVAL) ||
|
|
!(reg_classes[r] & rc) ||
|
|
((vtop->t & VT_BTYPE) == VT_LLONG &&
|
|
!(reg_classes[vtop->r2] & rc))) {
|
|
r = get_reg(rc);
|
|
if ((vtop->t & VT_BTYPE) == VT_LLONG) {
|
|
/* two register type load : expand to two words
|
|
temporarily */
|
|
if ((vtop->r & (VT_VALMASK | VT_LVAL)) == VT_CONST) {
|
|
/* load constant */
|
|
ll = vtop->c.ull;
|
|
vtop->c.ui = ll; /* first word */
|
|
load(r, vtop);
|
|
vtop->r = r; /* save register value */
|
|
vpushi(ll >> 32); /* second word */
|
|
} else if (r >= VT_CONST ||
|
|
(vtop->r & VT_LVAL)) {
|
|
/* load from memory */
|
|
load(r, vtop);
|
|
vdup();
|
|
vtop[-1].r = r; /* save register value */
|
|
/* increment pointer to get second word */
|
|
vtop->t = VT_INT;
|
|
gaddrof();
|
|
vpushi(4);
|
|
gen_op('+');
|
|
vtop->r |= VT_LVAL;
|
|
} else {
|
|
/* move registers */
|
|
load(r, vtop);
|
|
vdup();
|
|
vtop[-1].r = r; /* save register value */
|
|
vtop->r = vtop[-1].r2;
|
|
}
|
|
/* allocate second register */
|
|
rc2 = RC_INT;
|
|
if (rc == RC_IRET)
|
|
rc2 = RC_LRET;
|
|
r2 = get_reg(rc2);
|
|
load(r2, vtop);
|
|
vpop();
|
|
/* write second register */
|
|
vtop->r2 = r2;
|
|
} else if ((vtop->r & VT_LVAL) && !is_float(vtop->t)) {
|
|
int t1, t;
|
|
/* lvalue of scalar type : need to use lvalue type
|
|
because of possible cast */
|
|
t = vtop->t;
|
|
t1 = t;
|
|
/* compute memory access type */
|
|
if (vtop->r & VT_LVAL_BYTE)
|
|
t = VT_BYTE;
|
|
else if (vtop->r & VT_LVAL_SHORT)
|
|
t = VT_SHORT;
|
|
if (vtop->r & VT_LVAL_UNSIGNED)
|
|
t |= VT_UNSIGNED;
|
|
vtop->t = t;
|
|
load(r, vtop);
|
|
/* restore wanted type */
|
|
vtop->t = t1;
|
|
} else {
|
|
/* one register type load */
|
|
load(r, vtop);
|
|
}
|
|
}
|
|
vtop->r = r;
|
|
}
|
|
return r;
|
|
}
|
|
|
|
/* generate vtop[-1] and vtop[0] in resp. classes rc1 and rc2 */
|
|
void gv2(int rc1, int rc2)
|
|
{
|
|
/* generate more generic register first */
|
|
if (rc1 <= rc2) {
|
|
vswap();
|
|
gv(rc1);
|
|
vswap();
|
|
gv(rc2);
|
|
/* test if reload is needed for first register */
|
|
if ((vtop[-1].r & VT_VALMASK) >= VT_CONST) {
|
|
vswap();
|
|
gv(rc1);
|
|
vswap();
|
|
}
|
|
} else {
|
|
gv(rc2);
|
|
vswap();
|
|
gv(rc1);
|
|
vswap();
|
|
/* test if reload is needed for first register */
|
|
if ((vtop[0].r & VT_VALMASK) >= VT_CONST) {
|
|
gv(rc2);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* expand long long on stack in two int registers */
|
|
void lexpand(void)
|
|
{
|
|
int u;
|
|
|
|
u = vtop->t & VT_UNSIGNED;
|
|
gv(RC_INT);
|
|
vdup();
|
|
vtop[0].r = vtop[-1].r2;
|
|
vtop[0].r2 = VT_CONST;
|
|
vtop[-1].r2 = VT_CONST;
|
|
vtop[0].t = VT_INT | u;
|
|
vtop[-1].t = VT_INT | u;
|
|
}
|
|
|
|
/* build a long long from two ints */
|
|
void lbuild(int t)
|
|
{
|
|
gv2(RC_INT, RC_INT);
|
|
vtop[-1].r2 = vtop[0].r;
|
|
vtop[-1].t = t;
|
|
vpop();
|
|
}
|
|
|
|
/* rotate n first stack elements to the bottom */
|
|
void vrotb(int n)
|
|
{
|
|
int i;
|
|
SValue tmp;
|
|
|
|
tmp = vtop[-n + 1];
|
|
for(i=-n+1;i!=0;i++)
|
|
vtop[i] = vtop[i+1];
|
|
vtop[0] = tmp;
|
|
}
|
|
|
|
/* pop stack value */
|
|
void vpop(void)
|
|
{
|
|
int v;
|
|
v = vtop->r & VT_VALMASK;
|
|
#ifdef TCC_TARGET_I386
|
|
/* for x86, we need to pop the FP stack */
|
|
if (v == REG_ST0) {
|
|
o(0xd9dd); /* fstp %st(1) */
|
|
} else
|
|
#endif
|
|
if (v == VT_JMP || v == VT_JMPI) {
|
|
/* need to put correct jump if && or || without test */
|
|
gsym(vtop->c.ul);
|
|
}
|
|
vtop--;
|
|
}
|
|
|
|
/* convert stack entry to register and duplicate its value in another
|
|
register */
|
|
void gv_dup(void)
|
|
{
|
|
int rc, t, r, r1;
|
|
SValue sv;
|
|
|
|
t = vtop->t;
|
|
if ((t & VT_BTYPE) == VT_LLONG) {
|
|
lexpand();
|
|
gv_dup();
|
|
vswap();
|
|
vrotb(3);
|
|
gv_dup();
|
|
vrotb(4);
|
|
/* stack: H L L1 H1 */
|
|
lbuild(t);
|
|
vrotb(3);
|
|
vrotb(3);
|
|
vswap();
|
|
lbuild(t);
|
|
vswap();
|
|
} else {
|
|
/* duplicate value */
|
|
rc = RC_INT;
|
|
sv.t = VT_INT;
|
|
if (is_float(t)) {
|
|
rc = RC_FLOAT;
|
|
sv.t = t;
|
|
}
|
|
r = gv(rc);
|
|
r1 = get_reg(rc);
|
|
sv.r = r;
|
|
sv.c.ul = 0;
|
|
load(r1, &sv); /* move r to r1 */
|
|
vdup();
|
|
/* duplicates value */
|
|
vtop->r = r1;
|
|
}
|
|
}
|
|
|
|
/* generate CPU independent (unsigned) long long operations */
|
|
void gen_opl(int op)
|
|
{
|
|
int t, a, b, op1, c, i;
|
|
int func;
|
|
GFuncContext gf;
|
|
SValue tmp;
|
|
|
|
switch(op) {
|
|
case '/':
|
|
case TOK_PDIV:
|
|
func = TOK___divdi3;
|
|
goto gen_func;
|
|
case TOK_UDIV:
|
|
func = TOK___udivdi3;
|
|
goto gen_func;
|
|
case '%':
|
|
func = TOK___moddi3;
|
|
goto gen_func;
|
|
case TOK_UMOD:
|
|
func = TOK___umoddi3;
|
|
gen_func:
|
|
/* call generic long long function */
|
|
gfunc_start(&gf, FUNC_CDECL);
|
|
gfunc_param(&gf);
|
|
gfunc_param(&gf);
|
|
vpush_sym(func_old_type, func);
|
|
gfunc_call(&gf);
|
|
vpushi(0);
|
|
vtop->r = REG_IRET;
|
|
vtop->r2 = REG_LRET;
|
|
break;
|
|
case '^':
|
|
case '&':
|
|
case '|':
|
|
case '*':
|
|
case '+':
|
|
case '-':
|
|
t = vtop->t;
|
|
vswap();
|
|
lexpand();
|
|
vrotb(3);
|
|
lexpand();
|
|
/* stack: L1 H1 L2 H2 */
|
|
tmp = vtop[0];
|
|
vtop[0] = vtop[-3];
|
|
vtop[-3] = tmp;
|
|
tmp = vtop[-2];
|
|
vtop[-2] = vtop[-3];
|
|
vtop[-3] = tmp;
|
|
vswap();
|
|
/* stack: H1 H2 L1 L2 */
|
|
if (op == '*') {
|
|
vpushv(vtop - 1);
|
|
vpushv(vtop - 1);
|
|
gen_op(TOK_UMULL);
|
|
lexpand();
|
|
/* stack: H1 H2 L1 L2 ML MH */
|
|
for(i=0;i<4;i++)
|
|
vrotb(6);
|
|
/* stack: ML MH H1 H2 L1 L2 */
|
|
tmp = vtop[0];
|
|
vtop[0] = vtop[-2];
|
|
vtop[-2] = tmp;
|
|
/* stack: ML MH H1 L2 H2 L1 */
|
|
gen_op('*');
|
|
vrotb(3);
|
|
vrotb(3);
|
|
gen_op('*');
|
|
/* stack: ML MH M1 M2 */
|
|
gen_op('+');
|
|
gen_op('+');
|
|
} else if (op == '+' || op == '-') {
|
|
/* XXX: add non carry method too (for MIPS or alpha) */
|
|
if (op == '+')
|
|
op1 = TOK_ADDC1;
|
|
else
|
|
op1 = TOK_SUBC1;
|
|
gen_op(op1);
|
|
/* stack: H1 H2 (L1 op L2) */
|
|
vrotb(3);
|
|
vrotb(3);
|
|
gen_op(op1 + 1); /* TOK_xxxC2 */
|
|
} else {
|
|
gen_op(op);
|
|
/* stack: H1 H2 (L1 op L2) */
|
|
vrotb(3);
|
|
vrotb(3);
|
|
/* stack: (L1 op L2) H1 H2 */
|
|
gen_op(op);
|
|
/* stack: (L1 op L2) (H1 op H2) */
|
|
}
|
|
/* stack: L H */
|
|
lbuild(t);
|
|
break;
|
|
case TOK_SAR:
|
|
case TOK_SHR:
|
|
case TOK_SHL:
|
|
if ((vtop->r & (VT_VALMASK | VT_LVAL | VT_SYM)) == VT_CONST) {
|
|
t = vtop[-1].t;
|
|
vswap();
|
|
lexpand();
|
|
vrotb(3);
|
|
/* stack: L H shift */
|
|
c = (int)vtop->c.i;
|
|
/* constant: simpler */
|
|
/* NOTE: all comments are for SHL. the other cases are
|
|
done by swaping words */
|
|
vpop();
|
|
if (op != TOK_SHL)
|
|
vswap();
|
|
if (c >= 32) {
|
|
/* stack: L H */
|
|
vpop();
|
|
if (c > 32) {
|
|
vpushi(c - 32);
|
|
gen_op(op);
|
|
}
|
|
if (op != TOK_SAR) {
|
|
vpushi(0);
|
|
} else {
|
|
gv_dup();
|
|
vpushi(31);
|
|
gen_op(TOK_SAR);
|
|
}
|
|
vswap();
|
|
} else {
|
|
vswap();
|
|
gv_dup();
|
|
/* stack: H L L */
|
|
vpushi(c);
|
|
gen_op(op);
|
|
vswap();
|
|
vpushi(32 - c);
|
|
if (op == TOK_SHL)
|
|
gen_op(TOK_SHR);
|
|
else
|
|
gen_op(TOK_SHL);
|
|
vrotb(3);
|
|
/* stack: L L H */
|
|
vpushi(c);
|
|
gen_op(op);
|
|
gen_op('|');
|
|
}
|
|
if (op != TOK_SHL)
|
|
vswap();
|
|
lbuild(t);
|
|
} else {
|
|
/* XXX: should provide a faster fallback on x86 ? */
|
|
switch(op) {
|
|
case TOK_SAR:
|
|
func = TOK___sardi3;
|
|
goto gen_func;
|
|
case TOK_SHR:
|
|
func = TOK___shrdi3;
|
|
goto gen_func;
|
|
case TOK_SHL:
|
|
func = TOK___shldi3;
|
|
goto gen_func;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
/* compare operations */
|
|
t = vtop->t;
|
|
vswap();
|
|
lexpand();
|
|
vrotb(3);
|
|
lexpand();
|
|
/* stack: L1 H1 L2 H2 */
|
|
tmp = vtop[-1];
|
|
vtop[-1] = vtop[-2];
|
|
vtop[-2] = tmp;
|
|
/* stack: L1 L2 H1 H2 */
|
|
/* compare high */
|
|
op1 = op;
|
|
/* when values are equal, we need to compare low words. since
|
|
the jump is inverted, we invert the test too. */
|
|
if (op1 == TOK_LT)
|
|
op1 = TOK_LE;
|
|
else if (op1 == TOK_GT)
|
|
op1 = TOK_GE;
|
|
else if (op1 == TOK_ULT)
|
|
op1 = TOK_ULE;
|
|
else if (op1 == TOK_UGT)
|
|
op1 = TOK_UGE;
|
|
a = 0;
|
|
b = 0;
|
|
gen_op(op1);
|
|
if (op1 != TOK_NE) {
|
|
a = gtst(1, 0);
|
|
}
|
|
if (op != TOK_EQ) {
|
|
/* generate non equal test */
|
|
/* XXX: NOT PORTABLE yet */
|
|
if (a == 0) {
|
|
b = gtst(0, 0);
|
|
} else {
|
|
#ifdef TCC_TARGET_I386
|
|
b = psym(0x850f, 0);
|
|
#else
|
|
error("not implemented");
|
|
#endif
|
|
}
|
|
}
|
|
/* compare low */
|
|
gen_op(op);
|
|
a = gtst(1, a);
|
|
gsym(b);
|
|
vset(VT_INT, VT_JMPI, a);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* handle integer constant optimizations and various machine
|
|
independant opt */
|
|
void gen_opic(int op)
|
|
{
|
|
int fc, c1, c2, n;
|
|
SValue *v1, *v2;
|
|
|
|
v1 = vtop - 1;
|
|
v2 = vtop;
|
|
/* currently, we cannot do computations with forward symbols */
|
|
c1 = (v1->r & (VT_VALMASK | VT_LVAL | VT_SYM)) == VT_CONST;
|
|
c2 = (v2->r & (VT_VALMASK | VT_LVAL | VT_SYM)) == VT_CONST;
|
|
if (c1 && c2) {
|
|
fc = v2->c.i;
|
|
switch(op) {
|
|
case '+': v1->c.i += fc; break;
|
|
case '-': v1->c.i -= fc; break;
|
|
case '&': v1->c.i &= fc; break;
|
|
case '^': v1->c.i ^= fc; break;
|
|
case '|': v1->c.i |= fc; break;
|
|
case '*': v1->c.i *= fc; break;
|
|
|
|
case TOK_PDIV:
|
|
case '/':
|
|
case '%':
|
|
case TOK_UDIV:
|
|
case TOK_UMOD:
|
|
/* if division by zero, generate explicit division */
|
|
if (fc == 0) {
|
|
if (const_wanted)
|
|
error("division by zero in constant");
|
|
goto general_case;
|
|
}
|
|
switch(op) {
|
|
default: v1->c.i /= fc; break;
|
|
case '%': v1->c.i %= fc; break;
|
|
case TOK_UDIV: v1->c.i = (unsigned)v1->c.i / fc; break;
|
|
case TOK_UMOD: v1->c.i = (unsigned)v1->c.i % fc; break;
|
|
}
|
|
break;
|
|
case TOK_SHL: v1->c.i <<= fc; break;
|
|
case TOK_SHR: v1->c.i = (unsigned)v1->c.i >> fc; break;
|
|
case TOK_SAR: v1->c.i >>= fc; break;
|
|
/* tests */
|
|
case TOK_ULT: v1->c.i = (unsigned)v1->c.i < (unsigned)fc; break;
|
|
case TOK_UGE: v1->c.i = (unsigned)v1->c.i >= (unsigned)fc; break;
|
|
case TOK_EQ: v1->c.i = v1->c.i == fc; break;
|
|
case TOK_NE: v1->c.i = v1->c.i != fc; break;
|
|
case TOK_ULE: v1->c.i = (unsigned)v1->c.i <= (unsigned)fc; break;
|
|
case TOK_UGT: v1->c.i = (unsigned)v1->c.i > (unsigned)fc; break;
|
|
case TOK_LT: v1->c.i = v1->c.i < fc; break;
|
|
case TOK_GE: v1->c.i = v1->c.i >= fc; break;
|
|
case TOK_LE: v1->c.i = v1->c.i <= fc; break;
|
|
case TOK_GT: v1->c.i = v1->c.i > fc; break;
|
|
/* logical */
|
|
case TOK_LAND: v1->c.i = v1->c.i && fc; break;
|
|
case TOK_LOR: v1->c.i = v1->c.i || fc; break;
|
|
default:
|
|
goto general_case;
|
|
}
|
|
vtop--;
|
|
} else {
|
|
/* if commutative ops, put c2 as constant */
|
|
if (c1 && (op == '+' || op == '&' || op == '^' ||
|
|
op == '|' || op == '*')) {
|
|
vswap();
|
|
swap(&c1, &c2);
|
|
}
|
|
fc = vtop->c.i;
|
|
if (c2 && (((op == '*' || op == '/' || op == TOK_UDIV ||
|
|
op == TOK_PDIV) &&
|
|
fc == 1) ||
|
|
((op == '+' || op == '-' || op == '|' || op == '^' ||
|
|
op == TOK_SHL || op == TOK_SHR || op == TOK_SAR) &&
|
|
fc == 0) ||
|
|
(op == '&' &&
|
|
fc == -1))) {
|
|
/* nothing to do */
|
|
vtop--;
|
|
} else if (c2 && (op == '*' || op == TOK_PDIV || op == TOK_UDIV)) {
|
|
/* try to use shifts instead of muls or divs */
|
|
if (fc > 0 && (fc & (fc - 1)) == 0) {
|
|
n = -1;
|
|
while (fc) {
|
|
fc >>= 1;
|
|
n++;
|
|
}
|
|
vtop->c.i = n;
|
|
if (op == '*')
|
|
op = TOK_SHL;
|
|
else if (op == TOK_PDIV)
|
|
op = TOK_SAR;
|
|
else
|
|
op = TOK_SHR;
|
|
}
|
|
goto general_case;
|
|
} else {
|
|
general_case:
|
|
/* call low level op generator */
|
|
gen_opi(op);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* generate a floating point operation with constant propagation */
|
|
void gen_opif(int op)
|
|
{
|
|
int c1, c2;
|
|
SValue *v1, *v2;
|
|
long double f1, f2;
|
|
|
|
v1 = vtop - 1;
|
|
v2 = vtop;
|
|
/* currently, we cannot do computations with forward symbols */
|
|
c1 = (v1->r & (VT_VALMASK | VT_LVAL | VT_SYM)) == VT_CONST;
|
|
c2 = (v2->r & (VT_VALMASK | VT_LVAL | VT_SYM)) == VT_CONST;
|
|
if (c1 && c2) {
|
|
if (v1->t == VT_FLOAT) {
|
|
f1 = v1->c.f;
|
|
f2 = v2->c.f;
|
|
} else if (v1->t == VT_DOUBLE) {
|
|
f1 = v1->c.d;
|
|
f2 = v2->c.d;
|
|
} else {
|
|
f1 = v1->c.ld;
|
|
f2 = v2->c.ld;
|
|
}
|
|
|
|
/* NOTE: we only do constant propagation if finite number (not
|
|
NaN or infinity) (ANSI spec) */
|
|
if (!ieee_finite(f1) || !ieee_finite(f2))
|
|
goto general_case;
|
|
|
|
switch(op) {
|
|
case '+': f1 += f2; break;
|
|
case '-': f1 -= f2; break;
|
|
case '*': f1 *= f2; break;
|
|
case '/':
|
|
if (f2 == 0.0) {
|
|
if (const_wanted)
|
|
error("division by zero in constant");
|
|
goto general_case;
|
|
}
|
|
f1 /= f2;
|
|
break;
|
|
/* XXX: also handles tests ? */
|
|
default:
|
|
goto general_case;
|
|
}
|
|
/* XXX: overflow test ? */
|
|
if (v1->t == VT_FLOAT) {
|
|
v1->c.f = f1;
|
|
} else if (v1->t == VT_DOUBLE) {
|
|
v1->c.d = f1;
|
|
} else {
|
|
v1->c.ld = f1;
|
|
}
|
|
vtop--;
|
|
} else {
|
|
general_case:
|
|
gen_opf(op);
|
|
}
|
|
}
|
|
|
|
|
|
int pointed_size(int t)
|
|
{
|
|
return type_size(pointed_type(t), &t);
|
|
}
|
|
|
|
#if 0
|
|
void check_pointer_types(SValue *p1, SValue *p2)
|
|
{
|
|
char buf1[256], buf2[256];
|
|
int t1, t2;
|
|
t1 = p1->t;
|
|
t2 = p2->t;
|
|
if (!is_compatible_types(t1, t2)) {
|
|
type_to_str(buf1, sizeof(buf1), t1, NULL);
|
|
type_to_str(buf2, sizeof(buf2), t2, NULL);
|
|
error("incompatible pointers '%s' and '%s'", buf1, buf2);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/* generic gen_op: handles types problems */
|
|
void gen_op(int op)
|
|
{
|
|
int u, t1, t2, bt1, bt2, t;
|
|
|
|
t1 = vtop[-1].t;
|
|
t2 = vtop[0].t;
|
|
bt1 = t1 & VT_BTYPE;
|
|
bt2 = t2 & VT_BTYPE;
|
|
|
|
if (bt1 == VT_PTR || bt2 == VT_PTR) {
|
|
/* at least one operand is a pointer */
|
|
/* relationnal op: must be both pointers */
|
|
if (op >= TOK_ULT && op <= TOK_GT) {
|
|
// check_pointer_types(vtop, vtop - 1);
|
|
/* pointers are handled are unsigned */
|
|
t = VT_INT | VT_UNSIGNED;
|
|
goto std_op;
|
|
}
|
|
/* if both pointers, then it must be the '-' op */
|
|
if ((t1 & VT_BTYPE) == VT_PTR &&
|
|
(t2 & VT_BTYPE) == VT_PTR) {
|
|
if (op != '-')
|
|
error("cannot use pointers here");
|
|
// check_pointer_types(vtop - 1, vtop);
|
|
/* XXX: check that types are compatible */
|
|
u = pointed_size(t1);
|
|
gen_opic(op);
|
|
/* set to integer type */
|
|
vtop->t = VT_INT;
|
|
vpushi(u);
|
|
gen_op(TOK_PDIV);
|
|
} else {
|
|
/* exactly one pointer : must be '+' or '-'. */
|
|
if (op != '-' && op != '+')
|
|
error("cannot use pointers here");
|
|
/* Put pointer as first operand */
|
|
if ((t2 & VT_BTYPE) == VT_PTR) {
|
|
vswap();
|
|
swap(&t1, &t2);
|
|
}
|
|
/* XXX: cast to int ? (long long case) */
|
|
vpushi(pointed_size(vtop[-1].t));
|
|
gen_op('*');
|
|
#ifdef CONFIG_TCC_BCHECK
|
|
/* if evaluating constant expression, no code should be
|
|
generated, so no bound check */
|
|
if (do_bounds_check && !const_wanted) {
|
|
/* if bounded pointers, we generate a special code to
|
|
test bounds */
|
|
if (op == '-') {
|
|
vpushi(0);
|
|
vswap();
|
|
gen_op('-');
|
|
}
|
|
gen_bounded_ptr_add();
|
|
} else
|
|
#endif
|
|
{
|
|
gen_opic(op);
|
|
}
|
|
/* put again type if gen_opic() swaped operands */
|
|
vtop->t = t1;
|
|
}
|
|
} else if (is_float(bt1) || is_float(bt2)) {
|
|
/* compute bigger type and do implicit casts */
|
|
if (bt1 == VT_LDOUBLE || bt2 == VT_LDOUBLE) {
|
|
t = VT_LDOUBLE;
|
|
} else if (bt1 == VT_DOUBLE || bt2 == VT_DOUBLE) {
|
|
t = VT_DOUBLE;
|
|
} else {
|
|
t = VT_FLOAT;
|
|
}
|
|
/* floats can only be used for a few operations */
|
|
if (op != '+' && op != '-' && op != '*' && op != '/' &&
|
|
(op < TOK_ULT || op > TOK_GT))
|
|
error("invalid operands for binary operation");
|
|
goto std_op;
|
|
} else if (bt1 == VT_LLONG || bt2 == VT_LLONG) {
|
|
/* cast to biggest op */
|
|
t = VT_LLONG;
|
|
/* convert to unsigned if it does not fit in a long long */
|
|
if ((t1 & (VT_BTYPE | VT_UNSIGNED)) == (VT_LLONG | VT_UNSIGNED) ||
|
|
(t2 & (VT_BTYPE | VT_UNSIGNED)) == (VT_LLONG | VT_UNSIGNED))
|
|
t |= VT_UNSIGNED;
|
|
goto std_op;
|
|
} else {
|
|
/* integer operations */
|
|
t = VT_INT;
|
|
/* convert to unsigned if it does not fit in an integer */
|
|
if ((t1 & (VT_BTYPE | VT_UNSIGNED)) == (VT_INT | VT_UNSIGNED) ||
|
|
(t2 & (VT_BTYPE | VT_UNSIGNED)) == (VT_INT | VT_UNSIGNED))
|
|
t |= VT_UNSIGNED;
|
|
std_op:
|
|
/* XXX: currently, some unsigned operations are explicit, so
|
|
we modify them here */
|
|
if (t & VT_UNSIGNED) {
|
|
if (op == TOK_SAR)
|
|
op = TOK_SHR;
|
|
else if (op == '/')
|
|
op = TOK_UDIV;
|
|
else if (op == '%')
|
|
op = TOK_UMOD;
|
|
else if (op == TOK_LT)
|
|
op = TOK_ULT;
|
|
else if (op == TOK_GT)
|
|
op = TOK_UGT;
|
|
else if (op == TOK_LE)
|
|
op = TOK_ULE;
|
|
else if (op == TOK_GE)
|
|
op = TOK_UGE;
|
|
}
|
|
vswap();
|
|
gen_cast(t);
|
|
vswap();
|
|
/* special case for shifts and long long: we keep the shift as
|
|
an integer */
|
|
if (op == TOK_SHR || op == TOK_SAR || op == TOK_SHL)
|
|
gen_cast(VT_INT);
|
|
else
|
|
gen_cast(t);
|
|
if (is_float(t))
|
|
gen_opif(op);
|
|
else if ((t & VT_BTYPE) == VT_LLONG)
|
|
gen_opl(op);
|
|
else
|
|
gen_opic(op);
|
|
if (op >= TOK_ULT && op <= TOK_GT) {
|
|
/* relationnal op: the result is an int */
|
|
vtop->t = VT_INT;
|
|
} else {
|
|
vtop->t = t;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* generic itof for unsigned long long case */
|
|
void gen_cvt_itof1(int t)
|
|
{
|
|
GFuncContext gf;
|
|
|
|
if ((vtop->t & (VT_BTYPE | VT_UNSIGNED)) ==
|
|
(VT_LLONG | VT_UNSIGNED)) {
|
|
|
|
gfunc_start(&gf, FUNC_CDECL);
|
|
gfunc_param(&gf);
|
|
if (t == VT_FLOAT)
|
|
vpush_sym(func_old_type, TOK___ulltof);
|
|
else if (t == VT_DOUBLE)
|
|
vpush_sym(func_old_type, TOK___ulltod);
|
|
else
|
|
vpush_sym(func_old_type, TOK___ulltold);
|
|
gfunc_call(&gf);
|
|
vpushi(0);
|
|
vtop->r = REG_FRET;
|
|
} else {
|
|
gen_cvt_itof(t);
|
|
}
|
|
}
|
|
|
|
/* generic ftoi for unsigned long long case */
|
|
void gen_cvt_ftoi1(int t)
|
|
{
|
|
GFuncContext gf;
|
|
int st;
|
|
|
|
if (t == (VT_LLONG | VT_UNSIGNED)) {
|
|
/* not handled natively */
|
|
gfunc_start(&gf, FUNC_CDECL);
|
|
st = vtop->t & VT_BTYPE;
|
|
gfunc_param(&gf);
|
|
if (st == VT_FLOAT)
|
|
vpush_sym(func_old_type, TOK___fixunssfdi);
|
|
else if (st == VT_DOUBLE)
|
|
vpush_sym(func_old_type, TOK___fixunsdfdi);
|
|
else
|
|
vpush_sym(func_old_type, TOK___fixunsxfdi);
|
|
gfunc_call(&gf);
|
|
vpushi(0);
|
|
vtop->r = REG_IRET;
|
|
vtop->r2 = REG_LRET;
|
|
} else {
|
|
gen_cvt_ftoi(t);
|
|
}
|
|
}
|
|
|
|
/* force char or short cast */
|
|
void force_charshort_cast(int t)
|
|
{
|
|
int bits, dbt;
|
|
dbt = t & VT_BTYPE;
|
|
/* XXX: add optimization if lvalue : just change type and offset */
|
|
if (dbt == VT_BYTE)
|
|
bits = 8;
|
|
else
|
|
bits = 16;
|
|
if (t & VT_UNSIGNED) {
|
|
vpushi((1 << bits) - 1);
|
|
gen_op('&');
|
|
} else {
|
|
bits = 32 - bits;
|
|
vpushi(bits);
|
|
gen_op(TOK_SHL);
|
|
vpushi(bits);
|
|
gen_op(TOK_SAR);
|
|
}
|
|
}
|
|
|
|
/* cast 'vtop' to 't' type */
|
|
void gen_cast(int t)
|
|
{
|
|
int sbt, dbt, sf, df, c;
|
|
|
|
/* special delayed cast for char/short */
|
|
/* XXX: in some cases (multiple cascaded casts), it may still
|
|
be incorrect */
|
|
if (vtop->r & VT_MUSTCAST) {
|
|
vtop->r &= ~VT_MUSTCAST;
|
|
force_charshort_cast(vtop->t);
|
|
}
|
|
|
|
dbt = t & (VT_BTYPE | VT_UNSIGNED);
|
|
sbt = vtop->t & (VT_BTYPE | VT_UNSIGNED);
|
|
|
|
if (sbt != dbt) {
|
|
sf = is_float(sbt);
|
|
df = is_float(dbt);
|
|
c = (vtop->r & (VT_VALMASK | VT_LVAL | VT_SYM)) == VT_CONST;
|
|
if (sf && df) {
|
|
/* convert from fp to fp */
|
|
if (c) {
|
|
/* constant case: we can do it now */
|
|
/* XXX: in ISOC, cannot do it if error in convert */
|
|
if (dbt == VT_FLOAT && sbt == VT_DOUBLE)
|
|
vtop->c.f = (float)vtop->c.d;
|
|
else if (dbt == VT_FLOAT && sbt == VT_LDOUBLE)
|
|
vtop->c.f = (float)vtop->c.ld;
|
|
else if (dbt == VT_DOUBLE && sbt == VT_FLOAT)
|
|
vtop->c.d = (double)vtop->c.f;
|
|
else if (dbt == VT_DOUBLE && sbt == VT_LDOUBLE)
|
|
vtop->c.d = (double)vtop->c.ld;
|
|
else if (dbt == VT_LDOUBLE && sbt == VT_FLOAT)
|
|
vtop->c.ld = (long double)vtop->c.f;
|
|
else if (dbt == VT_LDOUBLE && sbt == VT_DOUBLE)
|
|
vtop->c.ld = (long double)vtop->c.d;
|
|
} else {
|
|
/* non constant case: generate code */
|
|
gen_cvt_ftof(dbt);
|
|
}
|
|
} else if (df) {
|
|
/* convert int to fp */
|
|
if (c) {
|
|
switch(sbt) {
|
|
case VT_LLONG | VT_UNSIGNED:
|
|
case VT_LLONG:
|
|
/* XXX: add const cases for long long */
|
|
goto do_itof;
|
|
case VT_INT | VT_UNSIGNED:
|
|
switch(dbt) {
|
|
case VT_FLOAT: vtop->c.f = (float)vtop->c.ui; break;
|
|
case VT_DOUBLE: vtop->c.d = (double)vtop->c.ui; break;
|
|
case VT_LDOUBLE: vtop->c.ld = (long double)vtop->c.ui; break;
|
|
}
|
|
break;
|
|
default:
|
|
switch(dbt) {
|
|
case VT_FLOAT: vtop->c.f = (float)vtop->c.i; break;
|
|
case VT_DOUBLE: vtop->c.d = (double)vtop->c.i; break;
|
|
case VT_LDOUBLE: vtop->c.ld = (long double)vtop->c.i; break;
|
|
}
|
|
break;
|
|
}
|
|
} else {
|
|
do_itof:
|
|
gen_cvt_itof1(dbt);
|
|
}
|
|
} else if (sf) {
|
|
/* convert fp to int */
|
|
/* we handle char/short/etc... with generic code */
|
|
if (dbt != (VT_INT | VT_UNSIGNED) &&
|
|
dbt != (VT_LLONG | VT_UNSIGNED) &&
|
|
dbt != VT_LLONG)
|
|
dbt = VT_INT;
|
|
if (c) {
|
|
switch(dbt) {
|
|
case VT_LLONG | VT_UNSIGNED:
|
|
case VT_LLONG:
|
|
/* XXX: add const cases for long long */
|
|
goto do_ftoi;
|
|
case VT_INT | VT_UNSIGNED:
|
|
switch(sbt) {
|
|
case VT_FLOAT: vtop->c.ui = (unsigned int)vtop->c.d; break;
|
|
case VT_DOUBLE: vtop->c.ui = (unsigned int)vtop->c.d; break;
|
|
case VT_LDOUBLE: vtop->c.ui = (unsigned int)vtop->c.d; break;
|
|
}
|
|
break;
|
|
default:
|
|
/* int case */
|
|
switch(sbt) {
|
|
case VT_FLOAT: vtop->c.i = (int)vtop->c.d; break;
|
|
case VT_DOUBLE: vtop->c.i = (int)vtop->c.d; break;
|
|
case VT_LDOUBLE: vtop->c.i = (int)vtop->c.d; break;
|
|
}
|
|
break;
|
|
}
|
|
} else {
|
|
do_ftoi:
|
|
gen_cvt_ftoi1(dbt);
|
|
}
|
|
if (dbt == VT_INT && (t & (VT_BTYPE | VT_UNSIGNED)) != dbt) {
|
|
/* additionnal cast for char/short/bool... */
|
|
vtop->t = dbt;
|
|
gen_cast(t);
|
|
}
|
|
} else if ((dbt & VT_BTYPE) == VT_LLONG) {
|
|
if ((sbt & VT_BTYPE) != VT_LLONG) {
|
|
/* scalar to long long */
|
|
if (c) {
|
|
if (sbt == (VT_INT | VT_UNSIGNED))
|
|
vtop->c.ll = vtop->c.ui;
|
|
else
|
|
vtop->c.ll = vtop->c.i;
|
|
} else {
|
|
/* machine independant conversion */
|
|
gv(RC_INT);
|
|
/* generate high word */
|
|
if (sbt == (VT_INT | VT_UNSIGNED)) {
|
|
vpushi(0);
|
|
gv(RC_INT);
|
|
} else {
|
|
gv_dup();
|
|
vpushi(31);
|
|
gen_op(TOK_SAR);
|
|
}
|
|
/* patch second register */
|
|
vtop[-1].r2 = vtop->r;
|
|
vpop();
|
|
}
|
|
}
|
|
} else if (dbt == VT_BOOL) {
|
|
/* scalar to bool */
|
|
vpushi(0);
|
|
gen_op(TOK_NE);
|
|
} else if ((dbt & VT_BTYPE) == VT_BYTE ||
|
|
(dbt & VT_BTYPE) == VT_SHORT) {
|
|
force_charshort_cast(t);
|
|
} else if ((dbt & VT_BTYPE) == VT_INT) {
|
|
/* scalar to int */
|
|
if (sbt == VT_LLONG) {
|
|
/* from long long: just take low order word */
|
|
lexpand();
|
|
vpop();
|
|
}
|
|
/* if lvalue and single word type, nothing to do because
|
|
the lvalue already contains the real type size (see
|
|
VT_LVAL_xxx constants) */
|
|
}
|
|
}
|
|
vtop->t = t;
|
|
}
|
|
|
|
/* return type size. Put alignment at 'a' */
|
|
int type_size(int t, int *a)
|
|
{
|
|
Sym *s;
|
|
int bt;
|
|
|
|
bt = t & VT_BTYPE;
|
|
if (bt == VT_STRUCT) {
|
|
/* struct/union */
|
|
s = sym_find(((unsigned)t >> VT_STRUCT_SHIFT) | SYM_STRUCT);
|
|
*a = 4; /* XXX: cannot store it yet. Doing that is safe */
|
|
return s->c;
|
|
} else if (bt == VT_PTR) {
|
|
if (t & VT_ARRAY) {
|
|
s = sym_find(((unsigned)t >> VT_STRUCT_SHIFT));
|
|
return type_size(s->t, a) * s->c;
|
|
} else {
|
|
*a = PTR_SIZE;
|
|
return PTR_SIZE;
|
|
}
|
|
} else if (bt == VT_LDOUBLE) {
|
|
*a = LDOUBLE_ALIGN;
|
|
return LDOUBLE_SIZE;
|
|
} else if (bt == VT_DOUBLE || bt == VT_LLONG) {
|
|
*a = 8;
|
|
return 8;
|
|
} else if (bt == VT_INT || bt == VT_ENUM || bt == VT_FLOAT) {
|
|
*a = 4;
|
|
return 4;
|
|
} else if (bt == VT_SHORT) {
|
|
*a = 2;
|
|
return 2;
|
|
} else {
|
|
/* char, void, function, _Bool */
|
|
*a = 1;
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
/* return the pointed type of t */
|
|
int pointed_type(int t)
|
|
{
|
|
Sym *s;
|
|
s = sym_find(((unsigned)t >> VT_STRUCT_SHIFT));
|
|
return s->t | (t & ~VT_TYPE);
|
|
}
|
|
|
|
int mk_pointer(int t)
|
|
{
|
|
int p;
|
|
p = anon_sym++;
|
|
sym_push(p, t, 0, -1);
|
|
return VT_PTR | (p << VT_STRUCT_SHIFT) | (t & ~VT_TYPE);
|
|
}
|
|
|
|
int is_compatible_types(int t1, int t2)
|
|
{
|
|
Sym *s1, *s2;
|
|
int bt1, bt2;
|
|
|
|
t1 &= VT_TYPE;
|
|
t2 &= VT_TYPE;
|
|
bt1 = t1 & VT_BTYPE;
|
|
bt2 = t2 & VT_BTYPE;
|
|
if (bt1 == VT_PTR) {
|
|
t1 = pointed_type(t1);
|
|
/* if function, then convert implicitely to function pointer */
|
|
if (bt2 != VT_FUNC) {
|
|
if (bt2 != VT_PTR)
|
|
return 0;
|
|
t2 = pointed_type(t2);
|
|
}
|
|
/* void matches everything */
|
|
t1 &= VT_TYPE;
|
|
t2 &= VT_TYPE;
|
|
if (t1 == VT_VOID || t2 == VT_VOID)
|
|
return 1;
|
|
return is_compatible_types(t1, t2);
|
|
} else if (bt1 == VT_STRUCT || bt2 == VT_STRUCT) {
|
|
return (t2 == t1);
|
|
} else if (bt1 == VT_FUNC) {
|
|
if (bt2 != VT_FUNC)
|
|
return 0;
|
|
s1 = sym_find(((unsigned)t1 >> VT_STRUCT_SHIFT));
|
|
s2 = sym_find(((unsigned)t2 >> VT_STRUCT_SHIFT));
|
|
if (!is_compatible_types(s1->t, s2->t))
|
|
return 0;
|
|
/* XXX: not complete */
|
|
if (s1->c == FUNC_OLD || s2->c == FUNC_OLD)
|
|
return 1;
|
|
if (s1->c != s2->c)
|
|
return 0;
|
|
while (s1 != NULL) {
|
|
if (s2 == NULL)
|
|
return 0;
|
|
if (!is_compatible_types(s1->t, s2->t))
|
|
return 0;
|
|
s1 = s1->next;
|
|
s2 = s2->next;
|
|
}
|
|
if (s2)
|
|
return 0;
|
|
return 1;
|
|
} else {
|
|
/* XXX: not complete */
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
/* print a type. If 'varstr' is not NULL, then the variable is also
|
|
printed in the type */
|
|
/* XXX: union */
|
|
/* XXX: add array and function pointers */
|
|
void type_to_str(char *buf, int buf_size,
|
|
int t, const char *varstr)
|
|
{
|
|
int bt, v;
|
|
Sym *s, *sa;
|
|
char buf1[256];
|
|
const char *tstr;
|
|
|
|
t = t & VT_TYPE;
|
|
bt = t & VT_BTYPE;
|
|
buf[0] = '\0';
|
|
if (t & VT_UNSIGNED)
|
|
pstrcat(buf, buf_size, "unsigned ");
|
|
switch(bt) {
|
|
case VT_VOID:
|
|
tstr = "void";
|
|
goto add_tstr;
|
|
case VT_BOOL:
|
|
tstr = "_Bool";
|
|
goto add_tstr;
|
|
case VT_BYTE:
|
|
tstr = "char";
|
|
goto add_tstr;
|
|
case VT_SHORT:
|
|
tstr = "short";
|
|
goto add_tstr;
|
|
case VT_INT:
|
|
tstr = "int";
|
|
goto add_tstr;
|
|
case VT_LONG:
|
|
tstr = "long";
|
|
goto add_tstr;
|
|
case VT_LLONG:
|
|
tstr = "long long";
|
|
goto add_tstr;
|
|
case VT_FLOAT:
|
|
tstr = "float";
|
|
goto add_tstr;
|
|
case VT_DOUBLE:
|
|
tstr = "double";
|
|
goto add_tstr;
|
|
case VT_LDOUBLE:
|
|
tstr = "long double";
|
|
add_tstr:
|
|
pstrcat(buf, buf_size, tstr);
|
|
break;
|
|
case VT_ENUM:
|
|
case VT_STRUCT:
|
|
if (bt == VT_STRUCT)
|
|
tstr = "struct ";
|
|
else
|
|
tstr = "enum ";
|
|
pstrcat(buf, buf_size, tstr);
|
|
v = (unsigned)t >> VT_STRUCT_SHIFT;
|
|
if (v >= SYM_FIRST_ANOM)
|
|
pstrcat(buf, buf_size, "<anonymous>");
|
|
else
|
|
pstrcat(buf, buf_size, get_tok_str(v, NULL));
|
|
break;
|
|
case VT_FUNC:
|
|
s = sym_find((unsigned)t >> VT_STRUCT_SHIFT);
|
|
type_to_str(buf, buf_size, s->t, varstr);
|
|
pstrcat(buf, buf_size, "(");
|
|
sa = s->next;
|
|
while (sa != NULL) {
|
|
type_to_str(buf1, sizeof(buf1), sa->t, NULL);
|
|
pstrcat(buf, buf_size, buf1);
|
|
sa = sa->next;
|
|
if (sa)
|
|
pstrcat(buf, buf_size, ", ");
|
|
}
|
|
pstrcat(buf, buf_size, ")");
|
|
goto no_var;
|
|
case VT_PTR:
|
|
s = sym_find((unsigned)t >> VT_STRUCT_SHIFT);
|
|
pstrcpy(buf1, sizeof(buf1), "*");
|
|
if (varstr)
|
|
pstrcat(buf1, sizeof(buf1), varstr);
|
|
type_to_str(buf, buf_size, s->t, buf1);
|
|
goto no_var;
|
|
}
|
|
if (varstr) {
|
|
pstrcat(buf, buf_size, " ");
|
|
pstrcat(buf, buf_size, varstr);
|
|
}
|
|
no_var: ;
|
|
}
|
|
|
|
/* verify type compatibility to store vtop in 'dt' type, and generate
|
|
casts if needed. */
|
|
void gen_assign_cast(int dt)
|
|
{
|
|
int st;
|
|
char buf1[256], buf2[256];
|
|
|
|
st = vtop->t; /* source type */
|
|
if ((dt & VT_BTYPE) == VT_PTR) {
|
|
/* special cases for pointers */
|
|
/* a function is implicitely a function pointer */
|
|
if ((st & VT_BTYPE) == VT_FUNC) {
|
|
if (!is_compatible_types(pointed_type(dt), st))
|
|
goto error;
|
|
else
|
|
goto type_ok;
|
|
}
|
|
/* '0' can also be a pointer */
|
|
if ((st & VT_BTYPE) == VT_INT &&
|
|
((vtop->r & (VT_VALMASK | VT_LVAL | VT_SYM)) == VT_CONST) &&
|
|
vtop->c.i == 0)
|
|
goto type_ok;
|
|
}
|
|
if (!is_compatible_types(dt, st)) {
|
|
error:
|
|
type_to_str(buf1, sizeof(buf1), st, NULL);
|
|
type_to_str(buf2, sizeof(buf2), dt, NULL);
|
|
error("cannot cast '%s' to '%s'", buf1, buf2);
|
|
}
|
|
type_ok:
|
|
gen_cast(dt);
|
|
}
|
|
|
|
/* store vtop in lvalue pushed on stack */
|
|
void vstore(void)
|
|
{
|
|
int sbt, dbt, ft, r, t, size, align, bit_size, bit_pos, rc, delayed_cast;
|
|
GFuncContext gf;
|
|
|
|
ft = vtop[-1].t;
|
|
sbt = vtop->t & VT_BTYPE;
|
|
dbt = ft & VT_BTYPE;
|
|
if (((sbt == VT_INT || sbt == VT_SHORT) && dbt == VT_BYTE) ||
|
|
(sbt == VT_INT && dbt == VT_SHORT)) {
|
|
/* optimize char/short casts */
|
|
delayed_cast = VT_MUSTCAST;
|
|
vtop->t = ft & VT_TYPE;
|
|
} else {
|
|
delayed_cast = 0;
|
|
gen_assign_cast(ft & VT_TYPE);
|
|
}
|
|
|
|
if (sbt == VT_STRUCT) {
|
|
/* if structure, only generate pointer */
|
|
/* structure assignment : generate memcpy */
|
|
/* XXX: optimize if small size */
|
|
vdup();
|
|
gfunc_start(&gf, FUNC_CDECL);
|
|
/* type size */
|
|
size = type_size(vtop->t, &align);
|
|
vpushi(size);
|
|
gfunc_param(&gf);
|
|
/* source */
|
|
vtop->t = VT_INT;
|
|
gaddrof();
|
|
gfunc_param(&gf);
|
|
/* destination */
|
|
vswap();
|
|
vtop->t = VT_INT;
|
|
gaddrof();
|
|
gfunc_param(&gf);
|
|
|
|
save_regs(0);
|
|
vpush_sym(func_old_type, TOK_memcpy);
|
|
gfunc_call(&gf);
|
|
/* leave source on stack */
|
|
} else if (ft & VT_BITFIELD) {
|
|
/* bitfield store handling */
|
|
bit_pos = (ft >> VT_STRUCT_SHIFT) & 0x3f;
|
|
bit_size = (ft >> (VT_STRUCT_SHIFT + 6)) & 0x3f;
|
|
/* remove bit field info to avoid loops */
|
|
vtop[-1].t = ft & ~(VT_BITFIELD | (-1 << VT_STRUCT_SHIFT));
|
|
|
|
/* duplicate destination */
|
|
vdup();
|
|
vtop[-1] = vtop[-2];
|
|
|
|
/* mask and shift source */
|
|
vpushi((1 << bit_size) - 1);
|
|
gen_op('&');
|
|
vpushi(bit_pos);
|
|
gen_op(TOK_SHL);
|
|
/* load destination, mask and or with source */
|
|
vswap();
|
|
vpushi(~(((1 << bit_size) - 1) << bit_pos));
|
|
gen_op('&');
|
|
gen_op('|');
|
|
/* store result */
|
|
vstore();
|
|
} else {
|
|
#ifdef CONFIG_TCC_BCHECK
|
|
/* bound check case */
|
|
if (vtop[-1].r & VT_MUSTBOUND) {
|
|
vswap();
|
|
gbound();
|
|
vswap();
|
|
}
|
|
#endif
|
|
rc = RC_INT;
|
|
if (is_float(ft))
|
|
rc = RC_FLOAT;
|
|
r = gv(rc); /* generate value */
|
|
/* if lvalue was saved on stack, must read it */
|
|
if ((vtop[-1].r & VT_VALMASK) == VT_LLOCAL) {
|
|
SValue sv;
|
|
t = get_reg(RC_INT);
|
|
sv.t = VT_INT;
|
|
sv.r = VT_LOCAL | VT_LVAL;
|
|
sv.c.ul = vtop[-1].c.ul;
|
|
load(t, &sv);
|
|
vtop[-1].r = t | VT_LVAL;
|
|
}
|
|
store(r, vtop - 1);
|
|
/* two word case handling : store second register at word + 4 */
|
|
if ((ft & VT_BTYPE) == VT_LLONG) {
|
|
vswap();
|
|
/* convert to int to increment easily */
|
|
vtop->t = VT_INT;
|
|
gaddrof();
|
|
vpushi(4);
|
|
gen_op('+');
|
|
vtop->r |= VT_LVAL;
|
|
vswap();
|
|
/* XXX: it works because r2 is spilled last ! */
|
|
store(vtop->r2, vtop - 1);
|
|
}
|
|
vswap();
|
|
vtop--; /* NOT vpop() because on x86 it would flush the fp stack */
|
|
vtop->r |= delayed_cast;
|
|
}
|
|
}
|
|
|
|
/* post defines POST/PRE add. c is the token ++ or -- */
|
|
void inc(int post, int c)
|
|
{
|
|
test_lvalue();
|
|
vdup(); /* save lvalue */
|
|
if (post) {
|
|
gv_dup(); /* duplicate value */
|
|
vrotb(3);
|
|
vrotb(3);
|
|
}
|
|
/* add constant */
|
|
vpushi(c - TOK_MID);
|
|
gen_op('+');
|
|
vstore(); /* store value */
|
|
if (post)
|
|
vpop(); /* if post op, return saved value */
|
|
}
|
|
|
|
/* Parse GNUC __attribute__ extension. Currently, the following
|
|
extensions are recognized:
|
|
- aligned(n) : set data/function alignment.
|
|
- section(x) : generate data/code in this section.
|
|
- unused : currently ignored, but may be used someday.
|
|
*/
|
|
void parse_attribute(AttributeDef *ad)
|
|
{
|
|
int t, n;
|
|
|
|
next();
|
|
skip('(');
|
|
skip('(');
|
|
while (tok != ')') {
|
|
if (tok < TOK_IDENT)
|
|
expect("attribute name");
|
|
t = tok;
|
|
next();
|
|
switch(t) {
|
|
case TOK_SECTION:
|
|
case TOK___SECTION__:
|
|
skip('(');
|
|
if (tok != TOK_STR)
|
|
expect("section name");
|
|
ad->section = find_section(tokc.ts->str);
|
|
next();
|
|
skip(')');
|
|
break;
|
|
case TOK_ALIGNED:
|
|
case TOK___ALIGNED__:
|
|
skip('(');
|
|
n = expr_const();
|
|
if (n <= 0 || (n & (n - 1)) != 0)
|
|
error("alignment must be a positive power of two");
|
|
ad->aligned = n;
|
|
skip(')');
|
|
break;
|
|
case TOK_UNUSED:
|
|
case TOK___UNUSED__:
|
|
/* currently, no need to handle it because tcc does not
|
|
track unused objects */
|
|
break;
|
|
case TOK_NORETURN:
|
|
case TOK___NORETURN__:
|
|
/* currently, no need to handle it because tcc does not
|
|
track unused objects */
|
|
break;
|
|
case TOK_CDECL:
|
|
case TOK___CDECL:
|
|
case TOK___CDECL__:
|
|
ad->func_call = FUNC_CDECL;
|
|
break;
|
|
case TOK_STDCALL:
|
|
case TOK___STDCALL:
|
|
case TOK___STDCALL__:
|
|
ad->func_call = FUNC_STDCALL;
|
|
break;
|
|
default:
|
|
warning("'%s' attribute ignored", get_tok_str(t, NULL));
|
|
/* skip parameters */
|
|
/* XXX: skip parenthesis too */
|
|
if (tok == '(') {
|
|
next();
|
|
while (tok != ')' && tok != -1)
|
|
next();
|
|
next();
|
|
}
|
|
break;
|
|
}
|
|
if (tok != ',')
|
|
break;
|
|
next();
|
|
}
|
|
skip(')');
|
|
skip(')');
|
|
}
|
|
|
|
/* enum/struct/union declaration */
|
|
int struct_decl(int u)
|
|
{
|
|
int a, t, b, v, size, align, maxalign, c, offset;
|
|
int bit_size, bit_pos, bsize, bt, lbit_pos;
|
|
Sym *s, *ss, **ps;
|
|
AttributeDef ad;
|
|
|
|
a = tok; /* save decl type */
|
|
next();
|
|
if (tok != '{') {
|
|
v = tok;
|
|
next();
|
|
/* struct already defined ? return it */
|
|
/* XXX: check consistency */
|
|
s = sym_find(v | SYM_STRUCT);
|
|
if (s) {
|
|
if (s->t != a)
|
|
error("invalid type");
|
|
goto do_decl;
|
|
}
|
|
} else {
|
|
v = anon_sym++;
|
|
}
|
|
s = sym_push(v | SYM_STRUCT, a, 0, 0);
|
|
/* put struct/union/enum name in type */
|
|
do_decl:
|
|
u = u | (v << VT_STRUCT_SHIFT);
|
|
|
|
if (tok == '{') {
|
|
next();
|
|
if (s->c)
|
|
error("struct/union/enum already defined");
|
|
/* cannot be empty */
|
|
c = 0;
|
|
maxalign = 0;
|
|
ps = &s->next;
|
|
bit_pos = 0;
|
|
offset = 0;
|
|
while (1) {
|
|
if (a == TOK_ENUM) {
|
|
v = tok;
|
|
next();
|
|
if (tok == '=') {
|
|
next();
|
|
c = expr_const();
|
|
}
|
|
/* enum symbols have static storage */
|
|
sym_push(v, VT_STATIC | VT_INT, VT_CONST, c);
|
|
if (tok == ',')
|
|
next();
|
|
c++;
|
|
} else {
|
|
parse_btype(&b, &ad);
|
|
while (1) {
|
|
bit_size = -1;
|
|
v = 0;
|
|
if (tok != ':') {
|
|
t = type_decl(&ad, &v, b, TYPE_DIRECT);
|
|
if ((t & VT_BTYPE) == VT_FUNC ||
|
|
(t & (VT_TYPEDEF | VT_STATIC | VT_EXTERN)))
|
|
error("invalid type for '%s'",
|
|
get_tok_str(v, NULL));
|
|
} else {
|
|
t = b;
|
|
}
|
|
if (tok == ':') {
|
|
next();
|
|
bit_size = expr_const();
|
|
/* XXX: handle v = 0 case for messages */
|
|
if (bit_size < 0)
|
|
error("negative width in bit-field '%s'",
|
|
get_tok_str(v, NULL));
|
|
if (v && bit_size == 0)
|
|
error("zero width for bit-field '%s'",
|
|
get_tok_str(v, NULL));
|
|
}
|
|
size = type_size(t, &align);
|
|
lbit_pos = 0;
|
|
if (bit_size >= 0) {
|
|
bt = t & VT_BTYPE;
|
|
if (bt != VT_INT &&
|
|
bt != VT_BYTE &&
|
|
bt != VT_SHORT)
|
|
error("bitfields must have scalar type");
|
|
bsize = size * 8;
|
|
if (bit_size > bsize) {
|
|
error("width of '%s' exceeds its type",
|
|
get_tok_str(v, NULL));
|
|
} else if (bit_size == bsize) {
|
|
/* no need for bit fields */
|
|
bit_pos = 0;
|
|
} else if (bit_size == 0) {
|
|
/* XXX: what to do if only padding in a
|
|
structure ? */
|
|
/* zero size: means to pad */
|
|
if (bit_pos > 0)
|
|
bit_pos = bsize;
|
|
} else {
|
|
/* we do not have enough room ? */
|
|
if ((bit_pos + bit_size) > bsize)
|
|
bit_pos = 0;
|
|
lbit_pos = bit_pos;
|
|
/* XXX: handle LSB first */
|
|
t |= VT_BITFIELD |
|
|
(bit_pos << VT_STRUCT_SHIFT) |
|
|
(bit_size << (VT_STRUCT_SHIFT + 6));
|
|
bit_pos += bit_size;
|
|
}
|
|
} else {
|
|
bit_pos = 0;
|
|
}
|
|
if (v) {
|
|
/* add new memory data only if starting
|
|
bit field */
|
|
if (lbit_pos == 0) {
|
|
if (a == TOK_STRUCT) {
|
|
c = (c + align - 1) & -align;
|
|
offset = c;
|
|
c += size;
|
|
} else {
|
|
offset = 0;
|
|
if (size > c)
|
|
c = size;
|
|
}
|
|
if (align > maxalign)
|
|
maxalign = align;
|
|
}
|
|
#if 0
|
|
printf("add field %s offset=%d",
|
|
get_tok_str(v, NULL), offset);
|
|
if (t & VT_BITFIELD) {
|
|
printf(" pos=%d size=%d",
|
|
(t >> VT_STRUCT_SHIFT) & 0x3f,
|
|
(t >> (VT_STRUCT_SHIFT + 6)) & 0x3f);
|
|
}
|
|
printf("\n");
|
|
#endif
|
|
ss = sym_push(v | SYM_FIELD, t, 0, offset);
|
|
*ps = ss;
|
|
ps = &ss->next;
|
|
}
|
|
if (tok == ';' || tok == -1)
|
|
break;
|
|
skip(',');
|
|
}
|
|
skip(';');
|
|
}
|
|
if (tok == '}')
|
|
break;
|
|
}
|
|
skip('}');
|
|
/* size for struct/union, dummy for enum */
|
|
s->c = (c + maxalign - 1) & -maxalign;
|
|
}
|
|
return u;
|
|
}
|
|
|
|
/* return 0 if no type declaration. otherwise, return the basic type
|
|
and skip it.
|
|
*/
|
|
int parse_btype(int *type_ptr, AttributeDef *ad)
|
|
{
|
|
int t, u, type_found;
|
|
Sym *s;
|
|
|
|
memset(ad, 0, sizeof(AttributeDef));
|
|
type_found = 0;
|
|
t = 0;
|
|
while(1) {
|
|
switch(tok) {
|
|
/* basic types */
|
|
case TOK_CHAR:
|
|
u = VT_BYTE;
|
|
basic_type:
|
|
next();
|
|
basic_type1:
|
|
if ((t & VT_BTYPE) != 0)
|
|
error("too many basic types");
|
|
t |= u;
|
|
break;
|
|
case TOK_VOID:
|
|
u = VT_VOID;
|
|
goto basic_type;
|
|
case TOK_SHORT:
|
|
u = VT_SHORT;
|
|
goto basic_type;
|
|
case TOK_INT:
|
|
next();
|
|
break;
|
|
case TOK_LONG:
|
|
next();
|
|
if ((t & VT_BTYPE) == VT_DOUBLE) {
|
|
t = (t & ~VT_BTYPE) | VT_LDOUBLE;
|
|
} else if ((t & VT_BTYPE) == VT_LONG) {
|
|
t = (t & ~VT_BTYPE) | VT_LLONG;
|
|
} else {
|
|
u = VT_LONG;
|
|
goto basic_type1;
|
|
}
|
|
break;
|
|
case TOK_BOOL:
|
|
u = VT_BOOL;
|
|
goto basic_type;
|
|
case TOK_FLOAT:
|
|
u = VT_FLOAT;
|
|
goto basic_type;
|
|
case TOK_DOUBLE:
|
|
next();
|
|
if ((t & VT_BTYPE) == VT_LONG) {
|
|
t = (t & ~VT_BTYPE) | VT_LDOUBLE;
|
|
} else {
|
|
u = VT_DOUBLE;
|
|
goto basic_type1;
|
|
}
|
|
break;
|
|
case TOK_ENUM:
|
|
u = struct_decl(VT_ENUM);
|
|
goto basic_type1;
|
|
case TOK_STRUCT:
|
|
case TOK_UNION:
|
|
u = struct_decl(VT_STRUCT);
|
|
goto basic_type1;
|
|
|
|
/* type modifiers */
|
|
case TOK_CONST:
|
|
case TOK_VOLATILE:
|
|
case TOK_REGISTER:
|
|
case TOK_SIGNED:
|
|
case TOK___SIGNED__:
|
|
case TOK_AUTO:
|
|
case TOK_INLINE:
|
|
case TOK___INLINE__:
|
|
case TOK_RESTRICT:
|
|
next();
|
|
break;
|
|
case TOK_UNSIGNED:
|
|
t |= VT_UNSIGNED;
|
|
next();
|
|
break;
|
|
|
|
/* storage */
|
|
case TOK_EXTERN:
|
|
t |= VT_EXTERN;
|
|
next();
|
|
break;
|
|
case TOK_STATIC:
|
|
t |= VT_STATIC;
|
|
next();
|
|
break;
|
|
case TOK_TYPEDEF:
|
|
t |= VT_TYPEDEF;
|
|
next();
|
|
break;
|
|
/* GNUC attribute */
|
|
case TOK___ATTRIBUTE__:
|
|
parse_attribute(ad);
|
|
break;
|
|
default:
|
|
s = sym_find(tok);
|
|
if (!s || !(s->t & VT_TYPEDEF))
|
|
goto the_end;
|
|
t |= (s->t & ~VT_TYPEDEF);
|
|
next();
|
|
break;
|
|
}
|
|
type_found = 1;
|
|
}
|
|
the_end:
|
|
/* long is never used as type */
|
|
if ((t & VT_BTYPE) == VT_LONG)
|
|
t = (t & ~VT_BTYPE) | VT_INT;
|
|
*type_ptr = t;
|
|
return type_found;
|
|
}
|
|
|
|
int post_type(int t, AttributeDef *ad)
|
|
{
|
|
int p, n, pt, l, t1;
|
|
Sym **plast, *s, *first;
|
|
AttributeDef ad1;
|
|
|
|
if (tok == '(') {
|
|
/* function declaration */
|
|
next();
|
|
l = 0;
|
|
first = NULL;
|
|
plast = &first;
|
|
while (tok != ')') {
|
|
/* read param name and compute offset */
|
|
if (l != FUNC_OLD) {
|
|
if (!parse_btype(&pt, &ad1)) {
|
|
if (l) {
|
|
error("invalid type");
|
|
} else {
|
|
l = FUNC_OLD;
|
|
goto old_proto;
|
|
}
|
|
}
|
|
l = FUNC_NEW;
|
|
if ((pt & VT_BTYPE) == VT_VOID && tok == ')')
|
|
break;
|
|
pt = type_decl(&ad1, &n, pt, TYPE_DIRECT | TYPE_ABSTRACT);
|
|
if ((pt & VT_BTYPE) == VT_VOID)
|
|
error("parameter declared as void");
|
|
} else {
|
|
old_proto:
|
|
n = tok;
|
|
pt = VT_INT;
|
|
next();
|
|
}
|
|
/* array must be transformed to pointer according to ANSI C */
|
|
pt &= ~VT_ARRAY;
|
|
s = sym_push(n | SYM_FIELD, pt, 0, 0);
|
|
*plast = s;
|
|
plast = &s->next;
|
|
if (tok == ',') {
|
|
next();
|
|
if (l == FUNC_NEW && tok == TOK_DOTS) {
|
|
l = FUNC_ELLIPSIS;
|
|
next();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
/* if no parameters, then old type prototype */
|
|
if (l == 0)
|
|
l = FUNC_OLD;
|
|
skip(')');
|
|
t1 = t & (VT_TYPEDEF | VT_STATIC | VT_EXTERN);
|
|
t = post_type(t & ~(VT_TYPEDEF | VT_STATIC | VT_EXTERN), ad);
|
|
/* we push a anonymous symbol which will contain the function prototype */
|
|
p = anon_sym++;
|
|
s = sym_push(p, t, ad->func_call, l);
|
|
s->next = first;
|
|
t = t1 | VT_FUNC | (p << VT_STRUCT_SHIFT);
|
|
} else if (tok == '[') {
|
|
/* array definition */
|
|
next();
|
|
n = -1;
|
|
if (tok != ']') {
|
|
n = expr_const();
|
|
if (n < 0)
|
|
error("invalid array size");
|
|
}
|
|
skip(']');
|
|
/* parse next post type */
|
|
t1 = t & (VT_TYPEDEF | VT_STATIC | VT_EXTERN);
|
|
t = post_type(t & ~(VT_TYPEDEF | VT_STATIC | VT_EXTERN), ad);
|
|
|
|
/* we push a anonymous symbol which will contain the array
|
|
element type */
|
|
p = anon_sym++;
|
|
sym_push(p, t, 0, n);
|
|
t = t1 | VT_ARRAY | VT_PTR | (p << VT_STRUCT_SHIFT);
|
|
}
|
|
return t;
|
|
}
|
|
|
|
/* Read a type declaration (except basic type), and return the
|
|
type. 'td' is a bitmask indicating which kind of type decl is
|
|
expected. 't' should contain the basic type. 'ad' is the attribute
|
|
definition of the basic type. It can be modified by type_decl(). */
|
|
int type_decl(AttributeDef *ad, int *v, int t, int td)
|
|
{
|
|
int u, p;
|
|
Sym *s;
|
|
|
|
while (tok == '*') {
|
|
next();
|
|
while (tok == TOK_CONST || tok == TOK_VOLATILE || tok == TOK_RESTRICT)
|
|
next();
|
|
t = mk_pointer(t);
|
|
}
|
|
|
|
/* recursive type */
|
|
/* XXX: incorrect if abstract type for functions (e.g. 'int ()') */
|
|
if (tok == '(') {
|
|
next();
|
|
/* XXX: this is not correct to modify 'ad' at this point, but
|
|
the syntax is not clear */
|
|
if (tok == TOK___ATTRIBUTE__)
|
|
parse_attribute(ad);
|
|
u = type_decl(ad, v, 0, td);
|
|
skip(')');
|
|
} else {
|
|
u = 0;
|
|
/* type identifier */
|
|
if (tok >= TOK_IDENT && (td & TYPE_DIRECT)) {
|
|
*v = tok;
|
|
next();
|
|
} else {
|
|
if (!(td & TYPE_ABSTRACT))
|
|
expect("identifier");
|
|
*v = 0;
|
|
}
|
|
}
|
|
/* append t at the end of u */
|
|
t = post_type(t, ad);
|
|
if (tok == TOK___ATTRIBUTE__)
|
|
parse_attribute(ad);
|
|
if (!u)
|
|
return t;
|
|
p = u;
|
|
while(1) {
|
|
s = sym_find((unsigned)p >> VT_STRUCT_SHIFT);
|
|
p = s->t;
|
|
if (!p) {
|
|
s->t = t;
|
|
break;
|
|
}
|
|
}
|
|
return u;
|
|
}
|
|
|
|
/* define a new external reference to a symbol 'v' of type 'u' */
|
|
Sym *external_sym(int v, int u, int r)
|
|
{
|
|
Sym *s;
|
|
|
|
s = sym_find(v);
|
|
if (!s) {
|
|
/* push forward reference */
|
|
s = sym_push1(&global_stack,
|
|
v, u | VT_EXTERN, 0);
|
|
s->r = r | VT_CONST | VT_SYM;
|
|
}
|
|
return s;
|
|
}
|
|
|
|
/* compute the lvalue VT_LVAL_xxx needed to match type t. */
|
|
static int lvalue_type(int t)
|
|
{
|
|
int bt, r;
|
|
r = VT_LVAL;
|
|
bt = t & VT_BTYPE;
|
|
if (bt == VT_BYTE)
|
|
r |= VT_LVAL_BYTE;
|
|
else if (bt == VT_SHORT)
|
|
r |= VT_LVAL_SHORT;
|
|
else
|
|
return r;
|
|
if (t & VT_UNSIGNED)
|
|
r |= VT_LVAL_UNSIGNED;
|
|
return r;
|
|
}
|
|
|
|
/* indirection with full error checking and bound check */
|
|
static void indir(void)
|
|
{
|
|
if ((vtop->t & VT_BTYPE) != VT_PTR)
|
|
expect("pointer");
|
|
if (vtop->r & VT_LVAL)
|
|
gv(RC_INT);
|
|
vtop->t = pointed_type(vtop->t);
|
|
/* an array is never an lvalue */
|
|
if (!(vtop->t & VT_ARRAY)) {
|
|
vtop->r |= lvalue_type(vtop->t);
|
|
/* if bound checking, the referenced pointer must be checked */
|
|
if (do_bounds_check)
|
|
vtop->r |= VT_MUSTBOUND;
|
|
}
|
|
}
|
|
|
|
/* pass a parameter to a function and do type checking and casting */
|
|
void gfunc_param_typed(GFuncContext *gf, Sym *func, Sym *arg)
|
|
{
|
|
int func_type;
|
|
func_type = func->c;
|
|
if (func_type == FUNC_OLD ||
|
|
(func_type == FUNC_ELLIPSIS && arg == NULL)) {
|
|
/* default casting : only need to convert float to double */
|
|
if ((vtop->t & VT_BTYPE) == VT_FLOAT)
|
|
gen_cast(VT_DOUBLE);
|
|
} else if (arg == NULL) {
|
|
error("too many arguments to function");
|
|
} else {
|
|
gen_assign_cast(arg->t);
|
|
}
|
|
gfunc_param(gf);
|
|
}
|
|
|
|
void unary(void)
|
|
{
|
|
int n, t, ft, fc, align, size, r, data_offset;
|
|
Sym *s;
|
|
GFuncContext gf;
|
|
AttributeDef ad;
|
|
|
|
if (tok == TOK_CINT || tok == TOK_CCHAR || tok == TOK_LCHAR) {
|
|
vpushi(tokc.i);
|
|
next();
|
|
} else if (tok == TOK_CUINT) {
|
|
vsetc(VT_INT | VT_UNSIGNED, VT_CONST, &tokc);
|
|
next();
|
|
} else if (tok == TOK_CLLONG) {
|
|
vsetc(VT_LLONG, VT_CONST, &tokc);
|
|
next();
|
|
} else if (tok == TOK_CULLONG) {
|
|
vsetc(VT_LLONG | VT_UNSIGNED, VT_CONST, &tokc);
|
|
next();
|
|
} else if (tok == TOK_CFLOAT) {
|
|
vsetc(VT_FLOAT, VT_CONST, &tokc);
|
|
next();
|
|
} else if (tok == TOK_CDOUBLE) {
|
|
vsetc(VT_DOUBLE, VT_CONST, &tokc);
|
|
next();
|
|
} else if (tok == TOK_CLDOUBLE) {
|
|
vsetc(VT_LDOUBLE, VT_CONST, &tokc);
|
|
next();
|
|
} else if (tok == TOK___FUNC__) {
|
|
void *ptr;
|
|
int len;
|
|
/* special function name identifier */
|
|
|
|
len = strlen(funcname) + 1;
|
|
/* generate char[len] type */
|
|
t = VT_ARRAY | mk_pointer(VT_BYTE);
|
|
s = sym_find(((unsigned)t >> VT_STRUCT_SHIFT));
|
|
s->c = len;
|
|
vpush_ref(t, data_section, data_section->data_offset);
|
|
ptr = section_ptr_add(data_section, len);
|
|
memcpy(ptr, funcname, len);
|
|
next();
|
|
} else if (tok == TOK_LSTR) {
|
|
t = VT_INT;
|
|
goto str_init;
|
|
} else if (tok == TOK_STR) {
|
|
/* string parsing */
|
|
t = VT_BYTE;
|
|
str_init:
|
|
type_size(t, &align);
|
|
data_offset = data_section->data_offset;
|
|
data_offset = (data_offset + align - 1) & -align;
|
|
fc = data_offset;
|
|
/* we must declare it as an array first to use initializer parser */
|
|
t = VT_ARRAY | mk_pointer(t);
|
|
decl_initializer(t, data_section, data_offset, 1, 0);
|
|
data_offset += type_size(t, &align);
|
|
/* XXX: is it correct to put it exactly as an array ? */
|
|
vpush_ref(t, data_section, fc);
|
|
data_section->data_offset = data_offset;
|
|
} else {
|
|
t = tok;
|
|
next();
|
|
if (t == '(') {
|
|
/* cast ? */
|
|
if (parse_btype(&t, &ad)) {
|
|
ft = type_decl(&ad, &n, t, TYPE_ABSTRACT);
|
|
skip(')');
|
|
/* check ISOC99 compound literal */
|
|
if (tok == '{') {
|
|
/* data is allocated locally by default */
|
|
if (global_expr)
|
|
r = VT_CONST;
|
|
else
|
|
r = VT_LOCAL;
|
|
/* all except arrays are lvalues */
|
|
if (!(ft & VT_ARRAY))
|
|
r |= lvalue_type(ft);
|
|
memset(&ad, 0, sizeof(AttributeDef));
|
|
decl_initializer_alloc(ft, &ad, r, 1, 0, 0);
|
|
} else {
|
|
unary();
|
|
gen_cast(ft);
|
|
}
|
|
} else {
|
|
gexpr();
|
|
skip(')');
|
|
}
|
|
} else if (t == '*') {
|
|
unary();
|
|
indir();
|
|
} else if (t == '&') {
|
|
unary();
|
|
/* functions names must be treated as function pointers,
|
|
except for unary '&' and sizeof. Since we consider that
|
|
functions are not lvalues, we only have to handle it
|
|
there and in function calls. */
|
|
/* arrays can also be used although they are not lvalues */
|
|
if ((vtop->t & VT_BTYPE) != VT_FUNC &&
|
|
!(vtop->t & VT_ARRAY))
|
|
test_lvalue();
|
|
vtop->t = mk_pointer(vtop->t);
|
|
gaddrof();
|
|
} else
|
|
if (t == '!') {
|
|
unary();
|
|
if ((vtop->r & (VT_VALMASK | VT_LVAL)) == VT_CONST)
|
|
vtop->c.i = !vtop->c.i;
|
|
else if ((vtop->r & VT_VALMASK) == VT_CMP)
|
|
vtop->c.i = vtop->c.i ^ 1;
|
|
else
|
|
vset(VT_INT, VT_JMP, gtst(1, 0));
|
|
} else
|
|
if (t == '~') {
|
|
unary();
|
|
vpushi(-1);
|
|
gen_op('^');
|
|
} else
|
|
if (t == '+') {
|
|
/* in order to force cast, we add zero */
|
|
unary();
|
|
if ((vtop->t & VT_BTYPE) == VT_PTR)
|
|
error("pointer not accepted for unary plus");
|
|
vpushi(0);
|
|
gen_op('+');
|
|
} else
|
|
if (t == TOK_SIZEOF) {
|
|
if (tok == '(') {
|
|
next();
|
|
if (parse_btype(&t, &ad)) {
|
|
t = type_decl(&ad, &n, t, TYPE_ABSTRACT);
|
|
} else {
|
|
/* XXX: some code could be generated: add eval
|
|
flag */
|
|
gexpr();
|
|
t = vtop->t;
|
|
vpop();
|
|
}
|
|
skip(')');
|
|
} else {
|
|
unary();
|
|
t = vtop->t;
|
|
vpop();
|
|
}
|
|
vpushi(type_size(t, &t));
|
|
} else
|
|
if (t == TOK_INC || t == TOK_DEC) {
|
|
unary();
|
|
inc(0, t);
|
|
} else if (t == '-') {
|
|
vpushi(0);
|
|
unary();
|
|
gen_op('-');
|
|
} else
|
|
{
|
|
if (t < TOK_UIDENT)
|
|
expect("identifier");
|
|
s = sym_find(t);
|
|
if (!s) {
|
|
if (tok != '(')
|
|
error("'%s' undeclared", get_tok_str(t, NULL));
|
|
/* for simple function calls, we tolerate undeclared
|
|
external reference to int() function */
|
|
s = external_sym(t, func_old_type, 0);
|
|
}
|
|
vset(s->t, s->r, s->c);
|
|
/* if forward reference, we must point to s */
|
|
if (vtop->r & VT_SYM)
|
|
vtop->c.sym = s;
|
|
}
|
|
}
|
|
|
|
/* post operations */
|
|
while (1) {
|
|
if (tok == TOK_INC || tok == TOK_DEC) {
|
|
inc(1, tok);
|
|
next();
|
|
} else if (tok == '.' || tok == TOK_ARROW) {
|
|
/* field */
|
|
if (tok == TOK_ARROW)
|
|
indir();
|
|
test_lvalue();
|
|
gaddrof();
|
|
next();
|
|
/* expect pointer on structure */
|
|
if ((vtop->t & VT_BTYPE) != VT_STRUCT)
|
|
expect("struct or union");
|
|
s = sym_find(((unsigned)vtop->t >> VT_STRUCT_SHIFT) | SYM_STRUCT);
|
|
/* find field */
|
|
tok |= SYM_FIELD;
|
|
while ((s = s->next) != NULL) {
|
|
if (s->v == tok)
|
|
break;
|
|
}
|
|
if (!s)
|
|
error("field not found");
|
|
/* add field offset to pointer */
|
|
vtop->t = char_pointer_type; /* change type to 'char *' */
|
|
vpushi(s->c);
|
|
gen_op('+');
|
|
/* change type to field type, and set to lvalue */
|
|
vtop->t = s->t;
|
|
/* an array is never an lvalue */
|
|
if (!(vtop->t & VT_ARRAY))
|
|
vtop->r |= lvalue_type(vtop->t);
|
|
next();
|
|
} else if (tok == '[') {
|
|
next();
|
|
gexpr();
|
|
gen_op('+');
|
|
indir();
|
|
skip(']');
|
|
} else if (tok == '(') {
|
|
SValue ret;
|
|
Sym *sa;
|
|
|
|
/* function call */
|
|
if ((vtop->t & VT_BTYPE) != VT_FUNC) {
|
|
/* pointer test (no array accepted) */
|
|
if ((vtop->t & (VT_BTYPE | VT_ARRAY)) == VT_PTR) {
|
|
vtop->t = pointed_type(vtop->t);
|
|
if ((vtop->t & VT_BTYPE) != VT_FUNC)
|
|
goto error_func;
|
|
} else {
|
|
error_func:
|
|
expect("function pointer");
|
|
}
|
|
} else {
|
|
vtop->r &= ~VT_LVAL; /* no lvalue */
|
|
}
|
|
/* get return type */
|
|
s = sym_find((unsigned)vtop->t >> VT_STRUCT_SHIFT);
|
|
save_regs(0); /* save used temporary registers */
|
|
gfunc_start(&gf, s->r);
|
|
next();
|
|
sa = s->next; /* first parameter */
|
|
#ifdef INVERT_FUNC_PARAMS
|
|
{
|
|
int parlevel;
|
|
Sym *args, *s1;
|
|
ParseState saved_parse_state;
|
|
TokenString str;
|
|
|
|
/* read each argument and store it on a stack */
|
|
/* XXX: merge it with macro args ? */
|
|
args = NULL;
|
|
if (tok != ')') {
|
|
for(;;) {
|
|
tok_str_new(&str);
|
|
parlevel = 0;
|
|
while ((parlevel > 0 || (tok != ')' && tok != ',')) &&
|
|
tok != -1) {
|
|
if (tok == '(')
|
|
parlevel++;
|
|
else if (tok == ')')
|
|
parlevel--;
|
|
tok_str_add_tok(&str);
|
|
next();
|
|
}
|
|
tok_str_add(&str, -1); /* end of file added */
|
|
tok_str_add(&str, 0);
|
|
s1 = sym_push2(&args, 0, 0, (int)str.str);
|
|
s1->next = sa; /* add reference to argument */
|
|
if (sa)
|
|
sa = sa->next;
|
|
if (tok == ')')
|
|
break;
|
|
skip(',');
|
|
}
|
|
}
|
|
|
|
/* now generate code in reverse order by reading the stack */
|
|
save_parse_state(&saved_parse_state);
|
|
while (args) {
|
|
macro_ptr = (int *)args->c;
|
|
next();
|
|
expr_eq();
|
|
if (tok != -1)
|
|
expect("',' or ')'");
|
|
gfunc_param_typed(&gf, s, args->next);
|
|
s1 = args->prev;
|
|
free((int *)args->c);
|
|
free(args);
|
|
args = s1;
|
|
}
|
|
restore_parse_state(&saved_parse_state);
|
|
}
|
|
#endif
|
|
/* compute first implicit argument if a structure is returned */
|
|
if ((s->t & VT_BTYPE) == VT_STRUCT) {
|
|
/* get some space for the returned structure */
|
|
size = type_size(s->t, &align);
|
|
loc = (loc - size) & -align;
|
|
ret.t = s->t;
|
|
ret.r = VT_LOCAL | VT_LVAL;
|
|
/* pass it as 'int' to avoid structure arg passing
|
|
problems */
|
|
vset(VT_INT, VT_LOCAL, loc);
|
|
ret.c = vtop->c;
|
|
gfunc_param(&gf);
|
|
} else {
|
|
ret.t = s->t;
|
|
ret.r2 = VT_CONST;
|
|
/* return in register */
|
|
if (is_float(ret.t)) {
|
|
ret.r = REG_FRET;
|
|
} else {
|
|
if ((ret.t & VT_BTYPE) == VT_LLONG)
|
|
ret.r2 = REG_LRET;
|
|
ret.r = REG_IRET;
|
|
}
|
|
ret.c.i = 0;
|
|
}
|
|
#ifndef INVERT_FUNC_PARAMS
|
|
if (tok != ')') {
|
|
for(;;) {
|
|
expr_eq();
|
|
gfunc_param_typed(&gf, s, sa);
|
|
if (sa)
|
|
sa = sa->next;
|
|
if (tok == ')')
|
|
break;
|
|
skip(',');
|
|
}
|
|
}
|
|
#endif
|
|
if (sa)
|
|
error("too few arguments to function");
|
|
skip(')');
|
|
gfunc_call(&gf);
|
|
/* return value */
|
|
vsetc(ret.t, ret.r, &ret.c);
|
|
vtop->r2 = ret.r2;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void uneq(void)
|
|
{
|
|
int t;
|
|
|
|
unary();
|
|
if (tok == '=' ||
|
|
(tok >= TOK_A_MOD && tok <= TOK_A_DIV) ||
|
|
tok == TOK_A_XOR || tok == TOK_A_OR ||
|
|
tok == TOK_A_SHL || tok == TOK_A_SAR) {
|
|
test_lvalue();
|
|
t = tok;
|
|
next();
|
|
if (t == '=') {
|
|
expr_eq();
|
|
} else {
|
|
vdup();
|
|
expr_eq();
|
|
gen_op(t & 0x7f);
|
|
}
|
|
vstore();
|
|
}
|
|
}
|
|
|
|
void sum(int l)
|
|
{
|
|
int t;
|
|
|
|
if (l == 0)
|
|
uneq();
|
|
else {
|
|
sum(--l);
|
|
while ((l == 0 && (tok == '*' || tok == '/' || tok == '%')) ||
|
|
(l == 1 && (tok == '+' || tok == '-')) ||
|
|
(l == 2 && (tok == TOK_SHL || tok == TOK_SAR)) ||
|
|
(l == 3 && ((tok >= TOK_ULE && tok <= TOK_GT) ||
|
|
tok == TOK_ULT || tok == TOK_UGE)) ||
|
|
(l == 4 && (tok == TOK_EQ || tok == TOK_NE)) ||
|
|
(l == 5 && tok == '&') ||
|
|
(l == 6 && tok == '^') ||
|
|
(l == 7 && tok == '|') ||
|
|
(l == 8 && tok == TOK_LAND) ||
|
|
(l == 9 && tok == TOK_LOR)) {
|
|
t = tok;
|
|
next();
|
|
sum(l);
|
|
gen_op(t);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* only used if non constant */
|
|
void eand(void)
|
|
{
|
|
int t;
|
|
|
|
sum(8);
|
|
t = 0;
|
|
while (1) {
|
|
if (tok != TOK_LAND) {
|
|
if (t) {
|
|
t = gtst(1, t);
|
|
vset(VT_INT, VT_JMPI, t);
|
|
}
|
|
break;
|
|
}
|
|
t = gtst(1, t);
|
|
next();
|
|
sum(8);
|
|
}
|
|
}
|
|
|
|
void eor(void)
|
|
{
|
|
int t;
|
|
|
|
eand();
|
|
t = 0;
|
|
while (1) {
|
|
if (tok != TOK_LOR) {
|
|
if (t) {
|
|
t = gtst(0, t);
|
|
vset(VT_INT, VT_JMP, t);
|
|
}
|
|
break;
|
|
}
|
|
t = gtst(0, t);
|
|
next();
|
|
eand();
|
|
}
|
|
}
|
|
|
|
/* XXX: better constant handling */
|
|
void expr_eq(void)
|
|
{
|
|
int t, u, c, r1, r2, rc;
|
|
|
|
if (const_wanted) {
|
|
sum(10);
|
|
if (tok == '?') {
|
|
c = vtop->c.i;
|
|
vpop();
|
|
next();
|
|
gexpr();
|
|
t = vtop->c.i;
|
|
vpop();
|
|
skip(':');
|
|
expr_eq();
|
|
if (c)
|
|
vtop->c.i = t;
|
|
}
|
|
} else {
|
|
eor();
|
|
if (tok == '?') {
|
|
next();
|
|
save_regs(1); /* we need to save all registers here except
|
|
at the top because it is a branch point */
|
|
t = gtst(1, 0);
|
|
gexpr();
|
|
/* XXX: long long handling ? */
|
|
rc = RC_INT;
|
|
if (is_float(vtop->t))
|
|
rc = RC_FLOAT;
|
|
r1 = gv(rc);
|
|
vtop--; /* no vpop so that FP stack is not flushed */
|
|
skip(':');
|
|
u = gjmp(0);
|
|
|
|
gsym(t);
|
|
expr_eq();
|
|
r2 = gv(rc);
|
|
move_reg(r1, r2);
|
|
vtop->r = r1;
|
|
gsym(u);
|
|
}
|
|
}
|
|
}
|
|
|
|
void gexpr(void)
|
|
{
|
|
while (1) {
|
|
expr_eq();
|
|
if (tok != ',')
|
|
break;
|
|
vpop();
|
|
next();
|
|
}
|
|
}
|
|
|
|
/* parse a constant expression and return value in vtop */
|
|
void expr_const1(void)
|
|
{
|
|
int a;
|
|
a = const_wanted;
|
|
const_wanted = 1;
|
|
expr_eq();
|
|
if ((vtop->r & (VT_VALMASK | VT_LVAL)) != VT_CONST)
|
|
expect("constant");
|
|
const_wanted = a;
|
|
}
|
|
|
|
/* parse an integer constant and return its value */
|
|
int expr_const(void)
|
|
{
|
|
int c;
|
|
expr_const1();
|
|
c = vtop->c.i;
|
|
vpop();
|
|
return c;
|
|
}
|
|
|
|
/* return the label token if current token is a label, otherwise
|
|
return zero */
|
|
int is_label(void)
|
|
{
|
|
int t;
|
|
CValue c;
|
|
|
|
/* fast test first */
|
|
if (tok < TOK_UIDENT)
|
|
return 0;
|
|
/* no need to save tokc since we expect an identifier */
|
|
t = tok;
|
|
c = tokc;
|
|
next();
|
|
if (tok == ':') {
|
|
next();
|
|
return t;
|
|
} else {
|
|
/* XXX: may not work in all cases (macros ?) */
|
|
tok1 = tok;
|
|
tok1c = tokc;
|
|
tok = t;
|
|
tokc = c;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
void block(int *bsym, int *csym, int *case_sym, int *def_sym, int case_reg)
|
|
{
|
|
int a, b, c, d;
|
|
Sym *s;
|
|
|
|
/* generate line number info */
|
|
if (do_debug &&
|
|
(last_line_num != file->line_num || last_ind != ind)) {
|
|
put_stabn(N_SLINE, 0, file->line_num, ind - func_ind);
|
|
last_ind = ind;
|
|
last_line_num = file->line_num;
|
|
}
|
|
|
|
if (tok == TOK_IF) {
|
|
/* if test */
|
|
next();
|
|
skip('(');
|
|
gexpr();
|
|
skip(')');
|
|
a = gtst(1, 0);
|
|
block(bsym, csym, case_sym, def_sym, case_reg);
|
|
c = tok;
|
|
if (c == TOK_ELSE) {
|
|
next();
|
|
d = gjmp(0);
|
|
gsym(a);
|
|
block(bsym, csym, case_sym, def_sym, case_reg);
|
|
gsym(d); /* patch else jmp */
|
|
} else
|
|
gsym(a);
|
|
} else if (tok == TOK_WHILE) {
|
|
next();
|
|
d = ind;
|
|
skip('(');
|
|
gexpr();
|
|
skip(')');
|
|
a = gtst(1, 0);
|
|
b = 0;
|
|
block(&a, &b, case_sym, def_sym, case_reg);
|
|
gjmp_addr(d);
|
|
gsym(a);
|
|
gsym_addr(b, d);
|
|
} else if (tok == '{') {
|
|
next();
|
|
/* declarations */
|
|
s = local_stack.top;
|
|
while (tok != '}') {
|
|
decl(VT_LOCAL);
|
|
if (tok != '}')
|
|
block(bsym, csym, case_sym, def_sym, case_reg);
|
|
}
|
|
/* pop locally defined symbols */
|
|
sym_pop(&local_stack, s);
|
|
next();
|
|
} else if (tok == TOK_RETURN) {
|
|
next();
|
|
if (tok != ';') {
|
|
gexpr();
|
|
gen_assign_cast(func_vt);
|
|
if ((func_vt & VT_BTYPE) == VT_STRUCT) {
|
|
/* if returning structure, must copy it to implicit
|
|
first pointer arg location */
|
|
vset(mk_pointer(func_vt), VT_LOCAL | VT_LVAL, func_vc);
|
|
indir();
|
|
vswap();
|
|
/* copy structure value to pointer */
|
|
vstore();
|
|
} else if (is_float(func_vt)) {
|
|
gv(RC_FRET);
|
|
} else {
|
|
gv(RC_IRET);
|
|
}
|
|
vtop--; /* NOT vpop() because on x86 it would flush the fp stack */
|
|
}
|
|
skip(';');
|
|
rsym = gjmp(rsym); /* jmp */
|
|
} else if (tok == TOK_BREAK) {
|
|
/* compute jump */
|
|
if (!bsym)
|
|
error("cannot break");
|
|
*bsym = gjmp(*bsym);
|
|
next();
|
|
skip(';');
|
|
} else if (tok == TOK_CONTINUE) {
|
|
/* compute jump */
|
|
if (!csym)
|
|
error("cannot continue");
|
|
*csym = gjmp(*csym);
|
|
next();
|
|
skip(';');
|
|
} else if (tok == TOK_FOR) {
|
|
int e;
|
|
next();
|
|
skip('(');
|
|
if (tok != ';') {
|
|
gexpr();
|
|
vpop();
|
|
}
|
|
skip(';');
|
|
d = ind;
|
|
c = ind;
|
|
a = 0;
|
|
b = 0;
|
|
if (tok != ';') {
|
|
gexpr();
|
|
a = gtst(1, 0);
|
|
}
|
|
skip(';');
|
|
if (tok != ')') {
|
|
e = gjmp(0);
|
|
c = ind;
|
|
gexpr();
|
|
vpop();
|
|
gjmp_addr(d);
|
|
gsym(e);
|
|
}
|
|
skip(')');
|
|
block(&a, &b, case_sym, def_sym, case_reg);
|
|
gjmp_addr(c);
|
|
gsym(a);
|
|
gsym_addr(b, c);
|
|
} else
|
|
if (tok == TOK_DO) {
|
|
next();
|
|
a = 0;
|
|
b = 0;
|
|
d = ind;
|
|
block(&a, &b, case_sym, def_sym, case_reg);
|
|
skip(TOK_WHILE);
|
|
skip('(');
|
|
gsym(b);
|
|
gexpr();
|
|
c = gtst(0, 0);
|
|
gsym_addr(c, d);
|
|
skip(')');
|
|
gsym(a);
|
|
skip(';');
|
|
} else
|
|
if (tok == TOK_SWITCH) {
|
|
next();
|
|
skip('(');
|
|
gexpr();
|
|
/* XXX: other types than integer */
|
|
case_reg = gv(RC_INT);
|
|
vpop();
|
|
skip(')');
|
|
a = 0;
|
|
b = gjmp(0); /* jump to first case */
|
|
c = 0;
|
|
block(&a, csym, &b, &c, case_reg);
|
|
/* if no default, jmp after switch */
|
|
if (c == 0)
|
|
c = ind;
|
|
/* default label */
|
|
gsym_addr(b, c);
|
|
/* break label */
|
|
gsym(a);
|
|
} else
|
|
if (tok == TOK_CASE) {
|
|
int v1, v2;
|
|
if (!case_sym)
|
|
expect("switch");
|
|
next();
|
|
v1 = expr_const();
|
|
v2 = v1;
|
|
if (gnu_ext && tok == TOK_DOTS) {
|
|
next();
|
|
v2 = expr_const();
|
|
if (v2 < v1)
|
|
warning("empty case range");
|
|
}
|
|
/* since a case is like a label, we must skip it with a jmp */
|
|
b = gjmp(0);
|
|
gsym(*case_sym);
|
|
vset(VT_INT, case_reg, 0);
|
|
vpushi(v1);
|
|
if (v1 == v2) {
|
|
gen_op(TOK_EQ);
|
|
*case_sym = gtst(1, 0);
|
|
} else {
|
|
gen_op(TOK_GE);
|
|
*case_sym = gtst(1, 0);
|
|
vset(VT_INT, case_reg, 0);
|
|
vpushi(v2);
|
|
gen_op(TOK_LE);
|
|
*case_sym = gtst(1, *case_sym);
|
|
}
|
|
gsym(b);
|
|
skip(':');
|
|
block(bsym, csym, case_sym, def_sym, case_reg);
|
|
} else
|
|
if (tok == TOK_DEFAULT) {
|
|
next();
|
|
skip(':');
|
|
if (!def_sym)
|
|
expect("switch");
|
|
if (*def_sym)
|
|
error("too many 'default'");
|
|
*def_sym = ind;
|
|
block(bsym, csym, case_sym, def_sym, case_reg);
|
|
} else
|
|
if (tok == TOK_GOTO) {
|
|
next();
|
|
s = sym_find1(&label_stack, tok);
|
|
/* put forward definition if needed */
|
|
if (!s)
|
|
s = sym_push1(&label_stack, tok, LABEL_FORWARD, 0);
|
|
/* label already defined */
|
|
if (s->t & LABEL_FORWARD)
|
|
s->c = gjmp(s->c);
|
|
else
|
|
gjmp_addr(s->c);
|
|
next();
|
|
skip(';');
|
|
} else {
|
|
b = is_label();
|
|
if (b) {
|
|
/* label case */
|
|
s = sym_find1(&label_stack, b);
|
|
if (s) {
|
|
if (!(s->t & LABEL_FORWARD))
|
|
error("multiple defined label");
|
|
gsym(s->c);
|
|
s->c = ind;
|
|
s->t = 0;
|
|
} else {
|
|
sym_push1(&label_stack, b, 0, ind);
|
|
}
|
|
/* we accept this, but it is a mistake */
|
|
if (tok == '}')
|
|
warning("deprecated use of label at end of compound statement");
|
|
else
|
|
block(bsym, csym, case_sym, def_sym, case_reg);
|
|
} else {
|
|
/* expression case */
|
|
if (tok != ';') {
|
|
gexpr();
|
|
vpop();
|
|
}
|
|
skip(';');
|
|
}
|
|
}
|
|
}
|
|
|
|
/* t is the array or struct type. c is the array or struct
|
|
address. cur_index/cur_field is the pointer to the current
|
|
value. 'size_only' is true if only size info is needed (only used
|
|
in arrays) */
|
|
void decl_designator(int t, Section *sec, unsigned long c,
|
|
int *cur_index, Sym **cur_field,
|
|
int size_only)
|
|
{
|
|
Sym *s, *f;
|
|
int notfirst, index, align, l;
|
|
|
|
notfirst = 0;
|
|
if (gnu_ext && (l = is_label()) != 0)
|
|
goto struct_field;
|
|
|
|
while (tok == '[' || tok == '.') {
|
|
if (tok == '[') {
|
|
if (!(t & VT_ARRAY))
|
|
expect("array type");
|
|
s = sym_find(((unsigned)t >> VT_STRUCT_SHIFT));
|
|
next();
|
|
index = expr_const();
|
|
if (index < 0 || (s->c >= 0 && index >= s->c))
|
|
expect("invalid index");
|
|
skip(']');
|
|
if (!notfirst)
|
|
*cur_index = index;
|
|
t = pointed_type(t);
|
|
c += index * type_size(t, &align);
|
|
} else {
|
|
next();
|
|
l = tok;
|
|
next();
|
|
struct_field:
|
|
if ((t & VT_BTYPE) != VT_STRUCT)
|
|
expect("struct/union type");
|
|
s = sym_find(((unsigned)t >> VT_STRUCT_SHIFT) | SYM_STRUCT);
|
|
l |= SYM_FIELD;
|
|
f = s->next;
|
|
while (f) {
|
|
if (f->v == l)
|
|
break;
|
|
f = f->next;
|
|
}
|
|
if (!f)
|
|
expect("field");
|
|
if (!notfirst)
|
|
*cur_field = f;
|
|
t = f->t | (t & ~VT_TYPE);
|
|
c += f->c;
|
|
}
|
|
notfirst = 1;
|
|
}
|
|
if (notfirst) {
|
|
if (tok == '=') {
|
|
next();
|
|
} else {
|
|
if (!gnu_ext)
|
|
expect("=");
|
|
}
|
|
} else {
|
|
if (t & VT_ARRAY) {
|
|
index = *cur_index;
|
|
t = pointed_type(t);
|
|
c += index * type_size(t, &align);
|
|
} else {
|
|
f = *cur_field;
|
|
if (!f)
|
|
error("too many field init");
|
|
t = f->t | (t & ~VT_TYPE);
|
|
c += f->c;
|
|
}
|
|
}
|
|
decl_initializer(t, sec, c, 0, size_only);
|
|
}
|
|
|
|
#define EXPR_VAL 0
|
|
#define EXPR_CONST 1
|
|
#define EXPR_ANY 2
|
|
|
|
/* store a value or an expression directly in global data or in local array */
|
|
void init_putv(int t, Section *sec, unsigned long c,
|
|
int v, int expr_type)
|
|
{
|
|
int saved_global_expr, bt;
|
|
void *ptr;
|
|
|
|
switch(expr_type) {
|
|
case EXPR_VAL:
|
|
vpushi(v);
|
|
break;
|
|
case EXPR_CONST:
|
|
/* compound literals must be allocated globally in this case */
|
|
saved_global_expr = global_expr;
|
|
global_expr = 1;
|
|
expr_const1();
|
|
global_expr = saved_global_expr;
|
|
break;
|
|
case EXPR_ANY:
|
|
expr_eq();
|
|
break;
|
|
}
|
|
|
|
if (sec) {
|
|
/* XXX: not portable */
|
|
/* XXX: generate error if incorrect relocation */
|
|
gen_assign_cast(t);
|
|
bt = t & VT_BTYPE;
|
|
ptr = sec->data + c;
|
|
if ((vtop->r & VT_SYM) &&
|
|
(bt == VT_BYTE ||
|
|
bt == VT_SHORT ||
|
|
bt == VT_DOUBLE ||
|
|
bt == VT_LDOUBLE ||
|
|
bt == VT_LLONG))
|
|
error("initializer element is not computable at load time");
|
|
switch(bt) {
|
|
case VT_BYTE:
|
|
*(char *)ptr = vtop->c.i;
|
|
break;
|
|
case VT_SHORT:
|
|
*(short *)ptr = vtop->c.i;
|
|
break;
|
|
case VT_DOUBLE:
|
|
*(double *)ptr = vtop->c.d;
|
|
break;
|
|
case VT_LDOUBLE:
|
|
*(long double *)ptr = vtop->c.ld;
|
|
break;
|
|
case VT_LLONG:
|
|
*(long long *)ptr = vtop->c.ll;
|
|
break;
|
|
default:
|
|
if (vtop->r & VT_SYM) {
|
|
greloc(sec, vtop->c.sym, c, R_DATA_32);
|
|
*(int *)ptr = 0;
|
|
} else {
|
|
*(int *)ptr = vtop->c.i;
|
|
}
|
|
break;
|
|
}
|
|
vtop--;
|
|
} else {
|
|
vset(t, VT_LOCAL, c);
|
|
vswap();
|
|
vstore();
|
|
vpop();
|
|
}
|
|
}
|
|
|
|
/* put zeros for variable based init */
|
|
void init_putz(int t, Section *sec, unsigned long c, int size)
|
|
{
|
|
GFuncContext gf;
|
|
|
|
if (sec) {
|
|
/* nothing to do because globals are already set to zero */
|
|
} else {
|
|
gfunc_start(&gf, FUNC_CDECL);
|
|
vpushi(size);
|
|
gfunc_param(&gf);
|
|
vpushi(0);
|
|
gfunc_param(&gf);
|
|
vset(VT_INT, VT_LOCAL, c);
|
|
gfunc_param(&gf);
|
|
vpush_sym(func_old_type, TOK_memset);
|
|
gfunc_call(&gf);
|
|
}
|
|
}
|
|
|
|
/* 't' contains the type and storage info. 'c' is the offset of the
|
|
object in section 'sec'. If 'sec' is NULL, it means stack based
|
|
allocation. 'first' is true if array '{' must be read (multi
|
|
dimension implicit array init handling). 'size_only' is true if
|
|
size only evaluation is wanted (only for arrays). */
|
|
void decl_initializer(int t, Section *sec, unsigned long c, int first, int size_only)
|
|
{
|
|
int index, array_length, n, no_oblock, nb, parlevel, i;
|
|
int t1, size1, align1, expr_type;
|
|
Sym *s, *f;
|
|
TokenSym *ts;
|
|
|
|
if (t & VT_ARRAY) {
|
|
s = sym_find(((unsigned)t >> VT_STRUCT_SHIFT));
|
|
n = s->c;
|
|
array_length = 0;
|
|
t1 = pointed_type(t);
|
|
size1 = type_size(t1, &align1);
|
|
|
|
no_oblock = 1;
|
|
if ((first && tok != TOK_LSTR && tok != TOK_STR) ||
|
|
tok == '{') {
|
|
skip('{');
|
|
no_oblock = 0;
|
|
}
|
|
|
|
/* only parse strings here if correct type (otherwise: handle
|
|
them as ((w)char *) expressions */
|
|
if ((tok == TOK_LSTR &&
|
|
(t1 & VT_BTYPE) == VT_INT) ||
|
|
(tok == TOK_STR &&
|
|
(t1 & VT_BTYPE) == VT_BYTE)) {
|
|
/* XXX: move multiple string parsing in parser ? */
|
|
while (tok == TOK_STR || tok == TOK_LSTR) {
|
|
ts = tokc.ts;
|
|
/* compute maximum number of chars wanted */
|
|
nb = ts->len;
|
|
if (n >= 0 && nb > (n - array_length))
|
|
nb = n - array_length;
|
|
if (!size_only) {
|
|
if (ts->len > nb)
|
|
warning("initializer-string for array is too long");
|
|
for(i=0;i<nb;i++) {
|
|
init_putv(t1, sec, c + (array_length + i) * size1,
|
|
ts->str[i], EXPR_VAL);
|
|
}
|
|
}
|
|
array_length += nb;
|
|
next();
|
|
}
|
|
/* only add trailing zero if enough storage (no
|
|
warning in this case since it is standard) */
|
|
if (n < 0 || array_length < n) {
|
|
if (!size_only) {
|
|
init_putv(t1, sec, c + (array_length * size1), 0, EXPR_VAL);
|
|
}
|
|
array_length++;
|
|
}
|
|
} else {
|
|
index = 0;
|
|
while (tok != '}') {
|
|
decl_designator(t, sec, c, &index, NULL, size_only);
|
|
if (n >= 0 && index >= n)
|
|
error("index too large");
|
|
/* must put zero in holes (note that doing it that way
|
|
ensures that it even works with designators) */
|
|
if (!size_only && array_length < index) {
|
|
init_putz(t1, sec, c + array_length * size1,
|
|
(index - array_length) * size1);
|
|
}
|
|
index++;
|
|
if (index > array_length)
|
|
array_length = index;
|
|
/* special test for multi dimensional arrays (may not
|
|
be strictly correct if designators are used at the
|
|
same time) */
|
|
if (index >= n && no_oblock)
|
|
break;
|
|
if (tok == '}')
|
|
break;
|
|
skip(',');
|
|
}
|
|
}
|
|
if (!no_oblock)
|
|
skip('}');
|
|
/* put zeros at the end */
|
|
if (!size_only && n >= 0 && array_length < n) {
|
|
init_putz(t1, sec, c + array_length * size1,
|
|
(n - array_length) * size1);
|
|
}
|
|
/* patch type size if needed */
|
|
if (n < 0)
|
|
s->c = array_length;
|
|
} else if ((t & VT_BTYPE) == VT_STRUCT && tok == '{') {
|
|
/* XXX: union needs only one init */
|
|
next();
|
|
s = sym_find(((unsigned)t >> VT_STRUCT_SHIFT) | SYM_STRUCT);
|
|
f = s->next;
|
|
array_length = 0;
|
|
index = 0;
|
|
n = s->c;
|
|
while (tok != '}') {
|
|
decl_designator(t, sec, c, NULL, &f, size_only);
|
|
/* fill with zero between fields */
|
|
index = f->c;
|
|
if (!size_only && array_length < index) {
|
|
init_putz(t, sec, c + array_length,
|
|
index - array_length);
|
|
}
|
|
index = index + type_size(f->t, &align1);
|
|
if (index > array_length)
|
|
array_length = index;
|
|
if (tok == '}')
|
|
break;
|
|
skip(',');
|
|
f = f->next;
|
|
}
|
|
/* put zeros at the end */
|
|
if (!size_only && array_length < n) {
|
|
init_putz(t, sec, c + array_length,
|
|
n - array_length);
|
|
}
|
|
skip('}');
|
|
} else if (tok == '{') {
|
|
next();
|
|
decl_initializer(t, sec, c, first, size_only);
|
|
skip('}');
|
|
} else if (size_only) {
|
|
/* just skip expression */
|
|
parlevel = 0;
|
|
while ((parlevel > 0 || (tok != '}' && tok != ',')) &&
|
|
tok != -1) {
|
|
if (tok == '(')
|
|
parlevel++;
|
|
else if (tok == ')')
|
|
parlevel--;
|
|
next();
|
|
}
|
|
} else {
|
|
/* currently, we always use constant expression for globals
|
|
(may change for scripting case) */
|
|
expr_type = EXPR_CONST;
|
|
if (!sec)
|
|
expr_type = EXPR_ANY;
|
|
init_putv(t, sec, c, 0, expr_type);
|
|
}
|
|
}
|
|
|
|
/* parse an initializer for type 't' if 'has_init' is true, and
|
|
allocate space in local or global data space ('r' is either
|
|
VT_LOCAL or VT_CONST). If 'v' is non zero, then an associated
|
|
variable 'v' of scope 'scope' is declared before initializers are
|
|
parsed. If 'v' is zero, then a reference to the new object is put
|
|
in the value stack. */
|
|
void decl_initializer_alloc(int t, AttributeDef *ad, int r, int has_init,
|
|
int v, int scope)
|
|
{
|
|
int size, align, addr, data_offset;
|
|
int level;
|
|
ParseState saved_parse_state;
|
|
TokenString init_str;
|
|
Section *sec;
|
|
|
|
size = type_size(t, &align);
|
|
/* If unknown size, we must evaluate it before
|
|
evaluating initializers because
|
|
initializers can generate global data too
|
|
(e.g. string pointers or ISOC99 compound
|
|
literals). It also simplifies local
|
|
initializers handling */
|
|
tok_str_new(&init_str);
|
|
if (size < 0) {
|
|
if (!has_init)
|
|
error("unknown type size");
|
|
/* get all init string */
|
|
level = 0;
|
|
while (level > 0 || (tok != ',' && tok != ';')) {
|
|
if (tok < 0)
|
|
error("unexpected end of file in initializer");
|
|
tok_str_add_tok(&init_str);
|
|
if (tok == '{')
|
|
level++;
|
|
else if (tok == '}') {
|
|
if (level == 0)
|
|
break;
|
|
level--;
|
|
}
|
|
next();
|
|
}
|
|
tok_str_add(&init_str, -1);
|
|
tok_str_add(&init_str, 0);
|
|
|
|
/* compute size */
|
|
save_parse_state(&saved_parse_state);
|
|
|
|
macro_ptr = init_str.str;
|
|
next();
|
|
decl_initializer(t, NULL, 0, 1, 1);
|
|
/* prepare second initializer parsing */
|
|
macro_ptr = init_str.str;
|
|
next();
|
|
|
|
/* if still unknown size, error */
|
|
size = type_size(t, &align);
|
|
if (size < 0)
|
|
error("unknown type size");
|
|
}
|
|
/* take into account specified alignment if bigger */
|
|
if (ad->aligned > align)
|
|
align = ad->aligned;
|
|
if ((r & VT_VALMASK) == VT_LOCAL) {
|
|
sec = NULL;
|
|
if (do_bounds_check && (t & VT_ARRAY))
|
|
loc--;
|
|
#ifdef TCC_TARGET_IL
|
|
/* XXX: ugly patch to allocate local variables for IL, just
|
|
for testing */
|
|
addr = loc;
|
|
loc++;
|
|
#else
|
|
loc = (loc - size) & -align;
|
|
addr = loc;
|
|
#endif
|
|
/* handles bounds */
|
|
/* XXX: currently, since we do only one pass, we cannot track
|
|
'&' operators, so we add only arrays */
|
|
if (do_bounds_check && (t & VT_ARRAY)) {
|
|
unsigned long *bounds_ptr;
|
|
/* add padding between regions */
|
|
loc--;
|
|
/* then add local bound info */
|
|
bounds_ptr = section_ptr_add(lbounds_section, 2 * sizeof(unsigned long));
|
|
bounds_ptr[0] = addr;
|
|
bounds_ptr[1] = size;
|
|
}
|
|
} else {
|
|
/* compute section */
|
|
sec = ad->section;
|
|
if (!sec) {
|
|
if (has_init)
|
|
sec = data_section;
|
|
else
|
|
sec = bss_section;
|
|
}
|
|
data_offset = sec->data_offset;
|
|
data_offset = (data_offset + align - 1) & -align;
|
|
addr = data_offset;
|
|
/* very important to increment global pointer at this time
|
|
because initializers themselves can create new initializers */
|
|
data_offset += size;
|
|
/* add padding if bound check */
|
|
if (do_bounds_check)
|
|
data_offset++;
|
|
sec->data_offset = data_offset;
|
|
}
|
|
if (!sec) {
|
|
if (v) {
|
|
/* local variable */
|
|
sym_push(v, t, r, addr);
|
|
} else {
|
|
/* push local reference */
|
|
vset(t, r, addr);
|
|
}
|
|
} else {
|
|
Sym *sym;
|
|
|
|
if (v) {
|
|
if (scope == VT_CONST) {
|
|
/* global scope: see if already defined */
|
|
sym = sym_find(v);
|
|
if (!sym)
|
|
goto do_def;
|
|
if (!is_compatible_types(sym->t, t))
|
|
error("incompatible types for redefinition of '%s'",
|
|
get_tok_str(v, NULL));
|
|
if (!(sym->t & VT_EXTERN))
|
|
error("redefinition of '%s'", get_tok_str(v, NULL));
|
|
sym->t &= ~VT_EXTERN;
|
|
} else {
|
|
do_def:
|
|
sym = sym_push(v, t, r | VT_SYM, 0);
|
|
}
|
|
put_extern_sym(sym, sec, addr);
|
|
} else {
|
|
CValue cval;
|
|
|
|
/* push global reference */
|
|
sym = get_sym_ref(t, sec, addr, 0);
|
|
cval.sym = sym;
|
|
vsetc(t, VT_CONST | VT_SYM, &cval);
|
|
}
|
|
|
|
/* handles bounds now because the symbol must be defined
|
|
before for the relocation */
|
|
if (do_bounds_check) {
|
|
unsigned long *bounds_ptr;
|
|
|
|
greloc(bounds_section, sym, bounds_section->data_offset, R_DATA_32);
|
|
/* then add global bound info */
|
|
bounds_ptr = section_ptr_add(bounds_section, 2 * sizeof(long));
|
|
bounds_ptr[0] = 0; /* relocated */
|
|
bounds_ptr[1] = size;
|
|
}
|
|
}
|
|
if (has_init) {
|
|
decl_initializer(t, sec, addr, 1, 0);
|
|
/* restore parse state if needed */
|
|
if (init_str.str) {
|
|
free(init_str.str);
|
|
restore_parse_state(&saved_parse_state);
|
|
}
|
|
}
|
|
}
|
|
|
|
void put_func_debug(Sym *sym)
|
|
{
|
|
char buf[512];
|
|
|
|
/* stabs info */
|
|
/* XXX: we put here a dummy type */
|
|
snprintf(buf, sizeof(buf), "%s:%c1",
|
|
funcname, sym->t & VT_STATIC ? 'f' : 'F');
|
|
put_stabs_r(buf, N_FUN, 0, file->line_num, 0,
|
|
cur_text_section, sym->c);
|
|
func_ind = ind;
|
|
last_ind = 0;
|
|
last_line_num = 0;
|
|
}
|
|
|
|
/* not finished : try to put some local vars in registers */
|
|
//#define CONFIG_REG_VARS
|
|
|
|
#ifdef CONFIG_REG_VARS
|
|
void add_var_ref(int t)
|
|
{
|
|
printf("%s:%d: &%s\n",
|
|
file->filename, file->line_num,
|
|
get_tok_str(t, NULL));
|
|
}
|
|
|
|
/* first pass on a function with heuristic to extract variable usage
|
|
and pointer references to local variables for register allocation */
|
|
void analyse_function(void)
|
|
{
|
|
int level, t;
|
|
|
|
for(;;) {
|
|
if (tok == -1)
|
|
break;
|
|
/* any symbol coming after '&' is considered as being a
|
|
variable whose reference is taken. It is highly unaccurate
|
|
but it is difficult to do better without a complete parse */
|
|
if (tok == '&') {
|
|
next();
|
|
/* if '& number', then no need to examine next tokens */
|
|
if (tok == TOK_CINT ||
|
|
tok == TOK_CUINT ||
|
|
tok == TOK_CLLONG ||
|
|
tok == TOK_CULLONG) {
|
|
continue;
|
|
} else if (tok >= TOK_UIDENT) {
|
|
/* if '& ident [' or '& ident ->', then ident address
|
|
is not needed */
|
|
t = tok;
|
|
next();
|
|
if (tok != '[' && tok != TOK_ARROW)
|
|
add_var_ref(t);
|
|
} else {
|
|
level = 0;
|
|
while (tok != '}' && tok != ';' &&
|
|
!((tok == ',' || tok == ')') && level == 0)) {
|
|
if (tok >= TOK_UIDENT) {
|
|
add_var_ref(tok);
|
|
} else if (tok == '(') {
|
|
level++;
|
|
} else if (tok == ')') {
|
|
level--;
|
|
}
|
|
next();
|
|
}
|
|
}
|
|
} else {
|
|
next();
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/* 'l' is VT_LOCAL or VT_CONST to define default storage type */
|
|
void decl(int l)
|
|
{
|
|
int t, b, v, has_init, r;
|
|
Sym *sym;
|
|
AttributeDef ad;
|
|
|
|
while (1) {
|
|
if (!parse_btype(&b, &ad)) {
|
|
/* skip redundant ';' */
|
|
/* XXX: find more elegant solution */
|
|
if (tok == ';') {
|
|
next();
|
|
continue;
|
|
}
|
|
/* special test for old K&R protos without explicit int
|
|
type. Only accepted when defining global data */
|
|
if (l == VT_LOCAL || tok < TOK_DEFINE)
|
|
break;
|
|
b = VT_INT;
|
|
}
|
|
if (((b & VT_BTYPE) == VT_ENUM ||
|
|
(b & VT_BTYPE) == VT_STRUCT) &&
|
|
tok == ';') {
|
|
/* we accept no variable after */
|
|
next();
|
|
continue;
|
|
}
|
|
while (1) { /* iterate thru each declaration */
|
|
t = type_decl(&ad, &v, b, TYPE_DIRECT);
|
|
#if 0
|
|
{
|
|
char buf[500];
|
|
type_to_str(buf, sizeof(buf), t, get_tok_str(v, NULL));
|
|
printf("type = '%s'\n", buf);
|
|
}
|
|
#endif
|
|
if (tok == '{') {
|
|
#ifdef CONFIG_REG_VARS
|
|
TokenString func_str;
|
|
ParseState saved_parse_state;
|
|
int block_level;
|
|
#endif
|
|
|
|
if (l == VT_LOCAL)
|
|
error("cannot use local functions");
|
|
if (!(t & VT_FUNC))
|
|
expect("function definition");
|
|
|
|
#ifdef CONFIG_REG_VARS
|
|
/* parse all function code and record it */
|
|
|
|
tok_str_new(&func_str);
|
|
|
|
block_level = 0;
|
|
for(;;) {
|
|
int t;
|
|
if (tok == -1)
|
|
error("unexpected end of file");
|
|
tok_str_add_tok(&func_str);
|
|
t = tok;
|
|
next();
|
|
if (t == '{') {
|
|
block_level++;
|
|
} else if (t == '}') {
|
|
block_level--;
|
|
if (block_level == 0)
|
|
break;
|
|
}
|
|
}
|
|
tok_str_add(&func_str, -1);
|
|
tok_str_add(&func_str, 0);
|
|
|
|
save_parse_state(&saved_parse_state);
|
|
|
|
macro_ptr = func_str.str;
|
|
next();
|
|
analyse_function();
|
|
#endif
|
|
|
|
/* compute text section */
|
|
cur_text_section = ad.section;
|
|
if (!cur_text_section)
|
|
cur_text_section = text_section;
|
|
ind = cur_text_section->data_offset;
|
|
funcname = get_tok_str(v, NULL);
|
|
sym = sym_find(v);
|
|
if (sym) {
|
|
/* if symbol is already defined, then put complete type */
|
|
sym->t = t;
|
|
} else {
|
|
/* put function symbol */
|
|
sym = sym_push1(&global_stack, v, t, 0);
|
|
}
|
|
put_extern_sym(sym, cur_text_section, ind);
|
|
sym->r = VT_SYM | VT_CONST;
|
|
/* put debug symbol */
|
|
if (do_debug)
|
|
put_func_debug(sym);
|
|
/* push a dummy symbol to enable local sym storage */
|
|
sym_push1(&local_stack, 0, 0, 0);
|
|
gfunc_prolog(t);
|
|
loc = 0;
|
|
rsym = 0;
|
|
#ifdef CONFIG_REG_VARS
|
|
macro_ptr = func_str.str;
|
|
next();
|
|
#endif
|
|
block(NULL, NULL, NULL, NULL, 0);
|
|
gsym(rsym);
|
|
gfunc_epilog();
|
|
cur_text_section->data_offset = ind;
|
|
sym_pop(&label_stack, NULL); /* reset label stack */
|
|
sym_pop(&local_stack, NULL); /* reset local stack */
|
|
/* end of function */
|
|
if (do_debug) {
|
|
put_stabn(N_FUN, 0, 0, ind - func_ind);
|
|
}
|
|
funcname = ""; /* for safety */
|
|
func_vt = VT_VOID; /* for safety */
|
|
ind = 0; /* for safety */
|
|
|
|
#ifdef CONFIG_REG_VARS
|
|
free(func_str.str);
|
|
restore_parse_state(&saved_parse_state);
|
|
#endif
|
|
break;
|
|
} else {
|
|
if (b & VT_TYPEDEF) {
|
|
/* save typedefed type */
|
|
/* XXX: test storage specifiers ? */
|
|
sym_push(v, t | VT_TYPEDEF, 0, 0);
|
|
} else if ((t & VT_BTYPE) == VT_FUNC) {
|
|
/* external function definition */
|
|
external_sym(v, t, 0);
|
|
} else {
|
|
/* not lvalue if array */
|
|
r = 0;
|
|
if (!(t & VT_ARRAY))
|
|
r |= lvalue_type(t);
|
|
if (b & VT_EXTERN) {
|
|
/* external variable */
|
|
external_sym(v, t, r);
|
|
} else {
|
|
if (t & VT_STATIC)
|
|
r |= VT_CONST;
|
|
else
|
|
r |= l;
|
|
has_init = (tok == '=');
|
|
if (has_init)
|
|
next();
|
|
decl_initializer_alloc(t, &ad, r,
|
|
has_init, v, l);
|
|
}
|
|
}
|
|
if (tok != ',') {
|
|
skip(';');
|
|
break;
|
|
}
|
|
next();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* compile the C file opened in 'file'. Return non zero if errors. */
|
|
static int tcc_compile(TCCState *s)
|
|
{
|
|
Sym *define_start;
|
|
char buf[512];
|
|
int p, section_sym;
|
|
|
|
funcname = "";
|
|
include_stack_ptr = include_stack;
|
|
ifdef_stack_ptr = ifdef_stack;
|
|
|
|
vtop = vstack - 1;
|
|
anon_sym = SYM_FIRST_ANOM;
|
|
|
|
/* file info: full path + filename */
|
|
section_sym = 0; /* avoid warning */
|
|
if (do_debug) {
|
|
section_sym = put_elf_sym(symtab_section, 0, 0,
|
|
ELF32_ST_INFO(STB_LOCAL, STT_SECTION), 0,
|
|
text_section->sh_num, NULL);
|
|
getcwd(buf, sizeof(buf));
|
|
pstrcat(buf, sizeof(buf), "/");
|
|
put_stabs_r(buf, N_SO, 0, 0,
|
|
text_section->data_offset, text_section, section_sym);
|
|
put_stabs_r(file->filename, N_SO, 0, 0,
|
|
text_section->data_offset, text_section, section_sym);
|
|
}
|
|
/* an elf symbol of type STT_FILE must be put so that STB_LOCAL
|
|
symbols can be safely used */
|
|
put_elf_sym(symtab_section, 0, 0,
|
|
ELF32_ST_INFO(STB_LOCAL, STT_FILE), 0,
|
|
SHN_ABS, file->filename);
|
|
|
|
/* define common 'char *' type because it is often used internally
|
|
for arrays and struct dereference */
|
|
char_pointer_type = mk_pointer(VT_BYTE);
|
|
/* define an old type function 'int func()' */
|
|
p = anon_sym++;
|
|
sym_push1(&global_stack, p, 0, FUNC_OLD);
|
|
func_old_type = VT_FUNC | (p << VT_STRUCT_SHIFT);
|
|
|
|
define_start = define_stack.top;
|
|
inp();
|
|
ch = '\n'; /* needed to parse correctly first preprocessor command */
|
|
next();
|
|
decl(VT_CONST);
|
|
if (tok != -1)
|
|
expect("declaration");
|
|
|
|
/* end of translation unit info */
|
|
if (do_debug) {
|
|
put_stabs_r(NULL, N_SO, 0, 0,
|
|
text_section->data_offset, text_section, section_sym);
|
|
}
|
|
|
|
/* reset define stack, but leave -Dsymbols (may be incorrect if
|
|
they are undefined) */
|
|
sym_pop(&define_stack, define_start);
|
|
|
|
sym_pop(&global_stack, NULL);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int tcc_compile_string(TCCState *s, const char *str)
|
|
{
|
|
BufferedFile bf1, *bf = &bf1;
|
|
int ret;
|
|
|
|
/* init file structure */
|
|
bf->fd = -1;
|
|
bf->buf_ptr = (char *)str;
|
|
bf->buf_end = (char *)str + strlen(bf->buffer);
|
|
pstrcpy(bf->filename, sizeof(bf->filename), "<string>");
|
|
bf->line_num = 1;
|
|
file = bf;
|
|
|
|
ret = tcc_compile(s);
|
|
|
|
/* currently, no need to close */
|
|
return ret;
|
|
}
|
|
|
|
/* define a symbol. A value can also be provided with the '=' operator */
|
|
void tcc_define_symbol(TCCState *s, const char *sym, const char *value)
|
|
{
|
|
BufferedFile bf1, *bf = &bf1;
|
|
|
|
pstrcpy(bf->buffer, IO_BUF_SIZE, sym);
|
|
pstrcat(bf->buffer, IO_BUF_SIZE, " ");
|
|
/* default value */
|
|
if (!value)
|
|
value = "1";
|
|
pstrcat(bf->buffer, IO_BUF_SIZE, value);
|
|
|
|
/* init file structure */
|
|
bf->fd = -1;
|
|
bf->buf_ptr = bf->buffer;
|
|
bf->buf_end = bf->buffer + strlen(bf->buffer);
|
|
bf->filename[0] = '\0';
|
|
bf->line_num = 1;
|
|
file = bf;
|
|
|
|
include_stack_ptr = include_stack;
|
|
|
|
/* parse with define parser */
|
|
inp();
|
|
ch = '\n'; /* needed to parse correctly first preprocessor command */
|
|
next_nomacro();
|
|
parse_define();
|
|
file = NULL;
|
|
}
|
|
|
|
void tcc_undefine_symbol(TCCState *s1, const char *sym)
|
|
{
|
|
TokenSym *ts;
|
|
Sym *s;
|
|
ts = tok_alloc(sym, 0);
|
|
s = sym_find1(&define_stack, tok);
|
|
/* undefine symbol by putting an invalid name */
|
|
if (s)
|
|
sym_undef(&define_stack, s);
|
|
}
|
|
|
|
static int put_elf_str(Section *s, const char *sym)
|
|
{
|
|
int offset, len;
|
|
char *ptr;
|
|
|
|
len = strlen(sym) + 1;
|
|
offset = s->data_offset;
|
|
ptr = section_ptr_add(s, len);
|
|
memcpy(ptr, sym, len);
|
|
return offset;
|
|
}
|
|
|
|
/* elf symbol hashing function */
|
|
static unsigned long elf_hash(const unsigned char *name)
|
|
{
|
|
unsigned long h = 0, g;
|
|
|
|
while (*name) {
|
|
h = (h << 4) + *name++;
|
|
g = h & 0xf0000000;
|
|
if (g)
|
|
h ^= g >> 24;
|
|
h &= ~g;
|
|
}
|
|
return h;
|
|
}
|
|
|
|
/* return the symbol number */
|
|
static int put_elf_sym(Section *s,
|
|
unsigned long value, unsigned long size,
|
|
int info, int other, int shndx, const char *name)
|
|
{
|
|
int name_offset, sym_index;
|
|
int nbuckets, h;
|
|
Elf32_Sym *sym;
|
|
Section *hs;
|
|
|
|
sym = section_ptr_add(s, sizeof(Elf32_Sym));
|
|
if (name)
|
|
name_offset = put_elf_str(s->link, name);
|
|
else
|
|
name_offset = 0;
|
|
/* XXX: endianness */
|
|
sym->st_name = name_offset;
|
|
sym->st_value = value;
|
|
sym->st_size = size;
|
|
sym->st_info = info;
|
|
sym->st_other = other;
|
|
sym->st_shndx = shndx;
|
|
sym_index = sym - (Elf32_Sym *)s->data;
|
|
hs = s->hash;
|
|
if (hs) {
|
|
int *ptr;
|
|
ptr = section_ptr_add(hs, sizeof(int));
|
|
/* only add global or weak symbols */
|
|
if (ELF32_ST_BIND(info) != STB_LOCAL) {
|
|
/* add another hashing entry */
|
|
nbuckets = ((int *)hs->data)[0];
|
|
h = elf_hash(name) % nbuckets;
|
|
*ptr = ((int *)hs->data)[2 + h];
|
|
((int *)hs->data)[2 + h] = sym_index;
|
|
} else {
|
|
*ptr = 0;
|
|
}
|
|
/* but still add room for all symbols */
|
|
((int *)hs->data)[1]++;
|
|
}
|
|
return sym_index;
|
|
}
|
|
|
|
/* find global ELF symbol 'name' and return its index. Return 0 if not
|
|
found. */
|
|
static int find_elf_sym(Section *s, const char *name)
|
|
{
|
|
Elf32_Sym *sym;
|
|
Section *hs;
|
|
int nbuckets, sym_index, h;
|
|
const char *name1;
|
|
|
|
hs = s->hash;
|
|
if (!hs)
|
|
return 0;
|
|
nbuckets = ((int *)hs->data)[0];
|
|
h = elf_hash(name) % nbuckets;
|
|
sym_index = ((int *)hs->data)[2 + h];
|
|
while (sym_index != 0) {
|
|
sym = &((Elf32_Sym *)s->data)[sym_index];
|
|
name1 = s->link->data + sym->st_name;
|
|
if (!strcmp(name, name1))
|
|
return sym_index;
|
|
sym_index = ((int *)hs->data)[2 + nbuckets + sym_index];
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* return elf symbol value or error */
|
|
static unsigned long get_elf_sym_val(const char *name)
|
|
{
|
|
int sym_index;
|
|
Elf32_Sym *sym;
|
|
|
|
sym_index = find_elf_sym(symtab_section, name);
|
|
if (!sym_index)
|
|
error("%s not defined", name);
|
|
sym = &((Elf32_Sym *)symtab_section->data)[sym_index];
|
|
return sym->st_value;
|
|
}
|
|
|
|
/* add an elf symbol : check if it is already defined and patch
|
|
it. Return symbol index. NOTE that sh_num can be SHN_UNDEF. */
|
|
static int add_elf_sym(Section *s, unsigned long value, unsigned long size,
|
|
int info, int sh_num, const char *name)
|
|
{
|
|
Elf32_Sym *esym;
|
|
int sym_bind, sym_index, sym_type, esym_bind;
|
|
|
|
sym_bind = ELF32_ST_BIND(info);
|
|
sym_type = ELF32_ST_TYPE(info);
|
|
|
|
if (sym_bind != STB_LOCAL) {
|
|
/* we search global or weak symbols */
|
|
sym_index = find_elf_sym(s, name);
|
|
if (!sym_index)
|
|
goto do_def;
|
|
esym = &((Elf32_Sym *)s->data)[sym_index];
|
|
if (esym->st_shndx != SHN_UNDEF) {
|
|
esym_bind = ELF32_ST_BIND(esym->st_info);
|
|
if (sh_num == SHN_UNDEF) {
|
|
/* ignore adding of undefined symbol if the
|
|
corresponding symbol is already defined */
|
|
} else if (sym_bind == STB_GLOBAL && esym_bind == STB_WEAK) {
|
|
/* global overrides weak, so patch */
|
|
goto do_patch;
|
|
} else if (sym_bind == STB_WEAK && esym_bind == STB_GLOBAL) {
|
|
/* weak is ignored if already global */
|
|
} else {
|
|
#if 0
|
|
printf("new_bind=%d new_shndx=%d last_bind=%d old_shndx=%d\n",
|
|
sym_bind, sh_num, esym_bind, esym->st_shndx);
|
|
#endif
|
|
/* NOTE: we accept that two DLL define the same symbol */
|
|
if (s != dynsymtab_section)
|
|
error("'%s' defined twice", name);
|
|
}
|
|
} else {
|
|
do_patch:
|
|
esym->st_info = ELF32_ST_INFO(sym_bind, sym_type);
|
|
esym->st_shndx = sh_num;
|
|
esym->st_value = value;
|
|
esym->st_size = size;
|
|
}
|
|
} else {
|
|
do_def:
|
|
sym_index = put_elf_sym(s, value, size,
|
|
ELF32_ST_INFO(sym_bind, sym_type), 0,
|
|
sh_num, name);
|
|
}
|
|
return sym_index;
|
|
}
|
|
|
|
/* update sym->c so that it points to an external symbol in section
|
|
'section' with value 'value' */
|
|
void put_extern_sym(Sym *sym, Section *section, unsigned long value)
|
|
{
|
|
int sym_type, sym_bind, sh_num, info;
|
|
Elf32_Sym *esym;
|
|
const char *name;
|
|
char buf[32];
|
|
|
|
if (section)
|
|
sh_num = section->sh_num;
|
|
else
|
|
sh_num = SHN_UNDEF;
|
|
if (!sym->c) {
|
|
if ((sym->t & VT_BTYPE) == VT_FUNC)
|
|
sym_type = STT_FUNC;
|
|
else
|
|
sym_type = STT_OBJECT;
|
|
if (sym->t & VT_STATIC)
|
|
sym_bind = STB_LOCAL;
|
|
else
|
|
sym_bind = STB_GLOBAL;
|
|
|
|
name = get_tok_str(sym->v, NULL);
|
|
#ifdef CONFIG_TCC_BCHECK
|
|
if (do_bounds_check) {
|
|
/* if bound checking is activated, we change some function
|
|
names by adding the "__bound" prefix */
|
|
switch(sym->v) {
|
|
case TOK_malloc:
|
|
case TOK_free:
|
|
case TOK_realloc:
|
|
case TOK_memalign:
|
|
case TOK_calloc:
|
|
case TOK_memcpy:
|
|
case TOK_memmove:
|
|
case TOK_memset:
|
|
case TOK_strlen:
|
|
case TOK_strcpy:
|
|
strcpy(buf, "__bound_");
|
|
strcat(buf, name);
|
|
name = buf;
|
|
break;
|
|
}
|
|
}
|
|
#endif
|
|
info = ELF32_ST_INFO(sym_bind, sym_type);
|
|
sym->c = add_elf_sym(symtab_section, value, 0, info, sh_num, name);
|
|
} else {
|
|
esym = &((Elf32_Sym *)symtab_section->data)[sym->c];
|
|
esym->st_value = value;
|
|
esym->st_shndx = sh_num;
|
|
}
|
|
}
|
|
|
|
/* put relocation */
|
|
static void put_elf_reloc(Section *symtab, Section *s, unsigned long offset,
|
|
int type, int symbol)
|
|
{
|
|
char buf[256];
|
|
Section *sr;
|
|
Elf32_Rel *rel;
|
|
|
|
sr = s->reloc;
|
|
if (!sr) {
|
|
/* if no relocation section, create it */
|
|
snprintf(buf, sizeof(buf), ".rel%s", s->name);
|
|
/* if the symtab is allocated, then we consider the relocation
|
|
are also */
|
|
sr = new_section(buf, SHT_REL, symtab->sh_flags);
|
|
sr->sh_entsize = sizeof(Elf32_Rel);
|
|
sr->link = symtab;
|
|
sr->sh_info = s->sh_num;
|
|
s->reloc = sr;
|
|
}
|
|
rel = section_ptr_add(sr, sizeof(Elf32_Rel));
|
|
rel->r_offset = offset;
|
|
rel->r_info = ELF32_R_INFO(symbol, type);
|
|
}
|
|
|
|
/* put stab debug information */
|
|
|
|
typedef struct {
|
|
unsigned long n_strx; /* index into string table of name */
|
|
unsigned char n_type; /* type of symbol */
|
|
unsigned char n_other; /* misc info (usually empty) */
|
|
unsigned short n_desc; /* description field */
|
|
unsigned long n_value; /* value of symbol */
|
|
} Stab_Sym;
|
|
|
|
static void put_stabs(const char *str, int type, int other, int desc,
|
|
unsigned long value)
|
|
{
|
|
Stab_Sym *sym;
|
|
|
|
sym = section_ptr_add(stab_section, sizeof(Stab_Sym));
|
|
if (str) {
|
|
sym->n_strx = put_elf_str(stabstr_section, str);
|
|
} else {
|
|
sym->n_strx = 0;
|
|
}
|
|
sym->n_type = type;
|
|
sym->n_other = other;
|
|
sym->n_desc = desc;
|
|
sym->n_value = value;
|
|
}
|
|
|
|
static void put_stabs_r(const char *str, int type, int other, int desc,
|
|
unsigned long value, Section *sec, int sym_index)
|
|
{
|
|
put_stabs(str, type, other, desc, value);
|
|
put_elf_reloc(symtab_section, stab_section,
|
|
stab_section->data_offset - sizeof(unsigned long),
|
|
R_DATA_32, sym_index);
|
|
}
|
|
|
|
static void put_stabn(int type, int other, int desc, int value)
|
|
{
|
|
put_stabs(NULL, type, other, desc, value);
|
|
}
|
|
|
|
static void put_stabd(int type, int other, int desc)
|
|
{
|
|
put_stabs(NULL, type, other, desc, 0);
|
|
}
|
|
|
|
/* In an ELF file symbol table, the local symbols must appear below
|
|
the global and weak ones. Since TCC cannot sort it while generating
|
|
the code, we must do it after. All the relocation tables are also
|
|
modified to take into account the symbol table sorting */
|
|
static void sort_syms(Section *s)
|
|
{
|
|
int *old_to_new_syms;
|
|
Elf32_Sym *new_syms;
|
|
int nb_syms, i;
|
|
Elf32_Sym *p, *q;
|
|
Elf32_Rel *rel, *rel_end;
|
|
Section *sr;
|
|
int type, sym_index;
|
|
|
|
nb_syms = s->data_offset / sizeof(Elf32_Sym);
|
|
new_syms = malloc(nb_syms * sizeof(Elf32_Sym));
|
|
if (!new_syms)
|
|
error("memory full");
|
|
old_to_new_syms = malloc(nb_syms * sizeof(int));
|
|
if (!old_to_new_syms)
|
|
error("memory full");
|
|
/* first pass for local symbols */
|
|
p = (Elf32_Sym *)s->data;
|
|
q = new_syms;
|
|
for(i = 0; i < nb_syms; i++) {
|
|
if (ELF32_ST_BIND(p->st_info) == STB_LOCAL) {
|
|
old_to_new_syms[i] = q - new_syms;
|
|
*q++ = *p;
|
|
}
|
|
p++;
|
|
}
|
|
/* save the number of local symbols in section header */
|
|
s->sh_info = q - new_syms;
|
|
|
|
/* then second pass for non local symbols */
|
|
p = (Elf32_Sym *)s->data;
|
|
for(i = 0; i < nb_syms; i++) {
|
|
if (ELF32_ST_BIND(p->st_info) != STB_LOCAL) {
|
|
old_to_new_syms[i] = q - new_syms;
|
|
*q++ = *p;
|
|
}
|
|
p++;
|
|
}
|
|
|
|
/* we copy the new symbols to the old */
|
|
memcpy(s->data, new_syms, nb_syms * sizeof(Elf32_Sym));
|
|
free(new_syms);
|
|
|
|
/* now we modify all the relocations */
|
|
for(i = 1; i < nb_sections; i++) {
|
|
sr = sections[i];
|
|
if (sr->sh_type == SHT_REL && sr->link == s) {
|
|
rel_end = (Elf32_Rel *)(sr->data + sr->data_offset);
|
|
for(rel = (Elf32_Rel *)sr->data;
|
|
rel < rel_end;
|
|
rel++) {
|
|
sym_index = ELF32_R_SYM(rel->r_info);
|
|
type = ELF32_R_TYPE(rel->r_info);
|
|
sym_index = old_to_new_syms[sym_index];
|
|
rel->r_info = ELF32_R_INFO(sym_index, type);
|
|
}
|
|
}
|
|
}
|
|
|
|
free(old_to_new_syms);
|
|
}
|
|
|
|
/* relocate common symbols in the .bss section */
|
|
static void relocate_common_syms(void)
|
|
{
|
|
Elf32_Sym *sym, *sym_end;
|
|
unsigned long offset, align;
|
|
|
|
sym_end = (Elf32_Sym *)(symtab_section->data + symtab_section->data_offset);
|
|
for(sym = (Elf32_Sym *)symtab_section->data + 1;
|
|
sym < sym_end;
|
|
sym++) {
|
|
if (sym->st_shndx == SHN_COMMON) {
|
|
/* align symbol */
|
|
align = sym->st_value;
|
|
offset = bss_section->data_offset;
|
|
offset = (offset + align - 1) & -align;
|
|
sym->st_value = offset;
|
|
sym->st_shndx = bss_section->sh_num;
|
|
offset += sym->st_size;
|
|
bss_section->data_offset = offset;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void *resolve_sym(const char *sym)
|
|
{
|
|
return dlsym(NULL, sym);
|
|
}
|
|
|
|
/* relocate symbol table, resolve undefined symbols if do_resolve is
|
|
true and output error if undefined symbol. */
|
|
static void relocate_syms(int do_resolve)
|
|
{
|
|
Elf32_Sym *sym, *esym, *sym_end;
|
|
int sym_bind, sh_num, sym_index;
|
|
const char *name;
|
|
unsigned long addr;
|
|
|
|
sym_end = (Elf32_Sym *)(symtab_section->data + symtab_section->data_offset);
|
|
for(sym = (Elf32_Sym *)symtab_section->data + 1;
|
|
sym < sym_end;
|
|
sym++) {
|
|
sh_num = sym->st_shndx;
|
|
if (sh_num == SHN_UNDEF) {
|
|
name = strtab_section->data + sym->st_name;
|
|
if (do_resolve) {
|
|
name = symtab_section->link->data + sym->st_name;
|
|
addr = (unsigned long)resolve_sym(name);
|
|
if (addr) {
|
|
sym->st_value = addr;
|
|
goto found;
|
|
}
|
|
} else if (dynsym) {
|
|
/* if dynamic symbol exist, then use it */
|
|
sym_index = find_elf_sym(dynsym, name);
|
|
if (sym_index) {
|
|
esym = &((Elf32_Sym *)dynsym->data)[sym_index];
|
|
sym->st_value = esym->st_value;
|
|
goto found;
|
|
}
|
|
}
|
|
/* XXX: _fp_hw seems to be part of the ABI, so we ignore
|
|
it */
|
|
if (!strcmp(name, "_fp_hw"))
|
|
goto found;
|
|
/* only weak symbols are accepted to be undefined. Their
|
|
value is zero */
|
|
sym_bind = ELF32_ST_BIND(sym->st_info);
|
|
if (sym_bind == STB_WEAK) {
|
|
sym->st_value = 0;
|
|
} else {
|
|
error("undefined symbol '%s'", name);
|
|
}
|
|
} else if (sh_num < SHN_LORESERVE) {
|
|
/* add section base */
|
|
sym->st_value += sections[sym->st_shndx]->sh_addr;
|
|
}
|
|
found: ;
|
|
}
|
|
}
|
|
|
|
/* elf relocation, CPU dependant */
|
|
static void elf_reloc(unsigned char *ptr,
|
|
unsigned long addr, unsigned long val, int type,
|
|
int sym_index)
|
|
{
|
|
switch(type) {
|
|
case R_386_32:
|
|
*(int *)ptr += val;
|
|
break;
|
|
case R_386_PLT32:
|
|
case R_386_PC32:
|
|
*(int *)ptr += val - addr;
|
|
break;
|
|
case R_386_GLOB_DAT:
|
|
case R_386_JMP_SLOT:
|
|
*(int *)ptr = val;
|
|
break;
|
|
case R_386_GOTPC:
|
|
*(int *)ptr += got->sh_addr - addr;
|
|
break;
|
|
case R_386_GOTOFF:
|
|
*(int *)ptr += val - got->sh_addr;
|
|
break;
|
|
case R_386_GOT32:
|
|
/* we load the got offset */
|
|
*(int *)ptr += got_offsets[sym_index];
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
/* relocate a given section */
|
|
static void relocate_section(Section *s)
|
|
{
|
|
Section *sr;
|
|
Elf32_Rel *rel, *rel_end;
|
|
Elf32_Sym *sym;
|
|
int type, sym_index;
|
|
unsigned char *ptr;
|
|
unsigned long val;
|
|
|
|
sr = s->reloc;
|
|
rel_end = (Elf32_Rel *)(sr->data + sr->data_offset);
|
|
for(rel = (Elf32_Rel *)sr->data;
|
|
rel < rel_end;
|
|
rel++) {
|
|
ptr = s->data + rel->r_offset;
|
|
|
|
sym_index = ELF32_R_SYM(rel->r_info);
|
|
sym = &((Elf32_Sym *)symtab_section->data)[sym_index];
|
|
val = sym->st_value;
|
|
type = ELF32_R_TYPE(rel->r_info);
|
|
elf_reloc(ptr, s->sh_addr + rel->r_offset, val, type, sym_index);
|
|
}
|
|
}
|
|
|
|
/* relocate relocation table in 'sr' */
|
|
static void relocate_rel(Section *sr)
|
|
{
|
|
Section *s;
|
|
Elf32_Rel *rel, *rel_end;
|
|
|
|
s = sections[sr->sh_info];
|
|
rel_end = (Elf32_Rel *)(sr->data + sr->data_offset);
|
|
for(rel = (Elf32_Rel *)sr->data;
|
|
rel < rel_end;
|
|
rel++) {
|
|
rel->r_offset += s->sh_addr;
|
|
}
|
|
}
|
|
|
|
static void put_got_offset(int index, unsigned long val)
|
|
{
|
|
int n;
|
|
unsigned long *tab;
|
|
|
|
if (index >= nb_got_offsets) {
|
|
/* find immediately bigger power of 2 and reallocate array */
|
|
n = 1;
|
|
while (index >= n)
|
|
n *= 2;
|
|
tab = realloc(got_offsets, n * sizeof(unsigned long));
|
|
if (!tab)
|
|
error("memory full");
|
|
got_offsets = tab;
|
|
memset(got_offsets + nb_got_offsets, 0,
|
|
(n - nb_got_offsets) * sizeof(unsigned long));
|
|
nb_got_offsets = n;
|
|
}
|
|
got_offsets[index] = val;
|
|
}
|
|
|
|
/* XXX: suppress that */
|
|
static void put32(unsigned char *p, unsigned int val)
|
|
{
|
|
p[0] = val;
|
|
p[1] = val >> 8;
|
|
p[2] = val >> 16;
|
|
p[3] = val >> 24;
|
|
}
|
|
|
|
static void build_got(void)
|
|
{
|
|
unsigned char *ptr;
|
|
|
|
/* if no got, then create it */
|
|
got = new_section(".got", SHT_PROGBITS, SHF_ALLOC | SHF_WRITE);
|
|
got->sh_entsize = 4;
|
|
add_elf_sym(symtab_section, 0, 4, ELF32_ST_INFO(STB_GLOBAL, STT_OBJECT),
|
|
got->sh_num, "_GLOBAL_OFFSET_TABLE_");
|
|
ptr = section_ptr_add(got, 3 * sizeof(int));
|
|
/* keep space for _DYNAMIC pointer, if present */
|
|
put32(ptr, 0);
|
|
/* two dummy got entries */
|
|
put32(ptr + 4, 0);
|
|
put32(ptr + 8, 0);
|
|
}
|
|
|
|
/* put a got entry corresponding to a symbol in symtab_section. 'size'
|
|
and 'info' can be modifed if more precise info comes from the DLL */
|
|
static void put_got_entry(int reloc_type, unsigned long size, int info,
|
|
int sym_index)
|
|
{
|
|
int index;
|
|
const char *name;
|
|
Elf32_Sym *sym;
|
|
unsigned long offset;
|
|
int *ptr;
|
|
|
|
if (!got)
|
|
build_got();
|
|
|
|
/* if a got entry already exists for that symbol, no need to add one */
|
|
if (sym_index < nb_got_offsets &&
|
|
got_offsets[sym_index] != 0)
|
|
return;
|
|
|
|
put_got_offset(sym_index, got->data_offset);
|
|
|
|
if (dynsym) {
|
|
sym = &((Elf32_Sym *)symtab_section->data)[sym_index];
|
|
name = symtab_section->link->data + sym->st_name;
|
|
offset = sym->st_value;
|
|
/* NOTE: we put temporarily the got offset */
|
|
if (reloc_type == R_386_JMP_SLOT) {
|
|
nb_plt_entries++;
|
|
offset = got->data_offset;
|
|
}
|
|
index = put_elf_sym(dynsym, offset,
|
|
size, info, 0, sym->st_shndx, name);
|
|
/* put a got entry */
|
|
put_elf_reloc(dynsym, got,
|
|
got->data_offset,
|
|
reloc_type, index);
|
|
}
|
|
ptr = section_ptr_add(got, sizeof(int));
|
|
*ptr = 0;
|
|
}
|
|
|
|
/* build GOT and PLT entries */
|
|
static void build_got_entries(void)
|
|
{
|
|
Section *s, *symtab;
|
|
Elf32_Rel *rel, *rel_end;
|
|
Elf32_Sym *sym;
|
|
int i, type, reloc_type, sym_index;
|
|
|
|
for(i = 1; i < nb_sections; i++) {
|
|
s = sections[i];
|
|
if (s->sh_type != SHT_REL)
|
|
continue;
|
|
/* no need to handle got relocations */
|
|
if (s->link != symtab_section)
|
|
continue;
|
|
symtab = s->link;
|
|
rel_end = (Elf32_Rel *)(s->data + s->data_offset);
|
|
for(rel = (Elf32_Rel *)s->data;
|
|
rel < rel_end;
|
|
rel++) {
|
|
type = ELF32_R_TYPE(rel->r_info);
|
|
switch(type) {
|
|
case R_386_GOT32:
|
|
case R_386_GOTOFF:
|
|
case R_386_GOTPC:
|
|
case R_386_PLT32:
|
|
if (!got)
|
|
build_got();
|
|
if (type == R_386_GOT32 || type == R_386_PLT32) {
|
|
sym_index = ELF32_R_SYM(rel->r_info);
|
|
sym = &((Elf32_Sym *)symtab_section->data)[sym_index];
|
|
/* look at the symbol got offset. If none, then add one */
|
|
if (type == R_386_GOT32)
|
|
reloc_type = R_386_GLOB_DAT;
|
|
else
|
|
reloc_type = R_386_JMP_SLOT;
|
|
put_got_entry(reloc_type, sym->st_size, sym->st_info,
|
|
sym_index);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static Section *new_section_hash(const char *name, int sh_flags,
|
|
int nb_buckets, Section *symtab)
|
|
{
|
|
Section *hash;
|
|
hash = new_section(name, SHT_HASH, sh_flags);
|
|
((int *)hash->data)[0] = nb_buckets;
|
|
((int *)hash->data)[1] = 1;
|
|
hash->sh_entsize = sizeof(int);
|
|
section_ptr_add(hash, (2 + nb_buckets + 1) * sizeof(int));
|
|
symtab->hash = hash;
|
|
hash->link = symtab;
|
|
return hash;
|
|
}
|
|
|
|
/* put dynamic tag */
|
|
static void put_dt(Section *dynamic, int dt, unsigned long val)
|
|
{
|
|
Elf32_Dyn *dyn;
|
|
dyn = section_ptr_add(dynamic, sizeof(Elf32_Dyn));
|
|
dyn->d_tag = dt;
|
|
dyn->d_un.d_val = val;
|
|
}
|
|
|
|
/* add tcc runtime libraries */
|
|
static void tcc_add_runtime(TCCState *s1)
|
|
{
|
|
char buf[1024];
|
|
unsigned long *ptr;
|
|
|
|
snprintf(buf, sizeof(buf), "%s/%s", tcc_lib_path, "libtcc1.o");
|
|
tcc_add_file(s1, buf);
|
|
#ifdef CONFIG_TCC_BCHECK
|
|
if (do_bounds_check) {
|
|
/* XXX: add an object file to do that */
|
|
ptr = section_ptr_add(bounds_section, sizeof(unsigned long));
|
|
*ptr = 0;
|
|
add_elf_sym(symtab_section, 0, 0,
|
|
ELF32_ST_INFO(STB_GLOBAL, STT_NOTYPE),
|
|
bounds_section->sh_num, "__bounds_start");
|
|
add_elf_sym(symtab_section, (long)&rt_error, 0,
|
|
ELF32_ST_INFO(STB_GLOBAL, STT_FUNC),
|
|
SHN_ABS, "rt_error");
|
|
/* add bound check code */
|
|
snprintf(buf, sizeof(buf), "%s/%s", tcc_lib_path, "bcheck.o");
|
|
tcc_add_file(s1, buf);
|
|
}
|
|
#endif
|
|
/* add libc if not memory output */
|
|
if (s1->output_type != TCC_OUTPUT_MEMORY) {
|
|
tcc_add_library(s1, "c");
|
|
tcc_add_file(s1, CONFIG_TCC_CRT_PREFIX "/crtn.o");
|
|
}
|
|
/* add various standard linker symbols */
|
|
add_elf_sym(symtab_section,
|
|
text_section->data_offset, 0,
|
|
ELF32_ST_INFO(STB_GLOBAL, STT_NOTYPE),
|
|
text_section->sh_num, "_etext");
|
|
add_elf_sym(symtab_section,
|
|
data_section->data_offset, 0,
|
|
ELF32_ST_INFO(STB_GLOBAL, STT_NOTYPE),
|
|
data_section->sh_num, "_edata");
|
|
add_elf_sym(symtab_section,
|
|
bss_section->data_offset, 0,
|
|
ELF32_ST_INFO(STB_GLOBAL, STT_NOTYPE),
|
|
bss_section->sh_num, "_end");
|
|
}
|
|
|
|
/* add dynamic sections so that the executable is dynamically linked */
|
|
static char elf_interp[] = "/lib/ld-linux.so.2";
|
|
|
|
#define ELF_START_ADDR 0x08048000
|
|
#define ELF_PAGE_SIZE 0x1000
|
|
|
|
/* output an ELF file */
|
|
/* XXX: handle realloc'ed sections (instead of mmaping them) */
|
|
/* XXX: suppress unneeded sections */
|
|
int tcc_output_file(TCCState *s1, const char *filename)
|
|
{
|
|
Elf32_Ehdr ehdr;
|
|
FILE *f;
|
|
int fd, mode;
|
|
int *section_order;
|
|
int shnum, i, phnum, file_offset, offset, size, j, tmp, sh_order_index, k;
|
|
unsigned long addr;
|
|
Section *strsec, *s;
|
|
Elf32_Shdr shdr, *sh;
|
|
Elf32_Phdr *phdr, *ph;
|
|
Section *interp, *plt, *dynamic, *dynstr, *hash;
|
|
unsigned long saved_dynamic_data_offset;
|
|
Elf32_Sym *sym;
|
|
int type, file_type;
|
|
unsigned long rel_addr, rel_size;
|
|
|
|
file_type = s1->output_type;
|
|
|
|
if (file_type != TCC_OUTPUT_OBJ)
|
|
tcc_add_runtime(s1);
|
|
|
|
interp = NULL;
|
|
dynamic = NULL;
|
|
dynsym = NULL;
|
|
got = NULL;
|
|
nb_plt_entries = 0;
|
|
plt = NULL; /* avoid warning */
|
|
hash = NULL; /* avoid warning */
|
|
dynstr = NULL; /* avoid warning */
|
|
saved_dynamic_data_offset = 0; /* avoid warning */
|
|
|
|
if (file_type != TCC_OUTPUT_OBJ) {
|
|
|
|
relocate_common_syms();
|
|
|
|
if (!static_link) {
|
|
const char *name;
|
|
int sym_index, index;
|
|
Elf32_Sym *esym, *sym_end;
|
|
|
|
if (file_type == TCC_OUTPUT_EXE) {
|
|
char *ptr;
|
|
/* add interpreter section only if executable */
|
|
interp = new_section(".interp", SHT_PROGBITS, SHF_ALLOC);
|
|
interp->sh_addralign = 1;
|
|
ptr = section_ptr_add(interp, sizeof(elf_interp));
|
|
strcpy(ptr, elf_interp);
|
|
}
|
|
|
|
/* add dynamic symbol table */
|
|
dynsym = new_section(".dynsym", SHT_DYNSYM, SHF_ALLOC);
|
|
dynsym->sh_entsize = sizeof(Elf32_Sym);
|
|
dynstr = new_section(".dynstr", SHT_STRTAB, SHF_ALLOC);
|
|
put_elf_str(dynstr, "");
|
|
dynsym->link = dynstr;
|
|
put_elf_sym(dynsym, 0, 0, 0, 0, 0, NULL);
|
|
|
|
/* hash table */
|
|
hash = new_section_hash(".hash", SHF_ALLOC,
|
|
ELF_DYNSYM_HASH_SIZE, dynsym);
|
|
|
|
/* add dynamic section */
|
|
dynamic = new_section(".dynamic", SHT_DYNAMIC,
|
|
SHF_ALLOC | SHF_WRITE);
|
|
dynamic->link = dynstr;
|
|
dynamic->sh_entsize = sizeof(Elf32_Dyn);
|
|
|
|
/* add PLT */
|
|
plt = new_section(".plt", SHT_PROGBITS, SHF_ALLOC | SHF_EXECINSTR);
|
|
plt->sh_entsize = 4;
|
|
|
|
/* scan for undefined symbols and see if they are in the
|
|
dynamic symbols. If a symbol STT_FUNC is found, then we
|
|
add it in the PLT. If a symbol STT_OBJECT is found, we
|
|
add it in the .bss section with a suitable relocation */
|
|
sym_end = (Elf32_Sym *)(symtab_section->data +
|
|
symtab_section->data_offset);
|
|
for(sym = (Elf32_Sym *)symtab_section->data + 1;
|
|
sym < sym_end;
|
|
sym++) {
|
|
if (sym->st_shndx == SHN_UNDEF) {
|
|
name = symtab_section->link->data + sym->st_name;
|
|
sym_index = find_elf_sym(dynsymtab_section, name);
|
|
if (sym_index) {
|
|
esym = &((Elf32_Sym *)dynsymtab_section->data)[sym_index];
|
|
type = ELF32_ST_TYPE(esym->st_info);
|
|
if (type == STT_FUNC) {
|
|
put_got_entry(R_386_JMP_SLOT, esym->st_size,
|
|
esym->st_info,
|
|
sym - (Elf32_Sym *)symtab_section->data);
|
|
} else if (type == STT_OBJECT) {
|
|
unsigned long offset;
|
|
offset = bss_section->data_offset;
|
|
/* XXX: which alignment ? */
|
|
offset = (offset + 8 - 1) & -8;
|
|
index = put_elf_sym(dynsym, offset, esym->st_size,
|
|
esym->st_info, 0,
|
|
bss_section->sh_num, name);
|
|
put_elf_reloc(dynsym, bss_section,
|
|
offset, R_386_COPY, index);
|
|
offset += esym->st_size;
|
|
bss_section->data_offset = offset;
|
|
}
|
|
} else {
|
|
/* STT_NOTYPE or STB_WEAK undefined symbols
|
|
are accepted */
|
|
/* XXX: STT_NOTYPE is only used to exclude the
|
|
unreferenced '_fp_hw' symbol. need a better
|
|
solution */
|
|
if (ELF32_ST_TYPE(sym->st_info) == STT_NOTYPE ||
|
|
ELF32_ST_BIND(sym->st_info) == STB_WEAK) {
|
|
} else {
|
|
error("undefined symbol '%s'", name);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* now look at unresolved dynamic symbols and export
|
|
corresponding symbol */
|
|
sym_end = (Elf32_Sym *)(dynsymtab_section->data +
|
|
dynsymtab_section->data_offset);
|
|
for(sym = (Elf32_Sym *)dynsymtab_section->data + 1;
|
|
sym < sym_end;
|
|
sym++) {
|
|
if (sym->st_shndx == SHN_UNDEF) {
|
|
name = dynsymtab_section->link->data + sym->st_name;
|
|
sym_index = find_elf_sym(symtab_section, name);
|
|
if (sym_index) {
|
|
} else {
|
|
if (ELF32_ST_BIND(sym->st_info) == STB_WEAK) {
|
|
/* weak symbols can stay undefined */
|
|
} else {
|
|
warning("undefined dynamic symbol '%s'", name);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
build_got_entries();
|
|
|
|
/* update PLT/GOT sizes so that we can allocate their space */
|
|
plt->data_offset += 16 * (nb_plt_entries + 1);
|
|
|
|
/* add a list of needed dlls */
|
|
for(i = 0; i < nb_loaded_dlls; i++) {
|
|
DLLReference *dllref = loaded_dlls[i];
|
|
if (dllref->level == 0)
|
|
put_dt(dynamic, DT_NEEDED, put_elf_str(dynstr, dllref->name));
|
|
}
|
|
|
|
/* add necessary space for other entries */
|
|
saved_dynamic_data_offset = dynamic->data_offset;
|
|
dynamic->data_offset += 8 * 9;
|
|
} else {
|
|
/* still need to build got entries in case of static link */
|
|
build_got_entries();
|
|
}
|
|
}
|
|
|
|
memset(&ehdr, 0, sizeof(ehdr));
|
|
|
|
/* we add a section for symbols */
|
|
strsec = new_section(".shstrtab", SHT_STRTAB, 0);
|
|
put_elf_str(strsec, "");
|
|
|
|
/* compute number of sections */
|
|
shnum = nb_sections;
|
|
|
|
/* this array is used to reorder sections in the output file */
|
|
section_order = malloc(sizeof(int) * shnum);
|
|
if (!section_order)
|
|
error("memory full");
|
|
section_order[0] = 0;
|
|
sh_order_index = 1;
|
|
|
|
/* compute number of program headers */
|
|
switch(file_type) {
|
|
default:
|
|
case TCC_OUTPUT_OBJ:
|
|
phnum = 0;
|
|
break;
|
|
case TCC_OUTPUT_EXE:
|
|
if (!static_link)
|
|
phnum = 4;
|
|
else
|
|
phnum = 2;
|
|
break;
|
|
case TCC_OUTPUT_DLL:
|
|
phnum = 3;
|
|
break;
|
|
}
|
|
|
|
/* allocate strings for section names */
|
|
for(i = 1; i < nb_sections; i++) {
|
|
s = sections[i];
|
|
s->sh_name = put_elf_str(strsec, s->name);
|
|
s->sh_size = s->data_offset;
|
|
}
|
|
|
|
/* allocate program segment headers */
|
|
phdr = malloc(phnum * sizeof(Elf32_Phdr));
|
|
if (!phdr)
|
|
error("memory full");
|
|
memset(phdr, 0, phnum * sizeof(Elf32_Phdr));
|
|
|
|
file_offset = sizeof(Elf32_Ehdr) + phnum * sizeof(Elf32_Phdr);
|
|
if (phnum > 0) {
|
|
/* compute section to program header mapping */
|
|
if (file_type == TCC_OUTPUT_DLL)
|
|
addr = 0;
|
|
else
|
|
addr = ELF_START_ADDR;
|
|
|
|
/* dynamic relocation table information, for .dynamic section */
|
|
rel_size = 0;
|
|
rel_addr = 0;
|
|
|
|
/* compute address after headers */
|
|
addr += (file_offset & (ELF_PAGE_SIZE - 1));
|
|
|
|
/* leave one program header for the program interpreter */
|
|
ph = &phdr[0];
|
|
if (interp)
|
|
ph++;
|
|
|
|
for(j = 0; j < 2; j++) {
|
|
ph->p_type = PT_LOAD;
|
|
if (j == 0)
|
|
ph->p_flags = PF_R | PF_X;
|
|
else
|
|
ph->p_flags = PF_R | PF_W;
|
|
ph->p_align = ELF_PAGE_SIZE;
|
|
|
|
/* we do the following ordering: interp, symbol tables,
|
|
relocations, progbits, nobits */
|
|
/* XXX: do faster and simpler sorting */
|
|
for(k = 0; k < 5; k++) {
|
|
for(i = 1; i < nb_sections; i++) {
|
|
s = sections[i];
|
|
/* compute if section should be included */
|
|
if (j == 0) {
|
|
if ((s->sh_flags & (SHF_ALLOC | SHF_WRITE)) !=
|
|
SHF_ALLOC)
|
|
continue;
|
|
} else {
|
|
if ((s->sh_flags & (SHF_ALLOC | SHF_WRITE)) !=
|
|
(SHF_ALLOC | SHF_WRITE))
|
|
continue;
|
|
}
|
|
if (s == interp) {
|
|
if (k != 0)
|
|
continue;
|
|
} else if (s->sh_type == SHT_DYNSYM ||
|
|
s->sh_type == SHT_STRTAB ||
|
|
s->sh_type == SHT_HASH) {
|
|
if (k != 1)
|
|
continue;
|
|
} else if (s->sh_type == SHT_REL) {
|
|
if (k != 2)
|
|
continue;
|
|
} else if (s->sh_type == SHT_NOBITS) {
|
|
if (k != 4)
|
|
continue;
|
|
} else {
|
|
if (k != 3)
|
|
continue;
|
|
}
|
|
section_order[sh_order_index++] = i;
|
|
|
|
/* section matches: we align it and add its size */
|
|
tmp = file_offset;
|
|
file_offset = (file_offset + s->sh_addralign - 1) &
|
|
~(s->sh_addralign - 1);
|
|
s->sh_offset = file_offset;
|
|
addr += file_offset - tmp;
|
|
s->sh_addr = addr;
|
|
|
|
/* update program header infos */
|
|
if (ph->p_offset == 0) {
|
|
ph->p_offset = file_offset;
|
|
ph->p_vaddr = addr;
|
|
ph->p_paddr = ph->p_vaddr;
|
|
}
|
|
/* update dynamic relocation infos */
|
|
if (s->sh_type == SHT_REL) {
|
|
if (rel_size == 0)
|
|
rel_addr = addr;
|
|
rel_size += s->sh_size;
|
|
}
|
|
addr += s->sh_size;
|
|
if (s->sh_type != SHT_NOBITS)
|
|
file_offset += s->sh_size;
|
|
}
|
|
}
|
|
ph->p_filesz = file_offset - ph->p_offset;
|
|
ph->p_memsz = addr - ph->p_vaddr;
|
|
ph++;
|
|
}
|
|
|
|
/* if interpreter, then add corresponing program header */
|
|
if (interp) {
|
|
ph = &phdr[0];
|
|
|
|
ph->p_type = PT_INTERP;
|
|
ph->p_offset = interp->sh_offset;
|
|
ph->p_vaddr = interp->sh_addr;
|
|
ph->p_paddr = ph->p_vaddr;
|
|
ph->p_filesz = interp->sh_size;
|
|
ph->p_memsz = interp->sh_size;
|
|
ph->p_flags = PF_R;
|
|
ph->p_align = interp->sh_addralign;
|
|
}
|
|
|
|
/* if dynamic section, then add corresponing program header */
|
|
if (dynamic) {
|
|
int plt_offset;
|
|
unsigned char *p;
|
|
Elf32_Sym *sym_end;
|
|
|
|
ph = &phdr[phnum - 1];
|
|
|
|
ph->p_type = PT_DYNAMIC;
|
|
ph->p_offset = dynamic->sh_offset;
|
|
ph->p_vaddr = dynamic->sh_addr;
|
|
ph->p_paddr = ph->p_vaddr;
|
|
ph->p_filesz = dynamic->sh_size;
|
|
ph->p_memsz = dynamic->sh_size;
|
|
ph->p_flags = PF_R | PF_W;
|
|
ph->p_align = dynamic->sh_addralign;
|
|
|
|
/* put GOT dynamic section address */
|
|
put32(got->data, dynamic->sh_addr);
|
|
|
|
/* compute the PLT */
|
|
plt->data_offset = 0;
|
|
|
|
/* first plt entry */
|
|
p = section_ptr_add(plt, 16);
|
|
p[0] = 0xff; /* pushl got + 4 */
|
|
p[1] = 0x35;
|
|
put32(p + 2, got->sh_addr + 4);
|
|
p[6] = 0xff; /* jmp *(got + 8) */
|
|
p[7] = 0x25;
|
|
put32(p + 8, got->sh_addr + 8);
|
|
|
|
/* relocation symbols in .dynsym and build PLT. */
|
|
plt_offset = 0;
|
|
sym_end = (Elf32_Sym *)(dynsym->data + dynsym->data_offset);
|
|
for(sym = (Elf32_Sym *)dynsym->data + 1;
|
|
sym < sym_end;
|
|
sym++) {
|
|
type = ELF32_ST_TYPE(sym->st_info);
|
|
if (sym->st_shndx == SHN_UNDEF) {
|
|
if (type == STT_FUNC) {
|
|
/* one more entry in PLT */
|
|
p = section_ptr_add(plt, 16);
|
|
p[0] = 0xff; /* jmp *(got + x) */
|
|
p[1] = 0x25;
|
|
put32(p + 2, got->sh_addr + sym->st_value);
|
|
p[6] = 0x68; /* push $xxx */
|
|
put32(p + 7, plt_offset);
|
|
p[11] = 0xe9; /* jmp plt_start */
|
|
put32(p + 12, -(plt->data_offset));
|
|
|
|
/* patch symbol value to point to plt */
|
|
sym->st_value = plt->sh_addr + p - plt->data;
|
|
|
|
plt_offset += 8;
|
|
}
|
|
} else if (sym->st_shndx < SHN_LORESERVE) {
|
|
/* do symbol relocation */
|
|
sym->st_value += sections[sym->st_shndx]->sh_addr;
|
|
}
|
|
}
|
|
/* put dynamic section entries */
|
|
|
|
dynamic->data_offset = saved_dynamic_data_offset;
|
|
put_dt(dynamic, DT_HASH, hash->sh_addr);
|
|
put_dt(dynamic, DT_STRTAB, dynstr->sh_addr);
|
|
put_dt(dynamic, DT_SYMTAB, dynsym->sh_addr);
|
|
put_dt(dynamic, DT_STRSZ, dynstr->data_offset);
|
|
put_dt(dynamic, DT_SYMENT, sizeof(Elf32_Sym));
|
|
put_dt(dynamic, DT_REL, rel_addr);
|
|
put_dt(dynamic, DT_RELSZ, rel_size);
|
|
put_dt(dynamic, DT_RELENT, sizeof(Elf32_Rel));
|
|
put_dt(dynamic, DT_NULL, 0);
|
|
}
|
|
|
|
ehdr.e_phentsize = sizeof(Elf32_Phdr);
|
|
ehdr.e_phnum = phnum;
|
|
ehdr.e_phoff = sizeof(Elf32_Ehdr);
|
|
}
|
|
|
|
/* all other sections come after */
|
|
for(i = 1; i < nb_sections; i++) {
|
|
s = sections[i];
|
|
if (phnum > 0 && (s->sh_flags & SHF_ALLOC))
|
|
continue;
|
|
section_order[sh_order_index++] = i;
|
|
|
|
file_offset = (file_offset + s->sh_addralign - 1) &
|
|
~(s->sh_addralign - 1);
|
|
s->sh_offset = file_offset;
|
|
if (s->sh_type != SHT_NOBITS)
|
|
file_offset += s->sh_size;
|
|
}
|
|
|
|
/* if building executable or DLL, then relocate each section
|
|
except the GOT which is already relocated */
|
|
if (file_type != TCC_OUTPUT_OBJ) {
|
|
relocate_syms(0);
|
|
|
|
/* relocate sections */
|
|
/* XXX: ignore sections with allocated relocations ? */
|
|
for(i = 1; i < nb_sections; i++) {
|
|
s = sections[i];
|
|
if (s->reloc && s != got)
|
|
relocate_section(s);
|
|
}
|
|
|
|
/* relocate relocation entries if the relocation tables are
|
|
allocated in the executable */
|
|
for(i = 1; i < nb_sections; i++) {
|
|
s = sections[i];
|
|
if ((s->sh_flags & SHF_ALLOC) &&
|
|
s->sh_type == SHT_REL) {
|
|
relocate_rel(s);
|
|
}
|
|
}
|
|
|
|
/* get entry point address */
|
|
ehdr.e_entry = get_elf_sym_val("_start");
|
|
}
|
|
|
|
sort_syms(symtab_section);
|
|
|
|
/* align to 4 */
|
|
file_offset = (file_offset + 3) & -4;
|
|
|
|
/* fill header */
|
|
ehdr.e_ident[0] = ELFMAG0;
|
|
ehdr.e_ident[1] = ELFMAG1;
|
|
ehdr.e_ident[2] = ELFMAG2;
|
|
ehdr.e_ident[3] = ELFMAG3;
|
|
ehdr.e_ident[4] = ELFCLASS32;
|
|
ehdr.e_ident[5] = ELFDATA2LSB;
|
|
ehdr.e_ident[6] = EV_CURRENT;
|
|
switch(file_type) {
|
|
default:
|
|
case TCC_OUTPUT_EXE:
|
|
ehdr.e_type = ET_EXEC;
|
|
break;
|
|
case TCC_OUTPUT_DLL:
|
|
ehdr.e_type = ET_DYN;
|
|
break;
|
|
case TCC_OUTPUT_OBJ:
|
|
ehdr.e_type = ET_REL;
|
|
break;
|
|
}
|
|
ehdr.e_machine = EM_386;
|
|
ehdr.e_version = EV_CURRENT;
|
|
ehdr.e_shoff = file_offset;
|
|
ehdr.e_ehsize = sizeof(Elf32_Ehdr);
|
|
ehdr.e_shentsize = sizeof(Elf32_Shdr);
|
|
ehdr.e_shnum = shnum;
|
|
ehdr.e_shstrndx = shnum - 1;
|
|
|
|
/* write elf file */
|
|
if (file_type == TCC_OUTPUT_OBJ)
|
|
mode = 0666;
|
|
else
|
|
mode = 0777;
|
|
fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, mode);
|
|
if (fd < 0)
|
|
error("could not write '%s'", filename);
|
|
|
|
f = fdopen(fd, "w");
|
|
fwrite(&ehdr, 1, sizeof(Elf32_Ehdr), f);
|
|
fwrite(phdr, 1, phnum * sizeof(Elf32_Phdr), f);
|
|
offset = sizeof(Elf32_Ehdr) + phnum * sizeof(Elf32_Phdr);
|
|
for(i=1;i<nb_sections;i++) {
|
|
s = sections[section_order[i]];
|
|
if (s->sh_type != SHT_NOBITS) {
|
|
while (offset < s->sh_offset) {
|
|
fputc(0, f);
|
|
offset++;
|
|
}
|
|
size = s->data_offset;
|
|
fwrite(s->data, 1, size, f);
|
|
offset += size;
|
|
}
|
|
}
|
|
while (offset < ehdr.e_shoff) {
|
|
fputc(0, f);
|
|
offset++;
|
|
}
|
|
|
|
/* output section headers */
|
|
for(i=0;i<nb_sections;i++) {
|
|
sh = &shdr;
|
|
memset(sh, 0, sizeof(Elf32_Shdr));
|
|
s = sections[i];
|
|
if (s) {
|
|
sh->sh_name = s->sh_name;
|
|
sh->sh_type = s->sh_type;
|
|
sh->sh_flags = s->sh_flags;
|
|
sh->sh_entsize = s->sh_entsize;
|
|
sh->sh_info = s->sh_info;
|
|
if (s->link)
|
|
sh->sh_link = s->link->sh_num;
|
|
sh->sh_addralign = s->sh_addralign;
|
|
sh->sh_addr = s->sh_addr;
|
|
sh->sh_offset = s->sh_offset;
|
|
sh->sh_size = s->sh_size;
|
|
}
|
|
fwrite(sh, 1, sizeof(Elf32_Shdr), f);
|
|
}
|
|
fclose(f);
|
|
|
|
free(section_order);
|
|
free(phdr);
|
|
return 0;
|
|
}
|
|
|
|
static void *load_data(int fd, unsigned long file_offset, unsigned long size)
|
|
{
|
|
void *data;
|
|
|
|
data = malloc(size);
|
|
if (!data)
|
|
error("memory full");
|
|
lseek(fd, file_offset, SEEK_SET);
|
|
read(fd, data, size);
|
|
return data;
|
|
}
|
|
|
|
typedef struct SectionMergeInfo {
|
|
Section *s; /* corresponding existing section */
|
|
unsigned long offset; /* offset of the new section in the existing section */
|
|
int new_section; /* true if section 's' was added */
|
|
} SectionMergeInfo;
|
|
|
|
/* load an object file and merge it with current files */
|
|
/* XXX: handle correctly stab (debug) info */
|
|
static int tcc_load_object_file(TCCState *s1,
|
|
int fd, unsigned long file_offset)
|
|
{
|
|
Elf32_Ehdr ehdr;
|
|
Elf32_Shdr *shdr, *sh;
|
|
int size, i, j, offset, offseti, nb_syms, sym_index;
|
|
unsigned char *strsec, *strtab;
|
|
int *old_to_new_syms;
|
|
char *sh_name, *name;
|
|
SectionMergeInfo *sm_table, *sm;
|
|
Elf32_Sym *sym, *symtab;
|
|
Elf32_Rel *rel, *rel_end;
|
|
Section *s;
|
|
|
|
if (read(fd, &ehdr, sizeof(ehdr)) != sizeof(ehdr))
|
|
goto fail;
|
|
if (ehdr.e_ident[0] != ELFMAG0 ||
|
|
ehdr.e_ident[1] != ELFMAG1 ||
|
|
ehdr.e_ident[2] != ELFMAG2 ||
|
|
ehdr.e_ident[3] != ELFMAG3)
|
|
goto fail;
|
|
/* test if object file */
|
|
if (ehdr.e_type != ET_REL)
|
|
goto fail;
|
|
/* test CPU specific stuff */
|
|
if (ehdr.e_ident[5] != ELFDATA2LSB ||
|
|
ehdr.e_machine != EM_386) {
|
|
fail:
|
|
error("invalid object file");
|
|
}
|
|
/* read sections */
|
|
shdr = load_data(fd, file_offset + ehdr.e_shoff,
|
|
sizeof(Elf32_Shdr) * ehdr.e_shnum);
|
|
sm_table = malloc(sizeof(SectionMergeInfo) * ehdr.e_shnum);
|
|
if (!sm_table)
|
|
error("memory full");
|
|
memset(sm_table, 0, sizeof(SectionMergeInfo) * ehdr.e_shnum);
|
|
|
|
/* load section names */
|
|
sh = &shdr[ehdr.e_shstrndx];
|
|
strsec = load_data(fd, file_offset + sh->sh_offset, sh->sh_size);
|
|
|
|
/* load symtab and strtab */
|
|
symtab = NULL;
|
|
strtab = NULL;
|
|
nb_syms = 0;
|
|
for(i = 1; i < ehdr.e_shnum; i++) {
|
|
sh = &shdr[i];
|
|
if (sh->sh_type == SHT_SYMTAB) {
|
|
if (symtab)
|
|
error("object must contain only one symtab");
|
|
nb_syms = sh->sh_size / sizeof(Elf32_Sym);
|
|
symtab = load_data(fd, file_offset + sh->sh_offset, sh->sh_size);
|
|
sm_table[i].s = symtab_section;
|
|
|
|
/* now load strtab */
|
|
sh = &shdr[sh->sh_link];
|
|
strtab = load_data(fd, file_offset + sh->sh_offset, sh->sh_size);
|
|
}
|
|
}
|
|
|
|
/* now examine each section and try to merge its content with the
|
|
ones in memory */
|
|
for(i = 1; i < ehdr.e_shnum; i++) {
|
|
/* no need to examine section name strtab */
|
|
if (i == ehdr.e_shstrndx)
|
|
continue;
|
|
sh = &shdr[i];
|
|
sh_name = strsec + sh->sh_name;
|
|
/* ignore sections types we do not handle */
|
|
if (sh->sh_type != SHT_PROGBITS &&
|
|
sh->sh_type != SHT_REL &&
|
|
sh->sh_type != SHT_NOBITS)
|
|
continue;
|
|
if (sh->sh_addralign < 1)
|
|
sh->sh_addralign = 1;
|
|
/* find corresponding section, if any */
|
|
for(j = 1; j < nb_sections;j++) {
|
|
s = sections[j];
|
|
if (!strcmp(s->name, sh_name))
|
|
goto found;
|
|
}
|
|
/* not found: create new section */
|
|
s = new_section(sh_name, sh->sh_type, sh->sh_flags);
|
|
/* take as much info as possible from the section. sh_link and
|
|
sh_info will be updated later */
|
|
s->sh_addralign = sh->sh_addralign;
|
|
s->sh_entsize = sh->sh_entsize;
|
|
sm_table[i].new_section = 1;
|
|
found:
|
|
if (sh->sh_type != s->sh_type)
|
|
goto fail;
|
|
|
|
/* align start of section */
|
|
offset = s->data_offset;
|
|
size = sh->sh_addralign - 1;
|
|
offset = (offset + size) & ~size;
|
|
if (sh->sh_addralign > s->sh_addralign)
|
|
s->sh_addralign = sh->sh_addralign;
|
|
s->data_offset = offset;
|
|
sm_table[i].offset = offset;
|
|
sm_table[i].s = s;
|
|
/* concatenate sections */
|
|
size = sh->sh_size;
|
|
if (sh->sh_type != SHT_NOBITS) {
|
|
unsigned char *ptr;
|
|
lseek(fd, file_offset + sh->sh_offset, SEEK_SET);
|
|
ptr = section_ptr(s, size);
|
|
read(fd, ptr, size);
|
|
}
|
|
s->data_offset += size;
|
|
}
|
|
|
|
/* second short pass to update sh_link and sh_info fields of new
|
|
sections */
|
|
sm = sm_table;
|
|
for(i = 1; i < ehdr.e_shnum; i++) {
|
|
s = sm_table[i].s;
|
|
if (!s || !sm_table[i].new_section)
|
|
continue;
|
|
sh = &shdr[i];
|
|
if (sh->sh_link > 0)
|
|
s->link = sm_table[sh->sh_link].s;
|
|
if (sh->sh_type == SHT_REL) {
|
|
s->sh_info = sm_table[sh->sh_info].s->sh_num;
|
|
/* update backward link */
|
|
sections[s->sh_info]->reloc = s;
|
|
}
|
|
}
|
|
|
|
/* resolve symbols */
|
|
old_to_new_syms = malloc(nb_syms * sizeof(int));
|
|
if (!old_to_new_syms)
|
|
error("memory full");
|
|
memset(old_to_new_syms, 0, nb_syms * sizeof(int));
|
|
sym = symtab + 1;
|
|
for(i = 1; i < nb_syms; i++, sym++) {
|
|
if (sym->st_shndx != SHN_UNDEF &&
|
|
sym->st_shndx < SHN_LORESERVE) {
|
|
sm = &sm_table[sym->st_shndx];
|
|
/* if no corresponding section added, no need to add symbol */
|
|
if (!sm->s)
|
|
continue;
|
|
/* convert section number */
|
|
sym->st_shndx = sm->s->sh_num;
|
|
/* offset value */
|
|
sym->st_value += sm->offset;
|
|
}
|
|
/* add symbol */
|
|
name = strtab + sym->st_name;
|
|
sym_index = add_elf_sym(symtab_section, sym->st_value, sym->st_size,
|
|
sym->st_info, sym->st_shndx, name);
|
|
old_to_new_syms[i] = sym_index;
|
|
}
|
|
|
|
/* third pass to patch relocation entries */
|
|
for(i = 1; i < ehdr.e_shnum; i++) {
|
|
s = sm_table[i].s;
|
|
if (!s)
|
|
continue;
|
|
sh = &shdr[i];
|
|
offset = sm_table[i].offset;
|
|
switch(s->sh_type) {
|
|
case SHT_REL:
|
|
/* take relocation offset information */
|
|
offseti = sm_table[sh->sh_info].offset;
|
|
rel_end = (Elf32_Rel *)(s->data + s->data_offset);
|
|
for(rel = (Elf32_Rel *)(s->data + offset);
|
|
rel < rel_end;
|
|
rel++) {
|
|
int type;
|
|
unsigned sym_index;
|
|
/* convert symbol index */
|
|
type = ELF32_R_TYPE(rel->r_info);
|
|
sym_index = ELF32_R_SYM(rel->r_info);
|
|
/* NOTE: only one symtab assumed */
|
|
if (sym_index >= nb_syms)
|
|
goto invalid_reloc;
|
|
sym_index = old_to_new_syms[sym_index];
|
|
if (!sym_index) {
|
|
invalid_reloc:
|
|
error("Invalid relocation entry");
|
|
}
|
|
rel->r_info = ELF32_R_INFO(sym_index, type);
|
|
/* offset the relocation offset */
|
|
rel->r_offset += offseti;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
free(symtab);
|
|
free(strtab);
|
|
free(old_to_new_syms);
|
|
free(sm_table);
|
|
free(shdr);
|
|
return 0;
|
|
}
|
|
|
|
#define ARMAG "!<arch>\012" /* For COFF and a.out archives */
|
|
|
|
typedef struct ArchiveHeader {
|
|
char ar_name[16]; /* name of this member */
|
|
char ar_date[12]; /* file mtime */
|
|
char ar_uid[6]; /* owner uid; printed as decimal */
|
|
char ar_gid[6]; /* owner gid; printed as decimal */
|
|
char ar_mode[8]; /* file mode, printed as octal */
|
|
char ar_size[10]; /* file size, printed as decimal */
|
|
char ar_fmag[2]; /* should contain ARFMAG */
|
|
} ArchiveHeader;
|
|
|
|
/* load a '.a' file */
|
|
static int tcc_load_archive(TCCState *s1, int fd)
|
|
{
|
|
ArchiveHeader hdr;
|
|
char ar_size[11];
|
|
char ar_name[17];
|
|
char magic[8];
|
|
int size, len, i;
|
|
unsigned long file_offset;
|
|
|
|
/* skip magic which was already checked */
|
|
read(fd, magic, sizeof(magic));
|
|
|
|
for(;;) {
|
|
len = read(fd, &hdr, sizeof(hdr));
|
|
if (len == 0)
|
|
break;
|
|
if (len != sizeof(hdr))
|
|
error("invalid archive");
|
|
memcpy(ar_size, hdr.ar_size, sizeof(hdr.ar_size));
|
|
ar_size[sizeof(hdr.ar_size)] = '\0';
|
|
size = strtol(ar_size, NULL, 0);
|
|
memcpy(ar_name, hdr.ar_name, sizeof(hdr.ar_name));
|
|
for(i = sizeof(hdr.ar_name) - 1; i >= 0; i--) {
|
|
if (ar_name[i] != ' ')
|
|
break;
|
|
}
|
|
ar_name[i + 1] = '\0';
|
|
// printf("name='%s' size=%d %s\n", ar_name, size, ar_size);
|
|
file_offset = lseek(fd, 0, SEEK_CUR);
|
|
if (!strcmp(ar_name, "/") ||
|
|
!strcmp(ar_name, "//") ||
|
|
!strcmp(ar_name, "__.SYMDEF") ||
|
|
!strcmp(ar_name, "__.SYMDEF/") ||
|
|
!strcmp(ar_name, "ARFILENAMES/")) {
|
|
/* skip symbol table or archive names */
|
|
} else {
|
|
tcc_load_object_file(s1, fd, file_offset);
|
|
}
|
|
/* align to even */
|
|
size = (size + 1) & ~1;
|
|
lseek(fd, file_offset + size, SEEK_SET);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* load a DLL and all referenced DLLs. 'level = 0' means that the DLL
|
|
is referenced by the user (so it should be added as DT_NEEDED in
|
|
the generated ELF file) */
|
|
static int tcc_load_dll(TCCState *s1, int fd, const char *filename, int level)
|
|
{
|
|
Elf32_Ehdr ehdr;
|
|
Elf32_Shdr *shdr, *sh, *sh1;
|
|
int i, nb_syms, nb_dts, sym_bind;
|
|
Elf32_Sym *sym, *dynsym;
|
|
Elf32_Dyn *dt, *dynamic;
|
|
unsigned char *dynstr;
|
|
const char *name, *soname, *p;
|
|
DLLReference *dllref;
|
|
|
|
read(fd, &ehdr, sizeof(ehdr));
|
|
|
|
/* test CPU specific stuff */
|
|
if (ehdr.e_ident[5] != ELFDATA2LSB ||
|
|
ehdr.e_machine != EM_386)
|
|
error("bad architecture");
|
|
|
|
/* read sections */
|
|
shdr = load_data(fd, ehdr.e_shoff, sizeof(Elf32_Shdr) * ehdr.e_shnum);
|
|
|
|
/* load dynamic section and dynamic symbols */
|
|
nb_syms = 0;
|
|
nb_dts = 0;
|
|
dynamic = NULL;
|
|
dynsym = NULL; /* avoid warning */
|
|
dynstr = NULL; /* avoid warning */
|
|
for(i = 0, sh = shdr; i < ehdr.e_shnum; i++, sh++) {
|
|
switch(sh->sh_type) {
|
|
case SHT_DYNAMIC:
|
|
nb_dts = sh->sh_size / sizeof(Elf32_Dyn);
|
|
dynamic = load_data(fd, sh->sh_offset, sh->sh_size);
|
|
break;
|
|
case SHT_DYNSYM:
|
|
nb_syms = sh->sh_size / sizeof(Elf32_Sym);
|
|
dynsym = load_data(fd, sh->sh_offset, sh->sh_size);
|
|
sh1 = &shdr[sh->sh_link];
|
|
dynstr = load_data(fd, sh1->sh_offset, sh1->sh_size);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* compute the real library name */
|
|
soname = filename;
|
|
p = strrchr(soname, '/');
|
|
if (p)
|
|
soname = p + 1;
|
|
|
|
for(i = 0, dt = dynamic; i < nb_dts; i++, dt++) {
|
|
if (dt->d_tag == DT_SONAME) {
|
|
soname = dynstr + dt->d_un.d_val;
|
|
}
|
|
}
|
|
|
|
/* if the dll is already loaded, do not load it */
|
|
for(i = 0; i < nb_loaded_dlls; i++) {
|
|
dllref = loaded_dlls[i];
|
|
if (!strcmp(soname, dllref->name)) {
|
|
/* but update level if needed */
|
|
if (level < dllref->level)
|
|
dllref->level = level;
|
|
goto the_end;
|
|
}
|
|
}
|
|
|
|
// printf("loading dll '%s'\n", soname);
|
|
|
|
/* add the dll and its level */
|
|
dllref = malloc(sizeof(DLLReference) + strlen(soname));
|
|
dllref->level = level;
|
|
strcpy(dllref->name, soname);
|
|
dynarray_add((void ***)&loaded_dlls, &nb_loaded_dlls, dllref);
|
|
|
|
/* add dynamic symbols in dynsym_section */
|
|
for(i = 1, sym = dynsym + 1; i < nb_syms; i++, sym++) {
|
|
sym_bind = ELF32_ST_BIND(sym->st_info);
|
|
if (sym_bind == STB_LOCAL)
|
|
continue;
|
|
name = dynstr + sym->st_name;
|
|
add_elf_sym(dynsymtab_section, sym->st_value, sym->st_size,
|
|
sym->st_info, sym->st_shndx, name);
|
|
}
|
|
|
|
/* load all referenced DLLs */
|
|
for(i = 0, dt = dynamic; i < nb_dts; i++, dt++) {
|
|
switch(dt->d_tag) {
|
|
case DT_NEEDED:
|
|
name = dynstr + dt->d_un.d_val;
|
|
for(i = 0; i < nb_loaded_dlls; i++) {
|
|
dllref = loaded_dlls[i];
|
|
if (!strcmp(name, dllref->name))
|
|
goto already_loaded;
|
|
}
|
|
if (tcc_add_dll(s1, name, AFF_REFERENCED_DLL) < 0)
|
|
error("referenced dll '%s' not found", name);
|
|
already_loaded:
|
|
break;
|
|
}
|
|
}
|
|
the_end:
|
|
free(shdr);
|
|
return 0;
|
|
}
|
|
|
|
/* return -2 if error and CH_EOF if eof */
|
|
static void ld_skipspaces(void)
|
|
{
|
|
while (ch == ' ' || ch == '\t' || ch == '\n')
|
|
cinp();
|
|
}
|
|
|
|
static int ld_get_cmd(char *cmd, int cmd_size)
|
|
{
|
|
char *q;
|
|
|
|
ld_skipspaces();
|
|
if (ch == CH_EOF)
|
|
return -1;
|
|
q = cmd;
|
|
for(;;) {
|
|
if (!((ch >= 'a' && ch <= 'z') ||
|
|
(ch >= 'A' && ch <= 'Z') ||
|
|
(ch >= '0' && ch <= '9') ||
|
|
strchr("/.-_+=$:\\,~?*", ch)))
|
|
break;
|
|
if ((q - cmd) >= (cmd_size - 1))
|
|
return -2;
|
|
*q++ = ch;
|
|
cinp();
|
|
}
|
|
*q = '\0';
|
|
return 0;
|
|
}
|
|
|
|
/* interpret a subset of GNU ldscripts to handle the dummy libc.so
|
|
files */
|
|
static int tcc_load_ldscript(TCCState *s1)
|
|
{
|
|
char cmd[64];
|
|
char filename[1024];
|
|
int ret;
|
|
|
|
inp();
|
|
cinp();
|
|
for(;;) {
|
|
ret = ld_get_cmd(cmd, sizeof(cmd));
|
|
if (ret == CH_EOF)
|
|
return 0;
|
|
else if (ret < 0)
|
|
return -1;
|
|
// printf("cmd='%s'\n", cmd);
|
|
if (!strcmp(cmd, "INPUT") ||
|
|
!strcmp(cmd, "GROUP")) {
|
|
ld_skipspaces();
|
|
if (ch != '(')
|
|
expect("(");
|
|
cinp();
|
|
for(;;) {
|
|
ld_get_cmd(filename, sizeof(filename));
|
|
tcc_add_file(s1, filename);
|
|
ld_skipspaces();
|
|
if (ch == ',') {
|
|
cinp();
|
|
} else if (ch == ')') {
|
|
cinp();
|
|
break;
|
|
} else if (ch == CH_EOF) {
|
|
error("unexpected end of file");
|
|
}
|
|
}
|
|
} else {
|
|
return -1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* print the position in the source file of PC value 'pc' by reading
|
|
the stabs debug information */
|
|
static void rt_printline(unsigned long wanted_pc)
|
|
{
|
|
Stab_Sym *sym, *sym_end;
|
|
char func_name[128];
|
|
unsigned long func_addr, last_pc, pc;
|
|
const char *incl_files[INCLUDE_STACK_SIZE];
|
|
int incl_index, len, last_line_num, i;
|
|
const char *str, *p;
|
|
|
|
func_name[0] = '\0';
|
|
func_addr = 0;
|
|
incl_index = 0;
|
|
last_pc = 0xffffffff;
|
|
last_line_num = 1;
|
|
sym = (Stab_Sym *)stab_section->data + 1;
|
|
sym_end = (Stab_Sym *)(stab_section->data + stab_section->data_offset);
|
|
while (sym < sym_end) {
|
|
switch(sym->n_type) {
|
|
/* function start or end */
|
|
case N_FUN:
|
|
if (sym->n_strx == 0) {
|
|
func_name[0] = '\0';
|
|
func_addr = 0;
|
|
} else {
|
|
str = stabstr_section->data + sym->n_strx;
|
|
p = strchr(str, ':');
|
|
if (!p) {
|
|
pstrcpy(func_name, sizeof(func_name), str);
|
|
} else {
|
|
len = p - str;
|
|
if (len > sizeof(func_name) - 1)
|
|
len = sizeof(func_name) - 1;
|
|
memcpy(func_name, str, len);
|
|
func_name[len] = '\0';
|
|
}
|
|
func_addr = sym->n_value;
|
|
}
|
|
break;
|
|
/* line number info */
|
|
case N_SLINE:
|
|
pc = sym->n_value + func_addr;
|
|
if (wanted_pc >= last_pc && wanted_pc < pc)
|
|
goto found;
|
|
last_pc = pc;
|
|
last_line_num = sym->n_desc;
|
|
break;
|
|
/* include files */
|
|
case N_BINCL:
|
|
str = stabstr_section->data + sym->n_strx;
|
|
add_incl:
|
|
if (incl_index < INCLUDE_STACK_SIZE) {
|
|
incl_files[incl_index++] = str;
|
|
}
|
|
break;
|
|
case N_EINCL:
|
|
if (incl_index > 1)
|
|
incl_index--;
|
|
break;
|
|
case N_SO:
|
|
if (sym->n_strx == 0) {
|
|
incl_index = 0; /* end of translation unit */
|
|
} else {
|
|
str = stabstr_section->data + sym->n_strx;
|
|
/* do not add path */
|
|
len = strlen(str);
|
|
if (len > 0 && str[len - 1] != '/')
|
|
goto add_incl;
|
|
}
|
|
break;
|
|
}
|
|
sym++;
|
|
}
|
|
/* did not find line number info: */
|
|
fprintf(stderr, "(no debug info, pc=0x%08lx): ", wanted_pc);
|
|
return;
|
|
found:
|
|
for(i = 0; i < incl_index - 1; i++)
|
|
fprintf(stderr, "In file included from %s\n",
|
|
incl_files[i]);
|
|
if (incl_index > 0) {
|
|
fprintf(stderr, "%s:%d: ",
|
|
incl_files[incl_index - 1], last_line_num);
|
|
}
|
|
if (func_name[0] != '\0') {
|
|
fprintf(stderr, "in function '%s()': ", func_name);
|
|
}
|
|
}
|
|
|
|
/* emit a run time error at position 'pc' */
|
|
void rt_error(unsigned long pc, const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
|
|
rt_printline(pc);
|
|
vfprintf(stderr, fmt, ap);
|
|
fprintf(stderr, "\n");
|
|
exit(255);
|
|
va_end(ap);
|
|
}
|
|
|
|
#ifndef WIN32
|
|
/* signal handler for fatal errors */
|
|
static void sig_error(int signum, siginfo_t *siginf, void *puc)
|
|
{
|
|
struct ucontext *uc = puc;
|
|
unsigned long pc;
|
|
|
|
#ifdef __i386__
|
|
pc = uc->uc_mcontext.gregs[14];
|
|
#else
|
|
#error please put the right sigcontext field
|
|
#endif
|
|
|
|
switch(signum) {
|
|
case SIGFPE:
|
|
switch(siginf->si_code) {
|
|
case FPE_INTDIV:
|
|
case FPE_FLTDIV:
|
|
rt_error(pc, "division by zero");
|
|
break;
|
|
default:
|
|
rt_error(pc, "floating point exception");
|
|
break;
|
|
}
|
|
break;
|
|
case SIGBUS:
|
|
case SIGSEGV:
|
|
rt_error(pc, "dereferencing invalid pointer");
|
|
break;
|
|
case SIGILL:
|
|
rt_error(pc, "illegal instruction");
|
|
break;
|
|
case SIGABRT:
|
|
rt_error(pc, "abort() called");
|
|
break;
|
|
default:
|
|
rt_error(pc, "caught signal %d", signum);
|
|
break;
|
|
}
|
|
exit(255);
|
|
}
|
|
#endif
|
|
|
|
/* launch the compiled program with the given arguments */
|
|
int tcc_run(TCCState *s1, int argc, char **argv)
|
|
{
|
|
Section *s;
|
|
int (*prog_main)(int, char **);
|
|
void (*bound_init)(void);
|
|
int i;
|
|
|
|
tcc_add_runtime(s1);
|
|
|
|
relocate_common_syms();
|
|
|
|
/* compute relocation address : section are relocated in place */
|
|
for(i = 1; i < nb_sections; i++) {
|
|
s = sections[i];
|
|
if (s->sh_flags & SHF_ALLOC)
|
|
s->sh_addr = (unsigned long)s->data;
|
|
}
|
|
|
|
relocate_syms(1);
|
|
|
|
/* relocate each section */
|
|
for(i = 1; i < nb_sections; i++) {
|
|
s = sections[i];
|
|
if (s->reloc)
|
|
relocate_section(s);
|
|
}
|
|
|
|
prog_main = (void *)get_elf_sym_val("main");
|
|
|
|
if (do_debug) {
|
|
#ifdef WIN32
|
|
error("debug mode currently not available for Windows");
|
|
#else
|
|
struct sigaction sigact;
|
|
/* install TCC signal handlers to print debug info on fatal
|
|
runtime errors */
|
|
sigact.sa_flags = SA_SIGINFO | SA_ONESHOT;
|
|
sigact.sa_sigaction = sig_error;
|
|
sigemptyset(&sigact.sa_mask);
|
|
sigaction(SIGFPE, &sigact, NULL);
|
|
sigaction(SIGILL, &sigact, NULL);
|
|
sigaction(SIGSEGV, &sigact, NULL);
|
|
sigaction(SIGBUS, &sigact, NULL);
|
|
sigaction(SIGABRT, &sigact, NULL);
|
|
#endif
|
|
}
|
|
|
|
#ifdef CONFIG_TCC_BCHECK
|
|
if (do_bounds_check) {
|
|
/* XXX: use .init section so that it also work in binary ? */
|
|
bound_init = (void *)get_elf_sym_val("__bound_init");
|
|
bound_init();
|
|
}
|
|
#endif
|
|
return (*prog_main)(argc, argv);
|
|
}
|
|
|
|
TCCState *tcc_new(void)
|
|
{
|
|
char *p, *r;
|
|
TCCState *s;
|
|
|
|
s = malloc(sizeof(TCCState));
|
|
if (!s)
|
|
return NULL;
|
|
s->output_type = TCC_OUTPUT_MEMORY;
|
|
|
|
/* default include paths */
|
|
tcc_add_include_path(s, "/usr/include");
|
|
tcc_add_include_path(s, CONFIG_TCC_PREFIX "/lib/tcc/include");
|
|
tcc_add_include_path(s, "/usr/local/include");
|
|
|
|
/* add all tokens */
|
|
tok_ident = TOK_IDENT;
|
|
p = tcc_keywords;
|
|
while (*p) {
|
|
r = p;
|
|
while (*r++);
|
|
tok_alloc(p, r - p - 1);
|
|
p = r;
|
|
}
|
|
|
|
/* standard defines */
|
|
tcc_define_symbol(s, "__STDC__", NULL);
|
|
#if defined(TCC_TARGET_I386)
|
|
tcc_define_symbol(s, "__i386__", NULL);
|
|
#endif
|
|
/* tiny C specific defines */
|
|
tcc_define_symbol(s, "__TINYC__", NULL);
|
|
|
|
/* default library paths */
|
|
tcc_add_library_path(s, "/usr/local/lib");
|
|
tcc_add_library_path(s, "/usr/lib");
|
|
tcc_add_library_path(s, "/lib");
|
|
|
|
/* no section zero */
|
|
dynarray_add((void ***)§ions, &nb_sections, NULL);
|
|
|
|
/* create standard sections */
|
|
text_section = new_section(".text", SHT_PROGBITS, SHF_ALLOC | SHF_EXECINSTR);
|
|
data_section = new_section(".data", SHT_PROGBITS, SHF_ALLOC | SHF_WRITE);
|
|
bss_section = new_section(".bss", SHT_NOBITS, SHF_ALLOC | SHF_WRITE);
|
|
|
|
/* symbols are always generated for linking stage */
|
|
symtab_section = new_section(".symtab", SHT_SYMTAB, 0);
|
|
symtab_section->sh_entsize = sizeof(Elf32_Sym);
|
|
strtab_section = new_section(".strtab", SHT_STRTAB, 0);
|
|
put_elf_str(strtab_section, "");
|
|
symtab_section->link = strtab_section;
|
|
put_elf_sym(symtab_section, 0, 0, 0, 0, 0, NULL);
|
|
new_section_hash(".hashtab", SHF_PRIVATE,
|
|
ELF_SYM_HASH_SIZE, symtab_section);
|
|
|
|
/* private symbol table for dynamic symbols */
|
|
dynsymtab_section = new_section(".dynsymtab", SHT_SYMTAB, SHF_PRIVATE);
|
|
dynsymtab_section->sh_entsize = sizeof(Elf32_Sym);
|
|
dynstrtab_section = new_section(".dynstrtab", SHT_STRTAB, SHF_PRIVATE);
|
|
put_elf_str(dynstrtab_section, "");
|
|
dynsymtab_section->link = dynstrtab_section;
|
|
put_elf_sym(dynsymtab_section, 0, 0, 0, 0, 0, NULL);
|
|
new_section_hash(".dynhashtab", SHF_PRIVATE,
|
|
ELF_SYM_HASH_SIZE, dynsymtab_section);
|
|
return s;
|
|
}
|
|
|
|
void tcc_delete(TCCState *s)
|
|
{
|
|
free(s);
|
|
}
|
|
|
|
int tcc_add_include_path(TCCState *s, const char *pathname)
|
|
{
|
|
char *pathname1;
|
|
|
|
pathname1 = strdup(pathname);
|
|
if (!pathname1)
|
|
return -1;
|
|
dynarray_add((void ***)&include_paths, &nb_include_paths, pathname1);
|
|
return 0;
|
|
}
|
|
|
|
static int tcc_add_file_internal(TCCState *s, const char *filename, int flags)
|
|
{
|
|
const char *ext;
|
|
Elf32_Ehdr ehdr;
|
|
int fd;
|
|
BufferedFile *saved_file;
|
|
|
|
/* find source file type with extension */
|
|
ext = strrchr(filename, '.');
|
|
if (ext)
|
|
ext++;
|
|
|
|
/* open the file */
|
|
saved_file = file;
|
|
file = tcc_open(filename);
|
|
if (!file) {
|
|
if (flags & AFF_PRINT_ERROR) {
|
|
error("file '%s' not found", filename);
|
|
} else {
|
|
file = saved_file;
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (!ext || !strcmp(ext, "c")) {
|
|
/* C file assumed */
|
|
tcc_compile(s);
|
|
} else {
|
|
fd = file->fd;
|
|
/* assume executable format: auto guess file type */
|
|
if (read(fd, &ehdr, sizeof(ehdr)) != sizeof(ehdr))
|
|
error("could not read header");
|
|
lseek(fd, 0, SEEK_SET);
|
|
|
|
if (ehdr.e_ident[0] == ELFMAG0 &&
|
|
ehdr.e_ident[1] == ELFMAG1 &&
|
|
ehdr.e_ident[2] == ELFMAG2 &&
|
|
ehdr.e_ident[3] == ELFMAG3) {
|
|
file->line_num = 0; /* do not display line number if error */
|
|
if (ehdr.e_type == ET_REL) {
|
|
tcc_load_object_file(s, fd, 0);
|
|
} else if (ehdr.e_type == ET_DYN) {
|
|
tcc_load_dll(s, fd, filename, (flags & AFF_REFERENCED_DLL) != 0);
|
|
} else {
|
|
error("unrecognized ELF file");
|
|
}
|
|
} else if (memcmp((char *)&ehdr, ARMAG, 8) == 0) {
|
|
file->line_num = 0; /* do not display line number if error */
|
|
tcc_load_archive(s, fd);
|
|
} else {
|
|
/* as GNU ld, consider it is an ld script if not recognized */
|
|
if (tcc_load_ldscript(s) < 0)
|
|
error("unrecognized file type");
|
|
}
|
|
}
|
|
tcc_close(file);
|
|
file = saved_file;
|
|
return 0;
|
|
}
|
|
|
|
void tcc_add_file(TCCState *s, const char *filename)
|
|
{
|
|
tcc_add_file_internal(s, filename, AFF_PRINT_ERROR);
|
|
}
|
|
|
|
int tcc_add_library_path(TCCState *s, const char *pathname)
|
|
{
|
|
char *pathname1;
|
|
|
|
pathname1 = strdup(pathname);
|
|
if (!pathname1)
|
|
return -1;
|
|
dynarray_add((void ***)&library_paths, &nb_library_paths, pathname1);
|
|
return 0;
|
|
}
|
|
|
|
/* find and load a dll. Return non zero if not found */
|
|
/* XXX: add '-rpath' option support ? */
|
|
static int tcc_add_dll(TCCState *s, const char *filename, int flags)
|
|
{
|
|
char buf[1024];
|
|
int i;
|
|
|
|
for(i = 0; i < nb_library_paths; i++) {
|
|
snprintf(buf, sizeof(buf), "%s/%s",
|
|
library_paths[i], filename);
|
|
if (tcc_add_file_internal(s, buf, flags) == 0)
|
|
return 0;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/* the library name is the same as the argument of the '-l' option */
|
|
int tcc_add_library(TCCState *s, const char *libraryname)
|
|
{
|
|
char buf[1024];
|
|
int i;
|
|
void *h;
|
|
|
|
/* if we output to memory, then we simply we dlopen(). */
|
|
if (s->output_type == TCC_OUTPUT_MEMORY) {
|
|
/* Since the libc is already loaded, we don't need to load it again */
|
|
if (!strcmp(libraryname, "c"))
|
|
return 0;
|
|
snprintf(buf, sizeof(buf), "lib%s.so", libraryname);
|
|
h = dlopen(buf, RTLD_GLOBAL | RTLD_LAZY);
|
|
if (!h)
|
|
return -1;
|
|
return 0;
|
|
}
|
|
|
|
/* first we look for the dynamic library if not static linking */
|
|
if (!static_link) {
|
|
snprintf(buf, sizeof(buf), "lib%s.so", libraryname);
|
|
if (tcc_add_dll(s, buf, 0) == 0)
|
|
return 0;
|
|
}
|
|
|
|
/* then we look for the static library */
|
|
for(i = 0; i < nb_library_paths; i++) {
|
|
snprintf(buf, sizeof(buf), "%s/lib%s.a",
|
|
library_paths[i], libraryname);
|
|
if (tcc_add_file_internal(s, buf, 0) == 0)
|
|
return 0;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
int tcc_add_symbol(TCCState *s, const char *name, unsigned long val)
|
|
{
|
|
add_elf_sym(symtab_section, val, 0,
|
|
ELF32_ST_INFO(STB_GLOBAL, STT_NOTYPE),
|
|
SHN_ABS, name);
|
|
return 0;
|
|
}
|
|
|
|
int tcc_set_output_type(TCCState *s, int output_type)
|
|
{
|
|
s->output_type = output_type;
|
|
|
|
/* add libc crt1/crti objects */
|
|
if (output_type == TCC_OUTPUT_EXE ||
|
|
output_type == TCC_OUTPUT_DLL) {
|
|
if (output_type != TCC_OUTPUT_DLL)
|
|
tcc_add_file(s, CONFIG_TCC_CRT_PREFIX "/crt1.o");
|
|
tcc_add_file(s, CONFIG_TCC_CRT_PREFIX "/crti.o");
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#if !defined(LIBTCC)
|
|
|
|
void help(void)
|
|
{
|
|
printf("tcc version 0.9.8 - Tiny C Compiler - Copyright (C) 2001, 2002 Fabrice Bellard\n"
|
|
"usage: tcc [-c] [-o outfile] [-Bdir] [-bench] [-Idir] [-Dsym[=val]] [-Usym]\n"
|
|
" [-g] [-b] [-Ldir] [-llib] [-shared] [-static]\n"
|
|
" [--] infile1 [infile2... --] [infile_args...]\n"
|
|
"\n"
|
|
"General options:\n"
|
|
" -c compile only - generate an object file\n"
|
|
" -o outfile set output filename\n"
|
|
" -- allows multiples input files if no -o option given. Also\n"
|
|
" separate input files from runtime arguments\n"
|
|
" -Bdir set tcc internal library path\n"
|
|
" -bench output compilation statistics\n"
|
|
"Preprocessor options:\n"
|
|
" -Idir add include path 'dir'\n"
|
|
" -Dsym[=val] define 'sym' with value 'val'\n"
|
|
" -Usym undefine 'sym'\n"
|
|
"C compiler options:\n"
|
|
" -g generate runtime debug info\n"
|
|
#ifdef CONFIG_TCC_BCHECK
|
|
" -b compile with built-in memory and bounds checker (implies -g)\n"
|
|
#endif
|
|
"Linker options:\n"
|
|
" -Ldir add library path 'dir'\n"
|
|
" -llib link with dynamic library 'lib'\n"
|
|
" -shared generate a shared library (NOT WORKING YET)\n"
|
|
" -static static linking\n"
|
|
);
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
char *r, *outfile;
|
|
int optind, output_type, multiple_files, i;
|
|
TCCState *s;
|
|
char **libraries;
|
|
int nb_libraries;
|
|
|
|
s = tcc_new();
|
|
output_type = TCC_OUTPUT_MEMORY;
|
|
|
|
optind = 1;
|
|
outfile = NULL;
|
|
multiple_files = 0;
|
|
libraries = NULL;
|
|
nb_libraries = 0;
|
|
while (1) {
|
|
if (optind >= argc) {
|
|
show_help:
|
|
help();
|
|
return 1;
|
|
}
|
|
r = argv[optind];
|
|
if (r[0] != '-')
|
|
break;
|
|
optind++;
|
|
if (r[1] == '-') {
|
|
/* '--' enables multiple files input */
|
|
multiple_files = 1;
|
|
} else if (r[1] == 'h' || r[1] == '?') {
|
|
goto show_help;
|
|
} else if (r[1] == 'I') {
|
|
if (tcc_add_include_path(s, r + 2) < 0)
|
|
error("too many include paths");
|
|
} else if (r[1] == 'D') {
|
|
char *sym, *value;
|
|
sym = r + 2;
|
|
value = strchr(sym, '=');
|
|
if (value) {
|
|
*value = '\0';
|
|
value++;
|
|
}
|
|
tcc_define_symbol(s, sym, value);
|
|
} else if (r[1] == 'U') {
|
|
tcc_undefine_symbol(s, r + 2);
|
|
} else if (r[1] == 'L') {
|
|
tcc_add_library_path(s, r + 2);
|
|
} else if (r[1] == 'B') {
|
|
/* set tcc utilities path (mainly for tcc development) */
|
|
tcc_lib_path = r + 2;
|
|
} else if (r[1] == 'l') {
|
|
dynarray_add((void ***)&libraries, &nb_libraries, r + 2);
|
|
} else if (!strcmp(r + 1, "bench")) {
|
|
do_bench = 1;
|
|
#ifdef CONFIG_TCC_BCHECK
|
|
} else if (r[1] == 'b') {
|
|
if (!do_bounds_check) {
|
|
do_bounds_check = 1;
|
|
/* define symbol */
|
|
tcc_define_symbol(s, "__BOUNDS_CHECKING_ON", NULL);
|
|
/* create bounds sections */
|
|
bounds_section = new_section(".bounds",
|
|
SHT_PROGBITS, SHF_ALLOC);
|
|
lbounds_section = new_section(".lbounds",
|
|
SHT_PROGBITS, SHF_ALLOC);
|
|
/* debug is implied */
|
|
goto debug_opt;
|
|
}
|
|
#endif
|
|
} else if (r[1] == 'g') {
|
|
#ifdef CONFIG_TCC_BCHECK
|
|
debug_opt:
|
|
#endif
|
|
if (!do_debug) {
|
|
do_debug = 1;
|
|
|
|
/* stab symbols */
|
|
stab_section = new_section(".stab", SHT_PROGBITS, 0);
|
|
stab_section->sh_entsize = sizeof(Stab_Sym);
|
|
stabstr_section = new_section(".stabstr", SHT_STRTAB, 0);
|
|
put_elf_str(stabstr_section, "");
|
|
stab_section->link = stabstr_section;
|
|
/* put first entry */
|
|
put_stabs("", 0, 0, 0, 0);
|
|
}
|
|
} else
|
|
/* the following options are only for testing, so not
|
|
documented */
|
|
if (r[1] == 'c') {
|
|
multiple_files = 1;
|
|
output_type = TCC_OUTPUT_OBJ;
|
|
} else if (!strcmp(r + 1, "static")) {
|
|
static_link = 1;
|
|
} else if (!strcmp(r + 1, "shared")) {
|
|
output_type = TCC_OUTPUT_DLL;
|
|
} else if (r[1] == 'o') {
|
|
if (optind >= argc)
|
|
goto show_help;
|
|
multiple_files = 1;
|
|
outfile = argv[optind++];
|
|
} else {
|
|
error("invalid option -- '%s'", r);
|
|
}
|
|
}
|
|
|
|
/* if outfile provided without other options, we output an
|
|
executable */
|
|
if (outfile && output_type == TCC_OUTPUT_MEMORY)
|
|
output_type = TCC_OUTPUT_EXE;
|
|
|
|
/* warning if not supported features */
|
|
if (output_type == TCC_OUTPUT_DLL)
|
|
warning("dll output is currently not supported");
|
|
#ifdef CONFIG_TCC_BCHECK
|
|
if (do_bounds_check && output_type != TCC_OUTPUT_MEMORY)
|
|
warning("bounds checking is currently only supported for in-memory execution");
|
|
#endif
|
|
|
|
tcc_set_output_type(s, output_type);
|
|
|
|
tcc_add_file(s, argv[optind]);
|
|
if (multiple_files) {
|
|
while ((optind + 1) < argc) {
|
|
optind++;
|
|
r = argv[optind];
|
|
if (r[0] == '-') {
|
|
if (r[1] != '-')
|
|
error("'--' expected");
|
|
break;
|
|
}
|
|
tcc_add_file(s, r);
|
|
}
|
|
}
|
|
|
|
/* add specified libraries */
|
|
for(i = 0; i < nb_libraries;i++) {
|
|
if (tcc_add_library(s, libraries[i]) < 0)
|
|
error("cannot find -l%s", libraries[i]);
|
|
}
|
|
|
|
if (do_bench) {
|
|
printf("total: %d idents, %d lines, %d bytes\n",
|
|
tok_ident - TOK_IDENT, total_lines, total_bytes);
|
|
}
|
|
|
|
if (s->output_type != TCC_OUTPUT_MEMORY) {
|
|
tcc_output_file(s, outfile);
|
|
return 0;
|
|
} else {
|
|
return tcc_run(s, argc - optind, argv + optind);
|
|
}
|
|
}
|
|
|
|
#endif
|