Put_Image aspect and Spark

How to use the Put_Image aspect in Spark code?

I get the following error with gnatprove for the example specification below.

test-put_images.ads:10:06: error: "Root_Buffer_Type'Class" is not allowed in SPARK (due to primitive calls in default initial condition)
   10 |     Put_Image => Image;
      |     ^~~~~~~~~
  violation of aspect SPARK_Mode at line 5
    5 |package Test.Put_Images with Spark_Mode is
      |                             ^ here

test-put_images.ads:13:07: error: "Root_Buffer_Type'Class" is not allowed in SPARK (due to primitive calls in default initial condition)
   13 |     (Output : in out Ada.Strings.Text_Buffers.Root_Buffer_Type'Class;
      |      ^~~~~~
  violation of aspect SPARK_Mode at line 5
    5 |package Test.Put_Images with Spark_Mode is
      |                             ^ here

Spec:

pragma Ada_2022;

with Ada.Strings.Text_Buffers;

package Test.Put_Images with Spark_Mode is

   type T is record
      Value : Integer := 1;
   end record with
     Put_Image => Image;

   procedure Image
     (Output : in out Ada.Strings.Text_Buffers.Root_Buffer_Type'Class;
      Item   :        T);

end Test.Put_Images;
2 Likes

It seems like it should be simple just to combine some ā€˜Image of a record into a string and tell the system where to output the String when ā€˜Image is used for the record or array but not only SPARK but light runtimes or wanting to avoid dispatching or the secondary stack seems to have been clobbered by using a Root_Buffer_Type as the interface?

I wonder how ā€˜Image works for individual components on light runtimes? It’s perfectly possible to ensure the String is the right size for most if not any particular type even with a procedure avoiding the use of the secondary stack.

I guess overloading & might work

It would require recursion or else try and error loop.

The design is to rather use a dynamically allocated storage. On top of that it attempts to support all obsolete character encoding options Ada had accumulated during its long life. But the best of the best is alphanumeric display / matrix printer support! I mean columns. BTW, I indeed used Ada.Text_IO columns once! :grinning:

Neither is good for SPARK. On the other hand 'Image is a debugging feature. You should not have it in the production code anyway.

1 Like

Why not use it to create more useful logs even in production?

Requires the secondary stack as far as I can tell so left wondering how it works without the secondary stack for built in scalar types. I guess the compiler does some static pre-allocation magic.

For logging you would use Put or maybe a custom Image function.

To me image the attributes are overdesigned. I would like attributes to be plain operators just like ā€œabsā€ is:

function "'Image" (X : Integer) return String;

1 Like

I’m not up to date on recent prebuilt runtime stuff, but are you sure the runtimes that support image for scalars don’t already have a secondary stack? It can be implemented in ā€œzero footprintā€ fashion in the current GNAT (it can only add footprint when you use it).

I can’t say I’m sure that the available secondary stack isn’t used for the built ins (probably it is) but I have pragma No_Secondary_Stack which prevents me overriding ā€œ&ā€ and ā€˜Image works whilst saying you can’t use it for composite types due to a configurable runtime.

In Gnat Studio under help => Gnat Runtime => light-cortex-m0p => System there are the Image procedures such as Image_U which has in out String so they aren’t using the secondary stack. I’ve moved on and I’m not sure that helps me much but clears that up.

Interestingly enough I asked now having a reasonable idea of the answer if ā€˜Image uses the secondary stack.

_______________________________________

Notebooklm grounded in the ARM stated that

_______________________________________

ā€œBased on the sources, the term ā€œsecondary stackā€ is not explicitly used in relation to the Image attribute. However, the mechanism by which Image functions operate, and certain implementation requirements, provide insight into their memory usage.

Specifically:

• The S'Image attribute, along with S'Wide_Image and S'Wide_Wide_Image, functions by calling S'Put_Image.

• The S'Put_Image procedure typically stores a sequence of character values in a text buffer.

• After S'Put_Image populates this buffer, the S'Image function then retrieves the contents from that buffer using a Get function (e.g., Get for S'Image, Wide_Get for S'Wide_Image, Wide_Wide_Get for S'Wide_Wide_Image).

Regarding memory allocation for this text buffer, the sources state:

• If a Max_Image_Length restriction applies to any compilation unit in a partition, then S'Image, S'Wide_Image, and S'Wide_Wide_Image shall be implemented without any dynamic allocation within that partition.

While the sources do not use the specific term ā€œsecondary stack,ā€ the requirement to implement image attributes without dynamic allocation when the Max_Image_Length restriction is in effect implies that the buffer used by S'Put_Image would need to be allocated on the primary call stack of the executing task or in a statically allocated region of memory, rather than on the heap (which is typically what ā€œdynamic allocationā€ refers to in this context).

General mentions of stack usage in the sources relate to:

• The Storage_Size attribute for a task object, which denotes the number of storage elements reserved for the task, including the size of its stack.

• The possibility that objects created by allocators, when passed as actual parameters to an access parameter, may be allocated on the stack and automatically reclaimed.

• Requirements for implementations to document the minimum stack space necessary for interrupt handlers.

• Documentation requirements for reviewable programs to identify the length of fixed-size and non-fixed-size portions of memory reserved on a run-time stack for constructs like subprograms and tasks.

In summary, the sources indicate that S'Image uses an internal text buffer, and under the Max_Image_Length restriction, this buffer must avoid dynamic allocation. While the specific term ā€œsecondary stackā€ is not used, this restriction suggests that the buffer would reside on a stack or in static memory if dynamic allocation is disallowed.ā€œ

____________________________________________

CoPilot/ChatGPT came up with this including the incorrect statements like the light runtime does not have secondary stack support. Unbounded strings aren’t available for the user to use in the light runtime.

____________________________________________

In Ada, 'Image is an attribute that produces a string representation of a value — for example, Integer'Image(42) yields " 42" (note the leading space for positive numbers).

Whether 'Image uses a secondary stack depends on your Ada implementation and run‑time model, but in most modern GNAT‑based compilers:

  • 'Image is implemented as a function that returns a String.

  • The compiler typically allocates that string in the secondary stack (also called the secondary storage pool) rather than the main call stack.

  • This is because the size of the string is determined at run time, and the secondary stack lets Ada handle variable‑length temporaries without fixed frame allocations.

  • The exact mechanism can vary — with optimizations, some small 'Image results might be placed in registers or on the primary stack.

Why it matters:
If you’re working in an environment where the secondary stack is small or fragmented, heavy use of 'Image (especially in loops or on large arrays) can cause stack‑space pressure. In those cases, you might want to:

  • Reuse a preallocated buffer and use 'Wide_Wide_Image-like logic manually.

  • Convert values to strings via Ada.Text_IO into an existing string or buffer.

  • Control secondary stack use with compiler switches (-gnatS, -gnatD, etc. in GNAT to inspect allocations).

If you let me know which Ada compiler or environment you’re using, I can give you more specific behaviour details for 'Image in that context.

ā€œAh, that changes things quite a bit. In the light runtime profile, Ada’s run‑time library is slimmed down — which affects how 'Image and other string‑producing attributes are handled.

Here’s the key difference:

  • No secondary stack support:
    The light runtime omits the usual secondary stack mechanism that the full GNAT run‑time uses for unconstrained temporaries. That means 'Image can’t quietly allocate a variable‑length string there.

  • Static or heap allocation instead:
    In this profile, 'Image typically allocates the return string on the heap via Ada.Strings.Unbounded‑style routines, or uses static buffers inside generated code if the maximum size is known at compile‑time (e.g., for fixed‑range discrete types).

  • Implication for your code:

    • You won’t hit secondary stack exhaustion, but you can incur dynamic heap allocations every time you call 'Image.

    • On small or embedded targets, that can create heap‑fragmentation risks or latency spikes.

    • If deterministic memory usage is important, you’ll want to avoid repeated 'Image calls in tight loops, and instead reuse preallocated buffers.

Possible workarounds in light runtime:

  • Use Ada.Text_IO into a pre‑allocated buffer.

  • Implement a custom integer‑to‑string or enumeration‑to‑string conversion that writes directly into an existing array.

  • Profile memory usage — heap use is your main pressure point here.

If you tell me which target/board you’re compiling for, I can suggest patterns that completely avoid heap allocation in the light profile.ā€œ

I have not read the text, just the preamble. The Secondary Stack is a GNAT specific thing. I believe other Ada compilers do not have such a thing. Therefore, you wont find information there. It may be best to process the GNAT RM and UG (Reference Manual and User Guide). Also the SPARK ones.

Best regards,
Fer

1 Like