0

Not very familiar with in-line assembly to begin with, and much less with that of the blackfin processor. I am in the process of migrating a legacy C application over to C++, and ran into a problem this morning regarding the following routine:

//
void clear_buffer ( short * buffer, int len ) {
    __asm__ (
            "/* clear_buffer */\n\t"
            "LSETUP (1f, 1f) LC0=%1;\n"
            "1:\n\t"
            "W [%0++] = %2;"
            :: "a" ( buffer ), "a" ( len ), "d" ( 0 )
            : "memory", "LC0", "LT0", "LB0"
    );
}

I have a class that contains an array of shorts that is used for audio processing:

class AudProc
{
    enum { buffer_size = 512 };

    short M_samples[ buffer_size * 2 ];

    // remaining part of class omitted for brevity
};

Within the AudProc class I have a method that calls clear_buffer, passing it the samples array:

clear_buffer ( M_samples, sizeof ( M_samples ) / 2 );

This generates a "Bus Error" and aborts the application.

I have tried making the array public, and that produces the same result. I have also tried making it static; that allows the call to go through without error, but no longer allows for multiple instances of my class as each needs its own buffer to work with. Now, my first thought is, it has something to do with where the buffer is in memory, or from where it is being accessed. Does something need to be changed in the in-line assembly to make this work, or in the way it is being called?

Thought that this was similar to what I was trying to accomplish, but it is using a different dialect of asm, and I can't figure out if it is the same problem I am experiencing or not:

GCC extended asm, struct element offset encoding

  1. Anyone know why this is occurring and how to correct it?
  2. Does anyone know where there is helpful documentation regarding the blackfin asm instruction set? I've tried looking on the ADSP site, but to no avail.
Community
  • 1
  • 1
Will
  • 3,500
  • 4
  • 30
  • 38

3 Answers3

0

I would suspect that you could define your clear_buffer as

 inline void clear_buffer (short * buffer, int len) {
   memset (buffer, 0, sizeof(short)*len);
 }

and probably GCC is able to optimize (when invoked with -O2 or -O3) that cleverly (because GCC knows about memset).

To understand assembly code, I suggest running gcc -S -O -fverbose-asm on some small C file, then to look inside the produced .s file.

Basile Starynkevitch
  • 223,805
  • 18
  • 296
  • 547
  • I would, but the developer before me noted that the inline asm code is actually faster than the call to memset. It is also used by other parts of the application that are (apparently) relying on it to be the way it is. – Will Dec 04 '12 at 16:18
  • Then you should try again with a more recent GCC compiler (current version is 4.7). I'm pretty sure it will optimize that quite well. – Basile Starynkevitch Dec 04 '12 at 16:20
  • That may be, but it still does not answer the question as to why I cannot seem to access the class member without the application having a brain cramp. I'd really like to know what specifically is wrong here, and how to fix that. The ability to access a class/struct member from inline asm is actually more valuable information to me than simply change the routine to `memset` instead, especially since I cannot seem to find an example on how to do this. – Will Dec 04 '12 at 16:24
  • Nothing should change if you put in some `void*` local pointer the address of that `class` member. And then, do your `asm` tricks as usual on that pointer. – Basile Starynkevitch Dec 04 '12 at 16:56
0

I would have take a guess, because I don't know Blackfin assembler:

That LC0 sounds like "loop counter", LSETUP looks like a macro/insn, which, well, setups a loop between two labels and with a certain loop counter.

The "%0" operands is apparently the address to write to and we can safely guess it's incremented in the loop, in other words it's both an input and output operand and should be described as such. Thus, I suggest describing it as in input-output operand, using "+" constraint modifier, as follows:

void clear_buffer ( short * buffer, int len ) {
    __asm__ (
            "/* clear_buffer */\n\t"
            "LSETUP (1f, 1f) LC0=%1;\n"
            "1:\n\t"
            "W [%0++] = %2;"
            : "+a" ( buffer )
            : "a" ( len ), "d" ( 0 )
            : "memory", "LC0", "LT0", "LB0"
    );
}

This is, of course, just a hypothesis, but you could disassemble the code and check if by any chance GCC allocated the same register for "%0" and "%2".

PS. Actually, only "+a" should be enough, early-clobber is irrelevant.

chill
  • 16,470
  • 2
  • 40
  • 44
0

For anyone else who runs into a similar circumstance, the problem here was not with the in-line assembly, nor with the way it was being called: it was with the classes / structs in the program. The class that I believed to be the offender was not the problem - there was another class that held an instance of it, and due to other members of that outer class, the inner one was not aligned on a word boundary. This was causing the "Bus Error" that I was experiencing. I had not come across this before because the classes were not declared with __attribute__((packed)) in other code, but they are in my implementation.

Giving Type Attributes - Using the GNU Compiler Collection (GCC) a read was what actually sparked the answer for me. Two particular attributes that affect memory alignment (and, thus, in-line assembly such as I am using) are packed and aligned.

As taken from the aforementioned link:

aligned (alignment)

This attribute specifies a minimum alignment (in bytes) for variables of the specified type. For example, the declarations:

          struct S { short f[3]; } __attribute__ ((aligned (8)));
          typedef int more_aligned_int __attribute__ ((aligned (8)));

force the compiler to ensure (as far as it can) that each variable whose type is struct S or more_aligned_int is allocated and aligned at least on a 8-byte boundary. On a SPARC, having all variables of type struct S aligned to 8-byte boundaries allows the compiler to use the ldd and std (doubleword load and store) instructions when copying one variable of type struct S to another, thus improving run-time efficiency.

Note that the alignment of any given struct or union type is required by the ISO C standard to be at least a perfect multiple of the lowest common multiple of the alignments of all of the members of the struct or union in question. This means that you can effectively adjust the alignment of a struct or union type by attaching an aligned attribute to any one of the members of such a type, but the notation illustrated in the example above is a more obvious, intuitive, and readable way to request the compiler to adjust the alignment of an entire struct or union type.

As in the preceding example, you can explicitly specify the alignment (in bytes) that you wish the compiler to use for a given struct or union type. Alternatively, you can leave out the alignment factor and just ask the compiler to align a type to the maximum useful alignment for the target machine you are compiling for. For example, you could write:

          struct S { short f[3]; } __attribute__ ((aligned));

Whenever you leave out the alignment factor in an aligned attribute specification, the compiler automatically sets the alignment for the type to the largest alignment that is ever used for any data type on the target machine you are compiling for. Doing this can often make copy operations more efficient, because the compiler can use whatever instructions copy the biggest chunks of memory when performing copies to or from the variables that have types that you have aligned this way.

In the example above, if the size of each short is 2 bytes, then the size of the entire struct S type is 6 bytes. The smallest power of two that is greater than or equal to that is 8, so the compiler sets the alignment for the entire struct S type to 8 bytes.

Note that although you can ask the compiler to select a time-efficient alignment for a given type and then declare only individual stand-alone objects of that type, the compiler's ability to select a time-efficient alignment is primarily useful only when you plan to create arrays of variables having the relevant (efficiently aligned) type. If you declare or use arrays of variables of an efficiently-aligned type, then it is likely that your program also does pointer arithmetic (or subscripting, which amounts to the same thing) on pointers to the relevant type, and the code that the compiler generates for these pointer arithmetic operations is often more efficient for efficiently-aligned types than for other types.

The aligned attribute can only increase the alignment; but you can decrease it by specifying packed as well. See below.

Note that the effectiveness of aligned attributes may be limited by inherent limitations in your linker. On many systems, the linker is only able to arrange for variables to be aligned up to a certain maximum alignment. (For some linkers, the maximum supported alignment may be very very small.) If your linker is only able to align variables up to a maximum of 8-byte alignment, then specifying aligned(16) in an __attribute__ still only provides you with 8-byte alignment. See your linker documentation for further information.

.

packed

This attribute, attached to struct or union type definition, specifies that each member (other than zero-width bit-fields) of the structure or union is placed to minimize the memory required. When attached to an enum definition, it indicates that the smallest integral type should be used.

Specifying this attribute for struct and union types is equivalent to specifying the packed attribute on each of the structure or union members. Specifying the -fshort-enums flag on the line is equivalent to specifying the packed attribute on all enum definitions.

In the following example struct my_packed_struct's members are packed closely together, but the internal layout of its s member is not packed—to do that, struct my_unpacked_struct needs to be packed too.

          struct my_unpacked_struct
           {
              char c;
              int i;
           };

          struct __attribute__ ((__packed__)) my_packed_struct
            {
               char c;
               int  i;
               struct my_unpacked_struct s;
            };

You may only specify this attribute on the definition of an enum, struct or union, not on a typedef that does not also define the enumerated type, structure or union.

The problem which I was experiencing was specifically due to the use of packed. I attempted to simply add the aligned attribute to the structs and classes, but the error persisted. Only removing the packed attribute resolved the problem. For now, I am leaving the aligned attribute on them and testing to see if I find any improvements in the efficiency of the code as mentioned above, simply due to their being aligned on word boundaries. The application makes use of arrays of these structures, so perhaps there will be better performance, but only profiling the code will say for certain.

Will
  • 3,500
  • 4
  • 30
  • 38