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

Cross-compiling 32 bit LuaJIT on a 64 bit host #664

Closed
jpc opened this issue Feb 13, 2021 · 5 comments
Closed

Cross-compiling 32 bit LuaJIT on a 64 bit host #664

jpc opened this issue Feb 13, 2021 · 5 comments

Comments

@jpc
Copy link

jpc commented Feb 13, 2021

LuaJIT only supports crosscompiling for a 32 bit target on a 32 bit host:

The GNU Makefile-based build system allows cross-compiling on any host for any supported target, as long as both architectures have the same pointer size. If you want to cross-compile to any 32 bit target on an x64 OS, you need to install the multilib development package (e.g. libc6-dev-i386 on Debian/Ubuntu) and build a 32 bit host part (HOST_CC="gcc -m32"). (from Cross-compiling LuaJIT)

Unfortunately some platforms (macOS) dropped support for 32 bit binaries for several years now. Rosetta 2 (Apple's x86_64 to arm64 translation layer) also does not support 32 bit x86 binaries at all.

The easiest solution would be to stick to compiling on Linux which will probably support 32 bit mode for years to come (and QEMU will ensure user-mode emulation even when CPUs drop 32 bit support in hardware).

Another alternative would be to fix the underlying issue. Does anybody know what prevents LuaJIT from doing crosscompilation with differing word sizes? How difficult would it be to remove this restriction?

@MikePall
Copy link
Member

IMHO Linux is better suited for embedded development and cross-compilation, anyway. And QEMU full system emulation ought to work on macOS, too.

The underlying issue is complicated:

  • The architecture-specific interpreter is written in assembler. It needs to access many different structures in the LuaJIT core, which are specified in C header files.
  • This is accomplished by DynASM: it translates a *.dasc file to C code with lots of macros that compute the struct offsets. This C code must be compiled and run on the host. It emits the actual target machine code for the interpreter, which is then linked together with the rest of the cross-compiled VM code for the target.
  • Of course, the struct offsets differ, depending on 32 bit or 64 bit. They also differ, depending on various defines that are derived from build options (e.g. with or without JIT, with or without FFI). Since they must be resolved during the build process, they need to be evaluated by a program on the host. That's why the host and target pointer size must match.
  • The process is already a bit delicate, because it has to pamper over some other architectural differences (alignment, endianess). But 32 bit vs. 64 bit is a major difference, which cannot easily be overcome: you cannot override the pointer size for the host C compiler. Except if you build for a different architecture -- -m32 actually changes the architecture for a multi-arch compiler, not just some type sizes.

If anyone feels really, really adventurous, they could of course reuse the C parser from the FFI and add (limited) support for CPP to actually parse LuaJIT's own header files. Then compute the struct offsets for the target architecture and finally replace the DynASM-generated macros with their own. Definitely a challenging project.

I'm tagging it as an enhancement, but closing now. If anyone really wants to dive into this and contribute a solution, they can request a reopen.

@jpc
Copy link
Author

jpc commented Feb 25, 2021

Hi, thanks for the explanation. I understand why you don't consider it worthwhile to change this.

I remember reading once about somebody who was using DWARF debug information to extract the struct layouts for FFI without running the code but since you already have a engine that calculates proper struct offsets from C declarations it seems like a good idea to reuse that.

@jpc
Copy link
Author

jpc commented Mar 16, 2021

I managed to hack around it using a Linux VM, qemu-user-arm (because the Apple M1 truly does not support any 32bit code – it's no longer a macOS software issue) and copious amounts of rsyncing back-and-forth between macOS and the VM. It allows me to compile using all of my macOS cross toolchains using a single 32bit armhf buildvm binary. For some strange reason I sometimes find joy in such things. ;)

But I was wondering if it could make sense to pre-build and ship the generated files for all the default configurations supported by LuaJIT. They are around 125k per arch (which is both a lot and almost nothing ;) but this would make the problem go away for many LuaJIT users (who neither modify the code nor customize the compilation options).

@MikePall
Copy link
Member

That would mean shipping thousands of configurations: num(archs) * num(toolchains) * num(OS) * (2 ^ num(build_options)).That's not feasible.

@jpc
Copy link
Author

jpc commented Mar 17, 2021

Yes, to ship every possible combination would not be possible. I was thinking about doing the actually existing ARCH*OS (no MIPS for Win32 for example – there should be a dozen or so, right?). The toolchains for a single (OS,ARCH) pair may have different ABIs (ARM softfp vs. hardfp comes to mind) but right now the buildvm tool has no insider knowledge of the target toolchain (since we only care about the pointer size agreement with the host compiler) – so only the LuaJIT-specific options are influencing the result. All of these options have some default values for each (ARCH,OS) pair and I think they could be supported out of the box. Only when someone needs a different config he would have do things the old way (compile and run buildvm himself).

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

No branches or pull requests

2 participants