Superbelko / ohmygentool

LLVM/Clang based bindings generator for D language
The Unlicense
38 stars 6 forks source link

How to debug "Segmentation Fault" with no warnings/errors? #8

Closed GavinRay97 closed 3 years ago

GavinRay97 commented 3 years ago

I'm trying to convert the headers for the JUCE C++ SDK for making audio plugins to D. Tried to do this with just gentool-config.json config but too many errors (the build/dependency is hell).

But I managed to use an amalgation tool to start from an entrypoint header and have it replace #include with file content recursively to generate a single-file.

https://github.com/rindeal/Amalgamate

Running the following on the JUCE sources: https://github.com/juce-framework/JUCE

$ ./amalgamate.exe -s -i ./modules/ ./modules/juce_audio_plugin_client/juce_audio_plugin_client.h juce_audio_plugin_client_amalgamated.h

Produces the below (minus manual removal of #pragma once and a handful of other redefined pragmas) https://gist.github.com/GavinRay97/b8bdb882a391e63cb7e2f73b247d4a6e

(Raw, it's huge): https://gist.githubusercontent.com/GavinRay97/b8bdb882a391e63cb7e2f73b247d4a6e/raw/87ddc1678079b7034b91229660a9ad160af0ed5a/juce_audio_plugin_client_amalgamated.h

Running it with this configuration:

{
  "$schema": "./gentool-config-schema.json",
  "version": 1,
  "input": {
    "std": "c++17",
    "paths": ["./juce_audio_plugin_client_amalgamated.h"],
    "includes": [],
    "system": [],
    "defines": [
      "_WIN32=1",
      "JUCE_WINDOWS=1",
      "JUCE_PLUGINHOST_VST3=1",
      "JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1"
    ],
    "cflags": []
  },
  "output": {
    "path": "./dlang/generated.d",
    "target": "dlang",
    "attr-nogc": false
  }
}

Maybe it's just too big/complex to be translated?

image

Superbelko commented 3 years ago

There could be some unhandled C++ language constructs and edge cases, and you might just hit one.

Github releases are without debug info, unfortuntely there is no practical way to publish debug builds due to it's size as it can reach 80 GB with debug data. The only way is to do debug build locally, but be aware building clang can take hours, and debug builds on Windows is particularly slow due to MS STL bounds checks and stuff.

I'll try this myself and see what's wrong since I already have debug build.

Superbelko commented 3 years ago

crashes on

CXXMethodDecl 0x24f68dd1338 <D:\prog\juce_audio_plugin_client_amalgamated.h:15239:9, line:15252:9> line:15239:14 referenced simplify 'void ()'

source

void simplify()
{
    for (int i = ranges.size(); --i > 0;) <--- here
    {
        auto& r1 = ranges.getReference(i - 1);
        auto& r2 = ranges.getReference(i);

        if (r1.getEnd() == r2.getStart())
        {
            r1.setEnd(r2.getEnd());
            ranges.remove(i);
        }
    }
}

Well... two possible issues, some weird logic with unary --i or lack of for loop increment expression.

GavinRay97 commented 3 years ago

Ah, thank you for looking into that for me. Much appreciated.

Seems like part of the issue is that I ought to have done a manual pass and stripped out any function bodies too, since I think the only way this will work is to build the JUCE .dll and link/load it in D.

That way only function signatures should be needed. (I think -- I've not much experience with native/systems programming so this is all new to me)

Also at 90,000 lines, I think I'd have to buy you quite a few beers if Ohmygentool could pull that off (with or without some manual finessing, ha) :sweat_smile:

This is a pipe-dream project for me but would open up a lot of doors (JUCE is so complex nobody's ported bindings outside of C++) so I figured I'd give it a shot.


By the way, have you come across techniques for improving chance of successful translation? What I've tried:

I haven't gotten unifdef or coan working quite right but they seem generally on track. Maybe there's something better.

A pipeline like this seems like Maybe your best chance:

amalgamate | unifdef $(DEFINES) $(UNDEFINES) | gentool
Superbelko commented 3 years ago

You don't really need to do anything platform(or rather compiler) specific, gentool is basically a clang and behaves like clang. In some cases you need to add custom defines to make it pretend as MSVC, all that unmatched ifdefs will not get into the produced bindings (just at this moment, definitely will be useful in the future if done right).

The real problem is actually STL usage, JUCE has stuff like this

class JUCE_API  LocalisedStrings {
...
std::unique_ptr<LocalisedStrings> fallback;
}

The problem is, unique_ptr support is incomplete, it is kind of there but is not ready, nor finished https://dlang.org/phobos/core_stdcpp_memory.html#.unique_ptr

The other problem is ConsoleApplication::Command

struct ConsoleApplication
{
  struct Command
  {
     ...
     std::function<void(const ArgumentList&)> command;
  }
 ...
}

std::function is a template, pragma mangle won't help that much and 'function' is reserved keyword in D so usually we just append underscore like 'function_', which will mess up naming scheme and this stuff won't link. It will need pragma mangle applied per instantiation which makes it impractical to do so by hand.

Another issue with std::function is that it actually can be 8 (single pointer, static function), 16 (two pointers, class method IIRC) or "random" size in bytes (when takes lambda), no guarantee it will ever work, I can't even say if it will work at all, need further investigation.

@cppsize(64) public function!(void function(ref const(ArgumentList))) command;

With such projects I would rather make opposite bindings, for example I have little OpenCV app in D where I just API as an abstract class and then handed it to C++ to access D data and write back the result, no manual fiddling (almost) thanks to -HC* flags in both DMD/LDC that generates extern(C++) definitions to .h

module glue.interfaces;

extern(C++)
struct Rect
{
    int x, y, w, h;
    this(int x, int y, int w, int h) { this.x = x; this.y = y; this.w = w; this.h = h; }
}

extern(C++)
abstract class ObjectDetector
{
    // Image data
    void getImageData(ref void* data, ref int w, ref int h, ref int bpp);

    // Add object to list 
    void addDetectedObject(Rect r, int label, float accuracy);

    // Clear objects list
    void clear();
}

extern(C) void run_mobilenet(ObjectDetector detector);
GavinRay97 commented 3 years ago

Ah I see what you mean

Better/easier to keep host application in C++ and write some D code then link it into the C++ app.

This seems easier, maybe I can have gentool translate headers for the main namespaces/interfaces in JUCE used:

https://github.com/juce-framework/JUCE/blob/master/examples/CMake/AudioPlugin/PluginEditor.cpp

Superbelko commented 3 years ago

Yes, definitely it is easier that way. Filter what you need first and probably make wrappers for things that simply doesn't translate.

For example one of the first functions , enable_if relies on C++ SFINAE which is basically primitive form of D static if/version, so you'll need to translate it by hand

extern(C++, "juce")
Type unalignedPointerCast(Type, enable_if!(is_pointer!(Type).value, int).type  = 0)(void* ptr) {
  return cast(Type)(ptr);
}

Or this case, while it will work as is in D you can make it much more efficient, also look a the Type, it is translated as it looks (parameterless template with empty body). However I'm unsure if it has same logic on type size in D. edit: it is not, doesn't even compiles

extern(C++, "juce","detail")
struct Type()
{
}

extern(C++, "juce","detail")
size_t getMaxAlignment(){
  return 0;
}

extern(C++, "juce","detail")
size_t getMaxAlignment(Head, Tail)(Type!(Head) , Type<Tail>... tail){
  return jmax((Head).alignof, getMaxAlignment(tail));
}

size_t maxAlignment  = getMaxAlignment(Type!(max_align_t){}, Type!(void*){}, Type!(float){}, Type!(double){}, Type!(long double){}, Type!(short){}, Type!(int){}, Type!(cpp_long){}, Type!(long){}, Type!(bool){}, Type!(char){}, Type!(char16_t){}, Type!(char32_t){}, Type!(wchar_t){});

btw I just found more uncovered cases.

GavinRay97 commented 3 years ago

Oh wow 😱

extern(C++, "juce","detail")
size_t getMaxAlignment(Head, Tail)(Type!(Head) , Type<Tail>... tail){
  return jmax((Head).alignof, getMaxAlignment(tail));
}

size_t maxAlignment  = getMaxAlignment(Type!(max_align_t){}, Type!(void*){}, Type!(float){}, Type!(double){}, Type!(long double){}, Type!(short){}, Type!(int){}, Type!(cpp_long){}, Type!(long){}, Type!(bool){}, Type!(char){}, Type!(char16_t){}, Type!(char32_t){}, Type!(wchar_t){});

Yes this does not seem very practical/feasible to translate sadly Ah well, maybe someday!