Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]


Groups > comp.arch.embedded > #30465 > unrolled thread

C++, Ada, ...

Started bypozz <pozzugno@gmail.com>
First post2021-04-17 17:48 +0200
Last post2021-04-18 14:39 +0200
Articles 20 on this page of 58 — 12 participants

Back to article view | Back to comp.arch.embedded


Contents

  C++, Ada, ... pozz <pozzugno@gmail.com> - 2021-04-17 17:48 +0200
    Re: C++, Ada, ... David Brown <david.brown@hesbynett.no> - 2021-04-17 18:45 +0200
      Re: C++, Ada, ... Tom Gardner <spamjunk@blueyonder.co.uk> - 2021-04-17 19:55 +0100
        Re: C++, Ada, ... David Brown <david.brown@hesbynett.no> - 2021-04-18 12:53 +0200
          Re: C++, Ada, ... Niklas Holsti <niklas.holsti@tidorum.invalid> - 2021-04-18 18:48 +0300
            Re: C++, Ada, ... David Brown <david.brown@hesbynett.no> - 2021-04-18 18:59 +0200
              Re: C++, Ada, ... Niklas Holsti <niklas.holsti@tidorum.invalid> - 2021-04-19 13:51 +0300
                Re: C++, Ada, ... David Brown <david.brown@hesbynett.no> - 2021-04-19 14:22 +0200
                  Re: C++, Ada, ... Niklas Holsti <niklas.holsti@tidorum.invalid> - 2021-04-19 19:16 +0300
                    Re: C++, Ada, ... David Brown <david.brown@hesbynett.no> - 2021-04-19 22:19 +0200
                      Re: C++, Ada, ... Niklas Holsti <niklas.holsti@tidorum.invalid> - 2021-04-20 01:03 +0300
                        Re: C++, Ada, ... Richard Damon <Richard@Damon-Family.org> - 2021-04-19 18:09 -0400
                        Re: C++, Ada, ... David Brown <david.brown@hesbynett.no> - 2021-04-20 06:58 +0200
                          Re: C++, Ada, ... Paul Rubin <no.email@nospam.invalid> - 2021-04-19 23:18 -0700
                            Re: C++, Ada, ... David Brown <david.brown@hesbynett.no> - 2021-04-20 09:36 +0200
                Re: C++, Ada, ... Paul Rubin <no.email@nospam.invalid> - 2021-04-19 12:20 -0700
                  Re: C++, Ada, ... Niklas Holsti <niklas.holsti@tidorum.invalid> - 2021-04-20 00:26 +0300
                    Re: C++, Ada, ... Paul Rubin <no.email@nospam.invalid> - 2021-04-19 16:01 -0700
                      Re: C++, Ada, ... Niklas Holsti <niklas.holsti@tidorum.invalid> - 2021-04-20 10:47 +0300
                  Re: C++, Ada, ... Tom Gardner <spamjunk@blueyonder.co.uk> - 2021-04-19 23:30 +0100
      Re: C++, Ada, ... Clifford Heath <no.spam@please.net> - 2021-04-18 08:29 +1000
      Re: C++, Ada, ... Niklas Holsti <niklas.holsti@tidorum.invalid> - 2021-04-18 10:39 +0300
        Re: C++, Ada, ... David Brown <david.brown@hesbynett.no> - 2021-04-18 14:59 +0200
          Re: C++, Ada, ... Niklas Holsti <niklas.holsti@tidorum.invalid> - 2021-04-18 19:23 +0300
            Re: C++, Ada, ... David Brown <david.brown@hesbynett.no> - 2021-04-18 19:26 +0200
              Re: C++, Ada, ... Tom Gardner <spamjunk@blueyonder.co.uk> - 2021-04-18 19:29 +0100
                Re: C++, Ada, ... David Brown <david.brown@hesbynett.no> - 2021-04-18 22:23 +0200
                  Re: C++, Ada, ... Tom Gardner <spamjunk@blueyonder.co.uk> - 2021-04-18 23:09 +0100
                    Re: C++, Ada, ... David Brown <david.brown@hesbynett.no> - 2021-04-19 08:56 +0200
                  Re: C++, Ada, ... Niklas Holsti <niklas.holsti@tidorum.invalid> - 2021-04-19 14:04 +0300
                    Re: C++, Ada, ... Dimiter_Popoff <dp@tgi-sci.com> - 2021-04-19 15:16 +0300
                      Re: C++, Ada, ... Paul Rubin <no.email@nospam.invalid> - 2021-04-19 12:47 -0700
                        Re: C++, Ada, ... Niklas Holsti <niklas.holsti@tidorum.invalid> - 2021-04-20 00:37 +0300
                          Re: C++, Ada, ... David Brown <david.brown@hesbynett.no> - 2021-04-20 07:50 +0200
                    Re: C++, Ada, ... David Brown <david.brown@hesbynett.no> - 2021-04-19 14:26 +0200
              Re: C++, Ada, ... Tom Gardner <spamjunk@blueyonder.co.uk> - 2021-04-18 19:30 +0100
                Re: C++, Ada, ... David Brown <david.brown@hesbynett.no> - 2021-04-18 22:17 +0200
                  Re: C++, Ada, ... Tom Gardner <spamjunk@blueyonder.co.uk> - 2021-04-18 23:17 +0100
                    Re: C++, Ada, ... David Brown <david.brown@hesbynett.no> - 2021-04-19 08:52 +0200
                      Re: C++, Ada, ... Tom Gardner <spamjunk@blueyonder.co.uk> - 2021-04-19 10:08 +0100
                        Re: C++, Ada, ... David Brown <david.brown@hesbynett.no> - 2021-04-19 12:15 +0200
                          Re: C++, Ada, ... Tom Gardner <spamjunk@blueyonder.co.uk> - 2021-04-19 12:00 +0100
                      Re: C++, Ada, ... Tauno Voipio <tauno.voipio@notused.fi.invalid> - 2021-04-19 16:15 +0300
                        Re: C++, Ada, ... David Brown <david.brown@hesbynett.no> - 2021-04-19 15:43 +0200
      Re: C++, Ada, ... pozz <pozzugno@gmail.com> - 2021-04-19 18:38 +0200
        Re: C++, Ada, ... Paul Rubin <no.email@nospam.invalid> - 2021-04-19 10:48 -0700
        Re: C++, Ada, ... Tom Gardner <spamjunk@blueyonder.co.uk> - 2021-04-19 19:33 +0100
          Re: C++, Ada, ... Tauno Voipio <tauno.voipio@notused.fi.invalid> - 2021-04-20 15:09 +0300
            Re: C++, Ada, ... Tom Gardner <spamjunk@blueyonder.co.uk> - 2021-04-20 15:29 +0100
        Re: C++, Ada, ... David Brown <david.brown@hesbynett.no> - 2021-04-19 22:48 +0200
          Re: C++, Ada, ... pozz <pozzugno@gmail.com> - 2021-04-19 23:17 +0200
            Re: C++, Ada, ... Paul Rubin <no.email@nospam.invalid> - 2021-04-19 15:55 -0700
              Re: C++, Ada, ... David Brown <david.brown@hesbynett.no> - 2021-04-20 08:21 +0200
            Re: C++, Ada, ... David Brown <david.brown@hesbynett.no> - 2021-04-20 08:20 +0200
    Re: C++, Ada, ... Dave Nadler <drn@nadler.com> - 2021-04-17 21:07 -0400
      Re: C++, Ada, ... Don Y <blockedofcourse@foo.invalid> - 2021-04-17 18:53 -0700
      Re: C++, Ada, ... Tom Gardner <spamjunk@blueyonder.co.uk> - 2021-04-18 09:42 +0100
    Re: C++, Ada, ... dalai lamah <antonio12358@hotmail.com> - 2021-04-18 14:39 +0200

Page 1 of 3  [1] 2 3  Next page →


#30465 — C++, Ada, ...

Frompozz <pozzugno@gmail.com>
Date2021-04-17 17:48 +0200
SubjectC++, Ada, ...
Message-ID<s5f01b$4j3$1@dont-email.me>
What do you think about different languages than usual C for embedded 
systems?

I mean C++, Ada but also Python. Always more powerful embedded 
processors are coming, so I expect new and modern languages will enter 
in the embedded world.

Hardware are cheaper and more powerful than ever, but software stays 
expensive. New and modern languages could reduce the software cost, 
because they are simpler than C and much more similar to desktop/server 
programming paradigm.

We embedded sw developers were lucky: electronics and technologies 
change rapidly, but sw for embedded has changed slower than 
desktop/mobile sw. Think of mobile app developers: maybe they already 
changed IDEs, tools and languages ten times in a few years.
C language for embedded is today very similar than 10 years ago.

However I think this situation for embedded developers will change in 
the very next years. And we will be forced to learn modern technologies, 
such as new languages and tools.
Is it ok for me to study and learn new things... but it will be more 
difficult to acquire new skills for real jobs.

What do you think?

[toc] | [next] | [standalone]


#30467

FromDavid Brown <david.brown@hesbynett.no>
Date2021-04-17 18:45 +0200
Message-ID<s5f3c1$s06$1@dont-email.me>
In reply to#30465
On 17/04/2021 17:48, pozz wrote:
> What do you think about different languages than usual C for embedded
> systems?
> 
> I mean C++, Ada but also Python. Always more powerful embedded
> processors are coming, so I expect new and modern languages will enter
> in the embedded world.
> 
> Hardware are cheaper and more powerful than ever, but software stays
> expensive. New and modern languages could reduce the software cost,
> because they are simpler than C and much more similar to desktop/server
> programming paradigm.
> 
> We embedded sw developers were lucky: electronics and technologies
> change rapidly, but sw for embedded has changed slower than
> desktop/mobile sw. Think of mobile app developers: maybe they already
> changed IDEs, tools and languages ten times in a few years.
> C language for embedded is today very similar than 10 years ago.
> 
> However I think this situation for embedded developers will change in
> the very next years. And we will be forced to learn modern technologies,
> such as new languages and tools.
> Is it ok for me to study and learn new things... but it will be more
> difficult to acquire new skills for real jobs.
> 
> What do you think?

You should probably add Rust to your list - I think its popularity will
increase.

Python is great when you have the resources.  It's the language I use
most on PC's and servers, and it is very common on embedded Linux
systems (like Pi's, and industrial equivalents).  Micropython is
sometimes found on smaller systems, such as ESP32 devices.

Ada involves a fair amount of change to how you work, compared to C
development.  (Disclaimer - I have only done a very small amount of Ada
coding, and no serious projects.  But I have considered it as an
option.)  I really don't see it becoming much more common, and outside
of niche areas (defence, aerospace) it is very rare.  Programming in Ada
often takes a lot more effort even for simple things, leading quickly to
code that is so wordy that it is unclear what is going on.  And most of
the advantages of Ada (such as better typing) can be achieved in C++
with less effort, and at the same time C++ can give additional safety on
resources that is harder to get on Ada.  (But Ada has some nice features
introspective that C++ programmers currently only dream about.)

C++ is not uncommon in embedded systems, and I expect to see more of it.
 I use it as my main embedded language now.  C++ gives more scope for
getting things wrong in weird ways, and more scope for accidentally
making your binary massively larger than you expect, but with care it
makes it a lot easier to write clear code and safe code, where common C
mistakes either can't happen or you get compile-time failures.

It used to be the case that C++ compilers were expensive and poor
quality, that the resulting code was very slow on common
microcontrollers, and that the language didn't have much extra to offer
small-systems embedded developers.  That, I think, has changed in all
aspects.  I use gcc 10 with C++17 on Cortex-M devices, and features like
templates, strong enumerations, std::array, and controlled automatic
conversions make it easier to write good code.

[toc] | [prev] | [next] | [standalone]


#30472

FromTom Gardner <spamjunk@blueyonder.co.uk>
Date2021-04-17 19:55 +0100
Message-ID<AaGeI.148580$qHh9.147135@fx18.ams4>
In reply to#30467
On 17/04/21 17:45, David Brown wrote:
> And most of
> the advantages of Ada (such as better typing) can be achieved in C++
> with less effort, and at the same time C++ can give additional safety on
> resources that is harder to get on Ada.  

Interesting. Could you give a quick outline of that?

[toc] | [prev] | [next] | [standalone]


#30485

FromDavid Brown <david.brown@hesbynett.no>
Date2021-04-18 12:53 +0200
Message-ID<s5h32f$edv$1@dont-email.me>
In reply to#30472
On 17/04/2021 20:55, Tom Gardner wrote:
> On 17/04/21 17:45, David Brown wrote:
>> And most of
>> the advantages of Ada (such as better typing) can be achieved in C++
>> with less effort, and at the same time C++ can give additional safety on
>> resources that is harder to get on Ada.  
> 
> Interesting. Could you give a quick outline of that?
> 

Which part?

My understanding of Ada classes is that, like Pascal classes, you need
to explicitly construct and destruct objects.  This gives far greater
scope for programmers to get things wrong than when they are handled
automatically by the language.

On the other hand, some of Ada's type mechanisms make it a lot easier to
make new types with similar properties while remaining distinct (such as
subranges of integer types).  You can do that in C++, but someone needs
to make the effort to make the appropriate class templates.  Thus these
are more often used in Ada coding than in C++ coding.

On the third hand (three hands are always useful for programming), the
wordy nature of type conversions in Ada mean programmers would be
tempted to take shortcuts and skip these extra types.

[toc] | [prev] | [next] | [standalone]


#30494

FromNiklas Holsti <niklas.holsti@tidorum.invalid>
Date2021-04-18 18:48 +0300
Message-ID<ie32luFlvghU1@mid.individual.net>
In reply to#30485
On 2021-04-18 13:53, David Brown wrote:
> On 17/04/2021 20:55, Tom Gardner wrote:
>> On 17/04/21 17:45, David Brown wrote:
>>> And most of
>>> the advantages of Ada (such as better typing) can be achieved in C++
>>> with less effort, and at the same time C++ can give additional safety on
>>> resources that is harder to get on Ada.
>>
>> Interesting. Could you give a quick outline of that?
>>
> 
> Which part?
> 
> My understanding of Ada classes is that, like Pascal classes, you need
> to explicitly construct and destruct objects.  This gives far greater
> scope for programmers to get things wrong than when they are handled
> automatically by the language.


If you mean automatic allocation and deallocation of storage, Ada lets 
you define types that have an "initializer" that is called 
automatically, and can allocate memory if it needs to, and a "finalizer" 
that is automatically called when leaving the scope of the object in 
question. The finalizer does have to explicitly deallocate any 
explicitly allocated and owned resources, and it may have to use 
reference counting for that, for complex data structures.

AUI that also holds for C++, although I believe recent C++ standards may 
have simplified the definition and use of reference-counted data structures.


> On the other hand, some of Ada's type mechanisms make it a lot easier to
> make new types with similar properties while remaining distinct (such as
> subranges of integer types).  You can do that in C++, but someone needs
> to make the effort to make the appropriate class templates.  Thus these
> are more often used in Ada coding than in C++ coding.


(That relative ease in Ada was the point of my other response -- more to 
come on that.)


> On the third hand (three hands are always useful for programming), the
> wordy nature of type conversions in Ada mean programmers would be
> tempted to take shortcuts and skip these extra types.


Huh? A normal conversion in C is written "(newtype)expression", the same 
in Ada is written "newtype(expression)". Exactly the same number of 
characters, only the placement of the () is different. The C form might 
even require an extra set of parentheses around it, to demarcate the 
expression to be converted from any containing expression.

Of course, in C you have implicit conversions between all kinds of 
numerical types, often leading to a whole lot of errors... not only 
apples+oranges, but also truncation or other miscomputation.

[toc] | [prev] | [next] | [standalone]


#30496

FromDavid Brown <david.brown@hesbynett.no>
Date2021-04-18 18:59 +0200
Message-ID<s5hogv$77o$1@dont-email.me>
In reply to#30494
On 18/04/2021 17:48, Niklas Holsti wrote:
> On 2021-04-18 13:53, David Brown wrote:
>> On 17/04/2021 20:55, Tom Gardner wrote:
>>> On 17/04/21 17:45, David Brown wrote:
>>>> And most of
>>>> the advantages of Ada (such as better typing) can be achieved in C++
>>>> with less effort, and at the same time C++ can give additional
>>>> safety on
>>>> resources that is harder to get on Ada.
>>>
>>> Interesting. Could you give a quick outline of that?
>>>
>>
>> Which part?
>>
>> My understanding of Ada classes is that, like Pascal classes, you need
>> to explicitly construct and destruct objects.  This gives far greater
>> scope for programmers to get things wrong than when they are handled
>> automatically by the language.
> 
> 
> If you mean automatic allocation and deallocation of storage, Ada lets
> you define types that have an "initializer" that is called
> automatically, and can allocate memory if it needs to, and a "finalizer"
> that is automatically called when leaving the scope of the object in
> question. The finalizer does have to explicitly deallocate any
> explicitly allocated and owned resources, and it may have to use
> reference counting for that, for complex data structures.

I had a little look for this (these discussions are great for inspiring
learning!).  The impression I got was that it was possible, but what
takes a few lines of C++ (excluding whatever work must be done inside
the constructor and destructor bodies) involves inheriting from a
specific library type.  And you don't have automatic initialisation of
subobjects and ancestors in a controlled order, nor automatic
finalisation of them in the reverse order.

Let's take a little example.  And since this is comp.arch.embedded,
let's take a purely embedded example of disabling interrupts, rather
than shunned dynamic memory allocations:

static inline uint32_t disableGlobalInterrupts(void) {
    uint32_t pri;
    asm volatile(
        "  mrs %[pri], primask\n\t" // Get old mask
        "  cpsid i\n\t"             // Disable interrupts entirely
        "  dsb"	    // Ensures that this takes effect before next
                    // instruction
        : [pri] "=r" (pri) :: "memory");
    return pri;
}

static inline void restoreGlobalInterrupts(uint32_t pri) {
    asm volatile(
        "  msr primask, %[pri]"         // Restore old mask
        :: [pri] "r" (pri) : "memory");
}

class CriticalSectionLock {
private :
    uint32_t oldpri;
public :
    CriticalSectionLock() { oldpri = disableGlobalInterrupts(); }
    ~CriticalSectionLock() { restoreGlobalInterrupts(oldpri); }
};


You can use it like this:

bool compare_and_swap64(uint64_t * p, uint64_t old, uint64_t x)
{
	CriticalSectionLock lock;

	if (*p != old) return false;
	*p = x;
	return true;
}

This is the code compiled for a 32-bit Cortex-M device:

<https://godbolt.org/z/7KM9M6Kcd>

The use of the class here has no overhead compared to manually disabling
and re-enabling interrupts.

What would be the Ada equivalent of this class, and of the
"compare_and_swap64" function?

> 
> AUI that also holds for C++, although I believe recent C++ standards may
> have simplified the definition and use of reference-counted data
> structures.
> 
> 
>> On the other hand, some of Ada's type mechanisms make it a lot easier to
>> make new types with similar properties while remaining distinct (such as
>> subranges of integer types).  You can do that in C++, but someone needs
>> to make the effort to make the appropriate class templates.  Thus these
>> are more often used in Ada coding than in C++ coding.
> 
> 
> (That relative ease in Ada was the point of my other response -- more to
> come on that.)
> 
> 
>> On the third hand (three hands are always useful for programming), the
>> wordy nature of type conversions in Ada mean programmers would be
>> tempted to take shortcuts and skip these extra types.
> 
> 
> Huh? A normal conversion in C is written "(newtype)expression", the same
> in Ada is written "newtype(expression)". Exactly the same number of
> characters, only the placement of the () is different. The C form might
> even require an extra set of parentheses around it, to demarcate the
> expression to be converted from any containing expression.
> 
> Of course, in C you have implicit conversions between all kinds of
> numerical types, often leading to a whole lot of errors... not only
> apples+oranges, but also truncation or other miscomputation.

C also makes explicit conversions wordy, yes.  In C++, you can choose
which conversions are explicit and which are implicit - done carefully,
your safe conversions will be implicit and unsafe ones need to be explicit.

(C++ suffers from its C heritage and backwards compatibility, meaning it
can't fix things that were always implicit conversion.  It's too late to
make "int x = 123.4;" an error.  The best C++ can do is add a new syntax
with better safety - so "int y { 123 };" is fine but "int z { 123.4 };"
is an error.)

[toc] | [prev] | [next] | [standalone]


#30508

FromNiklas Holsti <niklas.holsti@tidorum.invalid>
Date2021-04-19 13:51 +0300
Message-ID<ie55l3F3ou7U1@mid.individual.net>
In reply to#30496
On 2021-04-18 19:59, David Brown wrote:
> On 18/04/2021 17:48, Niklas Holsti wrote:
>> On 2021-04-18 13:53, David Brown wrote:
>>> On 17/04/2021 20:55, Tom Gardner wrote:
>>>> On 17/04/21 17:45, David Brown wrote:
>>>>> And most of
>>>>> the advantages of Ada (such as better typing) can be achieved in C++
>>>>> with less effort, and at the same time C++ can give additional
>>>>> safety on
>>>>> resources that is harder to get on Ada.
>>>>
>>>> Interesting. Could you give a quick outline of that?
>>>>
>>>
>>> Which part?
>>>
>>> My understanding of Ada classes is that, like Pascal classes, you need
>>> to explicitly construct and destruct objects.  This gives far greater
>>> scope for programmers to get things wrong than when they are handled
>>> automatically by the language.
>>
>>
>> If you mean automatic allocation and deallocation of storage, Ada lets
>> you define types that have an "initializer" that is called
>> automatically, and can allocate memory if it needs to, and a "finalizer"
>> that is automatically called when leaving the scope of the object in
>> question. The finalizer does have to explicitly deallocate any
>> explicitly allocated and owned resources, and it may have to use
>> reference counting for that, for complex data structures.
> 
> I had a little look for this (these discussions are great for inspiring
> learning!).  The impression I got was that it was possible, but what
> takes a few lines of C++ (excluding whatever work must be done inside
> the constructor and destructor bodies) involves inheriting from a
> specific library type.


Yes, you must inherit from one of the types Ada.Finalization.Controlled 
or Ada.Finalization.Limited_Controlled when you create a type for which 
you can program an initializer and/or a finalizer.

However, you can aggregate a component of such a type into some other 
composite type, and then that component's initializer and finalizer will 
be called automatically when any object of the containing composite type 
is constructed and destroyed.


> And you don't have automatic initialisation of
> subobjects and ancestors in a controlled order, nor automatic
> finalisation of them in the reverse order.


No, and yes. Subobjects (components) are automatically initialized 
before the composite is initialized (bottom-up), and are automatically 
finalized after the composite is finalized (top-down). But there is no 
automatic invocation of the initializer or finalizer of the parent 
class; that would have to be called explicitly (except in the case of an 
"extension aggregate" expression, where an object of the parent type is 
created and then extended to an object of the derived class).

The Ada initializer and finalizer concept is subtly different from the 
C++ constructor and destructor concept. In Ada, the construction and 
destruction are considered to happen implicitly and automatically. The 
construction step can assign some initial values that can be defined by 
default (pointers default to null, for example) or can be specified for 
the type of the component in question, or can be defined for that 
component explicitly. For example:

    type Down_Counter is range 0 .. 100 with Default_Value => 100;

    type Zero_Handler is access procedure;

    type Counter is record
       Count   : Down_Counter;      -- Implicit init to 100.
       Running : Boolean := False;  -- Explicit init.
       At_Zero : Zero_Handler;      -- Default init to null.
    end record;

Beyond that automatic construction step, the programmable initializer is 
used to perform further automatic activities that may further initialize 
the object, or may have some other effects. For example, we might want 
to automatically register every instance of a Counter (as above) with 
the kernel, and that would be done in the initializer. Conversely, the 
finalizer would then deregister the Counter, before the Counter is 
automatically destroyed (removed from the stack or from the heap).

So the Ada "initializer" is not like a C++ constructor, which in Ada 
corresponds more closely to a function returning an object of the class.

An Ada "finalizer" is more similar to a C++ destructor, taking care of 
any clean-up that is needed before the object disappears.

> 
> Let's take a little example.  And since this is comp.arch.embedded,
> let's take a purely embedded example of disabling interrupts, rather
> than shunned dynamic memory allocations:
> 
> static inline uint32_t disableGlobalInterrupts(void) {
>      uint32_t pri;
>      asm volatile(
>          "  mrs %[pri], primask\n\t" // Get old mask
>          "  cpsid i\n\t"             // Disable interrupts entirely
>          "  dsb"	    // Ensures that this takes effect before next
>                      // instruction
>          : [pri] "=r" (pri) :: "memory");
>      return pri;
> }
> 
> static inline void restoreGlobalInterrupts(uint32_t pri) {
>      asm volatile(
>          "  msr primask, %[pri]"         // Restore old mask
>          :: [pri] "r" (pri) : "memory");
> }


I won't try to write Ada equivalents of the above :-) though I have of 
course written much Ada code to manage and handle interrupts.


> class CriticalSectionLock {
> private :
>      uint32_t oldpri;
> public :
>      CriticalSectionLock() { oldpri = disableGlobalInterrupts(); }
>      ~CriticalSectionLock() { restoreGlobalInterrupts(oldpri); }
> };


Here is the same in Ada. I chose to derive from Limited_Controlled 
because that makes it illegal to assign a Critical_Section value from 
one object to another.

    -- Declaration of the type:

    type Critical_Section is new Ada.Finalization.Limited_Controlled
    with record
       old_pri : Interfaces.Unsigned_32;
    end record;

    overriding procedure Initialize (This : in out Critical_Section);
    overriding procedure Finalize   (This : in out Critical_Section);

    -- Implementation of the operations:

    procedure Initialize (This : in out Critical_Section)
    is begin
       This.old_pri := disableGlobalInterrupts;
    end Initialize;

    procedure Finalize (This : in out Critical_Section)
    is begin
       restoreGlobalInterrupts (This.old_pri);
    end Finalize;


> 
> You can use it like this:
> 
> bool compare_and_swap64(uint64_t * p, uint64_t old, uint64_t x)
> {
> 	CriticalSectionLock lock;
> 
> 	if (*p != old) return false;
> 	*p = x;
> 	return true;
> }
> 


    function Compare_and_Swap64 (
       p      : access Interfaces.Unsigned_64;
       old, x : in     Interfaces.Unsigned_64)
    return Boolean
    is
       Lock : Critical_Section;
    begin
       if p.all /= old then
          return False;
       else
          p.all := x;
          return True;
       end if;
    end Compare_and_Swap64;

(I think there should be a "volatile" spec for the "p" object, don't you?)



> This is the code compiled for a 32-bit Cortex-M device:
> 
> <https://godbolt.org/z/7KM9M6Kcd>
> 
> The use of the class here has no overhead compared to manually disabling
> and re-enabling interrupts.
> 
> What would be the Ada equivalent of this class, and of the
> "compare_and_swap64" function?


See above. I don't have an Ada-to-Cortex-M compiler at hand to compare 
the target code, sorry.

But critical sections in Ada applications are more often written using 
the Ada "protected object" feature. Here is the same as a protected 
object "CS", with separate declaration and body as usual in Ada. Here I 
must write the operation as a procedure instead of a function, because 
protected objects have "single writer, multiple readers" semantics, and 
any function is considered a "reader" although it may have side effects:

    protected CS
       with Priority => System.Interrupt_Priority'Last
    is

       procedure Compare_and_Swap64 (
          p      : access Interfaces.Unsigned_64;
          old, x : in     Interfaces.Unsigned_64;
          result :    out Boolean);

    end CS;

    protected body CS
    is

       procedure Compare_and_Swap64 (
          p      : access Interfaces.Unsigned_64;
          old, x : in     Interfaces.Unsigned_64;
          result :    out Boolean);
       is begin
	  result := p.all = old;
	  if result then
              p.all := x;
           end if;
       end Compare_and_Swap64;

    end CS;

However, it would be more in the style of Ada to focus on the thing that 
is being "compared and swapped", so that "p" would be either a 
discriminant of the protected object, or a component of the protected 
object, instead of a parameter to the copy-and-swap operation. But it 
would look similar to the above.


>>> On the third hand (three hands are always useful for programming), the
>>> wordy nature of type conversions in Ada mean programmers would be
>>> tempted to take shortcuts and skip these extra types.
>>
>>
>> Huh? A normal conversion in C is written "(newtype)expression", the same
>> in Ada is written "newtype(expression)". Exactly the same number of
>> characters, only the placement of the () is different. The C form might
>> even require an extra set of parentheses around it, to demarcate the
>> expression to be converted from any containing expression.
>>
>> Of course, in C you have implicit conversions between all kinds of
>> numerical types, often leading to a whole lot of errors... not only
>> apples+oranges, but also truncation or other miscomputation.
> 
> C also makes explicit conversions wordy, yes.  In C++, you can choose
> which conversions are explicit and which are implicit - done carefully,
> your safe conversions will be implicit and unsafe ones need to be explicit.


Ada does not have programmable implicit conversions, but one can 
override some innocuous operator, usually "+", to perform whatever 
conversions one wants. For example:

    function "+" (Item : Boolean) return Float
    is (if Item then 1.0 else 0.0);

or more directly

    function "+" (Item : Boolean) return Float
    is (Float (Boolean'Pos (Item)));


> (C++ suffers from its C heritage and backwards compatibility, meaning it
> can't fix things that were always implicit conversion.  It's too late to
> make "int x = 123.4;" an error.  The best C++ can do is add a new syntax
> with better safety - so "int y { 123 };" is fine but "int z { 123.4 };"
> is an error.)


Ada also has some warts, but perhaps not as easily illustrated.

[toc] | [prev] | [next] | [standalone]


#30512

FromDavid Brown <david.brown@hesbynett.no>
Date2021-04-19 14:22 +0200
Message-ID<s5jslh$gl8$1@dont-email.me>
In reply to#30508
On 19/04/2021 12:51, Niklas Holsti wrote:
> On 2021-04-18 19:59, David Brown wrote:
>> On 18/04/2021 17:48, Niklas Holsti wrote:
>>> On 2021-04-18 13:53, David Brown wrote:
>>>> On 17/04/2021 20:55, Tom Gardner wrote:
>>>>> On 17/04/21 17:45, David Brown wrote:
>>>>>> And most of
>>>>>> the advantages of Ada (such as better typing) can be achieved in C++
>>>>>> with less effort, and at the same time C++ can give additional
>>>>>> safety on
>>>>>> resources that is harder to get on Ada.
>>>>>
>>>>> Interesting. Could you give a quick outline of that?
>>>>>
>>>>
>>>> Which part?
>>>>
>>>> My understanding of Ada classes is that, like Pascal classes, you need
>>>> to explicitly construct and destruct objects.  This gives far greater
>>>> scope for programmers to get things wrong than when they are handled
>>>> automatically by the language.
>>>
>>>
>>> If you mean automatic allocation and deallocation of storage, Ada lets
>>> you define types that have an "initializer" that is called
>>> automatically, and can allocate memory if it needs to, and a "finalizer"
>>> that is automatically called when leaving the scope of the object in
>>> question. The finalizer does have to explicitly deallocate any
>>> explicitly allocated and owned resources, and it may have to use
>>> reference counting for that, for complex data structures.
>>
>> I had a little look for this (these discussions are great for inspiring
>> learning!).  The impression I got was that it was possible, but what
>> takes a few lines of C++ (excluding whatever work must be done inside
>> the constructor and destructor bodies) involves inheriting from a
>> specific library type.
> 
> 
> Yes, you must inherit from one of the types Ada.Finalization.Controlled
> or Ada.Finalization.Limited_Controlled when you create a type for which
> you can program an initializer and/or a finalizer.
> 
> However, you can aggregate a component of such a type into some other
> composite type, and then that component's initializer and finalizer will
> be called automatically when any object of the containing composite type
> is constructed and destroyed.
> 
> 
>> And you don't have automatic initialisation of
>> subobjects and ancestors in a controlled order, nor automatic
>> finalisation of them in the reverse order.
> 
> 
> No, and yes. Subobjects (components) are automatically initialized
> before the composite is initialized (bottom-up), and are automatically
> finalized after the composite is finalized (top-down). But there is no
> automatic invocation of the initializer or finalizer of the parent
> class; that would have to be called explicitly (except in the case of an
> "extension aggregate" expression, where an object of the parent type is
> created and then extended to an object of the derived class).
> 

OK.  Am I right in assuming the subobjects here also need to inherit
from the "Finalization" types individually, in order to be automatically
initialised in order?

Are there any overheads (other than in the source code) for all this
inheriting?  Ada (like C++) aims to be minimal overhead, AFAIUI, but its
worth checking.


> The Ada initializer and finalizer concept is subtly different from the
> C++ constructor and destructor concept. In Ada, the construction and
> destruction are considered to happen implicitly and automatically. The
> construction step can assign some initial values that can be defined by
> default (pointers default to null, for example) or can be specified for
> the type of the component in question, or can be defined for that
> component explicitly. For example:
> 
>    type Down_Counter is range 0 .. 100 with Default_Value => 100;
> 
>    type Zero_Handler is access procedure;
> 
>    type Counter is record
>       Count   : Down_Counter;      -- Implicit init to 100.
>       Running : Boolean := False;  -- Explicit init.
>       At_Zero : Zero_Handler;      -- Default init to null.
>    end record;
> 
> Beyond that automatic construction step, the programmable initializer is
> used to perform further automatic activities that may further initialize
> the object, or may have some other effects. For example, we might want
> to automatically register every instance of a Counter (as above) with
> the kernel, and that would be done in the initializer. Conversely, the
> finalizer would then deregister the Counter, before the Counter is
> automatically destroyed (removed from the stack or from the heap).
> 
> So the Ada "initializer" is not like a C++ constructor, which in Ada
> corresponds more closely to a function returning an object of the class.
> 
> An Ada "finalizer" is more similar to a C++ destructor, taking care of
> any clean-up that is needed before the object disappears.
> 

C++ gives you the choice.  You can do work in a constructor, or you can
leave it as a minimal (often automatically generated) function.  You can
give default values to members.  You can add "initialise" member
functions as you like.  You can have "factory functions" that generate
instances.  This lets you structure the code and split up functionality
in whatever way suits your requirements.

For a well-structured class, the key point is that a constructor will
always establish the class invariant.  Any publicly accessible function
will assume that invariant, and maintain it.  Private functions might
temporarily break the invariant - these are only accessible by code that
"knows what it is doing".  And the destructor will always clean up after
the object, recycling any resources used.

Having C++ style constructors are not a requirement for having control
of the class invariant, but they do make it more convenient and more
efficient (both at run-time and in the source code) than separate
minimal constructors (or default values) and initialisers.

>>
>> Let's take a little example.  And since this is comp.arch.embedded,
>> let's take a purely embedded example of disabling interrupts, rather
>> than shunned dynamic memory allocations:
>>
>> static inline uint32_t disableGlobalInterrupts(void) {
>>      uint32_t pri;
>>      asm volatile(
>>          "  mrs %[pri], primask\n\t" // Get old mask
>>          "  cpsid i\n\t"             // Disable interrupts entirely
>>          "  dsb"        // Ensures that this takes effect before next
>>                      // instruction
>>          : [pri] "=r" (pri) :: "memory");
>>      return pri;
>> }
>>
>> static inline void restoreGlobalInterrupts(uint32_t pri) {
>>      asm volatile(
>>          "  msr primask, %[pri]"         // Restore old mask
>>          :: [pri] "r" (pri) : "memory");
>> }
> 
> 
> I won't try to write Ada equivalents of the above :-) though I have of
> course written much Ada code to manage and handle interrupts.

I think Ada has built-in (or standard library) support for critical
sections, does it not?  But this is just an example, not necessarily
something that would be directly useful.  Obviously the code above is
highly target-specific.

> 
> 
>> class CriticalSectionLock {
>> private :
>>      uint32_t oldpri;
>> public :
>>      CriticalSectionLock() { oldpri = disableGlobalInterrupts(); }
>>      ~CriticalSectionLock() { restoreGlobalInterrupts(oldpri); }
>> };
> 
> 
> Here is the same in Ada. I chose to derive from Limited_Controlled
> because that makes it illegal to assign a Critical_Section value from
> one object to another.
> 
>    -- Declaration of the type:
> 
>    type Critical_Section is new Ada.Finalization.Limited_Controlled
>    with record
>       old_pri : Interfaces.Unsigned_32;
>    end record;
> 
>    overriding procedure Initialize (This : in out Critical_Section);
>    overriding procedure Finalize   (This : in out Critical_Section);
> 
>    -- Implementation of the operations:
> 
>    procedure Initialize (This : in out Critical_Section)
>    is begin
>       This.old_pri := disableGlobalInterrupts;
>    end Initialize;
> 
>    procedure Finalize (This : in out Critical_Section)
>    is begin
>       restoreGlobalInterrupts (This.old_pri);
>    end Finalize;
> 
> 

Are "Initialize" and "Finalize" overloaded global procedures, or is this
the syntax always used for member functions?

>>
>> You can use it like this:
>>
>> bool compare_and_swap64(uint64_t * p, uint64_t old, uint64_t x)
>> {
>>     CriticalSectionLock lock;
>>
>>     if (*p != old) return false;
>>     *p = x;
>>     return true;
>> }
>>
> 
> 
>    function Compare_and_Swap64 (
>       p      : access Interfaces.Unsigned_64;
>       old, x : in     Interfaces.Unsigned_64)
>    return Boolean
>    is
>       Lock : Critical_Section;
>    begin
>       if p.all /= old then
>          return False;
>       else
>          p.all := x;
>          return True;
>       end if;
>    end Compare_and_Swap64;
> 
> (I think there should be a "volatile" spec for the "p" object, don't you?)

It might be logical to make it volatile, but the code would not be
different (the inline assembly has memory clobbers already, which force
the memory accesses to be carried out without re-arrangements).  But
adding "volatile" would do no harm, and let the user of the function
pass a volatile pointer.

The Ada and C++ code is basically the same here, which is nice.

How would it look with block scope?

extern int bar(int x);
int foo(volatile int * p, int x, int y) {
	int u = bar(x);
	{
		CriticalSectionLock lock;
		*p += z;
	}
	int v = bar(y);
	return v;
}

The point of this example is that the "*p += z;" line should be within
the calls to disableGlobalInterrupts and restoreGlobalInterrupts, but
the calls to "bar" should be outside.  This requires the lifetime of the
lock variable to be more limited.


> 
> 
> 
>> This is the code compiled for a 32-bit Cortex-M device:
>>
>> <https://godbolt.org/z/7KM9M6Kcd>
>>
>> The use of the class here has no overhead compared to manually disabling
>> and re-enabling interrupts.
>>
>> What would be the Ada equivalent of this class, and of the
>> "compare_and_swap64" function?
> 
> 
> See above. I don't have an Ada-to-Cortex-M compiler at hand to compare
> the target code, sorry.

godbolt.org has Ada and gnat 10.2 too, but only for x86-64.  The
enable/restore interrupt functions could be changed to simply reading
and writing a volatile int.  Then you could compare the outputs of Ada
and C++ for x86-64.  If you have the time and inclination, it might be
fun to see.

> 
> But critical sections in Ada applications are more often written using
> the Ada "protected object" feature. Here is the same as a protected
> object "CS", with separate declaration and body as usual in Ada. Here I
> must write the operation as a procedure instead of a function, because
> protected objects have "single writer, multiple readers" semantics, and
> any function is considered a "reader" although it may have side effects:
> 
>    protected CS
>       with Priority => System.Interrupt_Priority'Last
>    is
> 
>       procedure Compare_and_Swap64 (
>          p      : access Interfaces.Unsigned_64;
>          old, x : in     Interfaces.Unsigned_64;
>          result :    out Boolean);
> 
>    end CS;
> 
>    protected body CS
>    is
> 
>       procedure Compare_and_Swap64 (
>          p      : access Interfaces.Unsigned_64;
>          old, x : in     Interfaces.Unsigned_64;
>          result :    out Boolean);
>       is begin
>       result := p.all = old;
>       if result then
>              p.all := x;
>           end if;
>       end Compare_and_Swap64;
> 
>    end CS;
> 
> However, it would be more in the style of Ada to focus on the thing that
> is being "compared and swapped", so that "p" would be either a
> discriminant of the protected object, or a component of the protected
> object, instead of a parameter to the copy-and-swap operation. But it
> would look similar to the above.
> 

I think the idea of language support for protected sections is nice, but
I'd be concerned about how efficiently it would map to the requirements
of the program and the target.  Such things are often a bit "brute
force", because they have to emphasise "always works" over efficiency.
For example, if you have a 64-bit atomic type (on a 32-bit device), you
don't /always/ need to disable interrupts around it.  If you are already
in an interrupt routine and know that no higher priority interrupt
accesses the data, no locking is needed.  If you are in main thread code
but only read the data, maybe repeatedly reading it until you get two
reads with the same value is more efficient.  Such shortcuts must, of
course, be used with care.

In C and C++, there are atomic types (since C11/C++11).  They require
library support for different targets, which are (unfortunately) not
always good.  But certainly it is common in C++ to think of an atomic
type here rather than atomic access functions, just as you describe in Ada.

> 
>>>> On the third hand (three hands are always useful for programming), the
>>>> wordy nature of type conversions in Ada mean programmers would be
>>>> tempted to take shortcuts and skip these extra types.
>>>
>>>
>>> Huh? A normal conversion in C is written "(newtype)expression", the same
>>> in Ada is written "newtype(expression)". Exactly the same number of
>>> characters, only the placement of the () is different. The C form might
>>> even require an extra set of parentheses around it, to demarcate the
>>> expression to be converted from any containing expression.
>>>
>>> Of course, in C you have implicit conversions between all kinds of
>>> numerical types, often leading to a whole lot of errors... not only
>>> apples+oranges, but also truncation or other miscomputation.
>>
>> C also makes explicit conversions wordy, yes.  In C++, you can choose
>> which conversions are explicit and which are implicit - done carefully,
>> your safe conversions will be implicit and unsafe ones need to be
>> explicit.
> 
> 
> Ada does not have programmable implicit conversions, but one can
> override some innocuous operator, usually "+", to perform whatever
> conversions one wants. For example:
> 
>    function "+" (Item : Boolean) return Float
>    is (if Item then 1.0 else 0.0);
> 
> or more directly
> 
>    function "+" (Item : Boolean) return Float
>    is (Float (Boolean'Pos (Item)));
> 
> 
>> (C++ suffers from its C heritage and backwards compatibility, meaning it
>> can't fix things that were always implicit conversion.  It's too late to
>> make "int x = 123.4;" an error.  The best C++ can do is add a new syntax
>> with better safety - so "int y { 123 };" is fine but "int z { 123.4 };"
>> is an error.)
> 
> 
> Ada also has some warts, but perhaps not as easily illustrated.
> 

A language without warts would be boring!

Thank you for the insights and updates to my Ada knowledge.

[toc] | [prev] | [next] | [standalone]


#30516

FromNiklas Holsti <niklas.holsti@tidorum.invalid>
Date2021-04-19 19:16 +0300
Message-ID<ie5onhF7e7mU1@mid.individual.net>
In reply to#30512
On 2021-04-19 15:22, David Brown wrote:
> On 19/04/2021 12:51, Niklas Holsti wrote:
>> On 2021-04-18 19:59, David Brown wrote:
>>> On 18/04/2021 17:48, Niklas Holsti wrote:
>>>> On 2021-04-18 13:53, David Brown wrote:
>>>>> On 17/04/2021 20:55, Tom Gardner wrote:
>>>>>> On 17/04/21 17:45, David Brown wrote:
>>>>>>> And most of
>>>>>>> the advantages of Ada (such as better typing) can be achieved in C++
>>>>>>> with less effort, and at the same time C++ can give additional
>>>>>>> safety on
>>>>>>> resources that is harder to get on Ada.
>>>>>>
>>>>>> Interesting. Could you give a quick outline of that?
>>>>>>
>>>>>
>>>>> Which part?
>>>>>
>>>>> My understanding of Ada classes is that, like Pascal classes, you need
>>>>> to explicitly construct and destruct objects.  This gives far greater
>>>>> scope for programmers to get things wrong than when they are handled
>>>>> automatically by the language.
>>>>
>>>>
>>>> If you mean automatic allocation and deallocation of storage, Ada lets
>>>> you define types that have an "initializer" that is called
>>>> automatically, and can allocate memory if it needs to, and a "finalizer"
>>>> that is automatically called when leaving the scope of the object in
>>>> question. The finalizer does have to explicitly deallocate any
>>>> explicitly allocated and owned resources, and it may have to use
>>>> reference counting for that, for complex data structures.
>>>
>>> I had a little look for this (these discussions are great for inspiring
>>> learning!).  The impression I got was that it was possible, but what
>>> takes a few lines of C++ (excluding whatever work must be done inside
>>> the constructor and destructor bodies) involves inheriting from a
>>> specific library type.
>>
>>
>> Yes, you must inherit from one of the types Ada.Finalization.Controlled
>> or Ada.Finalization.Limited_Controlled when you create a type for which
>> you can program an initializer and/or a finalizer.
>>
>> However, you can aggregate a component of such a type into some other
>> composite type, and then that component's initializer and finalizer will
>> be called automatically when any object of the containing composite type
>> is constructed and destroyed.
>>
>>
>>> And you don't have automatic initialisation of
>>> subobjects and ancestors in a controlled order, nor automatic
>>> finalisation of them in the reverse order.
>>
>>
>> No, and yes. Subobjects (components) are automatically initialized
>> before the composite is initialized (bottom-up), and are automatically
>> finalized after the composite is finalized (top-down). But there is no
>> automatic invocation of the initializer or finalizer of the parent
>> class; that would have to be called explicitly (except in the case of an
>> "extension aggregate" expression, where an object of the parent type is
>> created and then extended to an object of the derived class).
>>
> 
> OK.  Am I right in assuming the subobjects here also need to inherit
> from the "Finalization" types individually, in order to be automatically
> initialised in order?


Yes, if they need more initialization than provided by the automatic 
"construction" step (Default_Value etc.)

> 
> Are there any overheads (other than in the source code) for all this
> inheriting?  Ada (like C++) aims to be minimal overhead, AFAIUI, but its
> worth checking.
> 


If the compiler sees the actual type of an object that needs 
finalization (as in the critical-section example) it can generate direct 
calls to Initialize and Finalize without dispatching. If the object is 
polymorphic (what in Ada is called a "class") the calls must go through 
a dispatch table according to the actual type of the object.


>> So the Ada "initializer" is not like a C++ constructor, which in Ada
>> corresponds more closely to a function returning an object of the class.
>>
>> An Ada "finalizer" is more similar to a C++ destructor, taking care of
>> any clean-up that is needed before the object disappears.
>>
> 
> C++ gives you the choice.  You can do work in a constructor, or you can
> leave it as a minimal (often automatically generated) function.  You can
> give default values to members.  You can add "initialise" member
> functions as you like.  You can have "factory functions" that generate
> instances.  This lets you structure the code and split up functionality
> in whatever way suits your requirements.


So, just as in Ada.


> For a well-structured class, the key point is that a constructor will
> always establish the class invariant.  Any publicly accessible function
> will assume that invariant, and maintain it.  Private functions might
> temporarily break the invariant - these are only accessible by code that
> "knows what it is doing".  And the destructor will always clean up after
> the object, recycling any resources used.
> 
> Having C++ style constructors are not a requirement for having control
> of the class invariant, but they do make it more convenient and more
> efficient (both at run-time and in the source code) than separate
> minimal constructors (or default values) and initialisers.


In Ada one can write the preconditions, invariants and postconditions in 
the source code itself, with standard "aspects" and Ada Boolean 
expressions, and have them either checked at run-time or verified by 
static analysis/proof.


>>> Let's take a little example.  And since this is comp.arch.embedded,
>>> let's take a purely embedded example of disabling interrupts, rather
>>> than shunned dynamic memory allocations:
>>>
>>> static inline uint32_t disableGlobalInterrupts(void) {
>>>       uint32_t pri;
>>>       asm volatile(
>>>           "  mrs %[pri], primask\n\t" // Get old mask
>>>           "  cpsid i\n\t"             // Disable interrupts entirely
>>>           "  dsb"        // Ensures that this takes effect before next
>>>                       // instruction
>>>           : [pri] "=r" (pri) :: "memory");
>>>       return pri;
>>> }
>>>
>>> static inline void restoreGlobalInterrupts(uint32_t pri) {
>>>       asm volatile(
>>>           "  msr primask, %[pri]"         // Restore old mask
>>>           :: [pri] "r" (pri) : "memory");
>>> }
>>
>>
>> I won't try to write Ada equivalents of the above :-) though I have of
>> course written much Ada code to manage and handle interrupts.
> 
> I think Ada has built-in (or standard library) support for critical
> sections, does it not? 


Yes, "protected objects". See below.


> But this is just an example, not necessarily
> something that would be directly useful.  Obviously the code above is
> highly target-specific.
> 
>>
>>
>>> class CriticalSectionLock {
>>> private :
>>>       uint32_t oldpri;
>>> public :
>>>       CriticalSectionLock() { oldpri = disableGlobalInterrupts(); }
>>>       ~CriticalSectionLock() { restoreGlobalInterrupts(oldpri); }
>>> };
>>
>>
>> Here is the same in Ada. I chose to derive from Limited_Controlled
>> because that makes it illegal to assign a Critical_Section value from
>> one object to another.
>>
>>     -- Declaration of the type:
>>
>>     type Critical_Section is new Ada.Finalization.Limited_Controlled
>>     with record
>>        old_pri : Interfaces.Unsigned_32;
>>     end record;
>>
>>     overriding procedure Initialize (This : in out Critical_Section);
>>     overriding procedure Finalize   (This : in out Critical_Section);
>>
>>     -- Implementation of the operations:
>>
>>     procedure Initialize (This : in out Critical_Section)
>>     is begin
>>        This.old_pri := disableGlobalInterrupts;
>>     end Initialize;
>>
>>     procedure Finalize (This : in out Critical_Section)
>>     is begin
>>        restoreGlobalInterrupts (This.old_pri);
>>     end Finalize;
>>
>>
> 
> Are "Initialize" and "Finalize" overloaded global procedures, or is this
> the syntax always used for member functions?


They are operations ("member functions") of the 
Ada.Finalization.Limited_Controlled type, that are null (do nothing) for 
that (base) type, and here we override them for the derived 
Critical_Section type, to replace the inherited null operations.

The "overriding" keyword is optional (an unfortunate wart from history).


>>> You can use it like this:
>>>
>>> bool compare_and_swap64(uint64_t * p, uint64_t old, uint64_t x)
>>> {
>>>      CriticalSectionLock lock;
>>>
>>>      if (*p != old) return false;
>>>      *p = x;
>>>      return true;
>>> }
>>>
>>
>>
>>     function Compare_and_Swap64 (
>>        p      : access Interfaces.Unsigned_64;
>>        old, x : in     Interfaces.Unsigned_64)
>>     return Boolean
>>     is
>>        Lock : Critical_Section;
>>     begin
>>        if p.all /= old then
>>           return False;
>>        else
>>           p.all := x;
>>           return True;
>>        end if;
>>     end Compare_and_Swap64;
>>
>> (I think there should be a "volatile" spec for the "p" object, don't you?)
> 
> It might be logical to make it volatile, but the code would not be
> different (the inline assembly has memory clobbers already, which force
> the memory accesses to be carried out without re-arrangements).


So you are relying on the C++ compiler actually respecting the "inline" 
directive? Are C++ compilers required to do that?


> But adding "volatile" would do no harm, and let the user of the
> function pass a volatile pointer. >
> The Ada and C++ code is basically the same here, which is nice.


(Personally I dislike this style of critical sections. I think it is a 
confusing mis-use of local variables. Its only merit is that it ensures 
that the finalization occurs even in the case of an exception or other 
abnormal exit from the critical section.)


> How would it look with block scope?
> 
> extern int bar(int x);
> int foo(volatile int * p, int x, int y) {
> 	int u = bar(x);
> 	{
> 		CriticalSectionLock lock;
> 		*p += z;
> 	}
> 	int v = bar(y);
> 	return v;
> }
> 
> The point of this example is that the "*p += z;" line should be within
> the calls to disableGlobalInterrupts and restoreGlobalInterrupts, but
> the calls to "bar" should be outside.  This requires the lifetime of the
> lock variable to be more limited.


Much as you would expect; the block is

    declare
       Lock : Critical_Section;
    begin
       p.all := p.all + z:
    end:

(In the next Ada standard -- probably Ada 2022 -- one can write such 
updating assignments more briefly, as

     p.all := @ + z;

but the '@' can be anywhere in the right-hand-side expression, in one or 
more places, which is more flexible than the C/C++ combined 
assignment-operations like "+=".)


>> But critical sections in Ada applications are more often written 
>> using the Ada "protected object" feature.
>> 
>> <snip PO example> >>>
> I think the idea of language support for protected sections is nice, but
> I'd be concerned about how efficiently it would map to the requirements
> of the program and the target.


I haven't had any problems so far. Though in some highly stressed 
real-time applications I have resorted to communicating through shared 
atomic variables with lock-free protocols.


> Such things are often a bit "brute
> force", because they have to emphasise "always works" over efficiency.
> For example, if you have a 64-bit atomic type (on a 32-bit device), you
> don't /always/ need to disable interrupts around it.  If you are already
> in an interrupt routine and know that no higher priority interrupt
> accesses the data, no locking is needed.


Interrupt handlers in Ada are written as procedures in protected 
objects, with the protected object given the appropriate priority. Other 
operations in that same protected object can then be executed in 
automatic mutual exclusion with the interrupt handler. The protected 
object can also provide one or more "entry" operations with Boolean 
"guards" on which tasks can wait, for example to wait for an interrupt 
to have occurred. I find this works very well in practice.


> If you are in main thread code but only read the data, maybe
> repeatedly reading it until you get two reads with the same value is
> more efficient.  Such shortcuts must, of course, be used with care.

Sure.


> In C and C++, there are atomic types (since C11/C++11).  They require
> library support for different targets, which are (unfortunately) not
> always good.  But certainly it is common in C++ to think of an atomic
> type here rather than atomic access functions, just as you describe in Ada.


The next Ada standard includes several generic atomic operations on 
typed objects in the standard package System.Atomic_Operations and its 
child packages. The proposal is at

http://www.ada-auth.org/standards/2xaarm/html/AA-C-6-1.html

Follow the "Next" arrows to see all of it.

In summary, it seems to me that the main difference we have found in 
this discussion, so far, between Ada and C++ services in the areas we 
have looked at, is that Ada makes it simpler to define one's own scalar 
types, while C++ has more compile-time programmability like constexpr 
functions.

[toc] | [prev] | [next] | [standalone]


#30522

FromDavid Brown <david.brown@hesbynett.no>
Date2021-04-19 22:19 +0200
Message-ID<s5kokj$e1r$1@dont-email.me>
In reply to#30516
On 19/04/2021 18:16, Niklas Holsti wrote:
> On 2021-04-19 15:22, David Brown wrote:
>> On 19/04/2021 12:51, Niklas Holsti wrote:
>>> On 2021-04-18 19:59, David Brown wrote:

(I'm snipping for brevity - I appreciate your comments even though I've
snipped many.)

> 
> In Ada one can write the preconditions, invariants and postconditions in
> the source code itself, with standard "aspects" and Ada Boolean
> expressions, and have them either checked at run-time or verified by
> static analysis/proof.
> 

Yes, I like that for Ada.  These are on the drawing board for C++, but
it will be a while yet before they are in place.

>>>
>>> (I think there should be a "volatile" spec for the "p" object, don't
>>> you?)
>>
>> It might be logical to make it volatile, but the code would not be
>> different (the inline assembly has memory clobbers already, which force
>> the memory accesses to be carried out without re-arrangements).
> 
> 
> So you are relying on the C++ compiler actually respecting the "inline"
> directive? Are C++ compilers required to do that?
> 

No, it is not relying on the "inline" at all - it is relying on the
semantics of the inline assembly code (which is compiler-specific,
though several major compilers support the gcc inline assembly syntax).

Compilers are required to support "inline" correctly, of course - but
the keyword doesn't actually mean "generate this code inside the calling
function".  It is one of these historical oddities - it was originally
conceived as a hint to the compiler for optimisation purposes, but what
it /actually/ means is roughly "It's okay for there to be multiple
definitions of this function in the program - I promise they will all do
the same thing, so I don't mind which you use in any given case".

The compiler is likely to generate the code inline as part of normal
optimisation, but it would do that anyway.

> 
>> But adding "volatile" would do no harm, and let the user of the
>> function pass a volatile pointer. >
>> The Ada and C++ code is basically the same here, which is nice.
> 
> 
> (Personally I dislike this style of critical sections. I think it is a
> confusing mis-use of local variables. Its only merit is that it ensures
> that the finalization occurs even in the case of an exception or other
> abnormal exit from the critical section.)

Fair enough - styles are personal things.  And they are heavily
influenced by what is convenient or idiomatic in the language(s) we
commonly use (and vice versa).

> 
> 
>> How would it look with block scope?
>>
>> extern int bar(int x);
>> int foo(volatile int * p, int x, int y) {
>>     int u = bar(x);
>>     {
>>         CriticalSectionLock lock;
>>         *p += z;
>>     }
>>     int v = bar(y);
>>     return v;
>> }
>>
>> The point of this example is that the "*p += z;" line should be within
>> the calls to disableGlobalInterrupts and restoreGlobalInterrupts, but
>> the calls to "bar" should be outside.  This requires the lifetime of the
>> lock variable to be more limited.
> 
> 
> Much as you would expect; the block is
> 
>    declare
>       Lock : Critical_Section;
>    begin
>       p.all := p.all + z:
>    end:

Fair enough.  (I expected there was some way to have smaller block-scope
variables in Ada, though I didn't know how to write them.  And it is not
a given that the scope and the lifetime would be the same, though it
looks like it is the case here.)

As a matter of style, I really do not like the "declare all variables at
the start of the block" style, standard in Pascal, C90 (or older), badly
written (IMHO) newer C, and apparently also Ada.  I much prefer to avoid
defining variables until I know what value they should hold, at least
initially.  Amongst other things, it means I can be much more generous
about declaring them as "const", there are almost no risks of using
uninitialised data, and the smaller scope means it is easier to see all
use of the variable.

> 
> (In the next Ada standard -- probably Ada 2022 -- one can write such
> updating assignments more briefly, as
> 
>     p.all := @ + z;
> 
> but the '@' can be anywhere in the right-hand-side expression, in one or
> more places, which is more flexible than the C/C++ combined
> assignment-operations like "+=".)
> 

It may be flexible, but I'm not convinced it is clearer nor that it
would often be useful.  But I guess that will be highly related to
familiarity.

> 
>>> But critical sections in Ada applications are more often written
>>> using the Ada "protected object" feature.
>>>
>>> <snip PO example> >>>
>> I think the idea of language support for protected sections is nice, but
>> I'd be concerned about how efficiently it would map to the requirements
>> of the program and the target.
> 
> 
> I haven't had any problems so far. Though in some highly stressed
> real-time applications I have resorted to communicating through shared
> atomic variables with lock-free protocols.
> 
> 
>> Such things are often a bit "brute
>> force", because they have to emphasise "always works" over efficiency.
>> For example, if you have a 64-bit atomic type (on a 32-bit device), you
>> don't /always/ need to disable interrupts around it.  If you are already
>> in an interrupt routine and know that no higher priority interrupt
>> accesses the data, no locking is needed.
> 
> 
> Interrupt handlers in Ada are written as procedures in protected
> objects, with the protected object given the appropriate priority. Other
> operations in that same protected object can then be executed in
> automatic mutual exclusion with the interrupt handler. The protected
> object can also provide one or more "entry" operations with Boolean
> "guards" on which tasks can wait, for example to wait for an interrupt
> to have occurred. I find this works very well in practice.
> 
> 
>> If you are in main thread code but only read the data, maybe
>> repeatedly reading it until you get two reads with the same value is
>> more efficient.  Such shortcuts must, of course, be used with care.
> 
> Sure.
> 
> 
>> In C and C++, there are atomic types (since C11/C++11).  They require
>> library support for different targets, which are (unfortunately) not
>> always good.  But certainly it is common in C++ to think of an atomic
>> type here rather than atomic access functions, just as you describe in
>> Ada.
> 
> 
> The next Ada standard includes several generic atomic operations on
> typed objects in the standard package System.Atomic_Operations and its
> child packages. The proposal is at
> 
> http://www.ada-auth.org/standards/2xaarm/html/AA-C-6-1.html
> 
> Follow the "Next" arrows to see all of it.
> 
> In summary, it seems to me that the main difference we have found in
> this discussion, so far, between Ada and C++ services in the areas we
> have looked at, is that Ada makes it simpler to define one's own scalar
> types, while C++ has more compile-time programmability like constexpr
> functions.
> 

These are certainly example differences.  In general it would appear
that most things that can be written in one language could be written in
the other in roughly the same way (given appropriate libraries or type
definitions).

And we can probably agree that both are better than plain old C in terms
of expressibility and (in the right hands) writing safer code by
reducing tedious and error-prone manual boilerplate.

[toc] | [prev] | [next] | [standalone]


#30528

FromNiklas Holsti <niklas.holsti@tidorum.invalid>
Date2021-04-20 01:03 +0300
Message-ID<ie6d1iFb9dgU1@mid.individual.net>
In reply to#30522
On 2021-04-19 23:19, David Brown wrote:
> On 19/04/2021 18:16, Niklas Holsti wrote:
>> On 2021-04-19 15:22, David Brown wrote:
>>> On 19/04/2021 12:51, Niklas Holsti wrote:
>>>>
>>>> (I think there should be a "volatile" spec for the "p" object, don't
>>>> you?)
>>>
>>> It might be logical to make it volatile, but the code would not be
>>> different (the inline assembly has memory clobbers already, which force
>>> the memory accesses to be carried out without re-arrangements).
>>
>>
>> So you are relying on the C++ compiler actually respecting the "inline"
>> directive? Are C++ compilers required to do that?
>>
> 
> No, it is not relying on the "inline" at all - it is relying on the
> semantics of the inline assembly code (which is compiler-specific,
> though several major compilers support the gcc inline assembly syntax).
> 
> Compilers are required to support "inline" correctly, of course - but
> the keyword doesn't actually mean "generate this code inside the calling
> function".  It is one of these historical oddities - it was originally
> conceived as a hint to the compiler for optimisation purposes, but what
> it /actually/ means is roughly "It's okay for there to be multiple
> definitions of this function in the program - I promise they will all do
> the same thing, so I don't mind which you use in any given case".


If the call is not in fact inlined, it seems to me that the compilation 
of the caller does not see the "asm volatile" in the callee, and 
therefore might reorder non-volatile accesses in the caller with respect 
to the call. But perhaps such reordering is forbidden in this example, 
because p is a pointer, and the callee might access the same underlying 
object (*p) through some other pointer to it, or directly.


> As a matter of style, I really do not like the "declare all variables at
> the start of the block" style, standard in Pascal, C90 (or older), badly
> written (IMHO) newer C, and apparently also Ada.  I much prefer to avoid
> defining variables until I know what value they should hold, at least
> initially.  Amongst other things, it means I can be much more generous
> about declaring them as "const", there are almost no risks of using
> uninitialised data, and the smaller scope means it is easier to see all
> use of the variable.


I mostly agree. However, there is always the possibility of having an 
exception, and the question of which variables an exception handler can 
see and use.

When all variable declarations are collected in one place, as in

    declare
       <local var declarations>
    begin
       <statements>
    exception
       <handlers>
    end

it is easy and safe to say that the handlers can rely on all the 
variables declared between "declare" and "begin" being in existence when 
handling some exception from the <statements>. If variables are declared 
here and there in the <statements>, they might or might not yet exist 
when the exception happens, and it would not be safe for the local 
exception handler to access them in any way.

Of course one can nest exception handlers when one nests blocks, but 
that becomes cumbersome pretty quickly.

I don't know how C++ really addresses this problem.


>> (In the next Ada standard -- probably Ada 2022 -- one can write such
>> updating assignments more briefly, as
>>
>>      p.all := @ + z;
>>
>> but the '@' can be anywhere in the right-hand-side expression, in one or
>> more places, which is more flexible than the C/C++ combined
>> assignment-operations like "+=".)
>>
> 
> It may be flexible, but I'm not convinced it is clearer nor that it
> would often be useful.  But I guess that will be highly related to
> familiarity.


Yes, I have not yet used it at all, although I believe GNAT already 
implements it.

I imagine one not uncommon use might be in function calls, such as

    x := Foo (@, ...);

I believe that the main reason this new Ada feature was formulated in 
this "more flexible" way was not the desire for more flexibility, but to 
avoid the introduction of many more lexical tokens and variations like 
"+=", or "+:=" as it would have been for Ada.

[toc] | [prev] | [next] | [standalone]


#30529

FromRichard Damon <Richard@Damon-Family.org>
Date2021-04-19 18:09 -0400
Message-ID<wcnfI.64287$J_5.19904@fx46.iad>
In reply to#30528
On 4/19/21 6:03 PM, Niklas Holsti wrote:
> On 2021-04-19 23:19, David Brown wrote:
>> On 19/04/2021 18:16, Niklas Holsti wrote:
>>> On 2021-04-19 15:22, David Brown wrote:
>>>> On 19/04/2021 12:51, Niklas Holsti wrote:
>>>>>
>>>>> (I think there should be a "volatile" spec for the "p" object, don't
>>>>> you?)
>>>>
>>>> It might be logical to make it volatile, but the code would not be
>>>> different (the inline assembly has memory clobbers already, which force
>>>> the memory accesses to be carried out without re-arrangements).
>>>
>>>
>>> So you are relying on the C++ compiler actually respecting the "inline"
>>> directive? Are C++ compilers required to do that?
>>>
>>
>> No, it is not relying on the "inline" at all - it is relying on the
>> semantics of the inline assembly code (which is compiler-specific,
>> though several major compilers support the gcc inline assembly syntax).
>>
>> Compilers are required to support "inline" correctly, of course - but
>> the keyword doesn't actually mean "generate this code inside the calling
>> function".  It is one of these historical oddities - it was originally
>> conceived as a hint to the compiler for optimisation purposes, but what
>> it /actually/ means is roughly "It's okay for there to be multiple
>> definitions of this function in the program - I promise they will all do
>> the same thing, so I don't mind which you use in any given case".
> 
> 
> If the call is not in fact inlined, it seems to me that the compilation
> of the caller does not see the "asm volatile" in the callee, and
> therefore might reorder non-volatile accesses in the caller with respect
> to the call. But perhaps such reordering is forbidden in this example,
> because p is a pointer, and the callee might access the same underlying
> object (*p) through some other pointer to it, or directly.

Unless the compiler knows otherwise, it must assume that a call to a
function might access volatile information, so can not migrate volatile
access across that call. Non-volatile accesses. that can not be affected
by the call, can be.
> 

[toc] | [prev] | [next] | [standalone]


#30533

FromDavid Brown <david.brown@hesbynett.no>
Date2021-04-20 06:58 +0200
Message-ID<s5ln1h$p5h$1@dont-email.me>
In reply to#30528
On 20/04/2021 00:03, Niklas Holsti wrote:
> On 2021-04-19 23:19, David Brown wrote:
>> On 19/04/2021 18:16, Niklas Holsti wrote:
>>> On 2021-04-19 15:22, David Brown wrote:
>>>> On 19/04/2021 12:51, Niklas Holsti wrote:
>>>>>
>>>>> (I think there should be a "volatile" spec for the "p" object, don't
>>>>> you?)
>>>>
>>>> It might be logical to make it volatile, but the code would not be
>>>> different (the inline assembly has memory clobbers already, which force
>>>> the memory accesses to be carried out without re-arrangements).
>>>
>>>
>>> So you are relying on the C++ compiler actually respecting the "inline"
>>> directive? Are C++ compilers required to do that?
>>>
>>
>> No, it is not relying on the "inline" at all - it is relying on the
>> semantics of the inline assembly code (which is compiler-specific,
>> though several major compilers support the gcc inline assembly syntax).
>>
>> Compilers are required to support "inline" correctly, of course - but
>> the keyword doesn't actually mean "generate this code inside the calling
>> function".  It is one of these historical oddities - it was originally
>> conceived as a hint to the compiler for optimisation purposes, but what
>> it /actually/ means is roughly "It's okay for there to be multiple
>> definitions of this function in the program - I promise they will all do
>> the same thing, so I don't mind which you use in any given case".
> 
> 
> If the call is not in fact inlined, it seems to me that the compilation
> of the caller does not see the "asm volatile" in the callee, and
> therefore might reorder non-volatile accesses in the caller with respect
> to the call. But perhaps such reordering is forbidden in this example,
> because p is a pointer, and the callee might access the same underlying
> object (*p) through some other pointer to it, or directly.

Either the compiler "sees" the definition of the functions, and can tell
that there are things that force the memory access to be done in middle
(whether these functions are inlined or not), or the compiled does not
"see" the definitions and must therefore make pessimistic assumptions
about what the functions might do.  The compiler can't re-order things
unless it can /prove/ that it is safe to do so.

> 
> 
>> As a matter of style, I really do not like the "declare all variables at
>> the start of the block" style, standard in Pascal, C90 (or older), badly
>> written (IMHO) newer C, and apparently also Ada.  I much prefer to avoid
>> defining variables until I know what value they should hold, at least
>> initially.  Amongst other things, it means I can be much more generous
>> about declaring them as "const", there are almost no risks of using
>> uninitialised data, and the smaller scope means it is easier to see all
>> use of the variable.
> 
> 
> I mostly agree. However, there is always the possibility of having an
> exception, and the question of which variables an exception handler can
> see and use.
> 

In C++ (and C99, but it doesn't have exceptions), you don't need to put
your variables at the start of a block.  But their scope and lifetime
will last until the end of the block.  So if you need a variable in an
exception, you have to have declared it before the exception handling
but you don't need it in a rigid variables-then-code-block structure :

{
	foo1();
	int x = foo2();		// <- Not at start of block
	foo3(x);
	try {
		foo4(x, x);
	} catch (...) {
		foo5(x);
	}
}


> When all variable declarations are collected in one place, as in
> 
>    declare
>       <local var declarations>
>    begin
>       <statements>
>    exception
>       <handlers>
>    end
> 
> it is easy and safe to say that the handlers can rely on all the
> variables declared between "declare" and "begin" being in existence when
> handling some exception from the <statements>. If variables are declared
> here and there in the <statements>, they might or might not yet exist
> when the exception happens, and it would not be safe for the local
> exception handler to access them in any way.

Flexible placement of declarations does not mean /random/ placement, nor
does it mean you don't know what is in scope and what is not!

And you can be pretty sure the compiler will tell you if you are trying
to use a variable outside its scope.

> 
> Of course one can nest exception handlers when one nests blocks, but
> that becomes cumbersome pretty quickly.
> 
> I don't know how C++ really addresses this problem.
> 

For the kinds of code I write - on small systems - I disable exceptions
in C++.  I prefer to write code that doesn't go wrong (unusual
circumstances are just another kind of value), and the kind of places
where exceptions might be most useful don't turn up often.  (I use
exceptions in Python in PC programming, but the balance is different there.)

> 
>>> (In the next Ada standard -- probably Ada 2022 -- one can write such
>>> updating assignments more briefly, as
>>>
>>>      p.all := @ + z;
>>>
>>> but the '@' can be anywhere in the right-hand-side expression, in one or
>>> more places, which is more flexible than the C/C++ combined
>>> assignment-operations like "+=".)
>>>
>>
>> It may be flexible, but I'm not convinced it is clearer nor that it
>> would often be useful.  But I guess that will be highly related to
>> familiarity.
> 
> 
> Yes, I have not yet used it at all, although I believe GNAT already
> implements it.
> 
> I imagine one not uncommon use might be in function calls, such as
> 
>    x := Foo (@, ...);
> 
> I believe that the main reason this new Ada feature was formulated in
> this "more flexible" way was not the desire for more flexibility, but to
> avoid the introduction of many more lexical tokens and variations like
> "+=", or "+:=" as it would have been for Ada.

Sounds reasonable.

[toc] | [prev] | [next] | [standalone]


#30535

FromPaul Rubin <no.email@nospam.invalid>
Date2021-04-19 23:18 -0700
Message-ID<87im4hmqcg.fsf@nightsong.com>
In reply to#30533
David Brown <david.brown@hesbynett.no> writes:
> For the kinds of code I write - on small systems - I disable
> exceptions in C++.  I prefer to write code that doesn't go wrong
> (unusual circumstances are just another kind of value), and the kind
> of places where exceptions might be most useful don't turn up often.

That's not so great if you call library routines that can raise
exceptions, and having to propagate error values back up the call chain
is precisely the hassle that exceptions avoid.

There is a proposal for "deterministic exceptions" in C++ that work like
Haskell's Either monad.  I.e. they are syntax sugar for propagating
error values upwards, with the compiler automatically inserting the
tests so you don't have to manually check the return values everywhere.
I guess they are a good idea but I don't know if they can replace the
other kind of exceptions.  I don't know the current state of the
proposal but some info and links should be here:

https://github.com/cplusplus/papers/issues/310

[toc] | [prev] | [next] | [standalone]


#30538

FromDavid Brown <david.brown@hesbynett.no>
Date2021-04-20 09:36 +0200
Message-ID<s5m0a6$c8m$1@dont-email.me>
In reply to#30535
On 20/04/2021 08:18, Paul Rubin wrote:
> David Brown <david.brown@hesbynett.no> writes:
>> For the kinds of code I write - on small systems - I disable
>> exceptions in C++.  I prefer to write code that doesn't go wrong
>> (unusual circumstances are just another kind of value), and the kind
>> of places where exceptions might be most useful don't turn up often.
> 
> That's not so great if you call library routines that can raise
> exceptions, and having to propagate error values back up the call chain
> is precisely the hassle that exceptions avoid.
> 

If you don't use exceptions, don't call library functions that raise
exceptions.  If you don't treat "errors" as something mysterious that
might or might not happen, which should wander around the code in the
vague hope that something somewhere can make it all better, then you
don't need silent propagation of exceptions - also known as invisible
and undocumented longjumps.

When you are programming on a PC, you can be using huge libraries where
you know the basic interface of the parts that you use, but have no idea
of the contents and what might possibly go wrong.

When you are programming for small-systems embedded systems, you /know/
what can go wrong in the functions you call - /nothing/.  There are no
unexpected or untreated errors - or your development is not finished
yet.  Any possible returns are part of the interface to the function,
whether they are "success" returns or "failure" returns.  In this kind
of system, you don't have the option of showing a dialogue box to the
user saying "unexpected exception encountered - press OK to quit".

> There is a proposal for "deterministic exceptions" in C++ that work like
> Haskell's Either monad.  I.e. they are syntax sugar for propagating
> error values upwards, with the compiler automatically inserting the
> tests so you don't have to manually check the return values everywhere.
> I guess they are a good idea but I don't know if they can replace the
> other kind of exceptions.  I don't know the current state of the
> proposal but some info and links should be here:
> 
> https://github.com/cplusplus/papers/issues/310
> 

They are indeed a good idea.  I am quite happy with explicit exceptions
or error indications that are part of the interface to functions, and I
would be happy with syntactic sugar and efficient compiler support.
What I would /not/ use on embedded systems is hidden mechanisms where
you don't know what exceptions a function might throw.

There are plenty of other attempts at conveniently and explicitly
handling errors using templates and classes that typically work as sum
types (like the "Either" you mentioned) or a product type (like a struct
with a "valid" flag as well as the real result type).  I have found it
convenient to use std::optional, for example.

[toc] | [prev] | [next] | [standalone]


#30520

FromPaul Rubin <no.email@nospam.invalid>
Date2021-04-19 12:20 -0700
Message-ID<87blaagjyc.fsf@nightsong.com>
In reply to#30508
Niklas Holsti <niklas.holsti@tidorum.invalid> writes:
> In Ada... The construction step can assign some initial values that
> can be defined by default (pointers default to null, for example)...
>
>    type Zero_Handler is access procedure;
>    type Counter is record ...
>       At_Zero : Zero_Handler;      -- Default init to null.

Wow, that is disappointing.  I had thought Ada access types were like
C++ or ML references, i.e. they have to be initialized to point to a
valid object, so they can never be null.  I trust or at least hope that
SPARK has good ways to ensure that a given access variable is always
valid.

Debugging null pointer exceptions is a standard time-waster in most
languages that have null pointers.  Is it that way in Ada as well?

Fwiw, I'm not a C++ expert but I do use it.  I try to write in a style
that avoids pointers (e.g. by using references instead), and still find
myself debugging stuff with invalid addresses that I think wouldn't
happen in Ada.  But I've never used Ada beyond some minor playing
around.  It seems like a big improvement over C.  C++ it seems to me
also improves on C, but by going in a different direction than Ada.

I plan to spend some time on Rust pretty soon.  This is based on
impression rather than experience, but ISTM that a lot of Rust is
designed around managing dynamic memory allocation by ownership
tracking, like C++ unique_ptr on steroids built into the language.  That
lets you write big applications that heavily use dynamic allocation
while avoiding the usual malloc/free bugs and without using garbage
collection.  Ada on the other hand is built for high assurance embedded
applications that don't use dynamic allocation much, except maybe at
program initialization time.  So Rust and Ada aim to solve different
problems.

I like to think it is reasonable to write the outer layers of complex
applications in garbage collected languages, with critical or realtime
parts written in something like Ada.  Tim Sweeney talks about this in
his old slide deck "The Next Mainstream Programming Language":

https://www.st.cs.uni-saarland.de//edu/seminare/2005/advanced-fp/docs/sweeny.pdf

The above url currently throws an expired-certificate warning but it is
ok to click past that.

[toc] | [prev] | [next] | [standalone]


#30526

FromNiklas Holsti <niklas.holsti@tidorum.invalid>
Date2021-04-20 00:26 +0300
Message-ID<ie6aruFat9fU1@mid.individual.net>
In reply to#30520
On 2021-04-19 22:20, Paul Rubin wrote:
> Niklas Holsti <niklas.holsti@tidorum.invalid> writes:
>> In Ada... The construction step can assign some initial values that
>> can be defined by default (pointers default to null, for example)...
>>
>>     type Zero_Handler is access procedure;
>>     type Counter is record ...
>>        At_Zero : Zero_Handler;      -- Default init to null.
> 
> Wow, that is disappointing.  I had thought Ada access types were like
> C++ or ML references, i.e. they have to be initialized to point to a
> valid object, so they can never be null. 


You can impose that constraint if you want to: if I had defined

    type Zero_Handler is not null access procedure;

then the above declaration of the At_Zero component would be illegal, 
and the compiler would insist on a non-null initial value.

But IMO sometimes you need pointers that can be null, just as you 
sometimes need null values in a database.

There are also other means in Ada to force the explicit initialization 
of objects at declaration (the "unspeficied discriminants" method).

The state of the art in Ada implementations of critical systems is 
slowly becoming to use static analysis and proof tools to verify that no 
run-time check failures, such as accessing a null pointer, can happen. 
That is already fairly easy to do with the AdaCore tools (CodePeer, 
SPARK and others). Proving functional correctness still remains hard.


> I plan to spend some time on Rust pretty soon.  This is based on
> impression rather than experience, but ISTM that a lot of Rust is
> designed around managing dynamic memory allocation by ownership
> tracking, like C++ unique_ptr on steroids built into the language.  That
> lets you write big applications that heavily use dynamic allocation
> while avoiding the usual malloc/free bugs and without using garbage
> collection.  Ada on the other hand is built for high assurance embedded
> applications that don't use dynamic allocation much, except maybe at
> program initialization time.  So Rust and Ada aim to solve different
> problems. 


There is a proposal and an implementation from AdaCore to augment Ada 
pointers with an "ownership" concept, as in Rust. I believe that SPARK 
supports that proposal. Again, in some cases you want shared ownership 
(multiple pointers to the same object), and then the Rust ownership 
concept is not enough, as I understand it (not expert at all).

[toc] | [prev] | [next] | [standalone]


#30532

FromPaul Rubin <no.email@nospam.invalid>
Date2021-04-19 16:01 -0700
Message-ID<877dkxyj3o.fsf@nightsong.com>
In reply to#30526
Niklas Holsti <niklas.holsti@tidorum.invalid> writes:
> You can impose that constraint if you want to: if I had defined
>    type Zero_Handler is not null access procedure;
> then the above declaration of the At_Zero component would be illegal,
> and the compiler would insist on a non-null initial value.

Oh, this is nice.

> But IMO sometimes you need pointers that can be null, just as you
> sometimes need null values in a database.

Preferable these days is to use a separate type for a value that is
nullable or optional, so failing to check for null gives a compile-time
type error.  This is 't Option in ML, Maybe a in Haskell, and iirc
std::Optional<T> these days in C++.

> The state of the art in Ada implementations of critical systems is
> slowly becoming to use static analysis and proof tools to verify that
> no run-time check failures, such as accessing a null pointer, can
> happen. That is already fairly easy to do with the AdaCore tools
> (CodePeer, SPARK and others). Proving functional correctness still
> remains hard.

I know about SPARK.  Is CodePeer something along the same lines?  Is it
available through GNU, or is it Adacore proprietary or what?

I still have Burns & Wellings' book on SPARK on the recommendation of
someone here.  It looks good but has been in my want-to-read pile since
forever.  One of these days.

[toc] | [prev] | [next] | [standalone]


#30539

FromNiklas Holsti <niklas.holsti@tidorum.invalid>
Date2021-04-20 10:47 +0300
Message-ID<ie7f7qFhbedU1@mid.individual.net>
In reply to#30532
On 2021-04-20 2:01, Paul Rubin wrote:
> Niklas Holsti <niklas.holsti@tidorum.invalid> writes:
>> You can impose that constraint if you want to: if I had defined
>>     type Zero_Handler is not null access procedure;
>> then the above declaration of the At_Zero component would be illegal,
>> and the compiler would insist on a non-null initial value.
> 
> Oh, this is nice.
> 
>> But IMO sometimes you need pointers that can be null, just as you
>> sometimes need null values in a database.
> 
> Preferable these days is to use a separate type for a value that is
> nullable or optional, so failing to check for null gives a compile-time
> type error.  This is 't Option in ML, Maybe a in Haskell, and iirc
> std::Optional<T> these days in C++.


I'm not aware of proposals for such a thing in Ada.


>> The state of the art in Ada implementations of critical systems is
>> slowly becoming to use static analysis and proof tools to verify that
>> no run-time check failures, such as accessing a null pointer, can
>> happen. That is already fairly easy to do with the AdaCore tools
>> (CodePeer, SPARK and others). Proving functional correctness still
>> remains hard.
> 
> I know about SPARK.  Is CodePeer something along the same lines?  Is it
> available through GNU, or is it Adacore proprietary or what?


CodePeer is a for-money AdaCore tool, a static analyzer that basically 
(AIUI) applies bottom-up weakest-precondition construction and 
pre-post-condition analysis to detect many kinds of logical programming 
problems, in particular possible failures of run-time checks.

See https://www.adacore.com/codepeer.

To use it in practice on larger programs, one usually has to guide and 
help its analysis by writing a certain amount of explicit precondition 
and postcondition aspects into the Ada source (which CodePeer then 
verifies as part of its analysis), but that has other benefits too, of 
course.


> I still have Burns & Wellings' book on SPARK on the recommendation of
> someone here.  It looks good but has been in my want-to-read pile since
> forever.  One of these days.


Note that most if not all of the SPARK-specific comments that were used 
earlier have now been superseded by the precondition, postcondition, 
invariant etc. aspects in standard Ada, and the SPARK tools have been 
updated accordingly. I'm not sure if this replacement is complete; I 
haven't used SPARK.

By the way, did you know that NVIDIA has started using SPARK? There's a 
presentation at 
https://www.adacore.com/webinars/securing-future-of-embedded-software.

niklas holsti tidorum fi
       .      @       .

[toc] | [prev] | [next] | [standalone]


#30530

FromTom Gardner <spamjunk@blueyonder.co.uk>
Date2021-04-19 23:30 +0100
Message-ID<4wnfI.318062$XX1d.3203@fx31.ams4>
In reply to#30520
On 19/04/21 20:20, Paul Rubin wrote:

> I plan to spend some time on Rust pretty soon.  This is based on
> impression rather than experience, but ISTM that a lot of Rust is
> designed around managing dynamic memory allocation by ownership
> tracking, like C++ unique_ptr on steroids built into the language.  That
> lets you write big applications that heavily use dynamic allocation
> while avoiding the usual malloc/free bugs and without using garbage
> collection.  

It also helps with concurrency. From
https://doc.rust-lang.org/book/ch16-00-concurrency.html

  "Initially, the Rust team thought that ensuring memory safety
   and preventing concurrency problems were two separate challenges
   to be solved with different methods. Over time, the team
   discovered that the ownership and type systems are a powerful
   set of tools to help manage memory safety and concurrency
   problems! By leveraging ownership and type checking, many
   concurrency errors are  compile-time errors in Rust rather
   than runtime errors."

I haven't kicked Rust's tyres, but that seems plausible

[toc] | [prev] | [next] | [standalone]


Page 1 of 3  [1] 2 3  Next page →

Back to top | Article view | comp.arch.embedded


csiph-web