Closed saper closed 9 years ago
Thank you for improving the quality of machine's test suite!
I see that machine's ability to defend itself from neverending loops should be enhanced. It is able to detect some instruction-level loops, but is currently unable to find those caused by the fallthrough behaviour (as demonstrated by neverending0.asm
). Good that you've already written a test for that.
I made something similar to neverending0.asm
by accident in my first code I wrote and it hanged. I am not sure if a function without end
is legal, but it seems it kind of works. Would be interesting to check what happens with the parameters...
Functions without an end
instruction are legal. The fallthrough behaviour is there by design.
However, compiler can warn you about such functions as this functionality can be potentially dangerous.
Compile with --Wmissing-end
to tell the compiler to issue warnings when it finds functions without end
at the end. Compile with --Emissing-end
flag to make this warning fatal.
Alternatively, use --Wall
to turn on all warnings, and use --Eall
to make all warnings fatal.
As to what happens with the parameters: they are retained for as long as the frame is not popped from the stack.
To call a function you must create a frame, which frame is then used to store local registers and arguments. Until the frame is popped from the stack (and, effectively, destroyed) the objects held in its registers are present in memory. Fallthrough behaviour does not cause memory leaks and is generally safe to use, apart from the infinite loop scenario.
You can launch the neverending0.asm
program in debugger to see that every call generates new frame.
./build/bin/vm/asm sample/asm/functions/neverending0.asm
./build/bin/vm/vdb a.out
Commands above will let you launch a program in debugger. Use command below while inside debugger shell to inspect running program (each command is shown in monospace
, commands are sent to debugger after Enter is hit):
cpu.init
to initialise the CPU,cpu.tick 100
to execute 100 instructions,trace
to display stack trace (and see that every call has it's own frame),^D
to quit the debugger,Your output should look like this (I used cpu.tick 40
for shorter log):
message: running "a.out"
bytecode size: 101
>>> cpu.init
>>> cpu.tick 40
byte 51 (0x33) at 0x2206313: ress global
byte 56 (0x38) at 0x2206318: frame 1 16
byte 67 (0x43) at 0x2206323: paref 0 1
byte 78 (0x4e) at 0x220632e: call 1 main
byte 23 (0x17) at 0x22062f7: frame 0 2
byte 34 (0x22) at 0x2206302: call 0 one
byte 0 (0x0) at 0x22062e0: istore 1 42
byte 11 (0xb) at 0x22062eb: print 1
42
byte 17 (0x11) at 0x22062f1: izero 0
byte 23 (0x17) at 0x22062f7: frame 0 2
byte 34 (0x22) at 0x2206302: call 0 one
byte 0 (0x0) at 0x22062e0: istore 1 42
byte 11 (0xb) at 0x22062eb: print 1
42
byte 17 (0x11) at 0x22062f1: izero 0
byte 23 (0x17) at 0x22062f7: frame 0 2
byte 34 (0x22) at 0x2206302: call 0 one
byte 0 (0x0) at 0x22062e0: istore 1 42
byte 11 (0xb) at 0x22062eb: print 1
42
byte 17 (0x11) at 0x22062f1: izero 0
byte 23 (0x17) at 0x22062f7: frame 0 2
byte 34 (0x22) at 0x2206302: call 0 one
byte 0 (0x0) at 0x22062e0: istore 1 42
byte 11 (0xb) at 0x22062eb: print 1
42
byte 17 (0x11) at 0x22062f1: izero 0
byte 23 (0x17) at 0x22062f7: frame 0 2
byte 34 (0x22) at 0x2206302: call 0 one
byte 0 (0x0) at 0x22062e0: istore 1 42
byte 11 (0xb) at 0x22062eb: print 1
42
byte 17 (0x11) at 0x22062f1: izero 0
byte 23 (0x17) at 0x22062f7: frame 0 2
byte 34 (0x22) at 0x2206302: call 0 one
byte 0 (0x0) at 0x22062e0: istore 1 42
byte 11 (0xb) at 0x22062eb: print 1
42
byte 17 (0x11) at 0x22062f1: izero 0
byte 23 (0x17) at 0x22062f7: frame 0 2
byte 34 (0x22) at 0x2206302: call 0 one
byte 0 (0x0) at 0x22062e0: istore 1 42
byte 11 (0xb) at 0x22062eb: print 1
42
byte 17 (0x11) at 0x22062f1: izero 0
byte 23 (0x17) at 0x22062f7: frame 0 2
>>> trace
__entry/0(): frame at 0x2206d60
main/1(["a.out"]): frame at 0x2208110
one/0(): frame at 0x2207fc0
one/0(): frame at 0x22084f0
one/0(): frame at 0x22081f0
one/0(): frame at 0x22084c0
one/0(): frame at 0x2209170
one/0(): frame at 0x2209750
one/0(): frame at 0x2209640
>>>
You can use help
in debugger to get a list of available commands.
Ok! This should maybe go into the docs:)
one format question: you wrote "see that every call generates new frame." - during call
nothing is changed, only frame
instruction generates new frame, correct?
Seems like you are already pretty familiar with the machine! You're right, only frame
instructions create new frames. The "see that every call generates new frame." was only a language shortcut.
One thing should be clarified though - the call
instructions does in fact change something (besides the execution pointer, of course).
It must be noted that frame
instructions create new frames in a temporary frame slot. It is the call
instruction that actually pushes frames on stack, and resets the temporary frame slot (sets it to point to 0
, marking it as ready to accept new frames).
In similar manner, param
instructions operate on the temporary frame not on last frame on the stack. This is intended to prevent accidental frame modifications after the call
has been issued - once the frame has been pushed on the stack it must remain unchanged (for debugging purposes).