❌ About FreshRSS

Normal view

There are new articles available, click to refresh the page.
Before yesterdayNews from the Ada programming language world

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

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

Unable to link C source code with Ada static library (Error: libnewapi.a(unit1.o):unit1.adb:undefined reference to `__gnat_rcheck_CE_Overflow_Check')

I want to integrate my Ada static library (libnewapi.a) with my C source code (main.c). I do not have any issues generating the static library but when trying to link it with main.c. I get the below link error, libnewapi.a(unit1.o):unit1.adb:(.text+0x31): undefined reference to `__gnat_rcheck_CE_Overflow_Check' I am not using this reference in my main.c not in any of my Ada files. I do not know why this reference was added automatically. It is crucial that I need to link my main.c using a static Ada library for my project. I do not know where I am going wrong. Help is much appreciated. Thanks!

I am generating libnewapi.a using a GPR as below,

    -- ada_gen_a.gpr
    project ada_gen_a is
       for Languages use ("Ada");
       for Source_Dirs use ("./");
       for Library_Name use "newapi";
       for Library_Dir use "./Lib/";
       for Library_Kind use "static";

       package Naming is
          for Spec_Suffix ("ada") use ".ads";
          for Body_Suffix ("ada") use ".adb";
          for Separate_Suffix use ".adb";
          for Dot_Replacement use ".";
          for Casing use "mixedcase";
       end Naming;

       Ada_Switches := ("-gnato", "-O2");

       package Compiler is
          for Default_Switches ("ada") use Ada_Switches;
       end Compiler;

       package Binder is
          for Default_Switches ("Ada") use ("-n","-Lada");
       end Binder;
    end ada_gen_a;

Ada Sourcefiles:

    --  unit1.ads
    package Unit1 is
       function Add (A, B : Integer) return Integer;
       pragma Export (C, Add, "ada_add");
    end Unit1;
    -- unit1.adb
    package body Unit1 is
       function Add (A, B : Integer) return Integer is
       begin
          return A + B;
       end Add;
    end Unit1;

C Source File:

    /* main.c */
    #include <stdio.h>
    extern void ada_add (void);
    int main (int argc, char *argv[])
    {
       int a = 21, b = 7, c = 0;
       printf ("%d", a);
       printf ("%d", b);
       c = ada_add(a,b);
       printf ("%d", c);
       return 0;
    }

I am using the below GPR to link the above main.c with the Ada static library generated using ada_gen_a.gpr.

    -- Ada_Use_A.gpr
    with "newapi.gpr";
    project Ada_Use_A is

       for Languages use ("C");
       for Source_Dirs use (".");
       for Source_Files use ("main.c");

       package Naming is
          for Casing use "mixedcase";
       end Naming;

       Ada_Switches := ("-gnato", "-O2");

       package Compiler is
          for Default_Switches ("C") use ("-O2", "-Wall");
          for Default_Switches ("Ada") use Ada_Switches;
       end Compiler;

       package Binder is
          for Default_Switches ("Ada") use ("-n","-Lada");
       end Binder;

       for Main use ("main.c");

    end Ada_Use_A;
    -- newapi.gpr
    project newapi is
       for Externally_Built use "true";
       for Source_Files use ();
       for Library_Dir use ".\lib\";
       for Library_Name use "newapi";
       for Library_Kind use "static";
    end newapi;

When I try to build the Ada_Use_A.gpr GPS I get the below linker error, libnewapi.a(unit1.o):unit1.adb:(.text+0x31): undefined reference to __gnat_rcheck_CE_Overflow_Check' gprbuild: link of main.c failed`

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)



Stack corruption in Ada C binding to OpenGL function

I'm using SDL to retrieve the address of the function. That much seems to be working, since I'm using the same subprogram that's working for all my other function calls.

Here are all the relevant code snippits in the spec:

type GLenum is new Uint32;

subtype glTexImage2D_Target is GLenum with Static_Predicate => glTexImage2D_Target in GL_TEXTURE_2D | GL_TEXTURE_RECTANGLE | GL_PROXY_TEXTURE_RECTANGLE;
subtype glTexImage2D_Dtype is GLenum with Static_Predicate => glTexImage2D_Dtype in GL_BYTE | GL_UNSIGNED_BYTE;
subtype glTexImage2D_InternalFormat is GLenum with Static_Predicate => glTexImage2D_InternalFormat in GL_DEPTH_COMPONENT | GL_DEPTH_STENCIL | GL_RED | GL_RG | GL_RGB | GL_RGBA;
subtype glTexImage2D_Format is GLenum with Static_Predicate => glTexImage2D_Format in GL_RED | GL_RG | GL_RGB | GL_BGR | GL_RGBA | GL_BGRA | GL_RED_INTEGER | GL_RG_INTEGER | GL_RGB_INTEGER | GL_BGR_INTEGER | GL_RGBA_INTEGER | GL_BGRA_INTEGER | GL_STENCIL_INDEX | GL_DEPTH_COMPONENT | GL_DEPTH_STENCIL;

procedure glTexImage2D(target : glTexImage2D_Target; level : Integer; internalFormat : glTexImage2D_InternalFormat; width : Integer; height : Integer; format : glTexImage2D_Format; dtype : glTexImage2D_Dtype; pixels : System.Address) with Pre => (target /= GL_TEXTURE_RECTANGLE and target /= GL_PROXY_TEXTURE_RECTANGLE) or Level = 0;

procedure Set_OpenGL_Subprogram_Address(Addr : in out System.Address; Name : String);

glTexImage2D_C_Address : System.Address := System.Null_Address;

Could_Not_Load_OpenGL_Subprogram : exception;

And here are the relevant snippits in the body:

procedure glTexImage2D(target : glTexImage2D_Target; level : Integer; internalFormat : glTexImage2D_InternalFormat; width : Integer; height : Integer; format : glTexImage2D_Format; dtype : glTexImage2D_Dtype; pixels : System.Address) is
begin
    Set_OpenGL_Subprogram_Address(glTexImage2D_C_Address, "glTexImage2D");
    declare
        procedure glTexImage2D_C(target : GLenum; level : GLint; internalFormat : GLint; width : GLsizei; height : GLsizei; border : GLint; format : GLenum; dtype : GLenum; data : System.Address)
        with Import, Convention => Stdcall, Address => glTexImage2D_C_Address;
    begin
        glTexImage2D_C(GLenum(target), GLint(level), GLint(internalFormat), GLsizei(width), GLsizei(height), GLint'(0), GLenum(format), GLenum(dtype), pixels);
    end;
end glTexImage2D;

--This seems to work... but here it is just in case.
procedure Set_OpenGL_Subprogram_Address(Addr : in out System.Address; Name : String) is
begin
    if Addr = System.Null_Address then
        Addr := SDL_GL_GetProcAddress(Value(New_String(Name)));
        if Addr = System.Null_Address then
            raise Could_Not_Load_OpenGL_Subprogram with Name;
        end if;
    end if;
end Set_OpenGL_Subprogram_Address;

Finally, the pixels I'm passing in is an address indicating an object of type:

type Uc_Array is array(Integer range <>) of aliased Interfaces.C.unsigned_char;

The stack is being corrupted on the call to glTexImage2D_C. I discovered this by using gdb:

enter image description here

Here you can see the parameters I'm passing as well; target 3553, level 0, internalformat 6408, width 63, height 63, format 6408, dtype 5121, non-zero pixels address. The subprogram address is being set to a non-zero value. But after the call to glTexImage2D_C, the value of $sp (the stack pointer) has increased by 16#24#.

I've tried adjusting the height and width I pass to glTexImage2D_C, both to 10 and both to 1000, just to see if giving it more (or less) buffer prevents stack corruption, but in every case, the stack pointer is different before and after the procedure call.

I'm hoping this is a simple matter of an incorrect datatype or something... Here's the prototype of glTexImage2D on the OpenGL side:

void glTexImage2D(  GLenum target,
    GLint level,
    GLint internalformat,
    GLsizei width,
    GLsizei height,
    GLint border,
    GLenum format,
    GLenum type,
    const void * data);

(As a final note, I do intend to clean up the awkward lazy loading tactic you see above, if I can ever get this to actually work. For now I'm leaving it this way so I can eliminate the chance of trying to call a function that hasn't been loaded yet, while I'm still trying to debug the call itself. The tactic of using a nested procedure declared inside a declare...begin...end block is working for other OpenGL functions.)

How to pass complex data types, like records between Ada and C via a DLL?

I am trying to get Ada code to compile to a DLL to be callable in C. So far I have managed to get this to work for simple types such as integers but i'm having difficulty with more complex types such as array's or records/structs.

Below is my Ada code for an record/struct. It is a dynamic library which i compile to a dll using the command "gprbuild -P ./math.gpr -p":

person.adb
with Interfaces.C; use Interfaces.C;

package body Person is

   function Set_Age_To_Five(P : Person_Record) return Person_Record is
      Result : Person_Record;
   begin
      Result := P;
      Result.Age := 5;
      return Result;
   end Set_Age_To_Five;

end Person;

person.ads

with Interfaces.C; use Interfaces.C;

package Person is

   type Person_Record is record
      Name    : String(1 .. 100);
      Age     : Interfaces.C.int;
      Address : String(1 .. 100);
   end record with Convention => C_Pass_By_Copy;

   function Set_Age_To_Five(P : Person_Record) return Person_Record;
   pragma Export (C, Set_Age_To_Five, "Set_Age_To_Five");

end Person;

math.gpr

library project Math is
    for Languages use ("Ada");
    for Library_Name use "Person";
    for Source_Dirs use ("src");
    for Object_Dir use "obj";
    for Library_Dir use "lib";
    for Library_Kind use "Dynamic";
end Math;

I then have a C header file math.h:

#ifndef MATH_H
#define MATH_H

#ifdef __cplusplus
extern "C"
#endif

typedef struct {
    char Name[101];
    int Age;
    char Address[101];
} Person_Record;

Person_Record Set_Age_To_Five(Person_Record P);

#ifdef __cplusplus

#endif

#endif /* MATH_H */

and finally my C code:

#include <stdio.h>
#include "math.h"

int main() {
    Person_Record p, q, r;

    // Initialize the person record
    snprintf(p.Name, sizeof(p.Name), "John");
    p.Age = 25;
    snprintf(p.Address, sizeof(p.Address), "123 Main St");

    // Call the Set_Age_To_Five function from the DLL
    q = Set_Age_To_Five(p);

    // Print the modified person record
    printf("Name: %s\n", q.Name);
    printf("Age: %d\n", q.Age);
    printf("Address: %s\n", q.Address);

    return 0;
}

This should when executed return

Name: John
Age: 5
Address: 123 Main St

Instead its returning:

Name: John
Age: 25
Address: 123 Main St

I've tried passing by variable, passing by reference. using convention C and convention pass by c in ada.

Reentrant scanner and parser with Aflex and Ayacc

14 May 2023 at 19:02
[Aflex and Ayacc](Ada/Aflex-Ayacc-code.jpg)
    1. What's new in Aflex 1.6

- Support the flex options `%option output`, `%option nooutput`, `%option yywrap`, `%option noinput`,

 `%option noyywrap`, `%option unput`, `%option nounput`, `%option bufsize=NNN` to better control the
 generated `_IO` package.
- Aflex(https://github.com/Ada-France/aflex) templates provide more control for tuning the code generation and
 they are embedded with [Advanced Resource Embedder](https://gitlab.com/stcarrez/resource-embedder)
- Support to define Ada code block in the scanner that is inserted in the generated scanner - New option -P to generate a private Ada package for DFA and IO - New directive `%option reentrant` and `%yyvar` to generate a recursive scanner - New directive `%yydecl` to allow passing parameters to `YYLex`
 or change the default function name

Example of `%option` directives to tell Aflex(https://github.com/Ada-France/aflex) to avoid generating several function or procedures and customize the buffer size.

```Ada %option nounput %option noinput %option nooutput %option noyywrap %option bufsize=1024 ```

The tool supports some code block injection at various places in the generated scanner. The code block has the following syntax where `<block-name>` is the name of the code block:

```Ada %<block-name> {

 -- Put Ada code here

} ```

The `%yytype` code block can contain type declaration, function and procedure declarations. It is injected within the `YYLex` function in the declaration part. The `%yyinit` code block can contain statements that are executed at beginning of the `YYLex` function. The `%yyaction` code block can contain statements that are executed before running any action. The `%yywrap` code block can contain statements which are executed when the end of current file is reached to start parsing a next input.

    1. What's new in Ayacc 1.4

- Support the Bison `%define variable value` option to configure the parser generator - Support the Bison `%code name { ... }` directive to insert code verbatim into the output parser - Recognize some Bison variables `api.pure`, `api.private`, `parse.error`, `parse.stacksize`,

 `parse.name`, `parse.params`, `parse.yyclearin`, `parse.yyerrok`, `parse.error`
- New option `-S skeleton` to allow using an external skeleton file for the parser generator - Ayacc(https://github.com/Ada-France/ayacc) templates provide more control for tuning the code generation and
 they are embedded with [Advanced Resource Embedder](https://gitlab.com/stcarrez/resource-embedder)
- New option `-P` to generate a private Ada package for the tokens package - Improvement to allow passing parameters to `YYParse` for the grammar rules - New `%lex` directive to control the call of `YYLex` function - Fix #6: ayacc gets stuck creating an infinitely large file after encountering a comment in an action

The generator supports two code block injections, the first one `decl` is injected in the `YYParse` procedure declaration and the `init` is injected as first statements to be executed only once when the procedure is called. The syntax is borrowed from the Bison parser:

```Ada %code decl {

  -- Put Ada declarations

} %code init {

  -- Put Ada statements

} ```

Some other Bison like improvements have been introduced to control the generation of the parser code.

``` %define parse.error true %define parse.stacksize 256 %define parse.yyclearin false %define parse.yyerrok false %define parse.name MyParser ```

    1. How to use

The easiest way to use Ayacc(https://github.com/Ada-France/ayacc) and Aflex(https://github.com/Ada-France/aflex) is to use Alire(https://github.com/alire-project/alire), get the sources, build them and install them. You can do this as follows:

``` alr get aflex cd aflex_1.6.0_b3c21d99 alr build alr install alr get ayacc cd ayacc_1.4.0_c06f997f alr build alr install ```

  • UPDATE*: the `alr install` command is available only with Alire(https://github.com/alire-project/alire) 2.0.

Using these tools is done in two steps:

1. a first step to call `aflex` or `ayacc` command with the scanner file or grammar file, 2. a second step to call `gnatchop` to split the generated file in separate Ada files

For example, with a `calc_lex.l` scanner file, you would use:

``` aflex calc_lex.l gnatchop -w calc_lex.ada ```

And with a `calc.y` grammar file:

``` ayacc calc.y gnatchop -w calc.ada ```

To know more about how to write a scanner file or grammar file, have a look at Aflex 1.5 and Ayacc 1.3.0(https://blog.vacs.fr/vacs/blogs/post.html?post=2021/12/18/Aflex-1.5-and-Ayacc-1.3.0) which explains more into details some of these aspects.

    1. Highlight on reentrancy

By default Aflex(https://github.com/Ada-France/aflex) and Ayacc(https://github.com/Ada-France/ayacc) generate a scanner and a parser which use global variables declared in a generated Ada package. These global variables contain some state about the scanner such as the current file being scanned. The Ayacc(https://github.com/Ada-France/ayacc) parser generates on its side two global variables `YYLVal` and `YYVal`.

Using global variables creates some strong restrictions when using the generated scanner and parser: we can scan and parse only one file at a time. It cannot be used in a multi-thread environment unless the scan and parse is protected from concurrent access. We cannot use easily some grammars that need to recurse and parse another file such as an included file.

      1. Reentrant scanner

The reentrant scanner is created by using the `-R` option or the `%option reentrant` directive. The scanner will then need a specific declaration with a context parameter that will hold the scanner state and variables. The context parameter has its type generated in the `Lexer_IO` package. The `%yydecl` directive in the scanner file must be used to declare the `YYLex` function with its parameters. By default the name of the context variable is `Context` but you can decide to customize and change it to another name by using the `%yyvar` directive.

``` %option reentrant %yyvar Context %yydecl function YYLex (Context : in out Lexer_IO.Context_Type) return Token ```

When the `reentrant` option is activated, Aflex(https://github.com/Ada-France/aflex) will generate a first `Context_Type` limited type in the `Lexer_DFA` package and another one in the `Lexer_IO` package. The generator can probably be improved in the future to provide a single package with a single type declaration. The `Lexer_DFA` package contains the internal data structures for the scanner to maintain its state and the `Lexer_IO` package holds the input file as well as the `YYLVal` and `YYVal` values.

      1. Reentrant parser

On its side, Ayacc(https://github.com/Ada-France/ayacc) uses the `YYLVal` and `YYVal` variables. By default, it generates them in the `_tokens` package that contains the list of parser symbols. It must not generate them and it must now use the scanner `Context_Type` to hold them as well as the scanner internal state. The setup requires several steps:

1. The reentrant parser is activated by using the `%define api.pure`

  directive similar to the [bison %define](https://www.gnu.org/software/bison/manual/html_node/_0025define-Summary.html).

2. The `%lex` directive must be used to define how the `YYLex` function must be called since it now has some

  context parameter.

3. The scanner context variable must be declared somewhere, either as parameter to the `YYParse`

  procedure or as a local variable to `YYParse`.  This is done using the new `%code decl` directive
  and allows to customize the local declaration part of the `YYParse` generated procedure.

4. We must give visibility of the `YYLVal` and `YYVal` values defined in the scanner context variable.

  Again, we can do this within the `%code decl` directive.

A simple reentrant parser could be defined by using:

```Ada %define api.pure true %lex YYLex (Scanner) %code decl {

     Scanner : Lexer_IO.Context_Type;
     YYLVal  : YYSType renames Scanner.YYLVal;
     YYVal   : YYSType renames Scanner.YYVal;

} ```

However, this simple form is not really useful as you may need to open the file and setup the scanner to read from it. It is probably better to pass the scanner context as parameter to the `YYParse` procedure. For this, we can use the `%define parse.params` directive to control the procedure parameters. The reentrant parser is declared as follows:

```Ada %lex YYLex (Scanner) %define api.pure true %define parse.params "Scanner : in out Lexer_IO.Context_Type" %code decl {

     YYLVal : YYSType renames Scanner.YYLVal;
     YYVal  : YYSType renames Scanner.YYVal;

} ```

To use the reentrant parser and scanner, we only need to declare the scanner context, open the file by using the `Lexer_IO.Open_Input` procedure and call the `YYParse` procedure as follows:

```Ada

 Scanner : Lexer_IO.Context_Type;
 ...
   Lexer_IO.Open_Input (Scanner, "file-to-scan");
   YYParse (Scanner);

```

      1. Grammar examples:

To have a more complete example of a reentrant parser, you may have a look at the following files:

Libadalang, Alire, and macOS

Background

This exercise was prompted by the need for Scripted Testing to be supported by – as far as possible – code generation. The need is for the public or interfacing view of a supporting part (domain) of a system to be able to log calls made to it and to provide values on calls to it, using a scripting mechanism.

Read more »

Advent of Code 2022 in pictures

14 January 2023 at 09:25

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


Just as a teaser for the next Advent of Code in 10 1/2 months, we would like to show a few pictures related to last edition. The 25 puzzles, data and solutions can be found here and here. They are programmed with HAC (the HAC Ada Compiler), thus in a small subset of the Ada language. The HAC compiler is very fast, so you run your program without noticing it was ever compiled, which is perfect for completing a programming puzzle.

Day 22 run with HAC (here, embedded in the LEA environment)

However, the program will run substantially slower than compiled with a native, optimizing compiler like GNAT.
This is not an issue for most Advent of Code puzzles, but for some, it is, especially on the later days. Fortunately, changing from HAC to GNAT is trivial (just switch compilers), unlike the traditional reprogramming of Python prototypes in C++, for instance.

The pictures

Day 8's data is a map representing the height of trees. Once the puzzle was solved, I was curious how the forest looked like. Note that different users get different data, so you are unlikely to find a visualisation of exactly your data on the internet.


Day 12's puzzle is a shortest path problem with specific rules and a nice data - a terrain with a hill - which seems designed to trap depth-first-search algorithms into an almost infinite search. The yellowish path is the shortest from the green dot, elevation 'a', to blue dot, elevation 'z'.
The pinkish path is the shortest from the blue dot to any dot with elevation 'a'. Fortunately Dijkstra's algorithm (and perhaps others) allows for such a special criterion regarding the end point.

Click to enlarge


For day 22’s puzzle, a walk on a cube’s surface is involved, so it is helpful to sketch it on a piece of paper, cut it and glue the faces. A banana skin of that puzzle is that the example’s layout may be different from the data’s layout. We slipped on that one and ended up gluing and programming the face-to-face transitions for two layouts…


Click to enlarge

Other Adaists’ solutions, discussions and pictures can be found here and in the "2022 Day x" threads in the same forum.

How would I define the __builtin_blendvps256 GCC intrinsic in Ada using GNAT?

I am trying to define a library in Ada (built on GNAT specifically) for x86 ISA extensions. (This question is specific to AVX/AVX2).

Here is some example code below:

-- 256-bit Vector of Single Precision Floating Point Numbers
type Vector_256_Float_32 is array (0 .. 7) of IEEE_Float_32 with
  Alignment => 32, Size => 256, Object_Size => 256;
pragma Machine_Attribute (Vector_256_Float_32, "vector_type");
pragma Machine_Attribute (Vector_256_Float_32, "may_alias");

-- 256-bit Vector of 32-bit Signed Integers
type Vector_256_Integer_32 is array (0 .. 7) of Integer_32 with
  Alignment => 32, Size => 256, Object_Size => 256;
pragma Machine_Attribute (Vector_256_Integer_32, "vector_type");
pragma Machine_Attribute (Vector_256_Integer_32, "may_alias");

-- 256-bit Vector of 32-bit Unsigned Integers
type Vector_256_Unsigned_32 is array (0 .. 7) of Unsigned_32 with
  Alignment => 32, Size => 256, Object_Size => 256;
pragma Machine_Attribute (Vector_256_Unsigned_32, "vector_type");
pragma Machine_Attribute (Vector_256_Unsigned_32, "may_alias");

function vblendvps
  (Left, Right, Mask : Vector_256_Float_32)
   return Vector_256_Float_32 with
  Inline_Always => True, Convention => Intrinsic, Import => True,
  External_Name => "__builtin_ia32_blendvps256";

For the sake of education, I want to know how to do this in assembly.

I have tried to define the vblendvps function using the Asm function from System.Machine_Code. However, as I am not knowledgeable about assembly programming, I am struggling to find resources on how to do this.

This is what I have so far:

with System.Machine_Code; use System.Machine_Code;

function vblendvps
(Left, Right, Mask : Vector_256_Float_32)
return Vector_256_Float_32
is
result : Vector_256_Float_32;
begin
Asm
(Template => "vblendvps %3, %0, %1, %2",
 Outputs  => Vector_256_Float_32'Asm_Output ("=g", result),
 Inputs   =>
   (Vector_256_Float_32'Asm_Input ("g", Left),
    Vector_256_Float_32'Asm_Input ("g", Right),
    Vector_256_Unsigned_32'Asm_Input ("g", Mask)));
return result;
end vblendvps;

When compiling the complete code, I get

Error: too many memory references for `vblendvps'

I believe this means that I need to move the arguments from memory to registers, but I am not sure. If there are some helpful references that explain every instruction, I would greatly appreciate that. (I had quite some trouble looking up the arguments to vblendvps).

My understanding is that the instruction is of the form (from ymm registers in my case)

vblendvps RESULT, LEFT, RIGHT, MASK

Please let me know how I would do this. Even if it is not in Ada, I'm sure I can figure out how to translate it.

LEA 0.85 - the Tabs are here!

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

The 0.85 version of LEA (the Lightweight Editor for Ada) is available!

Web site: http://l-e-a.sf.net/
Source repository #1: https://sf.net/p/l-e-a/code/HEAD/tree/
Source repository #2: https://github.com/zertovitch/lea

The new version has two main new features:
  - Tabs (screenshot below)
  - LEA doesn't write scilexer.dll as a file; thus, it runs as a portable application (in the sense: you can run it from a read-only drive directly, without installation)

Click to enlarge

Enjoy!

Building GCC 12.2.0 on Ventura for aarch64

These are notes on building GCC 12.2.0 and GNAT tools for Apple silicon.

There were two main problems:

  • the base package was built on an Intel machine (lockheed - named after Shadowcat’s companion dragon), running Monterey (macOS 12).
  • the build machine, an M1 mac Mini (temeraire - named after Naomi Novik’s dragon) was by this time running Ventura (macOS 13), but I wanted to make sure that users could continue to run on Monterey.
Read more »

LEA 0.84 - the Build & Run button!

12 November 2022 at 11:43

 

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


The 0.84 version of LEA (the Lightweight Editor for Ada) is available!


Web site: http://l-e-a.sf.net/
Source repository #1: https://sf.net/p/l-e-a/code/HEAD/tree/
Source repository #2: https://github.com/zertovitch/lea

This version is essentialy a rework of LEA's ergonomy - we had recently the chance of having a group of young first-time programmers and first-time LEA users to test it!

The main addition is the green Build & Run button, which matches a (less intuitive) menu entry and the F9 key.

Click screenshot to enlarge

Another addition is a dedicated box for the "User_Abort" internal Virtual Machine exception, so that the LEA user doesn't take the exception message for an actual error (which would be the case for any other exception).

Before:


Now:

Enjoy!


A cell phone company is continually building and leasing communication towers. Each tower has one-way, direct communcation links to other tower [closed]

A cell phone company is continually building and leasing communication towers. Each tower has one-way, direct communcation links to other towers. The task is find if there is a communication link from one tower to another, possibly through other towers.

The input to the program is essentially a list of pairs of tower names. Each pair will appear on a single line and will contain distinct names. Some pairs represent one-way communcation links between towers, and other pairs represent queries. Links and queries may be interspersed.

All the communication links of the system are added one link at a time and no links are removed. Here is an example of a link:

Tower_A Tower_B. A link is terminated by a period.

A query asks if a communcation link is possible from one tower to another by any combination of one-way links. If another link is added to the system later in the input, then the answer to the same query may be different. In the output, a plus sign (+) represents an affirmative answer; a minus sign (-) says there is no such link.

Links are distinguished from queries by ending in a question mark and not a period.

Tower_A Tower_B? The program should read from the standard input and and write to the standard output. For each query in the input there should be exactly one line of output. If the query is true (there is a communication channel through the network), the output line should begin with a plus sign (+); otherwise with a minus sign (-). The rest of the output line repeats the towers in the query.

For the following input:

Tower_A Tower_B. Tower_B Tower_C . Tower_A Tower_C? # A query Tower_B Tower_D. Tower_A Tower_D ? # Another query Tower_F Tower_E. Tower_D Tower_B ? # A third query xxxx yyyy? # Unknown tower names Tower_D Tower_B . Tower_D Tower_B ? # Now there is a link the output should be

  • Tower_A -> Tower_C
  • Tower_A -> Tower_D
  • Tower_D -> Tower_B
  • xxxx -> yyyy
  • Tower_D -> Tower_B The names of the towers will contain only the characters [a-zA-Z_0-9], in particular they will have no spaces in them. Names will have at least 1 character and no more than 50 characters. Furthermore the capitialization of the names is significant. That is, WestMelbourneTower is not to be considered the same tower as wESTMeLbOuRnEtOwEr. Characters, if any, after the period (.) or question mark (?) on a line are to be ignored. No line will will contain more than 150 characters.

Please carefully observe spacing. There may, or may not, be a space before the period, the question mark, and the comment in the input. There will be one or more spaces between the tower names in the input. There must be a space after the plus and minus sign in the output. There must be a space before and after the arrow (->) in the output.

You are required to create your own graph package graph.ads graph.adb and name it Graph. The package can be generic or non-generic. The graph is to be a list of adajency lists. You are required to use the generic, doubly-linked list package from the standard library. These requirements are generally reasonable ones for the task anyway. There are more sophisticated approaches and less sophisticated approaches, but these requirements ensure the educational objectives of the assignment are met.

Ada Wireless Driver Progress

25 July 2022 at 07:00

A few weeks ago, Raspberry Pi released the Pico W, an update to the Pico development board with the addition of a wifi chip and trace antenna. As I’ve been using the Pico for many of my projects, I’m excited by the possibility of adding wireless connectivity.

I program in Ada, so I want to add support for the Pico’s wireless controller, CYW43439 to my driver library.

First, a reality check; Raspberry Pi is going to continue producing new microcontrollers and development boards with new features. They’ve put an immense amount of effort into their C/C++ SDK and MicroPython libraries. Do I really want to continue reimplementing their drivers in Ada? Would I be better off just generating a wrapper around their SDK? The Pico SDK is pretty heavily dependent on the CMake build infrastructure right now, so I’d have to figure out how to make that work with Ada. The generated Ada bindings from C code are usually pretty awkward to work with, so I’d have to write a fair bit of code either way. For now I’m going to keep going with my native Ada libraries, but I’m going to keep asking myself if this is a worthwhile effort when good vendor libraries exist.

The wireless chip they’ve chosen, CYW43439, is a beast. Infineon (formerly Cypress, formerly Broadcom) have a public datasheet, which contains information useful to PCB designers and test engineers as well as some rather confusing timing diagrams that contain no actual timing information. The Pico W has an RP2040 connected to the CYW43439 with the "gSPI" interface with both the transmit and receive signals multiplexed onto a single microcontroller pin.

The RP2040’s PIO should be perfect for implementing this sort of odd serial protocol, but I found it to be quite frustrating in this case. The CS, CLK, and DAT signals are not accessible from any test pad or trace that I can find on the Pico W, so I can’t get a logic analyzer connected while the RP2040 is communicating with the CYW43439. I considered buying a standalone module that uses this chip so that I could connect it to some pins I can probe, but I figured I’d try working blind first. I configured the PIO to use some of the Pico’s exposed pins and connected a logic analyzer. At the very least, I can get the CS, CLK and DAT transmit timing to look correct before trying to talk to the wireless controller.

The datasheet says that the device needs up to 50ms after pulling REG_ON high before it’ll respond to gSPI commands. The host should poll the registers beginning at address 0x14 for the value 0xFEEDBEAD. Once that value is read correctly, a test write/readback at address 0x18 verifies that the gSPI bus is working and the CYW43439 is powered up and ready to accept commands.

That 0xFEEDBEAD value led to some interesting search results; this protocol has been used in Broadcom wireless chipsets as far back as the venerable WRT54G router. There are many open source drivers of varying quality for this family of chips and it seems that Broadcom/Cypress/Infineon kept the host interface mostly the same over the years, only adding new commands when necessary. I found a very detailed writeup about reverse engineering the firmware for these chips, which was quite interesting, if not immediately useful.

The Pico SDK has a PIO driver for gSPI, so I spent a lot of time reading that code and trying to understand it. They use a single PIO SM to control the DAT signal, with CLK configured as a side-set. To begin a transfer, the host CPU resets the SM and executes set pindirs, 1 to configure DAT as an output, then sets the x register to the number of bits to write and y to the number of bits to read. These instructions are not included in the main PIO program as they’re not timing critical and executing them from the CPU saves on PIO memory, which is only 32 instructions. The PIO program shifts one bit out on the DAT pin, toggles the CLK sideset, then decrements the x register. Once x reaches zero, DAT is reconfigured as an input and the SM starts shifting in bits, decrementing y as it goes.

This seems fairly reasonable. We can control the number of bits per word by changing the x and y registers before beginning a transaction. The host CPU needs to get involved after each TX/RX cycle to reset the pindirs, x, and y registers and pull CS high if the transaction has completed. The driver code uses DMA to send data to the PIO, which doesn’t seem like a huge benefit when the CPU is going to intervene after every word transferred. The DMA controller can do byte swapping, which may be useful for big endian network data, but I’m not sure if it’s worth the trouble in this case.

I decided that I’d rather use up a bit more PIO memory and have separate programs for read and write that do their own setting of pindirs and the x register. The CPU still needs to control CS though, which is inconvenient. I really wish the PIO had just a few more Delay/Side-Set bits so that we could have longer delays in PIO programs for timing the CS "back porch."

The CYW43439 datasheet also illustrates a "Read-Write" transaction, where CS stays low between the Write and Read commands. The datasheet says there should be a "fixed delay" in between read and write in this mode. The diagram shows four clock edges during this delay time and there is a bus configuration register for delay timing, but I wasn’t able to get this working. As far as I know, this is just an optimization, not a required bus mode.

Speaking of optimizations, the CYW43439 defaults to 16-bit words on the gSPI interface, but the command to switch to 32-bit mode is 2 16-bit words long, so we can send that as the first command and never have to deal with changing word sizes. As far as I can tell, this is what every driver does.

After spending far too long trying to make my program block until the SM finished clocking bits and wondering if the idle polarity of the clock really matters, I got a logic analyzer trace that looked reasonably close to what the datasheet specifies. I changed my pin config to connect the PIO to the wireless controller and got some signs of life! Polling the 0x14 register returned 0xED 0xFE, which was half of my expected 0xFEEDBEAD with swapped bytes. I played with the PIO SHIFTCTRL register a bit and got a successful read of 0xFEEDBEAD. Surprisingly, the test write/readback worked too! I guess that means I have a working gSPI implementation on PIO! I noticed that the Pico SDK runs the PIO at 33 MHz for this interface, but my test program fails at anything faster than 20 MHz. I guess I still need to get a probe on those DAT, CLK, and CS traces to get the timing right. The datasheet says this interface can run at 50 MHz, but we can worry about that later.

Now that I can communicate with the CYW43439, I started looking at cyw43-driver more seriously. The datasheet makes no mention of most of the CYW43439’s internal registers and this is not a trivial driver, so I’m going to try to interoperate with this C driver from Ada, rather than trying to rewrite it. It looks like this driver calls a bunch of symbols like cyw43_hal_pin_config that we need to provide. Luckily, exporting Ada procedures as symbols that conform to the C ABI is easy.

procedure HAL_Pin_Config
  (Pin, Mode, Pull, Alt : Interfaces.C.int)
with Export, Convention => C, External_Name => "cyw43_hal_pin_config";

On the more complex side of things, we need a TCP/IP stack. cyw43 only contains bindings for LwIP and that’s what Pico SDK uses, so I went searching for Ada bindings for LwIP. Surely someone else has tried to do embedded networking in Ada before, right?

Right! I found a directory called ipstack buried in the SPARK unit tests. If the README is to be believed, this is a formally verified rewrite of a large chunk of LwIP in SPARK. Excellent! This code predates the Alire package manager by many years, so I spent some time understanding the existing Makefiles and hierarchy of GPRbuild projects and got the whole mess built as an Alire crate I can use as a dependency. There’s a lot of room for improvement here, but I want to get some packets flowing before I start trying to improve the IP implementation. If this works at all, I’ll be impressed.

The ipstack library only has two hardware drivers: a minimal TUN/TAP implementation for testing on a Linux host and a driver for the ancient NE2000 ISA card. I spent many hours learning about firewalls with OpenBSD on a 486 with a couple of NE2K cards in my formative years, so this brings back fond memories. Back in the day, the NE2000 was not the fastest network card you could buy, but it was cheap, reliable, and rock solid under Linux, which was far from a guarantee for any peripheral back then.

So now I have three bits of code to string together: my Ada/PIO gSPI implementation, the SPARK ipstack, and cyw43-driver.

Stay tuned for my next post, where I might actually get something working.

❌
❌