if, else, endif, do … while

One comparison, one block – and the loop to go with it: without a label of your own and without writing the jump.

Instead of cmp, a conditional jump and a label you write what you mean: if ebx == [0x04+eax], the block and endif. The assembler writes the comparison and the jump of the opposite condition itself. It is no expression and no macro language: a direct comparison of two operands, and with < and > you say whether it is signed or unsigned.

For .arch i386 (32 bit), anywhere in the code and in a func too. The bytes come from the real assembler.

The idea

The block runs only if the comparison holds. The assembler skips it with the jump of the opposite condition: a jne for ==.

You write
        if ebx == [0x04+eax]
          mov dword [0x04+eax], 0xFFFFFFFF
        endif
        mov eax, [eax]
The same by hand, and the bytes
        cmp ebx, [0x04+eax]
        jne skip              ; the opposite condition
        mov dword [0x04+eax], 0xFFFFFFFF
skip:
        mov eax, [eax]
3B 58 04 75 07 C7 40 04 FF FF FF FF
8B 00
added by the assembler (if, else, do, while) your lines, unchanged

The assembler makes the label itself (asmogen_if…, it is not shown in the program). The debugger runs the if line, the comparison and the jump, as one step.

The form

if [signed|unsigned] operand comparison operand, the block, optionally else with a second block, and endif. The operands are everything cmp takes: registers, numbers, labels, memory in square brackets.

You writeGenerated and bytesNote
if eax == ebx
  nop
endif
  cmp eax, ebx
  jne skip
  nop
skip:
3B C3 75 01 90
equal: cmp, then jne over the block (75 01 skips the one byte of the block)
if eax != 5
  nop
endif
  cmp eax, 5
  je skip
  nop
skip:
83 F8 05 74 01 90
not equal: je over the block; the right side may be a number
if dword [0x1000] == 5
  nop
endif
  cmp dword [0x1000], 5
  jne skip
  nop
skip:
83 3D 00 10 00 00 05 75 01 90
memory against a number needs the size (byte, word, dword), as with cmp
if signed eax < ebx
  nop
endif
  cmp eax, ebx
  jge skip
  nop
skip:
3B C3 7D 01 90
an ordered comparison says signed or unsigned; signed < jumps over the block with jge
if unsigned eax >= [ebx]
  nop
endif
  cmp eax, [ebx]
  jb skip
  nop
skip:
3B 03 72 01 90
memory may stand on one side; unsigned >= jumps over the block with jb
added by the assembler (if, endif) your lines, unchanged

The comparisons are ==, !=, <, <=, > and >=. In a func the names of the parameters and locals work in the comparison too: if signed eax < b.

Signed or unsigned

The same bits are different in size depending on how you see them: 0xFFFFFFFF is -1 signed and 4294967295 unsigned. So -1 < 1 holds signed and not unsigned. Addresses, lengths and counters are mostly unsigned, numbers with a sign are not – so Asmonite wants the answer in every line (or with .compare, see below). == and != do not need it.

Comparisonsignedjump over the blockunsignedjump over the block
<if signed eax < ebxjge 7D 01if unsigned eax < ebxjae 73 01
<=if signed eax <= ebxjg 7F 01if unsigned eax <= ebxja 77 01
>if signed eax > ebxjle 7E 01if unsigned eax > ebxjbe 76 01
>=if signed eax >= ebxjl 7C 01if unsigned eax >= ebxjb 72 01

The jumps in the right columns are those of the opposite condition: they skip the block if the comparison does not hold. Signed they are jl, jle, jg, jge, unsigned jb, jbe, ja, jae.

Testing bits: &

With & instead of a comparison the if becomes test: the block runs if at least one bit is set in both operands. & is not a calculation here but the whole condition – one &, then at most == 0 or != 0. There is no signed or unsigned word: whether something is zero does not depend on the sign.

You writeThe block runs if …Generated
if eax & ebxa bit is set in bothtest eax, ebx + jz 85 D8 74 01
if eax & ebx != 0as above (another way to write it)test eax, ebx + jz 85 D8 74 01
if eax & ebx == 0no bit is set in bothtest eax, ebx + jnz 85 D8 75 01
if al & 0x80the top bit of al is settest al, 0x80 + jz A8 80 74 01
if byte [ebx] & 1the lowest bit of the byte at [ebx] is settest byte [ebx], 1 + jz F6 03 01 74 01

As with cmp, memory against a number needs the size (byte, word, dword). test has no "register against memory" form, only "memory against register"; for if eax & [ebx] Asmonite swaps the operands by itself (this makes no difference for test). else, endif, nesting and the names of a func work as with every if.

The flags: if zero

If the flags are there already (after dec, sub, add, and ...) there is no need to compare: if names the condition with a word and writes only the jump around the block. The words are those of the jump instructions: zero (also equal), carry, sign, overflow, parity, and above / below (unsigned) and less / greater (signed). not may stand in front, or equal behind the last four.

You writeThe block runs if ...Generated (with the instruction in front)
dec ecx
if zero
the result is 0dec ecx + jnz 49 75 01
sub eax, 5
if not zero
the result is not 0sub eax, 5 + jz 83 E8 05 74 01
add eax, ecx
if carry
there was a carryadd eax, ecx + jnc 03 C1 73 01
cmp eax, ebx
if below or equal
eax <= ebx, unsignedcmp + ja 3B C3 77 01
cmp eax, ebx
if not less
eax >= ebx, signedcmp + jl 3B C3 7C 01

Which flags count is decided by the instruction in front, not by if: if zero after a mov tests the flag of an earlier instruction. There is no signed or unsigned word here, above / below and less / greater say it themselves. else, endif and nesting work as with every if.

Loops: do ... while

The block between do and while runs once and then as long as the condition behind while holds. The condition has the same forms as with if (a comparison, a & b or a flag word); the jump goes back to the do. The assembler makes the label there itself (asmogen_do…).

You write
        do
          dec ecx
        while not zero
The same by hand, and the bytes
again:
        dec ecx
        jnz again             ; the condition itself, back
49 75 FD
With a comparison
        do
          inc eax
        while signed eax < 10
By hand, and the bytes
again:
        inc eax
        cmp eax, 10
        jl again
40 83 F8 0A 7C FA
added by the assembler (if, else, do, while) your lines, unchanged

With while the jump is the condition itself (with if its opposite): while not zero is jnz, while signed eax < 10 is jl. Ordered comparisons need signed or unsigned here too (or a .compare in front). Loops nest in each other and with if; they must lie cleanly inside each other (an endif in an open do is an error). In the debugger the while line, comparison and jump, is one step.

An example with printf is in the ZIP: examples\func\func_loop.asm (the sum of 1 to 10).

The default: .compare

If you write a whole piece with signed numbers you need not type signed every time: .compare signed (or .compare unsigned) holds for all if behind it, until the next .compare. .compare none takes it back. A word in the if line always wins.

You write
.compare signed
  if eax < ebx          ; signed (from .compare)
    nop
  endif
  if unsigned eax < ebx ; the word in the line wins
    nop
  endif
.compare none           ; back to: a word is required
The assembler produces
3B C3 7D 01 90 3B C3 73 01 90

Without .compare and without a word an ordered comparison is an error – there is no silent default. The directive is shown in the same blue as the keywords, so it stands out in the source.

else and nested blocks

else adds a jmp over the second block behind the first; an if may stand in an if, every endif closes the innermost open one.

else
  if eax != 0
    inc eax
  else
    dec eax
  endif
By hand, and the bytes
  cmp eax, 0
  je else1              ; if not: to else
  inc eax
  jmp end1              ; then part done
else1:
  dec eax
end1:
83 F8 00 74 03 40 EB 01 48
nested
  if eax == 1
    if ebx == 2
      nop
    endif
  endif
By hand, and the bytes
  cmp eax, 1
  jne end1
  cmp ebx, 2
  jne end2
  nop
end2:
end1:
83 F8 01 75 06 83 FB 02 75 01 90
added by the assembler (if, else, do, while) your lines, unchanged

In a func

The example from the ZIP: the larger of two parameters. The names a and b become [ebp+8] and [ebp+12] as usual in func.

You write
func Max(a, b)
begin
  mov eax, a
  if signed eax < b
    mov eax, b
  endif
end
The same by hand, and the bytes
Max:
  push ebp
  mov ebp, esp
  mov eax, [ebp+8]
  cmp eax, dword [ebp+12]
  jge skip
  mov eax, [ebp+12]
skip:
  pop ebp
  ret 8
55 8B EC 8B 45 08 3B 45 0C 7D 03 8B
45 0C 5D C2 08 00
added by func and if your line, the names are replaced

The end of the function writes pop ebp and ret 8; an open if in front of an end is an error.

Rules and errors

It is a comparison, not an expression: if eax+1 == ebx does not calculate, and there is no && or || (a single & is the bit test above). If you need more, write two if or the jumps yourself. The messages name the line of the source.

You writeMessageCase
if eax < ebx
  nop
endif
if eax < ebx: say whether it is signed or unsigned (if signed eax < ebx / if unsigned ..., or .compare signed / .compare unsigned before)an ordered comparison without a word
if eax = ebx
  nop
endif
use == to compare: if eax == ebxa single =
if eax == ebx == ecx
  nop
endif
only one comparison behind if, no expressionstwo comparisons
if eax == ebx
  nop
endif is missing for the if in line 1endif is missing
nop
endif
endif without ifendif without if
if eax == ebx
  nop
else
  nop
else
  nop
endif
a second else for the if in line 1a second else
while eax != 1
while without dowhile without do
do
  nop
while is missing for the do in line 1while is missing
do
  nop
endif
endif without if (the open block is the do in line 1, while is missing)an endif in an open do
if foo
  nop
endif
if needs a comparison between two operands: ==, !=, <, <=, > or >=, or a flag condition (zero, carry, ...)neither a comparison nor a flag word

For .arch i386, .arch x64, .arch arm64 and .arch thumb. In x64 the assembler still uses the long form for jumps forward (6 instead of 2 bytes). In ARM 64 and Thumb you write the operands in ARM notation (if w0 == 5, if r1 != r2; a number gets its # by itself); the jump is b.ne, b.lt (Thumb: bne, blt) and so on, in Thumb the assembler picks the wide form of the jump by itself when the block is too big, if a & b is tst, there are no memory operands (load first) and no parity. carry is the flag C (after a subtraction C = 1 means nothing was borrowed). A label in front of the line (L: if …) works.

Next

Functions

func writes the stack frame, call Name(…) pushes the arguments.

The func page →

The syntax

Numbers, expressions, addresses, jumps and data with their bytes.

The syntax page →

Try it

The ZIP has func_if.asm and func_loop.asm (folder examples\func); from the download to the first breakpoint in 60 seconds.

Quick start →