How can I ensure an emxArray is correctly allocated in generated C code?

1k views Asked by At

I have the following .m file (named testmemoryallocation.m), targeted for code generation in Matlab Coder. This is just a test file demonstrating the concept, of course.

function output = testmemoryallocation(dim) %#codegen
%TESTMEMORYALLOCATION Tests allocation of large 3D arrays

output = coder.nullcopy(zeros([dim, dim, dim]));
output(:) = 5;

end

I build this using the following build script (default settings from the Coder app)

%% Create configuration object of class 'coder.CodeConfig'.
cfg = coder.config('lib','ecoder',false);
cfg.GenerateReport = true;
cfg.GenCodeOnly = true;
cfg.HardwareImplementation = coder.HardwareImplementation;
cfg.HardwareImplementation.ProdIntDivRoundTo = 'Undefined';
cfg.HardwareImplementation.TargetIntDivRoundTo = 'Undefined';

%% Define argument types for entry-point 'testmemoryallocation'.
ARGS = cell(1,1);
ARGS{1} = cell(1,1);
ARGS{1}{1} = coder.typeof(0);

%% Invoke MATLAB Coder.
codegen -config cfg testmemoryallocation -args ARGS{1}

The resulting generated C code from this build process looks like this:

/*
 * testmemoryallocation.c
 *
 * Code generation for function 'testmemoryallocation'
 *
 */

/* Include files */
#include "rt_nonfinite.h"
#include "testmemoryallocation.h"
#include "testmemoryallocation_emxutil.h"

/* Function Definitions */
void testmemoryallocation(double dim, emxArray_real_T *output)
{
  int i0;
  int loop_ub;

  /* TESTMEMORYALLOCATION Tests allocation of large 3D arrays */
  i0 = output->size[0] * output->size[1] * output->size[2];
  output->size[0] = (int)dim;
  emxEnsureCapacity((emxArray__common *)output, i0, (int)sizeof(double));
  i0 = output->size[0] * output->size[1] * output->size[2];
  output->size[1] = (int)dim;
  emxEnsureCapacity((emxArray__common *)output, i0, (int)sizeof(double));
  i0 = output->size[0] * output->size[1] * output->size[2];
  output->size[2] = (int)dim;
  emxEnsureCapacity((emxArray__common *)output, i0, (int)sizeof(double));
  loop_ub = (int)dim * (int)dim * (int)dim;
  for (i0 = 0; i0 < loop_ub; i0++) {
    output->data[i0] = 5.0;
  }
}

/* End of code generation (testmemoryallocation.c) */

The question is: is there a way in MATLAB Coder (in the testmemoryallocation.m file) to verify that the memory was actually allocated? The memory allocation occurs in the emxEnsureCapacity function, which itself does not return information about a successful allocation (as far as I can tell.) I'd like to be able to gracefully exit from the calling function if there isn't enough system memory to complete the process. For instance, I'd like to add a result output to testmemoryallocation that indicated if an error occurred allocating memory. Is there a way to do this?

Really, the question comes down to: is there a way to access emxArrays to test that the data field of the struct is not null. Perhaps a .c file called with coder.ceval? Is there a way to instruct coder.ceval to pass an emxArray type instead of base types (double or int) so the underlying .c code can be written to interact with the structure?

EDIT: The excellent answer accepted addresses both this question and an underlying bug in (2015a) Coder in the generated c code. In the _emxutil file, the emxEnsureCapacity function has a possible infinite loop if the requested number of elements is above intmax/2.

2

There are 2 answers

1
Alexander Bottema On BEST ANSWER

It's good to see that you're making progress. Regarding the emxEnsureCapacity issue it is unfortunate and it is a bug. I'll take care of it to ensure we fix it in a forthcoming release. Meanwhile, there's a way of patching the generated source code. For config objects there's the 'PostCodeGenCommand' option which is executed/evaluated after the C code has been generated. This allows you to apply a patch before it gets compiled. For example,

cfg.PostCodeGenCommand = 'mypatch';

and then 'mypatch.m' is:

function mypatch

cfiles = all_cfiles([pwd filesep 'codegen']);
for i = 1:numel(cfiles)
    cfile = cfiles{i};
    if strcmp(cfile(end-9:end), '_emxutil.c')
        text = fileread(cfile);
        text = regexprep(text, '(while \(i < newNumel\) \{\s+i <<= 1; (\s+\})', '$1 if (i < 0) i = 0x7fffffff; $2');
        filewrite(cfile, text);
        disp(cfiles{i});
    end
end

function filewrite(filename, text)
f = fopen(filename, 'w');
fprintf(f, '%s', text);
fclose(f);

function files = all_cfiles(d)
files = {};
fs = dir(d);
for i = 1:numel(fs)
    if (fs(i).name(1) == '.')
        continue
    end
    if fs(i).isdir
        files = [files all_cfiles([d filesep fs(i).name])];
    else
        if strcmp(fs(i).name(end-1:end), '.c')
            files{end+1} = [d filesep fs(i).name];
        end
    end
end

Regarding passing 'emxArray' to an external C function (with coder.ref() in a coder.ceval() call) it is somewhat problematic. This is because the 'emxArray' representation may or may not exist depending on how big the matrix is. There's also a threshold (in the config object) that enables you to tell when to switch to "full" emxArrays or keep them as "upperbound allocated" variables (which on call boundaries are split into data and size as separate variables.) There are even more representations for variable size arrays (e.g. if you have upperbound variables in structures.) This is the main reason why we don't have a direct interface for emxArrays as type compatibility could change based on these parameters. Thus, always extracting the data will make it type compliant. If you need to access the size, assuming 'x' is a vector here, then you can just pass the size explicitly:

coder.ceval('foo', x, int32(numel(x)));

Unfortunately, this doesn't allow you to change the size of 'x' inside 'foo'.

There's another thing you can do, but please contact me directly (email) and I'll tell you some more.

Alexander Bottema
Software Engineer
MATLAB Coder Team
Mathworks

0
Tony On

I've worked on this problem a bit, and I think I have a pretty good solution. I created a MATLAB function called isallocated, which takes as an input a double precision float array of variable size and outputs 1 if the array is allocated properly and 0 if not. If called in the MATLAB environment, it always returns 1.

function result = isallocated(inarray) %#codegen
%ISALLOCATED Returns true if the array memory is allocated properly.
%  Will check the array to verify that the data pointer is not null.

if coder.target('MATLAB')
    result = int32(1);
    return;
end

result = int32(0);
coder.cinclude('emxArray_helpers.h');
coder.updateBuildInfo('addSourceFiles', 'emxArray_helpers.c');
result = coder.ceval('isallocated_helper', coder.ref(inarray));

end

This function is just a wrapper around isallocated_helper, and the only trick here is that the coder.ref command will extract the pointer to the data array in inarray. This has the effect of getting the value of the data member of the emxArray structure.

The c function isallocated_helper is amazingly boring; it's just a test to see if the pointer is null. I'll put the code here for completeness:

// emxArray_helpers.c
// A set of helper functions for emxArray types

int isallocated_helper(double * arrayptr)
{
    if (arrayptr)
        return 1;
    return 0;
}

And that's all. This provides a nice test, and if I change the testmemoryallocation.m file to use it, it looks like:

function [result, output] = testmemoryallocation(dim) %#codegen
%TESTMEMORYALLOCATION Tests allocation of large 3D arrays

output = coder.nullcopy(zeros([dim, dim, dim]));
result = isallocated(output);
if result == 1
    output(:) = 5;
else
    output = zeros([1,1,2]);
end

end

In a snippet of the generated c code, we can see the test:

...
/* TESTMEMORYALLOCATION Tests allocation of large 3D arrays */
i0 = output->size[0] * output->size[1] * output->size[2];
output->size[0] = (int)dim;
output->size[1] = (int)dim;
output->size[2] = (int)dim;
emxEnsureCapacity((emxArray__common *)output, i0, (int)sizeof(double));

/* ISALLOCATED Returns true if the array memory is allocated properly. */
/*   Will check the array to verify that the data pointer is not null.   */
*result = isallocated_helper(&output->data[0]);
if (*result == 1) {
...