|
|
|
@ -1,9 +1,8 @@
|
|
|
|
|
---
|
|
|
|
|
title: "Proving My Compiler Code Incorrect With Alloy"
|
|
|
|
|
date: 2023-05-02T22:48:52-07:00
|
|
|
|
|
date: 2023-06-04T21:56:00-07:00
|
|
|
|
|
tags: ["Compilers", "Alloy"]
|
|
|
|
|
description: "In this post, I apply Alloy to a piece of code in the Chapel compiler to find a bug."
|
|
|
|
|
draft: true
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
__Disclaimer:__ though "my compiler code" makes for a fun title, I do not
|
|
|
|
@ -26,7 +25,7 @@ fairly silly by the standards of "real" Alloy users.
|
|
|
|
|
One of the things that a language like Chapel has to do is called _resolution_,
|
|
|
|
|
which is the process of figuring out what each identifier, like `x`, refers to,
|
|
|
|
|
and what its type is. Even the first part of that is pretty complicated, what
|
|
|
|
|
with public and private variables, methods (which can be decalred outside
|
|
|
|
|
with public and private variables, methods (which can be declared outside
|
|
|
|
|
of their receiver type in Chapel), and more...
|
|
|
|
|
|
|
|
|
|
Scope resolution in Chapel is further complicated by the fact that the same
|
|
|
|
@ -52,13 +51,34 @@ module M {
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
If you don't know Chapel (and you probably don't!) this program already merits a fair
|
|
|
|
|
bit of explanation. A _module_ in Chapel (declared via a `module` keyword)
|
|
|
|
|
bit of explanation. I've collapsed it for the sake of visual clarity; feel
|
|
|
|
|
free to expand the below section to learn more about the language features
|
|
|
|
|
used in the program above.
|
|
|
|
|
|
|
|
|
|
{{< details summary="Click here for an explanation of the above code snippet" >}}
|
|
|
|
|
A _module_ in Chapel (declared via a `module` keyword)
|
|
|
|
|
is just a collection of definitions. Such definitions could include variables,
|
|
|
|
|
methods, classes and more. Putting them in a module helps group them.
|
|
|
|
|
|
|
|
|
|
{{< todo >}}
|
|
|
|
|
Write the rest of this explanation.
|
|
|
|
|
{{< /todo >}}
|
|
|
|
|
A _class_ in Chapel (declared via a `class` keyword) is much like a class in
|
|
|
|
|
object oriented languages. The class `C` that we're creating on line 2 doesn't
|
|
|
|
|
have any fields or methods -- at least not yet. We will, however, add methods
|
|
|
|
|
to it using Chapel's _secondary method_ mechanism (more on that in a moment).
|
|
|
|
|
|
|
|
|
|
The `proc` keyword is used to create functions and methods. On line 5, we
|
|
|
|
|
create a procedure called `foo` that does nothing. On line 8, because we
|
|
|
|
|
write `C.foo` instead of just `foo`, we're actually creating a method on
|
|
|
|
|
the class `C` we declared earlier. This method does nothing too. Notice
|
|
|
|
|
that although declaring classes in Chapel works about the same as declaring
|
|
|
|
|
classes in other languages, it's fairly unusual to be able to declare a
|
|
|
|
|
class method (like the `foo` on line 8 in this case) outside of the `class
|
|
|
|
|
C { ... }` section of code. This is part of the reason that Chapel method
|
|
|
|
|
resolution is complicated (methods can be declared anywhere!). The only
|
|
|
|
|
other language that I know of that supports this feature is Kotlin with its
|
|
|
|
|
[extension function
|
|
|
|
|
mechanism](https://kotlinlang.org/docs/extensions.html), but it's possible
|
|
|
|
|
that other languages have similar functionality.
|
|
|
|
|
{{< /details >}}
|
|
|
|
|
|
|
|
|
|
The interesting part of the snippet is the body of the `doSomething` method.
|
|
|
|
|
It has a call to `foo`: but which `foo` is it referring to? There are two:
|
|
|
|
@ -87,10 +107,10 @@ the search order will be as follows:
|
|
|
|
|
be the non-method `foo`.
|
|
|
|
|
|
|
|
|
|
Notice that we've already had to search the module `M` twice, looking for
|
|
|
|
|
different things each time. First, we were looking for only method, but
|
|
|
|
|
different things each time. First, we were looking for only methods, but
|
|
|
|
|
later, we were looking for anything. However, this isn't as complicated as
|
|
|
|
|
things can get. The simplifying aspect of this program is that both `doSomething`
|
|
|
|
|
and `C` are defined inside the class `C`, and therefore have access to its
|
|
|
|
|
and `C` are defined inside the module `M`, and therefore have access to its
|
|
|
|
|
private methods and procedures. If we extracted `C.doSomething` into its
|
|
|
|
|
own separate module, the program would look like this.
|
|
|
|
|
|
|
|
|
@ -119,7 +139,7 @@ Since `doSomething` is now in another module, it can't just access the `foo`s fr
|
|
|
|
|
and make use of them. I opted for a `use` statement, which, in its simplest form,
|
|
|
|
|
just brings all the declarations inside the `use`d module into the current scope. Thus,
|
|
|
|
|
the `use` statement on line 11 would bring all things declared in `M1` into
|
|
|
|
|
the scope inside `M2`. There's a catch, though: since `M2` is not declared
|
|
|
|
|
the scope inside `M2`. There's a catch, though. Since `M2` is not declared
|
|
|
|
|
inside `M1`, a `use` statement will not be able to bring in _private_ symbols
|
|
|
|
|
from `M1` (they're private for a reason!). So, this time, when searching the scope
|
|
|
|
|
for `M1`, we will have to search only for public symbols. That's another,
|
|
|
|
@ -129,14 +149,13 @@ different way of searching `M1`. So far, we've seen three:
|
|
|
|
|
* Search `M1` for methods only.
|
|
|
|
|
* Search `M1` for public symbols only.
|
|
|
|
|
|
|
|
|
|
In Dyno, there are even more different ways of searching a single scope, and
|
|
|
|
|
some of them are mixes of others (one might consider, for instance, searching
|
|
|
|
|
for only public methods). To represent the various search configurations,
|
|
|
|
|
the Dyno team came up with using a bitfield of _flags_, each of which indicated
|
|
|
|
|
a necessary condition for a symbol to be returned. A bitfield with flags set
|
|
|
|
|
for two properties (like "public" and "method") requires that both such
|
|
|
|
|
properties be found on each symbol that's returned from a scope. This led to
|
|
|
|
|
C++ code along the lines of:
|
|
|
|
|
Dyno introduces more ways to search within a scope, including combinations of
|
|
|
|
|
search types, such as looking only for public methods. To represent the various
|
|
|
|
|
search configurations, the Dyno team came up with using a bitfield of _flags_,
|
|
|
|
|
each of which indicated a necessary condition for a symbol to be returned. A
|
|
|
|
|
bitfield with flags set for two properties (like "public" and "method") requires
|
|
|
|
|
that both such properties be found on each symbol that's returned from a scope.
|
|
|
|
|
This led to C++ code along the lines of:
|
|
|
|
|
|
|
|
|
|
```C++
|
|
|
|
|
auto allPublicSymbols = Flags::PUBLIC;
|
|
|
|
@ -167,7 +186,7 @@ In Dyno, we like to avoid additional work when we can. To do so, we track
|
|
|
|
|
which scopes have already been searched, and avoid searching them again.
|
|
|
|
|
Since what comes up from a search depends on the flags, we store the flags
|
|
|
|
|
alongside the scopes we've checked. __If we find that the previously-checked
|
|
|
|
|
bitfield is a subset of the current biset, we just skip the search__.
|
|
|
|
|
bitfield is a subset of the current bitset, we just skip the search__.
|
|
|
|
|
|
|
|
|
|
But then, what if it _isn't_ a subset? Another concern here is avoiding
|
|
|
|
|
duplicate results (it's easier to check for duplicate definitions if you
|
|
|
|
@ -186,7 +205,7 @@ versions of each flag available. Can't you just add those to the filter?
|
|
|
|
|
{{< message "answer" "Daniel" >}}
|
|
|
|
|
Good question. The difference is a little bit tricky. If we just negated
|
|
|
|
|
each flag, we'd turn an expression like \(A \land B\) into \(\lnot A \land
|
|
|
|
|
\lnot B\). However, according to De Morgan's laws, the proper negation of
|
|
|
|
|
\lnot B\). However, according to [De Morgan's laws](https://en.wikipedia.org/wiki/De_Morgan%27s_laws), the proper negation of
|
|
|
|
|
\(A \land B\) is \(\lnot A \lor \lnot B\) (notice the use of "or" instead
|
|
|
|
|
of "and"). On the other hand, using an "exclude" bitfield negates the whole
|
|
|
|
|
conjunction, rather than the individual flags, and so gives us the result
|
|
|
|
@ -303,7 +322,7 @@ Here's the Alloy definition for everything I just said:
|
|
|
|
|
|
|
|
|
|
{{< codelines "Alloy" "dyno-alloy/DynoAlloy.als" 59 63 >}}
|
|
|
|
|
|
|
|
|
|
Now, we can specify how flags in a bitfield relates to properties on a
|
|
|
|
|
Now, we can specify how flags in a bitfield relate to properties on a
|
|
|
|
|
symbol. We can do so by saying which flags match which properties. The
|
|
|
|
|
`Method` flag, for instance, will be satisfied by the `PMethod` property.
|
|
|
|
|
The `MethodOrField` flag is more lenient, and will be satisfied by either
|
|
|
|
@ -321,9 +340,9 @@ informally, the condition for a bitfield matching a symbol is twofold:
|
|
|
|
|
property on the symbol (that is to say, if `Method` is in the negative
|
|
|
|
|
flags set, then the symbol must not have `PMethod` property). It is more
|
|
|
|
|
conveniently to formulate this -- equivalently -- as follows: for each
|
|
|
|
|
negative flag, there most not be a property that satisfies it.
|
|
|
|
|
negative flag, there must not be a property that satisfies it.
|
|
|
|
|
|
|
|
|
|
Each of the above two conditions translate quite literally into Alloy:
|
|
|
|
|
Each of the above two conditions translates quite literally into Alloy:
|
|
|
|
|
|
|
|
|
|
{{< codelines "Alloy" "dyno-alloy/DynoAlloy.als" 71 74 >}}
|
|
|
|
|
|
|
|
|
|