From 250884c7bcfe8618f19a7acc54bc0677c31ea3c0 Mon Sep 17 00:00:00 2001 From: Danila Fedorin Date: Sun, 4 Jun 2023 21:56:45 -0700 Subject: [PATCH] Edit and publish Alloy article --- code/dyno-alloy/DynoAlloy.als | 6 +-- content/blog/dyno_alloy/index.md | 65 +++++++++++++++++++++----------- 2 files changed, 45 insertions(+), 26 deletions(-) diff --git a/code/dyno-alloy/DynoAlloy.als b/code/dyno-alloy/DynoAlloy.als index c984169..c490d10 100644 --- a/code/dyno-alloy/DynoAlloy.als +++ b/code/dyno-alloy/DynoAlloy.als @@ -62,15 +62,15 @@ sig Symbol { properties: set Property } -pred flagMatchesPropery[flag: Flag, property: Property] { +pred flagMatchesProperty[flag: Flag, property: Property] { (flag = Method and property = PMethod) or (flag = MethodOrField and (property = PMethod or property = PField)) or (flag = Public and property = PPublic) } pred bitfieldMatchesProperties[bitfield: Bitfield, symbol: Symbol] { - all flag: bitfield.positiveFlags | some property: symbol.properties | flagMatchesPropery[flag, property] - all flag: bitfield.negativeFlags | no property: symbol.properties | flagMatchesPropery[flag, property] + all flag: bitfield.positiveFlags | some property: symbol.properties | flagMatchesProperty[flag, property] + all flag: bitfield.negativeFlags | no property: symbol.properties | flagMatchesProperty[flag, property] } bitfieldExists: run { diff --git a/content/blog/dyno_alloy/index.md b/content/blog/dyno_alloy/index.md index 68eec5a..5a7d250 100644 --- a/content/blog/dyno_alloy/index.md +++ b/content/blog/dyno_alloy/index.md @@ -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 >}}