简体   繁体   中英

Are “functions” a fundamental part of the x86 hardware?

On the popular x86 architecture, execution flow can be controlled with one of the jmp -type instructions, as well as with the call instruction. But are both fundamental, or is one "syntactic sugar" for the other?

For example, the push instruction is syntactic sugar: push eax is equivalent to mov [esp], eax followed by sub esp, 4 . So mov is the only fundamental data operation.

But is this also the case for call ? Is there some other set of instructions that achieves the same?

In pseudo-code, I could write a function call with jmp if I had access to the instruction pointer:

bar:
    push eip    ;; pseudo-code!              ;; call foo
    jmp foo

foo:
    pop eax                                  ;; ret
    jmp [eax]

This isn't valid x86, since the instruction pointer register eip cannot be read directly. And all "tricks" I've seen so far that obtain the value of eip all use call . Or is call in fact the canonical way to read the instruction pointer on x86? As a bonus question, does every architecture on which C can be implemented require a way to read the instruction pointer, so that function pointers can be implemented?

int main(int argc, char * argv[])
{
    int (*p)() = strtoul(argv[1]);

    return p();                     // how to implement without
                                    // accessing the instruction pointer?
}

[This isn't a deep question; I would simply find it emotionally gratifying to know that functions are not just an abstraction introduced by high-level programming languages, but actually hardware-intrinsic.]

Machine instructions sets could be pretty trivial; see Wolfram's 2-state, 3 symbol Turing machine. The problem is that such machines are hard to program and hard to make run fast.

You can completely simulate call return with other instructions:

    ;  Simulate "call abc":
    push    offset next_location
    jmp     abc
 next_location:

    ; Simulate "ret"
    pop    eax
    jmp    eax

You can obviously simulate the push/pops with mov instructions and adjusting registers by adding constants as you noted in your question.

People add instructions to an instruction set to make common sequences efficient. Thus, "call" exists because it does a commonly useful task (and "call/ret" pairs can be optimized by hardware using an internal stack of return addresses to avoid instruction pipeline breaks; this is a real performance win).

So, yes, you could use a smallest set of instructions offered by a computer to achieve your assembly language purposes. But the right additional instructions really help in practice. (You can, with good intent, add instructions you think might be useful. Often they don't help, this is the source of the original RISC/CISC debate ).

Goofball minimal machines:

  • There was real minicomputer ("GRI") produced in the 1970s that had only one instruction: MOV LOC1 to LOC2; the target locations were magic and that's how real work got done. By moving something to location 33 (made up), a sideffect was that the value was added to an accumulator that could be read from location 32. It had lots of locations that did funny, convenient things, so in essence adding a new funny register is just a strange variant of adding useful instructions. I suspect JMP instructions consisted of moving a constant to the location that acted as the program counter. (If this seem strange to you, you haven't met a PDP-11).

  • A theory machine is one with the single instruction "SUB LOC1 from LOC2 jmp to LOC3 if negative". This machine is universal but not fun to program after your first toy problem.

It is interesting to note that both of these instructions sets have no opcode bits or addresssing mode bits. The instruction decoder is simple, indeed.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM