Conditional Compilation

Topics related to the use of Oberon language features
Post Reply
cfbsoftware
Site Admin
Posts: 534
Joined: Fri Dec 31, 2010 12:30 pm
Contact:

Conditional Compilation

Post by cfbsoftware » Tue Feb 04, 2025 1:09 am

In the process of testing a module it is often very useful to add diagnostic traces to monitor progress. As these diagnostics are generally only required for development they can be turned off for the release version of the application using various CONST declarations.

A new optimisation feature, Conditional Compilation, has been developed for the next feature release of Astrobe. Sections of code are now eliminated from the executable if they are included in an IF statement controlled by a BOOLEAN value declared in a CONST declaration as FALSE. The end result uses less memory and can result in improved performance.

This feature could have been implemented in a number of different ways but some of the advantages of this solution are that it is requires no change to the Oberon Language and can benefit existing source code without requiring any modifications.

The following is an example of how the feature can be exploited. This technique is particularly useful as it does not require any source code to be edited to switch from the test version of an application to the release version:

1. Implement a module e.g. Trace which includes a constant declaration enabled and a number of procedures to handle diagnostic information e.g.:

Code: Select all

       
MODULE Trace;
CONST
  enabled* = TRUE;

PROCEDURE Message*(s: ARRAY OF CHAR);
  ...
  ...
2. Your application could then include:

Code: Select all

IMPORT Trace;
  ...
  IF Trace.enabled THEN Trace.Message("Init started") END;
If Trace.enabled is declared as FALSE the resulting linked application is identical to what it would be if the entire IF statement did not exist.

3. Implement two versions of the Trace module, each in a separate folder. The release folder has the one with enabled set TRUE and a full implementation of each diagnostic procedure. The test folder has the one with enabled set FALSE and an empty body for each diagnostic procedure.

4. Create two configuration files for your application: a release configuration with a search path that includes the release folder and a test configuration file with a search path that includes the test folder. Use the appropriate one when you compile and link your application.

If you then need to check whether or not your code has been optimised:
  • At the module level, the disassembler listings clearly show statements for which no code has been generated.
  • At the application level the map file shows which folder contained the trace module that was actually linked.
Last edited by cfbsoftware on Wed Feb 05, 2025 8:54 pm, edited 1 time in total.
Reason: Corrected: VAR --> CONST

gray
Posts: 150
Joined: Tue Feb 12, 2019 2:59 am
Location: Mauritius

Re: Conditional Compilation

Post by gray » Wed Feb 05, 2025 12:06 pm

What would be the compilation and linking result of the following code, using your example (or similar, possibly nested, IF structures):

Code: Select all

  IF Trace.enabled THEN
    Trace.Message("Init started")
  ELSIF OtherTrace.enabled THEN
    (* ... *)
  ELSE
    (* ... *)
  END;
where 'OtherTrace' is some other tracing module, and '(* ... *)' represents valid code?

PS: Should:

Code: Select all

  VAR
    enabled* = TRUE;
not read:

Code: Select all

  CONST
   enabled* = TRUE;

cfbsoftware
Site Admin
Posts: 534
Joined: Fri Dec 31, 2010 12:30 pm
Contact:

Re: Conditional Compilation

Post by cfbsoftware » Wed Feb 05, 2025 9:03 pm

There are four possible combinations for the values of Trace.enabled (TRUE / FALSE) and OtherTrace.enabled (TRUE / FALSE). This is shown by the following hypothetical example. None of the calls to the procedure Eliminated are generated.

Code: Select all

MODULE Elsif;

CONST
  F1 = FALSE;
  F2 = FALSE;
  T1 = TRUE;
  T2 = TRUE;

PROCEDURE Eliminated();
BEGIN
END Eliminated;

PROCEDURE Included();
BEGIN
END Included;

PROCEDURE P0();
(* Both eliminated *)
BEGIN
  IF F1 THEN
    Eliminated()
  ELSIF F2 THEN
    Eliminated()
  END
END P0;

PROCEDURE P1();
(* First eliminated *)
BEGIN
  IF F1 THEN
    Eliminated()
  ELSIF T2 THEN
    Included()
  END
END P1;

PROCEDURE P2();
(* Second eliminated *)
BEGIN
  IF T1 THEN
    Included()
  ELSIF T2 THEN
    Eliminated()
  END
END P2;

PROCEDURE P3();
(* Second eliminated *)
BEGIN
  IF T1 THEN
    Included()
  ELSIF F2 THEN
    Eliminated()
  END
END P3;

END Elsif.
The disassembler listing is attached.

ELSE clauses are handled similarly. Nested IFs are only ever eliminated if the IF statement that contains them is eliminated.

Thank you for picking up the VAR / CONST mistake in my earlier post. It has now been corrected.
Attachments
Elsif.zip
Source code and diassembler listing of the ELSIF example
(989 Bytes) Downloaded 9 times

gray
Posts: 150
Joined: Tue Feb 12, 2019 2:59 am
Location: Mauritius

Re: Conditional Compilation

Post by gray » Wed Feb 05, 2025 11:50 pm

Thank you for your explanations and example code.
cfbsoftware wrote:
Wed Feb 05, 2025 9:03 pm
Nested IFs are only ever eliminated if the IF statement that contains them is eliminated.
Hence, to be sure I understand this correctly, using your example code:

Code: Select all

  CONST
    T0 = TRUE;
    F1 = FALSE;
    
  PROCEDURE P4();
  BEGIN
    IF T0 THEN
      IF F1 THEN
        Included() (* ?? *)
      END
    END
  END P4;
Does the quoted statement mean that the nested 'IF F1 ...' gets included?

cfbsoftware
Site Admin
Posts: 534
Joined: Fri Dec 31, 2010 12:30 pm
Contact:

Re: Conditional Compilation

Post by cfbsoftware » Thu Feb 06, 2025 1:03 am

gray wrote:
Wed Feb 05, 2025 11:50 pm
Does the quoted statement mean that the nested 'IF F1 ...' gets included?
Yes. If you compiled your code with v10 you would get the same code generated as if you compiled the following with v9.3:

Code: Select all

CONST
  T0 = TRUE;
  F1 = FALSE;
    
PROCEDURE P4();
BEGIN
  IF F1 THEN
    Included() (* ?? *)
  END
END P4;

gray
Posts: 150
Joined: Tue Feb 12, 2019 2:59 am
Location: Mauritius

Re: Conditional Compilation

Post by gray » Thu Feb 06, 2025 10:39 am

Thanks for the clarification. And this strictly works with BOOLEAN CONSTs only, correct?

With this facility, we could now structure modules thusly.

Code: Select all

MODULE Variant;
  CONST
    RP2040* = TRUE;
    RP2350* = FALSE;
END Variant.

MODULE M;
  IMPORT Variant;

  PROCEDURE Init*;
  BEGIN
    IF Variant.RP2040 THEN
      (* ... *)
    ELSIF Variant.RP2350 THEN
      (* ... *)
    END
  END Init;
END M.
While I wouldn't consider to use this in general, as it easily results in messy code with hard to track down defects (even though not as bad as C #ifdef and friends), it might come handy for certain library modules, which are nearly the same for both MCUs, but differ in some way, for example their initialisation. This could result in less code duplication, and easier maintenance -- if used in moderation. :)

Take Astrobe's Traps module, or RTK's RuntimeErrors module, which could be written to work on both MCUs for the error and fault handling running the exact same code, but require a different initialisation (vector table, fault enabling, and so on).

What do you think, from a conceptual and programmer's point of view?

cfbsoftware
Site Admin
Posts: 534
Joined: Fri Dec 31, 2010 12:30 pm
Contact:

Re: Conditional Compilation

Post by cfbsoftware » Thu Feb 06, 2025 11:51 pm

gray wrote:
Thu Feb 06, 2025 10:39 am
And this strictly works with BOOLEAN CONSTs only, correct?
Correct.

It has always been possible to parameterise code in the way you describe - the Conditional Compilation feature just makes it less inefficient than it otherwise would be. However that was not the intended use of the feature and I would not recommend it for that purpose. It is difficult enough to comprehend RP2040 code without having to navigate around RP2350 code as well. In the times when you are interested in the differences between the systems use a file comparison tool for the purpose.

Having said that, if you do have a compelling reason to use Conditional Compilation in that way, defend yourself against possible future additional variants by adding an ELSE clause to the tests e.g.:

Code: Select all

...
...
    IF Variant.RP2040 THEN
      (* ... *)
    ELSIF Variant.RP2350 THEN
      (* ... *)
    ELSE
      ASSERT(FALSE, ...)
    END
...
...

Post Reply