matrix_calc() documentation wrong; several SDK examples of matrix MOP fail

diablodale's icon

SDK examples fail to work and SDK documentation is wrong on how to handle `input` and `output` params to matrix_calc.

Compiled with SDK 6.1.4 and run on Max 7, that code fails. It does not get the matrix info, does not get data pointers. Therefore, the entire example fails.

### Setup

* Max 7 x64
* SDK 6.1.4
* VS Community 2019

### Repro

1. compile and test jit.simple

Probably also fails in jit.transpose, jit.print, and any of the other MOP examples that follow the documented SDK method.

### Result

Both fail to get the matrix info, and the data pointer. The entire example fails.

### Expected

Examples to work. Don't publish examples that don't work.
SDK documentation in PDF and their online HTML files to correctly represent actual working functionality.

### Notes

> Another example of dynamic binding inside the matrix_calc method is that the list elements might be instances of jit_mop_io, rather than instances of jit_matrix. However, since Jitter uses dynamic binding and the jit_mop_io object is a "decorator" class for jit_matrix, all corresponding methods are passed on to the jit_matrix referenced by the jit_mop_io.

That is false. Methods like those to get the matrix_info, and data pointer fail when called on a jit_mop_io. And it is jit_mop_io that is being passed by Max 7 in these simple default scenario. I verified that by pseudocode `object_class(getindex(output,0))` and then inspecting that t_class

On 3 Jan 2007, jkc wrote something might be wrong in this forum post https://cycling74.com/forums/js-jit-print

> I noticed that jit.print's matrix_calc erroneously assumes that a matrix and not a linked list will be passed into the object

The jit.print example in the current SDK follows the same pattern as jit.simple. Same pattern as my own code. And same pattern as in the SDK documentation.

They are all failing. Therefore, something must be errant that is common across all four.

1. Was backwards compatibility broken somewhere along the Max 5,6,7,8 path?
2. Did the interfaces and behaviors of matrix_calc, mproc, jit_mop_io, etc change and therefore break the code of multiple examples in the SDK + the SDK docs themselves?
3. Is this known and nothing updated for 13 years? This is troublesome as wrong information continues to be proliferated.
3. Or ???!

### Workaround

Check the class type of the incoming object(s) passed to matrix_calc() with object_class(). And if a jit_mop_io, call `_jit_sym_getmatrix` and then continue on. That works in my brief tests.

Rob Ramirez's icon

Hi Dale, the 8.0.3 jit.simple sample project compiles and runs as expected, and matrix info and data pointers are retrieved as expected for both input and output matrices when testing in both Max 7 and Max 8, so I'm not sure what issues you are encountering. I'm not sure why your report states running with the Max 6.1.4 SDK but you're linked to the 8.0.3 SDK example.

Please try running the example using the 8.0.3 SDK from the link you provided.

Retrieving matrix data pointers and info structs from jit_mop_io objects as accurately described in the documentation is used throughout the Jitter codebase, so there must be something else going on in your projects.

diablodale's icon

Hi. I'm able to reproduce the problem. From what I can discern...the API (header interface) hasn't changed between the SDK versions. However, the ABI interface *has* changed in some layer between dev code and Max core.

What I did to repo it again was a bit more involved to use simplejit

1. Use simplejit from any 6,7,8 SDK. They're all the same except for whitespace and one param size that doesn't affect this repro.
2. Use the headers from https://github.com/Cycling74/max-api
3. Use the static linking libraries (maxapi.lib, etc.) from Max 6.1.4 SDK.
4. simplejit code has to be updated to remove "JIT_" on some constants, casting of void* <-> t_object*, jit_object_method->object_method, etc. Nothing worrysome.
5. Compile Windows x64 using cmake and VS 2019 x64 Community with current updates

What I'm seeing so far, is that code written with the newer "max-api" headers is not ABI compatible with previous SDK linking libraries. When matrix_calc() gets the inputs, object_method(getinfo), the t_jit_matrix_info struct does not contain valid information.

If I use the Max 6.1.4 headers with Max 6.1.4 libraries. simplejit works no codechange, no problems.
If I use the Max 7.3.3 headers with Max 7.3.3 libraries. simplejit works no codechange, no problems.

My intention with the cross-usage is to have the cleaner layout of the "max-api" headers, its namespace, etc. while restricting myself to compatibility with Max 6.x. I use 6 because I have customers across the 6,7,8 range and the later SDKs add nothing that I need. I code/compile/distrib one and it runs everywhere.

Attached are files you can use to repro the scenario and follow steps 1-5 above. The jit.simple in it is directly from the Max SDK 7.3.3 download + the changes from step 4 above. A cmake and test patch is included so you can quickly reconfig and build. Put a stop on line 121 of jit.simple.cpp and inspect the in_minfo struct. It won't have valid information.

simplejit-repro1.zip
zip 5.04 KB
SIMPLEJIT-REPRO1.ZIP

diablodale's icon

Checking back on this. Is the ABI in flux and binaries/header not able to be used across versions?

Joshua Kit Clayton's icon

Hi Dale,

In general, it is not wise to use newer headers than the minimum Max version you'd like to support. Our SDK is not generally structured for such legacy support. Almost anything built with the 6.1.x headers and SDK should work fine in Max 8 (a small number of caveats of feature changes might be unsupported, but there have been no ABI changes within a given OS/Architecture).

That said, thank you for bringing this to our attention as it points out some flaws that we'd like to address. The real reason you are encountering this is in flaws from the Max-API headers rather than using the Max-SDK headers. The Max-API headers are part of the Min-API and DevKit work and are not as road tested as the standard SDK. Despite some of the language on the Max-API github page, the "Legacy SDK" should be considered primary.

The limitation is that the Max-API headers, rather than calling object_method_imp() like the Max SDK does, uses c++ style function overloading, and specifically calls object_getmethod() to establish which method to call. With Jitter MOP IO objects, there is an esoteric feature of our object system called decorators, which enable one object to be added to an existing object to provide an additional set of methods. object_getmethod() does not support retrieving the correct "getinfo" methods from the objects they decorate. object_method() and object_method_imp() from the standard SDK on the other hand correctly resolves them.

This is an oversight in the object_getmethod() implementation, which will be corrected in a future version of Max. However, that doesn't help your legacy context if you still are determined to use the Max-API. For that, the better solution is to change the Max-API implementation to call object_method_imp() rather than calling the results of object_getmethod(). For example the following and all similar

    inline void* object_method(t_object* target_object, t_symbol* method_name) {
        method m = object_getmethod(target_object, method_name);
        if (m)
            return m(target_object);
        else
            return nullptr;
    }

Should be changed to

    inline void* object_method(t_object* target_object, t_symbol* method_name) {
        return object_method_imp(target_object, method_name, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
    }

We will also make such a change for the Max-API in the future.

To summarize:

  1. The issue here is not ABI related, but due to inconsistencies in the way that object_getmethod() and object_method_imp() support decorator objects, and the usage of these separate paths in the Max-API (breaks decorator usage) and Max-SDK (supports decorator usage).

  2. The most supported and recommended way for you to proceed would be to use the headers included with the binary library you are linking with (Max 6.1.3 SDK in this case).

  3. If you still want to venture into territory that is unsupported, for your particular development needs/preferences, you may make the modifications to the Max-API implementation of object_method() to use object_method_imp() suggested above.

  4. We will fix object_getmethod() and the Max-API object_method() implementation to support decorator usage in future Max and Max-API releases

I hope that this clarifies this confusing situation a bit. Please let us know if this doesn't solve your needs at this time. If it is something reasonable for us to support or follow up with relevant information, we will do so.

Thanks,
Joshua

diablodale's icon

Got it. I confirmed using object_method_imp() corrects the errant behavior. Glad we have a clear resolution.

Personally, I updated the headers to use C74_VARFUN() almost as Max 6.1.4 headers. Doesn't have type checking, yet guarantees the call to _imp() will not be wrapped with a function. Use of `inline` is only a hint and the compiler may ignore it; discussed at https://github.com/Cycling74/min-api/issues/152

Cheers!