On Friday, July 30, 2021 at 12:13:00 AM UTC-5,
luser...@nospicedham.gmail.com wrote:
> I wrote this machine code to trace the LIT word in my forth interpreter.
> Is it possible to tighten up this code? It's trying to print a 16bit signed
> integer (ignoring INT_MIN) left to right with no leading zeros.
>
> This is assembly code, but I hope you'll forgive my ideosyncratic syntax.
> The operands map left to right to the reg and reg/mem fields into the
> mod-reg-reg/mem byte and the opcodes target the "to" form (direction
> field = 1), eg. in "MOV(,R,BX,AX)", BX is the dest, AX is the source, the
> R is needed to select register-to-register mode (the mod field). The empty
> first argument is for tweaks to the opcode which could be F (clear the
> direction field by subtracting 2) or BYTE (clear the word field by subtracting 1).
>
> It tests each digit for zero where it jumps over the int 10h call except
> the last digit.
>
> It occurs to me that the DIV instruction has a mod field. So that means
> the divisor could be register indirect, right? like through SI or BX, maybe?
> Then it could probably be rolled up in a loop, right? Sorry if I'm jumping
> the gun asking for help before exerting the requisite effort.
>
Thanks for the help, everyone! Thanks to Rudy for pointing out that my
algorithm was malformed. I had tested it (sort of, I ran it and saw that
there was output), but I didn't check it against known values and so
didn't see the problem of suppressing inner zeros. (the numbers it
printed did appear weird, but I didn't properly consider and valuate
that clue).
John's code is so sweet and simple, I am envious of the ability to write
in such a way. I translated it to my syntax and then tried a few of the
variations alluded to earlier, like using BX to point to a table of divisors.
But I haven't actually implemented the LOOP instruction in the emulator
and the syntax I have for using labels in machine code hasn't been
incorporated into the emulator at all yet. So I haven't tested any of the
following code except to run it through CPP to check that macro expansion
works.
First I just translated John's code to my syntax (now with labels) and
lo and behold, it's super short and sweet. Exactly what I asked for.
CODE(lit, (LIT, LODS, PUSH(AX))
#ifdef TRACE
, (PrtNum, TEST(R,AX,AX), JGE, NotNeg-_, \
PUSH(AX), MOVAX(0xE00+'-'), INT(10), POP(AX), NEG(R,AX)),
(NotNeg, MOVBX(10), XOR(,R,CX,CX)),
(NextDigit, INC(CX), XOR(DX,DX), DIV(BX), PUSH(DX), TEST(AX,AX), JNZ, NextDigit-_),
(PutDigit, POP(AX), ADDAX(0xE00+'0'), INT(10), LOOP, PutDigit-_)
#endif
)
First I tried doing it with a table of divisors and unrolled code to divide by each
word in the table.
CODE(lit, (LIT, LODS, PUSH(AX))
#ifdef TRACE
, (stub, sJMP, CODE-_),
(divisors, DW(10000), DW(1000), DW(100), DW(10)),
(CODE, MOVBX(divisors), XOR(,R,DX,DX),
DIV(Z,BX_), ADDAX(0xE00+'0'), INT(10)),
(L1, MOV(,R,AX,DX), DIV(B,BX_),2, ADDAX(0xE000+'0'), INT(10),
MOV(,R,AX,DX), DIV(B,BX_),4, ADDAX(0xE000+'0'), INT(10)),
(L2, MOV(,R,AX,DX), DIV(B,BX_),6, ADDAX(0xE000+'0'), INT(10),
MOD(,R,AX,DX), ADDAX(0xE00+'0'), INT(10))
#endif
)
Which is longer, uglier, replete with repetition.
Then I tried doing it with a table of divisors and a loop to run through left-to-right.
CODE(lit, (LIT, LODS, PUSH(AX))
#ifdef TRACE
, (tracelit, MOVBX(divisors), XOR(,R,DX,DX), MOVCX(5)),
(digit, DIV(Z,BX_), ADDAX(0xE00+'0'), INT(10),
MOV(,R,AX,DX), ADDI(BX,2), LOOP, digit-_,
NEXT)
(divisors, DW(10000), DW(1000), DW(100), DW(10), DW(1)),
#endif
)
where NEXT is a macro for jumping to the next Forth word, pointed to by SI.
That's pretty short, but it treats all numbers as unsigned and makes
no attempt to suppress leading zeros.
Then I tried without using a table and calling a procedure for each digit.
CODE(lit, (LIT, LODS, PUSH(AX))
#ifdef TRACE
, (tracelit, XOR(,R,DX,DX), MOVCX(5),
TEST(R,AX,AX), JGE,notneg,
MOVI(AX,0xE00+'-'), INT(10), NEG(R,AX), JNO, notneg,
INC(R,DX)),
(notneg, MOVI(BX,10000), DIV(R,BX), CALL, print,
MOVI(BX,1000), MOV(,R,AX,DX), DIV(R,BX), CALL, print,
MOVI(BX,100), MOV(,R,AX,DX), DIV(R,BX), CALL, print,
MOVI(BX,10), MOV(,R,AX,DX), DIV(R,BX), CALL, print,
MOV(,R,AX,DX), CALL, print,
NEXT),
(print, ADDAX(0xE00'0'), INT(10), RET)
#endif
)
It's pretty cool that adding labels also lets me have local procedures,
but I feel like the call instruction vs, the ADD and INT instructions
doesn't really save much and is slower and longer on the screen.
But maybe all that just means that I should be using a DOS string
printing function instead of a character printing function.
And also, Rod was right and this whole effort was a wholy
unnecessary X/Y problem. And if I just add a high-level LIT word,
then I can call U. to print the number be done with this snipe hunt.
On a side note, I stumbled across the source on github for DOS 1.25
and 2.0 and I'm browsing ASM.ASM from 1.25 to get ideas about how to
make an assembler with a more common syntax.