Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions gtwrap/matlab_wrapper/mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ class CheckMixin:
"char",
"unsigned char",
"size_t",
"uint64_t",
"Key", # This is an alias for a uint64_t
)
# Ignore the namespace for these datatypes
Expand Down
2 changes: 2 additions & 0 deletions gtwrap/matlab_wrapper/wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ def __init__(self,
'ConstMatrixView': 'double',
'int': 'numeric',
'size_t': 'numeric',
'uint64_t': 'numeric',
'Key': 'numeric',
'bool': 'logical'
}
Expand All @@ -64,6 +65,7 @@ def __init__(self,
'char': 'char',
'unsigned char': 'unsigned char',
'size_t': 'int',
'uint64_t': 'numeric',
'int': 'int',
'double': 'double',
'Point2': 'double',
Expand Down
108 changes: 53 additions & 55 deletions matlab.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,14 @@ extern "C" {
#include <mex.h>
}

#include <cstdint>
#include <limits>
#include <list>
#include <set>
#include <sstream>
#include <streambuf>
#include <string>
#include <type_traits>
#include <typeinfo>

using namespace std;
Expand Down Expand Up @@ -122,13 +124,33 @@ void checkArguments(const string& name, int nargout, int nargin, int expected) {
// wrapping C++ basic types in MATLAB arrays
//*****************************************************************************

// default wrapping throws an error: only basic types are allowed in wrap
template <typename T>
struct IsMatlabSizeOrKeyScalar
: std::integral_constant<bool, std::is_same<T, size_t>::value ||
std::is_same<T, uint64_t>::value> {};

// size_t and uint64_t can be the same C++ type, so handle both through the
// primary template rather than potentially duplicate explicit specializations.
template <typename Class>
mxArray* wrap(const Class& value) {
mxArray* wrapDefault(const Class& value, std::true_type) {
mxArray *result = scalar(mxUINT32OR64_CLASS);
*static_cast<Class*>(mxGetData(result)) = value;
return result;
}

// Unsupported types retain the generic runtime error.
template <typename Class>
mxArray* wrapDefault(const Class&, std::false_type) {
error("wrap internal error: attempted wrap of invalid type");
return 0;
}

// Default wrapping supports size/key scalars and rejects all other types.
template <typename Class>
mxArray* wrap(const Class& value) {
return wrapDefault(value, IsMatlabSizeOrKeyScalar<Class>());
}

// specialization to string
// wraps into a character array
template<>
Expand Down Expand Up @@ -160,16 +182,6 @@ mxArray* wrap<bool>(const bool& value) {
return result;
}

// specialization to size_t but skip Win64 size check & CUDACC check
#if (!defined(_WIN64) && !defined(__LP64__)) || defined(__CUDACC__)
template<>
mxArray* wrap<size_t>(const size_t& value) {
mxArray *result = scalar(mxUINT32OR64_CLASS);
*(size_t*)mxGetData(result) = value;
return result;
}
#endif

// specialization to int
template<>
mxArray* wrap<int>(const int& value) {
Expand All @@ -178,14 +190,6 @@ mxArray* wrap<int>(const int& value) {
return result;
}

// specialization to gtsam::Key which is an alias for uint64_t
template<>
mxArray* wrap<uint64_t>(const uint64_t& value) {
mxArray *result = scalar(mxUINT32OR64_CLASS);
*(uint64_t*)mxGetData(result) = value;
return result;
}

// specialization to double -> just double
template<>
mxArray* wrap<double>(const double& value) {
Expand Down Expand Up @@ -261,14 +265,40 @@ mxArray* wrap_enum(const T x, const std::string& classname) {
// unwrapping MATLAB arrays into C++ basic types
//*****************************************************************************

// default unwrapping throws an error
// as wrap only supports passing a reference or one of the basic types
// Check for 64-bit, as Mathworks says mxGetScalar only good for 32 bit
template <typename T>
T unwrap(const mxArray* array) {
T myGetScalar(const mxArray* array) {
switch (mxGetClassID(array)) {
case mxINT64_CLASS:
return (T) *(std::int64_t*) mxGetData(array);
case mxUINT64_CLASS:
return (T) *(std::uint64_t*) mxGetData(array);
default:
// hope for the best!
return (T) mxGetScalar(array);
}
}

// size_t and uint64_t share this path whether they are aliases or distinct.
template <typename T>
T unwrapDefault(const mxArray* array, std::true_type) {
checkScalar(array, "unwrap<size_t or uint64_t>");
return myGetScalar<T>(array);
}

// Unsupported types retain the generic runtime error.
template <typename T>
T unwrapDefault(const mxArray*, std::false_type) {
error("wrap internal error: attempted unwrap of invalid type");
return T();
}

// Default unwrapping supports size/key scalars and rejects all other types.
template <typename T>
T unwrap(const mxArray* array) {
return unwrapDefault<T>(array, IsMatlabSizeOrKeyScalar<T>());
}

/// @brief Unwrap from matlab array to C++ enum type
/// @tparam T The C++ enum type
/// @param array Matlab mxArray
Expand Down Expand Up @@ -299,20 +329,6 @@ string unwrap<string>(const mxArray* array) {
return str;
}

// Check for 64-bit, as Mathworks says mxGetScalar only good for 32 bit
template <typename T>
T myGetScalar(const mxArray* array) {
switch (mxGetClassID(array)) {
case mxINT64_CLASS:
return (T) *(std::int64_t*) mxGetData(array);
case mxUINT64_CLASS:
return (T) *(std::uint64_t*) mxGetData(array);
default:
// hope for the best!
return (T) mxGetScalar(array);
}
}

// specialization to bool
template<>
bool unwrap<bool>(const mxArray* array) {
Expand Down Expand Up @@ -341,24 +357,6 @@ int unwrap<int>(const mxArray* array) {
return myGetScalar<int>(array);
}

// specialization to gtsam::Key which is an alias for uint64_t
template<>
uint64_t unwrap<uint64_t>(const mxArray* array) {
checkScalar(array,"unwrap<uint64_t>");
return myGetScalar<uint64_t>(array);
}

// specialization to size_t; omit it on Win64 because size_t is uint64_t there
// and would duplicate unwrap<uint64_t>. The __CUDACC__ case intentionally
// bypasses the Win64 guard when compiling with CUDA.
#if (!defined(_WIN64) && !defined(__LP64__)) || defined(__CUDACC__)
template<>
size_t unwrap<size_t>(const mxArray* array) {
checkScalar(array, "unwrap<size_t>");
return myGetScalar<size_t>(array);
}
#endif

// specialization to double
template<>
double unwrap<double>(const mxArray* array) {
Expand Down
2 changes: 2 additions & 0 deletions tests/fixtures/matlab_integer_aliases.i
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
size_t roundTripSizeT(size_t value);
uint64_t roundTripUint64(uint64_t value);
46 changes: 46 additions & 0 deletions tests/test_matlab_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,52 @@ def test_class(self):
actual = osp.join(self.MATLAB_ACTUAL_DIR, file)
self.compare_and_diff(file, actual)

def test_size_t_round_trip(self):
"""Generated size_t wrappers use alias-safe scalar conversions."""
file = osp.join(self.INTERFACE_DIR, 'matlab_integer_aliases.i')

wrapper = MatlabWrapper(
module_name='matlab_integer_aliases',
top_module_namespace=['gtsam'],
ignore_classes=[''],
)
wrapper.wrap([file], path=self.MATLAB_ACTUAL_DIR)

cpp_file = osp.join(self.MATLAB_ACTUAL_DIR,
'matlab_integer_aliases_wrapper.cpp')
with open(cpp_file, 'r', encoding='UTF-8') as generated_file:
cpp_content = generated_file.read()

self.assertIn('size_t value = unwrap< size_t >(in[0]);', cpp_content)
self.assertIn(
'out[0] = wrap< size_t >(roundTripSizeT(value));',
cpp_content)
self.assertIn('uint64_t value = unwrap< uint64_t >(in[0]);',
cpp_content)
self.assertIn(
'out[0] = wrap< uint64_t >(roundTripUint64(value));',
cpp_content)
self.assertNotIn('unwrap_shared_ptr< uint64_t >', cpp_content)
self.assertNotIn('wrap_shared_ptr(std::make_shared<uint64_t>',
cpp_content)

uint64_file = osp.join(self.MATLAB_ACTUAL_DIR, 'roundTripUint64.m')
with open(uint64_file, 'r', encoding='UTF-8') as generated_file:
uint64_content = generated_file.read()
self.assertIn("isa(varargin{1},'numeric')", uint64_content)

matlab_header = osp.join(self.TEST_DIR, '..', 'matlab.h')
with open(matlab_header, 'r', encoding='UTF-8') as header_file:
header_content = header_file.read()

self.assertIn('struct IsMatlabSizeOrKeyScalar', header_content)
self.assertIn('std::is_same<T, size_t>::value', header_content)
self.assertIn('std::is_same<T, uint64_t>::value', header_content)
self.assertNotIn('mxArray* wrap<size_t>', header_content)
self.assertNotIn('size_t unwrap<size_t>', header_content)
self.assertIn('attempted wrap of invalid type', header_content)
self.assertIn('attempted unwrap of invalid type', header_content)

def test_enum(self):
"""Test interface file with only enum info."""
file = osp.join(self.INTERFACE_DIR, 'enum.i')
Expand Down
Loading