Design of FPOptions

classic Classic list List threaded Threaded
7 messages Options
Reply | Threaded
Open this post in threaded view
|

Design of FPOptions

suyash singh via cfe-dev
Hi all,

There are development efforts aimed at reducing space occupied by the field FPFeatures (https://reviews.llvm.org/D72841). The expected result is reduction of the FPFeatures size from 8 bits to 7, so it is really a battle for bit. I have concern that such work can impact readability and maintainability of the code and would like to discuss alternative ways.

Background

There are two main things that form background of the problem.

First, operations that involve floating point arguments need to specify floating point environment. The latter is comprised of bits of hardware state registers  (rounding direction, denormals behavior, exception mask) and options and hints provided by user to compiler (whether NANs may occur, shall sign of zero be conserved, may exceptions be ignored). The list of relevant options was discussed in http://lists.llvm.org/pipermail/llvm-dev/2020-January/138652.html. It is convenient to collect all aspects of the FP environment into one object, this is what class FPOptions does. Now this class contains only few attributes but in https://reviews.llvm.org/D72841 an attempt was made to extend it in right direction.

Second, the Clang internal representation (AST) was implemented to be as compact as possible. The dark side of this principle is inconvenient class organization and diffuse class boundaries, - derived classes keep their states in parent class fields, which are fixed in size. FP environment now is represented by a field in the class BinaryOperatorBitfields and is only 8 bits in size.

The problem

8 bits is too few to keep all FP related state and options. So in https://reviews.llvm.org/D72841 FPOptions was moved out of BinaryOperatorBitfields and made a field in classes UnaryOperator, BinaryOperator, CallExpr and CXXOperatorCallExpr. It is a step in right direction as, for example, now UnaryOperator does not have FPOption.

This change however resulted in that every instance of BinaryOperator, CallExpr and other now increase in size. To reduce the space refactoring of https://reviews.llvm.org/D72841 was started.

Current support of FP environment in clang is weak, ongoing development in this direction will eventually increase size required for FP state and options and increase number of AST nodes that require knowledge of FP environment (these could be FloatingLiteral, InitListExpr, CXXDefaultArgExpr and some other). We must elaborate solution that allow future extensions without massive changes.

Possible solutions

1. Consider memory consumption as inevitable evil and add FPOtions to every node where it is needed.

2. Add FPOptions to subclasses, for example FloatingUnaryOperator and similar. It however would swamp class hierarchy with duplicates and make handling AST more complex, as we use static polymorphism, not dynamic.

3. Use trailing objects. While this way is natural when the size of additional information is variable, using trailing objects to keep just some fields of an object is inconvenient.

4. Keep FPOptions is a special node, say FloatingPointRegionStmt. As now FP environment can be modified only at block and function level, actually the same FPOption is replicated to all nodes of that block. This FloatingPointRegionStmt would be similar to nodes like ExprWithCleanups, as it would represent a notion rather than source code construct. We also could embed FPOptions directly into CompoundStmt, but we must also provide similar facility at least for initializers and default arguments.

I am inclined to the solution 4, as it reduces size consumption and at the same time does not limit implementation of FPOptions.

Thanks,
--Serge

_______________________________________________
cfe-dev mailing list
[hidden email]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev
Reply | Threaded
Open this post in threaded view
|

Re: Design of FPOptions

suyash singh via cfe-dev

Oh I like Option 4.  I think I understand what you mean.  Clang would create and insert a “floating point region statement” into the code stream whenever we need to modify the floating point state.

 

Globally there is the floating point state that comes from the command line options.  Any pragma that modifies the floating point state must occur at the start of a block, this would get encoded into FloatingPointRegionStmt. Any blocks nested within this block takes the settings of the outer block. The floating point state reverts to the surrounding state when the block is exited.  Expression nodes no longer need to carry around FPOptions.

 

float f(float a, float b) {

#pragma float_control(except, off) // the floating point environment from the command line is merged with the settings from the pragma and inserted at the beginning of the compound statement

return a*b + 3;

}

 

Using Trailing storage on BinaryOperator is hard because CompoundAssignment derives from BinaryOperator and Trailing storage demands that BinaryOperator be finalized.

 

Also I got an email a while ago – I think that floating point state also affects complex floating point literals. Intrinsics are called for these literals and so we’d need the information during codegen for those expressions too.

 

From: Serge Pavlov <[hidden email]>
Sent: Thursday, March 12, 2020 2:24 AM
To: Clang Dev <[hidden email]>
Cc: [hidden email]; Sam McCall <[hidden email]>; Blower, Melanie I <[hidden email]>; Theko Lekena <[hidden email]>; Nicolas Lesser <[hidden email]>; Han Shen <[hidden email]>; Robinson, Paul <[hidden email]>
Subject: Design of FPOptions

 

Hi all,

 

There are development efforts aimed at reducing space occupied by the field FPFeatures (https://reviews.llvm.org/D72841). The expected result is reduction of the FPFeatures size from 8 bits to 7, so it is really a battle for bit. I have concern that such work can impact readability and maintainability of the code and would like to discuss alternative ways.

Background

There are two main things that form background of the problem.

First, operations that involve floating point arguments need to specify floating point environment. The latter is comprised of bits of hardware state registers  (rounding direction, denormals behavior, exception mask) and options and hints provided by user to compiler (whether NANs may occur, shall sign of zero be conserved, may exceptions be ignored). The list of relevant options was discussed in http://lists.llvm.org/pipermail/llvm-dev/2020-January/138652.html. It is convenient to collect all aspects of the FP environment into one object, this is what class FPOptions does. Now this class contains only few attributes but in https://reviews.llvm.org/D72841 an attempt was made to extend it in right direction.

Second, the Clang internal representation (AST) was implemented to be as compact as possible. The dark side of this principle is inconvenient class organization and diffuse class boundaries, - derived classes keep their states in parent class fields, which are fixed in size. FP environment now is represented by a field in the class BinaryOperatorBitfields and is only 8 bits in size.

The problem

8 bits is too few to keep all FP related state and options. So in https://reviews.llvm.org/D72841 FPOptions was moved out of BinaryOperatorBitfields and made a field in classes UnaryOperator, BinaryOperator, CallExpr and CXXOperatorCallExpr. It is a step in right direction as, for example, now UnaryOperator does not have FPOption.

This change however resulted in that every instance of BinaryOperator, CallExpr and other now increase in size. To reduce the space refactoring of https://reviews.llvm.org/D72841 was started.

Current support of FP environment in clang is weak, ongoing development in this direction will eventually increase size required for FP state and options and increase number of AST nodes that require knowledge of FP environment (these could be FloatingLiteral, InitListExpr, CXXDefaultArgExpr and some other). We must elaborate solution that allow future extensions without massive changes.

Possible solutions

1. Consider memory consumption as inevitable evil and add FPOtions to every node where it is needed.


2. Add FPOptions to subclasses, for example FloatingUnaryOperator and similar. It however would swamp class hierarchy with duplicates and make handling AST more complex, as we use static polymorphism, not dynamic.


3. Use trailing objects. While this way is natural when the size of additional information is variable, using trailing objects to keep just some fields of an object is inconvenient.


4. Keep FPOptions is a special node, say FloatingPointRegionStmt. As now FP environment can be modified only at block and function level, actually the same FPOption is replicated to all nodes of that block. This FloatingPointRegionStmt would be similar to nodes like ExprWithCleanups, as it would represent a notion rather than source code construct. We also could embed FPOptions directly into CompoundStmt, but we must also provide similar facility at least for initializers and default arguments.

I am inclined to the solution 4, as it reduces size consumption and at the same time does not limit implementation of FPOptions.


Thanks,
--Serge


_______________________________________________
cfe-dev mailing list
[hidden email]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev
Reply | Threaded
Open this post in threaded view
|

Re: Design of FPOptions

suyash singh via cfe-dev
On Thu, Mar 12, 2020 at 3:51 PM Blower, Melanie I
<[hidden email]> wrote:
> Oh I like Option 4.  I think I understand what you mean.  Clang would create and insert a “floating point region statement” into the code stream whenever we need to modify the floating point state.

Or just some sort of PragmaStmt within a block, and some sort of
function-scope attribute to capture global pragma state.  That would
certainly be easier for IR generation.  However, it would be much
harder for clients like the constant evaluator that might be sensitive
to context-sensitive evaluation settings, to the extent that some of
these pragmas affect formal evaluation rules (by e.g. specifying
static but non-standard rounding) rather than just allowing looser
optimization.

I want to note that using as few bits as possible is only important to
the extent that you need these bits in common representations.  The
narrower the case in which extra storage is required, the more extra
storage it's okay to use.  So if you need storage in every binary
operator, you need to be very sparing; if you only need storage in
binary operators on FP types, it's fine to use a little more memory;
if you only need storage in binary operators on FP types when the
pragmas are actually being used, your budget is quite expansive; and
if you can avoid per-expression overhead entirely by doing something
at the scope level — especially if it's only needed when there's  an
explicit pragma in that scope — your budget is basically unlimited.

John.




>
>
>
> Globally there is the floating point state that comes from the command line options.  Any pragma that modifies the floating point state must occur at the start of a block, this would get encoded into FloatingPointRegionStmt. Any blocks nested within this block takes the settings of the outer block. The floating point state reverts to the surrounding state when the block is exited.  Expression nodes no longer need to carry around FPOptions.
>
>
>
> float f(float a, float b) {
>
> #pragma float_control(except, off) // the floating point environment from the command line is merged with the settings from the pragma and inserted at the beginning of the compound statement
>
> return a*b + 3;
>
> }
>
>
>
> Using Trailing storage on BinaryOperator is hard because CompoundAssignment derives from BinaryOperator and Trailing storage demands that BinaryOperator be finalized.
>
>
>
> Also I got an email a while ago – I think that floating point state also affects complex floating point literals. Intrinsics are called for these literals and so we’d need the information during codegen for those expressions too.
>
>
>
> From: Serge Pavlov <[hidden email]>
> Sent: Thursday, March 12, 2020 2:24 AM
> To: Clang Dev <[hidden email]>
> Cc: [hidden email]; Sam McCall <[hidden email]>; Blower, Melanie I <[hidden email]>; Theko Lekena <[hidden email]>; Nicolas Lesser <[hidden email]>; Han Shen <[hidden email]>; Robinson, Paul <[hidden email]>
> Subject: Design of FPOptions
>
>
>
> Hi all,
>
>
>
> There are development efforts aimed at reducing space occupied by the field FPFeatures (https://reviews.llvm.org/D72841). The expected result is reduction of the FPFeatures size from 8 bits to 7, so it is really a battle for bit. I have concern that such work can impact readability and maintainability of the code and would like to discuss alternative ways.
>
> Background
>
> There are two main things that form background of the problem.
>
> First, operations that involve floating point arguments need to specify floating point environment. The latter is comprised of bits of hardware state registers  (rounding direction, denormals behavior, exception mask) and options and hints provided by user to compiler (whether NANs may occur, shall sign of zero be conserved, may exceptions be ignored). The list of relevant options was discussed in http://lists.llvm.org/pipermail/llvm-dev/2020-January/138652.html. It is convenient to collect all aspects of the FP environment into one object, this is what class FPOptions does. Now this class contains only few attributes but in https://reviews.llvm.org/D72841 an attempt was made to extend it in right direction.
>
> Second, the Clang internal representation (AST) was implemented to be as compact as possible. The dark side of this principle is inconvenient class organization and diffuse class boundaries, - derived classes keep their states in parent class fields, which are fixed in size. FP environment now is represented by a field in the class BinaryOperatorBitfields and is only 8 bits in size.
>
> The problem
>
> 8 bits is too few to keep all FP related state and options. So in https://reviews.llvm.org/D72841 FPOptions was moved out of BinaryOperatorBitfields and made a field in classes UnaryOperator, BinaryOperator, CallExpr and CXXOperatorCallExpr. It is a step in right direction as, for example, now UnaryOperator does not have FPOption.
>
> This change however resulted in that every instance of BinaryOperator, CallExpr and other now increase in size. To reduce the space refactoring of https://reviews.llvm.org/D72841 was started.
>
> Current support of FP environment in clang is weak, ongoing development in this direction will eventually increase size required for FP state and options and increase number of AST nodes that require knowledge of FP environment (these could be FloatingLiteral, InitListExpr, CXXDefaultArgExpr and some other). We must elaborate solution that allow future extensions without massive changes.
>
> Possible solutions
>
> 1. Consider memory consumption as inevitable evil and add FPOtions to every node where it is needed.
>
>
> 2. Add FPOptions to subclasses, for example FloatingUnaryOperator and similar. It however would swamp class hierarchy with duplicates and make handling AST more complex, as we use static polymorphism, not dynamic.
>
>
> 3. Use trailing objects. While this way is natural when the size of additional information is variable, using trailing objects to keep just some fields of an object is inconvenient.
>
>
> 4. Keep FPOptions is a special node, say FloatingPointRegionStmt. As now FP environment can be modified only at block and function level, actually the same FPOption is replicated to all nodes of that block. This FloatingPointRegionStmt would be similar to nodes like ExprWithCleanups, as it would represent a notion rather than source code construct. We also could embed FPOptions directly into CompoundStmt, but we must also provide similar facility at least for initializers and default arguments.
>
> I am inclined to the solution 4, as it reduces size consumption and at the same time does not limit implementation of FPOptions.
>
>
> Thanks,
> --Serge
_______________________________________________
cfe-dev mailing list
[hidden email]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev
Reply | Threaded
Open this post in threaded view
|

Re: Design of FPOptions

suyash singh via cfe-dev
On Fri, Mar 13, 2020 at 3:17 AM John McCall <[hidden email]> wrote:
On Thu, Mar 12, 2020 at 3:51 PM Blower, Melanie I
<[hidden email]> wrote:
> Oh I like Option 4.  I think I understand what you mean.  Clang would create and insert a “floating point region statement” into the code stream whenever we need to modify the floating point state.

Or just some sort of PragmaStmt within a block, and some sort of
function-scope attribute to capture global pragma state.

Different blocks inside the same function may have different FP environment, so FP attributes cannot have function-scope, we should associate them with CompoundStmt. We might have a bit in the state of CompoundStmt to indicate that FP state in it is modified. The relevant FP attributes could be kept in some node inside the compound statement. PragmaStmt could a good choice for the node name, as all state modification are made by pragmas so far. It also could be used to keep information of other pragmas, if that matters.

However we need something similar for initializers and default arguments at least. Statement cannot be used here, so we need also introduce PragmaDecl. This node may be instead of PragmaStmt, if enveloped with DeclStmt. 

  However, it would be much
harder for clients like the constant evaluator that might be sensitive
to context-sensitive evaluation settings, to the extent that some of
these pragmas affect formal evaluation rules (by e.g. specifying
static but non-standard rounding) rather than just allowing looser
optimization. 

I hope keeping a stack of state pragmas in Sema may alleviate this problem. It may be done by keeping only the top pragma in Sema and saving previous pragma by using RAII objects, as we already do in many cases. CodeGen also need to maintain such stack. Some function, mainly static, will require additional argument, like Sema, as some AST nodes may do not have all information about the operation they represent. The added complexity seems modest.

Thanks,
--Serge


On Fri, Mar 13, 2020 at 3:17 AM John McCall <[hidden email]> wrote:
On Thu, Mar 12, 2020 at 3:51 PM Blower, Melanie I
<[hidden email]> wrote:
> Oh I like Option 4.  I think I understand what you mean.  Clang would create and insert a “floating point region statement” into the code stream whenever we need to modify the floating point state.

Or just some sort of PragmaStmt within a block, and some sort of
function-scope attribute to capture global pragma state.  That would
certainly be easier for IR generation.  However, it would be much
harder for clients like the constant evaluator that might be sensitive
to context-sensitive evaluation settings, to the extent that some of
these pragmas affect formal evaluation rules (by e.g. specifying
static but non-standard rounding) rather than just allowing looser
optimization.

I want to note that using as few bits as possible is only important to
the extent that you need these bits in common representations.  The
narrower the case in which extra storage is required, the more extra
storage it's okay to use.  So if you need storage in every binary
operator, you need to be very sparing; if you only need storage in
binary operators on FP types, it's fine to use a little more memory;
if you only need storage in binary operators on FP types when the
pragmas are actually being used, your budget is quite expansive; and
if you can avoid per-expression overhead entirely by doing something
at the scope level — especially if it's only needed when there's  an
explicit pragma in that scope — your budget is basically unlimited.

John.




>
>
>
> Globally there is the floating point state that comes from the command line options.  Any pragma that modifies the floating point state must occur at the start of a block, this would get encoded into FloatingPointRegionStmt. Any blocks nested within this block takes the settings of the outer block. The floating point state reverts to the surrounding state when the block is exited.  Expression nodes no longer need to carry around FPOptions.
>
>
>
> float f(float a, float b) {
>
> #pragma float_control(except, off) // the floating point environment from the command line is merged with the settings from the pragma and inserted at the beginning of the compound statement
>
> return a*b + 3;
>
> }
>
>
>
> Using Trailing storage on BinaryOperator is hard because CompoundAssignment derives from BinaryOperator and Trailing storage demands that BinaryOperator be finalized.
>
>
>
> Also I got an email a while ago – I think that floating point state also affects complex floating point literals. Intrinsics are called for these literals and so we’d need the information during codegen for those expressions too.
>
>
>
> From: Serge Pavlov <[hidden email]>
> Sent: Thursday, March 12, 2020 2:24 AM
> To: Clang Dev <[hidden email]>
> Cc: [hidden email]; Sam McCall <[hidden email]>; Blower, Melanie I <[hidden email]>; Theko Lekena <[hidden email]>; Nicolas Lesser <[hidden email]>; Han Shen <[hidden email]>; Robinson, Paul <[hidden email]>
> Subject: Design of FPOptions
>
>
>
> Hi all,
>
>
>
> There are development efforts aimed at reducing space occupied by the field FPFeatures (https://reviews.llvm.org/D72841). The expected result is reduction of the FPFeatures size from 8 bits to 7, so it is really a battle for bit. I have concern that such work can impact readability and maintainability of the code and would like to discuss alternative ways.
>
> Background
>
> There are two main things that form background of the problem.
>
> First, operations that involve floating point arguments need to specify floating point environment. The latter is comprised of bits of hardware state registers  (rounding direction, denormals behavior, exception mask) and options and hints provided by user to compiler (whether NANs may occur, shall sign of zero be conserved, may exceptions be ignored). The list of relevant options was discussed in http://lists.llvm.org/pipermail/llvm-dev/2020-January/138652.html. It is convenient to collect all aspects of the FP environment into one object, this is what class FPOptions does. Now this class contains only few attributes but in https://reviews.llvm.org/D72841 an attempt was made to extend it in right direction.
>
> Second, the Clang internal representation (AST) was implemented to be as compact as possible. The dark side of this principle is inconvenient class organization and diffuse class boundaries, - derived classes keep their states in parent class fields, which are fixed in size. FP environment now is represented by a field in the class BinaryOperatorBitfields and is only 8 bits in size.
>
> The problem
>
> 8 bits is too few to keep all FP related state and options. So in https://reviews.llvm.org/D72841 FPOptions was moved out of BinaryOperatorBitfields and made a field in classes UnaryOperator, BinaryOperator, CallExpr and CXXOperatorCallExpr. It is a step in right direction as, for example, now UnaryOperator does not have FPOption.
>
> This change however resulted in that every instance of BinaryOperator, CallExpr and other now increase in size. To reduce the space refactoring of https://reviews.llvm.org/D72841 was started.
>
> Current support of FP environment in clang is weak, ongoing development in this direction will eventually increase size required for FP state and options and increase number of AST nodes that require knowledge of FP environment (these could be FloatingLiteral, InitListExpr, CXXDefaultArgExpr and some other). We must elaborate solution that allow future extensions without massive changes.
>
> Possible solutions
>
> 1. Consider memory consumption as inevitable evil and add FPOtions to every node where it is needed.
>
>
> 2. Add FPOptions to subclasses, for example FloatingUnaryOperator and similar. It however would swamp class hierarchy with duplicates and make handling AST more complex, as we use static polymorphism, not dynamic.
>
>
> 3. Use trailing objects. While this way is natural when the size of additional information is variable, using trailing objects to keep just some fields of an object is inconvenient.
>
>
> 4. Keep FPOptions is a special node, say FloatingPointRegionStmt. As now FP environment can be modified only at block and function level, actually the same FPOption is replicated to all nodes of that block. This FloatingPointRegionStmt would be similar to nodes like ExprWithCleanups, as it would represent a notion rather than source code construct. We also could embed FPOptions directly into CompoundStmt, but we must also provide similar facility at least for initializers and default arguments.
>
> I am inclined to the solution 4, as it reduces size consumption and at the same time does not limit implementation of FPOptions.
>
>
> Thanks,
> --Serge

_______________________________________________
cfe-dev mailing list
[hidden email]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev
Reply | Threaded
Open this post in threaded view
|

Re: Design of FPOptions

suyash singh via cfe-dev
On Fri, Mar 13, 2020 at 1:28 PM Serge Pavlov <[hidden email]> wrote:

> On Fri, Mar 13, 2020 at 3:17 AM John McCall <[hidden email]> wrote:
>> On Thu, Mar 12, 2020 at 3:51 PM Blower, Melanie I
>> <[hidden email]> wrote:
>> > Oh I like Option 4.  I think I understand what you mean.  Clang would create and insert a “floating point region statement” into the code stream whenever we need to modify the floating point state.
>>
>> Or just some sort of PragmaStmt within a block, and some sort of
>> function-scope attribute to capture global pragma state.
>
>
> Different blocks inside the same function may have different FP environment, so FP attributes cannot have function-scope, we should associate them with CompoundStmt.

I did say that the attribute would just capture the global pragma
state, i.e. the current file-scope pragmas in effect.  You would still
need a PragmaStmt in order to express block-local pragmas, of course.
If you're saying that you could just use an implicit PragmaStmt in the
top-level CompoundStmt instead of an attribute, well, that's
theoretically possible, but you'll need to worry about constructors
(because the base-or-member-initializers are executed before the
block) and function-try-blocks.  Also, I believe you want an attribute
on the definition anyway in order to record whether it has local
pragmas, since that changes how we have to generate code for the rest
of it.

> However we need something similar for initializers and default arguments at least. Statement cannot be used here, so we need also introduce PragmaDecl. This node may be instead of PragmaStmt, if enveloped with DeclStmt.

I don't know how PragmaDecl would fix anything for initializers and
default arguments.  It's not like normal expressions can embed
pragmas.

>>   However, it would be much
>> harder for clients like the constant evaluator that might be sensitive
>> to context-sensitive evaluation settings, to the extent that some of
>> these pragmas affect formal evaluation rules (by e.g. specifying
>> static but non-standard rounding) rather than just allowing looser
>> optimization.
>
>
> I hope keeping a stack of state pragmas in Sema may alleviate this problem. It may be done by keeping only the top pragma in Sema and saving previous pragma by using RAII objects, as we already do in many cases. CodeGen also need to maintain such stack. Some function, mainly static, will require additional argument, like Sema, as some AST nodes may do not have all information about the operation they represent. The added complexity seems modest.

The constant evaluator is not part of Sema, and maintaining global
state is dangerous because we often need to try to evaluate things
that were written in different contexts.

John.
_______________________________________________
cfe-dev mailing list
[hidden email]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev
Reply | Threaded
Open this post in threaded view
|

Re: Design of FPOptions

suyash singh via cfe-dev
In reply to this post by suyash singh via cfe-dev
I don't have a lot to add here but wanted to say thanks for thinking deeply about this.
Us trying to squeeze into 7 bits was a pretty shallow+short-term view that might make sense if FP was fairly static. Fortunately the immediate need for that has passed (thanks Roman!)

Growing in #bits suggested that TrailingObjects makes sense, and growing in applicable scope means even that might not be enough (if it's used in more and more nodes). So I guess this was bound to come up, and solution #4 sounds like the most scalable answer, though I don't know the domain well.

On Thu, Mar 12, 2020 at 7:24 AM Serge Pavlov <[hidden email]> wrote:
Hi all,

There are development efforts aimed at reducing space occupied by the field FPFeatures (https://reviews.llvm.org/D72841). The expected result is reduction of the FPFeatures size from 8 bits to 7, so it is really a battle for bit. I have concern that such work can impact readability and maintainability of the code and would like to discuss alternative ways.

Background

There are two main things that form background of the problem.

First, operations that involve floating point arguments need to specify floating point environment. The latter is comprised of bits of hardware state registers  (rounding direction, denormals behavior, exception mask) and options and hints provided by user to compiler (whether NANs may occur, shall sign of zero be conserved, may exceptions be ignored). The list of relevant options was discussed in http://lists.llvm.org/pipermail/llvm-dev/2020-January/138652.html. It is convenient to collect all aspects of the FP environment into one object, this is what class FPOptions does. Now this class contains only few attributes but in https://reviews.llvm.org/D72841 an attempt was made to extend it in right direction.

Second, the Clang internal representation (AST) was implemented to be as compact as possible. The dark side of this principle is inconvenient class organization and diffuse class boundaries, - derived classes keep their states in parent class fields, which are fixed in size. FP environment now is represented by a field in the class BinaryOperatorBitfields and is only 8 bits in size.

The problem

8 bits is too few to keep all FP related state and options. So in https://reviews.llvm.org/D72841 FPOptions was moved out of BinaryOperatorBitfields and made a field in classes UnaryOperator, BinaryOperator, CallExpr and CXXOperatorCallExpr. It is a step in right direction as, for example, now UnaryOperator does not have FPOption.

This change however resulted in that every instance of BinaryOperator, CallExpr and other now increase in size. To reduce the space refactoring of https://reviews.llvm.org/D72841 was started.

Current support of FP environment in clang is weak, ongoing development in this direction will eventually increase size required for FP state and options and increase number of AST nodes that require knowledge of FP environment (these could be FloatingLiteral, InitListExpr, CXXDefaultArgExpr and some other). We must elaborate solution that allow future extensions without massive changes.

Possible solutions

1. Consider memory consumption as inevitable evil and add FPOtions to every node where it is needed.

2. Add FPOptions to subclasses, for example FloatingUnaryOperator and similar. It however would swamp class hierarchy with duplicates and make handling AST more complex, as we use static polymorphism, not dynamic.

3. Use trailing objects. While this way is natural when the size of additional information is variable, using trailing objects to keep just some fields of an object is inconvenient.

4. Keep FPOptions is a special node, say FloatingPointRegionStmt. As now FP environment can be modified only at block and function level, actually the same FPOption is replicated to all nodes of that block. This FloatingPointRegionStmt would be similar to nodes like ExprWithCleanups, as it would represent a notion rather than source code construct. We also could embed FPOptions directly into CompoundStmt, but we must also provide similar facility at least for initializers and default arguments.

I am inclined to the solution 4, as it reduces size consumption and at the same time does not limit implementation of FPOptions.

Thanks,
--Serge

_______________________________________________
cfe-dev mailing list
[hidden email]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev
Reply | Threaded
Open this post in threaded view
|

Re: Design of FPOptions

suyash singh via cfe-dev

I posted a review (still a bug or 2) to put FPOptions into BinaryOperator trailing storage. The review is here, https://reviews.llvm.org/D76384

 

I think choice #4 is also reasonable, but John McCall raised concerns about optimization passes like constant folding, and I had already started making changes for #3.

 

BTW I had implemented #2 as part of a bigger patch, but John indicated that #2 doesn’t sufficiently reduce the storage demands.

 

D76384 requires that BinaryOperator and CompoundAssignmentOperator be collapsed into a single class, so the patch is pretty big.  FPOptions are also needed on CXXOperatorCall and I think they are needed on CallExpr too (intrinsic calls are used in the creation of certain floating point literals).  I’d like to know from John, Is it OK to leave FPOptions on CallExpr nodes (see D76384) or do you want them moved to Trailing storage here as well?

 

From: Sam McCall <[hidden email]>
Sent: Tuesday, March 17, 2020 12:55 PM
To: Serge Pavlov <[hidden email]>
Cc: Clang Dev <[hidden email]>; Haojian Wu <[hidden email]>; Blower, Melanie I <[hidden email]>; Theko Lekena <[hidden email]>; Nicolas Lesser <[hidden email]>; Han Shen <[hidden email]>; Robinson, Paul <[hidden email]>
Subject: Re: Design of FPOptions

 

I don't have a lot to add here but wanted to say thanks for thinking deeply about this.

Us trying to squeeze into 7 bits was a pretty shallow+short-term view that might make sense if FP was fairly static. Fortunately the immediate need for that has passed (thanks Roman!)

 

Growing in #bits suggested that TrailingObjects makes sense, and growing in applicable scope means even that might not be enough (if it's used in more and more nodes). So I guess this was bound to come up, and solution #4 sounds like the most scalable answer, though I don't know the domain well.

 

On Thu, Mar 12, 2020 at 7:24 AM Serge Pavlov <[hidden email]> wrote:

Hi all,

 

There are development efforts aimed at reducing space occupied by the field FPFeatures (https://reviews.llvm.org/D72841). The expected result is reduction of the FPFeatures size from 8 bits to 7, so it is really a battle for bit. I have concern that such work can impact readability and maintainability of the code and would like to discuss alternative ways.

Background

There are two main things that form background of the problem.

First, operations that involve floating point arguments need to specify floating point environment. The latter is comprised of bits of hardware state registers  (rounding direction, denormals behavior, exception mask) and options and hints provided by user to compiler (whether NANs may occur, shall sign of zero be conserved, may exceptions be ignored). The list of relevant options was discussed in http://lists.llvm.org/pipermail/llvm-dev/2020-January/138652.html. It is convenient to collect all aspects of the FP environment into one object, this is what class FPOptions does. Now this class contains only few attributes but in https://reviews.llvm.org/D72841 an attempt was made to extend it in right direction.

Second, the Clang internal representation (AST) was implemented to be as compact as possible. The dark side of this principle is inconvenient class organization and diffuse class boundaries, - derived classes keep their states in parent class fields, which are fixed in size. FP environment now is represented by a field in the class BinaryOperatorBitfields and is only 8 bits in size.

The problem

8 bits is too few to keep all FP related state and options. So in https://reviews.llvm.org/D72841 FPOptions was moved out of BinaryOperatorBitfields and made a field in classes UnaryOperator, BinaryOperator, CallExpr and CXXOperatorCallExpr. It is a step in right direction as, for example, now UnaryOperator does not have FPOption.

This change however resulted in that every instance of BinaryOperator, CallExpr and other now increase in size. To reduce the space refactoring of https://reviews.llvm.org/D72841 was started.

Current support of FP environment in clang is weak, ongoing development in this direction will eventually increase size required for FP state and options and increase number of AST nodes that require knowledge of FP environment (these could be FloatingLiteral, InitListExpr, CXXDefaultArgExpr and some other). We must elaborate solution that allow future extensions without massive changes.

Possible solutions

1. Consider memory consumption as inevitable evil and add FPOtions to every node where it is needed.


2. Add FPOptions to subclasses, for example FloatingUnaryOperator and similar. It however would swamp class hierarchy with duplicates and make handling AST more complex, as we use static polymorphism, not dynamic.


3. Use trailing objects. While this way is natural when the size of additional information is variable, using trailing objects to keep just some fields of an object is inconvenient.


4. Keep FPOptions is a special node, say FloatingPointRegionStmt. As now FP environment can be modified only at block and function level, actually the same FPOption is replicated to all nodes of that block. This FloatingPointRegionStmt would be similar to nodes like ExprWithCleanups, as it would represent a notion rather than source code construct. We also could embed FPOptions directly into CompoundStmt, but we must also provide similar facility at least for initializers and default arguments.

I am inclined to the solution 4, as it reduces size consumption and at the same time does not limit implementation of FPOptions.


Thanks,
--Serge


_______________________________________________
cfe-dev mailing list
[hidden email]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev