❌ About FreshRSS

Normal view

There are new articles available, click to refresh the page.
Before yesterdayBlogs

Bridge returned error 0! (19713)

22 December 2023 at 23:07

Details

Type: HttpException
Code: 0
Message: cURL error Failed to connect to tomekw.com port 443 after 189 ms: Couldn't connect to server: 7 (https://curl.haxx.se/libcurl/c/libcurl-errors.html) for https://tomekw.com/feed.xml
File: lib/http.php
Line: 127

Trace

#0 index.php(11): RssBridge->main()
#1 lib/RssBridge.php(113): DisplayAction->execute()
#2 actions/DisplayAction.php(71): DisplayAction->createResponse()
#3 actions/DisplayAction.php(106): FilterBridge->collectData()
#4 bridges/FilterBridge.php(168): FeedExpander->collectExpandableDatas()
#5 lib/FeedExpander.php(88): getContents()
#6 lib/contents.php(67): CurlHttpClient->request()
#7 lib/http.php(127)

Context

Query: action=display&bridge=Filter&url=https://tomekw.com/feed.xml&filter=Ada&filter_type=permit&format=Atom
Version: 2023-09-24
OS: Linux
PHP: 8.2.7

Go back

Frenzie, ORelio

  • 22 December 2023 at 23:07

Advent of Code 2023, day 10

10 December 2023 at 17:12

The puzzle: https://adventofcode.com/2023/day/10

My Ada solution: here.

In a picture (generated by the program linked above)...

Click to enlarge

 

It is always a pleasure to display the data created by Eric Wastl.

Here, an improved representation with the five cases (outside tile, inside tile, inside pixel on a path tile, outside pixel on a path tile, path pixel) in shown different colors:

Click to enlarge


Parallel Comparison of C++ and Ada Producer-Consumer Implementations

 

Producer Consumer Comparison 

The C++ source code for this example is taken from the blog post here by Andrew Wei. A detailed description of the C++ software is given in the blog post.

This solution is shown in parallel with a corresponding Ada solution.

Both C++ and Ada separate interface specifications from implementation. C++ uses the header file, in this case the file named Buffer.hpp, to provide the interface specification for the buffer used in this example. C++ is not very strict about what goes into a header file and what goes into a .cpp file. The Ada version creates an Ada package. The Ada package specification defines the task types named producer_Int and consumer_Int. The buffer shared by all instances of producer_int and consumer_int is defined within the Ada package body file.

Interface Specification Files

Ada C++
package pc_tasks is
   task type produce_Int (Id : Natural);
   task type consume_Int (Id : Natural);
end pc_tasks;
//
//  Buffer.hpp
//  ProducerConsumer
//
//  Created by Andrew Wei on 5/31/21.
//

#ifndef Buffer_hpp
#define Buffer_hpp

#include <mutex>
#include <condition_variable>
#include <stdio.h>
 
#define BUFFER_CAPACITY 10

class Buffer {
    // Buffer fields
    int buffer [BUFFER_CAPACITY];
    int buffer_size;
    int left; // index where variables are put inside of buffer (produced)
    int right; // index where variables are removed from buffer (consumed)
    
    // Fields for concurrency
    std::mutex mtx;
    std::condition_variable not_empty;
    std::condition_variable not_full;
    
public:
    // Place integer inside of buffer
    void produce(int thread_id, int num);
    
    // Remove integer from buffer
    int consume(int thread_id);
    
    Buffer();
};

#endif /* Buffer_hpp */

This comparison shows the interface for Ada task types while the C++ interface (.hpp) file shows the interface for the Buffer class. The C++ interface definition defines the interfaces, both public and private, to the Buffer class.

Shared Buffer Implementations

The Ada package body contains both the definition and implementation of the shared buffer object and the implementation of the task types.

Ada C++
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Numerics.Discrete_Random;

package body pc_tasks is
   type Index_T is mod 10; -- Modular type for 10 values
   type Circular_Array is array (Index_T) of Integer;

   protected buffer is
      entry produce (Item : in Integer);
      entry consume (Item : out Integer);
   private
      Buf      : Circular_Array;
      P_Index  : Index_T := Index_T'First;
      C_Index  : Index_T := Index_T'First;
      Buf_Size : Natural := 0;
   end buffer;

   protected body buffer is
      entry produce (Item : in Integer) when Buf_Size < Index_T'Modulus is
      begin
         Buf (P_Index) := Item;
         P_Index       := P_Index + 1;
         Buf_Size      := Buf_Size + 1;
      end produce;

      entry consume (Item : out Integer) when Buf_Size > 0 is
      begin
         Item     := Buf (C_Index);
         C_Index  := C_Index + 1;
         Buf_Size := Buf_Size - 1;
      end consume;
   end buffer;

   task body produce_Int is
      subtype decimal is Integer range 1 .. 10;
      package rand_int is new Ada.Numerics.Discrete_Random (decimal);
      use rand_int;
      value : decimal;
      seed  : Generator;
   begin
      Reset (seed);
      for I in 1 .. 4 loop
         value := Random (seed);
         buffer.produce (value);
         Put_Line ("Task" & Id'Image & " produced" & value'Image);
         delay 0.1;
      end loop;
   end produce_Int;

   task body consume_Int is
      Num : Integer;
   begin
      for I in 1 .. 6 loop
         buffer.consume (Num);
         Put_Line ("Task" & Id'Image & " consumed" & Num'Image);
         delay 0.1;
      end loop;
   end consume_Int;

end pc_tasks;
//
//  Buffer.cpp
//  ProducerConsumer
//
//  Created by Andrew Wei on 5/31/21.
//

#include <iostream>
#include "Buffer.hpp"

Buffer::Buffer() {
    buffer_size = 0;
    left = 0;
    right = 0;
}

void Buffer::produce(int thread_id, int num) {
    // Acquire a unique lock on the mutex
    std::unique_lock<std::mutex> unique_lock(mtx);
    
    std::cout << "thread " << thread_id << " produced " << num << "\n";
    
    // Wait if the buffer is full
    not_full.wait(unique_lock, [this]() {
        return buffer_size != BUFFER_CAPACITY;
    });
    
    // Add input to buffer
    buffer[right] = num;
    
    // Update appropriate fields
    right = (right + 1) % BUFFER_CAPACITY;
    buffer_size++;
    
    // Unlock unique lock
    unique_lock.unlock();
    
    // Notify a single thread that buffer isn't empty
    not_empty.notify_one();
}

int Buffer::consume(int thread_id) {
    // Acquire a unique lock on the mutex
    std::unique_lock<std::mutex> unique_lock(mtx);
    
    // Wait if buffer is empty
    not_empty.wait(unique_lock, [this]() {
        return buffer_size != 0;
    });
    
    // Getvalue from position to remove in buffer
    int result = buffer[left];
    
    std::cout << "thread " << thread_id << " consumed " << result << "\n";
    
    // Update appropriate fields
    left = (left + 1) % BUFFER_CAPACITY;
    buffer_size--;
    
    // Unlock unique lock
    unique_lock.unlock();
    
    // Notify a single thread that the buffer isn't full
    not_full.notify_one();
    
    // Return result
    return result;
}

The Ada part of the package implementing the shared buffer is isolated below, along with a repeat of the C++ Buffer class implementation.

Ada C++
   type Index_T is mod 10; -- Modular type for 10 values
   type Circular_Array is array (Index_T) of Integer;

   protected buffer is
      entry produce (Item : in Integer);
      entry consume (Item : out Integer);
   private
      Buf      : Circular_Array;
      P_Index  : Index_T := Index_T'First;
      C_Index  : Index_T := Index_T'First;
      Buf_Size : Natural := 0;
   end buffer;

   protected body buffer is
      entry produce (Item : in Integer) when Buf_Size < Index_T'Modulus is
      begin
         Buf (P_Index) := Item;
         P_Index       := P_Index + 1;
         Buf_Size      := Buf_Size + 1;
      end produce;

      entry consume (Item : out Integer) when Buf_Size > 0 is
      begin
         Item     := Buf (C_Index);
         C_Index  := C_Index + 1;
         Buf_Size := Buf_Size - 1;
      end consume;
   end buffer;
//
//  Buffer.cpp
//  ProducerConsumer
//
//  Created by Andrew Wei on 5/31/21.
//

#include <iostream>
#include "Buffer.hpp"

Buffer::Buffer() {
    buffer_size = 0;
    left = 0;
    right = 0;
}

void Buffer::produce(int thread_id, int num) {
    // Acquire a unique lock on the mutex
    std::unique_lock<std::mutex> unique_lock(mtx);
    
    std::cout << "thread " << thread_id << " produced " << num << "\n";
    
    // Wait if the buffer is full
    not_full.wait(unique_lock, [this]() {
        return buffer_size != BUFFER_CAPACITY;
    });
    
    // Add input to buffer
    buffer[right] = num;
    
    // Update appropriate fields
    right = (right + 1) % BUFFER_CAPACITY;
    buffer_size++;
    
    // Unlock unique lock
    unique_lock.unlock();
    
    // Notify a single thread that buffer isn't empty
    not_empty.notify_one();
}

int Buffer::consume(int thread_id) {
    // Acquire a unique lock on the mutex
    std::unique_lock<std::mutex> unique_lock(mtx);
    
    // Wait if buffer is empty
    not_empty.wait(unique_lock, [this]() {
        return buffer_size != 0;
    });
    
    // Getvalue from position to remove in buffer
    int result = buffer[left];
    
    std::cout << "thread " << thread_id << " consumed " << result << "\n";
    
    // Update appropriate fields
    left = (left + 1) % BUFFER_CAPACITY;
    buffer_size--;
    
    // Unlock unique lock
    unique_lock.unlock();
    
    // Notify a single thread that the buffer isn't full
    not_full.notify_one();
    
    // Return result
    return result;
}

 The Ada buffer definition begins by defining the array type to be used in the shared buffer. Ada allows arrays to be indexed by any discrete type. In this example an Ada modular type is declared and named Index_T. Type Index_T is declared to be "mod 10", which specifies that all its values are in the range of 0 through 9 and all the arithmetic operators return a value within this range. The only arithmetic operator used in this example is "+". Addition on a modular type is modular. Thus, in this example, 9 + 1 yields 0, which is exactly as needed for a circular buffer.

Type Circular_Array is an array indexed by Index_T. Every element of Circular_Array is an Integer.

Ada protected objects are protected against race conditions. They are specifically used for data shared by Ada tasks. Ada tasks are commonly implemented as operating system threads, but may also be used on a bare-bones system using only a compiler-generated Ada runtime.

The protected object is named buffer. It is separated into a specification and an implementation, but both parts in this example are contained in the package body. The protected specification declares the name of the protected object. There are three kinds of methods that may be used inside a protected object: procedures, entries and functions. Procedures have exclusive unconditional read-write access to the data in the protected object. Entries have conditional read-write access to the data in a shared object. Functions have shared read-only access to the data in the protected object. This example only uses two entries. The private portion of the protected object contains the definition of the data members in the protected object. Buf is an instance of Circular_Array. P_Index is an instance of the Index_T type and is used by the producer to add new data to the protected object. C_Index is an instance of Index_T type and is used by the consumer to index data read from the protected object. Buf_Size is an instance of the Natural subtype of Integer. Natural is a pre-defined subtype of Integer with a minimum value of 0. Buf_Size is initialized with the value 0.

The protected body implements the two entries. Each entry has an associated condition which must evaluate to True for the entry to execute. All tasks calling an entry while its controlling condition evaluates to False are implicitly placed in an entry queue for the called entry using a queuing policy of First-In-First-Out (FIFO). The next entry call in the queue is serviced as soon as the condition evaluates to TRUE. Tasks suspended in the entry queue are given access to the protected entry before any new tasks, thus maintaining the temporal condition of the calls.

The produce entry can only write to the protected object when Buf_Size is less than Index_T'Modulus, which in this case evaluates to 10. The consumer entry can only read from the protected object when Buf_Size is greater than 0.

Each entry implicitly handles all locking and unlocking of the protected object. The protected object is locked just before the "begin" reserved word in each entry and is unlocked just before the "end" reserved word in each entry. The modular nature of the index manipulations as well as the implicit lock manipulations explain the relatively simple Ada code compared with the more verbose corresponding C++ code.

Thread Implementations

The Ada task implementations are also contained in the package body while the C++ thread implementations are contained in the main.cpp file. Those corresponding source code sections are compared below.

Ada C++
   task body produce_Int is
      subtype decimal is Integer range 1 .. 10;
      package rand_int is new Ada.Numerics.Discrete_Random (decimal);
      use rand_int;
      value : decimal;
      seed  : Generator;
   begin
      Reset (seed);
      for I in 1 .. 4 loop
         value := Random (seed);
         buffer.produce (value);
         Put_Line ("Task" & Id'Image & " produced" & value'Image);
         delay 0.1;
      end loop;
   end produce_Int;

   task body consume_Int is
      Num : Integer;
   begin
      for I in 1 .. 6 loop
         buffer.consume (Num);
         Put_Line ("Task" & Id'Image & " consumed" & Num'Image);
         delay 0.1;
      end loop;
   end consume_Int;
// Takes in reference to a buffer and adds a random integer
void produceInt(Buffer &buffer) {
    for (int i = 0; i < 4; i++) {
        // Generate random number between 1 and 10
        int new_int = rand() % 10 + 1;
        buffer.produce(i, new_int);
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
}

// Takes in reference to a buffer and returns the latest int added
// in the buffer
void consumeInt(Buffer &buffer) {
    for (int i = 0; i < 6; i++) {
        buffer.consume(i);
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
}

The Ada tasks have visibility to the buffer protected object because the task bodies and the protected object are both defined within the same package body scope.

The produce_Int task defines an Integer subtype named decimal with valid values in the range of 1 through 10. That type is passed to the generic package Ada.Numerics.Discrete_Random so that random numbers in the range of 1 through 10 will be generated for this program. The random number seed is reset based upon the system clock using the Reset(seed) command. The for loop iterates through the values 1 through 4. Each iteration generates a new random value, writes the value to buffer.produce, outputs the value to standard output and then delays 0.1 seconds (100 milliseconds).

The consumer_int task defines a local integer variable named Num. The for loop iterates through the numbers 1 through 6. Each iteration calls buffer.consume, assigning the entry out parameter to Num, outputs the consumed value and delays 0.1 seconds.

Program Entry Points

While the program entry point for a C++ program is named "main", the program entry point for an Ada program may have any name chosen by the programmer. Note that the Ada entry point is a procedure, meaning it does not return any value and that procedure has no parameters.

Ada C++
with pc_tasks;    use pc_tasks;
with Ada.Text_IO; use Ada.Text_IO;

procedure Main is
begin
   Put_Line("Executing code in main...");
   declare
      Produce_Task0 : produce_Int (0);
      Consume_Task0 : consume_Int (1);
      Produce_Task1 : produce_Int (2);
      Consume_Task1 : consume_Int (3);
      Produce_Task2 : produce_Int (4);
   begin
      null;
   end;
   Put_Line ("Done!");
end Main;
int main(int argc, const char * argv[]) {
    std::cout << "Executing code in main...\n";
    
    // Initialize random seed
    srand (time(NULL));
    
    // Create Buffer
    Buffer buffer;
    
    // Create a thread to produce
    std::thread produceThread0(produceInt, std::ref(buffer));
    
    std::thread consumeThread0(consumeInt, std::ref(buffer));
    
    std::thread produceThread1(produceInt, std::ref(buffer));
    
    std::thread consumeThread1(consumeInt, std::ref(buffer));
    
    std::thread produceThread2(produceInt, std::ref(buffer));
    
    produceThread0.join();
    produceThread1.join();
    produceThread2.join();
    consumeThread0.join();
    consumeThread1.join();
    
    std::cout << "Done!\n";
    return 0;
}

Ada does not provide a "join()" method. Instead, the code block in which a task or set of task is declared cannot complete until all the tasks within that code block complete. The idiom shown above declared the 5 task type instances within an inner code block, which does not complete until all five of the tasks have terminated. Upon completion of that inner block the message "Done!" is output to standard output.

Complete code listings for both languages

Ada C++
package pc_tasks is
   task type produce_Int (Id : Natural);
   task type consume_Int (Id : Natural);
end pc_tasks;
//
//  Buffer.hpp
//  ProducerConsumer
//
//  Created by Andrew Wei on 5/31/21.
//

#ifndef Buffer_hpp
#define Buffer_hpp

#include <mutex>
#include <condition_variable>
#include <stdio.h>
 
#define BUFFER_CAPACITY 10

class Buffer {
    // Buffer fields
    int buffer [BUFFER_CAPACITY];
    int buffer_size;
    int left; // index where variables are put inside of buffer (produced)
    int right; // index where variables are removed from buffer (consumed)
    
    // Fields for concurrency
    std::mutex mtx;
    std::condition_variable not_empty;
    std::condition_variable not_full;
    
public:
    // Place integer inside of buffer
    void produce(int thread_id, int num);
    
    // Remove integer from buffer
    int consume(int thread_id);
    
    Buffer();
};

#endif /* Buffer_hpp */
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Numerics.Discrete_Random;

package body pc_tasks is
   type Index_T is mod 10; -- Modular type for 10 values
   type Circular_Array is array (Index_T) of Integer;

   protected buffer is
      entry produce (Item : in Integer);
      entry consume (Item : out Integer);
   private
      Buf      : Circular_Array;
      P_Index  : Index_T := Index_T'First;
      C_Index  : Index_T := Index_T'First;
      Buf_Size : Natural := 0;
   end buffer;

   protected body buffer is
      entry produce (Item : in Integer) when Buf_Size < Index_T'Modulus is
      begin
         Buf (P_Index) := Item;
         P_Index       := P_Index + 1;
         Buf_Size      := Buf_Size + 1;
      end produce;

      entry consume (Item : out Integer) when Buf_Size > 0 is
      begin
         Item     := Buf (C_Index);
         C_Index  := C_Index + 1;
         Buf_Size := Buf_Size - 1;
      end consume;
   end buffer;

   task body produce_Int is
      subtype decimal is Integer range 1 .. 10;
      package rand_int is new Ada.Numerics.Discrete_Random (decimal);
      use rand_int;
      value : decimal;
      seed  : Generator;
   begin
      Reset (seed);
      for I in 1 .. 4 loop
         value := Random (seed);
         buffer.produce (value);
         Put_Line ("Task" & Id'Image & " produced" & value'Image);
         delay 0.1;
      end loop;
   end produce_Int;

   task body consume_Int is
      Num : Integer;
   begin
      for I in 1 .. 6 loop
         buffer.consume (Num);
         Put_Line ("Task" & Id'Image & " consumed" & Num'Image);
         delay 0.1;
      end loop;
   end consume_Int;

end pc_tasks;
//
//  Buffer.cpp
//  ProducerConsumer
//
//  Created by Andrew Wei on 5/31/21.
//

#include <iostream>
#include "Buffer.hpp"

Buffer::Buffer() {
    buffer_size = 0;
    left = 0;
    right = 0;
}

void Buffer::produce(int thread_id, int num) {
    // Acquire a unique lock on the mutex
    std::unique_lock<std::mutex> unique_lock(mtx);
    
    std::cout << "thread " << thread_id << " produced " << num << "\n";
    
    // Wait if the buffer is full
    not_full.wait(unique_lock, [this]() {
        return buffer_size != BUFFER_CAPACITY;
    });
    
    // Add input to buffer
    buffer[right] = num;
    
    // Update appropriate fields
    right = (right + 1) % BUFFER_CAPACITY;
    buffer_size++;
    
    // Unlock unique lock
    unique_lock.unlock();
    
    // Notify a single thread that buffer isn't empty
    not_empty.notify_one();
}

int Buffer::consume(int thread_id) {
    // Acquire a unique lock on the mutex
    std::unique_lock<std::mutex> unique_lock(mtx);
    
    // Wait if buffer is empty
    not_empty.wait(unique_lock, [this]() {
        return buffer_size != 0;
    });
    
    // Getvalue from position to remove in buffer
    int result = buffer[left];
    
    std::cout << "thread " << thread_id << " consumed " << result << "\n";
    
    // Update appropriate fields
    left = (left + 1) % BUFFER_CAPACITY;
    buffer_size--;
    
    // Unlock unique lock
    unique_lock.unlock();
    
    // Notify a single thread that the buffer isn't full
    not_full.notify_one();
    
    // Return result
    return result;
}
with pc_tasks;    use pc_tasks;
with Ada.Text_IO; use Ada.Text_IO;

procedure Main is
begin
   Put_Line("Executing code in main...");
   declare
      Produce_Task0 : produce_Int (0);
      Consume_Task0 : consume_Int (1);
      Produce_Task1 : produce_Int (2);
      Consume_Task1 : consume_Int (3);
      Produce_Task2 : produce_Int (4);
   begin
      null;
   end;
   Put_Line ("Done!");
end Main;
//
//  main.cpp
//  ProducerConsumer
//
//  Created by Andrew Wei on 5/30/21.
//

#include <thread>
#include <iostream>
#include "Buffer.hpp"
#include <stdlib.h>

// Takes in reference to a buffer and adds a random integer
void produceInt(Buffer &buffer) {
    for (int i = 0; i < 4; i++) {
        // Generate random number between 1 and 10
        int new_int = rand() % 10 + 1;
        buffer.produce(i, new_int);
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
}

// Takes in reference to a buffer and returns the latest int added
// in the buffer
void consumeInt(Buffer &buffer) {
    for (int i = 0; i < 6; i++) {
        buffer.consume(i);
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
}

int main(int argc, const char * argv[]) {
    std::cout << "Executing code in main...\n";
    
    // Initialize random seed
    srand (time(NULL));
    
    // Create Buffer
    Buffer buffer;
    
    // Create a thread to produce
    std::thread produceThread0(produceInt, std::ref(buffer));
    
    std::thread consumeThread0(consumeInt, std::ref(buffer));
    
    std::thread produceThread1(produceInt, std::ref(buffer));
    
    std::thread consumeThread1(consumeInt, std::ref(buffer));
    
    std::thread produceThread2(produceInt, std::ref(buffer));
    
    produceThread0.join();
    produceThread1.join();
    produceThread2.join();
    consumeThread0.join();
    consumeThread1.join();
    
    std::cout << "Done!\n";
    return 0;
}

 Summary

The same producer-consumer problem can be solved using both Ada and C++. The C++ approach to the producer-consumer pattern requires far more attention to low level details than does the Ada approach to the producer-consumer pattern. The greatest difference in the two approaches is the requirement for the C++ programmer to explicitly manipulate mutex locking/unlocking and suspended thread wait and notification commanding.

  • 15 October 2023 at 22:53

Parallel Comparison of Java Producer-Consumer with Ada Producer-Consumer

 

Comparison of Java producer-consumer source code with Ada producer-consumer source code. While the Java program defines several classes the Ada program defines one Ada package and a main procedure. The Ada package encapsulates the definitions of the shared buffer type and the task types. The main procedure creates instances of the shared buffer type and the task types.

Ada packages separate interface definitions from implementation. In this example the interface definition and the definition are split into two files, which Ada calls the package specification and the package body.

The Shared buffer

Both programs implement a producer-consumer model using a single-element buffer shared by the producer and the consumer.

Both programs implement a shared buffer object with two data elements. A count is stored in a variable named materials and a  Boolean variable named available is used to control access to the shared buffer by the producer and consumer thread (or task in Ada).

The Java program implements the shared buffer as a class while the Ada program implements the shared buffer as a protected object.

Ada Java
   protected type buffer is
      entry Put (Item : in Integer);
      entry Get (Item : out Integer);
   private
      Materials : Integer;
      Available : Boolean := False;
   end buffer;

   type Buffer_Access is access buffer;
   protected body buffer is

      ---------
      -- Put --
      ---------

      entry Put (Item : in Integer) when not Available is
      begin
         Materials := Item;
         Available := True;
      end Put;

      ---------
      -- Get --
      ---------

      entry Get (Item : out Integer) when Available is
      begin
         Item      := Materials;
         Available := False;
      end Get;

   end buffer;
class Shop
{
      private int materials;
      private boolean available = false;
      public synchronized int get()
      {
            while (available == false)
            {
                  try
                  {
                        wait();
                  }
                  catch (InterruptedException ie)
                  {
                  }
            }
            available = false;
            notifyAll();
            return materials;
      }
      public synchronized void put(int value)
      {
            while (available == true)
            {
                  try
                  {
                        wait();
                  }
                  catch (InterruptedException ie)
                  {
                        ie.printStackTrace();
                  }
            }
            materials = value;
            available = true;
            notifyAll();
      }
}

Ada separates interface definition from implementation while Java does not separate the two. The implementation is the interface definition.

The Ada protected object named buffer is defined to have two entries named Put and Get. Put writes an Integer value to Buffer.  Get reads the current Integer value in Buffer and passes that value out to the calling task. The buffer protected object has two private  data members. Materials is an Integer. Available is a Boolean instance initialized to False.

The Java Shop class has two private data members. They are materials, which is an int and available, which is a boolean initialized to false.

The Ada implementation provides the implementation of the two entries named Put and Get. Ada protected entries always have an associated condition which must evaluate to True for the entry to be executed. The Put entry condition is defined as "when not Available" while the Get entry condition is defined as "when Available". Thus, the Put entry will only execute when Available is True and the GET entry will only execute when Available is False.

When the entry condition is false the task calling the entry is suspended in an entry queue implicitly maintained by the protected object. The entry queue defaults to a First-In, First-Out (FIFO) queuing policy so that the entry queue is emptied in the order the tasks called the entry.

The Java Shop class achieves the same effect by explicitly writing a polling loop which only exits when the available private data member evaluates to the proper condition. The while loop condition is the inverse of the Ada condition because the loop continues while the available  data member does not satisfy the condition for the method to execute. Each time through the loop the method calls the wait()  method because the method will only be executed when the thread calling the method awakes due to the notifyAll() method call. The notifyAll() method awakens all waiting threads and the one that can proceed does so.



The Producer

The Ada program creates a producer task. The Java program creates a Producer task which extends the Thread class.

Ada Java
  task type Producer (Buf : Buffer_Access; Id : Positive);
  task body Producer is
  begin
   for I in 0 .. 9 loop
     Buf.Put (I);
     Put_Line ("Producer" & Id'Image & " produced" & I'Image);
     delay 0.000_1;
   end loop;
  end Producer;
class Producer extends Thread
{
      private Shop Shop;
      private int number;

      public Producer(Shop c, int number)
      {
            Shop = c;
            this.number = number;
      }
      public void run()
      {
            for (int i = 0; i < 10; i++)
            {
                  Shop.put(i);
                  System.out.println("Produced value " + this.number+ " put: " + i);
                  try
                  {
                        sleep((int)(Math.random() * 100));
                  }
                  catch (InterruptedException ie)
                  {
                        ie.printStackTrace();
                  }
            }
      }
}

Ada tasks separate the task interface from the task implementation. A task type named producer is declared with a discriminant value. In this case the discriminant value is used to identify each instance of the task type. The task implementation simply iterates through the values 0 through 9, each time calling buffer.put with the loop iteration value, and then outputting the value to standard output. The buffer protected object is visible to the task because they are all created within the same scope. In this case that scope is the program main procedure.

The Java program creates a Producer class which extends Thread. An variable of the Shop class named Shop is created. The Producer constructor is used to set the value of the Shop variable and also to set the value of the number private data member. The run() method iterates through the values 0 through 9, each time calling Shop.put with the iteration value. Each iteration the method sleeps a random number between 0 and 100 milliseconds.

The Consumer

The Ada consumer task type is very similar to the Ada producer task type and the Java Consumer class is very similar to the Java Producer class.

Ada Java
   task type Consumer (Buf : Buffer_Access; Id : Positive);
   task body Consumer is
      Value : Integer;
   begin
      for I in 1 .. 10 loop
         Buf.Get (Value);
         Put_Line ("Consumer" & Id'Image & " consumed" & Value'Image);
      end loop;
   end Consumer;
class Consumer extends Thread
{
      private Shop Shop;
      private int number;
      public Consumer(Shop c, int number)
      {
            Shop = c;
            this.number = number;
      }
      public void run()
      {
            int value = 0;
            for (int i = 0; i < 10; i++)
            {
                  value = Shop.get();
                  System.out.println("Consumed value " + this.number+ " got: " + value);
            }
      }
}

The Ada consumer task declares a local variable named Value. The task iterates 10 times, each time calling buffer.Get and then printing the value passed out to the Value variable.

The Java Consumer class run() method iterates 10 times, each time calling Shop.get() and printing out the value returned.

The Program Entry Point

The program entry point for Java is always a method named public static void main(String[] args) while the Ada program entry point can have any name. The Ada program entry point is always a parameter-less procedure. In this example that procedure is also named main for similarity with the Java example. The Java entry point method resides in a class named ProducerConsumer

while the Ada main procedure encapsulates all the other parts of this program.

Ada Java
with simple_buffer_pc; use simple_buffer_pc;

procedure Main is
   shop : Buffer_Access := new buffer;
   P1 : Producer (Buf => shop, Id => 1);
   C1 : Consumer (Buf => shop, Id => 1);
begin
   null;
end Main;
public class ProducerConsumer
{
      public static void main(String[] args)
      {
            Shop c = new Shop();
            Producer p1 = new Producer(c, 1);
            Consumer c1 = new Consumer(c, 1);
            p1.start();
            c1.start();
      }
}

The Java main method creates a new instance of the Shop class then creates one instance each of the Producer class and the Consumer class, passing the Shop class instance to both so both threads reference the same shop instance. The threads are started by calling the start() method inherited from the Thread class.

The Ada main procedure contains the definitions of the protected object Buffer and the two task types producer and consumer. After the end of the consumer task definition one instance of the producer task type is created and one instance of the consumer task type is created. The tasks automatically start when the program reached the begin reserved word for the main procedure. The main procedure then does nothing, which is signified by the null reserved word. The main procedure will not terminate until all the tasks created within the main task terminate. In Java terms, the main task implicitly joins the producer and consumer tasks.

Both entire programs

The full source code of programs are presented below to provide full context for the reader.

Ada Java
package simple_buffer_pc is
   protected type buffer is
      entry Put (Item : in Integer);
      entry Get (Item : out Integer);
   private
      Materials : Integer;
      Available : Boolean := False;
   end buffer;

   type Buffer_Access is access buffer;
   task type Producer (Buf : Buffer_Access; Id : Positive);
   task type Consumer (Buf : Buffer_Access; Id : Positive);
end simple_buffer_pc;
with Ada.Text_IO; use Ada.Text_IO;

package body simple_buffer_pc is

   protected body buffer is

      entry Put (Item : in Integer) when not Available is
      begin
         Materials := Item;
         Available := True;
      end Put;

      entry Get (Item : out Integer) when Available is
      begin
         Item      := Materials;
         Available := False;
      end Get;
   end buffer;

   task body Producer is
   begin
      for I in 0 .. 9 loop
         Buf.Put (I);
         Put_Line ("Producer" & Id'Image & " produced" & I'Image);
         delay 0.000_1;
      end loop;
   end Producer;

   task body Consumer is
      Value : Integer;
   begin
      for I in 1 .. 10 loop
         Buf.Get (Value);
         Put_Line ("Consumer" & Id'Image & " consumed" & Value'Image);
      end loop;
   end Consumer;

end simple_buffer_pc;
with simple_buffer_pc; use simple_buffer_pc;

procedure Main is
   shop : Buffer_Access := new buffer;
   P1 : Producer (Buf => shop, Id => 1);
   C1 : Consumer (Buf => shop, Id => 1);
begin
   null;
end Main;
public class ProducerConsumer
{
      public static void main(String[] args)
      {
            Shop c = new Shop();
            Producer p1 = new Producer(c, 1);
            Consumer c1 = new Consumer(c, 1);
            p1.start();
            c1.start();
      }
}
class Shop
{
      private int materials;
      private boolean available = false;
      public synchronized int get()
      {
            while (available == false)
            {
                  try
                  {
                        wait();
                  }
                  catch (InterruptedException ie)
                  {
                  }
            }
            available = false;
            notifyAll();
            return materials;
      }
      public synchronized void put(int value)
      {
            while (available == true)
            {
                  try
                  {
                        wait();
                  }
                  catch (InterruptedException ie)
                  {
                        ie.printStackTrace();
                  }
            }
            materials = value;
            available = true;
            notifyAll();
      }
}
class Consumer extends Thread
{
      private Shop Shop;
      private int number;
      public Consumer(Shop c, int number)
      {
            Shop = c;
            this.number = number;
      }
      public void run()
      {
            int value = 0;
            for (int i = 0; i < 10; i++)
            {
                  value = Shop.get();
                  System.out.println("Consumed value " + this.number+ " got: " + value);
            }
      }
}
class Producer extends Thread
{
      private Shop Shop;
      private int number;

      public Producer(Shop c, int number)
      {
            Shop = c;
            this.number = number;
      }
      public void run()
      {
            for (int i = 0; i < 10; i++)
            {
                  Shop.put(i);
                  System.out.println("Produced value " + this.number+ " put: " + i);
                  try
                  {
                        sleep((int)(Math.random() * 100));
                  }
                  catch (InterruptedException ie)
                  {
                        ie.printStackTrace();
                  }
            }
      }
}
  • Both programs produce very similar results although the Java example needs to deal with possible exceptions while the Ada example does not need to deal with exceptions. 
  • The logic used to ensure the Java Shop put method uses a form of a spin lock implementing a subtle form of negative logic to ensure the materials data member is only updated when available is false while the Ada buffer put entry uses positive logic.
  • The logic used to ensure the Java Shop get method uses a form of a spin lock implementing a subtle form of negative logic to ensure the materials data member is only read when available is true while the Ada buffer get entry uses positive logic.
  • Even with the need to separate interface from implementation the Ada program requires less typing than the Java program.



  • 14 October 2023 at 16:42

LEA, a tiny but smart editor

14 October 2023 at 13:39

Note for subscribers: if you are interested in my Ada programming articles only, you can use this RSS feed link.


LEA, the Lightweight Editor for Ada, is based on the Scintilla text editor widget, which excels at colouring syntactic elements and letting the user rework his or her programs very swiftly, including manipulating rectangular selections and adding text on multiple points (usually vertically aligned).
But until recently, the cool features were limited to automatic indentation, adding comment delimiters (--) at the right column, or highlighting portions of text that match a selection.
Users were frustrated by not finding in LEA programming helpers (commonly grouped under the term "smart editor" or "intellisense") that they enjoy in their programming "studios".
An excuse would have been to dismiss such features as out of the scope of a "lightweight" editor.
But we know we can make of LEA a powerful mini-studio, with the ease of use of a simple editor, thanks to its "projectless" mode and the integrated HAC compiler.
So now, it happened.
From revision 385 of LEA (and revision 886 of HAC), you have now a good sense of intellisense:

  • mouse-hover tips: when you let the mouse pointer on an identifier for a little while, a tip appears about that identifier (typically, where it was declared)
  • context menu with a go-to-declaration entry, when applicable
  • call tips: on typing '(' after a subprogram name, a tip appears with the subprogram's parameters
  • auto-complete: on typing characters that belong to identifiers, a list of possible declared identifier appears; that list depends on where in the source code the text cursor is: declarations below the cursor are invisible and local declarations, possibly within multiple nested subprograms. 

Some screenshots will help illustrate the above points. Let's start with the sudoku_sample.adb example shipped with the HAC project. What's that "Sudo_Strings" type, for instance?
Just let the mouse pointer hang out around an occurrence of that keyword.

Mouse hover tip - Click image to enlarge it

Want to know more about that declaration? Right-click on it...

Go to declaration - Click image to enlarge it

...and here you go.

Declaration in the Sudoku package - Click image to enlarge it

That's it, so far, for features enabling navigation within existing code.
For writing code, here are two other key helpers. First, the call tips.
If we type '(' after a subprogram's name, we see the parameters list appear:


Call tip - Click image to enlarge it

If we type the beginning of an identifier, a list of possible completions appears:

Auto-complete - Click image to enlarge it

A few remarks about the present state of LEA's "smart editor" features:
  • Currently, LEA only supports the HAC subset - it was the first priority. GNAT and other Ada compilers have their own editors and navigation features, so there is a lesser pressure to support them in LEA.
  • The "smart editor" features are quite new and still under test and development. If you want to have them, you need to build LEA on a fresh clone (instructions are given in lea.gpr), then set (via regedit) the registry key SMART_EDITOR, in the branch HKEY_CURRENT_USER\SOFTWARE\LEA\, to "true", before starting LEA.
  • You will notice a few missing things, like a list of fields on typing '.' after a record variable. They are not forgotten and just still "under construction".
  • The "smart editor" features work on incomplete Ada sources. They might be just less present after the point of an error, especially a syntax error.

In order to develop the last point, here are a few examples on how LEA understands your program "on the fly", while you are typing it:

Smart editor on an incomplete piece of code - Click image to enlarge it

Smart editor on an incomplete piece of code, sample 2 - Click image to enlarge it

Smart editor on an incomplete piece of code, sample 3 - Click image to enlarge it

One more thing: the Windows version of LEA is contained in a single executable holding in currently 4.2 MiB, including data such as code samples and templates, and runs as a portable application (no installation required).

Some Web links:

LEA

Web site: http://l-e-a.sf.net/
Sources, site #1: https://sf.net/p/l-e-a/code/HEAD/tree/
Sources, site #2: https://github.com/zertovitch/lea
Alire Crate: Alire - LEA

HAC

Web site: https://hacadacompiler.sourceforge.io/
Sources, site #1: https://sf.net/p/hacadacompiler/code/HEAD/tree/
Sources, site #2: https://github.com/zertovitch/hac
Alire Crate: Alire - HAC

Enjoy!

Unlocking the Power of OpenAI in Ada programs

1 October 2023 at 16:33
[Ada/openai-img.jpg](Ada/openai-img.jpg)

The OpenAI(https://openai.com/) provides a service that can be queried using REST requests. The service API can be classified as follows:

  • OpenAI's GPT (generative pre-trained transformer) is the well-known text generation based on text inputs.
 It gives access to several AI models that have been trained to understand natural language and code.
  • Image generation gives access to the DALL.E(https://platform.openai.com/docs/models/dall-e) for both
  •  generating images based on a text description, or, modify an existing image based on a text description.
    
  • Speech to text gives access to the Whisper model(https://openai.com/research/whisper) that converts
  •  audio records into text (several languages are supported).
    
  • OpenAI’s text embeddings measure the relatedness of text strings.
  • For a detailed description of these API have a look at the OpenAI API Introduction(https://platform.openai.com/docs/introduction) document. To use these APIs, you'll first need to register on their service and get an API key that will grant access to the operations.

    The library was generated by the OpenAPI(https://github.com/OpenAPITools/openapi-generator) code generator from the OpenAPI description of the OpenAI service and it uses the OpenAPI Ada(https://github.com/stcarrez/swagger-ada) library to make HTTP requests and perform JSON serialization and de-serialization. Each OpenAI operation is made available through an Ada procedure which takes care of building the request that was filled within some Ada record, submitting it to the OpenAI server and extract the response in another Ada record. Every request submitted to the OpenAI server is therefore strongly typed!

      1. Setup

    To use the library, you should use Alire to setup your project and use:

    ``` alr index add git+https://gitlab.com/stcarrez/awa-alire-index.git name awa alr with openai ```

    For the HTTP connection, you can either use AWS(https://github.com/AdaCore/aws) or curl(https://curl.se/) and run one of the following commands:

    ``` alr with utilada_curl alr with utilada_aws ```

      1. Initialization

    First, make sure you import at least the following Ada packages:

    ``` with Util.Http.Clients.Curl; -- replace Curl with AWS if needed with OpenAPI.Credentials.OAuth; with OpenAI.Clients; with OpenAI.Models; ```

    If you want to use curl(https://curl.se/), the initialization should use the following:

    ``` Util.Http.Clients.Curl.Register; ```

    But if you want to use AWS(https://github.com/AdaCore/aws), you will initialize with:

    ``` Util.Http.Clients.AWS.Register; ```

    After the initialization is done, you will declare the `OpenAI` client instance to access the API operations. The OpenAI(https://openai.com/) service uses an OAuth bearer API key to authenticate requests made on the server. We will need an `OAuth2_Credential_Type` instance represented by `Cred` below.

    ``` Cred : aliased OpenAPI.Credentials.OAuth.OAuth2_Credential_Type; Client : OpenAI.Clients.Client_Type; ```

      1. Credential setup

    For the credential setup you will first need to get your access key from your account. Once you have your key as a `String`, you can configure the `Cred` object and tell the client connection entry point which credentials to use:

    ``` Api_Key : constant String := ...;

      Cred.Bearer_Token (Api_Key);
      Client.Set_Credentials (Cred'Unchecked_Access);
    

    ```

      1. OpenAPI client setup

    The last step necessary before you can make requests, is to setup the server base URL to connect for the REST requests:

    ```

     Client.Set_Server ("https://api.openai.com/v1");
    

    ```

      1. API for chat

    The `Create_Chat` is the main operation for the conversation chat generation. The request is represented by the `ChatRequest_Type` type which describes the OpenAI chat model that must be used and the query parameters. The request can be filled with a complete conversation chat which means it is possible to call it several times with previous queries and responses to proceed in the chat conversation.

    ``` C : OpenAI.Clients.Client_Type; Req : OpenAI.Models.ChatRequest_Type; Reply : OpenAI.Models.ChatResponse_Type; ...

      Req.Model := OpenAPI.To_UString ("gpt-3.5-turbo");
      Req.Messages.Append ((Role => Openapi.To_Ustring ("user"),
                            Content => Prompt,
                            others => <>));
      Req.Temperature := 0.3;
      Req.Max_Tokens := (Is_Null => False, Value => 1000);
      Req.Top_P := 1.0;
      Req.Frequency_Penalty := 0.0;
      Req.Presence_Penalty := 0.0;
      C.Create_Chat (Req, Reply);
    

    ```

    Upon successful completion, we get a list of choices in the reply that contains the text of the conversation. You can iterate over the list with the following code extract. Beware that the returned string is using UTF-8 encoding and it may need a conversion to a `Wide_Wide_String` if necessary.

    ```

      for Choice of Reply.Choices loop
         declare
            Utf8 : constant String := OpenAPI.To_String (Choice.Message.Content);
         begin
            Put_Line (Ada.Strings.UTF_Encoding.Wide_Wide_Strings.Decode (Utf8));
         end;
      end loop;
    

    ```

    The complete chat example is available at: OpenAI Chat(https://gitlab.com/stcarrez/openai-chat) .

      1. Generating an image

    The image generation is based on the DALL.E(https://platform.openai.com/docs/models/dall-e) model generator. The creation is made by populating a request object, making the call (which is an HTTP POST) and getting the result in a response. Both request and response are represented by full Ada types and are strongly typed:

    The request contains a prompt string which must be provided and is the textual description of the image to create. An optional parameter allows to control the number of images which are created (from 1 to 10). Another optional parameter controls the dimension of the final image. The OpenAI API limits the possible values to: `256x256`, `512x512` and `1024x1024`. The image creation is represented by the `Create_Image` procedure and we can call it with our request instance:

    ``` Req : OpenAI.Models.CreateImagesRequest_Type; Reply : OpenAI.Models.ImagesResponse_Type; ...

      Req.Prompt := Prompt;
      Req.N := (Is_Null => False, Value => 3);
      Req.Size := (Is_Null => False, Value => "512x512");
      C.Create_Image (Req, Reply);
    

    ```

    Once it has finished, it produces a response which basically contains a list of URLs for each generated image.

    ```

      for Url of Reply.Data loop
         if not Url.Url.Is_Null then
            Ada.Text_IO.Put_Line (OpenAPI.To_String (Url.Url.Value));
         end if;
      end loop;
    

    ```

    The complete image generation example is available at: OpenAI Image Generation(https://gitlab.com/stcarrez/openai-image) .

    For more information about the image creation, have a look at the OpenAI Images API reference(https://platform.openai.com/docs/api-reference/images).

      1. Conclusion

    The Ada OpenAI(https://gitlab.com/stcarrez/ada-openai) library is eagerly awaiting your Ada programming creativity. You can try using the chat generation example to ask the AI to generate some Ada code, but you'll likely be disappointed by the poor quality of Ada programs that OpenAI(https://openai.com/) generates. However, you may still find some small benefits in using it across different domains. I encourage you to give it a try for your enjoyment and pleasure.

    Bridge returned error 500! (19629)

    29 September 2023 at 14:06

    Details

    Type: HttpException
    Code: 500
    Message: https://tomekw.com/feed.xml resulted in 500 Internal Server Error
    File: lib/contents.php
    Line: 106

    Trace

    #0 index.php(11): RssBridge->main()
    #1 lib/RssBridge.php(113): DisplayAction->execute()
    #2 actions/DisplayAction.php(71): DisplayAction->createResponse()
    #3 actions/DisplayAction.php(106): FilterBridge->collectData()
    #4 bridges/FilterBridge.php(168): FeedExpander->collectExpandableDatas()
    #5 lib/FeedExpander.php(88): getContents()
    #6 lib/contents.php(106)

    Context

    Query: action=display&bridge=Filter&url=https://tomekw.com/feed.xml&filter=Ada&filter_type=permit&format=Atom
    Version: 2023-09-24
    OS: Linux
    PHP: 8.2.7

    Go back

    Frenzie, ORelio

    • 29 September 2023 at 14:06

    LEA: first steps as a "smart editor"

    2 September 2023 at 19:53

    Note for subscribers: if you are interested in my Ada programming articles only, you can use this RSS feed link.


    Spot the "tool tip" on the following screenshot...

    Click to enlarge

    Translation: cross-references and source code navigation in LEA are becoming a reality since this evening!

     


    Some Web links:

    LEA

    Web site: http://l-e-a.sf.net/
    Sources, site #1: https://sf.net/p/l-e-a/code/HEAD/tree/
    Sources, site #2: https://github.com/zertovitch/lea
    Alire Crate: Alire - LEA


    Since LEA embeds the HAC compiler, here are a few links about HAC as well:

    HAC

    Web site: https://hacadacompiler.sourceforge.io/
    Sources, site #1: https://sf.net/p/hacadacompiler/code/HEAD/tree/
    Sources, site #2: https://github.com/zertovitch/hac
    Alire Crate: Alire - HAC

    Ada BFD 1.3.0

    20 August 2023 at 14:14
    [Ada/ada-bfd-1.3.jpg](Ada/ada-bfd-1.3.jpg)
      1. Integration with Alire

    For Linux users only, the Ada BFD(https://github.com/stcarrez/ada-bfd) has an associated Alire crate which allows you to use it easily. To get access to the Alire crate, you should add the AWA Alire index(https://github.com/stcarrez/awa-alire-index) in your Alire configuration as follows:

    ``` alr index add=https://github.com/stcarrez/awa-alire-index.git name awa ```

    Then, you can get access to the crate by using

    ``` alr with bfdada ```

    Let's see how to use this library...

      1. Declarations

    The Ada BFD(https://github.com/stcarrez/ada-bfd) library provides a set of Ada bindings that give access to the BFD library. A binary file such as an object file, an executable or an archive is represented by the `Bfd.Files.File_Type` limited type. The symbol table is represented by the `Bfd.Symbols.Symbol_Table` limited type. These two types hold internal data used and managed by the BFD library.

    ```ada with Bfd.Files; with Bfd.Sections; with Bfd.Symbols; ...

     File    : Bfd.Files.File_Type;
     Symbols : Bfd.Symbols.Symbol_Table;
    

    ```

      1. Opening the BFD file

    The first step is to use the `Open` procedure to read the object or executable file whose path is given as argument. The `File_Type` parameter will be initialized to get access to the binary file information. The `Check_Format` function must then be called to let the BFD library gather the file format information and verify that it is an object file or an executable.

    ```ada Bfd.Files.Open (File, Path, ""); if Bfd.Files.Check_Format (File, Bfd.Files.OBJECT) then

       ...
    

    end if; ```

    The `File_Type` uses finalization so that it will close and reclaim resources automatically.

      1. Loading the symbol table

    The symbol table is loaded by using the `Read_Symbols` procedure.

    ```ada

      Bfd.Symbols.Read_Symbols (File, Symbols);
    

    ```

    The resources used by the symbol table will be freed when the symbol table instance is finalized.

      1. Find nearest line

    Once the symbol table is loaded, we can use the `Find_Nearest_Line` function to find the nearest line of a function knowing some address. This is almost a part of that function that the addr2line (1)(https://www.man7.org/linux/man-pages/man1/addr2line.1.html) command is using.

    ```ada File_Name, Func_Name : Ada.Strings.Unbounded.Unbounded_String; Text_Section : Bfd.Sections.Section; Line : Natural; Pc : constant Bfd.Vma_Type := ...; ...

      Text_Section := Bfd.Sections.Find_Section (File, ".text");
      Bfd.Symbols.Find_Nearest_Line (File    => File,
                                     Sec     => Text_Section,
                                     Symbols => Symbols,
                                     Addr    => Pc,
                                     Name    => File_Name,
                                     Func    => Func_Name,
                                     Line    => Line);
    

    ```

    One tricky aspect of using `Find_Nearest_Line` is the fact that the address we are giving must **sometimes** be converted to an offset within the text region. With Address space layout randomization (ASLR)(https://en.wikipedia.org/wiki/Address_space_layout_randomization) a program is mapped at a random address when it executes. Before calling `Find_Nearest_Line`, we must subtract the base address of the memory region. We must now find the virtual address of the start of the text region that is mapped in memory. While the program is running, you can find the base address of the program by looking at the `/proc/self/maps` file. This special file indicates the list of memory regions used by the process with the addresses, flags and other information. Without ASLR, the program is almost always loaded at the `0x00400000` address.

    ``` 00400000-007f9000 r-xp 00000000 fd:01 12067645 /home/... 009f8000-009fa000 r--p 003f8000 fd:01 12067645 /home/... 009fa000-00a01000 rw-p 003fa000 fd:01 12067645 /home/... ```

    But when it is mapped at a random address, we get a different address each time the program is launched:

    ``` 55d5983d9000-55d598592000 r--p 00000000 fc:02 1573554 /... 55d598592000-55d599376000 r-xp 001b9000 fc:02 1573554 /... 55d599376000-55d5997ed000 r--p 00f9d000 fc:02 1573554 /... 55d5997ee000-55d5998bb000 r--p 01414000 fc:02 1573554 /... 55d5998bb000-55d5998c6000 rw-p 014e1000 fc:02 1573554 /... ```

    In that case, the value to use it the first address of first `r--p` region associated with the program (here `0x55d5983d9000`).

    Another method to know the virtual base address is to use the dl_iterate_phdr (3)(https://man7.org/linux/man-pages/man3/dl_iterate_phdr.3.html) function and look at the shared objects which are loaded. This function must be executed by the program itself: it gets as parameter a callback function which is called for each loaded shared object and a data parameter that will be passed to the callback.

    ```

    1. include <dlfcn.h>

    static int dl_callback (struct dl_phdr_info* info, size_t size, void* data) {

     /* VM base address is: info->dlpi_addr */
     return 0;
    

    } ...

      dl_iterate_phdr (dl_callback, 0);
    

    ```

    When the callback is called, you can get the name of the shared object by looking at `info->dlpi_name` and the virtual base address by looking at `info->dlpi_addr`.

    Ada BFD(https://github.com/stcarrez/ada-bfd) is a very specific library that is not always easy to use due to the complexity of binary program representation (ELF, DWARF, ...) and program execution. It is however used in very specific contexts such as the Muen Separation Kernel(https://muen.codelabs.ch/) and the Memory Analysis Tool(https://github.com/stcarrez/mat).

    HAC for native targets

    20 August 2023 at 13:57
    Note for subscribers: if you are interested in my Ada programming articles only, you can use this RSS feed link.

    HAC (the HAC Ada Compiler) was since the first of its previous lives, from Pascal-S, in 1973, to recent commits, translating high-level language exclusively to the machine code of a fictitious machine (a Virtual Machine, abbreviated VM).

    For writing a tiny compiler, a VM is very convenient:

    • You (the compiler creator) can model and adapt it to your needs.
    • You don't depend on hardware.
    • You don't need to rewrite the code generation for each new hardware.
    • You can make the VM running on many hardwares (hence the success of the JVM and .NET around year 2000).


    The HAC VM and its users are enjoying all these advantages.

    However, there is a flip side:

    • A VM is much slower than a real machine (but till year 2000, it didn't matter; you could say: don't worry, just wait a few months for a more powerful computer).
    • Since year 2000, the speed of microprocessors stopped doubling every six months (hence the declining interest in Virtual Machines). Despite the improvements in cache, RAM, and the multiplication of cores, the per-core performance improvement is slower and slower.
    • Supporting only a VM gives the impression that your compiler can only compile to a VM.


    Well, so far, the latter blame was objectively correct regarding HAC until a few weeks ago.
    Now it is changing!
    We have begun an abstract framework for emitting machine code.
    With that framework, HAC can, on demand, emit code for its captive VM or for any implemented machine.
    So you read well, it is not a just-in-time compiler translating VM instructions to native ones.
    We are implementing a direct Ada-to-native compiler - and cross-compiler, since HAC doesn't know on which machine it is running!

    The framework looks like this:

    with HAC_Sys.Defs;

    package HAC_Sys.Targets is

      type Machine is limited interface;

      type Abstract_Machine_Reference is access Machine'Class;

      --------------------
      --  Informations  --
      --------------------

      function Name (m : Machine) return String is abstract;
      function CPU (m : Machine) return String is abstract;
      function OS (m : Machine) return String is abstract;
      function Null_Terminated_String_Literals (m : Machine) return Boolean is abstract;

      ----------------------------
      --  Machine Instructions  --
      ----------------------------

      procedure Emit_Arithmetic_Binary_Instruction
        (m         : in out Machine;
         operator  :        Defs.Arithmetic_Binary_Operator;
         base_typ  :        Defs.Numeric_Typ) is abstract;

    ...


    The code emission in the compiler is being changed (very slowly, be patient!) for going through this new abstract machine mechanism.
    So far we have two implementations:

    • The HAC VM - that way, we ensure HAC-for-HAC-VM works exactly as previously and can pass the test suite.
    • A real target: the AMD64, running under Windows; the machine code is emitted in Assembler form, for the Flat Assembler (FASM).


    The development for multiple targets is embryonic so far.
    However, we can already compile a "hello world"-style program:

    with HAT;

    procedure Native is
      use HAT;
      a : Integer;
    begin
      --  a := 1;  --  Variables: TBD.
      Put_Line ("Hello ...");
      Put_Line ("... world!");
      Put_Line (12340 + 5);
      Put_Line (12350 - 5);
      Put_Line (2469 * 5);
      Put_Line (61725 / 5);
    end Native;


    With the command:
    hac -tamd64_windows_console_fasm native.adb

    HAC produces this:

    ;  Assembler file for the Flat Assembler - https://flatassembler.net/

    format PE64 console
    entry _start
    include 'include\win64a.inc'

    section '.code' code readable executable

    _start:
             push                9
             push                1
             push                -1
             push                -1
             pop                 r14
             pop                 r13
             pop                 r12
             pop                 r11
             add                 r12, _hac_strings_pool
             ccall               [printf], r12
             ccall               [printf], _hac_end_of_line
             push                10
             push                11
             push                -1
             push                -1
             pop                 r14
             pop                 r13
             pop                 r12
             pop                 r11
             add                 r12, _hac_strings_pool
             ccall               [printf], r12
             ccall               [printf], _hac_end_of_line
             push                12340
             push                5
             pop                 r11
             pop                 rax
             add                 rax, r11
             push                rax
             push                20
             push                10
             push                -1
             pop                 r14
             pop                 r13
             pop                 r12
             pop                 r11
             ccall               [printf], _hac_decimal_format, r11
             ccall               [printf], _hac_end_of_line
             push                12350
             push                5
             pop                 r11
             pop                 rax
             sub                 rax, r11
             push                rax
             push                20
             push                10
             push                -1
             pop                 r14
             pop                 r13
             pop                 r12
             pop                 r11
             ccall               [printf], _hac_decimal_format, r11
             ccall               [printf], _hac_end_of_line
             push                2469
             push                5
             pop                 r11
             pop                 rax
             imul                rax, r11
             push                rax
             push                20
             push                10
             push                -1
             pop                 r14
             pop                 r13
             pop                 r12
             pop                 r11
             ccall               [printf], _hac_decimal_format, r11
             ccall               [printf], _hac_end_of_line
             push                61725
             push                5
             pop                 r11
             pop                 rax
             xor                 rdx, rdx
             idiv                r11
             push                rax
             push                20
             push                10
             push                -1
             pop                 r14
             pop                 r13
             pop                 r12
             pop                 r11
             ccall               [printf], _hac_decimal_format, r11
             ccall               [printf], _hac_end_of_line
             stdcall             [ExitProcess],0

    section '.data' data readable writeable
    _hac_end_of_line  db 10, 0
    _hac_decimal_format  db "%d", 0
    _hac_strings_pool db "XHello ...", \
        0, "... world!", 0

    section '.idata' import data readable
    library kernel,'kernel32.dll',\
            msvcrt,'msvcrt.dll'
    import  kernel,\
            ExitProcess,'ExitProcess'
    import  msvcrt,\
            printf,'printf'

    As you can see, the assembler code needs badly some simplification, but, anyway: it works.
    FASM produces from it a relatively small 2048-byte executable which writes

    Hello ...
    ... world!
    12345
    12345
    12345
    12345


    The executable is full of zeroes, due to alignments. The non-zero bytes (more or less, the actual machine code and data) take 605 bytes.

    Some Web links for HAC:

    Main URL: https://hacadacompiler.sourceforge.io/
    Sources, site #1: HAC Ada Compiler download | SourceForge.net
    Sources, site #2: GitHub - zertovitch/hac: HAC Ada Compiler - a small, quick Ada compiler fully in Ada
    Alire Crate: Alire - Hac

    Alternative to exceptions when attempting to pop an empty stack

     

    A recent question on StackOverflow asked about whether or not a mutex is locked twice by the same thread without unlocking the mutex. See https://stackoverflow.com/questions/76890110/c-the-thread-repeatedly-locks-the-same-mutex-multiple-times?noredirect=1#comment135552146_76890110

    The C++ source code presented in the question is:

    1 #include <exception>
    2 #include <memory>
    3 #include <mutex>
    4 #include <stack>
    5
    6 struct empty_stack : std::exception
    7 {
    8    const char* what() const throw();
    9 };
    10
    11 template<typename T>
    12 class threadsafe_stack
    13 {
    14 private:
    15    std::stack<T> data;
    16    mutable std::mutex m;
    17 public:
    18    threadsafe_stack(){}
    19    threadsafe_stack(const threadsafe_stack& other) {
    20       std::lock_guard<std::mutex> lock(other.m);
    21       data = other.data;
    22 }
    23    threadsafe_stack& operator=(const threadsafe_stack& other) = delete;
    24    void push(T new_value)
    25    {
    26       std::lock_guard<std::mutex> lk(m);
    27       data.push(std::move(new_value));
    28    }
    29    std::shared_ptr<T> pop()
    30    {
    31       std::lock_guard<std::mutex> lock(m);
    32       if (data.empty()) throw empty_stack();
    33       std::shared_ptr<T> const res(std::make_shared<T>(data.top()));
    34       data.pop();
    35       return res;
    36    }
    37    void pop(T& value)
    38    {
    39       std::lock_guard<std::mutex> lock(m);
    40       if (data.empty()) throw empty_stack();
    41       *value = data.top();
    42       data.pop();
    43    }
    44    bool empty() const
    45    {
    46       std::lock_guard<std::mutex> lock(m);
    47       return data.empty();
    48    }
    49 };
     

    One issue with this program design, which has nothing to do with the question asked, is throwing an exception when pop is called on an empty stack. An empty stack is not an exceptional situation. In fact the stack starts off as an empty stack when it is created.

    Exception handling should not be used to deal with non-exceptional program state.

    The following Ada stack implementation demonstrates how an empty stack should be handled in a thread-safe manner.

    The Ada program defines a generic thread package with the same operations shown in the C++ example above.

    The Ada program creates a generic stack package and a main procedure. Ada packages are separated into two parts, the package specification and the package body or implementation. The package specification defines the package API.

    1 with Ada.Containers.Doubly_Linked_Lists;
    2 generic
    3    type element_type is private;
    4 package generic_stack is
    5    package List_Pack is new
    6       Ada.Containers.Doubly_Linked_Lists (element_type);
    7 use List_Pack;
    8
    9 protected type Stack is
    10    procedure Push (Item : element_type);
    11    entry Pop (Item : out element_type);
    12    function Is_Empty return Boolean;
    13 private
    14    Buffer : List := Empty_List;
    15 end Stack;
    16
    17 end generic_stack;
     

    The package creates an Ada protected type. A protected type or protected object is a passive unit of concurrency which is implicitly protected from race conditions. There are three kinds of methods available to be defined within a protected type. Protected procedures implicitly acquire an unconditional exclusive read-write lock on the data in the protected type. Protected entries implicitly acquire a conditional exclusive read-write lock on the data in the protected type. A task suspends while the entry boundary condition evaluates to false. The suspended tasks are placed in an entry queue so that they can be activated when the boundary condition evaluates to true. The third kind of method available for a protected object is a function. Protected functions are limited to read-only access to the data in the protected type. Protected functions acquire a shared read lock on the data in the protected object.

    The implementation of the package is in a separate file containing the package body.

    1  package body generic_stack is
    2
    3     -----------
    4     -- Stack --
    5     -----------
    6
    7     protected body Stack is
    8
    9        ----------
    10       -- Push --
    11       ----------
    12
    13       procedure Push (Item : element_type) is
    14       begin
    15          Buffer.Append (Item);
    16       end Push;
    17
    18       ---------
    19       -- Pop --
    20       ---------
    21
    22       entry Pop (Item : out element_type) when
    23                  not Buffer.Is_Empty is
    24       begin
    25          Item := Buffer.Last_Element;
    26          Buffer.Delete_Last;
    27       end Pop;
    28
    29       --------------
    30       -- Is_Empty --
    31       --------------
    32
    33       function Is_Empty return Boolean is
    34       begin
    35          return Buffer.Is_Empty;
    36       end Is_Empty;
    37
    38    end Stack;
    39
    40 end generic_stack;

     

    The push procedure implicitly locks the Buffer when it begins executing and unlocks the Buffer as the procedure completes.

    The pop entry only executes when Buffer.Is_Empty evaluates to false. When that condition is met any immediate call, or any task waiting in the entry queue is allowed to execute the entry. The default queuing policy of the entry queue is FIFO and all tasks suspended in the entry queue will be executed before any new call can be executed. The shared read-write lock is applied when execution of the entry starts and is release upon completion of the entry.

    The Is_Empty function can only execute when no read-write lock is applied to the Buffer data element. Upon starting execution the function applies a shared read lock, allowing any number of tasks to read simultaneously and preventing any procedure or function to execute until the function completes and releases its shared read lock.

    The main procedure, which is the program entry point for this example, follows.

    1  with Ada.Text_IO; use Ada.Text_IO;
    2  with generic_stack;
    3
    4  procedure Main is
    5     package int_stack is new generic_stack (Integer);
    6     use int_stack;
    7
    8     The_Stack : Stack;
    9
    10    task producer is
    11       entry Start;
    12    end producer;
    13
    14    task body producer is
    15    begin
    16       accept Start;
    17       Put_Line ("Producer started.");
    18       for I in 1 .. 20 loop
    19          The_Stack.Push (I);
    20          Put_Line ("Pushed" & I'Image);
    21          delay 0.001;
    22       end loop;
    23    end producer;
    24
    25    task consumer is
    26       entry Start;
    27    end consumer;
    28
    29    task body consumer is
    30       Num : Integer;
    31    begin
    32       accept Start;
    33       Put_Line ("Consumer started.");
    34       for I in 1 .. 20 loop
    35          The_Stack.Pop (Num);
    36          Put_Line ("Popped" & Num'Image);
    37       end loop;
    38    end consumer;
    39
    40 begin
    41    consumer.Start;
    42    delay 0.01;
    43    producer.Start;
    44 end Main;
     

    Line 5 creates an instance of the generic_stack package passing the type Integer as the generic parameter. This results in a stack of integer values.

    Line 8 declares an instance of the Stack type named The_Stack.

    Line 10 declares the interface for the producer task. This task has one entry named Start.

    Line 14 declares the implementation of the producer task.

    Line 16 accepts the Start entry. The producer task will suspend until another task calls its Start entry. Task entries implement a Rendezvous logic allowing synchronous coordination of tasks. After accepting Start the producer executes a for loop 20 times, each time pushing the current value of the loop control variable onto the stack, displaying a message to standard output and then delaying (sleeping) for 0.001 seconds.

    The consumer task declaration begins at line 25. The consumer also has a Start entry.

    The consumer task accepts its start entry at line 32 and then pops 20 values off of the stack. The consumer task has no delay statement. The consumer tasks will then complete its iteration before the producer task pushes another value onto the stack. Each call to the pop entry will therefore encounter an empty stack until the producer pushes another value onto the stack.

    Both the producer task and the consumer tasks begin execution immediately, but both tasks suspend until their Start entry is called.

    Line 40 begins execution of the main procedure which is also the top-level task for the program. Line 41 calls consumer.Start. Line 42 causes the main procedure to delay for 0.01 seconds before line 43 calls producer.start.

    This delay in starting the tasks ensures that the consumer must wait at least 0.01 seconds before the first data will be pushed onto the stack. In other words, the stack will be empty for at least 0.01 seconds.

    The output of this program is:

     

    Consumer started.
    Producer started.
    Pushed 1
    Popped 1
    Pushed 2
    Popped 2
    Pushed 3
    Popped 3
    Pushed 4
    Popped 4
    Pushed 5
    Popped 5
    Pushed 6
    Popped 6
    Pushed 7
    Popped 7
    Pushed 8
    Popped 8
    Pushed 9
    Popped 9
    Pushed 10
    Popped 10
    Pushed 11
    Popped 11
    Pushed 12
    Popped 12
    Pushed 13
    Popped 13
    Pushed 14
    Popped 14
    Pushed 15
    Popped 15
    Pushed 16
    Popped 16
    Pushed 17
    Popped 17
    Pushed 18
    Popped 18
    Pushed 19
    Popped 19
    Pushed 20
    Popped 20
     

    Clearly the program did not encounter any exceptions, yet the consumer could only pop values after they were pushed onto the stack.

    • 13 August 2023 at 05:16

    Variant Records used as a return type from search functions

     When searching for a value in an array a programmer is faced with two possibilities.

    1.       The value searched for is found and the index of the value in the array is returned.

    2.       The value is not found and no array index is returned.

    It is common practice to return a scalar value from functions using the C programming language. This tradition goes back to the earliest days of the C language when unspecified function return types were given the int type by default by the compiler.

    The C language does not have a concept of exceptions or exception handlers. Instead, the value returned by a function either indicates success or failure.

    It is common for positive return values to indicate success and negative or zero values to indicate failure. This pattern was established by the Unix operating system. C was invented to write the Unix operating system.

    A search function is often designed to return the index of the array element containing the value corresponding to the target value for which one is searching. This programming pattern is also commonly found in the C++ language.

    The following function prototype is written using C++;

    int binary_search(string names[], int len, const string target);

    The binary_search function returns an int. The first parameter is an array of string elements. The second parameter is the number of elements in the array of strings. The third element is the string value to search for in the array of strings.

    The binary_search function returns the index value of the array element equal to the target string or it returns -1 if no array element corresponds to the target string. This behavior creates an abstraction with a strong vulnerability of confusion. The return value serves two purposes. It identifies whether or not the target value was found in the array and it identifies the index value of the array element equaling the target value.

    The complete binary_search function is defined as:

    int binary_search(string names[], int len, const string target)
    {
      int low = 0;
      int high = len - 1;
      int mid = 0;\

      
    string item;
      while (low <= high) {
        mid = (low + high) / 2;
        item = names[mid];
        if (item == target) {
          return mid;
        }
        if (strcmp(target.c_str(), item.c_str()) < 0) {
          high = mid - 1;
        } else
          low = mid + 1;
      }
      return -1;
    }

     

    Logically whether or not the value was found is separate from the index value of the found element. There should be no index value for a found element when no element is found. The function allows the programmer to make the mistake of trying to reference an array element before the beginning of the array. In C and C++ doing this is undefined behavior, and is always wrong.

    Avoiding undefined behavior

    The C and C++ languages provide no assistance in the effort to avoid undefined behaviors. The compilers are not required to check for undefined behavior because the programmer is burdened with the responsibility of avoiding undefined behavior.

    The Ada programming language provides a useful data structure known as a variant record, which C and C++ programmers may think is similar to a union. C and C++ unions provide different views of a common memory area so that, for instance, a number might be viewed as an int or a float. Ada variant records are different.

    Every Ada variant record has a publicly visible discriminant field, even if the type is opaque. That discriminant field controls the contents, not just the view, of the record. The following example is used as a return type for a search function.

       type Result_T (found : Boolean) is record
          case found is
             when True =>
                Index : Natural;
             when False =>
                null;
          end case;
       end record;

    The discriminant field in this example is named “found”. Found is a Boolean value. Every instance of Result_T contains a “found” field. The case statement within the record definition declares a second field named Index, which is of type Natural when and only when found is True. When found is False the record type contains no additional field. It only contains the found field.

    This data structure separates the responsibility of identifying whether or not the target value was found from identifying the index value of the element equaling the target value.

    The complete Ada version of the binary_search function is

       function binary_search
        
    (Names : in str_arr; Target : Unbounded_String) return            Result_T is
          low  : Natural := Names'First;
          High : Natural := Names'Last;
          Mid  : Natural;
          Item : Unbounded_String;
       begin
          while low <= High loop
             Mid  := (High - low) / 2 + low;
             Item := Names (Mid);
             if Item = Target then
                return (found => True, Index => Mid);
             end if;
             if Target < Item then
                High := Mid - 1;
             else
                low := Mid + 1;
             end if;
          end loop;
          return (found => False);
       end binary_search;

    If the item corresponding to Target is found the return value contains both a found and an Index field.

                return (found => True, Index => Mid);

    If no item corresponding to Target is found the return value only contains the found field.

          return (found => False);

    Since no Index value exists when found equals False, no undefined behavior can occur. The Ada runtime raises the pre-defined exception CONSTRAINT_ERROR along with the message “discriminant check failed” if the programmer attempts to reference the Index field when found equals False.

    The proper way to deal with a variant record such as this returned by a function is:

       Result : Result_T := binary_search (Names,                                                      To_Unbounded_String ("ali2"));
    begin
     if Result.found then
          Put_Line ("The given index is " & Result.Index'Image);
          Put_Line ("The found name is " & To_String (Names                                                      (Result.Index)));
     else
        Put_Line ("String not found!");
     end if;

    This is similar to the proper way to respond to the return value from the C or C++ function. The difference is that when the return value is not evaluated the C or C++ program will not raise an exception. Instead it will simply exhibit an undefined behavior.
    • 8 August 2023 at 02:00

    Array Handling

     

    Arrays in both C and Ada are a compound type with the elements arranged sequentially in memory. All the elements in an array are of a single type. For instance an array may contain integer elements or it may contain floating point elements, or it may contain strings of characters, or it may even contain other arrays.

    One might therefore assume that arrays in C and Ada are fundamentally the same. This article explores that assumption and finds some clear differences between C arrays and Ada arrays.

    Array Characteristic

    C language

    Ada Language

    Array types

    C only allows definition of the type of an array element. It does not allow declaration of an array type.

    Every Ada array is a member of a type, even if it is an anonymous type.

    Index range

    Valid C array indices always start at 0. The C language provides no implicit checking for referencing invalid array indices.

    Ada array indices may begin at any programmer-chosen scalar value and end at any programmer-chosen scalar value. Ada compilers validate static array index references and by default the Ada runtime checks array index values during program execution.

    Array declaration

    C arrays are declared by specifying the element type, the array name, and the number of elements in the array.

    Ada arrays are declared by specifying the array name, the array index range and the array element type.

    Array size

    C array sizes can only be calculated using the sizeof operator within the scope in which the array is first declared. Passing an array to a function results in only the address of the first array element being passed to the function. The programmer must pass another parameter to the function indicating the size of the array. The compiler cannot ensure the size parameter is correct.

    Ada array attributes are available wherever the array is visible. Ada array attributes include:

    •      ‘Size – The number of bits the array occupies in memory.
    •        ‘Length – The number of elements in the array.
    •         ‘First – The first valid index value for the array.
    •      ‘Last – The last valid index value for the array
    •      ‘Range – The range specified by ‘First .. ‘Last

     

    Array slicing

    C does not provide a facility for array slicing.

    Ada provides the ability to define a slice of an array.

    Array assignment

    C does not allow the programmer to copy one array to another using a single assignment statement.

    Ada allow arrays to be copied using a single assignment statement.

    The impact of these differences is demonstrated by reviewing the Merge-Sort algorithm implemented in both languages.

    The first example is a C implementation of the Merge-Sort algorithm.

    1 /*     
    2 * C Program to Perform Merge Sort using Recursion and Functions
    3 */
    4
    5 #include <stdio.h>
    6 #include <stdlib.h>
    7
    8 // merge function
    9 void Merge(int arr[], int left, int mid, int right)
    10 {
    11    int i, j, k;
    12    int size1 = mid - left + 1;
    13    int size2 = right - mid;
    14
    15    // created temporary array
    16    int Left[size1], Right[size2];
    17
    18    // copying the data from arr to temporary array
    19    for (i = 0; i < size1; i++)
    20        Left[i] = arr[left + i];
    21
    22    for (j = 0; j < size2; j++)
    23       Right[j] = arr[mid + 1 + j];
    24
    25    // merging of the array
    26    i = 0; // intital index of first subarray
    27    j = 0; // inital index of second subarray
    28    k = left; // initial index of parent array
    29    while (i < size1 && j < size2)
    30    {
    31        if (Left[i] <= Right[j])
    32        {
    33            arr[k] = Left[i];
    34            i++;
    35        }
    36        else
    37        {
    38            arr[k] = Right[j];
    39            j++;
    40        }
    41        k++;
    42    }
    43
    44    // copying the elements from Left[], if any
    45    while (i < size1)
    46    {
    47        arr[k] = Left[i];
    48        i++;
    49        k++;
    50    }
    51
    52    // copying the elements from Right[], if any
    53    while (j < size2)
    54    {
    55        arr[k] = Right[j];
    56        j++;
    57        k++;
    58    }
    59 }
    60
    61 //merge sort function
    62 void Merge_Sort(int arr[], int left, int right)
    63 {
    64    if (left < right)
    65    {
    66
    67        int mid = left + (right - left) / 2;
    68
    69        // recursive calling of merge_sort
    70        Merge_Sort(arr, left, mid);
    71        Merge_Sort(arr, mid + 1, right);
    72
    73        Merge(arr, left, mid, right);
    74    }
    75 }
    76
    77 // driver code
    78 int main()
    79 {
    80     int size;
    81     printf("Enter the size: ");
    82     scanf("%d", &size);
    83
    84     int arr[size];
    85     printf("Enter the elements of array: ");
    86     for (int i = 0; i < size; i++)
    87     {
    88         scanf("%d", &arr[i]);
    89     }
    90
    91     Merge_Sort(arr, 0, size - 1);
    92
    93     printf("The sorted array is: ");
    94     for (int i = 0; i < size; i++)
    95     {
    96         printf("%d ", arr[i]);
    97     }
    98     printf("\n");
    99     return 0;
    100 } 

    Both the Merge_Sort function and the Merge function take several parameters.

     

    62 void Merge_Sort(int arr[], int left, int right)
    9 void Merge(int arr[], int left, int mid, int right) 

    The first parameter in each function specifies an array of int elements. The remaining parameters specify various int values. These extra parameters are needed in C because all array indices must begin at 0, the size of the array parameter is not passed with the array, and C does not provide array slicing.

    The C programmer must provide all this information as additional function parameters.

    The C language does not provide assignment of one array to another. The programmer must explicitly create a loop and assign each element one at a time:

    18    // copying the data from arr to temporary array
    19    for (i = 0; i < size1; i++)
    20        Left[i] = arr[left + i];
    21
    22    for (j = 0; j < size2; j++)
    23       Right[j] = arr[mid + 1 + j];
     

    Each of these examples is a demonstration of the low-level terseness of C. Each of these examples shows how much more writing must be done in C to achieve what Ada does very simply.

    The Ada version of the Merge-Sort algorithm is implemented in a generic Ada package so that the sort procedure declared within the package specification can be used to sort any mutable data type.

    The Ada solution is done using three filles.

    1 generic
    2    type element_type is private;
    3    type array_type is array (Integer range <>) of element_type;
    4    with function "<" (left, right : element_type) return Boolean is <>;
    5 package generic_merge_sort is
    6    procedure sort (Item : in out array_type);
    7 end generic_merge_sort;

    The generic package specification requires three parameters when an instance of the package is declared. The first parameter is the name of the element type for the array which will be sorted. The second parameter is the name of the array type to be sorted. This parameter is restricted to an unconstrained array type indexed by Integer values. Each element of the array type must be the type passed to the first parameter. The third parameter is a possible overloading of the “<” function. This is an optional parameter. If the element_type already has a “<” function defined that function will be used by default.

    The procedure sort is defined to take one parameter with mode “in out”, meaning the parameter value(s) will be used and possibly modified within the procedure. All modifications will be visible to the calling scope.

    The package body contains the logic used to implement the merge-sort algorithm.

    1 package body generic_merge_sort is
    2    procedure merge (Item : in out array_type) is
    3       mid   : Integer    := Item'First + (Item'Last - Item'First) / 2;
    4       Left  : array_type := Item (Item'First .. mid);
    5       Right : array_type := Item (mid + 1 .. Item'Last);
    6       I     : Integer    := Left'First;
    7       J     : Integer    := Right'First;
    8       K     : Integer    := Item'First;
    9    begin
    10      while I <= Left'Last and then J <= Right'Last loop
    11         if Left (I) < Right (J) then
    12            Item (K) := Left (I);
    13            I        := I + 1;
    14         else
    15            Item (K) := Right (J);
    16            J        := J + 1;
    17         end if;
    18         K := K + 1;
    19      end loop;
    20      -- copying unused items from Left
    21      while I <= Left'Last loop
    22         Item (K) := Left (I);
    23         I        := I + 1;
    24         K        := K + 1;
    25      end loop;
    26      -- copying unused items from right
    27      while J <= Right'Last loop
    28         Item (K) := Right (J);
    29         J        := J + 1;
    30         K        := K + 1;
    31      end loop;
    32   end merge;
    33
    34   ----------
    35   -- sort --
    36   ----------
    37
    38   procedure sort (Item : in out array_type) is
    39      mid : Integer;
    40   begin
    41      if Item'Length > 1 then
    42         mid := Item'First + (Item'Last - Item'First)/2;
    43         sort(Item(Item'First .. Mid));
    44         sort(Item(Mid + 1 .. Item'Last));
    45         merge(Item);
    46      end if;
    47   end sort;
    48
    49 end generic_merge_sort;

    This example make extensive use of Ada array attributes. The value of mid in both the merge procedure and the sort procedure is calculated using the ‘First and ‘Last array attributes, simplifying the procedure signature for both sort and merge. Both procedures only need an instance or slice of an array passed to them.

    In the merge procedure the Left and Right instances of array_type are created and initialized with slices of the array Item passed to the procedure. Ada’s ability to assign one array or slice to another array or slice of the same type eliminates the need to explicitly calculate the sizes needed for the Left and Right arrays. It also eliminates the need to explicitly copy the values iteratively.

    4       Left  : array_type := Item (Item'First .. mid);
    5       Right : array_type := Item (mid + 1 .. Item'Last);

    For reference, here is the corresponding C code:

    12    int size1 = mid - left + 1;
    13    int size2 = right - mid;
    14
    15    // created temporary array
    16    int Left[size1], Right[size2];
    17
    18    // copying the data from arr to temporary array
    19    for (i = 0; i < size1; i++)
    20        Left[i] = arr[left + i];
    21
    22    for (j = 0; j < size2; j++)
    23       Right[j] = arr[mid + 1 + j];

    A comparison of the C merge_sort function and the Ada sort procedure provides more insight to the differences between C arrays and Ada arrays.

    C merge_sort:

    62 void Merge_Sort(int arr[], int left, int right)
    63 {
    64    if (left < right)
    65    {
    66
    67        int mid = left + (right - left) / 2;
    68
    69        // recursive calling of merge_sort
    70        Merge_Sort(arr, left, mid);
    71        Merge_Sort(arr, mid + 1, right);
    72
    73        Merge(arr, left, mid, right);
    74    }
    75 }

    Ada sort:

    38   procedure sort (Item : in out array_type) is
    39      mid : Integer;
    40   begin
    41      if Item'Length > 1 then
    42         mid := Item'First + (Item'Last - Item'First)/2;
    43         sort(Item(Item'First .. Mid));
    44         sort(Item(Mid + 1 .. Item'Last));
    45         merge(Item);
    46      end if;
    47   end sort;

    The condition in the “if” statement, while achieving the same result, is quite different. The C function relies upon the correctness of the left and right parameters passed as the second and third arguments to the function. The Ada procedure only relies upon the ‘Length attribute of the array. The Ada syntax is much less error-prone and a more direct statement to a human reader about the intention of the conditional statement. In order to get a complete understanding of the purposes of the C left and right parameters one must read the context of the main procedure, while the meaning of the Ada conditional is completely clear in the local context. Since the sort parameter type is an unconstrained array type every slice of the array is also an instance of the unconstrained array type.

    The C Merge_Sort function recursively calls itself passing the beginning of the arr parameter each time but with different values for left and right.

    The Ada sort function recursively calls itself passing slices of the parameter Item to each recursive call. In this manner each recursion of Sort only sees the slice passed to it and all the other elements of the array initially passed to the sort procedure from main are not available to the recursive call.

    The C Merge procedure passes the full array, but also passes three parameters containing the left, mid and right index positions in the array. The Ada merge procedure only array slice passed to it by the sort procedure.

    The main function in C is:

    78 int main()
    79 {
    80     int size;
    81     printf("Enter the size: ");
    82     scanf("%d", &size);
    83
    84     int arr[size];
    85     printf("Enter the elements of array: ");
    86     for (int i = 0; i < size; i++)
    87     {
    88         scanf("%d", &arr[i]);
    89     }
    90
    91     Merge_Sort(arr, 0, size - 1);
    92
    93     printf("The sorted array is: ");
    94     for (int i = 0; i < size; i++)
    95     {
    96         printf("%d ", arr[i]);
    97     }
    98     printf("\n");
    99     return 0;
    100 }

    The main procedure in Ada is:

    1 with Ada.Text_IO;         use Ada.Text_IO;
    2 with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;
    3 with generic_merge_sort;
    4
    5 procedure Main is
    6    type int_arr is array (Integer range <>) of Integer;
    7    package int_sort is new generic_merge_sort
    8           (element_type => Integer, array_type => int_arr);
    9    use int_sort;
    10   Num_Elements : Positive;
    11 begin
    12   Put ("Enter the size of the array: ");
    13   Get (Num_Elements);
    14   declare
    15      the_array : int_arr (1 .. Num_Elements);
    16   begin
    17      Put_Line ("Enter the array values:");
    18      for value of the_array loop
    19         Get (value);
    20      end loop;
    21      sort (the_array);
    22      Put_Line ("The sorted array is:");
    23      for value of the_array loop
    24         Put (value'Image & " ");
    25      end loop;
    26      New_Line;
    27   end;
    28 end Main;

    Lines 6 through 9 of the Ada program are needed to create an instance of the generic_merge_sort package.

    6    type int_arr is array (Integer range <>) of Integer;
    7    package int_sort is new generic_merge_sort
    8           (element_type => Integer, array_type => int_arr);
    9    use int_sort;

    An inner block is declared so that an array of the size entered by the user can be created. The rest of the program is performed in this inner block.

    14   declare
    15      the_array : int_arr (1 .. Num_Elements);
    16   begin
    17      Put_Line ("Enter the array values:");
    18      for value of the_array loop
    19         Get (value);
    20      end loop;
    21      sort (the_array);
    22      Put_Line ("The sorted array is:");
    23      for value of the_array loop
    24         Put (value'Image & " ");
    25      end loop;
    26      New_Line;
    27   end;

    The for loop used to read all the values into the array starting at line 18 is an Ada iterator loop. The loop parameter “value” becomes an alias for each array element starting at the first element and ending at the last element. Thus, each value entered by the user is placed directly into the array without explicit index notation.

    The array is sorted.

    21      sort (the_array);

    Finally, the sorted array is output to standard output and the program ends.

    Conclusion:

    The C and Ada examples implement the same merge-sort algorithm to sort an array of integer values. C arrays are more primitive abstractions than are Ada arrays. The more primitive abstractions require more lines of C source code than does the Ada implementation. The C implementation also requires a more complex parameter list for both its Merge function and its Merge_Sort function. The Ada implementation concentrates on merely sorting an array without additional parameters.

    The built-in characteristics of Ada arrays really do simplify the manipulation of arrays, even when those arrays are passed to an Ada function or procedure.

    I learned long ago that complexity is the enemy of correctness. In these examples we see that lower complexity also provides fewer opportunities for programmer error. We also see that a programming language with a low level syntax is not necessarily simpler than a programming language with a higher level syntax. Terse is not always simpler. Terse does not always result in less typing for the programmer.

    • 5 August 2023 at 20:36

    Your Amazon Affiliate Account has been closed

    29 July 2023 at 00:00

    Back in 2015, I wanted to build a NAS for backups at home. I found the shopping experience for hard drives on Amazon extremely frustrating. There is no sort option for Price per $ATTRIBUTE. You can only view 10 search results at a time. The first dozen results are sponsored listings. Many products are available through third party sellers on Amazon’s marketplace at prices below what’s shown in the “Buy Box.” but you’ll never see these prices unless you click through to every product.

    I decided to write a Python script to find all of the hard drives on Amazon, figure out their capacity, then sort by price per GB. I found the Amazon Product Advertising API, which allows you to query their product catalog and get results back as XML (later JSON). You need an access key to use the API which is only available through the Amazon Affiliates program. So I signed up.

    The idea behind the affiliates program is that you’ll generate links to Amazon with a tracking ID and get people to click on them and buy stuff. I think Amazon’s original expectation of this is that bloggers and journalists would review products and refer people to Amazon to buy them. In an ideal world, this is beneficial for everyone; merchants get more sales, Amazon gets more revenue, and affiliates get a few percent commission off the top.

    Amazon won’t give new affiliates access to the Product Advertising API until they’ve referred at least three sales. So my Python script turned into a diskprices.com. I built a list of hard drives in a spreadsheet and sorted by price per GB. A few people clicked my links. Armed with an API key, I was able to programmatically generate the listing and keep it up to date. I built spam filters and moderation tools so I could remove fake products and fix errors in the listings where I found them.

    More people started visiting my site and I signed up for Amazon’s affiliate program in other countries. Amazon treats each country as a separate business, so you have to do the three initial sales for each region separately.

    The site kept growing in both traffic and commission income. I’ve been running diskprices.com for nearly eight years, reviewed and approved by the affiliate program administrators several times.

    The problem

    Amazon has automated tools that crawl affiliate sites looking for violations of their Operating Agreement. Sometimes, people will find a good deal on diskprices.com and copy the link to a forum or chat room to share with their friends. If one of Amazon’s bots finds a link with my affiliate tag on a site other than diskprices.com, they assume I’m doing something shady and flag my account. This generates a form email.

    The warning email

    Created at Fri, Jun 30, 2023 at 9:55 AM
    From Amazon Associates <[email protected]>
    To [email protected]
    Subject ACTION REQUIRED – Your Amazon Affiliate Account - diskprices0a-22

    Hello diskprices0a-22, Your Affiliate account is at risk of closure. Why? We reviewed your account as part of our ongoing monitoring of the Amazon Affiliate Programme. During our review, we determined that you are not in compliance with the Operating Agreement, found here: https://affiliate-program.amazon.com.au/help/operating/agreement.

    While reviewing your account we noticed traffic coming to Amazon from one or more sites that are currently not included in your Associates account. To understand how your Special Links are used by customers to get to the Amazon site, we request you to provide a detailed description of: • A list of the Sites, Social media handles, Mobile applications, link sharing sites (like linktree.com, campsite.bio) on which your Special Links or banner ads are posted, • Methods you are using to get customers to your website/social media page, • Live links to your Sites, (provide links to Amazon ads/special links displayed on your website/webpage) • Any other ways in which your Special Links are used by customers to get to the Amazon site (like advertising services), Please ensure that the list of sites associated with your account is complete, accurate, and up-to-date. Please do refer to the Operating Agreement for more details.

    What’s next? Within five business days please correct the violations and notify us when you are in compliance through our customer service contact page: https://affiliate-program.amazon.com.au/home/contact. Please choose the subject ‘Warning/Information Request Response’ from the drop-down menu, and be sure to reference Issue Code 83441-AU in the comments field. If you do not respond within five business days, we may close your Affiliate account and withhold commissions. More information. For more information about what we’re looking for in your response, see our Warning help content, found here: https://affiliate-program.amazon.com.au/help/node/topic/GPPXH5WSX22AUNKR. We look forward to hearing from you soon. Amazon.com.au

    The response

    Created at Fri, Jun 30, 2023 at 10:18 AM
    From Jeremy Grosser <[email protected]>
    To Amazon.com.au Affiliate Program (replied via web form)
    Subject Re: ACTION REQUIRED – Your Amazon Affiliate Account - diskprices0a-22

    Issue Code 83441-AU

    • A list of the Sites, Social media handles, Mobile applications, link sharing sites (like linktree.com, campsite.bio) on which your Special Links or banner ads are posted,

    I only post links on diskprices.com. If my links have been posted elsewhere, it’s because people have copy/pasted them from diskprices.com. I have no control over this.

    • Methods you are using to get customers to your website/social media page,

    I do not promote my site, inbound traffic to diskprices.com is organic. Here’s a list of referrers reported by browsers in the last 24 hours for all of diskprices.com, not limited to the amazon.com.au section. For the social media sites (reddit, fark, etc), I did not create those posts and have no relationship with the users that posted them.

    Redacted list of referrer URLs, including forums.overclockers.com.au

    • Live links to your Sites, (provide links to Amazon ads/special links displayed on your website/webpage)

    All links to amazon.com.au originate from https://diskprices.com/?locale=au

    • Any other ways in which your Special Links are used by customers to get to the Amazon site (like advertising services),

    I do not pay for advertising, search traffic, or SEO services. I just built a website that some people find useful and share with their friends.

    The waiting

    I’ve had similar exchanges with the affiliate programs for Amazon.com, Amazon.co.uk, Amazon.ca, Amazon.it, Amazon.de, Amazon.fr, Amazon.es, and Amazon.se. They always wait 10 days after sending the warning email to review your response. After 10 days, the flag on your account either goes away with no further communication, or your account is deleted.

    I didn’t hear anything after 10 days, so I thought I was safe and had provided sufficient evidence to prove that I’m not violating the Operating Agreement.

    One month later…

    The bad news

    Created at: Sat, Jul 29, 2023 at 5:20 AM
    From: Amazon Associates <[email protected]>
    To: [email protected]
    Subject: Your Amazon Affiliate Account has been closed - diskprices0a-22

    Hello diskprices0a-22, Effective today, Amazon is terminating your Affiliate account as well as the Operating Agreement that governs it (link below). Why? We reviewed your account as part of our ongoing monitoring of the Amazon Affiliate Programme. During our review, we determined that you are not in compliance with the Operating Agreement, found here: https://affiliate-program.amazon.com.au/help/operating/agreement. The violations include the following:

    You are referring traffic from a site that is not your Site. An example can be found here: https://forums.overclockers.com.au/ While reviewing your account we noticed traffic coming to Amazon from one or more site/social media handle/mobile application that are currently not included in your Associates account. Please ensure that the list of site/social media handle/mobile application associated with your account is complete, accurate, and up-to-date. Please do refer to the Operating Agreement for more details https://affiliate-program.amazon.com.au/help/operating/policies We previously issued you a non-compliance warning regarding your Associates account. You did not respond or come into compliance within the specified time.

    What’s next? You must stop using all Amazon Program Content (for example: Images, trademarks and other information in connection with the Associates Program) and promptly remove all links to the Amazon site. Because you are not in compliance with the Operating Agreement, Amazon will not pay you any outstanding commission income. Please be aware that any other related accounts may be closed without payment of any commissions. Amazon reserves all other rights and claims. In limited cases this closure may be appealable. See Appeals help for more information, found here: https://affiliate-program.amazon.com.au/help/node/topic/GACDBRFKVDTXSPTH. Amazon.com.au

    The point

    Amazon.com.au approved of my account for the last four years and has paid significant commissions. I don’t even have an account on the forum they claim I’m spamming with affiliate links.

    diskprices.com no longer serves data from amazon.com.au

    Threads of Confusion

    Many programming languages support concurrent behavior through the creation of threads and the explicit manipulation of semaphores, locks or mutexes. The C language seems to have initiated this approach to handling concurrency.

    C example

    The following example of using a mutex with thread comes from C Program to Show Thread Interface and Memory Consistency Errors – GeeksforGeeks

    1       // C program to use a mutex to avoid memory consistency

    2       // errors

    3       #include <pthread.h>

    4       #include <stdio.h>

    5       #include <stdlib.h>

    6

    7       // Global variable that will be shared among threads

    8       int shared_counter = 0;

    9

    10       // Mutex to protect the shared counter

    11       pthread_mutex_t shared_counter_mutex

    12       = PTHREAD_MUTEX_INITIALIZER;

    13

    14       // Function that will be executed by each thread

    15       void* thread_function(void* thread_id)

    16       {

    17               // Get the thread ID

    18               long tid = (long)thread_id;

    19

    20               // Lock the mutex to protect the shared counter

    21               pthread_mutex_lock(&shared_counter_mutex);

    22

    23               // Increment the shared counter

    24               shared_counter++;

    25

    26               // Print the thread ID and the updated value of the

    27               // shared counter

    28               printf("Thread %ld: shared_counter = %d\n", tid,

    29                             shared_counter);

    30

    31               // Unlock the mutex

    32               pthread_mutex_unlock(&shared_counter_mutex);

    33

    34               // Return NULL to indicate successful execution of the

    35               // thread

    36               return NULL;

    37   }

    38

    39       int main(int argc, char* argv[])

    40       {

    41               // Check if the number of arguments is correct

    42               if (argc != 2) {

    43                       printf("Usage: %s <number_of_threads>\n", argv[0]);

    44                       exit(EXIT_FAILURE);

    45       }

    46

    47               // Get the number of threads to create from the command

    48               // line arguments

    49               int num_threads = atoi(argv[1]);

    50

    51               // Create an array of pthread_t structures to store the

    52               // thread IDs

    53               pthread_t* threads = (pthread_t*)malloc(

    54                       num_threads * sizeof(pthread_t));

    55

    56               // Create the specified number of threads

    57               for (int i = 0; i < num_threads; i++) {

    58                       int status = pthread_create(

    59                               &threads[i], NULL, thread_function, (void*)i);

    60                       if (status != 0) {

    61                               printf("Error: pthread_create() returned error "

    62                                             "code %d\n",

    63                                             status);

    64                               exit(EXIT_FAILURE);

    65           }

    66       }

    67

    68               // Wait for all threads to finish execution

    69               for (int i = 0; i < num_threads; i++) {

    70                       int status = pthread_join(threads[i], NULL);

    71                       if (status != 0) {

    72                               printf("Error: pthread_join() returned error "

    73                                             "code %d\n",

    74                                             status);

    75                               exit(EXIT_FAILURE);

    76           }

    77       }

    78

    79               // Free the memory allocated for the thread IDs

    80               free(threads);

    81

    82               // Print the final value of the shared counter

    83               printf("Final value of shared_counter: %d\n",

    84                             shared_counter);

    85

    86               // Return success

    87               return 0;

    88   }

    The C threading model uses the pthread library to create threads. The behavior of each created thread is controlled by the function passed to the thread as part of the pthread_create function parameter list. First an array of pthread_t structures is created:

    53               pthread_t* threads = (pthread_t*)malloc(

    54                       num_threads * sizeof(pthread_t));

    Next, the Id number of each thread is created as the function named thread_function is passed to each element of the thread as a parameter to the pthread_create function:

    57               for (int i = 0; i < num_threads; i++) {

    58                       int status = pthread_create(

    59                               &threads[i], NULL, thread_function, (void*)i);

    60                       if (status != 0) {

    61                               printf("Error: pthread_create() returned error "

    62                                             "code %d\n",

    63                                             status);

    64                               exit(EXIT_FAILURE);

    65           }

    66       }

    The third parameter to the pthread_create command is the function passed to the newly created thread. The fourth argument is the parameter passed to the function passed to the thread. Not only is the thread function parameter passed as the fourth argument to the pthread_create function, it is also cast to a pointer to void. This bit of syntax can be confusing because the value being passed is not a pointer, but rather an int.

    Let's now look inside the function named thread_function, but before that, let's look at the creation of the pthread_mutex used to control access to the shared counter.

    10       // Mutex to protect the shared counter

    11       pthread_mutex_t shared_counter_mutex

    12       = PTHREAD_MUTEX_INITIALIZER;

    Yes, the pthread_mutex_t instance used to control access to the shared counter is itself a shared instance of a type. You might also see that the shared_counter_mutex is only loosely connected to the shared_counter integer variable. That loose connection is a voluntary connection not enforced by any syntax in the C pthreads library.

    The thread_function is defined as:

    14       // Function that will be executed by each thread

    15       void* thread_function(void* thread_id)

    16       {

    17               // Get the thread ID

    18               long tid = (long)thread_id;

    19

    20               // Lock the mutex to protect the shared counter

    21               pthread_mutex_lock(&shared_counter_mutex);

    22

    23               // Increment the shared counter

    24               shared_counter++;

    25

    26               // Print the thread ID and the updated value of the

    27               // shared counter

    28               printf("Thread %ld: shared_counter = %d\n", tid,

    29                             shared_counter);

    30

    31               // Unlock the mutex

    32               pthread_mutex_unlock(&shared_counter_mutex);

    33

    34               // Return NULL to indicate successful execution of the

    35               // thread

    36               return NULL;

    37   }

    The thread function must explicitly lock the shared_counter mutex, then increment the shared_counter, then output the current value of the shared_counter and finally unlock the shared_counter_mutex.

    A major source of confusion

    The order of the locking modifying and unlocking the mutex is critical. The shared_counter knows nothing of these locks. The pthread_mutex_t has no syntactical connection to the shared_counter. On the other hand, failing to lock, perform operations and then unlock the mutex will result in semantic failures which prevent the mutex from properly locking the shared_counter and then unlocking the shared counter after the operations are completed. Even worse, there is no syntax in the C pthread library prohibiting a thread from simply accessing the shared_counter while completely ignoring the mutex lock and unlock behaviors.

    Further program issues

    The remainder of the program calls pthread_join, forcing the main thread to wait until all the pthreads have completed before continuing to execute its sequence of instructions.

    68               // Wait for all threads to finish execution

    69               for (int i = 0; i < num_threads; i++) {

    70                       int status = pthread_join(threads[i], NULL);

    71                       if (status != 0) {

    72                               printf("Error: pthread_join() returned error "

    73                                             "code %d\n",

    74                                             status);

    75                               exit(EXIT_FAILURE);

    76           }

    77       }

    It is important to understand that the pthread_join function called for each thread can fail and return failure status.

    Once the pthreads have completed the program frees all the pointers in the array of pointers to pthread_t that were created to create the array of threads. This is done because it is always correct to free dynamically allocated memory when that memory is no longer needed in the program.

    While this action is not particularly confusing, it is yet another detail that should be explicitly implemented.

    Ada Example

    The Ada programming language has tasking, which is roughly equivalent to threads, built into the core language since the first Ada language standard in 1983. In the 1995 standard protected types were added to the core language, which allow asynchronous communication between tasks. There are no special Ada libraries to implement tasking or protected types.

    A protected type or protected object is protected against inappropriate simultaneous access to a shared data structure. A protected type or object is built very much in the philosophy of Object Oriented Programming inasmuch as the protected object has programmer-defined behaviors, however the locking and unlocking of the protected object is performed implicitly by the object itself and not through explicit lock and unlock methods called by the task accessing the protected object.

    There are three kinds of protected methods.

    • Protected procedures – Protected procedures control an unconditional exclusive read-write lock on the protected object. The task calling the protected procedure can access the protected object whenever the object is unlocked.

    • Protected entries – Protected entries control a conditional exclusive read-write lock on the protected object. The calling task can only execute the entry when its boundary condition evaluates to True and only then can the task implicitly acquire the exclusive read-write lock.

    • Protected functions – Protected functions control an unconditional shared read lock on the protected object. This allows multiple tasks to simultaneously read from the protected object at the same time, but prevents any task to execute any procedure or entry while tasks are calling protected functions.

    The following Ada example creates a protected object implementing the shared counter. The protected object in this example implements one procedure and one function. The source code for this program is:

    1       with Ada.Text_IO;         use Ada.Text_IO;

    2       with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;

    3

    4       procedure Main is

    5             Num_Tasks : Positive;

    6

    7             -- protected object shared by all the tasks

    8             protected counter is

    9                   procedure update (The_Count : out Natural);

    10                   function get_value return Natural;

    11             private

    12                   count : Natural := 0;

    13             end counter;

    14

    15             protected body counter is

    16                   procedure update (The_Count : out Natural) is

    17                   begin

    18                         count     := count + 1;

    19                         The_Count := count;

    20                   end update;

    21                   function get_value return Natural is

    22                   begin

    23                         return count;

    24                   end get_value;

    25             end counter;

    26

    27             -- Define the task type

    28             task type thread is

    29                   entry set_id (Id : in Positive);

    30             end thread;

    31

    32             task body thread is

    33                   Me       : Positive;

    34                   My_Count : Natural;

    35             begin

    36                   -- accept the set_id entry call from the main task

    37                   accept set_id (Id : in Positive) do

    38                         Me := Id;

    39                   end set_id;

    40

    41                   counter.update (My_Count);

    42                   Put_Line ("Task" & Me'Image & ": counter =" & My_Count'Image);

    43             end thread;

    44

    45       begin

    46             Put ("Enter the number of tasks to create: ");

    47             Get (Num_Tasks);

    48

    49             -- declare an inner block in which all the tasks will execute

    50             -- the inner block will only complete after all tasks have completed

    51             declare

    52                   -- create an array of thread objects

    53                   pool : array (1 .. Num_Tasks) of thread;

    54             begin

    55                   for I in pool'Range loop

    56                         -- set the id number of each thread object

    57                         pool (I).set_id (I);

    58                   end loop;

    59             end;

    60

    61      -- output the total after all threads have completed

    62      Put_Line

    63        ("The final value of the shared counter:" &

    64         Natural'Image (counter.get_value));

    65

    66   end Main;

    Ada allows functions, procedures, protected objects and task types to be declared within any function, procedure or task type. This means all items declared in this program are completely local to this program and not “static” as are multiple functions created in the same file in C.

    Ada variables are declared by stating the name of the variable, followed by a colon, followed by the type of the variable. This is opposite the order of declaring variables in C where the type is listed first followed by the variable name.

    5             Num_Tasks : Positive;

    The variable name is Num_Tasks. The type is Positive, which is a pre-defined subtype of the Ada Integer type. Integer is equivalent to the C int type. Positive is an Integer with a minimum possible value of 1 and a maximum possible value of Integer'Last, which is the same as MAX_INT in C. This variable is used to contain the number of tasks the user specifies during the execution of the program.

    The protected object named counter is declared in two parts. Protected objects are always declared in two parts. The protected specification defines the public view of the protected methods along with a private view of the data elements in the protected object. The protected body contains the definition of the protected methods, and is not visible to any task calling the protected object.

    The protected specification for the counter protected object is:

    8             protected counter is

    9                   procedure update (The_Count : out Natural);

    10                   function get_value return Natural;

    11             private

    12                   count : Natural := 0;

    13             end counter;

    There are two methods for this protected object. The procedure named update has one parameter which passes a value of type Natural out to the calling task. Natural is a predefined subtype of Integer with a minimum value of 0. The function named get_value returns a value of the subtype Natural.

    In the private section of the protected specification one data element is declared. It is a variable named count. The type of the variable is Natural and it is initialized to 0.

    The protected body for the counter protected object is:

    15             protected body counter is

    16                   procedure update (The_Count : out Natural) is

    17                   begin

    18                         count     := count + 1;

    19                         The_Count := count;

    20                   end update;

    21                   function get_value return Natural is

    22                   begin

    23                         return count;

    24                   end get_value;

    25             end counter;

    The update procedure increments count and passes its current value out through the parameter named The_Count.

    The function get_value simply returns the value of count. The Ada compiler will issue an error message if the programmer attempts to modify the count data member of the protected object within the execution of the get_value function. Functions are read-only methods in a protected object.

    The procedure update implicitly implements a read-write lock and the function get_value implicitly implements a read lock.

    The task type named thread is defined in two parts. The first part, the task specification, defines that name of the task type and the direct interfaces to the task type. In this case one task entry is defined. That entry is used to set the task ID.

    28             task type thread is

    29                   entry set_id (Id : in Positive);

    30             end thread;

    The task entry implements a direct synchronous communication channel to each instance of the thread type. The entry synchronization scheme is called a Rendezvous. The word "rendezvous" is a French word meaning "a meeting at an agreed time and place". A task calling another task's entry is will suspend until the task declaring the entry accepts the entry call. Similarly, a task accepting an entry will suspend until another task calls that entry. Once both the task calling the entry and the task accepting the entry are in this state for an overlapping time period any data specified in the entry specification is passed between the calling task and the called task. Once the entry has completed both tasks continue to execute concurrently.

    The behavior of the task type is defined in the task body.

    32             task body thread is

    33                   Me       : Positive;

    34                   My_Count : Natural;

    35             begin

    36                   -- accept the set_id entry call from the main task

    37                   accept set_id (Id : in Positive) do

    38                         Me := Id;

    39                   end set_id;

    40

    41                   counter.update (My_Count);

    42                   Put_Line ("Task" & Me'Image & ": counter =" & My_Count'Image);

    43             end thread;

    The task body declared two local variables. Each instance of the task type has unique instances of these two variables.

    The first action taken by the thread task is to accept the set_id entry, passing the Id value from a calling task to this task. The accept statement assigns the value of Id to the task's local variable named Me. Task entries implement a Rendezvous behavior. The thread task will wait at the accept statement until its entry is called by another task. Once the value is passed to the thread task both tasks will the resume concurrent behavior.

    The thread task calls the protected object's update procedure, using its local variable My_Count to receive the current value of the counter protected object. Finally, the thread task simply outputs the value it received from the counter.update procedure.

    The next “begin” begins the execution of the Main task.

    45       begin

    46             Put ("Enter the number of tasks to create: ");

    47             Get (Num_Tasks);

    48

    49             -- declare an inner block in which all the tasks will execute

    50             -- the inner block will only complete after all tasks have completed

    51             declare

    52                   -- create an array of thread objects

    53                   pool : array (1 .. Num_Tasks) of thread;

    54             begin

    55                   for I in pool'Range loop

    56                         -- set the id number of each thread object

    57                         pool (I).set_id (I);

    58                   end loop;

    59             end;

    60

    61      -- output the total after all threads have completed

    62      Put_Line

    63        ("The final value of the shared counter:" &

    64         Natural'Image (counter.get_value));

    65

    66   end Main;

    The Main task prompts the user for the number of tasks to create and reads the number entered by the user.

    An inner block is created starting at the “declare” reserved word. That inner block has a declarative section in which an array of thread tasks, equal in number to the value entered by the user, is created. The tasks in the array named pool begin executing immediately. Their first responsibility is to accept the set_id entry, so each task waits until its set_id entry is called.

    The “for” loop iterates through each element in the pool array assigning the element's index value as the ID for the task.

    Immediately after accepting its ID number each task executes counter.update and then outputs the value of the counter passed back through the counter.update procedure.

    The inner block will only complete when all the tasks created within the block complete. This produces the same effect as the “join” command in the C example.

    After the inner block completes the Main task calls the counter.get_value function and displays the final value of the shared counter. Completion of the inner block automatically frees the array of task objects which were created on the stack at the start of the inner block. No explicit loop to free the task elements is needed or even possible.

    Conclusion

    While the two programs above achieve the same behavior dealing with allowing multiple threads or tasks to update a shared counter, the C solution contains more opportunities for programmer confusion and error. It also requires a lot more coding by the programmer than the Ada solution.

    • 22 July 2023 at 01:49

    HAC version 0.26

    8 July 2023 at 06:40
    Note for subscribers: if you are interested in my Ada programming articles only, you can use this RSS feed link.

    Main URL: https://hacadacompiler.sourceforge.io/
    Sources, site #1: HAC Ada Compiler download | SourceForge.net
    Sources, site #2: GitHub - zertovitch/hac: HAC Ada Compiler - a small, quick Ada compiler fully in Ada
    Alire Crate: Alire - Hac

    What’s new:

    • You can use a loop’s name for the exit statement: exit Loop_Name.
    • You can exit multiple, nested, loops, by using exit Loop_Name.
    • Ada semantics are better verified:
      • Array indexing (i)(j) (array of array) and (i, j) (multi-dimensional array) are no more treated as equivalent (this feature was a remnant of the Pascal syntax).
      • Separation between Type and Subtype declarations (anonymous types are allowed only in the few cases foreseen by the language).
      • Operators of the HAT package (+, -, & for strings) are visible without prefix only in the scope of a use HAT clause.

    Note that correct Ada programs, in relation to the above points, were already accepted and parsed correctly by HAC before that change.

    Finally, a bit of cosmetics in the build messages:

    C:\Ada\hac\exm>..\hac -v2 -c pkg_demo.adb

    *******[ HAC ]*******   HAC is free and open-source. Type "hac" for license.
           [ HAC ]          HAC Ada Compiler version 0.26, 08-Jul-2023
           [ HAC ]          Compiling main: pkg_demo.adb
           [ HAC ]          | Compiling x_pkg_demo_s.ads (specification)
           [ HAC ]           \ Compiling x_pkg_demo_s1.ads (specification)
           [ HAC ]            \ Compiling x_pkg_demo_s11.ads (specification)
           [ HAC ]            /           x_pkg_demo_s11.ads: done.
           [ HAC ]            \ Compiling x_pkg_demo_s12.ads (specification)
           [ HAC ]            /           x_pkg_demo_s12.ads: done.
           [ HAC ]            \ Compiling x_pkg_demo_s13.ads (specification)
           [ HAC ]            /           x_pkg_demo_s13.ads: done.
           [ HAC ]           /           x_pkg_demo_s1.ads: done.
           [ HAC ]           \ Compiling x_pkg_demo_s2.ads (specification)
           [ HAC ]            \ Compiling x_pkg_demo_s21.ads (specification)
           [ HAC ]            /           x_pkg_demo_s21.ads: done.
           [ HAC ]            \ Compiling x_pkg_demo_s22.ads (specification)
           [ HAC ]            /           x_pkg_demo_s22.ads: done.
           [ HAC ]            \ Compiling x_pkg_demo_s23.ads (specification)
           [ HAC ]            /           x_pkg_demo_s23.ads: done.
           [ HAC ]           /           x_pkg_demo_s2.ads: done.
           [ HAC ]           \ Compiling x_pkg_demo_s3.ads (specification)
           [ HAC ]            \ Compiling x_pkg_demo_s31.ads (specification)
           [ HAC ]            /           x_pkg_demo_s31.ads: done.
           [ HAC ]            \ Compiling x_pkg_demo_s32.ads (specification)
           [ HAC ]            /           x_pkg_demo_s32.ads: done.
           [ HAC ]            \ Compiling x_pkg_demo_s33.ads (specification)
           [ HAC ]            /           x_pkg_demo_s33.ads: done.
           [ HAC ]           /           x_pkg_demo_s3.ads: done.
           [ HAC ]          |           x_pkg_demo_s.ads: done.
           [ HAC ]          | Compiling x_pkg_demo_m.ads (specification)
           [ HAC ]          |           x_pkg_demo_m.ads: done.
           [ HAC ]          | Compiling x_pkg_demo_b.ads (specification)
           [ HAC ]          |           x_pkg_demo_b.ads: done.
           [ HAC ]          Compilation of pkg_demo.adb (main) completed
           [ HAC ]          ------  Compilation of eventual with'ed unit's bodies
           [ HAC ]          | Compiling x_pkg_demo_s.adb (body)
           [ HAC ]          |           x_pkg_demo_s.adb: done.
           [ HAC ]          | Compiling x_pkg_demo_s1.adb (body)



    Bit Arrays

     

    A bit array is an array that compactly stores bits. A bit may represent the values 0 or 1, or it can be viewed as representing the values False or True.

    The C language has a somewhat loose interpretation of Boolean values, treating 0 as false and non-zero as true. The Ada programming language defines the Boolean type as the enumeration (False, True). Since that enumeration has only two values the Boolean type has only two position values. False is at position 0 and True is at position 1.

    C array indexing is connected to memory addresses. The typical smallest memory addressable value is a byte, which is typically 8 bits. Indexing a bit array, where each bit within a string of bytes has its own index value requires some bit gymnastics.

    The following C example is taken from https://www.sanfoundry.com/c-program-implement-bit-array/


    Two functions are created.

    The function toggle_bit toggles the bit at the index value specified by the function’s second parameter.

    The function get_bit returns the numeric value of the bit at the index value specified by the function’s second value.

    The output of the program is:

    0: 1

    1: 0

    2: 1

    3: 0

    4: 1

    5: 0

    6: 1

    7: 0

    8: 1

    9: 0

    10: 1

    11: 0

    12: 1

    13: 0

    14: 1

    15: 0

    16: 1

    17: 0

    18: 1

    19: 0

    20: 1

    21: 0

    22: 1

    23: 0

    24: 1

    25: 0

    26: 1

    27: 0

    28: 1

    29: 0

    30: 1

    31: 0

    32: 1

    33: 0

    34: 1

    35: 0

    36: 1

    37: 0

    38: 1

    39: 0

    40: 1

    41: 0

    42: 1

    43: 0

    44: 1

    45: 0

    46: 1

    47: 0

    48: 1

    49: 0

    50: 1

    51: 0

    52: 1

    53: 0

    54: 1

    55: 0

    56: 0

    57: 0

     

    The Ada language allows the declaration of packed arrays of Boolean to represent each Boolean as a single bit.

    Ada allows the programmer to use normal array index notation to access each element of a bit array.

    The following Ada implementation of a bit array produces the same behavior as the C program except that the Ada program also displays the number of elements in the array and the number of bits occupied by an instance of the array.

    An unconstrained array type named Bit_Array is declared. Each element of Bit_Array is a Boolean. Every instance of Bit_Array is packed, which causes the Boolean values to be represented by single bits.

    Similar to the C program, the variable X is declared to be an instance of Bit_Array containing 58 elements. Whereas the C program uses a macro to create the array of bits, the Ada program uses normal Ada array declaration syntax. In C all the elements of the array are initialized to 0. In Ada all elements of the array are initialized to False.

    The “while” loop in the Ada program toggles every other element in the array. The element indexed by the value 56 is then toggled again.

    The “for” loop prints the Boolean position of each element of the array.

    The output of the Ada program is

    X contains 58 elements.

    X occupies 64 bits

     

     0: 1

     1: 0

     2: 1

     3: 0

     4: 1

     5: 0

     6: 1

     7: 0

     8: 1

     9: 0

     10: 1

     11: 0

     12: 1

     13: 0

     14: 1

     15: 0

     16: 1

     17: 0

     18: 1

     19: 0

     20: 1

     21: 0

     22: 1

     23: 0

     24: 1

     25: 0

     26: 1

     27: 0

     28: 1

     29: 0

     30: 1

     31: 0

     32: 1

     33: 0

     34: 1

     35: 0

     36: 1

     37: 0

     38: 1

     39: 0

     40: 1

     41: 0

     42: 1

     43: 0

     44: 1

     45: 0

     46: 1

     47: 0

     48: 1

     49: 0

     50: 1

     51: 0

     52: 1

     53: 0

     54: 1

     55: 0

     56: 0

     57: 0

    Conclusion:

    Both C and Ada can implement bit arrays. The Ada implementation is simpler and easier to understand than is the C implementation.


    • 4 July 2023 at 01:33
    ❌
    ❌