How to allocate buffer memory in Spin2
I'd like to write a generic FIR filter object. The amount of required buffer memory depends on the type and order of the filter. I'm looking for a way to allocate a "variable" length hub memory chunk so that I can call the Start/Init() function of the filter object without having to provide a pointer to the buffer. I write "variable" in quotes because theoretically it's static i.e. known at compile time. It doesn't have to be dynamic like malloc or new() in C/C++ and I don't have to free/discard it at any time. But declaring it static inside the object would mean that there is a fixed limit for it's size. And requiring the programmer of the calling code to pre-calculate and declare the buffer (as DAT section in the calling code) sabotages the idea of encapsulation. I think a library like object should ideally hide its implementation details.
Comments
Wouldn't you want to use VAR, so that each instance gets a unique buffer?
One other problem with placing the buffer size into the object's DAT is that the object becomes uncollapsible if there are multiple instances with different buffer sizes, causing their binaries to be different.
What would help is some compile-time function feedback from the child object, where it can compute the buffer size, based on the parent object's requirement. The parent object could then declare its own buffer as VAR.
Some run-time memory manager would simplify this, from a source code perspective.
As it is, there is no way to make a child object unique, without causing it to be uncollapsible.
Yes of course, I mean VAR not DAT. I haven't thought about multiple instances so far.
What would help is some compile-time function feedback from the child object, where it can compute the buffer size, based on the parent object's requirement.
This is hypothetical, I mean it doesn't exist yet, right?
What also would help is some mechanism to reserve hub memory like RES does for cog space. I could even write my own memory manager object if I wanted. But it wouldn't help much as long as everyone agrees to call the same alloc functions. Maybe we could just define two long adresses where the lowest and highest free memory adresses are stored (like for clock frequency and PLL mode)
So in the simplest case when I don't need dynamic alloc/free I could reserve some memory by simply incrementing the lower address.
You might like to take a look at my multi port serial driver as I have just gone thru some of those issues. The top object defines the port_controls and port_parameters for the 16 ports. Also the buffers are defined in the top object. This information is passed to each lower object (the same lower object can be referenced multiple times depending on the number of ports used. And lastly there is a cog that runs the actual I/O code that supports all ports with the smart pins.
Yes, it's just hypothetical. I don't think it is ideal, though.
It would be easy to reserve two longs in lower space for free-memory pointer purposes. The only problem is that they'd need to be operated on atomically, which requires the use of a LOCK.
All things considered, just having the parent object allocate an appropriately-sized VAR buffer, based on documentation from the child object, isn't that bad.
It would be easy to reserve two longs in lower space for free-memory pointer purposes. The only problem is that they'd need to be operated on atomically, which requires the use of a LOCK.
Depends. If I just do it once during the startup phase and never change anything later then it might be OK without using locks. Of course if somebody needs to allocate memory dynamically and concurrently with multiple cogs then locks are mandatory.
All things considered, just having the parent object allocate an appropriately-sized VAR buffer, based on documentation from the child object, isn't that bad.
Yes, it's perfectly OK for me. But for not so experienced "Lego programmers" or for very big projects where it becomes difficult to keep track of everything encapsulation becomes more important. And if the idea of reserving two longs is good then it would be best to do it as soon as possible before everyone invents his own method.
@ManAtWork As you mentioned you need the user defined size at compile time. Hence, you could use one/many constants in an object with a predefined name, which the programmer has to create in the project folder. So, you can refer this constant in your filter object and use it at compile time to allocate some buffers as needed.
This would require that your filter object has to use a Spin2 object with the predefined name which would be provided as a template and should not be located in the library folder. There you could place all constants to be specified by the user to configure your filter object without need editing the filter object itself.
You could mention that it will be complicated for use. But I think this is the only way to do it if you want provide a generic filter object which can be used without any changes by a user. The attached archive shows an example.
A constraint of this concept is, that only one filter configuration can be used at runtime.
The buffers for the serial drivers are just arrays of bytes, right? The internal state info like buffer pointers, flags, modes etc. are stored in the cog that processes the pins. My idea was to store the internal state together with the sample buffer in hub ram. It's easy to swap the descriptor data into the cog with a SETQ+RDLONG, only read the tap values from the big buffer, process the filtering ans swap out the descriptor, again.
Ok, I could declare a constant "DescriptorSize" so that the caller can add this to the raw sample buffer size. It would just look cleaner if that was hidden inside the implementation.
@Kaio,
this is basically a good idea. But it reminds me of some C libraries that make extensive use of #define-macros not only to define some numerical parameters but to totally change the internal behaviour to adapt to different target CPUs, for example. This might be a clever trick but if the information is scattered over too many files it makes it almost impossible to find out where the actual definition of some symbol is. If it's not well documented then all you can do is to use the global file search just to find out that what you have found is not the actual definition but just expands to another macro defined elsewhere. I hate that.
When working with big software projects, it's all about interfaces. Information should be kept as local as possible to avoid confusion. So the best scenario is that all parameters for configuration are passed as arguments to the Start or Init() function and the implementation should do whatever it needs silently. If that is not possible then there should be some clear documentation that explains what the caller must do.
Cannot work out how to split the quotes in the new forum :(
Yes, the serial buffers are bytes.
Both port_control (16 bytes storing 1-bit = active, 1-bit = tx/rx, 6-bits = pin#) are in hub (and read in by SETQ), and are followed by port_parameters (2-16 sets of 4 longs) which store 4 hub address pointers to the hub buffer for the port/pin (head, tail, start, end+1).
The driver cog needs access to all the above. The caller cog (could be cogs) need access to the relevant port_control and port_parameters plus obviously the actual relevant hub buffer(s).
I don’t have a mechanism to calculate how many port_parameters must be created so I use a CON which can be changed pre-compile to save hub space if less than 16 ports are required. Likewise, the buffer lengths can be changed by editing the con xxBUFn_SIZE’s.
I have yet to figure out (problematic with the compilers) how to specifically locate these controls/parameters/buffers in hub at physical addresses. This is necessary in my OS as some parts remain resident and operational (with appropriate pasm and spin cogs) while loading new separate binaries to implement os or user programs.
Does Spin2 still support _STACK and _FREE like on the P1 and if so how to get the absolute HUB address?
curious,
Mike
Mike, I need to check. I am not sure.