diff --git a/fec/src/emitcm7.c b/fec/src/emitcm7.c index 60d16cf..c91b96c 100644 --- a/fec/src/emitcm7.c +++ b/fec/src/emitcm7.c @@ -670,6 +670,26 @@ static void m7_emit_call(FeEmitter *e, FeNode *n) } } +/* `dst = ;` as a statement, avoiding a comma expression on the right. + + A consumed identifier lowers to `(fe_live_x=0, x)`. When dst is a struct, + Watcom crashes on a struct assignment whose right side is a comma expression + -- hard enough to take DOSBox-X down with it -- so clear the move flag as its + own statement and assign the plain name. */ +static void m7_emit_assign_stmt(FeEmitter *e, const char *dst, FeNode *src) +{ + if (src && src->kind==FE_N_IDENT && (src->flags & FE_OWN_NODE_CONSUMED) && + src->sem_type && type_needs_drop(src->sem_type)) { + pad(e); fputs("fe_live_",e->out); fputs(cname(src,"owned"),e->out); + fputs("=0;\n",e->out); + pad(e); fputs(dst,e->out); fputs(" = ",e->out); + fputs(cname(src,"fe_missing"),e->out); fputs(";\n",e->out); + return; + } + pad(e); fputs(dst,e->out); fputs(" = ",e->out); + emit_expr(e,src); fputs(";\n",e->out); +} + static void m7_emit_raw_expr(FeEmitter *e, FeNode *n) { FeNode *x; @@ -1208,6 +1228,32 @@ static void emit_stmt(FeEmitter *e, FeNode *n) fputs(";\n",e->out); emit_cleanup_all(e); pad(e); fputs("return fe_return_value;\n",e->out); + } else if (n->a && n->a->kind==FE_N_BINARY && !n->a->c && + fe_m7_lazy_kind(n->a)==FE_M7_LAZY_CATCH && + e->current_ret && e->current_ret->kind!=FE_TYPE_VOID) { + /* Short catch in return position. As an expression this lowers to + `((tmp = X), tmp.e ? fallback : tmp.v)`, and when X carries a move + it becomes a struct assignment whose right side is itself a comma + expression -- which crashes wcc386 hard enough to take DOSBox-X + down with it. The same lowering as statements is also plainer. */ + FeNode *cx=n->a; + FeType *res=cx->a ? cx->a->sem_type : 0; + int has_value=res && res->error_value && + res->error_value->kind!=FE_TYPE_VOID; + m7_emit_assign_stmt(e,cx->aux_cname,cx->a); + pad(e); fputs("if (",e->out); fputs(cx->aux_cname,e->out); + if (has_value) fputs(".e",e->out); + fputs(") {\n",e->out); ++e->indent; + pad(e); fputs("fe_return_value = ",e->out); + emit_expr(e,cx->b); fputs(";\n",e->out); + --e->indent; pad(e); fputs("} else {\n",e->out); ++e->indent; + pad(e); fputs("fe_return_value = ",e->out); + fputs(cx->aux_cname,e->out); + if (has_value) fputs(".v",e->out); + fputs(";\n",e->out); + --e->indent; pad(e); fputs("}\n",e->out); + emit_cleanup_all(e); + pad(e); fputs("return fe_return_value;\n",e->out); } else { if (n->a && e->current_ret && e->current_ret->kind!=FE_TYPE_VOID) { pad(e); fputs("fe_return_value = ",e->out); diff --git a/src/ferrolang_vm/dosboxx.py b/src/ferrolang_vm/dosboxx.py index 102b5b3..d6c8c75 100644 --- a/src/ferrolang_vm/dosboxx.py +++ b/src/ferrolang_vm/dosboxx.py @@ -158,7 +158,10 @@ def _batch(cases: list[Case], *, show_dos: bool, trace_dos: bool, build = "call BUILD.BAT" if trace_dos else "call BUILD.BAT > RESULTS\\BUILD.LOG" if prebuilt: # FEC.EXE was restored from cache; BUILD.BAT would delete and rebuild it. - build = "echo OK>BUILD.OK" + # It also puts the Watcom binaries on PATH, which the case commands need + # after it, so that line has to be reproduced rather than skipped. + build = ("set PATH=%WATCOM%\\BINW;%WATCOM%\\BINP;%PATH%\r\n" + "echo OK>BUILD.OK") lines = [ "@echo off", "if not exist RESULTS md RESULTS", "if not exist OUT md OUT", "set WATCOM=W:", "set INCLUDE=W:\\H", @@ -284,13 +287,13 @@ def run_suite(cases: list[Case], *, keep: bool = False, show_dos: bool = False, console = run_root / "CONSOLE.LOG" config = run_root / "DOSBOX.CON" config.write_text( - # core=auto falls back to the interpreter in real mode, which is - # where the 16-bit compiler build spends its time. Nothing here is - # timing sensitive -- it is a compiler and a batch file -- so ask for - # the recompiler and uncapped cycles explicitly. + # Do not tune [cpu] here. core=dynamic is roughly 5x faster but the + # recompiler loses abort()'s exit status -- a program that traps + # exits 0 instead, so the M3 bounds cases stop reporting the trap + # they exist to prove. Verified against a compiler built under + # core=normal, so it is the runtime and not the build. f"[log]\nlogfile={console}\n" - f"[dosbox]\nlog console=quiet\n" - f"[cpu]\ncore=dynamic\ncycles=max\n", + f"[dosbox]\nlog console=quiet\n", encoding="ascii", ) if cached.is_file():