The Error Of My Ways

BlitzMax Forums/BlitzMax Beginners Area/The Error Of My Ways

dw817(Posted 2016) [#1]
Likely this question has been asked before so I thought I would put it in the beginner's section.

Most BASIC languages have the ability of catching errors when they occur. I'd like to be able to have this in BlitzMAX - but I don't think it's available. It works like this.

ON ERROR GOTO 1000
100 .. 900 .. code

1000 {{ Handle error, tell which line it occurred on AND the error code it gave }} Option to continue code.

In BASIC once you went to the error routine you can say RESUME to repeat the same line (possibly repeating the error again), OR you could say RESUME NEXT and it would SKIP over the line that had the error in it and continue back to the program.

Thus,

100 .. 900 .. code
Let's say line 450 has an error, an out of range
You already have the 'ON ERROR' routine active so instead of showing an error it jumps to 1000

At line 1000 (or label or what have you), it says error occurred in line 450, it was error #16 (let's say, out of range), and line 1010 has a wait for a key pause and then RESUME NEXT, which will return back but to the line JUST AFTER the one that made an error, let's say 460.

Is this feature available in BlitzMAX - the ability to catch any error and act on it ?


GW(Posted 2016) [#2]
Bmax doesn't have any resume mechanism like the older basic languages, but it does 'Exceptions'.
I've never really used them myself, I prefer my programs to halt on errors, but you can find some details in the Bmax help under 'Language'->'Exceptions' or searching for 'Exceptions' here in the forums.


Bobysait(Posted 2016) [#3]
You can surround codes with Try/Catch to catch "thrown" errors.


Function do()
	Throw "stop me now !"
End Function



Function test:Byte()
	
	Try
		do()
		Return True;
	Catch err:String
		Print err
		Return False;
	End Try
	
End Function

Print "test : "+test()



dw817(Posted 2016) [#4]
Hi Bobysait. I'm not understanding this example. There are no errors in it ?

How about catching this,
Global deck[51]
For i=0 To 51
  Print deck[i]
Next


Where it's clear it will crash because DECK[] is not big enough. How would you catch this with TRY/CATCH ?


Kryzon(Posted 2016) [#5]
Try / Catch catches explicitly thrown errors (hence Bobysait's emphasis on 'thrown'). This means when code executes the Throw function (check the Bmax documentation on that).

You cannot recover from SEGFAULT (memory access errors, like invalid pointers etc.) in any way, be it in BlitzMax or C & C++. When the error happens, your program has already been put in an invalid stage. The program is terminated.


dw817(Posted 2016) [#6]
Hi Kryzon, wow that bites. Guess I'll have to write some work-arounds.

Was really hoping BlitzMAX could handle a case like this. I know it would help a lot of programmers who are putting out commercial games and software and don't want the end user to see any error messages or crashes at any time.


Wiebo(Posted 2016) [#7]
If you want to make that happen, write code that checks the situation before execution, or insert fail saves for user input. Improve the quality of your code.


Bobysait(Posted 2016) [#8]
I think you really prefer the user to run a runtime error. It's much better than having the application running with random behaviors that could be dangerous in the long run.

You can obviously track every instance and check "if null" / "If index>0 and index<length" etc ... anytime, but the code will be slow.

If your code is well writen, the only errors that will happen should be some impredictible random OS/GC Drivers errors.

If you're familiar with the debugger, you can also make the debugmode very slow using "?Debug" pre-processor, then check everything that can be checked in "?debug"
Your release won't care of the checks while your debug version should prevent you from lots of unexpected errors


Yasha(Posted 2016) [#9]
I know it would help a lot of programmers


Yeah I wanna jump in and emphasize what Wiebo said. It wouldn't help anyone at all.

Out-of-bounds errors and null-reference-exceptions are a debug feature. BlitzMax will help you find the error so you can fix it and make a release build that doesn't do that. Code that depends on debug mode being active has no right or reason to ever leave your workstation.

The way to ensure the user never sees crashes is to write code that doesn't crash. Anything less is completely unacceptable to deliver to a customer.


You can minimize the errors in your code by using generally better programming practice. For instance, you can ensure your array accesses never go out of bounds by avoiding explicit access to an array:

Global deck:Int[51]

' read-only
For Local c:Int = Eachin deck   'places the values directly into c
    Print c
Next

' writable
Function ForEach(arr:Int[], modify(i:Int Var))
    For Local k:Int = 0 Until arr.Length
        modify(arr[k])
    Next
End Function

Function AddOne(i:Int Var)
    i :+ 1
End Function

ForEach(deck, AddOne)  'no explicit loop either = even less room for error

Arrays are a low-level data structure, anyway - if there's the slightest chance that this could be a problem, it's probably a sign that you should consider using a more semantically-relevant data structure in the array's place.


Derron(Posted 2016) [#10]
Yasha, while your code might save you from some trouble, it adds a big pile of function calls (imagine array being a dataset of 100k entries).

For me the biggest trouble is to distinguish between "to" and "until" (that is better written as "<" or "<=" like in a while loop or in C-for-loops).

I think problematic data is most often coming from dynamic data (user input, media data, ..) so you could sanitize it when it enters your application. Within the application problems should be avoidable by running them with debug once in a while (MaxIDE...*shivering*).


Nonetheless: programmers are just human, so be ready to face imperfection.


bye
Ron


Yasha(Posted 2016) [#11]
Function calls are dirt cheap on modern hardware. 100k function calls per frame is nothing, won't have any observable effect (if it does, it's because the function was actually doing something and is therefore inescapable). And if you use NG, the function gets inlined away and is therefore literally free anyway.

Worst thing you can possibly do when coding is let trivial concerns about optimization get in the way of writing clean code. If the compiler generates slow code when you use lots of functions, you need a better compiler. Worrying about that sort of thing is not a human's job.

Programmers are indeed human, and that's precisely why it's important to use tricks that make it difficult or impossible to express an error in the first place. The machine won't let you use ForEach above in a way that goes out of bounds, and thus it never will. Humans make mistakes when they decide not to leverage design in this way and try to do the machine's job for it, keeping track of sizes and indices manually and so on.


dw817(Posted 2016) [#12]
You guys are going at it. :) Let me toss this into the pile and see what you think. One advantage of being able to catch errors is, you can generate your own error messages.

Who wants commercial code to CRASH with a nasty BlitzMAX message box ? Or - if you could catch the error, you could make a nice error box - like I saw someone working on a cooking in the kitchen game earlier I think ?

You could show a background picture of a woman cooking showing smoke coming out of her skillet, a comical look of surprise on her face and a message like, "Oops ! Cook-A-Lot ran into a problem. This has been logged in ERROR.TXT. Please try running this game again or if the error persists, contact our company and we will be glad to help you."


Yasha(Posted 2016) [#13]
Or - if you could catch the error


...I'm not seeing what the logic is in delivering code that you know has errors in it. The time you spent on that animation could have been spent looking at the source of the exception and dealing with it.

This is never excusable no matter how pretty you make it look. Heck, this isn't even primarily about the customer's experience - as a professional, how can you live with the thought of doing that?

Yes, in the real world, we make mistakes, things sometimes crash. But this happens ONCE, and then you fix it. You don't need to plan for the long haul, because if more than one customer ever sees the error, or they see it twice, something's gone seriously badly wrong with your entire process. If you can't deal with it, you pull the program. It's not ready to face the public in that state.

The important thing to keep at the front of your mind is that the "nasty BlitzMax message box" is not BlitzMax's fault. It is yours.


dw817(Posted 2016) [#14]
Yasha, there is no such thing as fool-proof code. One way or another, you or I will write code that will crash on a PC some day. And the most likely culprit won't be ourselves, it will be Microsoft who keeps changing the rules with their new OS's and system libraries.



As Inspector Gadget says, "Better safe than sore." :)


Derron(Posted 2016) [#15]
When you work with outside code (DLLs or whatever) you are free to run it in a try-catch-block.

So if something fails to calculate X there ... you are able to present a dialogue to the user telling him that someone borked a 3rd party dll you failed to provide with your app but relied on being available in c:\Windows\System(32)

Another example is: you externed your AI to some lua scripts - now the lua script was modified and is no longer working (old API calls or so). In that case you inform the user (or ai dev) that this is the case and the AI should be upgraded.

But, this does not count for your code, your "internal code" could be error free. Exception is dynamic code (evaluated inputs, scripts...) they should be sanitized/checked.


bye
Ron


Yasha(Posted 2016) [#16]
there is no such thing as fool-proof code


Sure there is. For a start, code written in a non-Turing-complete language, or language subset (like P10 for C), can always be fully analyzed and mathematically proven to be error-free at compile time (Turing-completeness is the enemy of the engineer). It's also possible to rule out large classes of errors even when using generally Turing-complete languages, e.g. the errors you describe above are guaranteed never to occur when writing in an ML-derived language; and even if you work in a very liberal language like C, there are powerful tools that can analyze your code and identify the possible error sites for you ([1], [2], [3 for verifying the compiler itself]).

Not all software is games. Code written for the automotive/defence/aerospace/etc. sectors doesn't have the liberty of making mistakes even once. So it doesn't. We should all strive to be at least that good.


dw817(Posted 2016) [#17]
Yasha, BlitzMAX does not meet P10. A simple example of a crash. Someone earlier wrote code to work on their maximum resolution, and they wanted to hardware snap it for display.

Not only did that not work for me but I had to Ctrl-Alt-Del to remove the task from executables as it royally hung with a black and badly flickering pixeled screen. I thought it would damage my monitor.

You can write code that is error-proof on your computer, sure. But once you distribute it, anything goes. Someone may or may not have the same version of DirectX, OpenGL - and that's just for graphics.

Audio could be a whole new bag. Maybe they formatted their Windows on drive F instead of C ? That's the way mine upstairs is.

All these little 'nuisances' would have to be accounted for to make a 'less' error-prone model. But I cannot see at any point a program, even with just one line that will not crash or refuse to run entirely on every possible computer that was compiled to an EXE <-- that's the kicker.

Yes you can write code that is error free, and send me or anyone a copy.
But once it is compiled to an EXE - anything goes. :)


Derron(Posted 2016) [#18]
- you can find out on which drive Windows was installed
- ctrl-alt-del: do you remember that Screen-resolution-change-window-countdown of 15seconds to press "OK", if not the old resolution is set again (might have helped)

If something like above stalls your app, this is not always _your_ code but a bug in an 3rd party (module) code. So this code then is not written error-prone. Nonetheless it is possible.

Something you cannot stop is broken hardware (you send data, this incarnation of a GPU does not like, that is why it powers down) - again this is not your code's fault then (it does what it is expected to do).

bye
Ron


LT(Posted 2016) [#19]
if you use NG, the function gets inlined away
I would like to understand this better, because I've not seen this mentioned anywhere else.

Does this happen to ALL function calls (or is there some tagging)? Seems like it would make the executables rather bloated...


Derron(Posted 2016) [#20]
the inline-optimisations depend on some params you give to the c-compiler.

For gcc:
-O2 only inlines functions if it does not increase filesize
-O3 inlines functions if the compiler thinks it improves execution (ignoring filesize)

In all cases it inlines functions, which are only called once at all.


Inlining is not a direct decision of BMX-NG (but it could enforce it, or give hints like the "inline" command) but of the used C-Compiler.


bye
Ron


LT(Posted 2016) [#21]
Thanks, Derron. Are you saying it happens in regular BMX? Is more information on this somewhere that I've missed? Just wondering if those switches are made as CC_OPTS?

Would be great to have an inline hint to force it in certain cases...


Derron(Posted 2016) [#22]
vanilla BMX translates to ASM and compiles using FASM (Flat Assembler). I think this does not do such "automagically done" optimizations.

If you are talking about used C-code in the modules (compiled with gcc/minGW) then this optimization is done there too if the compiler options allow it (hope the "big brains" of the forum won't correct me now :-)).

bye
Ron


Yasha(Posted 2016) [#23]
BRL BlitzMax never inlines anything. It's not an optimizing compiler, and doesn't significantly change the code you wrote. In practice, this is fine for 99% of real-world uses - machine code is fast, and the BlitzMax runtime powering the backend stuff is well-written. (A huge amount of real-world code is interpreted anyway and still fast enough.)

C is compiled via GCC (unless you hacked the backend to use something else). GCC is a ludicrously powerful optimizing compiler, and thus has a whole rack of things it can do to code, of which inlining is just one small option among many. These optimizations are not available to BlitzMax code compiled with the BRL compiler, because that code goes straight from BMX to binary without passing through C in between (it passes through an x86 text representation, but this is 1:1 to final code, you can ignore it). The NG compiler uses C as its assembly language, and thus has the option to insert an "extra" optimization step after compilation, when the C is "assembled" by GCC.

Would be great to have an inline hint


Look back up at post #9. Notice what's interesting about `modify`? It's not a function, it's a function pointer. GCC isn't just identifying inlinable candidate functions and marking them, then expanding at call sites - it's doing complex dataflow analysis to see that the entire indirection structure can be unwound and replaced with something simpler. You can't even come close to this level of optimization with a simple "inline" directive attached to function definitions in your code!


Inline hint directives are totally obsolete in modern programming, and C and C++ compilers will completely ignore them when optimizing normally (for code generation, anyway: they do have an unrelated semantic meaning too). The compiler simply doesn't need or want your help for this task. That's in addition to inlining becoming less relevant as a single optimization as compiler technology moves forward (e.g. modern function calls are dirt-cheap anyway; x64 has builtin support for "leaf functions", which have all of the speed of inlining with none of the drawbacks; and a decent optimizer will heavily rewrite your code, making it difficult for you to tell which calls will even remain in the final product, or what structure it will have, or whether any of the calls and jumps that do exist are to anything that was ever defined as a "function" in your source).


Derron(Posted 2016) [#24]
Under the assumption of Yasha telling "the truth" (I think so :-)).


Got my "Yasha teaches us something" for today, cannot await to see what comes next.

Thanks ...again.


bye
Ron


LT(Posted 2016) [#25]
Oh, I thought vanilla BMX was doing some translation to C. My misunderstanding...

How do we know which function calls get optimized and which don't? I suspect your answer is that we don't care because we should just trust the compiler. I'm not sure that's good enough...not because I'm unwilling to take your word for it but because it seems that would make it hard to find and especially fix bottlenecks.

However, your example (in post #9) does make me wonder if passed in function pointers are always inlined...? That would cover the use case I was most concerned about. I have a bunch of functions that share similar code. I'd like to simplify them by having them call sub-functions with the shared code, but they're somewhat speed critical and I've refrained over fears of adding functions calls.


LT(Posted 2016) [#26]
Just for fun, I wrote up a quick test with passed in function pointers. Legacy BlitzMax definitely does not inline them...NG optimization was smart enough to know that I was doing the same thing a million times and gave a 0 ms result no matter how many iterations. Heh...


Yasha(Posted 2016) [#27]
does make me wonder if passed in function pointers are always inlined...?


Given that the, um, point, of function pointers is that you don't generally know until runtime what the destination of the call is going to be (the whole point of the example is that it's GCC doing something quite smart, finding a case where it can know it), this wouldn't really be possible. You could add a language feature that asked for "static function pointers only" as an argument type, but that's basically be more like macros, or C++ templates, than a normal function.

Some ideas for benchmarking optimized code without the compiler removing your test:

- put the test and the test executor in separate translation units, so the compiler can't see (at least, without LTO enabled) that the million iterations of the loop achieve nothing

- write the test loop in C so you can mark its control variables as `volatile`, which is specifically designed to disable optimizations

- in the worst-case, pass the tested function in as a volatile pointer itself; this should help defeat even LTO or putting the functions in the same translation unit

You should definitely at least try out the cleaner version of your code.

How do we know which function calls get optimized and which don't?


You can always examine the emitted assembly...

More importantly, you shouldn't be thinking in terms of individual optimizations or the application of individual optimizations (e.g. a compiler might generate multiple versions of a function depending on context: an unoptimized one for external calls, a "leaf" version for fast calls within this unit, a specialized one because it's seen you often use it with "1" as the second argument, and fully inlined it in two other places that were deepest within nested loops). Instead, try profiling at a higher level. There's a profiling framework in BlitzMax floating around the forums somewhere (I think by grable?), and if you're using NG, you might be able to work out something with gprof or similar.

This will tell you what the results are, rather than what it's doing to get those results. You only really need to actually know what it's doing if you can see that the results are not good enough and don't understand which part of the code as you wrote it would account for that.


LT(Posted 2016) [#28]
You should definitely at least try out the cleaner version of your code.
Well, "cleaner" is a bit subjective. But smaller and easier to maintain is slower in my tests with vanilla Blitz. It's probably fine with NG, but I haven't switched yet.
the point of function pointers is that you don't generally know until runtime what the destination of the call is going to be
Uh, yeah...um, duh.
that's basically more like macros
That's probably more like what I'm after here. Some kind of pre-processing of the functions. OTOH, I think those tend to be confusing to anyone else looking at the code.