Live data from Hacker News

Inline assembly in Linux

github.com

1–10 of 36 posts

Re: Inline assembly in Linux

#3
I'm trying to add call/cc to node, or to lua.

Recap: call/cc is the ability to save the current state of a running thread, then revert to that state at a later point in time. In other words, at any point in your program, you can say "Save the current stack." It's saved as a function. Later, whenever you call that function, the current stack is thrown out, and replaced with the saved stack.

This is very useful for a number of reasons. It's also a very rare feature to have in your language.

Neither node nor lua has support for this. The closest I've found is a Lua extension which adds coroutine.clone(). In principle, this is the solution. In practice, it has a number of limitations, such as restrictions on when you're allowed to call coroutine.clone(). (For example, if your stack looks like Lua -> C -> Lua, then it won't work.)

I tried to channel my inner Mike Pall and solve this problem once and for all, but I'm not Mike Pall, and this is really hard. I was hoping you might know of any possible solution which is (a) practical, (b) cross-platform, and (c) works in all cases.

Why post this here? Because this post happens to attract exactly the kind of people that might know a way forward. There must be a way. Apologies for the off-topic comment.

Re: Inline assembly in Linux

#4
I've been using a lot of inline assembly lately, and while the Stockholm syndrome might be in effect, I'm coming to like the GCC syntax. For me, main thing that has helped has been to adopt a consistent syntax. Here's some examples of what I'm currently using for an AVX2 popcnt optimization, with some explanation.

  #define ASM_VEC_BYTE_COUNT_SET(vec, sum, mask, shuf)                  \
    __asm volatile ("vpsrld $4, %[VEC], %[SUM]\n"                       \
                    "vpand %[MASK], %[VEC], %[VEC]\n"                   \
                    "vpand %[MASK], %[SUM], %[SUM]\n"                   \
                    "vpshufb %[VEC], %[SHUF], %[VEC]\n"                 \
                    "vpshufb %[SUM], %[SHUF], %[SUM]\n"                 \
                    "vpaddb %[VEC], %[SUM], %[SUM]\n" :                 \
                    /* rd/wr ymm */ [VEC] "+&x" (vec),                  \
                    /* write ymm */ [SUM] "=&x" (sum) :                  \
        	    /* read ymm  */ [MASK] "x" (mask),                  \
                    /* read ymm  */ [SHUF] "x" (shuf))
1) Try to use the %[symbolic] syntax rather than %[n] numeric. It's slightly longer to write, but usually clearer to read. Use upper case for the symbolic name. Put your inputs one per line, with a preceding comment.

2) If you are using the same assembly more than once in your program, declare your assembly within a #define macro, then use the macro in your code.

3) Use "__asm volatile". Declaring "volatile" is not required, but once you are writing inline assembly you usually know more than the compiler about where the block should go.

5) If you have multiple lines of assembly and output registers, you are almost always safer to use "+&" and "=&" for your constraint rather than just "+" or "=". Search for "early clobber" for details.

6) Strongly prefer single type constraints. The more flexibility you give the compiler, the more likely it will defeat your efforts at optimization. Use explicit memory addressing modes rather than "m". The modifier "c" is needed for the offset.

  #define ASM_VEC_LOAD_OFFSET_MEM(off, mem, vec)                    \
    __asm volatile ("vmovdqu %c[OFF](%[MEM]), %[VEC]\n" :           \
                    /* destination */ [VEC] "=x" (vec) :            \
                    /* byte offset */ [OFF] "i" (off),              \
                    /* mem address */ [MEM] "r" (mem))
7) The register constraints for vectors are tricky, because the "x" constraint is used for both XMM and YMM vectors. There is no way to specify that one wants only one or the other. This sort of makes sense, since in hardware they share the same register. You can use the "q" modifier when you need to specify XMM syntax in the output when you need both forms of the same vector.

Re: Inline assembly in Linux

#5

I'm trying to add call/cc to node, or to lua. Recap: call/cc is the ability to save the current state of a running thread, then revert to that state at a later point in time. In other words, at any point in your program, you can say "Save the current stack." It's saved as a function. Later, whenever you call that function, the current stack is thrown out, and replaced with the saved stack. This is very useful for a n…

getcontext(3) / setcontext(3). Not standard, but exists in both Linux and FreeBSD.

Re: Inline assembly in Linux

#6

I'm trying to add call/cc to node, or to lua. Recap: call/cc is the ability to save the current state of a running thread, then revert to that state at a later point in time. In other words, at any point in your program, you can say "Save the current stack." It's saved as a function. Later, whenever you call that function, the current stack is thrown out, and replaced with the saved stack. This is very useful for a n…

James Long is currently playing with call/cc in JavaScript [1].

He mentioned a paper titled "Exceptional Continuations in JavaScript" [2], that describes the method he used in his implementation.

Maybe you should get in touch?

[1] https://twitter.com/jlongster/status/726259468405751808

[2] http://www.schemeworkshop.org/2007/procPaper4.pdf

Re: Inline assembly in Linux

#7
post #4

I've been using a lot of inline assembly lately, and while the Stockholm syndrome might be in effect, I'm coming to like the GCC syntax. For me, main thing that has helped has been to adopt a consistent syntax. Here's some examples of what I'm currently using for an AVX2 popcnt optimization, with some explanation. #define ASM_VEC_BYTE_COUNT_SET(vec, sum, mask, shuf) \ __asm volatile ("vpsrld $4, %[VEC], %[SUM]\n" \ "…

I'd recommend to use intrinsics for SIMD vectorization, which is portable to platforms that don't support the GCC syntax (e.g. Windows with MSVC). You can use Intel's Intrinsics Guide (https://software.intel.com/sites/landingpage/IntrinsicsGuide...) to find the intrinsics that corresponds to the instructions you are using.

Re: Inline assembly in Linux

#8
post #4

I've been using a lot of inline assembly lately, and while the Stockholm syndrome might be in effect, I'm coming to like the GCC syntax. For me, main thing that has helped has been to adopt a consistent syntax. Here's some examples of what I'm currently using for an AVX2 popcnt optimization, with some explanation. #define ASM_VEC_BYTE_COUNT_SET(vec, sum, mask, shuf) \ __asm volatile ("vpsrld $4, %[VEC], %[SUM]\n" \ "…

3 - using volatile for asm that doesn't have otherwise inexpressible side effects has the same askance that using it for thread safety has. If you think you need it, maybe you needed to add a "memory" clobber instead.

5 - I can't think of any meaning early clobber has on an input+output constraint ("+")?

6 - there are many cases where you really do want to give the compiler flexibility in addressing modes. Unfortunately clang tends to ignore that and generate (reg) regardless.

7 - not really different than GPRs; you use "r" as the constraint then a modifier like "k" for the size.

I guess the lesson is that yeah gcc inline asm is powerful, but they try to leave it undocumented for a reason. Also, who stole number 4?

Re: Inline assembly in Linux

#9

I'm trying to add call/cc to node, or to lua. Recap: call/cc is the ability to save the current state of a running thread, then revert to that state at a later point in time. In other words, at any point in your program, you can say "Save the current stack." It's saved as a function. Later, whenever you call that function, the current stack is thrown out, and replaced with the saved stack. This is very useful for a n…

I assume you have read http://okmij.org/ftp/continuations/against-callcc.html

Re: Inline assembly in Linux

#10

I'm trying to add call/cc to node, or to lua. Recap: call/cc is the ability to save the current state of a running thread, then revert to that state at a later point in time. In other words, at any point in your program, you can say "Save the current stack." It's saved as a function. Later, whenever you call that function, the current stack is thrown out, and replaced with the saved stack. This is very useful for a n…

> if your stack looks like Lua -> C -> Lua, then it won't work.

I don't think you can safely solve this in the general case. There is a key problem I don't think you can work around.

Say your stack looks like C(1) -> Lua -> C -> Lua. The outermost C frames might not know anything about Lua (they just use some library that uses Lua as a library). Say you try to take a snapshot of this stack to create a continuation. You probably just want to snapshot the Lua -> C -> Lua part, since that is the portion of the stack representing the execution of the Lua program.

Now say all these frames return. Then the main program calls Lua again, through through a slightly different code-path, and now you have C(2) -> Lua. Say the embedded Lua program decides to resume the continuation.

Now keep in mind that the C stack is not position-independent. The C stack can contain pointers to the C stack, so when you resume, you need your resumed stack to live at exactly the same address as last time it ran. But what if C(1) and C(2) are not exactly the same size? What if we called one extra function before calling Lua the second time? It is impossible to copy the continuation's C stack back into its original position. So it's impossible to resume the continuation.

You could try to snapshot the entire C stack to get around this, including the outermost C frames. But this would be most unexpected for the C program that is using the Lua interpreter. Lua is supposed to just be a regular C library: you call a function, it does things, and then returns. It wouldn't be acceptable that calling a Lua interpreter function like lua_call() backs your entire C program to a previous state just because the embedded Lua program used a fancy feature called continuations!

There are many other things that would make this tricky at best to get working, but I think the problem above really tanks the idea completely.

Post reply on HN