Optimization attributes


(Charles Kissinger) #1

I wanted to float the idea of adding new attributes for function and method declarations that would allow various safety checks to be turned off (or on) at the level of individual functions. Examples of such attributes would be:

@uncheckedmath // integer overflow
@uncheckedbounds // array bounds
@unchecked // no safety checks, equivalent to -Ounchecked
@fastmath // if the —fastmath compiler option becomes available in the future

These attributes could have an argument with a value of true or false, allowing the global compiler option to be overridden for particular functions.

This is primarily motivated by the fact that the -Ounchecked compiler option is a very blunt instrument. It globally disables several unrelated safety checks such as integer overflow and array bounds checking. And it operates only at the level of an entire compilation unit. It is hard to reason about all the effects of this option across a large module, and isolating specific code to be compiled separately with -Ounchecked is inconvenient.

These new attributes would allow specific safety checks to be enabled or disabled on a per-function basis. I think the overall effect would be safer programs, because developers would be less likely to resort to the global -Ounchecked compiler option when searching for better performance.

Are optimization attributes such as these feasible?

-CK


(Jacob Bandes-Storch) #2

Would these be more appropriate as scoped modifiers similar to
#available(...)?

    func foo() {
        ...
        #unchecked {
            // stuff in here is unchecked
        }
    }

···

On Thu, Jan 7, 2016 at 1:55 PM, Charles Kissinger via swift-evolution < swift-evolution@swift.org> wrote:

I wanted to float the idea of adding new attributes for function and
method declarations that would allow various safety checks to be turned off
(or on) at the level of individual functions. Examples of such attributes
would be:

@uncheckedmath // integer overflow
@uncheckedbounds // array bounds
@unchecked // no safety checks, equivalent to -Ounchecked
@fastmath // if the —fastmath compiler option becomes available in
the future

These attributes could have an argument with a value of true or false,
allowing the global compiler option to be overridden for particular
functions.

This is primarily motivated by the fact that the -Ounchecked compiler
option is a very blunt instrument. It globally disables several unrelated
safety checks such as integer overflow and array bounds checking. And it
operates only at the level of an entire compilation unit. It is hard to
reason about all the effects of this option across a large module, and
isolating specific code to be compiled separately with -Ounchecked is
inconvenient.

These new attributes would allow specific safety checks to be enabled or
disabled on a per-function basis. I think the overall effect would be safer
programs, because developers would be less likely to resort to the global
-Ounchecked compiler option when searching for better performance.

Are optimization attributes such as these feasible?

-CK

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Joe Groff) #3

Scoped semantics changes like this are problematic. It's easy to accidentally change behavior you didn't intend to by applying the attributes too broadly, and it's harder for readers to understand the behavior within the contextual modifiers. It's also hard to define exactly what these mean, since many of the operations in question are defined as library functions. I think it's better in general to use explicitly unsafe operations, for instance, using &+ instead of + to ignore overflow checks. You could theoretically get something like the scoped attribute effect by using scoped imports, which could bring in a different set of operations shadowing the standard ones:

do {
  import FastMath // FastMath.* now shadows Swift.*

  return 4*x*x + 2*x + 1
}

-Joe

···

On Jan 7, 2016, at 1:55 PM, Charles Kissinger via swift-evolution <swift-evolution@swift.org> wrote:

I wanted to float the idea of adding new attributes for function and method declarations that would allow various safety checks to be turned off (or on) at the level of individual functions. Examples of such attributes would be:

@uncheckedmath // integer overflow
@uncheckedbounds // array bounds
@unchecked // no safety checks, equivalent to -Ounchecked
@fastmath // if the —fastmath compiler option becomes available in the future

These attributes could have an argument with a value of true or false, allowing the global compiler option to be overridden for particular functions.

This is primarily motivated by the fact that the -Ounchecked compiler option is a very blunt instrument. It globally disables several unrelated safety checks such as integer overflow and array bounds checking. And it operates only at the level of an entire compilation unit. It is hard to reason about all the effects of this option across a large module, and isolating specific code to be compiled separately with -Ounchecked is inconvenient.

These new attributes would allow specific safety checks to be enabled or disabled on a per-function basis. I think the overall effect would be safer programs, because developers would be less likely to resort to the global -Ounchecked compiler option when searching for better performance.

Are optimization attributes such as these feasible?


(David Sweeris) #4

Oh! That reminds me! Can we add @associative, @commutative, and @distributive attributes to functions? Or at least to operators? I know it wouldn’t change anything *right now*, but it’d lay the groundwork for an entire new class of compiler optimizations. I think. Maybe they can already figure out stuff like this and I just don’t know about it.

For example, if the compiler knew that “*” could be distributed over “+”, then the compiler could rewrite this:
  let y = x*foo + x*bar
to this:
  let y = x*(foo + bar)
if it thinks that’s more efficient (which, I think it pretty much always would be for the native numeric types).

There’s an obvious danger in that overflows/underflows could be generated that wouldn’t have otherwise happened. However, this kind of rewriting could just as easily prevent them from happening, depending on the details.

Maybe the syntax could be tacked on the end of the definitions like this?
func + (lhs: Integer, rhs: Integer) -> Integer {
  …
} @commutative @associative

func * (lhs: Integer, rhs: Integer) -> Integer {
  …
} @commutative @associative @distributive over (+ - * /) // whitespace-delimited so that it won’t matter if an operator ends with a comma

Or maybe make it kinda sorta similar to an extension? (Although separating “the rules that tell the compiler how it’s allowed to literally rewrite your code” from “the functions to which said rules apply" seems like a bad idea)
@attributes infix operator + {
  func + (_: Integer, _: Integer) -> Integer @commutative @associative
}
@attributes infix operator * {
  func * (_: Integer, _: Integer) -> Integer @commutative @associative @distributive over (+ - * /)
}

I think my favorite would be between the return type and the opening curly brace:
func + (lhs: Integer, rhs: Integer) -> Integer @commutative @associative {
  …
}
func * (lhs: Integer, rhs: Integer) -> Integer @commutative @associative @distributive over (+ - * /) {
  …
}

Going a step (or two) further, for funcs/ops that *aren’t* <any given attribute>, it could conceivably help to be able to optionally express a way to sorta fake it. For instance, while “a-b” usually doesn’t equal “b-a”, it does equal “-(b-a)”. Similarly, a/b is equal to 1/(b/a). (I know commutativity isn’t a particularly good example, since that attribute doesn’t affect when values need to be known, operand grouping, or the total number of operations, but I couldn’t think of any examples of the other types.) I’m much less supportive of this part… I think it’d be *cool*, but I have no idea if it’d be *useful*. It just seems to me that the more metadata we can give the compiler, the more opportunities it’ll have to optimize our code.

···

On Jan 7, 2016, at 13:55, Charles Kissinger via swift-evolution <swift-evolution@swift.org> wrote:

I wanted to float the idea of adding new attributes for function and method declarations that would allow various safety checks to be turned off (or on) at the level of individual functions. Examples of such attributes would be:

@uncheckedmath // integer overflow
@uncheckedbounds // array bounds
@unchecked // no safety checks, equivalent to -Ounchecked
@fastmath // if the —fastmath compiler option becomes available in the future


(Charles Kissinger) #5

Yes! (As far as I’m concerned.) Operating on scopes was what I originally had in mind, but I then overlooked the # modifier approach.

—CK

···

On Jan 7, 2016, at 2:10 PM, Jacob Bandes-Storch <jtbandes@gmail.com> wrote:

Would these be more appropriate as scoped modifiers similar to #available(...)?

    func foo() {
        ...
        #unchecked {
            // stuff in here is unchecked
        }
    }

On Thu, Jan 7, 2016 at 1:55 PM, Charles Kissinger via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
I wanted to float the idea of adding new attributes for function and method declarations that would allow various safety checks to be turned off (or on) at the level of individual functions. Examples of such attributes would be:

@uncheckedmath // integer overflow
@uncheckedbounds // array bounds
@unchecked // no safety checks, equivalent to -Ounchecked
@fastmath // if the —fastmath compiler option becomes available in the future

These attributes could have an argument with a value of true or false, allowing the global compiler option to be overridden for particular functions.

This is primarily motivated by the fact that the -Ounchecked compiler option is a very blunt instrument. It globally disables several unrelated safety checks such as integer overflow and array bounds checking. And it operates only at the level of an entire compilation unit. It is hard to reason about all the effects of this option across a large module, and isolating specific code to be compiled separately with -Ounchecked is inconvenient.

These new attributes would allow specific safety checks to be enabled or disabled on a per-function basis. I think the overall effect would be safer programs, because developers would be less likely to resort to the global -Ounchecked compiler option when searching for better performance.

Are optimization attributes such as these feasible?

-CK

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(Charles Kissinger) #6

Joe,

Thanks very much for the feedback. I accept your points, but just a few comments:

It's easy to accidentally change behavior you didn't intend to by applying the attributes too broadly

Yes, but this was exactly the reason for proposing this as an alternative to the global compiler switch.

It's also hard to define exactly what these mean, since many of the operations in question are defined as library functions.

But the -Ounchecked compiler switch already has a defined mechanism in place for eliding these function calls, correct?

I think it's better in general to use explicitly unsafe operations, for instance, using &+ instead of + to ignore overflow checks.

Agreed. The biggest problem with those operators is that they are a pain to scan through quickly and understand in even slightly more complex expressions. To take your equation below, translated to overflow operators:

return 4 &* x &* x &+ 2 &* x &+ 1

Yuck! All I see are the ampersands unless I squint. I had to read through it three times just to convince myself I translated the equation correctly.

You could theoretically get something like the scoped attribute effect by using scoped imports, which could bring in a different set of operations shadowing the standard ones:

do {
import FastMath // FastMath.* now shadows Swift.*

return 4*x*x + 2*x + 1
}

Interesting approach. It would allow developers to implement a very limited set of “do-it-yourself” optimizations for some things. (I’m pretty sure DIY fast-math is out of the question though.)

Thanks again,
-CK

···

On Jan 7, 2016, at 4:04 PM, Joe Groff <jgroff@apple.com> wrote:

On Jan 7, 2016, at 1:55 PM, Charles Kissinger via swift-evolution <swift-evolution@swift.org> wrote:

I wanted to float the idea of adding new attributes for function and method declarations that would allow various safety checks to be turned off (or on) at the level of individual functions. Examples of such attributes would be:

@uncheckedmath // integer overflow
@uncheckedbounds // array bounds
@unchecked // no safety checks, equivalent to -Ounchecked
@fastmath // if the —fastmath compiler option becomes available in the future

These attributes could have an argument with a value of true or false, allowing the global compiler option to be overridden for particular functions.

This is primarily motivated by the fact that the -Ounchecked compiler option is a very blunt instrument. It globally disables several unrelated safety checks such as integer overflow and array bounds checking. And it operates only at the level of an entire compilation unit. It is hard to reason about all the effects of this option across a large module, and isolating specific code to be compiled separately with -Ounchecked is inconvenient.

These new attributes would allow specific safety checks to be enabled or disabled on a per-function basis. I think the overall effect would be safer programs, because developers would be less likely to resort to the global -Ounchecked compiler option when searching for better performance.

Are optimization attributes such as these feasible?

Scoped semantics changes like this are problematic. It's easy to accidentally change behavior you didn't intend to by applying the attributes too broadly, and it's harder for readers to understand the behavior within the contextual modifiers. It's also hard to define exactly what these mean, since many of the operations in question are defined as library functions. I think it's better in general to use explicitly unsafe operations, for instance, using &+ instead of + to ignore overflow checks. You could theoretically get something like the scoped attribute effect by using scoped imports, which could bring in a different set of operations shadowing the standard ones:

do {
import FastMath // FastMath.* now shadows Swift.*

return 4*x*x + 2*x + 1
}

-Joe


(Charles Kissinger) #7

Modern optimizing compilers have a very robust understanding of algebraic rearrangement already built in, I think.

It can’t safely be done on floating point calculations, by the way, because the result of the calculation is likely to change (usually very slightly, sometimes catastrophically). Some compilers have a -fast-math option that (among other things) allows some floating point rearrangement for cases where the developer knows it is safe and gives acceptable results.

—CK

···

On Jan 7, 2016, at 5:36 PM, Dave via swift-evolution <swift-evolution@swift.org> wrote:

On Jan 7, 2016, at 13:55, Charles Kissinger via swift-evolution <swift-evolution@swift.org> wrote:

I wanted to float the idea of adding new attributes for function and method declarations that would allow various safety checks to be turned off (or on) at the level of individual functions. Examples of such attributes would be:

@uncheckedmath // integer overflow
@uncheckedbounds // array bounds
@unchecked // no safety checks, equivalent to -Ounchecked
@fastmath // if the —fastmath compiler option becomes available in the future

Oh! That reminds me! Can we add @associative, @commutative, and @distributive attributes to functions? Or at least to operators? I know it wouldn’t change anything *right now*, but it’d lay the groundwork for an entire new class of compiler optimizations. I think. Maybe they can already figure out stuff like this and I just don’t know about it.

For example, if the compiler knew that “*” could be distributed over “+”, then the compiler could rewrite this:
  let y = x*foo + x*bar
to this:
  let y = x*(foo + bar)
if it thinks that’s more efficient (which, I think it pretty much always would be for the native numeric types).

There’s an obvious danger in that overflows/underflows could be generated that wouldn’t have otherwise happened. However, this kind of rewriting could just as easily prevent them from happening, depending on the details.

Maybe the syntax could be tacked on the end of the definitions like this?
func + (lhs: Integer, rhs: Integer) -> Integer {
  …
} @commutative @associative

func * (lhs: Integer, rhs: Integer) -> Integer {
  …
} @commutative @associative @distributive over (+ - * /) // whitespace-delimited so that it won’t matter if an operator ends with a comma

Or maybe make it kinda sorta similar to an extension? (Although separating “the rules that tell the compiler how it’s allowed to literally rewrite your code” from “the functions to which said rules apply" seems like a bad idea)
@attributes infix operator + {
  func + (_: Integer, _: Integer) -> Integer @commutative @associative
}
@attributes infix operator * {
  func * (_: Integer, _: Integer) -> Integer @commutative @associative @distributive over (+ - * /)
}

I think my favorite would be between the return type and the opening curly brace:
func + (lhs: Integer, rhs: Integer) -> Integer @commutative @associative {
  …
}
func * (lhs: Integer, rhs: Integer) -> Integer @commutative @associative @distributive over (+ - * /) {
  …
}

Going a step (or two) further, for funcs/ops that *aren’t* <any given attribute>, it could conceivably help to be able to optionally express a way to sorta fake it. For instance, while “a-b” usually doesn’t equal “b-a”, it does equal “-(b-a)”. Similarly, a/b is equal to 1/(b/a). (I know commutativity isn’t a particularly good example, since that attribute doesn’t affect when values need to be known, operand grouping, or the total number of operations, but I couldn’t think of any examples of the other types.) I’m much less supportive of this part… I think it’d be *cool*, but I have no idea if it’d be *useful*. It just seems to me that the more metadata we can give the compiler, the more opportunities it’ll have to optimize our code.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution