Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]
Groups > comp.arch.embedded > #30465 > unrolled thread
| Started by | pozz <pozzugno@gmail.com> |
|---|---|
| First post | 2021-04-17 17:48 +0200 |
| Last post | 2021-04-18 14:39 +0200 |
| Articles | 20 on this page of 58 — 12 participants |
Back to article view | Back to comp.arch.embedded
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 →
| From | pozz <pozzugno@gmail.com> |
|---|---|
| Date | 2021-04-17 17:48 +0200 |
| Subject | C++, 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]
| From | David Brown <david.brown@hesbynett.no> |
|---|---|
| Date | 2021-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]
| From | Tom Gardner <spamjunk@blueyonder.co.uk> |
|---|---|
| Date | 2021-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]
| From | David Brown <david.brown@hesbynett.no> |
|---|---|
| Date | 2021-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]
| From | Niklas Holsti <niklas.holsti@tidorum.invalid> |
|---|---|
| Date | 2021-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]
| From | David Brown <david.brown@hesbynett.no> |
|---|---|
| Date | 2021-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]
| From | Niklas Holsti <niklas.holsti@tidorum.invalid> |
|---|---|
| Date | 2021-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]
| From | David Brown <david.brown@hesbynett.no> |
|---|---|
| Date | 2021-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]
| From | Niklas Holsti <niklas.holsti@tidorum.invalid> |
|---|---|
| Date | 2021-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]
| From | David Brown <david.brown@hesbynett.no> |
|---|---|
| Date | 2021-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]
| From | Niklas Holsti <niklas.holsti@tidorum.invalid> |
|---|---|
| Date | 2021-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]
| From | Richard Damon <Richard@Damon-Family.org> |
|---|---|
| Date | 2021-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]
| From | David Brown <david.brown@hesbynett.no> |
|---|---|
| Date | 2021-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]
| From | Paul Rubin <no.email@nospam.invalid> |
|---|---|
| Date | 2021-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]
| From | David Brown <david.brown@hesbynett.no> |
|---|---|
| Date | 2021-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]
| From | Paul Rubin <no.email@nospam.invalid> |
|---|---|
| Date | 2021-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]
| From | Niklas Holsti <niklas.holsti@tidorum.invalid> |
|---|---|
| Date | 2021-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]
| From | Paul Rubin <no.email@nospam.invalid> |
|---|---|
| Date | 2021-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]
| From | Niklas Holsti <niklas.holsti@tidorum.invalid> |
|---|---|
| Date | 2021-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]
| From | Tom Gardner <spamjunk@blueyonder.co.uk> |
|---|---|
| Date | 2021-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