An emulator, assembler, and disassembler for the Sega Game Gear
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

546 lines
16 KiB

  1. /* Copyright (C) 2014-2015 Ben Kurtovic <ben.kurtovic@gmail.com>
  2. Released under the terms of the MIT License. See LICENSE for details. */
  3. #include <stdlib.h>
  4. #include <string.h>
  5. #include "tokenizer.h"
  6. #include "directives.h"
  7. #include "instructions.h"
  8. #include "inst_args.h"
  9. #include "parse_util.h"
  10. #include "../logging.h"
  11. #include "../mmu.h"
  12. #include "../rom.h"
  13. #include "../util.h"
  14. /* Internal structs */
  15. typedef struct {
  16. size_t size;
  17. const ASMLine **overlap_table;
  18. const ASMLine **overlap_origins;
  19. const ASMLine *origin;
  20. uint8_t bank;
  21. bool cross_blocks;
  22. } ASMLayoutInfo;
  23. typedef struct {
  24. int8_t slots[MMU_NUM_ROM_BANKS];
  25. const ASMLine *lines[MMU_NUM_ROM_BANKS];
  26. } ASMSlotInfo;
  27. /* Sentinel values for overlap table */
  28. const ASMLine header_sentinel, bounds_sentinel;
  29. /* Typedef for parse_util data parser functions */
  30. typedef bool (*parser_func)(uint8_t**, size_t*, const char*, ssize_t);
  31. /*
  32. Return the address of a given ROM offset when mapped into the given slot.
  33. */
  34. static inline uint16_t map_into_slot(size_t offset, int8_t slot)
  35. {
  36. return (slot * MMU_ROM_BANK_SIZE) + (offset & (MMU_ROM_BANK_SIZE - 1));
  37. }
  38. /*
  39. Return the default slot associated with a given memory bank.
  40. */
  41. static inline int8_t default_bank_slot(uint8_t bank)
  42. {
  43. return bank > 2 ? 2 : bank;
  44. }
  45. /*
  46. Initialize an ASMLayoutInfo object.
  47. */
  48. static void init_layout_info(ASMLayoutInfo *li, AssemblerState *state)
  49. {
  50. li->size = state->rom_size ? state->rom_size : ROM_SIZE_MAX;
  51. li->origin = NULL;
  52. li->bank = 0;
  53. li->cross_blocks = state->cross_blocks;
  54. if (!(li->overlap_table = calloc(li->size, sizeof(const ASMLine*))))
  55. OUT_OF_MEMORY()
  56. if (!(li->overlap_origins = calloc(li->size, sizeof(const ASMLine*))))
  57. OUT_OF_MEMORY()
  58. for (size_t i = 0; i < HEADER_SIZE; i++)
  59. li->overlap_table[state->header.offset + i] = &header_sentinel;
  60. }
  61. /*
  62. Free the resources allocated by an ASMLayoutInfo object.
  63. */
  64. static void free_layout_info(ASMLayoutInfo *li)
  65. {
  66. free(li->overlap_table);
  67. free(li->overlap_origins);
  68. }
  69. /*
  70. Add a given line, representing a label, to the symbol table.
  71. Return NULL on success and an ErrorInfo object on failure (e.g. in the case
  72. of duplicate labels, or labels sharing names with registers/conditions).
  73. */
  74. static ErrorInfo* add_label_to_table(
  75. ASMSymbolTable *symtable, const ASMLine *line, size_t offset, int8_t slot)
  76. {
  77. if (line->length - 1 >= MAX_SYMBOL_SIZE)
  78. return error_info_create(line, ET_SYMBOL, ED_SYM_TOO_LONG);
  79. ASMArgParseInfo info = {.arg = line->data, .size = line->length - 1};
  80. ASMArgRegister reg;
  81. if (argparse_register(&reg, info))
  82. return error_info_create(line, ET_SYMBOL, ED_SYM_IS_REGISTER);
  83. ASMArgCondition cond;
  84. if (argparse_condition(&cond, info))
  85. return error_info_create(line, ET_SYMBOL, ED_SYM_IS_CONDITION);
  86. char *symbol = strndup(line->data, line->length - 1);
  87. if (!symbol)
  88. OUT_OF_MEMORY()
  89. const ASMSymbol *current = asm_symtable_find(symtable, symbol);
  90. if (current) {
  91. ErrorInfo *ei = error_info_create(line, ET_SYMBOL, ED_SYM_DUPE_LABELS);
  92. error_info_append(ei, current->line);
  93. free(symbol);
  94. return ei;
  95. }
  96. ASMSymbol *label = malloc(sizeof(ASMSymbol));
  97. if (!label)
  98. OUT_OF_MEMORY()
  99. label->offset = map_into_slot(offset,
  100. (slot >= 0) ? slot : default_bank_slot(offset / MMU_ROM_BANK_SIZE));
  101. label->symbol = symbol;
  102. label->line = line;
  103. asm_symtable_insert(symtable, label);
  104. return NULL;
  105. }
  106. /*
  107. Handle a define directive by adding an entry to the define table.
  108. Return NULL on success and an ErrorInfo object on failure.
  109. */
  110. static ErrorInfo* handle_define_directive(
  111. const ASMLine *line, ASMDefineTable *deftab)
  112. {
  113. if (!DIRECTIVE_HAS_ARG(line, DIR_DEFINE))
  114. return error_info_create(line, ET_PREPROC, ED_PP_NO_ARG);
  115. size_t start = DIRECTIVE_OFFSET(line, DIR_DEFINE) + 1, i;
  116. for (i = start; i < line->length; i++) {
  117. if (!is_valid_symbol_char(line->data[i], i == start)) {
  118. if (line->data[i] == ' ' && i > start) {
  119. i++;
  120. break;
  121. }
  122. return error_info_create(line, ET_PREPROC, ED_PP_BAD_ARG);
  123. }
  124. }
  125. if (i >= line->length) // Missing value for define
  126. return error_info_create(line, ET_PREPROC, ED_PP_NO_ARG);
  127. const char *key = line->data + start;
  128. size_t keylen = i - start - 1;
  129. const ASMDefine *current = asm_deftable_find(deftab, key, keylen);
  130. if (current) {
  131. ErrorInfo *ei = error_info_create(line, ET_PREPROC, ED_PP_DUPLICATE);
  132. error_info_append(ei, current->line);
  133. return ei;
  134. }
  135. ASMArgImmediate imm;
  136. ASMArgParseInfo info = {
  137. .arg = line->data + i, .size = line->length - i, .deftable = deftab};
  138. if (!argparse_immediate(&imm, info))
  139. return error_info_create(line, ET_PREPROC, ED_PP_BAD_ARG);
  140. ASMDefine *define = malloc(sizeof(ASMDefine));
  141. if (!define)
  142. OUT_OF_MEMORY()
  143. if (!(define->name = strndup(key, keylen)))
  144. OUT_OF_MEMORY()
  145. define->value = imm;
  146. define->line = line;
  147. asm_deftable_insert(deftab, define);
  148. return NULL;
  149. }
  150. /*
  151. Handle an undefine directive by remove an entry in the define table.
  152. Return NULL on success and an ErrorInfo object on failure.
  153. */
  154. static ErrorInfo* handle_undef_directive(
  155. const ASMLine *line, ASMDefineTable *deftab)
  156. {
  157. if (!DIRECTIVE_HAS_ARG(line, DIR_UNDEF))
  158. return error_info_create(line, ET_PREPROC, ED_PP_NO_ARG);
  159. size_t offset = DIRECTIVE_OFFSET(line, DIR_UNDEF) + 1;
  160. const char *arg = line->data + offset;
  161. size_t size = line->length - offset, i;
  162. for (i = 0; i < size; i++) {
  163. if (!is_valid_symbol_char(arg[i], i == 0))
  164. return error_info_create(line, ET_PREPROC, ED_PP_BAD_ARG);
  165. }
  166. asm_deftable_remove(deftab, arg, size);
  167. return NULL;
  168. }
  169. /*
  170. Handle an origin directive by updating the offset.
  171. Return NULL on success and an ErrorInfo object on failure.
  172. */
  173. static ErrorInfo* handle_origin_directive(const ASMLine *line, size_t *offset)
  174. {
  175. if (!DIRECTIVE_HAS_ARG(line, DIR_ORIGIN))
  176. return error_info_create(line, ET_PREPROC, ED_PP_NO_ARG);
  177. uint32_t arg;
  178. if (!dparse_uint32_t(&arg, line, DIR_ORIGIN))
  179. return error_info_create(line, ET_PREPROC, ED_PP_BAD_ARG);
  180. if (arg >= ROM_SIZE_MAX)
  181. return error_info_create(line, ET_PREPROC, ED_PP_ARG_RANGE);
  182. *offset = arg;
  183. return NULL;
  184. }
  185. /*
  186. Handle a block directive by updating the offset and slot.
  187. Return NULL on success and an ErrorInfo object on failure.
  188. */
  189. static ErrorInfo* handle_block_directive(
  190. const ASMLine *line, size_t *offset, ASMSlotInfo *si)
  191. {
  192. if (!DIRECTIVE_HAS_ARG(line, DIR_BLOCK))
  193. return error_info_create(line, ET_PREPROC, ED_PP_NO_ARG);
  194. uint8_t *args, bank, slot;
  195. size_t dir_offset = DIRECTIVE_OFFSET(line, DIR_BLOCK) + 1, nargs;
  196. if (!parse_bytes(&args, &nargs, line->data + dir_offset,
  197. line->length - dir_offset))
  198. return error_info_create(line, ET_PREPROC, ED_PP_BAD_ARG);
  199. if (nargs < 1 || nargs > 2)
  200. return free(args), error_info_create(line, ET_PREPROC, ED_PP_BAD_ARG);
  201. bank = args[0];
  202. slot = nargs == 2 ? args[1] : default_bank_slot(bank);
  203. free(args);
  204. if (bank >= MMU_NUM_ROM_BANKS || slot >= MMU_NUM_SLOTS)
  205. return error_info_create(line, ET_PREPROC, ED_PP_ARG_RANGE);
  206. if (bank == 0 && slot != 0)
  207. return error_info_create(line, ET_LAYOUT, ED_LYT_BLOCK0);
  208. if (si->slots[bank] >= 0 && si->slots[bank] != slot) {
  209. ErrorInfo *ei = error_info_create(line, ET_LAYOUT, ED_LYT_SLOTS);
  210. error_info_append(ei, si->lines[bank]);
  211. return ei;
  212. }
  213. *offset = bank * MMU_ROM_BANK_SIZE;
  214. si->slots[bank] = slot;
  215. if (!si->lines[bank])
  216. si->lines[bank] = line;
  217. return NULL;
  218. }
  219. /*
  220. Parse a .space directive, which fills a region with a single byte.
  221. */
  222. static bool parse_space(
  223. uint8_t **result, size_t *length, const char *arg, ssize_t size)
  224. {
  225. uint8_t *bytes;
  226. size_t nbytes;
  227. if (!parse_bytes(&bytes, &nbytes, arg, size))
  228. return false;
  229. if (nbytes < 1 || nbytes > 2) {
  230. free(bytes);
  231. return false;
  232. }
  233. *length = bytes[0];
  234. if (!(*result = malloc(sizeof(uint8_t) * (*length))))
  235. OUT_OF_MEMORY()
  236. memset(*result, nbytes == 2 ? bytes[1] : 0, *length);
  237. free(bytes);
  238. return true;
  239. }
  240. /*
  241. Parse data encoded in a line into an ASMData object.
  242. On success, return NULL and store the instruction in *data_ptr. On failure,
  243. return an ErrorInfo object; *data_ptr is not modified.
  244. */
  245. static ErrorInfo* parse_data(
  246. const ASMLine *line, ASMData **data_ptr, size_t offset)
  247. {
  248. const char *directive;
  249. parser_func parser = (parser_func) parse_string;
  250. if (IS_DIRECTIVE(line, DIR_BYTE)) {
  251. directive = DIR_BYTE;
  252. parser = parse_bytes;
  253. } else if (IS_DIRECTIVE(line, DIR_SPACE)) {
  254. directive = DIR_SPACE;
  255. parser = parse_space;
  256. } else if (IS_DIRECTIVE(line, DIR_ASCII)) {
  257. directive = DIR_ASCII;
  258. } else if (IS_DIRECTIVE(line, DIR_ASCIZ)) {
  259. directive = DIR_ASCIZ;
  260. } else if (IS_DIRECTIVE(line, DIR_ASCIIZ)) {
  261. directive = DIR_ASCIIZ;
  262. } else {
  263. return error_info_create(line, ET_PREPROC, ED_PP_UNKNOWN);
  264. }
  265. if (!DIRECTIVE_HAS_ARG(line, directive))
  266. return error_info_create(line, ET_PREPROC, ED_PP_NO_ARG);
  267. size_t dir_offset = DIRECTIVE_OFFSET(line, directive) + 1;
  268. const char *arg = line->data + dir_offset;
  269. size_t arglen = line->length - dir_offset;
  270. ASMData *data = malloc(sizeof(ASMData));
  271. if (!data)
  272. OUT_OF_MEMORY()
  273. data->loc.offset = offset;
  274. data->next = NULL;
  275. if (!parser(&data->bytes, &data->loc.length, arg, arglen)) {
  276. free(data);
  277. return error_info_create(line, ET_PREPROC, ED_PP_BAD_ARG);
  278. }
  279. *data_ptr = data;
  280. return NULL;
  281. }
  282. /*
  283. Parse an instruction encoded in a line into an ASMInstruction object.
  284. On success, return NULL and store the instruction in *inst_ptr. On failure,
  285. return an ErrorInfo object; *inst_ptr is not modified.
  286. */
  287. static ErrorInfo* parse_instruction(
  288. const ASMLine *line, ASMInstruction **inst_ptr, size_t offset,
  289. ASMDefineTable *deftab)
  290. {
  291. char mnemonic[MAX_MNEMONIC_SIZE] = {0};
  292. size_t i = 0;
  293. while (i < line->length) {
  294. char c = line->data[i];
  295. if (c == ' ')
  296. break;
  297. if (i >= MAX_MNEMONIC_SIZE)
  298. return error_info_create(line, ET_PARSER, ED_PS_OP_TOO_LONG);
  299. if ((c < 'a' || c > 'z') && (c < '0' || c > '9'))
  300. return error_info_create(line, ET_PARSER, ED_PS_OP_INVALID);
  301. mnemonic[i++] = c;
  302. }
  303. if (i < MIN_MNEMONIC_SIZE)
  304. return error_info_create(line, ET_PARSER, ED_PS_OP_TOO_SHORT);
  305. if (i + 1 < line->length)
  306. i++; // Advance past space
  307. uint8_t *bytes;
  308. size_t arglen = line->length - i, length;
  309. char *argstart = arglen > 0 ? line->data + i : NULL, *symbol = NULL;
  310. ASMInstParser parser = get_inst_parser(mnemonic);
  311. if (!parser)
  312. return error_info_create(line, ET_PARSER, ED_PS_OP_UNKNOWN);
  313. ASMArgParseInfo ai = {.arg = argstart, .size = arglen, .deftable = deftab};
  314. ASMErrorDesc edesc = parser(&bytes, &length, &symbol, ai);
  315. if (edesc != ED_NONE)
  316. return error_info_create(line, ET_PARSER, edesc);
  317. ASMInstruction *inst = malloc(sizeof(ASMInstruction));
  318. if (!inst)
  319. OUT_OF_MEMORY()
  320. inst->loc.offset = offset;
  321. inst->loc.length = length;
  322. inst->bytes = bytes;
  323. inst->symbol = symbol;
  324. inst->line = line;
  325. inst->next = NULL;
  326. *inst_ptr = inst;
  327. return NULL;
  328. }
  329. /*
  330. Check if the given object location is legal.
  331. Checks include ROM size bounding, overlapping with existing objects, and
  332. block-crossing assuming the .cross_blocks directive has not been specified.
  333. On success, return NULL and add the location to the overlap table.
  334. On failure, return an ErrorInfo object.
  335. */
  336. static ErrorInfo* check_layout(
  337. ASMLayoutInfo *li, const ASMLocation *loc, const ASMLine *line)
  338. {
  339. const ASMLine *clash = NULL, *clash_origin;
  340. if (loc->offset + loc->length > li->size) {
  341. clash = &bounds_sentinel;
  342. } else {
  343. for (size_t i = 0; i < loc->length; i++) {
  344. if (li->overlap_table[loc->offset + i]) {
  345. clash = li->overlap_table[loc->offset + i];
  346. clash_origin = li->overlap_origins[loc->offset + i];
  347. break;
  348. }
  349. }
  350. }
  351. if (clash) {
  352. ErrorInfo *ei = error_info_create(line, ET_LAYOUT,
  353. (clash == &header_sentinel) ? ED_LYT_OVERLAP_HEAD :
  354. (clash == &bounds_sentinel) ? ED_LYT_BOUNDS : ED_LYT_OVERLAP);
  355. if (li->origin)
  356. error_info_append(ei, li->origin);
  357. if (clash != &header_sentinel && clash != &bounds_sentinel) {
  358. error_info_append(ei, clash);
  359. if (clash_origin)
  360. error_info_append(ei, clash_origin);
  361. }
  362. return ei;
  363. }
  364. uint8_t bank = (loc->offset + loc->length - 1) / MMU_ROM_BANK_SIZE;
  365. if (bank != li->bank && !li->cross_blocks) {
  366. ErrorInfo *ei = error_info_create(line, ET_LAYOUT, ED_LYT_BLOCK_CROSS);
  367. if (li->origin)
  368. error_info_append(ei, li->origin);
  369. return ei;
  370. }
  371. for (size_t i = 0; i < loc->length; i++) {
  372. li->overlap_table[loc->offset + i] = line;
  373. li->overlap_origins[loc->offset + i] = li->origin;
  374. }
  375. return NULL;
  376. }
  377. /*
  378. Tokenize ASMLines into ASMInstructions and ASMData.
  379. NULL is returned on success and an ErrorInfo object is returned on failure.
  380. state->instructions, state->data, and state->symtable may or may not be
  381. modified regardless of success.
  382. */
  383. ErrorInfo* tokenize(AssemblerState *state)
  384. {
  385. ErrorInfo *ei = NULL;
  386. ASMLayoutInfo li;
  387. ASMSlotInfo si = {.lines = {0}};
  388. ASMDefineTable *deftab = asm_deftable_new();
  389. ASMInstruction dummy_inst = {.next = NULL}, *inst, *prev_inst = &dummy_inst;
  390. ASMData dummy_data = {.next = NULL}, *data, *prev_data = &dummy_data;
  391. const ASMLine *line = state->lines;
  392. size_t offset = 0;
  393. init_layout_info(&li, state);
  394. memset(si.slots, -1, MMU_NUM_ROM_BANKS);
  395. while (line) {
  396. if (line->is_label) {
  397. if (offset >= li.size) {
  398. ei = error_info_create(line, ET_LAYOUT, ED_LYT_BOUNDS);
  399. goto cleanup;
  400. }
  401. int8_t slot = si.slots[offset / MMU_NUM_ROM_BANKS];
  402. if ((ei = add_label_to_table(state->symtable, line, offset, slot)))
  403. goto cleanup;
  404. }
  405. else if (IS_LOCAL_DIRECTIVE(line)) {
  406. if (IS_DIRECTIVE(line, DIR_DEFINE)) {
  407. if ((ei = handle_define_directive(line, deftab)))
  408. goto cleanup;
  409. }
  410. else if (IS_DIRECTIVE(line, DIR_UNDEF)) {
  411. if ((ei = handle_undef_directive(line, deftab)))
  412. goto cleanup;
  413. }
  414. else if (IS_DIRECTIVE(line, DIR_ORIGIN)) {
  415. if ((ei = handle_origin_directive(line, &offset)))
  416. goto cleanup;
  417. li.origin = line;
  418. li.bank = offset / MMU_ROM_BANK_SIZE;
  419. }
  420. else if (IS_DIRECTIVE(line, DIR_BLOCK)) {
  421. if ((ei = handle_block_directive(line, &offset, &si)))
  422. goto cleanup;
  423. li.origin = line;
  424. li.bank = offset / MMU_ROM_BANK_SIZE;
  425. }
  426. else {
  427. if ((ei = parse_data(line, &data, offset)))
  428. goto cleanup;
  429. offset += data->loc.length;
  430. prev_data->next = data;
  431. prev_data = data;
  432. if ((ei = check_layout(&li, &data->loc, line)))
  433. goto cleanup;
  434. }
  435. }
  436. else {
  437. if ((ei = parse_instruction(line, &inst, offset, deftab)))
  438. goto cleanup;
  439. offset += inst->loc.length;
  440. prev_inst->next = inst;
  441. prev_inst = inst;
  442. if ((ei = check_layout(&li, &inst->loc, line)))
  443. goto cleanup;
  444. }
  445. line = line->next;
  446. }
  447. cleanup:
  448. state->instructions = dummy_inst.next;
  449. state->data = dummy_data.next;
  450. free_layout_info(&li);
  451. asm_deftable_free(deftab);
  452. return ei;
  453. }