simon987 / Much-Assembly-Required

Assembly programming game
GNU General Public License v3.0
930 stars 87 forks source link

Large programs will reach the 100 ms execution cap #230

Closed kevinramharak closed 4 years ago

kevinramharak commented 4 years ago

Currently there are 2 ways to timeout. The execution cap is currently the min() of either the Cubot's energy value or the timeout value from the configuration. If execution times out it starts again at .txt in the next tick with the result of reaching the timeout every tick draining the cubot of all energy and leaving it with an empty battery.

Maybe this is a good reason to implement the interupt branch. Either way there has to be a way for the user code to react to a timeout reached and save state so it can resume execution next tick.

An example program where i reach this cap is a simple brainfuck interpreter. I can make the program work by executing a fixed amount of brainfuck instructions and then saving state and calling the BRK instruction and resuming execution on the next tick.

simon987 commented 4 years ago

Can you help me understand specifically how that would work?

Am I getting this right?

kevinramharak commented 4 years ago

That is exactly what i have in mind. Tough there probably should be a difference between timeout reached because of the server config and timeout reached because the battery is drained.

As a timeout because of a low battery probably means its to late to respond (besides sleeping until the cubot it charged up above a certain level).

I think its a good idea to limit users to a certain execution time, but being able to know when you have reached it would be expected. The reason my mind reaches for interupts is because having the user to query for it every n instructions would make the code very tedious.

simon987 commented 4 years ago

I implemented most of what we discussed above (with the exception of the battery thing). I still need to do some testing but it looks like it's what we're looking for.

Example (See IntInstructionTest.java):

(Interrupt vector table starts at 0x0)

isr:            
   MOV X, 0x1234
   BRK

.text
MOV [32], isr ; INT 32 is the interrupt number for "execution limit reached"

loop:
ADD A, 1
JMP loop

MOV Y, 0x4567
brk

; After execution, X=0x1234 and Y=0

When INT 32 is called, you have 10'000 'grace instructions' and after that you're powered off forcefully

kevinramharak commented 4 years ago

That looks good. I do think that having the interupt vector contain code to execute might be a more complicated way of handling it tough. A quick search on wikipedia shows that there are 2 popular implementations that could apply to our case:

In my opinion the second option would be more friendly since the first option can lead to hard to discover bugs with variable length instruction encoding. Users might not realize their interupt handler is trashing the rest of the table so an interrupt can lead to a jump to invalid or seemingly random instructions.

I just realized you implemented it the way i suggested 🤦

Also something to keep in mind for the future is to have a pseudo instruction to define the table starting location, tough I don't know how common that is.

simon987 commented 4 years ago

Implemented in branch refactor-1.6