Is there a way to control codegen function prototypes

Is there any way to see the internals of why codegen decided to define a prototype the way it did? Or to force a particular function prototype for non-entry point functions?
I have a fairly complex function, let's call it functionA, which calls several internal functions.
outStruct = functionA( struct1, double1, double2, struct2)
When I use codegen to generate functionA as an entry point, the prototype looks the way I'd expect it to:
extern void functionA( const struct1_T *struct1, double double1, double double2, const struct2_T *struct2, struct0_T *outStruct)
Now, I want to add a second function, test_functionA, which coder.load's some test structures from a file, then calls functionA on the data.
function test_functionA()
coder.inline('never');
inputs = coder.load('data');
struct1 = coder.ignoreConst(inputs.struct1);
double1 = coder.ignoreConst(inputs.double1);
double2 = coder.ignoreConst(inputs.double2);
struct2 = coder.ignoreConst(inputs.struct2);
o = functionA(struct1,double1,double2,struct2);
% then I print elements of o, so o won't get optimized out
What I was hoping -- and what happens in my simple test cases trying to reproduce this -- was that test_funcitonA C++ code does in fact call the functionA using the already generated library code. But what actually happens, is that test_functionA calls a copy of the function, with a totally different prototype
void b_functionA(const double struct1_field1[8], double double2, double struct2_field1, struct0_T *outStruct)
Coder has obviously folded some constants and folded constants within the structures into separate arguments, even though I told it to ignoreConst.
Is there some way to force a particular function prototype in the generated code? I'd have thought that since functionA is an entry point with specified args, that test_functionA would use that same definition, rather than some strange optimized specialization.
Possibly related ... functionA lives in a package. When I codegen test_functionA, the copy of functionA that it creates lives in its own namespace. I can't seem to reproduce that with simple files.
Is there a way to sort of debug what codegen thinks it is dealing with ? Like some sort of AST output of the code before it starts to optimize?

4 Comments

I'm not sure if you've used the Code Generation Report before, but that has the extent of debugging information that is available for consumption. The various metadata this report shows framed against one's MATLAB code speaks to the "what codegen thinks it is dealing with" side of things.
When you see multiple "copies" of functions, these are denoted within the report with the notation "functionA > n" wherein the integer "n" is used to distinguish between the individual specializations of the "functionA". When you successively select each such function entry in the MATLAB Source pane, the report will attempt to temporarily highlight "differences" between the specializations on top of the corresponding MATLAB code. These differences often boil down to constant values or varying classes and/or sizes for inputs so manually inspecting the input variables either by way of the report's tooltips over MATLAB code or its variable table is also viable.
I have used the report, and have trace enabled, but it is of no help. When I hover over the matlab function input arguments, they are all the correct type and size.
The problem isn't specialization from type or size, but simply that coder has decided to completely optimize away some of the variables. Despite wrapping all the arguments when calling the functionA in coder.ignoreConst.
That may be what I need help understanding:
1) in test_functionA, I use coder.load() to load in some test data
2) I then create some structures based on that data
3) I then call functionA( coder.ignoreConst(struct1), etc)
Does coder.ignoreConst apply to an entire nested structure? Or, do I need to call it on each non-const field of the structure?
Does the fact that some of the data is starting from a coder.load force coder to consider some variables as const even though I am telling it to ignoreConst?
Is there some way to see what coder thinks is const or not? (If I directly use coder.Constant I can create const that show up in the code generation report as such, but the constants that coder determines on its own don't seem to show that status).
I've been banging my head against the wall fighting coder on this for the last few days, I ended up just using ceval in the test code to force it to use the proper function call. I'd really rather be able to create test code from matlab that just works though.
Hi Jon,
Three thoughts here:
  1. One possible thing to try is to disable the compiler optimization that splits structs into individual fields:
cfg = coder.config('lib'); % or whatever config you use
cfg.EnableStructExplosion = false;
codegen ...... -config cfg .....
I don't expect this to fully resolve the issue but it should made the prototype of functionA to be more similar to what you expect.
2. Can you speak more about your end goal, why do you need the same functionA to be an entry point and also invoked from test_functionA? If you explain the intent, maybe someone can suggest a different solution to get there.
3. To fully resolve the issue, we need figure out why codegen thinks that the version of functionA that you already have (the entry point) is not good enough to be called from test_functionA. Something makes it unable to reuse the function in that 2nd call site, and makes it to create a new one. This is hard to diagnose without a specific example. If you can attach reproduction steps (.m files and doit.m that calls codegen) we'll take a look. If you prefer to not to post your code publicly, please contact the technical support, the support request will get routed to the coder team and we can debug further (we still need repros though).
Thanks,
Denis
MATLAB Coder dev team
Well, problem isn't totally solved, I have things working today.
There must have been something funny with my paths, since, today the main problem of agressive struct explosion went away today even without using EnableStructExplosion.
But, for whatever reason, one of my non-constant uint32 scalars was still being optimized away in the non-entry point function. This despite wrapping each argument with coder.ignoreConst in my test_functionA. (For what it is worth, this scalar, after some manipulation, was being used to index into other variables).
Once I had the rest of the prototype matching, I was able to simply force that uint32 to change within the test_functionA, to avoid it being optimized away, and that seemed to work now.
1) I don't see EnableStructExplosion in the documentation (or in the autocomplete on the cfg object), but that seems like it could be useful to a lot of people. For that matter, there seem to be some properties which
2) Just for completeness, on the use case: I am integrating some new algorithms, written in matlab, with an existing c++ project (written in c++). Since the inputs are somewhat complicated, I wanted to also provide some "unit test" code to run the new function on the target, with realistic inputs, so we could check timing on the target before we get too far down the road. So, test_functionA was simply loading in some inputs (coder.load -- which turn into compile time constants in generated code), and I wanted to call functionA with those inputs. If I allowed StructExplosion, the non-entry point call was getting exploded, while the entry point version was not.
3) It seems that in the end, for some reason coder was ignoring my ignoreConst on one particular parameter. I was able to get it working by putting that parameter in a loop, so it wasn't actually constant within test_functionA. I'll have to spend some time stripping things out to get to a minimum reproducible scenario.
Thanks for your help!

Sign in to comment.

Answers (0)

Products

Release

R2021a

Asked:

Jon
on 3 May 2023

Commented:

Jon
on 5 May 2023

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!