Register a SA Forums Account here!
JOINING THE SA FORUMS WILL REMOVE THIS BIG AD, THE ANNOYING UNDERLINED ADS, AND STUPID INTERSTITIAL ADS!!!

You can: log in, read the tech support FAQ, or request your lost password. This dumb message (and those ads) will appear on every screen until you register! Get rid of this crap by registering your own SA Forums Account and joining roughly 150,000 Goons, for the one-time price of $9.95! We charge money because it costs us money per month for bills, and since we don't believe in showing ads to our users, we try to make the money back through forum registrations.
 
  • Post
  • Reply
Sweeper
Nov 29, 2007
The Joe Buck of Posting
Dinosaur Gum

Rocko Bonaparte posted:

I have multiple variables packed together in a memory buffer that I'd like to assign to a struct. Are there any gotchas for this beyond some basic memory alignment stuff? What I want to assume is:

1. The memory for a struct is going to start at a proper alignment.
2. The amount of padding then depends on the next variable in the struct. It would be aligned on cleanly divisible boundaries based on that type's width. So a char and then a (32-bit) int would imply I have to skip 3 bytes before dealing with the integer in the struct.

I know the actual schema of the data when I'm doing this.

I didn't know if there was any other major things to worry about before I create a segfault generator here. Also, I'd rather not get into X instead of Y either unless you want to read a pile on how I got here.

How is the data laid out in this buffer? Are you trying to cast the buffer to the struct? You can pack the structs to remove the padding and force the alignment to 1 :v:

I’m assuming your architecture will handle whatever unaligned reads you are doing

Adbot
ADBOT LOVES YOU

chglcu
May 17, 2007

I'm so bored with the USA.

Rocko Bonaparte posted:

I have multiple variables packed together in a memory buffer that I'd like to assign to a struct. Are there any gotchas for this beyond some basic memory alignment stuff? What I want to assume is:

1. The memory for a struct is going to start at a proper alignment.
2. The amount of padding then depends on the next variable in the struct. It would be aligned on cleanly divisible boundaries based on that type's width. So a char and then a (32-bit) int would imply I have to skip 3 bytes before dealing with the integer in the struct.

I know the actual schema of the data when I'm doing this.

I didn't know if there was any other major things to worry about before I create a segfault generator here. Also, I'd rather not get into X instead of Y either unless you want to read a pile on how I got here.

The only gotchas I’m aware of is dealing with pointer sizes on 32- vs 64-bit and dealing with the viable pointer if you’ve got virtual functions. I’m also not sure how standardized the alignment and padding is, but I’ve implemented something along these lines for game data serialization before and I know it worked fine for the 360, PS3 and PC at the time.

more falafel please
Feb 26, 2005

forums poster

As mentioned before, this works fine as long as you know the struct alignment/packing. That can be adjusted in most compilers using
pre:
#pragma pack
(I think there are subtle differences between the MSVC/clang version of this and the GCC version), so you can guarantee that a struct will be packed on 1/4/8/16/64 byte boundaries. What packing is best for your particular use case depends on a lot of factors, as mentioned, reads of data that's not aligned to the CPU word size are likely slower, and can even cause more cache misses.

But I've definitely done things like that, you just need to be sure you've got it right on all platforms/compilers.

yippee cahier
Mar 28, 2005

I would memcpy each member into the struct from the packed byte stream, and let the compiler sort out the alignment and padding.

Foxfire_
Nov 8, 2010

It is undefined behavior to alias a char buffer as a struct by casting a pointer (the reverse is legal because there's a special exception for char*)

It will likely produce correct behavior in many cases and gcc specifically has a switch (-fno-strict-aliasing) to make it safe in all cases by disabling the optimizations that use the no-aliasing assumption.

The safe way to do it is with a memcpy(), which your compiler may or may not be smart enough to optimize out if it's equivalent to an alias

Gorman Thomas
Jul 24, 2007
Aliasing like that in C is legal but if you use a static analyzer it will probably complain. Like everyone else, I'd just use memcpy.

Foxfire_
Nov 8, 2010

It's not legal in C either. It's specified in section 6.5.7 in C11.

Example of something that will break on it

The output will change depending on whether you add -fno-strict-aliasing or not. Without it, the optimizer assumes that a float* and a int* never point to the same memory, so that it doesn't have to reload the other one after a write (the generated assembly is kind of dumb for an example, because it sees the whole program and can keep folding until the result is a constant. Look at the outputted numbers).

There is a complicated exception for structs that share common prefixes that gets used by BSD sockets. I don't really understand its subtilties and don't use it

Doing something like

char aBigDataBuffer[4096];
SomeStruct* someStruct = (SomeStruct*)aBigDataBuffer;

is more likely to happen to work, but it's violating assumptions in the optimizer even if you make all the alignment issues work out. You may end up with behavior where changes in the buffer are sometimes not reflected in the struct (because the optimizer can assume that changing a char[] doesn't invalidate previous reads of a SomeStruct's fields because they're different types)

Foxfire_ fucked around with this message at 05:03 on Sep 3, 2021

Gorman Thomas
Jul 24, 2007
Thanks for the clarification. I still fall into the trap that if the compiler doesn't complain, and the assembly looks OK, it must be legal!

PittTheElder
Feb 13, 2012

:geno: Yes, it's like a lava lamp.

Foxfire_ posted:

Doing something like

char aBigDataBuffer[4096];
SomeStruct* someStruct = (SomeStruct*)aBigDataBuffer;

is more likely to happen to work, but it's violating assumptions in the optimizer even if you make all the alignment issues work out. You may end up with behavior where changes in the buffer are sometimes not reflected in the struct (because the optimizer can assume that changing a char[] doesn't invalidate previous reads of a SomeStruct's fields because they're different types)

lol wait is this not actually legal/well defined? our I/O code does this all over the place

Foxfire_
Nov 8, 2010

Yep, that's undefined behavior.

The legal way to do it is to do a memcpy() because char* pointers are permitted to alias anything (but not the reverse).
A good optimizer will often eliminate the actual copy, but if you're stuck with a less fancy compiler you end up having to decide between technically wrong code (that will probably work if your optimizer isn't sophisticated enough to use those aliasing assumptions anyway) or extra copies and temporary space

Example with gcc eliding a memcpy()

Twerk from Home
Jan 17, 2009

This avatar brought to you by the 'save our dead gay forums' foundation.
Can someone point me to a crash course in autotools? I'm hacking away at https://github.com/samtools/htslib, experimenting with adding zstd as a compression format over libz or libdeflate, and I can't get the error message to yell at me if libzstd-dev isn't installed and available:

I've added
code:
AC_ARG_ENABLE([lzstd],
  [AS_HELP_STRING([--enable-lzstd],
                  [enable support for zstd, allowing build of bzstd])],
  [], [enable_lzstd=no])

if test "$enable_lzstd" != no; then
    zstd_devel=ok
    AC_CHECK_HEADERS([zstd.h], [], [zstd_devel=header-missing], [;])
    AC_CHECK_LIB([zstd], [ZSTD_compress], [], [zstd_devel=missing])
    if test zstd_devel = missing; then
MSG_ERROR([cannot find lzstd])
    fi
    pc_requires="$pc_requires libzstd"
    static_LIBS="$static_LIBS -lzstd"
fi
following the existing norms in https://github.com/samtools/htslib/blob/develop/configure.ac, but I honestly have no idea what I'm doing and need to take a step back and hit the books about Autotools. I'm historically a Java / C# / Scala / that family of things developer.

PittTheElder
Feb 13, 2012

:geno: Yes, it's like a lava lamp.

Foxfire_ posted:

Yep, that's undefined behavior.

The legal way to do it is to do a memcpy() because char* pointers are permitted to alias anything (but not the reverse).
A good optimizer will often eliminate the actual copy, but if you're stuck with a less fancy compiler you end up having to decide between technically wrong code (that will probably work if your optimizer isn't sophisticated enough to use those aliasing assumptions anyway) or extra copies and temporary space

Example with gcc eliding a memcpy()

Very interesting. I mean we do know it works, but no wonder the static analysis tools we recently implemented is so grumpy about it :v:

Plorkyeran
Mar 22, 2007

To Escape The Shackles Of The Old Forums, We Must Reject The Tribal Negativity He Endorsed
You're missing a $ in if test zstd_devel = missing and so are just comparing some string literals.

There's also a bunch of nitpicky autotools things that that code isn't doing properly but I'm assuming you copied and pasted that from existing checks and so you should just stick with matching the existing style.

Twerk from Home
Jan 17, 2009

This avatar brought to you by the 'save our dead gay forums' foundation.

Plorkyeran posted:

You're missing a $ in if test zstd_devel = missing and so are just comparing some string literals.

There's also a bunch of nitpicky autotools things that that code isn't doing properly but I'm assuming you copied and pasted that from existing checks and so you should just stick with matching the existing style.

I am absolutely interested in hearing about the nitpicky autotools things!

cheetah7071
Oct 20, 2010

honk honk
College Slice
Is there a way to refer to a specific member function of a class, without referring to any specific instantiation of that function? Or, alternatively, any way to concisely do what I'm doing here without the macro:

code:
#define ADDMETRIC(func,name) \
auto func = [&](GridMetric& gm)->rv {return gm.func(); }; \
funcs.push_back(gridfunc{ func }); \
//some more stuff goes here that I could easily put into a function instead of a macro
Which, given a text name for a member function of the GridMetric class, adds it to a list of functions to call later on in the code. The actual GridMetric object itself doesn't exist yet, and there will be over a dozen of them in the end, all of which refer to this list of functions needing to be called. This works well enough, but I'm coming back to the code and need to do some more complicated stuff that will take the macro past ugly into unreadable.

leper khan
Dec 28, 2010
Honest to god thinks Half Life 2 is a bad game. But at least he likes Monster Hunter.

cheetah7071 posted:

Is there a way to refer to a specific member function of a class, without referring to any specific instantiation of that function? Or, alternatively, any way to concisely do what I'm doing here without the macro:

code:
#define ADDMETRIC(func,name) \
auto func = [&](GridMetric& gm)->rv {return gm.func(); }; \
funcs.push_back(gridfunc{ func }); \
//some more stuff goes here that I could easily put into a function instead of a macro
Which, given a text name for a member function of the GridMetric class, adds it to a list of functions to call later on in the code. The actual GridMetric object itself doesn't exist yet, and there will be over a dozen of them in the end, all of which refer to this list of functions needing to be called. This works well enough, but I'm coming back to the code and need to do some more complicated stuff that will take the macro past ugly into unreadable.

Not clear why you wouldn't be able to do what you're trying to do with a function pointer.

cheetah7071
Oct 20, 2010

honk honk
College Slice

leper khan posted:

Not clear why you wouldn't be able to do what you're trying to do with a function pointer.

It might be entirely possible but I couldn't figure it out when I first tried.

Basically, the GridMetric class has a whole bunch of functions, of which a subset are relevant any time I run this code. I need all of my GridMetric objects to call the ones in a list I specify, which isn't known at compile time, and I want to do that concisely--i.e., ideally with one line per function at most, which I currently accomplish with the macro. I'd prefer to call a function rather than a macro, obviously, but I wasn't able to figure out how to get a function pointer to the member function in a way that all of my GridMetric objects could refer to it (which is trivial with a macro because it's just manipulating text). Maybe it's really easy and I just wasn't googling the right things to find the syntax

cheetah7071 fucked around with this message at 00:20 on Sep 4, 2021

Plorkyeran
Mar 22, 2007

To Escape The Shackles Of The Old Forums, We Must Reject The Tribal Negativity He Endorsed

Twerk from Home posted:

I am absolutely interested in hearing about the nitpicky autotools things!

Always use AS_IF rather than shell if. AS_IF just expands to if but it results in macro expansion happening in a different order. This almost never matters but it's easier to just always use AS_IF than to determine if it matters.

There's some convoluted idioms around handling various values of --enable-foo=bar. The libcurl check in the file you linked is a decent example: if it's passed --enable-libcurl=check then it'll warn if libcurl isn't found instead of erroring. Similarly, each of the checks sets libcurl_devel to a human-readable string in the error case which is then used in the error message. It's also checking that libcurl_devel is ok rather than for the specific error string as there's multiple error cases (which you currently have wrong).

You're probably missing a AC_DEFINE([HAVE_LIBZSTD]). This one is a bit less nitpicky....

Xerophyte
Mar 17, 2008

This space intentionally left blank

cheetah7071 posted:

It might be entirely possible but I couldn't figure it out when I first tried.

Basically, the GridMetric class has a whole bunch of functions, of which a subset are relevant any time I run this code. I need all of my GridMetric objects to call the ones in a list I specify, which isn't known at compile time, and I want to do that concisely--i.e., ideally with one line per function at most, which I currently accomplish with the macro. I'd prefer to call a function rather than a macro, obviously, but I wasn't able to figure out how to get a function pointer to the member function in a way that all of my GridMetric objects could refer to it (which is trivial with a macro because it's just manipulating text). Maybe it's really easy and I just wasn't googling the right things to find the syntax

The pointer-to-member-function syntax is a pain and a half, but if I'm following you right you're looking to do something like

C++ code:
class Fnord;

// I dislike the pointer-to-member-function syntax and it trips me up whenever I see it
static std::vector<void(Fnord::*)(void)> funcs;

class Fnord {
public:
  Fnord(int v_) : v(v_) {}
  void x() { std::cout << v << std::endl; }
  void xSqr() { std::cout << v*v << std::endl; }

  void doFuncs() {
    for (auto f : funcs) {
      (this->*f)();
    }
  }
private:
  int v;
};

int main() {
  Fnord a(3);
  Fnord b(4);

  // prints 3 and 4.
  funcs.push_back(&Fnord::x);
  a.doFuncs();
  b.doFuncs();

  funcs.clear();

  // prints 9 and 16
  funcs.push_back(&Fnord::xSqr);
  a.doFuncs();
  b.doFuncs();
}
Although presumably with less global state. This should still work with virtual functions, far as I am aware. If you define a member function pointer to a virtual function then it's not going to point to a concrete implementation but to some sort of thunk that does the dispatch. I haven't actually tried it though, because pointer-to-member-functions are a pain and I dislike them.

Xerophyte fucked around with this message at 14:40 on Sep 4, 2021

cheetah7071
Oct 20, 2010

honk honk
College Slice
Thanks, that was exactly what I was looking for, and the messiness involved is probably why I couldn't figure it out on my own

e: ::* is a spicy bit of syntax

cheetah7071 fucked around with this message at 02:05 on Sep 4, 2021

Nalin
Sep 29, 2007

Hair Elf

cheetah7071 posted:

Thanks, that was exactly what I was looking for, and the messiness involved is probably why I couldn't figure it out on my own

e: ::* is a spicy bit of syntax

You could just get into a habit of using std::function. It is slightly less messy!

C++ code:
// Shamelessly stolen a bit from cppreference.com
struct Foo {
    Foo(int num) : num_(num) {}
    void print_add(int i) const { std::cout << num_+i << '\n'; }
    int num_;
};

std::function<void(const Foo&, int)> foo_member_function = &Foo::print_add;
std::function<int(Foo const&)> foo_member_variable = &Foo::num_;

cheetah7071
Oct 20, 2010

honk honk
College Slice

Nalin posted:

You could just get into a habit of using std::function. It is slightly less messy!

C++ code:
// Shamelessly stolen a bit from cppreference.com
struct Foo {
    Foo(int num) : num_(num) {}
    void print_add(int i) const { std::cout << num_+i << '\n'; }
    int num_;
};

std::function<void(const Foo&, int)> foo_member_function = &Foo::print_add;
std::function<int(Foo const&)> foo_member_variable = &Foo::num_;

I actually tried something like that when I was first writing this, and it didn't work so I assumed you couldn't reference member function pointers that way. I must have just made some minor error throwing it all off.

OneEightHundred
Feb 28, 2008

Soon, we will be unstoppable!
Re: This about concatenating the line number to a token:
https://stackoverflow.com/questions/1597007/creating-c-macro-with-and-line-token-concatenation-with-positioning-macr

code:
#define TOKENPASTE(x, y) x ## y
#define TOKENPASTE2(x, y) TOKENPASTE(x, y)
#define UNIQUE static void TOKENPASTE2(Unique_, __LINE__)(void) {}
... why does it work this way? i.e. I understand why TOKENPASTE(whatever, __LINE__) doesn't work by itself, because the ## operator disables macro expansion, but why is __LINE__ substituted when TOKENPASTE2 is expanded, but not when UNIQUE is?

Jabor
Jul 16, 2010

#1 Loser at SpaceChem
__LINE__ is not substituted when UNIQUE is expanded because it doesn't exist yet at that point - it's only created as the result of expanding UNIQUE.

Given this:
code:
#define TOKENPASTE(x, y) x ## y
#define TOKENPASTE2(x, y) TOKENPASTE(x, y)
#define UNIQUE static void TOKENPASTE2(Unique_, __LINE__)(void) {}
It'll expand like this:
code:
UNIQUE
...
static void TOKENPASTE2(Unique_, __LINE__)(void) {}
...
static void TOKENPASTE(Unique_, 1)(void) {}
...
static void Unique_1(void) {}
If you didn't define TOKENPASTE2, and instead used:
code:
#define TOKENPASTE(x, y) x ## y
#define UNIQUE static void TOKENPASTE(Unique_, __LINE__)(void) {}
It'd expand like:
code:
UNIQUE
...
static void TOKENPASTE(Unique_, __LINE__)(void) {}
...
static void Unique___LINE__(void) {}

rjmccall
Sep 7, 2007

no worries friend
Fun Shoe
C macro “evaluation order” is kindof weird. When expansion identifies a use of a function-like macro, it doesn’t immediately expand macros in the arguments. Therefore, the argument token sequence passed by UNIQUE is just the raw identifier __LINE__. But within the “callee”, argument substitution does happen immediately, and that expansion normally immediately recursively expands macros in the argument. So the parameter in TOKENPASTE2 is replaced by the macro expansion of the argument token sequence, i.e. 42 or whatever, and then that becomes the argument received by TOKENPASTE. But as a special case, that immediate expansion of arguments is not done during argument substitution when the parameter was immediately adjacent to an operator like ##. So if UNIQUE used TOKENPASTE directly, the parameter in TOKENPASTE would just be replaced with __LINE__, and that’s what would be token-concatenated.

PittTheElder
Feb 13, 2012

:geno: Yes, it's like a lava lamp.

Okay so going back to the question of pulling data from a char buffer, what's the best way to actually handle something like this?


code:
char acDataBuffer[4096];

typedef struct
{
   double dData1;
   long lData2;
   double dData3;	// Note, this will not fall on a 8-byte boundary and so padding will be inserted prior
   ...
} DataStruct;
I'm going to receive data in acDataBuffer, and it will be packed according to the order shown in DataStruct, minus the 4 padding bytes that will be inserted between lData2 and dData3. That padding means I can't cleanly memcpy from the buffer into a local struct (without adding pragma pack directives), nor do I have control over the input data, so I can't re-order it to avoid padding.

Currently this is actually handled in the code by declaring DataStruct with a bunch of char arrays instead of the full types, and then just casting it into the full type when required, as below (with simplified operations of course, these do need to be accessed separately):
code:
typedef struct
{
   char acData1[8];
   char acData2[4];
   char acData3[8];
   ...
} CharDataStruct;

void func(char* acCharBuffer)
{
   CharDataStruct* pstData = (CharDataStruct*)acCharBuffer;
   
   double dCombinedData = *(double*)(pstData->acData1) + *(double*)(pstData->acData3);
}
The above code works just fine, but it freaks our static analysis tool right the gently caress out every time we cast a char pointer to a pointer with greater alignment requirements.

The options as I see it are:
  1. pragma pack the version of DataStruct that uses the full data types to avoid padding, and memcpy into a local copy of that struct, then access each variable normally.
  2. Individually memcpy each of the data members out of the char version of the struct (which I am trying to avoid as it will look really messy, there's actually 16 variables in that struct)

Is there a serious argument to made for one approach over the other?

Foxfire_
Nov 8, 2010

(1) is equivalent to what you are doing now, just without the undefined behavior. It will crash if unaligned loads don't work on your architecture.
(2) will work even if unaligned loads don't.

I would either do (1) or something like:

code:
typedef struct
{
	double data1;
	uint32_t data2;
	double data3;		// Will get padding before on sane architectures
} DataStructUnpacked;

DataStructUnpacked UnpackDataStruct(const char* byteStream, uint32_t length)
{
	assert(byteStream != NULL)
	DataStructUnpacked retVal;

	assert(length >= sizeof(retVal.data1);
	memcpy(&retVal.data1, byteStream, sizeof(retVal.data1);
	byteStream += sizeof(retVal.data1);
	length -= sizeof(retVal.data1);

	...

	return retVal;
}
to consolidate the unpacking code. Replace the char* -> DataStruct* cast with a const DataStructUnpacked foo = UnpackDataStruct(...) then access the fields normally
GCC optimizer is willing to elide all of that into unaligned loads directly from the buffer (presumably it wouldn't if you did a lot of math using it and it decided the load cost was worth a copy)

The casting thing you're doing now looks error prone and hard to maintain.

Foxfire_ fucked around with this message at 02:03 on Sep 8, 2021

leper khan
Dec 28, 2010
Honest to god thinks Half Life 2 is a bad game. But at least he likes Monster Hunter.
I would use (2)

yippee cahier
Mar 28, 2005

16 members isn’t too bad. Maybe look for opportunities to have arrays of values or even substructures that you can wrap in loops. Or helper functions that advance the pointer so you can have 16 straightforward lines of code.

Presto
Nov 22, 2002

Keep calm and Harry on.
I would leave it the way it is and tell the static analyzer to gently caress off

But that's just me.

PittTheElder
Feb 13, 2012

:geno: Yes, it's like a lava lamp.

Very helpful, thanks for the input all!

Beef
Jul 26, 2004
A colleague has bumped into an interesting issue. For reasons, he needs a way to force the compiler to use the stack to pass arguments to certain functions. My first thought was something like forcing cdecl, but this is x86-64, so that doesn't work.

The only way I see this can work is either :
- by adding six (5 for members) garbage arguments to the function, maybe with some macro magic to make it palatable, or
- working with varargs.

Either way, it's messy. Anyone has a better way?

Sweeper
Nov 29, 2007
The Joe Buck of Posting
Dinosaur Gum

Beef posted:

A colleague has bumped into an interesting issue. For reasons, he needs a way to force the compiler to use the stack to pass arguments to certain functions. My first thought was something like forcing cdecl, but this is x86-64, so that doesn't work.

The only way I see this can work is either :
- by adding six (5 for members) garbage arguments to the function, maybe with some macro magic to make it palatable, or
- working with varargs.

Either way, it's messy. Anyone has a better way?

I have to know why this is a thing, please expound

Xarn
Jun 26, 2015

Sweeper posted:

I have to know why this is a thing, please expound

This, and a preemptive please don't mix abis :v:

Beef
Jul 26, 2004
Weird research co-processor thing. He says he can pass it a function pointer and its stack, and let it do its thing.

I suggested doing something like pthread_create where the function takes a struct of args, but that's apparently too labor intensive when a bunch of existing code has to be adapted.


(imo, this is what happens when no compiler person gets involved)

nielsm
Jun 1, 2009



Write a generator for asm call thunks.

Beef
Jul 26, 2004
Write what where now?

Are you suggesting a compiler modification or a C/C++ function to generate the asm string?
If it is the latter, that sounds like it requires compile-time information.

Jabor
Jul 16, 2010

#1 Loser at SpaceChem
I assume you've got some thunk functions that take in parameters, and then instead of doing the work themselves they invoke a routine in the coprocessor. And you want to avoid shuffling things around unnecessarily, so you'd prefer the caller pass all the arguments on the stack (which would give you a neat array of all the parameters instead of having some of them in registers), so you can just pass a pointer to your own stack to the coprocessor?

I'd be inclined to just copy everything into an array and set whatever attributes would heavily encourage the whole thunk function to be inlined. Let the optimizer take care of putting those values in the right spots straight away instead of copying them around excessively. Also doing this explicitly instead of playing calling convention games means you can more easily do things like "put the coprocessor arguments somewhere that isn't on the thread stack, so we can do other stuff while the coprocessor is working without having to worry about corrupting its arguments".

Subjunctive
Sep 12, 2006

✨sparkle and shine✨

Yeah, for that I would allocate some heap space for the coprocessor to use as its stack and directly place the arguments in that memory where you want them. Sharing stacks here sounds like borrowing hard-to-debug trouble.

(For one thing, what happens if you’re near the edge of the “main” stack’s allocated memory and the co-processor need more? Does it run in the right process context for the kernel to allocate more correctly? What if your main task gets scheduled away while the co-processor is running, as is likely if you’re sleeping on something waiting for the result.)

Subjunctive fucked around with this message at 18:00 on Sep 13, 2021

Adbot
ADBOT LOVES YOU

rjmccall
Sep 7, 2007

no worries friend
Fun Shoe
Yeah, there’s no apparent reason this is tied to argument passing except it worked that way on i386 and they’re being lazy about the port. Write the “arguments” into memory and send a pointer to that to the coprocessor.

  • 1
  • 2
  • 3
  • 4
  • 5
  • Post
  • Reply