520 lines
		
	
	
	
		
			13 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			520 lines
		
	
	
	
		
			13 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| #include "mcg.h"
 | |
| 
 | |
| static struct e_instr em;
 | |
| static struct basicblock* code_bb;
 | |
| static struct basicblock* data_bb;
 | |
| 
 | |
| static void queue_insn_label(int opcode, const char* label, arith offset);
 | |
| 
 | |
| static const char* type_to_str(int type)
 | |
| {
 | |
| 	switch (type)
 | |
| 	{
 | |
| 		case EM_MNEM:       return "EM_MNEM";
 | |
| 		case EM_PSEU:	    return "EM_PSEU";
 | |
| 		case EM_STARTMES:   return "EM_STARTMES";
 | |
| 		case EM_MESARG:     return "EM_MESARG";
 | |
| 		case EM_ENDMES:     return "EM_ENDMES";
 | |
| 		case EM_DEFILB:     return "EM_DEFILB";
 | |
| 		case EM_DEFDLB:     return "EM_DEFDLB";
 | |
| 		case EM_DEFDNAM:    return "EM_DEFDNAM";
 | |
| 		case EM_ERROR:      return "EM_ERROR";
 | |
| 		case EM_FATAL:      return "EM_FATAL";
 | |
| 		case EM_EOF:        return "EM_EOF";
 | |
| 	}
 | |
| 
 | |
|     assert(0 && "invalid EM type");
 | |
| }
 | |
| 
 | |
| static const char* argtype_to_str(int type)
 | |
| {
 | |
|     if (type == 0)        return "...";
 | |
|     if (type == ilb_ptyp) return "ilb";
 | |
|     if (type == nof_ptyp) return "nof";
 | |
|     if (type == sof_ptyp) return "sof";
 | |
|     if (type == cst_ptyp) return "cst";
 | |
|     if (type == pro_ptyp) return "pro";
 | |
|     if (type == str_ptyp) return "str";
 | |
|     if (type == ico_ptyp) return "ico";
 | |
|     if (type == uco_ptyp) return "uco";
 | |
|     if (type == fco_ptyp) return "fco";
 | |
|     return "???";
 | |
| }
 | |
| 
 | |
| static void unknown_type(const char* s)
 | |
| {
 | |
|     fatal("%s with unknown type '%s'",
 | |
|         s,
 | |
|         argtype_to_str(em.em_arg.ema_argtype));
 | |
| }
 | |
| 
 | |
| static const char* ilabel_to_str(label l)
 | |
| {
 | |
|     assert(current_proc != NULL);
 | |
|     return aprintf(".%s_I%d", current_proc->name, l);
 | |
| }
 | |
| 
 | |
| static const char* dlabel_to_str(label l)
 | |
| {
 | |
|     return aprintf(".D%d", l);
 | |
| }
 | |
| 
 | |
| static void terminate_block(void)
 | |
| {
 | |
|     code_bb->is_terminated = true;
 | |
|     code_bb = NULL;
 | |
| }
 | |
| 
 | |
| static struct em* new_insn(int opcode)
 | |
| {
 | |
|     struct em* em = calloc(sizeof(struct em), 1);
 | |
|     em->opcode = opcode;
 | |
|     return em;
 | |
| }
 | |
| 
 | |
| static void queue_insn_simple(int opcode)
 | |
| {
 | |
|     struct em* em = new_insn(opcode);
 | |
|     em->paramtype = PARAM_NONE;
 | |
|     array_append(&code_bb->ems, em);
 | |
| 
 | |
|     switch (opcode)
 | |
|     {
 | |
|         case op_bra:
 | |
|             terminate_block();
 | |
|             break;
 | |
|     }
 | |
| }
 | |
| 
 | |
| static void queue_insn_value(int opcode, arith value)
 | |
| {
 | |
|     struct em* em = new_insn(opcode);
 | |
|     em->paramtype = PARAM_IVALUE;
 | |
|     em->u.ivalue = value;
 | |
|     array_append(&code_bb->ems, em);
 | |
| 
 | |
|     switch (opcode)
 | |
|     {
 | |
|         case op_csa:
 | |
|         case op_csb:
 | |
|         case op_ret:
 | |
|             terminate_block();
 | |
|             break;
 | |
|     }
 | |
| }
 | |
| 
 | |
| static void queue_insn_label(int opcode, const char* label, arith offset)
 | |
| {
 | |
|     struct em* em = new_insn(opcode);
 | |
|     em->paramtype = PARAM_LVALUE;
 | |
|     em->u.lvalue.label = label;
 | |
|     em->u.lvalue.offset = offset;
 | |
|     array_append(&code_bb->ems, em);
 | |
| 
 | |
|     switch (opcode)
 | |
|     {
 | |
|         case op_bra:
 | |
|             terminate_block();
 | |
|             break;
 | |
|     }
 | |
| }
 | |
| 
 | |
| static void queue_insn_block(int opcode, struct basicblock* left, struct basicblock* right)
 | |
| {
 | |
|     struct em* em = new_insn(opcode);
 | |
|     em->paramtype = PARAM_BVALUE;
 | |
|     em->u.bvalue.left = left;
 | |
|     em->u.bvalue.right = right;
 | |
|     array_append(&code_bb->ems, em);
 | |
|     
 | |
|     terminate_block();
 | |
| }
 | |
| 
 | |
| static void change_basicblock(struct basicblock* newbb)
 | |
| {
 | |
|     array_appendu(¤t_proc->blocks, newbb);
 | |
| 
 | |
|     if (code_bb && !code_bb->is_terminated)
 | |
|         queue_insn_block(op_bra, newbb, NULL);
 | |
| 
 | |
|     code_bb = newbb;
 | |
| }
 | |
| 
 | |
| static void queue_insn_ilabel(int opcode, int label)
 | |
| {
 | |
|     const char* name = ilabel_to_str(em.em_ilb);
 | |
|     struct basicblock* left = bb_get(name);
 | |
| 
 | |
|     switch (opcode)
 | |
|     {
 | |
|         case op_bra:
 | |
|             queue_insn_block(em.em_opcode, left, NULL);
 | |
|             break;
 | |
| 
 | |
|         case op_zeq:
 | |
|         case op_zne:
 | |
|         case op_zlt:
 | |
|         case op_zle:
 | |
|         case op_zgt:
 | |
|         case op_zge:
 | |
|         case op_beq:
 | |
|         case op_bne:
 | |
|         case op_blt:
 | |
|         case op_ble:
 | |
|         case op_bgt:
 | |
|         case op_bge:
 | |
|         {
 | |
|             struct basicblock* bb = bb_get(NULL);
 | |
|             queue_insn_block(em.em_opcode, left, bb);
 | |
|             change_basicblock(bb);
 | |
|             break;
 | |
|         }
 | |
| 
 | |
|         default:
 | |
|             fatal("parse_em: unhandled conditional '%s'", 
 | |
|                 em_mnem[opcode - sp_fmnem]);
 | |
|     }
 | |
| }
 | |
| 
 | |
| static void queue_ilabel(arith label)
 | |
| {
 | |
|     change_basicblock(bb_get(ilabel_to_str(label)));
 | |
| }
 | |
| 
 | |
| /* This is really hacky; to handle basic block flow
 | |
|  * descriptor blocks, we need to be able to identify
 | |
|  * them, read them and parse them. So we create
 | |
|  * can exit to. So we create fake bb objects for each
 | |
|  * block, purely to track this, and copy ints and labels
 | |
|  * into them.
 | |
|  */
 | |
| 
 | |
| static void data_block_int(arith value)
 | |
| {
 | |
| 	if (data_bb)
 | |
| 	{
 | |
| 		struct em* em = new_insn(op_loc);
 | |
| 		em->paramtype = PARAM_IVALUE;
 | |
| 		em->u.ivalue = value;
 | |
| 		array_append(&data_bb->ems, em);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static void data_block_label(const char* label)
 | |
| {
 | |
| 	if (data_bb)
 | |
| 	{
 | |
| 		struct em* em = new_insn(op_bra);
 | |
| 		em->paramtype = PARAM_BVALUE;
 | |
| 		em->u.bvalue.left = bb_get(label);
 | |
| 		array_append(&data_bb->ems, em);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static arith safe_atol(const char* s)
 | |
| {
 | |
| 	arith result;
 | |
| 
 | |
| 	errno = 0;
 | |
| 	result = strtoul(s, NULL, 0);
 | |
| 	if (errno == ERANGE)
 | |
| 		result = strtol(s, NULL, 0);
 | |
| 	if (errno == ERANGE)
 | |
| 		fatal("constant '%s' not parseable", s);
 | |
| 	return result;
 | |
| }
 | |
| 
 | |
| static void parse_pseu(void)
 | |
| {
 | |
| 	switch (em.em_opcode)
 | |
| 	{
 | |
| 		case ps_exp: /* external proc */
 | |
| 		case ps_exa: /* external array */
 | |
| 		case ps_inp: /* internal proc */
 | |
| 		case ps_ina: /* internal array */
 | |
| 		{
 | |
| 			bool export = (em.em_opcode == ps_exp) || (em.em_opcode == ps_exa);
 | |
| 			bool proc = (em.em_opcode == ps_exp) || (em.em_opcode == ps_inp);
 | |
| 
 | |
| 			switch (em.em_arg.ema_argtype)
 | |
| 			{
 | |
| 				case pro_ptyp:
 | |
| 					symbol_declare(strdup(em.em_pnam), export, proc);
 | |
| 					break;
 | |
| 
 | |
| 				case sof_ptyp:
 | |
|                     assert(em.em_off == 0);
 | |
| 					symbol_declare(strdup(em.em_dnam), export, proc);
 | |
| 					break;
 | |
| 
 | |
|                 case nof_ptyp:
 | |
|                     assert(em.em_off == 0);
 | |
|                     symbol_declare(dlabel_to_str(em.em_dlb), export, proc);
 | |
|                     break;
 | |
| 
 | |
| 				default:
 | |
|                     unknown_type("exp, exa, inp, ina");
 | |
| 			}
 | |
| 			break;
 | |
| 		}
 | |
| 
 | |
| 		case ps_con: /* .data */
 | |
| 		case ps_rom: /* .rom */
 | |
|         {
 | |
|             bool ro = (em.em_opcode == ps_rom);
 | |
| 
 | |
| 			switch (em.em_arg.ema_argtype)
 | |
| 			{
 | |
| 				case ico_ptyp:
 | |
| 				case uco_ptyp:
 | |
|                 {
 | |
|                     arith val = safe_atol(em.em_string);
 | |
|                     data_int(val, em.em_size, ro);
 | |
|                     data_block_int(val);
 | |
|                     break;
 | |
|                 }
 | |
| 
 | |
|                 case fco_ptyp:
 | |
|                 {
 | |
|                     data_float(em.em_string, em.em_size, ro);
 | |
|                     break;
 | |
|                 }
 | |
| 
 | |
| 				case str_ptyp:
 | |
|                     data_block((const uint8_t*) strdup(em.em_string), em.em_size, ro);
 | |
| 					break;
 | |
| 
 | |
|                 case cst_ptyp:
 | |
|                 {
 | |
|                     arith value = em.em_cst;
 | |
|                     data_int(value, EM_wordsize, ro);
 | |
|                     data_block_int(value);
 | |
|                     break;
 | |
| 				}
 | |
|                     
 | |
|                 case nof_ptyp:
 | |
|                     data_offset(dlabel_to_str(em.em_dlb), em.em_off, ro);
 | |
|                     break;
 | |
| 
 | |
|                 case sof_ptyp:
 | |
|                 case pro_ptyp:
 | |
|                     data_offset(strdup(em.em_dnam), em.em_off, ro);
 | |
|                     break;
 | |
| 
 | |
|                 case ilb_ptyp:
 | |
|                 {
 | |
|                     const char* label = ilabel_to_str(em.em_ilb);
 | |
|                     data_offset(label, 0, ro);
 | |
|                     data_block_label(label);
 | |
|                     break;
 | |
|                 }
 | |
| 
 | |
| 				default:
 | |
|                     unknown_type("con, rom");
 | |
| 			}
 | |
| 			break;
 | |
|         }
 | |
| 
 | |
|         case ps_bss:
 | |
|         {
 | |
|             switch (em.em_arg.ema_argtype)
 | |
|             {
 | |
|                 case cst_ptyp:
 | |
|                     data_bss(EM_bsssize, em.em_cst);
 | |
|                     break;
 | |
|                     
 | |
| 				case ico_ptyp:
 | |
| 				case uco_ptyp:
 | |
|                 {
 | |
|                     arith val = safe_atol(em.em_string);
 | |
|                     data_int(val, em.em_size, false);
 | |
|                     break;
 | |
|                 }
 | |
| 
 | |
|                 case sof_ptyp:
 | |
|                     data_offset(strdup(em.em_dnam), em.em_off, false);
 | |
|                     break;
 | |
| 
 | |
|                 default:
 | |
|                     unknown_type("bss");
 | |
|             }
 | |
|             break;
 | |
|         }
 | |
| 
 | |
| 		case ps_pro: /* procedure start */
 | |
|         {
 | |
|             struct symbol* symbol;
 | |
| 
 | |
|             current_proc = calloc(sizeof(struct procedure), 1);
 | |
|             current_proc->name = strdup(em.em_pnam);
 | |
|             current_proc->entry = bb_get(current_proc->name);
 | |
|             current_proc->locals_size = em.em_nlocals;
 | |
|             code_bb = current_proc->entry;
 | |
|             code_bb->is_root = true;
 | |
|             array_append(¤t_proc->blocks, code_bb);
 | |
| 
 | |
|             symbol = symbol_get(current_proc->name);
 | |
|             symbol->section = SECTION_TEXT;
 | |
|             symbol->proc = current_proc;
 | |
|             symbol->is_proc = true;
 | |
|             break;
 | |
|         }
 | |
| 
 | |
| 		case ps_end: /* procedure end */
 | |
|             tb_procedure();
 | |
| 
 | |
|             current_proc = NULL;
 | |
|             code_bb = NULL;
 | |
| 			break;
 | |
| 
 | |
| 		default:
 | |
|             fatal("unknown pseudo with opcode %d\n", em.em_opcode);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static arith mes_get_cst(void)
 | |
| {
 | |
|     EM_getinstr(&em);
 | |
|     if (em.em_type != EM_MESARG)
 | |
|         fatal("malformed MES");
 | |
|     return em.em_cst;
 | |
| }
 | |
| 
 | |
| static void parse_mes(void)
 | |
| {
 | |
|     assert(em.em_arg.ema_argtype == cst_ptyp);
 | |
|     switch (em.em_cst)
 | |
|     {
 | |
|         case 0: /* error */
 | |
|             fatal("MES 0 received (explicit halt)");
 | |
| 
 | |
|         case 3: /* register variable */
 | |
|         {
 | |
|             /* ego will sometimes generate 'mes 3' pseudos with no actual
 | |
|              * parameters. Detect and ignore these. */
 | |
|             
 | |
|             EM_getinstr(&em);
 | |
|             if (em.em_type == EM_MESARG)
 | |
|             {
 | |
|                 arith offset = em.em_cst;
 | |
|                 int size = mes_get_cst();
 | |
|                 int type = mes_get_cst();
 | |
|                 int priority = mes_get_cst();
 | |
|                 tb_regvar(current_proc, offset, size, type, priority);
 | |
|             }
 | |
|             break;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     while ((em.em_type == EM_STARTMES) || (em.em_type == EM_MESARG))
 | |
|         EM_getinstr(&em);
 | |
| 
 | |
|     if (em.em_type != EM_ENDMES)
 | |
|         fatal("malformed MES");
 | |
| }
 | |
| 
 | |
| static void create_data_label(const char* label)
 | |
| {
 | |
|     data_label(label);
 | |
|     if (current_proc)
 | |
|     {
 | |
|         /* Create the fake bb used to track values inside this data block
 | |
|          * (as it's a chance it's a jump table which we'll need to process
 | |
|          * later).
 | |
|          */
 | |
|         data_bb = bb_get(label);
 | |
|         data_bb->is_fake = true;
 | |
|     }
 | |
| }
 | |
| 
 | |
| void parse_em(void)
 | |
| {
 | |
|     EM_getinstr(&em);
 | |
| 	tb_filestart();
 | |
| 
 | |
| 	while (em.em_type != EM_EOF)
 | |
| 	{
 | |
|         switch (em.em_type)
 | |
|         {
 | |
|             case EM_PSEU:
 | |
| 				parse_pseu();
 | |
|                 break;
 | |
| 
 | |
|             case EM_DEFILB:
 | |
|                 queue_ilabel(em.em_ilb);
 | |
|                 break;
 | |
| 
 | |
|             case EM_DEFDLB:
 | |
|                 create_data_label(dlabel_to_str(em.em_dlb));
 | |
|                 break;
 | |
| 
 | |
|             case EM_DEFDNAM:
 | |
|                 create_data_label(strdup(em.em_dnam));
 | |
|                 break;
 | |
| 
 | |
|             case EM_STARTMES:
 | |
|                 parse_mes();
 | |
|                 break;
 | |
| 
 | |
|             case EM_MNEM:
 | |
|                 if (code_bb)
 | |
|                 {
 | |
|                     int flags = em_flag[em.em_opcode - sp_fmnem];
 | |
| 
 | |
|                     if (flags & EM_PAR)
 | |
|                     {
 | |
|                         switch (em.em_argtype)
 | |
|                         {
 | |
|                             case 0:
 | |
|                                 /* This is an instruction which would normally
 | |
|                                  * take a size, but the size is provided on the
 | |
|                                  * stack. We hates them. */
 | |
|                                 queue_insn_simple(em.em_opcode);
 | |
|                                 break;
 | |
|                                 
 | |
|                             case ilb_ptyp:
 | |
|                                 queue_insn_ilabel(em.em_opcode, em.em_ilb);
 | |
|                                 break;
 | |
| 
 | |
|                             case nof_ptyp:
 | |
|                                 queue_insn_label(em.em_opcode,
 | |
|                                     dlabel_to_str(em.em_dlb), em.em_off);
 | |
|                                 break;
 | |
| 
 | |
|                             case sof_ptyp:
 | |
|                                 queue_insn_label(em.em_opcode,
 | |
|                                     strdup(em.em_dnam), em.em_off);
 | |
|                                 break;
 | |
| 
 | |
|                             case pro_ptyp:
 | |
|                                 queue_insn_label(em.em_opcode,
 | |
|                                     strdup(em.em_pnam), 0);
 | |
|                                 break;
 | |
| 
 | |
|                             case cst_ptyp:
 | |
|                                 if ((flags & EM_PAR) == PAR_B)
 | |
|                                     queue_insn_ilabel(em.em_opcode, em.em_ilb);
 | |
|                                 else
 | |
|                                     queue_insn_value(em.em_opcode, em.em_cst);
 | |
|                                 break;
 | |
| 
 | |
|                             default:
 | |
|                                 unknown_type("instruction");
 | |
|                         }
 | |
|                     }
 | |
|                     else
 | |
|                         queue_insn_simple(em.em_opcode);
 | |
|                 }
 | |
|                 break;
 | |
| 
 | |
|             default:
 | |
|                 fatal("unrecognised instruction type '%d'", em.em_type);
 | |
|         }
 | |
| 
 | |
| 		EM_getinstr(&em);
 | |
| 	}
 | |
| 
 | |
| 	tb_fileend();
 | |
| }
 | |
| 
 | |
| /* vim: set sw=4 ts=4 expandtab : */
 |