Chapter 4: Programming concepts Up Main page Chapter 6: Generic programming 

5 The logic system

As many other programming languages, Quasar has an assert function. The assert function will evaluate the specified condition and will result in an error message when the condition is false. The assert function can be called with either one or two arguments:
assert(condition, "condition is false")
In the second case, the error message is specified, which makes it easier for the user to resolve the issue. The assert function also gives hints to the compiler system (see Section 5.3↓). When the compiler is able to figure out that the condition will never be true, a compiler error will be generated! Note that this is in contrast to most existing programming languages, for which assert is simply a run-time function. The algorithm for evaluating assertions is then as follows:
  1. The compiler checks the condition of the assertion.
  2. There are three possible outcomes: valid, satisfiable or unsatisfiable:\begin_inset Separator latexpar\end_inset
    1. If the result is unsatisfiable, a compile-time error will be generated.
    2. If the result is satisfiable, the compiler will take the condition as a hint.
    3. If the result is valid, the compiler is certain that the condition will be met in all situations. Therefore, the compiler may remove the assertion instruction from the program.
    The compiler is either able to recognize the condition (see Section 5.3↓) or not able to do so. In the former case, a logic evaluation will be performed. In the latter case, the result is always satisfiable.
  3. In case the result is satisfiable, the condition will still be checked at run-time.
The compiler is free to decide which assertions to take into account and also how to propagate information through the various compilation phases. The exact behavior may be controlled using compiler settings. For example, the following program may result in a compile-time error:
function [] = __kernel__ kernel (b : scalar, pos : ivec3)
parallel_do(size(im), 2, kernel)
Here, the constant parameter value for b, is passed through the parallel_do function to the kernel function kernel. Through automatic specialization techniques (see Section 6.6↓), the Quasar compiler will know in this case that the value for b is 2, resulting in a compiler error. In the future, this behavior may be extended to even more complex scenarios.

5.1 Kernel function assertions

It is possible to call the assert function from a kernel or device function:
function [] = __kernel__ kernel (pos : ivec3)
b = 2
Obviously, the above assertion fails. Quasar breaks with the following error message:
(parallel_do) test_kernel - assertion failed: line 23Recall that the kernel function is typically called by many threads in parallel. Therefore, the following rules apply:
  1. When the user program catches an assertion failure from a kernel function, there is at least one thread (or position pos) for which the condition failed.
  2. It is currently not possible to retrieve the position that corresponds to assertion failure. [J]  [J] Note that the kernel function debugger in Quasar Redshift can bring a solution here.
  3. The output of the kernel function is undetermined. Some threads may have completely finished, others may not have started. The order in which this happens is completely unspecified. In other words, when an assertion fails, the output of the kernel function should be ignored.
Kernel function assertions provide a very useful mechanism for directly debugging and verifying code on a CPU or GPU. The assertion system is also used internally by Quasar to perform vector and matrix boundary checking.

5.2 Built-in compiler functions

There are three meta functions that help with assertions. These functions are evaluated at compile-time (as indicated by the $-prefix)
Usually, you will not need to call these functions directly from your code. Nevertheless, they can be useful for testing (for example in interactive mode).

5.3 Assertion types recognized by the compiler

There are different types of assertions recognized by the Quasar compiler. These assertions can be combined in a transparent way using the Boolean operators ! (inversion), && (and) and || (or).

5.3.1 Equalities

The most simple cases of assertions are the equality assertions a==b. For example:
symbolic a, b
assert(a==4 && b==6)
assert($check(a==4 && b==6)=="Valid")
assert($check(a==4 && b==5)=="Unsatisfiable")
assert($check(a==4 && b!=6)=="Unsatisfiable")
assert($check(a==4 || b==6)=="Valid")
assert($check(a==4 || b==7)=="Valid")
assert($check(a==3 || b==6)=="Valid")
assert($check(a==3 || b==5)=="Unsatisfiable")
assert($check(a!=4 || b==6)=="Valid")
print $str($assump(a)),",",$str($assump(b)) % prints (a==4),(b==6)
Here, we use symbolic to declare symbolic variables (variables that are not to be "evaluated", i.e. translated into their actual value since they don’t have a specific value). Next, the function assert tests whether the $check(.) function works correctly (=self-checking).

5.3.2 Inequalities

The propositional logic system can also work with inequalities:
symbolic a
assert(a>2 && a<4)
The idea is here that the inequality assertions can help the simplification of if conditions. For example,
assert(x > 10)
if x > 0
y = x
y = -x
In this case, the if-test can be completely eliminated thereby ignoring the else-block, because it is certain that x is positive.

5.3.3 Type assertions

Type assertions are useful for 1) checking whether a variable has a given type and 2) for giving hints to the compiler. For example, as mentioned in Section 2.2↑, we may use a type assertion to make sure that data read from a file has the right type:
[A, B] = load("myfile.dat")
assert(type(A,"ccube") && type(B,"vec"))
Please note that assertions should not be used with the intention of variable type declaration. To declare the type of certain variables type annotations can be used:
[A : ccube, B : vec] = load("myfile.dat")Type annotations should be used on the first occurrence of a variable. In this case, the type annotation prevents A and B from becoming a polymorphic variable (see Section 4.1↑). For type assertions, there is no such requirement (they can be used in combination with a polymorphic variable).

5.4 User-defined properties

It is also possible to define "properties" of variables, using a symbolic declaration. For example:
symbolic is_a_hero, Jan_AeltermanThen you can assert:
assert(is_a_hero(Jan_Aelterman))Correspondingly, if you perform the test:
print $check(is_a_hero(Jan_Aelterman)) % Prints: Valid
print $check(!is_a_hero(Jan_Aelterman)) % Prints: Unsatisfiable
If you then try to assert the opposite:
assert(!is_a_hero(Jan_Aelterman))The compiler will complain:
assert.q - Line 119: NO NO NO I don’t believe this, can’t be true!
Assertion ’!(is_a_hero(Jan_Aelterman))’ is contradictory with ’is_a_hero(Jan_Aelterman)’

5.5 Unassert

In some cases, it is neccesary to undo certain assertions that were previously made. For this task, the function ‘unassert‘ can be used:
unassert(propositions)This function only has a meaning at compile-time; at run-time nothing needs to be done. For example, if you wish to reconsider the assertion ‘is_a_hero(Jan_Aelterman)‘ you can write:
print $check(is_a_hero(Jan_Aelterman)) % Prints: Satisfiable
print $check(!is_a_hero(Jan_Aelterman)) % Prints: Satisfiable
Alternatively you could have written:
print $check(is_a_hero(Jan_Aelterman)) % Prints: Valid
print $check(!is_a_hero(Jan_Aelterman)) % Prints: Unsatisfiable

5.6 The role of assertions

In Quasar, the role of assertions is two-fold:

 Chapter 4: Programming concepts Up Main page Chapter 6: Generic programming