The classic 8086 processor from Intel has about 113 instructions, depending on how you group them together. One would think all instructions are computational, but in fact some instructions interact with other parts of the computer to do something. Thus, the consequences of executing them is not directly evident from the code. This also means that if we avoid using them in a function, we can actually know that our function will run the same every time it is run. When using one of the non-computational ones, we cannot always guarantee that.
See if you know all the non-computational instructions, and see below for comments on them.
HLT IN OUT CLI STI INT INTO IRET POPF*
What do the non-computational instructions do?
The HLT instruction asks the processor to halt. It stops the processor and waits for some external event to wake it up again, for example, the timer interrupt. Trigger this instruction if there is no more work to do until something happens.
The IN instruction reads from one of the ports on the CPU itself. The value it returns will depend on the value available for reading from the device connected to the port. Conversely, the OUT instruction sends data to a port on the CPU itself. What will happen as a result of sending this data depends on the device connected on the other side.
The IN and OUT instruction serves the role of making the instruction set open-ended. Connect devices to the ports of the CPU to give the computer more features. For example, an OUT instruction might cause an instruction on a disk device to be executed, even though the 8086 has no disk instructions.
The CLI instruction seems to be computational, as it only sets a bit of the flag register to 0 (the interrupt bit). However, this can have far reaching consequences as the interrupt system can no longer trigger an interrupt. Conversely, the STI instruction sets a bit of the flag register to 1 (the interrupt bit). This means that interrupts from components outside the CPU now can influence what happens on the CPU.
The rest of the instructions, INT, INTO, IRET and POPF also modify the interrupt bit in the flags register, meaning they can have the same consequences as CLI and STI. Regarding POPF, it is mostly used as computational instructions: One often first saves the flags using PUSHF and then restore them later using POPF. When used in this way, POPF is computational.
The INT instruction is similar to a call, and a common use case is to call the BIOS and the Operating System, both of which would ultimately call an IN or OUT instruction to interact with another device, such as a disk device or the screen.
Another term of the non-computational instruction might be infrastructural, as they all influence some part of the computer outside the CPU.
Here are the computational instructions. With these, one can create any computation we like, of course. Use them to create code that can be run again and again, always producing the same result.
AAA/AAD/AAM/AAS ADD/ADC AND CALL CBW CLC CLD CMC CMP CMPSB/CMPSW CWD DAA/DAS DEC DIV/IDIV IMUL INC JA/JAE/JB/JBE/JC/JCXZ/JE/JG/JGE/JL/JLE/JMP/JNA/JNAE/JNB/JNBE JNC/JNE/JNG/JNGE/JNL/JNLE/JNO/JNP/JNS/JNZ/JO/JP/JPE/JPO/JS/JZ LAHF LDS LEA LES LODSB/LODSW LOOP/LOOPE/LOOPNE/LOOPNZ/LOOPZ MOV MOVSB/MOVSW MUL NEG NOP NOT OR POP PUSH/PUSHF RCL RCR REP/REPE/REPNE/REPNZ/REPZ RET/RETF ROL ROR SAHF* SAL SAR SBB SCASB/SCASW SHL SHR STC STD STOSB/STOSW SUB TEST XCHG XLATB XOR
Many of these instructions have inputs that are not given by the programmer directly. For example, the ADC instruction takes the carry bit in the flags register and adds it as well in the addition. Many also have outputs not explicitly received by the programmer, for example the ADD instruction, sets the carry bit of the flags register if the addition overflows. Implicit inputs and outputs like these are still inputs and outputs.
With memory mapped IO, a device might write directly to memory. This means that a MOV instruction might give different outputs when run with the same inputs. This must be regarded as separate from the MOV instruction.
Some of the mnemonics do not represent instructions, these are REP, REPE, REPNE, REPNZ and REPZ. They are prefixes to other instructions.
What about exceptions? If we run the DIV instruction with a divisor of 0, it will cause a hardware exception, and an interrupt is triggered. If we follow the principle that we should not use exceptions unless it is to detect programmer mistakes, then this is less of an issue. We can regard such exceptions as canceling our program.
What about the CALL instruction, can't it end up invoking one of the non-computational insturctions? It can, and in those cases, the call itself should be regarded as non-computational instruction where it occurs.
Note (*) that SHAF only modifies SF, ZF, AF, PF, and CF. It does not mofidy the interrupt flag, which would make it non-computational.
Why is analyzing instructions as computational and non-computational useful? There are several reasons. One reason is that we can analyze what a function is supposed to do, and then explicitly avoid the non-computational instructions if they are not needed. This means that the function is more testable and reusable, as it working is less dependent on outside factors.
Another reason is that we can turn a function into a pure computation for the purposes of testing. If a function does include non-computational instructions, we can, for the purpose of testing, replace the instructions with calls to functions. These functions can then serve as mocks. Thus, we can use these mocks to control the non-computational instructions directly each time the function is run for testing.
We would be more than happy to help you. Our opening hours are 9–15 (CET).
📞 (+47) 93 68 22 77
Nils Bays vei 50, 0876 Oslo, Norway