X-Git-Url: https://jxself.org/git/?a=blobdiff_plain;f=src%2Fasm.c;h=858ff9dd59477220fdc1887cc59386ed7c935ef6;hb=HEAD;hp=faace9c168ad549a8c3e6ebfcacb751bd18931b9;hpb=c881aa3386c00d7021ffabf2f66275d6c110c1c1;p=inform.git diff --git a/src/asm.c b/src/asm.c index faace9c..858ff9d 100644 --- a/src/asm.c +++ b/src/asm.c @@ -1,8 +1,8 @@ /* ------------------------------------------------------------------------- */ /* "asm" : The Inform assembler */ /* */ -/* Part of Inform 6.35 */ -/* copyright (c) Graham Nelson 1993 - 2021 */ +/* Part of Inform 6.42 */ +/* copyright (c) Graham Nelson 1993 - 2024 */ /* */ /* Inform is free software: you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ @@ -21,27 +21,32 @@ #include "header.h" -uchar *zcode_holding_area; /* Area holding code yet to be transferred +static uchar *zcode_holding_area; /* Area holding code yet to be transferred to either zcode_area or temp file no 1 */ -uchar *zcode_markers; /* Bytes holding marker values for this +static memory_list zcode_holding_area_memlist; +static uchar *zcode_markers; /* Bytes holding marker values for this code */ +static memory_list zcode_markers_memlist; static int zcode_ha_size; /* Number of bytes in holding area */ -memory_block zcode_area; /* Block to hold assembled code (if - temporary files are not being used) */ +uchar *zcode_area; /* Array to hold assembled code */ + +memory_list zcode_area_memlist; /* Manages zcode_area */ int32 zmachine_pc; /* PC position of assembly (byte offset from start of Z-code area) */ int32 no_instructions; /* Number of instructions assembled */ -int execution_never_reaches_here, /* TRUE if the current PC value in the +int execution_never_reaches_here; /* nonzero if the current PC value in the code area cannot be reached: e.g. if the previous instruction was a "quit" - opcode and no label is set to here */ - next_label, /* Used to count the labels created all + opcode and no label is set to here + (see EXECSTATE flags for more) */ +int next_label, /* Used to count the labels created all over Inform in current routine, from 0 */ next_sequence_point; /* Likewise, for sequence points */ -int no_sequence_points; /* Kept for statistics purposes only */ +int no_sequence_points; /* Total over all routines; kept for + statistics purposes only */ static int label_moved_error_already_given; /* When one label has moved, all subsequent @@ -60,18 +65,19 @@ int uses_acceleration_features; /* Makes use of Glulx acceleration (3.1.1) features? */ int uses_float_features; /* Makes use of Glulx floating-point (3.1.2) features? */ +int uses_extundo_features; /* Makes use of Glulx extended undo (3.1.3) + features? */ +int uses_double_features; /* Makes use of Glulx double-prec (3.1.3) + features? */ debug_location statement_debug_location; /* Location of current statement */ -int32 *variable_tokens; /* The allocated size is - (MAX_LOCAL_VARIABLES + - MAX_GLOBAL_VARIABLES). The entries - MAX_LOCAL_VARIABLES and up give the - symbol table index for the names of - the global variables */ -int *variable_usage; /* TRUE if referred to, FALSE otherwise */ +variableinfo *variables; /* The allocated size is + (MAX_LOCAL_VARIABLES + no_globals). + Local variables first, then globals. */ +memory_list variables_memlist; assembly_instruction AI; /* A structure used to hold the full specification of a single Z-code @@ -85,14 +91,15 @@ static char opcode_syntax_string[128]; /* Text buffer holding the correct static int routine_symbol; /* The symbol index of the routine currently being compiled */ -static char *routine_name; /* The name of the routine currently being - compiled */ -static int routine_locals; /* The number of local variables used by - the routine currently being compiled */ +static memory_list current_routine_name; /* The name of the routine currently + being compiled. (This may not be a + simple symbol, e.g. for an "obj.prop" + property routine.) */ static int32 routine_start_pc; -int32 *named_routine_symbols; +int32 *named_routine_symbols; /* Allocated up to no_named_routines */ +static memory_list named_routine_symbols_memlist; static void transfer_routine_z(void); static void transfer_routine_g(void); @@ -101,34 +108,160 @@ static void transfer_routine_g(void); /* Label data */ /* ------------------------------------------------------------------------- */ +static labelinfo *labels; /* Label offsets (i.e. zmachine_pc values). + These are allocated sequentially, but accessed + as a double-linked list from first_label + to last_label (in PC order). */ +static memory_list labels_memlist; static int first_label, last_label; -static int32 *label_offsets; /* Double-linked list of label offsets */ -static int *label_next, /* (i.e. zmachine_pc values) in PC order */ - *label_prev; -static int32 *label_symbols; /* Symbol numbers if defined in source */ -static int *sequence_point_labels; - /* Label numbers for each */ -static debug_location *sequence_point_locations; - /* Source code references for each */ - /* (used for making debugging file) */ +static int *labeluse; /* Flags indicating whether a given label has been + used as a branch target yet. */ +static memory_list labeluse_memlist; +static int labeluse_size; /* Entries up to here are initialized */ + +static sequencepointinfo *sequence_points; /* Allocated to next_sequence_point. + Only used when debugfile_switch + is set. */ +static memory_list sequence_points_memlist; + +/* ------------------------------------------------------------------------- */ +/* Label management */ +/* ------------------------------------------------------------------------- */ + +/* The stripping of unreachable code requires a bit of explanation. + + As we compile a function, we update the execution_never_reaches_here + flag to reflect whether the current line is reachable. If the flag + is set (EXECSTATE_UNREACHABLE), we skip generating opcodes, + compiling strings, and so on. (See assemblez_instruction(), + assembleg_instruction(), and compile_string() for these checks.) + + If we're *between* functions, the execution_never_reaches_here flag + is always clear (EXECSTATE_REACHABLE), so global strings are + compiled correctly. + + In general, every time we compile a JUMP or RETURN opcode, the flag + gets set. Every time we compile a label, the flag gets cleared. + This makes sense if you consider a common "while" loop: + + while (true) { + ... + if (flag) break; + ... + } + ... + + This is compiled as: + + .TopLabel; + ... + @jnz flag ?ExitLabel; + ... + @jump TopLabel; + .ExitLabel; + ... + + Code after an unconditional JUMP is obviously unreachable. But the + next thing that happens is the .ExitLabel, which is the target of a + branch, so the next line is reachable again. + + However, if the unreachable flag is set when we *begin* a statement + (or braced block of statements), we can get tougher. We set the + EXECSTATE_ENTIRE flag for the entire duration of the statement or + block. This flag cannot be cleared by compiling labels. An example + of this: + + if (DOSTUFF) { + while (true) { + if (flag) break; + } + } + + If the DOSTUFF constant is false, the entire "while" loop is definitely + unreachable. So we should skip .TopLabel, .ExitLabel, and everything + in between. To ensure this, we set EXECSTATE_ENTIRE upon entering the + "if {...}" braced block, and reset it upon leaving. + + (See parse_code_block() and parse_statement() for the (slightly fugly) + bit-fidding that accomplishes this.) + + As an added optimization, some labels are known to be "forward"; they + are only reached by forward jumps. (.ExitLabel above is an example.) + If we reach a forward label and nothing has in fact jumped there, + the label is dead and we can skip it. (And thus also skip clearing + the unreachable flag!) + + To understand *that*, consider a "while true" loop with no "break": + + while (true) { + ... + if (flag) return; + ... + } + + This never branches to .ExitLabel. So when we reach .ExitLabel, + we can say for sure that *nothing* branches there. So we skip + compiling that label. The unreachable flag is left set (because we + just finished the jump to .TopLabel). Thus, any code following the + entire loop is known to be unreachable, and we can righteously + complain about it. + + (In contrast, .TopLabel cannot be skipped because it might be the + target of a *backwards* branch later on. In fact there might be + several -- any "continue" in the loop will jump to .TopLabel.) +*/ + +/* Set the position of the given label. The offset will be the current + zmachine_pc, or -1 if the label is definitely unused. + + This adds the label to a linked list (via first_label, last_label). + + The linked list must be in increasing PC order. We know this will + be true because we call this as we run through the function, so + zmachine_pc always increases. + (It won't necessarily be in *label index* order, though.) +*/ static void set_label_offset(int label, int32 offset) { - if (label >= MAX_LABELS) memoryerror("MAX_LABELS", MAX_LABELS); - - label_offsets[label] = offset; + ensure_memory_list_available(&labels_memlist, label+1); + + labels[label].offset = offset; + labels[label].symbol = -1; + if (offset < 0) { + /* Mark this label as invalid and don't put it in the linked list. */ + labels[label].prev = -1; + labels[label].next = -1; + return; + } + if (last_label == -1) - { label_prev[label] = -1; + { labels[label].prev = -1; first_label = label; } else - { label_prev[label] = last_label; - label_next[last_label] = label; + { labels[label].prev = last_label; + labels[last_label].next = label; } last_label = label; - label_next[label] = -1; - label_symbols[label] = -1; + labels[label].next = -1; +} + +/* Set a flag indicating that the given label has been jumped to. */ +static void mark_label_used(int label) +{ + if (label < 0) + return; + + /* Entries from 0 to labeluse_size have meaningful values. + If we have to increase labeluse_size, initialize the new + entries to FALSE. */ + ensure_memory_list_available(&labeluse_memlist, label+1); + for (; labeluse_size < label+1; labeluse_size++) { + labeluse[labeluse_size] = FALSE; + } + labeluse[label] = TRUE; } /* ------------------------------------------------------------------------- */ @@ -187,7 +320,7 @@ extern int is_variable_ot(int otval) extern char *variable_name(int32 i) { if (i==0) return("sp"); - if (imarker) { + printf((!any) ? " (" : ": "); + any = TRUE; + printf("%s", describe_mv(o->marker)); + switch (o->marker) { + case VROUTINE_MV: + printf(": %s", veneer_routine_name(o->value)); + break; + case INCON_MV: + printf(": %s", name_of_system_constant(o->value)); + break; + case DWORD_MV: + printf(": '"); + print_dict_word(o->value); + printf("'"); + break; + } + } + if (o->symindex >= 0 && o->symindex < no_symbols) { + printf((!any) ? " (" : ": "); + any = TRUE; + printf("%s", symbols[o->symindex].name); + } + if (any) printf(")"); +} + +static void print_operand_z(const assembly_operand *o, int annotate) +{ switch(o->type) { case EXPRESSION_OT: printf("expr_"); break; case LONG_CONSTANT_OT: printf("long_"); break; case SHORT_CONSTANT_OT: printf("short_"); break; case VARIABLE_OT: - if (o.value==0) { printf("sp"); return; } - printf("%s", variable_name(o.value)); return; + if (o->value==0) { printf("sp"); return; } + printf("%s", variable_name(o->value)); return; case OMITTED_OT: printf(""); return; } - printf("%d", o.value); + printf("%d", o->value); + if (annotate) + print_operand_annotation(o); } -static void print_operand_g(assembly_operand o) +static void print_operand_g(const assembly_operand *o, int annotate) { - switch (o.type) { + switch (o->type) { case EXPRESSION_OT: printf("expr_"); break; case CONSTANT_OT: printf("long_"); break; case HALFCONSTANT_OT: printf("short_"); break; @@ -244,32 +409,34 @@ static void print_operand_g(assembly_operand o) case ZEROCONSTANT_OT: printf("zero_"); return; case DEREFERENCE_OT: printf("*"); break; case GLOBALVAR_OT: - printf("%s (global_%d)", variable_name(o.value), o.value); + printf("global_%d (%s)", o->value, variable_name(o->value)); return; case LOCALVAR_OT: - if (o.value == 0) + if (o->value == 0) printf("stackptr"); else - printf("%s (local_%d)", variable_name(o.value), o.value-1); + printf("local_%d (%s)", o->value-1, variable_name(o->value)); return; case SYSFUN_OT: - if (o.value >= 0 && o.value < NUMBER_SYSTEM_FUNCTIONS) - printf("%s", system_functions.keywords[o.value]); + if (o->value >= 0 && o->value < NUMBER_SYSTEM_FUNCTIONS) + printf("%s", system_functions.keywords[o->value]); else printf(""); return; case OMITTED_OT: printf(""); return; default: printf("???_"); break; } - printf("%d", o.value); + printf("%d", o->value); + if (annotate) + print_operand_annotation(o); } -extern void print_operand(assembly_operand o) +extern void print_operand(const assembly_operand *o, int annotate) { if (!glulx_mode) - print_operand_z(o); + print_operand_z(o, annotate); else - print_operand_g(o); + print_operand_g(o, annotate); } /* ------------------------------------------------------------------------- */ @@ -277,8 +444,9 @@ extern void print_operand(assembly_operand o) /* ------------------------------------------------------------------------- */ static void byteout(int32 i, int mv) -{ if (zcode_ha_size >= MAX_ZCODE_SIZE) - memoryerror("MAX_ZCODE_SIZE",MAX_ZCODE_SIZE); +{ + ensure_memory_list_available(&zcode_markers_memlist, zcode_ha_size+1); + ensure_memory_list_available(&zcode_holding_area_memlist, zcode_ha_size+1); zcode_markers[zcode_ha_size] = (uchar) mv; zcode_holding_area[zcode_ha_size++] = (uchar) i; zmachine_pc++; @@ -286,7 +454,7 @@ static void byteout(int32 i, int mv) /* ------------------------------------------------------------------------- */ /* A database of the 115 canonical Infocom opcodes in Versions 3 to 6 */ -/* And of the however-many-there-are Glulx opcode */ +/* And of the however-many-there-are Glulx opcodes */ /* ------------------------------------------------------------------------- */ typedef struct opcodez @@ -337,6 +505,8 @@ typedef struct opcodeg #define GOP_MemHeap 2 /* uses_memheap_features */ #define GOP_Acceleration 4 /* uses_acceleration_features */ #define GOP_Float 8 /* uses_float_features */ +#define GOP_ExtUndo 16 /* uses_extundo_features */ +#define GOP_Double 32 /* uses_double_features */ /* Codes for the number of operands */ @@ -485,7 +655,12 @@ static opcodez opcodes_table_z[] = /* Opcodes introduced in Z-Machine Specification Standard 1.0 */ /* 116 */ { (uchar *) "print_unicode", 5, 0, -1, 0x0b, 0, 0, 0, EXT }, -/* 117 */ { (uchar *) "check_unicode", 5, 0, -1, 0x0c, St, 0, 0, EXT } +/* 117 */ { (uchar *) "check_unicode", 5, 0, -1, 0x0c, St, 0, 0, EXT }, + + /* Opcodes introduced in Z-Machine Specification Standard 1.1 */ + +/* 118 */ { (uchar *) "set_true_colour", 5, 0, -1, 0x0d, 0, 0, 0, EXT }, +/* 119 */ { (uchar *) "buffer_screen", 6, 6, -1, 0x1d, St, 0, 0, EXT } }; /* Subsequent forms for opcodes whose meaning changes with version */ @@ -605,6 +780,8 @@ static opcodeg opcodes_table_g[] = { { (uchar *) "mfree", 0x179, 0, GOP_MemHeap, 1 }, { (uchar *) "accelfunc", 0x180, 0, GOP_Acceleration, 2 }, { (uchar *) "accelparam", 0x181, 0, GOP_Acceleration, 2 }, + { (uchar *) "hasundo", 0x128, St, GOP_ExtUndo, 1 }, + { (uchar *) "discardundo",0x129, 0, GOP_ExtUndo, 0 }, { (uchar *) "numtof", 0x190, St, GOP_Float, 2 }, { (uchar *) "ftonumz", 0x191, St, GOP_Float, 2 }, { (uchar *) "ftonumn", 0x192, St, GOP_Float, 2 }, @@ -634,13 +811,47 @@ static opcodeg opcodes_table_g[] = { { (uchar *) "jfge", 0x1C5, Br, GOP_Float, 3 }, { (uchar *) "jisnan", 0x1C8, Br, GOP_Float, 2 }, { (uchar *) "jisinf", 0x1C9, Br, GOP_Float, 2 }, + { (uchar *) "numtod", 0x200, St|St2, GOP_Double, 3 }, + { (uchar *) "dtonumz", 0x201, St, GOP_Double, 3 }, + { (uchar *) "dtonumn", 0x202, St, GOP_Double, 3 }, + { (uchar *) "ftod", 0x203, St|St2, GOP_Double, 3 }, + { (uchar *) "dtof", 0x204, St, GOP_Double, 3 }, + { (uchar *) "dceil", 0x208, St|St2, GOP_Double, 4 }, + { (uchar *) "dfloor", 0x209, St|St2, GOP_Double, 4 }, + { (uchar *) "dadd", 0x210, St|St2, GOP_Double, 6 }, + { (uchar *) "dsub", 0x211, St|St2, GOP_Double, 6 }, + { (uchar *) "dmul", 0x212, St|St2, GOP_Double, 6 }, + { (uchar *) "ddiv", 0x213, St|St2, GOP_Double, 6 }, + { (uchar *) "dmodr", 0x214, St|St2, GOP_Double, 6 }, + { (uchar *) "dmodq", 0x215, St|St2, GOP_Double, 6 }, + { (uchar *) "dsqrt", 0x218, St|St2, GOP_Double, 4 }, + { (uchar *) "dexp", 0x219, St|St2, GOP_Double, 4 }, + { (uchar *) "dlog", 0x21A, St|St2, GOP_Double, 4 }, + { (uchar *) "dpow", 0x21B, St|St2, GOP_Double, 6 }, + { (uchar *) "dsin", 0x220, St|St2, GOP_Double, 4 }, + { (uchar *) "dcos", 0x221, St|St2, GOP_Double, 4 }, + { (uchar *) "dtan", 0x222, St|St2, GOP_Double, 4 }, + { (uchar *) "dasin", 0x223, St|St2, GOP_Double, 4 }, + { (uchar *) "dacos", 0x224, St|St2, GOP_Double, 4 }, + { (uchar *) "datan", 0x225, St|St2, GOP_Double, 4 }, + { (uchar *) "datan2", 0x226, St|St2, GOP_Double, 6 }, + { (uchar *) "jdeq", 0x230, Br, GOP_Double, 7 }, + { (uchar *) "jdne", 0x231, Br, GOP_Double, 7 }, + { (uchar *) "jdlt", 0x232, Br, GOP_Double, 5 }, + { (uchar *) "jdle", 0x233, Br, GOP_Double, 5 }, + { (uchar *) "jdgt", 0x234, Br, GOP_Double, 5 }, + { (uchar *) "jdge", 0x235, Br, GOP_Double, 5 }, + { (uchar *) "jdisnan", 0x238, Br, GOP_Double, 3 }, + { (uchar *) "jdisinf", 0x239, Br, GOP_Double, 3 }, }; /* The opmacros table is used for fake opcodes. The opcode numbers are ignored; this table is only used for argument parsing. */ static opcodeg opmacros_table_g[] = { - { (uchar *) "pull", 0, St, 0, 1 }, - { (uchar *) "push", 0, 0, 0, 1 }, + { (uchar *) "pull", pull_gm, St, 0, 1 }, + { (uchar *) "push", push_gm, 0, 0, 1 }, + { (uchar *) "dload", dload_gm, St|St2, 0, 3 }, + { (uchar *) "dstore", dstore_gm, 0, 0, 3 }, }; static opcodeg custom_opcode_g; @@ -664,6 +875,7 @@ static opcodez internal_number_to_opcode_z(int32 i) static void make_opcode_syntax_z(opcodez opco) { char *p = "", *q = opcode_syntax_string; + /* TODO: opcode_syntax_string[128] is unsafe */ sprintf(q, "%s", opco.name); switch(opco.no) { case ONE: p=" "; break; @@ -711,6 +923,7 @@ static void make_opcode_syntax_g(opcodeg opco) int ix; char *cx; char *q = opcode_syntax_string; + /* TODO: opcode_syntax_string[128] is unsafe */ sprintf(q, "%s", opco.name); sprintf(q+strlen(q), " <%d operand%s", opco.no, @@ -765,10 +978,6 @@ static void make_opcode_syntax_g(opcodeg opco) /* This is for Z-code only. */ static void write_operand(assembly_operand op) { int32 j; - if (module_switch && (op.marker != 0)) - { if ((op.marker != VARIABLE_MV) && (op.type == SHORT_CONSTANT_OT)) - op.type = LONG_CONSTANT_OT; - } j=op.value; switch(op.type) { case LONG_CONSTANT_OT: @@ -778,7 +987,7 @@ static void write_operand(assembly_operand op) byteout(j, 0); else byteout(j, 0x80 + op.marker); return; case VARIABLE_OT: - byteout(j, (module_switch)?(0x80 + op.marker):0); return; + byteout(j, 0); return; case CONSTANT_OT: case HALFCONSTANT_OT: case BYTECONSTANT_OT: @@ -792,9 +1001,10 @@ static void write_operand(assembly_operand op) } } -extern void assemblez_instruction(assembly_instruction *AI) +extern void assemblez_instruction(const assembly_instruction *AI) { - uchar *start_pc, *operands_pc; + int32 operands_pc; + int32 start_pc; int32 offset, j, topbits=0, types_byte1, types_byte2; int operand_rules, min=0, max=0, no_operands_given, at_seq_point = FALSE; assembly_operand o1, o2; @@ -802,6 +1012,15 @@ extern void assemblez_instruction(assembly_instruction *AI) ASSERT_ZCODE(); + if (execution_never_reaches_here) { + if (!(execution_never_reaches_here & EXECSTATE_NOWARN)) { + warning("This statement can never be reached"); + /* only show the warning once */ + execution_never_reaches_here |= EXECSTATE_NOWARN; + } + return; + } + offset = zmachine_pc; no_instructions++; @@ -810,8 +1029,10 @@ extern void assemblez_instruction(assembly_instruction *AI) if (sequence_point_follows) { sequence_point_follows = FALSE; at_seq_point = TRUE; if (debugfile_switch) - { sequence_point_labels[next_sequence_point] = next_label; - sequence_point_locations[next_sequence_point] = + { + ensure_memory_list_available(&sequence_points_memlist, next_sequence_point+1); + sequence_points[next_sequence_point].label = next_label; + sequence_points[next_sequence_point].location = statement_debug_location; set_label_offset(next_label++, zmachine_pc); } @@ -825,11 +1046,8 @@ extern void assemblez_instruction(assembly_instruction *AI) return; } - if (execution_never_reaches_here) - warning("This statement can never be reached"); - operand_rules = opco.op_rules; - execution_never_reaches_here = ((opco.flags & Rf) != 0); + execution_never_reaches_here = ((opco.flags & Rf) ? EXECSTATE_UNREACHABLE : EXECSTATE_REACHABLE); if (opco.flags2_set != 0) flags2_requirements[opco.flags2_set] = 1; @@ -840,7 +1058,7 @@ extern void assemblez_instruction(assembly_instruction *AI) /* 1. Write the opcode byte(s) */ - start_pc = zcode_holding_area + zcode_ha_size; + start_pc = zcode_ha_size; switch(opco.no) { case VAR_LONG: topbits=0xc0; min=0; max=8; break; @@ -855,23 +1073,31 @@ extern void assemblez_instruction(assembly_instruction *AI) } byteout(opco.code + topbits, 0); - operands_pc = zcode_holding_area + zcode_ha_size; + operands_pc = zcode_ha_size; /* 2. Dispose of the special rules LABEL and TEXT */ if (operand_rules==LABEL) { j = (AI->operand[0]).value; + mark_label_used(j); byteout(j/256, LABEL_MV); byteout(j%256, 0); goto Instruction_Done; } if (operand_rules==TEXT) { int32 i; - uchar *tmp = translate_text(zcode_holding_area + zcode_ha_size, zcode_holding_area+MAX_ZCODE_SIZE, AI->text); - if (!tmp) - memoryerror("MAX_ZCODE_SIZE", MAX_ZCODE_SIZE); - j = subtract_pointers(tmp, (zcode_holding_area + zcode_ha_size)); - for (i=0; itext, STRCTX_GAMEOPC); + if (j < 0) { + error("text translation failed"); + j = 0; + } + ensure_memory_list_available(&zcode_markers_memlist, zcode_ha_size+j); + ensure_memory_list_available(&zcode_holding_area_memlist, zcode_ha_size+j); + for (i=0; ioperand[0]; - *start_pc=(*start_pc) + o1.type*0x10; + zcode_holding_area[start_pc] += o1.type*0x10; write_operand(o1); break; @@ -919,12 +1145,12 @@ extern void assemblez_instruction(assembly_instruction *AI) /* Transfer to VAR form if either operand is a long constant */ if ((o1.type==LONG_CONSTANT_OT)||(o2.type==LONG_CONSTANT_OT)) - { *start_pc=(*start_pc) + 0xc0; + { zcode_holding_area[start_pc] += 0xc0; byteout(o1.type*0x40 + o2.type*0x10 + 0x0f, 0); } else - { if (o1.type==VARIABLE_OT) *start_pc=(*start_pc) + 0x40; - if (o2.type==VARIABLE_OT) *start_pc=(*start_pc) + 0x20; + { if (o1.type==VARIABLE_OT) zcode_holding_area[start_pc] += 0x40; + if (o2.type==VARIABLE_OT) zcode_holding_area[start_pc] += 0x20; } write_operand(o1); write_operand(o2); @@ -934,17 +1160,18 @@ extern void assemblez_instruction(assembly_instruction *AI) /* 4. Assemble a Store destination, if needed */ if ((AI->store_variable_number) != -1) - { if (AI->store_variable_number >= MAX_LOCAL_VARIABLES+MAX_GLOBAL_VARIABLES) { + { if (AI->store_variable_number >= MAX_LOCAL_VARIABLES+MAX_ZCODE_GLOBAL_VARS) { goto OpcodeSyntaxError; } o1.type = VARIABLE_OT; o1.value = AI->store_variable_number; - variable_usage[o1.value] = TRUE; + variables[o1.value].usage = TRUE; o1.marker = 0; /* Note that variable numbers 249 to 255 (i.e. globals 233 to 239) are used as scratch workspace, so need no mapping between - modules and story files: nor do local variables 0 to 15 */ + modules and story files: nor do local variables 0 to 15. + (Modules no longer exist but why drop a good comment.) */ if ((o1.value >= MAX_LOCAL_VARIABLES) && (o1.value < 249)) o1.marker = VARIABLE_MV; @@ -956,7 +1183,7 @@ extern void assemblez_instruction(assembly_instruction *AI) if (AI->branch_label_number != -1) { int32 addr, long_form; int branch_on_true = (AI->branch_flag)?1:0; - + mark_label_used(AI->branch_label_number); switch (AI->branch_label_number) { case -2: addr = 2; branch_on_true = 0; long_form = 0; break; /* branch nowhere, carry on */ @@ -994,7 +1221,7 @@ extern void assemblez_instruction(assembly_instruction *AI) for (i=0; ioperand_count; i++) { if ((i==0) && (opco.op_rules == VARIAB)) { if ((AI->operand[0]).type == VARIABLE_OT) - { printf("["); print_operand_z(AI->operand[i]); } + { printf("["); print_operand_z(&AI->operand[i], TRUE); } else printf("%s", variable_name((AI->operand[0]).value)); } @@ -1002,14 +1229,14 @@ extern void assemblez_instruction(assembly_instruction *AI) if ((i==0) && (opco.op_rules == LABEL)) { printf("L%d", AI->operand[0].value); } - else print_operand_z(AI->operand[i]); + else print_operand_z(&AI->operand[i], TRUE); printf(" "); } if (AI->store_variable_number != -1) { assembly_operand AO; printf("-> "); AO.type = VARIABLE_OT; AO.value = AI->store_variable_number; - print_operand_z(AO); printf(" "); + print_operand_z(&AO, TRUE); printf(" "); } switch(AI->branch_label_number) @@ -1025,17 +1252,17 @@ extern void assemblez_instruction(assembly_instruction *AI) } if (asm_trace_level>=2) - { for (j=0;start_pcinternal_number); no_operands_given = AI->operand_count; @@ -1075,14 +1304,51 @@ static void assembleg_macro(assembly_instruction *AI) } } - /* expand the macro */ - switch (AI->internal_number) { - case pull_gm: - assembleg_store(AI->operand[0], stack_pointer); + /* Expand the macro. + The assembleg_() functions overwrite AI, so we need to copy out + its operands before we call them. */ + + switch (opco.code) { + case pull_gm: /* @pull STORE */ + AMO_0 = AI->operand[0]; + assembleg_store(AMO_0, stack_pointer); break; - case push_gm: - assembleg_store(stack_pointer, AI->operand[0]); + case push_gm: /* @push LOAD */ + AMO_0 = AI->operand[0]; + assembleg_store(stack_pointer, AMO_0); + break; + + case dload_gm: /* @dload LOAD STORELO STOREHI */ + AMO_0 = AI->operand[0]; + AMO_1 = AI->operand[1]; + AMO_2 = AI->operand[2]; + if ((AMO_0.type == LOCALVAR_OT) && (AMO_0.value == 0)) { + /* addr is on the stack */ + assembleg_store(temp_var3, stack_pointer); + assembleg_3(aload_gc, temp_var3, one_operand, AMO_1); + assembleg_3(aload_gc, temp_var3, zero_operand, AMO_2); + } + else { + assembleg_3(aload_gc, AMO_0, one_operand, AMO_1); + assembleg_3(aload_gc, AMO_0, zero_operand, AMO_2); + } + break; + + case dstore_gm: /* @dload LOAD LOADHI LOADLO */ + AMO_0 = AI->operand[0]; + AMO_1 = AI->operand[1]; + AMO_2 = AI->operand[2]; + if ((AMO_0.type == LOCALVAR_OT) && (AMO_0.value == 0)) { + /* addr is on the stack */ + assembleg_store(temp_var3, stack_pointer); + assembleg_3(astore_gc, temp_var3, zero_operand, AMO_1); + assembleg_3(astore_gc, temp_var3, one_operand, AMO_2); + } + else { + assembleg_3(astore_gc, AMO_0, zero_operand, AMO_1); + assembleg_3(astore_gc, AMO_0, one_operand, AMO_2); + } break; default: @@ -1098,9 +1364,10 @@ static void assembleg_macro(assembly_instruction *AI) error_named("Assembly mistake: syntax is", opcode_syntax_string); } -extern void assembleg_instruction(assembly_instruction *AI) +extern void assembleg_instruction(const assembly_instruction *AI) { - uchar *start_pc, *opmodes_pc; + int32 opmodes_pc; + int32 start_pc; int32 offset, j; int no_operands_given, at_seq_point = FALSE; int ix, k; @@ -1108,6 +1375,15 @@ extern void assembleg_instruction(assembly_instruction *AI) ASSERT_GLULX(); + if (execution_never_reaches_here) { + if (!(execution_never_reaches_here & EXECSTATE_NOWARN)) { + warning("This statement can never be reached"); + /* only show the warning once */ + execution_never_reaches_here |= EXECSTATE_NOWARN; + } + return; + } + offset = zmachine_pc; no_instructions++; @@ -1116,8 +1392,10 @@ extern void assembleg_instruction(assembly_instruction *AI) if (sequence_point_follows) { sequence_point_follows = FALSE; at_seq_point = TRUE; if (debugfile_switch) - { sequence_point_labels[next_sequence_point] = next_label; - sequence_point_locations[next_sequence_point] = + { + ensure_memory_list_available(&sequence_points_memlist, next_sequence_point+1); + sequence_points[next_sequence_point].label = next_label; + sequence_points[next_sequence_point].location = statement_debug_location; set_label_offset(next_label++, zmachine_pc); } @@ -1126,10 +1404,7 @@ extern void assembleg_instruction(assembly_instruction *AI) opco = internal_number_to_opcode_g(AI->internal_number); - if (execution_never_reaches_here) - warning("This statement can never be reached"); - - execution_never_reaches_here = ((opco.flags & Rf) != 0); + execution_never_reaches_here = ((opco.flags & Rf) ? EXECSTATE_UNREACHABLE : EXECSTATE_REACHABLE); if (opco.op_rules & GOP_Unicode) { uses_unicode_features = TRUE; @@ -1143,12 +1418,18 @@ extern void assembleg_instruction(assembly_instruction *AI) if (opco.op_rules & GOP_Float) { uses_float_features = TRUE; } + if (opco.op_rules & GOP_ExtUndo) { + uses_extundo_features = TRUE; + } + if (opco.op_rules & GOP_Double) { + uses_double_features = TRUE; + } no_operands_given = AI->operand_count; /* 1. Write the opcode byte(s) */ - start_pc = zcode_holding_area + zcode_ha_size; + start_pc = zcode_ha_size; if (opco.code < 0x80) { byteout(opco.code, 0); @@ -1168,7 +1449,7 @@ extern void assembleg_instruction(assembly_instruction *AI) every two operands (rounded up). We write zeroes for now; when the operands are written, we'll go back and fix them. */ - opmodes_pc = zcode_holding_area + zcode_ha_size; + opmodes_pc = zcode_ha_size; for (ix=0; ix= BRANCH_MV && marker < BRANCHMAX_MV)) { @@ -1340,7 +1621,7 @@ extern void assembleg_instruction(assembly_instruction *AI) if (ix & 1) j = (j << 4); - opmodes_pc[ix/2] |= j; + zcode_holding_area[opmodes_pc+ix/2] |= j; } /* Print assembly trace. */ @@ -1359,23 +1640,23 @@ extern void assembleg_instruction(assembly_instruction *AI) printf("to L%d", AI->operand[i].value); } else { - print_operand_g(AI->operand[i]); + print_operand_g(&AI->operand[i], TRUE); } printf(" "); } if (asm_trace_level>=2) { for (j=0; - start_pc= 0 && n < labeluse_size && labeluse[n]); + + if ((!inuse) && (execution_never_reaches_here & EXECSTATE_ENTIRE) && STRIP_UNREACHABLE_LABELS) { + /* We're not going to compile this label at all. Set a negative + offset, which will trip an error if this label is jumped to. */ + set_label_offset(n, -1); + return; + } + if (asm_trace_level > 0) printf("%5d +%05lx .L%d\n", ErrorReport.line_number, ((long int) zmachine_pc), n); set_label_offset(n, zmachine_pc); - execution_never_reaches_here = FALSE; + execution_never_reaches_here = EXECSTATE_REACHABLE; +} + +/* This is the same as assemble_label_no, except we only set up the label + if there has been a forward branch to it. + Returns whether the label is created. + Only use this for labels which never have backwards branches! +*/ +extern int assemble_forward_label_no(int n) +{ + if (n >= 0 && n < labeluse_size && labeluse[n]) { + assemble_label_no(n); + return TRUE; + } + else { + /* There were no forward branches to this label and we promise + there will be no backwards branches to it. Set a negative + offset, which will trip an error if we break our promise. */ + set_label_offset(n, -1); + return FALSE; + } } extern void define_symbol_label(int symbol) -{ label_symbols[svals[symbol]] = symbol; +{ + int label = symbols[symbol].value; + /* We may be creating a new label (label = next_label) or filling in + the value of an old one. So we call ensure. */ + ensure_memory_list_available(&labels_memlist, label+1); + labels[label].symbol = symbol; } -extern int32 assemble_routine_header(int no_locals, - int routine_asterisked, char *name, int embedded_flag, int the_symbol) +/* The local variables must already be set up; no_locals indicates + how many exist. */ +extern int32 assemble_routine_header(int routine_asterisked, char *name, + int embedded_flag, int the_symbol) { int i, rv; int stackargs = FALSE; int name_length; - execution_never_reaches_here = FALSE; + execution_never_reaches_here = EXECSTATE_REACHABLE; - routine_locals = no_locals; - for (i=0; i= 1 - && !strcmp(local_variables.keywords[0], "_vararg_count")) { - stackargs = TRUE; + if (no_locals >= 1 + && strcmpcis(get_local_variable_name(0), "_vararg_count")==0) { + stackargs = TRUE; } if (veneer_mode) routine_starts_line = blank_brief_location; @@ -1448,9 +1773,8 @@ extern int32 assemble_routine_header(int no_locals, routine_symbol = the_symbol; name_length = strlen(name) + 1; - routine_name = - my_malloc(name_length * sizeof(char), "temporary copy of routine name"); - strncpy(routine_name, name, name_length); + ensure_memory_list_available(¤t_routine_name, name_length); + strncpy(current_routine_name.data, name, name_length); /* Update the routine counter */ @@ -1476,13 +1800,15 @@ extern int32 assemble_routine_header(int no_locals, for (i=0; i"); if (embedded_flag) { debug_file_printf ("%s", routine_name); } - else if (sflags[routine_symbol] & REPLACE_SFLAG) + else if (symbols[routine_symbol].flags & REPLACE_SFLAG) { /* The symbol type will be set to ROUTINE_T once the replaced version has been given; if it is already set, we must be dealing with a replacement, and we can use the routine name as-is. Otherwise we look for a rename. And if that doesn't work, we fall back to an artificial identifier. */ - if (stypes[routine_symbol] == ROUTINE_T) + if (symbols[routine_symbol].type == ROUTINE_T) { /* Optional because there may be further replacements. */ write_debug_optional_identifier(routine_symbol); } else if (find_symbol_replacement(&routine_symbol)) { debug_file_printf - ("%s", symbs[routine_symbol]); + ("%s", symbols[routine_symbol].name); } else { debug_file_printf @@ -1721,7 +2052,7 @@ void assemble_routine_end(int embedded_flag, debug_locations locations) debug_file_printf ("%d", zmachine_pc - routine_start_pc); write_debug_locations(locations); - for (i = 1; i <= routine_locals; ++i) + for (i = 1; i <= no_locals; ++i) { debug_file_printf(""); debug_file_printf("%s", variable_name(i)); if (glulx_mode) @@ -1737,67 +2068,61 @@ void assemble_routine_end(int embedded_flag, debug_locations locations) { debug_file_printf(""); debug_file_printf("
"); write_debug_code_backpatch - (label_offsets[sequence_point_labels[i]]); + (labels[sequence_points[i].label].offset); debug_file_printf("
"); - write_debug_location(sequence_point_locations[i]); + write_debug_location(sequence_points[i].location); debug_file_printf("
"); } debug_file_printf(""); } - my_free(&routine_name, "temporary copy of routine name"); - /* Issue warnings about any local variables not used in the routine. */ - for (i=1; i<=routine_locals; i++) - if (!(variable_usage[i])) + for (i=1; i<=no_locals; i++) + if (!(variables[i].usage)) dbnu_warning("Local variable", variable_name(i), routine_starts_line); for (i=0; i= 4) - printf("To label %d, which is %d from here\n", - j, label_offsets[j]-pc); - if ((label_offsets[j] >= pc+2) && (label_offsets[j] < pc+64)) - { if (asm_trace_level >= 4) printf("Short form\n"); + printf("...To label %d, which is %d from here\n", + j, labels[j].offset-pc); + if ((labels[j].offset >= pc+2) && (labels[j].offset < pc+64)) + { if (asm_trace_level >= 4) printf("...Using short form\n"); + zcode_markers[i+1] = DELETED_MV; + } + } + else if (zcode_markers[i] == LABEL_MV) + { + if (asm_trace_level >= 4) + printf("Jump detected at offset %04x\n", pc); + j = (256*zcode_holding_area[i] + zcode_holding_area[i+1]) & 0x7fff; + if (asm_trace_level >= 4) + printf("...To label %d, which is %d from here\n", + j, labels[j].offset-pc); + if (labels[j].offset-pc == 2 && i >= 1 && zcode_holding_area[i-1] == opcodes_table_z[jump_zc].code+128) { + if (asm_trace_level >= 4) printf("...Deleting jump\n"); + zcode_markers[i-1] = DELETED_MV; + zcode_markers[i] = DELETED_MV; zcode_markers[i+1] = DELETED_MV; } } @@ -1841,17 +2181,18 @@ static void transfer_routine_z(void) { printf("Opening label: %d\n", first_label); for (i=0;i %d previous -> %d\n", - i, label_offsets[i], label_next[i], label_prev[i]); + i, labels[i].offset, labels[i].next, labels[i].prev); } + /* label will advance through the linked list as pc increases. */ for (i=0, pc=adjusted_pc, new_pc=adjusted_pc, label = first_label; i= 4) printf("Position of L%d corrected from %04x to %04x\n", - label, label_offsets[label], new_pc); - label_offsets[label] = new_pc; - label = label_next[label]; + label, labels[label].offset, new_pc); + labels[label].offset = new_pc; + label = labels[label].next; } if (zcode_markers[i] != DELETED_MV) new_pc++; } @@ -1861,6 +2202,8 @@ static void transfer_routine_z(void) operands with offsets to those labels. Also issue markers, now that we know where they occur in the final Z-code area. */ + ensure_memory_list_available(&zcode_area_memlist, adjusted_pc+zcode_ha_size); + for (i=0, new_pc=adjusted_pc; i= 0 && labels[j].symbol < no_symbols) + lname = symbols[labels[j].symbol].name; + error_named("Attempt to jump to an unreachable label", lname); + addr = 0; + } + else { + addr = labels[j].offset - offset_of_next + 2; + } if (addr<-0x2000 || addr>0x1fff) - fatalerror("Branch out of range: divide the routine up?"); + error_fmt("Branch out of range: routine \"%s\" is too large", current_routine_name.data); if (addr<0) addr+=(int32) 0x10000L; addr=addr&0x3fff; @@ -1887,18 +2239,27 @@ static void transfer_routine_z(void) } zcode_holding_area[i] = branch_on_true + 0x40 + (addr&0x3f); } - transfer_byte(zcode_holding_area + i); new_pc++; + zcode_area[adjusted_pc++] = zcode_holding_area[i]; new_pc++; break; case LABEL_MV: j = 256*zcode_holding_area[i] + zcode_holding_area[i+1]; - addr = label_offsets[j] - new_pc; + if (labels[j].offset < 0) { + char *lname = "(anon)"; + if (labels[j].symbol >= 0 && labels[j].symbol < no_symbols) + lname = symbols[labels[j].symbol].name; + error_named("Attempt to jump to an unreachable label", lname); + addr = 0; + } + else { + addr = labels[j].offset - new_pc; + } if (addr<-0x8000 || addr>0x7fff) - fatalerror("Jump out of range: divide the routine up?"); + error_fmt("Jump out of range: routine \"%s\" is too large", current_routine_name.data); if (addr<0) addr += (int32) 0x10000L; zcode_holding_area[i] = addr/256; zcode_holding_area[i+1] = addr%256; - transfer_byte(zcode_holding_area + i); new_pc++; + zcode_area[adjusted_pc++] = zcode_holding_area[i]; new_pc++; break; case DELETED_MV: @@ -1907,11 +2268,12 @@ static void transfer_routine_z(void) default: switch(zcode_markers[i] & 0x7f) { case NULL_MV: break; + case ERROR_MV: break; case VARIABLE_MV: case OBJECT_MV: case ACTION_MV: case IDENT_MV: - if (!module_switch) break; + break; default: if ((zcode_markers[i] & 0x7f) > LARGEST_BPATCH_MV) { compiler_error("Illegal code backpatch value"); @@ -1920,20 +2282,26 @@ static void transfer_routine_z(void) break; } - write_byte_to_memory_block(&zcode_backpatch_table, - zcode_backpatch_size++, - zcode_markers[i] + 32*(new_pc/65536)); - write_byte_to_memory_block(&zcode_backpatch_table, - zcode_backpatch_size++, (new_pc/256)%256); - write_byte_to_memory_block(&zcode_backpatch_table, - zcode_backpatch_size++, new_pc%256); + if (bpatch_trace_setting >= 2) + printf("BP added: MV %d PC %04x\n", zcode_markers[i], new_pc); + + ensure_memory_list_available(&zcode_backpatch_table_memlist, zcode_backpatch_size+3); + zcode_backpatch_table[zcode_backpatch_size++] = zcode_markers[i] + 32*(new_pc/65536); + zcode_backpatch_table[zcode_backpatch_size++] = (new_pc/256)%256; + zcode_backpatch_table[zcode_backpatch_size++] = new_pc%256; break; } - transfer_byte(zcode_holding_area + i); new_pc++; + zcode_area[adjusted_pc++] = zcode_holding_area[i]; new_pc++; break; } } + /* Consistency check */ + if (new_pc - rstart_pc > zcode_ha_size || adjusted_pc != new_pc) + { + fatalerror("Optimisation increased routine length or failed to match; should not happen"); + } + if (asm_trace_level >= 3) { printf("After branch optimisation, routine length is %d bytes\n", new_pc - rstart_pc); @@ -1942,13 +2310,12 @@ static void transfer_routine_z(void) /* Insert null bytes if necessary to ensure the next routine address is */ /* expressible as a packed address */ - { uchar zero[1]; - zero[0] = 0; - if (oddeven_packing_switch) - while ((adjusted_pc%(scale_factor*2))!=0) transfer_byte(zero); - else - while ((adjusted_pc%scale_factor)!=0) transfer_byte(zero); - } + ensure_memory_list_available(&zcode_area_memlist, adjusted_pc+2*scale_factor); + + if (oddeven_packing_switch) + while ((adjusted_pc%(scale_factor*2))!=0) zcode_area[adjusted_pc++] = 0; + else + while ((adjusted_pc%scale_factor)!=0) zcode_area[adjusted_pc++] = 0; zmachine_pc = adjusted_pc; zcode_ha_size = 0; @@ -1957,7 +2324,7 @@ static void transfer_routine_z(void) static void transfer_routine_g(void) { int32 i, j, pc, new_pc, label, form_len, offset_of_next, addr, rstart_pc; - void (* transfer_byte)(uchar *); + int32 adjusted_pc; adjusted_pc = zmachine_pc - zcode_ha_size; rstart_pc = adjusted_pc; @@ -1966,12 +2333,12 @@ static void transfer_routine_g(void) (long int) adjusted_pc, zcode_ha_size, next_label); } - transfer_byte = - (temporary_files_switch)?transfer_to_temp_file:transfer_to_zcode_area; - /* (1) Scan through for branches and make short/long decisions in each case. Mark omitted bytes (bytes 2-4 in branches converted to - short form) with DELETED_MV. */ + short form) with DELETED_MV. + We also look for branches that can be entirely eliminated (because + they are jumping to the very next instruction). The opcode and + all label bytes get DELETED_MV. */ for (i=0, pc=adjusted_pc; i= BRANCH_MV && zcode_markers[i] < BRANCHMAX_MV) { @@ -1984,16 +2351,25 @@ static void transfer_routine_g(void) | (zcode_holding_area[i+2] << 8) | (zcode_holding_area[i+3])); offset_of_next = pc + 4; - addr = (label_offsets[j] - offset_of_next) + 2; + addr = (labels[j].offset - offset_of_next) + 2; + opmodebyte = i - ((opmodeoffset+1)/2); if (asm_trace_level >= 4) - printf("To label %d, which is (%d-2) = %d from here\n", - j, addr, label_offsets[j] - offset_of_next); - if (addr >= -0x80 && addr < 0x80) { + printf("...To label %d, which is (%d-2) = %d from here\n", + j, addr, labels[j].offset - offset_of_next); + if (addr == 2 && i >= 2 && opmodeoffset == 2 && zcode_holding_area[opmodebyte-1] == opcodes_table_g[jump_gc].code) { + if (asm_trace_level >= 4) printf("...Deleting branch\n"); + zcode_markers[i-2] = DELETED_MV; + zcode_markers[i-1] = DELETED_MV; + zcode_markers[i] = DELETED_MV; + zcode_markers[i+1] = DELETED_MV; + zcode_markers[i+2] = DELETED_MV; + zcode_markers[i+3] = DELETED_MV; + } + else if (addr >= -0x80 && addr < 0x80) { if (asm_trace_level >= 4) printf("...Byte form\n"); zcode_markers[i+1] = DELETED_MV; zcode_markers[i+2] = DELETED_MV; zcode_markers[i+3] = DELETED_MV; - opmodebyte = i - ((opmodeoffset+1)/2); if ((opmodeoffset & 1) == 0) zcode_holding_area[opmodebyte] = (zcode_holding_area[opmodebyte] & 0xF0) | 0x01; @@ -2005,7 +2381,6 @@ static void transfer_routine_g(void) if (asm_trace_level >= 4) printf("...Short form\n"); zcode_markers[i+2] = DELETED_MV; zcode_markers[i+3] = DELETED_MV; - opmodebyte = i - ((opmodeoffset+1)/2); if ((opmodeoffset & 1) == 0) zcode_holding_area[opmodebyte] = (zcode_holding_area[opmodebyte] & 0xF0) | 0x02; @@ -2028,18 +2403,19 @@ static void transfer_routine_g(void) printf("Opening label: %d\n", first_label); for (i=0;i %d previous -> %d\n", - i, label_offsets[i], label_next[i], label_prev[i]); + i, labels[i].offset, labels[i].next, labels[i].prev); } + /* label will advance through the linked list as pc increases. */ for (i=0, pc=adjusted_pc, new_pc=adjusted_pc, label = first_label; i= 4) printf("Position of L%d corrected from %04x to %04x\n", - label, label_offsets[label], new_pc); - label_offsets[label] = new_pc; - label = label_next[label]; + label, labels[label].offset, new_pc); + labels[label].offset = new_pc; + label = labels[label].next; } if (zcode_markers[i] != DELETED_MV) new_pc++; } @@ -2049,6 +2425,8 @@ static void transfer_routine_g(void) operands with offsets to those labels. Also issue markers, now that we know where they occur in the final Z-code area. */ + ensure_memory_list_available(&zcode_area_memlist, adjusted_pc+zcode_ha_size); + for (i=0, new_pc=adjusted_pc; i= BRANCH_MV && zcode_markers[i] < BRANCHMAX_MV) { @@ -2070,7 +2448,16 @@ static void transfer_routine_g(void) after it. */ offset_of_next = new_pc + form_len; - addr = (label_offsets[j] - offset_of_next) + 2; + if (labels[j].offset < 0) { + char *lname = "(anon)"; + if (labels[j].symbol >= 0 && labels[j].symbol < no_symbols) + lname = symbols[labels[j].symbol].name; + error_named("Attempt to jump to an unreachable label", lname); + addr = 0; + } + else { + addr = (labels[j].offset - offset_of_next) + 2; + } if (asm_trace_level >= 4) { printf("Branch at offset %04x: %04x (%s)\n", new_pc, addr, ((form_len == 1) ? "byte" : @@ -2095,7 +2482,7 @@ static void transfer_routine_g(void) zcode_holding_area[i+2] = (addr >> 8) & 0xFF; zcode_holding_area[i+3] = (addr) & 0xFF; } - transfer_byte(zcode_holding_area + i); new_pc++; + zcode_area[adjusted_pc++] = zcode_holding_area[i]; new_pc++; } else if (zcode_markers[i] == LABEL_MV) { error("*** No LABEL opcodes in Glulx ***"); @@ -2107,9 +2494,11 @@ static void transfer_routine_g(void) switch(zcode_markers[i] & 0x7f) { case NULL_MV: break; + case ERROR_MV: + break; case ACTION_MV: case IDENT_MV: - if (!module_switch) break; + break; case OBJECT_MV: case VARIABLE_MV: default: @@ -2124,26 +2513,27 @@ static void transfer_routine_g(void) Then a byte indicating the data size to be patched (1, 2, 4). Then the four-byte address (new_pc). */ - write_byte_to_memory_block(&zcode_backpatch_table, - zcode_backpatch_size++, - zcode_markers[i]); - write_byte_to_memory_block(&zcode_backpatch_table, - zcode_backpatch_size++, - 4); - write_byte_to_memory_block(&zcode_backpatch_table, - zcode_backpatch_size++, ((new_pc >> 24) & 0xFF)); - write_byte_to_memory_block(&zcode_backpatch_table, - zcode_backpatch_size++, ((new_pc >> 16) & 0xFF)); - write_byte_to_memory_block(&zcode_backpatch_table, - zcode_backpatch_size++, ((new_pc >> 8) & 0xFF)); - write_byte_to_memory_block(&zcode_backpatch_table, - zcode_backpatch_size++, (new_pc & 0xFF)); + if (bpatch_trace_setting >= 2) + printf("BP added: MV %d size %d PC %04x\n", zcode_markers[i], 4, new_pc); + ensure_memory_list_available(&zcode_backpatch_table_memlist, zcode_backpatch_size+6); + zcode_backpatch_table[zcode_backpatch_size++] = zcode_markers[i]; + zcode_backpatch_table[zcode_backpatch_size++] = 4; + zcode_backpatch_table[zcode_backpatch_size++] = ((new_pc >> 24) & 0xFF); + zcode_backpatch_table[zcode_backpatch_size++] = ((new_pc >> 16) & 0xFF); + zcode_backpatch_table[zcode_backpatch_size++] = ((new_pc >> 8) & 0xFF); + zcode_backpatch_table[zcode_backpatch_size++] = (new_pc & 0xFF); break; } - transfer_byte(zcode_holding_area + i); new_pc++; + zcode_area[adjusted_pc++] = zcode_holding_area[i]; new_pc++; } } + /* Consistency check */ + if (new_pc - rstart_pc > zcode_ha_size || adjusted_pc != new_pc) + { + fatalerror("Optimisation increased routine length or failed to match; should not happen"); + } + if (asm_trace_level >= 3) { printf("After branch optimisation, routine length is %d bytes\n", new_pc - rstart_pc); @@ -2213,7 +2603,22 @@ void assemblez_1_to(int internal_number, void assemblez_1_branch(int internal_number, assembly_operand o1, int label, int flag) -{ AI.internal_number = internal_number; +{ + /* Some clever optimizations first. A constant is always or never equal + to zero. */ + if (o1.marker == 0 && is_constant_ot(o1.type)) { + if (internal_number == jz_zc) { + if ((flag && o1.value == 0) || (!flag && o1.value != 0)) { + assemblez_jump(label); + return; + } + else { + /* assemble nothing at all! */ + return; + } + } + } + AI.internal_number = internal_number; AI.operand_count = 1; AI.operand[0] = o1; AI.branch_label_number = label; @@ -2563,9 +2968,6 @@ void assembleg_1_branch(int internal_number, if ((internal_number == jz_gc && o1.value == 0) || (internal_number == jnz_gc && o1.value != 0)) { assembleg_0_branch(jump_gc, label); - /* We clear the "can't reach statement" flag here, - so that "if (1)" doesn't produce that warning. */ - execution_never_reaches_here = 0; return; } if ((internal_number == jz_gc && o1.value != 0) @@ -2670,7 +3072,9 @@ static assembly_operand parse_operand_z(void) } static void parse_assembly_z(void) -{ int n, min, max, indirect_addressed, error_flag = FALSE; +{ int n, min, max; + int indirect_addressed, jumplabel_args; + int error_flag = FALSE; opcodez O; AI.operand_count = 0; @@ -2716,8 +3120,7 @@ static void parse_assembly_z(void) if (i>0) token_text[i-1] = ':'; if (n==-1) - { ebf_error("Expected 0OP, 1OP, 2OP, VAR, EXT, VAR_LONG or EXT_LONG", - token_text); + { ebf_curtoken_error("Expected 0OP, 1OP, 2OP, VAR, EXT, VAR_LONG or EXT_LONG"); n = EXT; } custom_opcode_z.no = n; @@ -2733,10 +3136,9 @@ static void parse_assembly_z(void) case TWO: max = 32; break; } if ((custom_opcode_z.code < min) || (custom_opcode_z.code >= max)) - { char range[32]; - sprintf(range, "%d to %d", min, max-1); - error_named("For this operand type, opcode number must be in range", - range); + { + error_fmt("For this operand type, opcode number must be in range %d to %d", + min, max-1); custom_opcode_z.code = min; } } @@ -2757,9 +3159,74 @@ T (text), I (indirect addressing), F** (set this Flags 2 bit)"); } O = custom_opcode_z; } + else if ((token_type == SEP_TT) && (token_value == ARROW_SEP || token_value == DARROW_SEP)) + { + int32 start_pc = zcode_ha_size; + int bytecount = 0; + int isword = (token_value == DARROW_SEP); + while (1) { + assembly_operand AO; + /* This isn't the start of a statement, but it's safe to + release token texts anyway. */ + release_token_texts(); + get_next_token(); + if ((token_type == SEP_TT) && (token_value == SEMICOLON_SEP)) break; + put_token_back(); + AO = parse_expression(ARRAY_CONTEXT); + if (AO.marker == ERROR_MV) { + break; + } + if (!isword) { + if (AO.marker != 0) + error("Entries in code byte arrays must be known constants"); + if (AO.value >= 256) + warning("Entry in code byte array not in range 0 to 255"); + } + if (execution_never_reaches_here) { + continue; + } + if (bytecount == 0 && asm_trace_level > 0) { + printf("%5d +%05lx %3s %-12s", ErrorReport.line_number, + ((long int) zmachine_pc), " ", + isword?"":""); + } + if (!isword) { + byteout((AO.value & 0xFF), 0); + bytecount++; + if (asm_trace_level > 0) { + printf(" %02x", (AO.value & 0xFF)); + } + } + else { + byteout(((AO.value >> 8) & 0xFF), AO.marker); + byteout((AO.value & 0xFF), 0); + bytecount += 2; + if (asm_trace_level > 0) { + printf(" "); + print_operand(&AO, TRUE); + } + } + } + if (bytecount > 0 && asm_trace_level > 0) { + printf("\n"); + } + if (asm_trace_level>=2) + { + int j; + for (j=0;start_pc' destination not 'sp' or a variable:", token_text); - else n = svals[token_value]; + else n = symbols[token_value].value; } } AI.store_variable_number = n; @@ -2837,7 +3307,7 @@ T (text), I (indirect addressing), F** (set this Flags 2 bit)"); n = parse_label(); } else - ebf_error("label name after '?' or '?~'", token_text); + ebf_curtoken_error("label name after '?' or '?~'"); } AI.branch_label_number = n; continue; @@ -2856,10 +3326,16 @@ T (text), I (indirect addressing), F** (set this Flags 2 bit)"); AI.operand[AI.operand_count++] = parse_operand_z(); get_next_token(); if (!((token_type == SEP_TT) && (token_value == CLOSE_SQUARE_SEP))) - { ebf_error("']'", token_text); + { ebf_curtoken_error("']'"); put_token_back(); } } + else if (jumplabel_args) + { assembly_operand AO; + put_token_back(); + INITAOTV(&AO, LONG_CONSTANT_OT, parse_label()); + AI.operand[AI.operand_count++] = AO; + } else { put_token_back(); AI.operand[AI.operand_count++] = parse_operand_z(); @@ -2956,150 +3432,218 @@ static assembly_operand parse_operand_g(void) static void parse_assembly_g(void) { - opcodeg O; - assembly_operand AO; - int error_flag = FALSE, is_macro = FALSE; + opcodeg O; + assembly_operand AO; + int error_flag = FALSE, is_macro = FALSE; - AI.operand_count = 0; + AI.operand_count = 0; + AI.text = NULL; - opcode_names.enabled = TRUE; - opcode_macros.enabled = TRUE; - get_next_token(); - opcode_names.enabled = FALSE; - opcode_macros.enabled = FALSE; + opcode_names.enabled = TRUE; + opcode_macros.enabled = TRUE; + get_next_token(); + opcode_names.enabled = FALSE; + opcode_macros.enabled = FALSE; - if (token_type == DQ_TT) { - char *cx; - int badflags; + if (token_type == DQ_TT) { + char *cx; + int badflags; - AI.internal_number = -1; + AI.internal_number = -1; - /* The format is @"FlagsCount:Code". Flags (which are optional) - can include "S" for store, "SS" for two stores, "B" for branch - format, "R" if execution never continues after the opcode. The - Count is the number of arguments (currently limited to 0-9), - and the Code is a decimal integer representing the opcode - number. + /* The format is @"FlagsCount:Code". Flags (which are optional) + can include "S" for store, "SS" for two stores, "B" for branch + format, "R" if execution never continues after the opcode. The + Count is the number of arguments (currently limited to 0-9), + and the Code is a decimal integer representing the opcode + number. - So: @"S3:123" for a three-argument opcode (load, load, store) - whose opcode number is (decimal) 123. Or: @"2:234" for a - two-argument opcode (load, load) whose number is 234. */ + So: @"S3:123" for a three-argument opcode (load, load, store) + whose opcode number is (decimal) 123. Or: @"2:234" for a + two-argument opcode (load, load) whose number is 234. */ - custom_opcode_g.name = (uchar *) token_text; - custom_opcode_g.flags = 0; - custom_opcode_g.op_rules = 0; - custom_opcode_g.no = 0; + custom_opcode_g.name = (uchar *) token_text; + custom_opcode_g.flags = 0; + custom_opcode_g.op_rules = 0; + custom_opcode_g.no = 0; - badflags = FALSE; + badflags = FALSE; - for (cx = token_text; *cx && *cx != ':'; cx++) { - if (badflags) - continue; + for (cx = token_text; *cx && *cx != ':'; cx++) { + if (badflags) + continue; - switch (*cx) { - case 'S': - if (custom_opcode_g.flags & St) - custom_opcode_g.flags |= St2; - else - custom_opcode_g.flags |= St; - break; - case 'B': - custom_opcode_g.flags |= Br; - break; - case 'R': - custom_opcode_g.flags |= Rf; - break; - default: - if (isdigit(*cx)) { - custom_opcode_g.no = (*cx) - '0'; - break; - } - badflags = TRUE; - error("Unknown custom opcode flag: options are B (branch), \ + switch (*cx) { + case 'S': + if (custom_opcode_g.flags & St) + custom_opcode_g.flags |= St2; + else + custom_opcode_g.flags |= St; + break; + case 'B': + custom_opcode_g.flags |= Br; + break; + case 'R': + custom_opcode_g.flags |= Rf; + break; + default: + if (isdigit(*cx)) { + custom_opcode_g.no = (*cx) - '0'; + break; + } + badflags = TRUE; + error("Unknown custom opcode flag: options are B (branch), \ S (store), SS (two stores), R (execution never continues)"); - break; - } - } + break; + } + } - if (*cx != ':') { - error("Custom opcode must have colon"); - } - else { - cx++; - if (!(*cx)) - error("Custom opcode must have colon followed by opcode number"); - else - custom_opcode_g.code = atoi(cx); - } + if (*cx != ':') { + error("Custom opcode must have colon"); + } + else { + cx++; + if (!(*cx)) + error("Custom opcode must have colon followed by opcode number"); + else + custom_opcode_g.code = atoi(cx); + } - O = custom_opcode_g; - } - else { - if (token_type != OPCODE_NAME_TT && token_type != OPCODE_MACRO_TT) { - ebf_error("an opcode name", token_text); - panic_mode_error_recovery(); - return; + O = custom_opcode_g; } - AI.internal_number = token_value; - if (token_type == OPCODE_MACRO_TT) { - O = internal_number_to_opmacro_g(AI.internal_number); - is_macro = TRUE; + else if ((token_type == SEP_TT) && (token_value == ARROW_SEP || token_value == DARROW_SEP)) + { + int32 start_pc = zcode_ha_size; + int bytecount = 0; + int isword = (token_value == DARROW_SEP); + while (1) { + assembly_operand AO; + /* This isn't the start of a statement, but it's safe to + release token texts anyway. */ + release_token_texts(); + get_next_token(); + if ((token_type == SEP_TT) && (token_value == SEMICOLON_SEP)) break; + put_token_back(); + AO = parse_expression(ARRAY_CONTEXT); + if (AO.marker == ERROR_MV) { + break; + } + if (!isword) { + if (AO.marker != 0) + error("Entries in code byte arrays must be known constants"); + if (AO.value >= 256) + warning("Entry in code byte array not in range 0 to 255"); + } + if (execution_never_reaches_here) { + continue; + } + if (bytecount == 0 && asm_trace_level > 0) { + printf("%5d +%05lx %3s %-12s", ErrorReport.line_number, + ((long int) zmachine_pc), " ", + isword?"":""); + } + if (!isword) { + byteout((AO.value & 0xFF), 0); + bytecount++; + if (asm_trace_level > 0) { + printf(" %02x", (AO.value & 0xFF)); + } + } + else { + byteout(((AO.value >> 24) & 0xFF), AO.marker); + byteout(((AO.value >> 16) & 0xFF), 0); + byteout(((AO.value >> 8) & 0xFF), 0); + byteout((AO.value & 0xFF), 0); + bytecount += 4; + if (asm_trace_level > 0) { + printf(" "); + print_operand(&AO, TRUE); + } + } + } + if (bytecount > 0 && asm_trace_level > 0) { + printf("\n"); + } + if (asm_trace_level>=2) + { + int j; + for (j=0;start_pc