Shop OBEX P1 Docs P2 Docs Learn Events
Inline assembly bug/flaw for LONG directive — Parallax Forums

Inline assembly bug/flaw for LONG directive

jac_goudsmitjac_goudsmit Posts: 418
edited 2013-02-09 17:07 in Propeller 1
When you use a named parameter in inline assembly, you can specify a restriction in the form of a string. One of the restrictions you can use is "i" which means that the value is be used as an immediate parameter to an instruction. You can use enum values as parameters, and they get expanded correctly:
enum myenum
{
    myenum_A,
    myenum_B = 234
} myvar;

__asm__ __volatile__ {
"x   long 0;"
"    mov x, %[myenumb];"  // expands to "mov x, #234"
:
:
    [myenumb] "i" (myenum_B)
:
};

There are other restrictions too, for example "rC" specifies that an operand is a register in Cog memory (right?). Example (based on the above):
enum myenum
{
    myenum_A,
    myenum_B = 234
} myvar;

__asm__ __volatile__ {
"    mov %[myvar], %[myenumb];"  // expands to "mov myvar, #234"
:
    [myvar] "=rC" (myvar) // "=" means output, write-only
:
    [myenumb] "i" (myenum_B)
:
};

As every PASM programmer knows, the only way to use pointers is with self-modifying code. Example:
enum myenum
{
    myenum_A,
    myenum_B = 234
} myvar;

__asm__ __volatile__ {
"    movd store_ins, ptr;"
"    nop;" // there must be one instruction between modifying code and modified code
"store_ins"
"   mov 0, %[myenumb];"  // destination modified; result "mov myvar, #234"
/*...*/
"ptr long %[myvar]"
:
    [myvar] "=rC" (myvar) // "=" means output, write-only
:
    [myenumb] "i" (myenum_B)
:
};

Now here comes the problem: what if these instructions are part of a bigger piece of code (e.g. a table parser that looks for an enum value)
__asm__ __volatile__ {
"    movd store_ins, ptr;"
"    movs store_ins, value;"
"    nop;" // there must be one instruction between modifying code and modified code
"store_ins"
"    mov 0, #0;"  // destination and source modified
/*...*/
"ptr long %[myvar];"
"value long %[myenumb]" // ERROR: expands to "value long #234" which is invalid syntax
:
    [myvar] "=rC" (myvar) // "=" means output, write-only
:
    [myenumb] "i" (myenum_B)
:
};

If I change the "i" restriction to "rC", the inline assembler stores the value in a register somewhere, and then puts the address of the register in the long, which is wrong:

__asm__ __volatile__ {
// An instruction such as "mov r7, #234" is inserted here automatically by the compiler or assembler
"    movd store_ins, ptr;"
"    movs store_ins, value;"
"    nop;" // there must be one instruction between modifying code and modified code
"store_ins"
"    mov 0, #0;"  // destination and source modified
/*...*/
"ptr long %[myvar];"
"value long %[myenumb]" // Now expands to "value long r7" instead of "value long 234"
:
    [myvar] "=rC" (myvar) // "=" means output, write-only
:
    [myenumb] "i" (myenum_B)
:
};

I think with the current software (SimpleIDE 0.8.5 and the GCC version that comes with it) there is no way to fix this or work around the problem, other than using a literal value in the LONG directive instead of an inline assembly parameter; apparently this is a corner case that the Propeller GCC / GAS developers didn't think of.

I think the assembler should allow the LONG directive to accept a value with #, meaning "put this actual value here, not the address of the value" (an example of "put the address of the value" is the line "ptr long myvar" in the code above). And obviously there should be an error if # is used with a value that can only be used as an address (so "ptr long #myvar" in the above code would generate an error because myvar is a memory location, not a literal value that's known at compile time).

I expect the same problem to happen if you use one of the other directives (can BYTE or WORD even be used? I haven't tried).

I also filed an issue about this on the PropGCC google code site but I'm not sure if that's the right location (after all this is an Assembler problem), and I thought it might be a good idea to explain the problem here a little further.

Thanks!

===Jac

Comments

  • ersmithersmith Posts: 6,054
    edited 2013-02-09 13:37
    You could declare the long location in C, rather than in the assembly code:
    #include <propeller.h>
    _COGMEM long value1;
    

    In general the fewer things you put in inline assembly, the better.

    Eric
  • jac_goudsmitjac_goudsmit Posts: 418
    edited 2013-02-09 17:07
    Thanks for the reply, Eric, but that's not going to work.

    The code I'm working on contains a patch table, containing a number of values which would be an array of structs if it was written in C. The table describes which instructions in the rest of the code need to be modified and how, before they are executed. The selection of the correct structs from the table is done by using a mode parameter, and I iterate through the values by comparing that parameter with a constant value in the table.

    So basically if I would have the option of programming self-modifying code in C it would look something like this:
    typedef enum
    {
        Mode_A = 123, // Values don't matter, they're used here for illustration only
        Mode_B = 234,
    } Mode;
    
    void DoTheThing(
        Mode mode)
    {
        //
        // My code has the equivalent of the following, in the form of inline assembly
        //
        unsigned u;
        const static struct patchentry
        {
            Mode modeforthismod;
            unsigned *pInstructionToModify;
            unsigned uNewInstruction;
        } patchtable[ ] =
        {
            { Mode_A, address1, instruction1 },
            { Mode_A, address2, instruction2 },
            { Mode_B, address3, instruction3 },
        };
    
        for (u = 0; u < 3; u++)
        {
            struct patchentry *p = &patchtable[u];
    
            if (p->modeforthismode == mode)
            {
                *p->pInstructionToModify = p->uNewInstruction;
            }
        }
    
        // ... Rest of code follows here
        // ... The pointers in the patch table obviously refer to addresses in this part,
        // ... which has to be coded in Assembly because of tight timing constraints.
    }
    

    So, the table contains literal values (represented as enums) combined with cog addresses that refer to code in the inline assembly. The addresses of the assembly code aren't available to the C compiler of course, so I can't move the table into the C part of the program. I also can't rewrite the code in C or C++ because the timing is too constrained.

    I know there are other ways to implement this but I think this is something that should be possible, right?

    For now, I worked around it by declaring the mode values as preprocessor macros instead of enums, and I insert them in the code using the stringize operator of the preprocessor. It's ugly and I'm not sure yet if it works, but I'm working on it.
    // The stringize feature can only be used on macro parameters and stringizing produces the parameter's value in quotes
    // We want to evaluate the parameter before we stringize it, so we need two macros
    #define stringize1(x) #x
    #define stringize(x) stringize1(x)
    
    #ifdef WORKAROUND_ISSUE60
    #define Mode_A (123) // Again: actual values are for illustration only and are irrelevant
    #define Mode_B (234)
    #define w60(x) stringize(x)
    typedef int Mode;
    #else
    typedef enum
    {
        Mode_A = 123,
        Mode_B = 234,
    } Mode;
    #define w60(x) "%[" #x "]"
    #endif
    ...
    
    __asm__ __volatile__ {
    
    ...
    
    "value long " w60(Mode_A) "\n"
    
    ...
    
    :
    :
        ...
        [Mode_A] "i" (Mode_A),
        ...
    :
    };
    

    I did some digging in the binutils code, and it looks like the "LONG" directive is handled by a different part of `gas` than the regular opcodes, so I understand that it would be difficult to implement my initial proposal. Maybe a new pseudo-opcode needs to be added?

    If you can tell me in which binutils source file the LONG directive is handled, I may be able to post a patch. I'm not familiar with the binutils code but I'll be happy to do some more digging to make this work.

    ===Jac

    (PS the code in this post and the previous one aren't tested; I'm not trying to hide things, just making them easier to read. The entire module is several hundreds of lines of inline assembly and will be on github soon).
Sign In or Register to comment.