Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/large primitive arrays #489

Open
wants to merge 10 commits into
base: master
Choose a base branch
from

Conversation

jarrodmoldrich
Copy link
Contributor

@akbertram This is the first draft of the implementation. It compiles and passes the current test suite, but I have yet to write tests for the new functionality, or even try it with my production code, but just wanted to get your feedback. Would be much appreciated if you could take a look 🙂

Related to this issue

@jarrodmoldrich jarrodmoldrich force-pushed the feature/large-primitive-arrays branch 4 times, most recently from 56407d8 to ee6ae03 Compare November 12, 2019 00:58
@jarrodmoldrich
Copy link
Contributor Author

@akbertram Tests have been added, and are passing locally. I'll now try and integrate the modified plugin with our build.

@jarrodmoldrich
Copy link
Contributor Author

@akbertram Is there any easy way for me to install a development build of the maven plugin in my local maven repository along with all the requisite dependencies? I'd like to test these code changes against our problematic C library. Any help would be appreciated.

@akbertram
Copy link
Member

akbertram commented Nov 13, 2019 via email

@jarrodmoldrich
Copy link
Contributor Author

Thank you, @akbertram , I have started down the path of option #1, and I'm manually building a pom.xml that's partially taken from the old version. I'm noticing there's a number of repackaged libraries that I don't have access to. Am I going down the right path? Is there some way I can get or generate these dependencies?

        guava:              "org.renjin:renjin-guava:28.0b",
        asm:                "org.renjin:renjin-asm:5.0.4b",
        soot:               'org.renjin:renjin-soot:2.9.198'

@jarrodmoldrich
Copy link
Contributor Author

Sorry @akbertram , also learning Maven as I go. Added the nexus repository to the pom and now that appears to be working fine... until the next problem :)

@akbertram
Copy link
Member

Well I'd say you're a pretty quick learner!!

}
ByteBuffer buffer = ByteBuffer.allocate(elements.size() * byteSize).order(ByteOrder.LITTLE_ENDIAN);
for (GimpleConstructor.Element element : elements) {
converter.append(buffer, element);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The constructor is not guaranteed to contain a GimpleConstructor.Element for each array element. Sometimes GCC will emit 'jagged' arrays with uninitialized elements, mostly from Fortran code. See here:

int elementIndex = ((GimpleIntegerConstant) element.getField()).getValue().intValue();

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. Would inserting 0 values for these values be acceptable?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes that's correct. I've extracted the normalizeArrayElements() logic to GimpleConstructor so that it can be used outside of ArrayTypeStrategy.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No worries, judging by your latest commits it looks like you've got it covered :)

@akbertram
Copy link
Member

I am doing some testing on R packages that I know suffer from the problem of large array initializers, it's looking good!

@jarrodmoldrich
Copy link
Contributor Author

jarrodmoldrich commented Nov 16, 2019

@akbertram I've noticed something with the generated code. I'm compiling into a JAR and then looking at the decompiled version in Android Studio/IntelliJ which in turn uses 'FernFlower', and I'm seeing that static arrays are doubled up in the output:

    public static Ptr c_X$3467 = MixedPtr.malloc(7045120);
    public static Ptr $SomeClass$c_X = MixedPtr.malloc(7045120);

These arrays are quite massive to begin with, and Matlab C code loves to pre-allocate them on the stack. This means whenever the .class file is loaded into memory, these arrays both allocated, when really only one is used. Do you have any idea why it would duplicate these variables?

It's causing lots of OOM errors, so it's a deal breaker for using Renjin in our project, but it's so close to what we need. If there's an easy solution to removing these redundant arrays, I'd be grateful to hear it :)

EDIT:

This is a small snippet that should elicit the problem

void someFunction()
{
  static double someStaticArray[123456];
}

@jarrodmoldrich
Copy link
Contributor Author

jarrodmoldrich commented Nov 16, 2019

@akbertram I've worked around the issue by isolating all the function-scoped static large arrays and putting them in their own class - this allows me to allocate in one go, and moving the static vars outside of functions seems to remove the redundant entry.

Another issue of concern is that the gcc-bridge runtime, as it stands, cannot be used with Android < API 26 (i.e., before 'Oreo') as the MethodHandle.invoke and invokeExact methods are not available. I had to explicitly remove them to use the library like this

Do you think it would be possible to have conditional compilation for an Android build of the library? Or perhaps to use a different mechanism that is more compatible?

It would be nice if in the end our builds can target a sanctioned downstream release - but at the moment it looks like we need to target this custom branch.

@akbertram
Copy link
Member

Re: excessive memory allocation. IIRC, the extra global variable (suffixed with $NNNN) are actually emitted by GCC as part of its strategy for compiling C functions with static local variables. A static local variable is not allocated on the stack, which is why some gymnastics on GCC's part are required.

I think the real problem lies with how we initializes global arrays. In principle, arrays are value types, so assignment involves copying. We naively use the assignment machinery to initialize global variables, so this involves 1) allocating the global variables, 2) constructing an initial value (another array), and 3) copying the initial value array into the allocated global variable.

This is trivially solved by splitting up the code generation for assignment and initialization, at least for arrays. This has been on my list for awhile, so I will see if quickly fix this.

You might also want to look at why you are seeing MixedPtrs in the output. MixedPtrs are used as a last resort when Renjin either can't figure out what type of data will be stored in an array or structure, or if a mix of primitives and pointers are stored in an array/structure. Note that this process relies on some basic static analysis as you can't trust always trust the declared type of arrays/structs. If Renjin is falling back to MixedPtrs when primitive arrays should clearly be used, I'd be interested in taking a looking at the source code.

@akbertram
Copy link
Member

@jarrodmoldrich If it's just a problem with the runtime classes, we could probably configure gradle to produce a gcc-runtime-android.jar that simply excludes classes that rely on MethodHandles. The PosixThreads class for example, you're unlikely to need for your project.

The GimpleCompiler also uses MethodHandles to compile code that has function pointers. That's a much harder problem to work around, but it's probably not relevant for matlab-generated code.

@jarrodmoldrich
Copy link
Contributor Author

Re: MixedPtr

MixedPtr is a rare case, and since I've re-organised the code I can only see one instance of it. It is an array of structs:

   typedef double real_T;
   typedef struct {
      real_T re;
      real_T im;
   } creal_T;
   extern creal_T B1[220160];

Re: MethodHandle

The MethodHandle class itself is available on Android, but I think specifically invoke and invokeExact are not provided until Oreo. It only matters for the runtime, or if these methods are called by the generated Java code. So PosixThreads and FunPtrCallGenerator are the only two exceptional classes that I can see.

@jarrodmoldrich
Copy link
Contributor Author

@akbertram Just FYI, the MethodHandle errors were caused because my submodules gradle file was missing:

     compileOptions {
          sourceCompatibility JavaVersion.VERSION_1_8
          targetCompatibility JavaVersion.VERSION_1_8
     }

It compiles fine now. The last few days I've been playing with the transpiled code, and observed a couple of things. Firstly, it's possible that the code isn't quite correct. The original code is quite complex and it's possible that it's exercising the transpiler in a way that's eliciting undiscovered buggy use-cases. The other thing is that the code is several times slower, from ~5 sec to ~20+, though some of that time might be due to JIT compilation on the first run.

We've abandoned the JVM line of development for now, so I'm not sure I'll get to the bottom of the correctness issue - I might have a look in my own time though. In terms of the performance issues, my naive question would be whether many of the function calls could be inlined? I can't see a mechanism for warming up untouched code in Android, beyond actually making a dummy function call - but this is what I'd planned to do if there wasn't a good fix for code bloat at transpilation time. I'd be curious to know if there are any simple solutions?

Beyond all that, what are the next steps to making this PR merge-able? 🙂

@mjkallen
Copy link
Contributor

mjkallen commented Mar 6, 2020

@akbertram can you confirm that all changes in this PR have been merged into master as part of #498?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants