02 · Code Generation (MATLAB Coder)¶
Verification note
MATLAB Coder was not available in the environment used to write this page. Type inference rules, code generation restrictions, and generated-code structure below are documented, version-stable MATLAB Coder behaviors, hand-traced against the MathWorks documentation rather than run through the actual code generator.
MATLAB Coder translates a restricted subset of MATLAB code into standalone C/C++ source — code that compiles and runs without MATLAB installed, suitable for embedding in a larger application or deploying to hardware that can't run the MATLAB runtime.
Why not every MATLAB function is generatable¶
MATLAB is dynamically typed and permits things C fundamentally cannot represent without extra constraints: variables that change size or type mid-function, cell arrays of heterogeneous types, unbounded recursion depth. MATLAB Coder requires statically determinable types and sizes for every variable — the generated C code needs to know at compile time how much memory a variable needs.
function y = badForCodegen(x)
if x > 0
y = 5; % y is a scalar double here
else
y = [1 2 3]; % y is a 1x3 double here — TYPE-UNSTABLE, codegen fails
end
end
function y = goodForCodegen(x)
if x > 0
y = 5;
else
y = -5; % y is always a scalar double — type-stable
end
end
Coder analyzes types and sizes through the whole function; a variable whose class or dimensions can differ across branches like the first example fails with a type-mismatch error at code-generation time, even though the MATLAB function itself runs fine interpretively.
Generating code: codegen¶
function y = movingAverageFilter(x, windowSize) %#codegen
n = length(x);
y = zeros(1, n);
for i = 1:n
lo = max(1, i - windowSize + 1);
y(i) = mean(x(lo:i));
end
end
The %#codegen pragma is a hint to static analysis tools (and to
readers) that the function is intended for code generation, enabling
extra codegen-specific checks in the MATLAB Editor even before running
codegen.
-args supplies example inputs so Coder can infer concrete types and
sizes (zeros(1,100) tells it the first argument is a 1x100 double;
5 tells it the second is a scalar double) — Coder cannot infer types
from a bare function signature the way a compiled-language function
declaration would, since MATLAB source carries no type annotations
itself.
-config:lib generates a standalone C library (.c/.h plus a build
folder); other targets include -config:exe (standalone executable),
-config:dll, and -config:mex (a compiled MEX function callable from
MATLAB itself, primarily used to speed up a slow inner loop without
leaving the MATLAB environment).
Variable-size data¶
function y = flexibleSum(x) %#codegen
y = sum(x);
end
codegen flexibleSum -args {coder.typeof(0, [1 Inf], [false true])}
coder.typeof(0, [1 Inf], [false true]) declares the input as a 1xN
double where the second dimension is variable (Inf upper bound, true
flag) — this lets one generated function handle inputs of different
lengths at runtime, at the cost of dynamic memory allocation in the
generated code (versus a fixed-size input, which Coder can allocate
statically, avoiding malloc calls entirely — often preferred for
hard-real-time embedded targets).
MEX for acceleration inside MATLAB¶
A generated MEX function replaces the original .m function at the
call site with a compiled binary honoring the same signature — useful
for accelerating one identified performance bottleneck (found via the
Profiler, Module 04) without rewriting the surrounding MATLAB code, at
the cost of losing MATLAB's flexible dynamic typing for that one
function going forward.
tic; y1 = mandelbrotPixel(0, 0, 1000); toc; % interpreted MATLAB
tic; y2 = mandelbrotPixel_mex(0, 0, 1000); toc; % compiled MEX — often 10-100x faster for tight numeric loops
The speed-up is largest for code dominated by scalar loop overhead (exactly what interpreted MATLAB is slowest at and compiled C is fastest at) — a function already dominated by calls into optimized built-ins (matrix multiply, FFT) sees far less benefit, since those built-ins are already compiled code either way.
Supported vs. unsupported constructs¶
Generally supported: numeric arrays, fixed-structure structs,
classdef (value classes, with restrictions), most control flow,
most built-in math/matrix functions, fixed-size cell arrays (with
restrictions).
Generally unsupported or restricted: eval and other
runtime-code-generation constructs (fundamentally incompatible with
static compilation — there's no way to compile code that doesn't exist
yet), unbounded recursion (must have a statically provable bound or be
converted to iteration), most graphics/plotting functions (no display
exists on an embedded target), cell arrays of mixed, unbounded content,
containers.Map in some contexts (varies by target and release).
function y = recursiveFactorial(n) %#codegen
coder.inline('never'); % example of a codegen-specific directive
if n <= 1
y = 1;
else
y = n * recursiveFactorial(n - 1);
end
end
Coder directives like coder.inline, coder.varsize, and
coder.extrinsic give fine-grained control over generation decisions
that have no equivalent meaning in interpreted MATLAB — they only affect
the code-generation process itself.
coder.extrinsic: calling non-generatable functions¶
function plotDuringSimulation(x, y) %#codegen
coder.extrinsic('plot');
if coder.target('MATLAB')
plot(x, y); % only actually called when running in MATLAB, not in generated code
end
end
coder.extrinsic declares that a call (like plot, which has no
meaning without a display) should run in MATLAB itself rather than be
translated to C — useful for keeping diagnostic plotting or logging in
a function during development/testing without blocking code generation
for the rest of the function, though the call is skipped (or requires a
MATLAB runtime callback) in a truly standalone generated deployment.
Verifying generated code against the original¶
x = randn(1, 200);
yOriginal = movingAverageFilter(x, 5);
yGenerated = movingAverageFilter_mex(x, 5);
maxDiff = max(abs(yOriginal - yGenerated));
disp(maxDiff); % should be exactly 0 or at floating-point-noise level
Bit-for-bit (or near-identical, allowing for legitimate floating-point association differences) agreement between the interpreted and generated versions on a battery of test inputs is the standard way to validate that code generation preserved the intended behavior — essentially running the Module 09 test suite against both versions and comparing results.
How It Actually Works¶
MATLAB Coder does not interpret your .m code at all when generating C —
it performs static type inference over your entire function, working
out a fixed size and class for every variable at every point in the code
(this is why Coder-compatible MATLAB code must avoid changing a variable's
class or dimensions partway through a function — the interpreter tolerates
that dynamically, but a statically-typed C output cannot represent a
variable whose type changes at runtime). Once types and sizes are
resolved, Coder translates MATLAB semantics into equivalent C: a
vectorized MATLAB expression like y = sin(x) typically becomes an
explicit for loop over x's elements in the generated C, since C has no
built-in vectorized elementwise math — the "vectorization removes loop
overhead" story from Module 03 (Level 2) is specifically about MATLAB's
interpreter overhead, and doesn't apply the same way once you're
looking at compiled C, where a hand-written loop and a Coder-generated one
from vectorized code run at comparable native speed.
Fixed-size versus variable-size arrays make a real difference to the generated code's memory model: if Coder can prove an array's size never changes, it generates a stack- or statically-allocated fixed-size C array; if the size is data-dependent, Coder must generate dynamic (heap-allocating) memory-management code — which is why Coder-targeted MATLAB functions are often written to declare maximum array sizes explicitly, trading flexibility for the ability to avoid dynamic allocation entirely in the generated code, a hard requirement for many embedded targets.
Note: based on the documented static-typing and code-generation approach of MATLAB Coder; the toolchain is unavailable in this environment to compile and inspect generated C directly.
Practice¶
- Take the type-unstable
badForCodegenexample and fix it two different ways (uniform scalar output vs. uniform fixed-size vector output), explaining the type-stability requirement in your own words. - Write a codegen-ready function computing a fixed-size 5-tap FIR
filter and generate a
-config:mexversion; describe (since you can't run it here) what test you would run to confirm the MEX version matches the interpreted version numerically. - Explain why
coder.typeofwith anInfupper bound produces generated code that allocates memory dynamically, and why a hard real-time embedded target would generally prefer a fixed maximum size instead, even if it wastes some memory in the common case. - Identify three MATLAB language features you've used earlier in this course (Levels 1-3) that would need to be rewritten or avoided before a function using them could pass through MATLAB Coder, and explain the underlying static-compilation constraint each one violates.