ziglang / zig

General-purpose programming language and toolchain for maintaining robust, optimal, and reusable software.
https://ziglang.org
MIT License
34.29k stars 2.51k forks source link

Missing compiler_rt functions #1290

Closed skyfex closed 1 year ago

skyfex commented 6 years ago

I was playing around with the latest version of Zig (https://ci.appveyor.com/project/andrewrk/zig-d3l86/build/0.2.0+95f45cfc) on ARM Cortex-M0 and I had a lot of issues with missing compiler_rt functions.

It was missing '__aeabi_memcpy'

It was complaining about missing '__aeabi_uldivmod', which I fixed by commenting out if (isArmArch()) { in compiler_rt/index.zig .. later I found out that it also helped to use --target-arch armv6 instead of thumb, or adding builtin.Arch.thumb to isArmArch (so that should probably be fixed)

After that it complained about __aeabi_h2f, __aeabi_f2h and __multi3

This is the command I used

zig build-exe --static --target-os freestanding --target-arch thumb --target-environ eabihf  --libc-include-dir include --linker-script system\nrf51_xxaa.ld --verbose-link -isystem include --libc-include-dir include --library-path system --assembly system\gcc_startup_nrf51.S --object zig-cache/system_nrf51.o test.zig

Using fmt.bufPrint is what is triggering these errors for me right now.

Is it possible to get a more permanent/robust solution to compiler_rt related problems? Some kind of automated testing that everything is there somehow?

(Btw, good news is that linking with Zig (rather than GCC or LLD) seems to work just fine for me now)

skyfex commented 6 years ago

I'm also often getting these warnings, don't know what they mean: lld: warning: lld may use movt/movw, no object with architecture supporting feature detected.

andrewrk commented 6 years ago

Is it possible to get a more permanent/robust solution to compiler_rt related problems? Some kind of automated testing that everything is there somehow?

So far, I haven't tried to make sure we have all the compiler_rt functions. I've just been adding the ones that were missing when I personally tried something and got these errors. So I've never tried to make sure that we have all of them.

So the first step to getting a permanent/robust solution to compiler_rt related problems is to go ahead and port all the rest of compiler_rt. I thought we had an issue open for that but I'm unable to find it, so this will be that issue.

andrewrk commented 5 years ago

Thanks to @winksaville we now have these functions:

Next steps toward solving this issue is to compile a checklist of all the functions from llvm's compiler-rt builtins/ directory and then start porting them one by one.

tiehuis commented 5 years ago

This list is derived from the following README file.

https://raw.githubusercontent.com/llvm-mirror/compiler-rt/master/lib/builtins/README.txt

There are some platform-specific functions that need to be added here, for example under https://github.com/llvm-mirror/compiler-rt/tree/master/lib/builtins/arm.

Integral bit manipulation

Integral arithmetic

// Integral arithmetic with trapping overflow

Integral arithmetic which returns if overflow

Integral comparison:

a  < b -> 0
a == b -> 1
a  > b -> 2

Integral / floating point conversion

Floating point raised to integer power

Complex arithmetic

Following are not required since we do not have language-level complex number support.

(a + ib) * (c + id)

- [ ] float _Complex __mulsc3( float a, float b, float c, float d); - [ ] double _Complex __muldc3(double a, double b, double c, double d); - [ ] long double _Complex __mulxc3(long double a, long double b, long double c, long double d); ~~- [ ] long double _Complex __multc3(long double a, long double b, long double c, long double d); // ppc only~~

(a + ib) / (c + id)

Omitted Runtime support functions

Power PC specific functions

adds two 128-bit double-double precision values ( x + y )

subtracts two 128-bit double-double precision values ( x - y )

multiples two 128-bit double-double precision values ( x * y )

divides two 128-bit double-double precision values ( x / y )

ARM specific functions

Undocumented functions

radek-senfeld commented 5 years ago

After that it complained about __aeabi_h2f, __aeabi_f2h and __multi3

I had the same problem. It turns out you need to compile for gnueabi target in order to link using GCC ld.

EABI names: __aeabi_f2h, __aeabi_h2f GNUEABI names: __gnu_f2h_ieee, __gnu_h2f_ieee

This works for me:

zig build-obj -target armv7m-freestanding-gnueabi --output-dir build/ /opt/zig/lib/zig/std/special/compiler_rt.zig

zig build-obj -target armv7m-freestanding-gnueabi --output-dir build/ src/main.zig

arm-none-eabi-g++ -o build/bluepill.elf --static -nostartfiles -Wl,--gc-sections -T ld/stm32f103c8.ld --specs=nosys.specs lib/libopencm3/lib/libopencm3_stm32f1.a build/compiler_rt.o build/debug.o build/stm32.o build/stm32f1.o build/main.o -Llib/libopencm3/lib -lc -lm -lgcc -lnosys -lopencm3_stm32f1
andrewrk commented 5 years ago

@radek-senfeld we can add __aeabi_f2h, __aeabi_h2f, and __multi3 and then you should be able to zig build-exe -target armv7m-freestanding-eabi directly, no dependency on a cross compiled g++.

It's pretty easy to add compiler-rt functions. I can try to get those in today. Were there any other missing ones for you besides these 3?

radek-senfeld commented 5 years ago

Hi Andrew, at first I want to thank you for your amazing project! I'm hooked to Zig!

Well, my motivation was that I just had to know what is the problem and why it doesn't work. After spending quite a few hours digging around I now much better understand how things work under the hood.

Well, there's apparently no issue with EABIs when Zig links the file. Running zig build-exe -target armv7m-freestanding-eabi src/main.zig doesn't emit any errors.

Which is quite strange because of ommited opencm3_stm32f1 library. It should babble about missing symbols, shouldn't it?

Were there any other missing ones for you besides these 3?

After linking with compiler_rt.o everything I've tried has been sorted out. I just wanted to know if there's chance to have fp formatting in the firmware. Unfortunately the size of this feature is prohibitive (~100kB) at this moment. I've tried just a simple test:

const value: f32 = 3.1415;
debug.message("test! value: {}", value);

There's one more issue but I'm not quite sure about the cause yet. It just hangs the MCU. I need to inspect it using a debugger. This causes MCU to hang:

const value: u32 = 5;
debug.message("test! value: {}", value);

Environment:

rush@jarvis:~$ zig version
0.3.0+6acabd6b
andrewrk commented 5 years ago

Hi Andrew, at first I want to thank you for your amazing project! I'm hooked to Zig!

Thank you for the compliment and I'm happy that you like it.

It should babble about missing symbols, shouldn't?

Only if the symbols are called or used. Perhaps this is zig's lazy analysis of top level declarations? If you do not call a function then it does not get analyzed or included in the result.

andrewrk commented 5 years ago

I just wanted to know if there's chance to have fp formatting in the firmware. Unfortunately the size of this feature is prohibitive (~100kB) at this moment.

Ah that's interesting. Here we have a good use case for perhaps selecting a different implementation of floating point formatting when the --release-small mode is selected. This is a related issue: #1299. @tiehuis has a work-in-progress implementation of this and probably knows how much smaller of a payload it would be than errol3. It's also possible that we include another floating point printing algorithm that is optimized for code size rather than performance.

Feel free to open a new issue which is dedicated to exploring your exact use case. We can comment back and forth there and perhaps learn some new Zig issues that need to be filed.

radek-senfeld commented 5 years ago

Only if the symbols are called or used. Perhaps this is zig's lazy analysis of top level declarations? If you do not call a function then it does not get analyzed or included in the result.

You're probably right.

I guess it's caused by me not specifying a linker script. Which means entry point isn't defined thus fn main() is not linked in and no symbols are missing because they are not used.

radek-senfeld commented 5 years ago

Ah that's interesting. Here we have a good use case for perhaps selecting a different implementation of floating point formatting when the --release-small mode is selected. This is a related issue: #1299. @tiehuis has a work-in-progress implementation of this and probably knows how much smaller of a payload it would be than errol3. It's also possible that we include another floating point printing algorithm that is optimized for code size rather than performance.

Oh, my bad. Now I feel a bit stupid. When compiled using --release-small the resulting binary is actually < 9kB!

Floating-point formatting enabled:

const value: f32 = 3.1415;
debug.message("test! value: {}", value);

Here is the summary:

no release specified (debug, right?): 108016B
--release-fast: 8524B
--release-safe: 13068B
--release-small: 8532B
andrewrk commented 5 years ago

The above commit adds:

magv commented 5 years ago

Hi, everyone. Can we also get __ashlti3 and __lshrti3?

These two prevent the float formatting via fmt.bufPrint from being used under the wasm32-freestanding target. They show up as extra imports:

  (type $t0 (func (param i32 i64 i64 i32)))
  (import "env" "__ashlti3" (func $__ashlti3 (type $t0)))
  (import "env" "__lshrti3" (func $__lshrti3 (type $t0)))

Note that because the imports have i64 in the arguments, it's not even possible to provide them from the JavaScript side. In fact, the browsers refuse to compile the wasm files zig is producing here.

andrewrk commented 5 years ago

Thanks to @LemonBoy, __ashlti3 and __lshrti3 are now available for all targets, including wasm32.

magv commented 5 years ago

Thanks, @LemonBoy and Andrew; fmt.bufPrint works under wasm now.

matu3ba commented 3 years ago

Unfortunately I can not tick the boxes from @tiehuis , so I will add it here.

NOTE: libgcc changed the definition and LLVM did not update it in compiler_rt yet.

Integral bit manipulation

Integral arithmetic

NOTE: ti ones look architecture specific, so unsure if complete.

Integral arithmetic with trapping overflow

Integral arithmetic which returns if overflow

Integral comparison:

a  < b -> 0
a == b -> 1
a  > b -> 2

Integral / floating point conversion

TODO sort these

matu3ba commented 3 years ago

@andrewrk The signedness between libgcc 4.9 and current libgcc has changed for the 4.1.4 Bit operations from http://www.chiark.greenend.org.uk/doc/gcc-4.9-doc/gccint.html#index-_005f_005fclzsi2 (Bit operations). How should we deal with this?

libgcc says and that what the README in compiler_rt explicitly refers to:

Runtime Function: int __clzsi2 (unsigned int a)
Runtime Function: int __clzdi2 (unsigned long a)
Runtime Function: int __clzti2 (unsigned long long a)

    These functions return the number of leading 0-bits in a, starting at the most significant bit position. If a is zero, the result is undefined. 
Here is the specification for this library:

http://gcc.gnu.org/onlinedocs/gccint/Libgcc.html#Libgcc
...
Here is a synopsis of the contents of this library:

typedef  int32_t si_int;
typedef uint32_t su_int;

typedef  int64_t di_int;
typedef uint64_t du_int;

However in the implementation they use (ie file clzsi2.c):

COMPILER_RT_ABI int __clzsi2(si_int a) {  //  < --------------THIS IS int32_t --------------
...

The implementation of counting leading zeros looks also very odd, as

unsigned int v;
unsigned r = 0;

while (v >>= 1) {
    r++;
}

is much shorter (taken from here). Doesnt work with big endian though.

zhaozg commented 3 years ago

focus, I hit ld.lld: error: undefined symbol: __divdc3 when zig cc -target x86_64-linux-gnu build https://github.com/facebookarchive/luaffifb

[ 98%] Linking C executable luvi
ld.lld: error: undefined symbol: __muldc3
>>> referenced by ffi.c
>>>               CMakeFiles/ffi.dir/thirdparty/ffi/ffi.c.o:(check_complex_double) in archive luv.dir/lua/libffi.a
>>> referenced by ffi.c
>>>               CMakeFiles/ffi.dir/thirdparty/ffi/ffi.c.o:(check_complex_float) in archive luv.dir/lua/libffi.a
>>> referenced by ffi.c
>>>               CMakeFiles/ffi.dir/thirdparty/ffi/ffi.c.o:(set_value) in archive luv.dir/lua/libffi.a
>>> referenced 1 more times
>>> did you mean: __muldf3
>>> defined in: /Users/zhaozg/.cache/zig/o/6fbc72a7825d842d0abf7291021696e1/libcompiler_rt.a

ld.lld: error: undefined symbol: __divdc3
>>> referenced by ffi.c
>>>               CMakeFiles/ffi.dir/thirdparty/ffi/ffi.c.o:(cdata_div) in archive luv.dir/lua/libffi.a
matu3ba commented 3 years ago

If anyone besides me is working on this, you can use older releases (<80) of LLVM as hinted here ie release tag 80 builtins and release tag 80 unit test.

Alternatively https://bits.stephan-brumme.com/, http://aggregate.org/MAGIC/ and http://graphics.stanford.edu/~seander/bithacks.html#ParityParallel are good sources besides Hackers Delight. Hackers Delight also has acommpanied code.

There is also this wiki https://www.chessprogramming.org/Bit-Twiddling with nice resources.

collection of some tricks, go bit twiddling API. fefes blog.

matu3ba commented 2 years ago

Also we really want to improve the compiler-rt libraries as they lead to very bad codegen.

andrewrk commented 2 years ago

Agreed! More optimized, well-tested compiler-rt implementations are welcome, and definitely within scope of the Zig project.

matu3ba commented 2 years ago

After manual inspection of the assmebly generated from popcount, the CPU simulator shows me ~5% performance penalty vs optimized assembly on x86_64 architectures, but for legal reasons I dont want to include and link the comparion here (128 bit popcount): link

__popcountti2:
        mov     rax, rsi
        shr     rax
        movabs  r8, 6148914691236517205
        and     rax, r8
        sub     rsi, rax
        movabs  rax, 3689348814741910323
        mov     rcx, rsi
        and     rcx, rax
        shr     rsi, 2
        and     rsi, rax
        add     rsi, rcx
        mov     rcx, rsi
        shr     rcx, 4
        add     rcx, rsi
        movabs  r9, 1085102592571150095
        and     rcx, r9
        movabs  rdx, 72340172838076673
        imul    rcx, rdx
        shr     rcx, 56
        mov     rsi, rdi
        shr     rsi
        and     rsi, r8
        sub     rdi, rsi
        mov     rsi, rdi
        and     rsi, rax
        shr     rdi, 2
        and     rdi, rax
        add     rdi, rsi
        mov     rax, rdi
        shr     rax, 4
        add     rax, rdi
        and     rax, r9
        imul    rax, rdx
        shr     rax, 56
        add     eax, ecx
        ret

Generated on llvm-mca: Instructions: 3700 vs 3800 with -mcpu=haswell and Total Cycles: 914 vs 1016. My left hypothesis is that codegen by LLVM is significantly worse for architectures without register renaming. Another unclear things is if and how power consumption deviates. Other unclear things are, how good the CPU simulator works. The better one from icu still needs to be tested, but doesnt have a web interface.

matu3ba commented 2 years ago

The fastest routine to do multiplication overflow checks is via finite field arithmetic via Galois Linear Feedback Shift Register.

The best introduction and overview work (kinda like a goldmine) on Linear Feedback Shift Register techniques is "Linear Feedback Shift Registers for the Uninitiated, Part I: Ex-Pralite Monks and Finite Fields" by Jason Sachs, who has !16! very long introductions into various performance optimization techniques for integer stuff and likely also describes how to easily derive algorithms users may want for optimal performance in a related library.

As I understand compiler_rt, it is for embedded devices without hardware capabilities, and thus space optimized. Unless there is a hardware routine.

andrewrk commented 2 years ago

As I understand compiler_rt, it is for embedded devices without hardware capabilities, and thus space optimized.

For Zig it actually has multiple purposes which can be determined by inspecting @import("builtin").mode. When the programmer builds their application with -OReleaseSmall, then Zig builds compiler-rt with -OReleaseSmall; otherwise Zig builds compiler-rt with -OReleaseFast.

Our compiler-rt has the ability to choose different implementations depending on the desired mode.

vladfaust commented 2 years ago

By the way, in https://andrewkelley.me/post/zig-cc-powerful-drop-in-replacement-gcc-clang.html Andrew says:

Zig's compiler-rt is not yet complete. However, completing it is a prerequisite for releasing Zig version 1.0.0.

Shouldn't the 1.0.0 milestone be referencing this issue then?

matu3ba commented 2 years ago

@vladfaust The base stuff is implemented except the overflowing check primitives, which are semi-blocked by testing panics, which in turn is blocked by an OS-independent socket abstraction: IPC to write the result from within the spawned process is the most sane/simple way for testing panics, which does not compromise panic handler design.

Testing architectures without OS-layer (without process isolation and IPC) would be deferred, as they may compromise panic handler design. embedded devs are expected to use their own panic handler and modify _start etc.

Only thing I am dissatisfied (as its not ready yet) is an overflow multiplication improvement that does not rely on division, but I should be able to finish this within 1-2 days after staring long enough at the paper. approach from paper does not work. mulv and the other routines in Integral arithmetic with trapping overflow will be upstreamed after testing panics work:

__addvsi3
__addvdi3
__addvti3
__subvsi3
__subvdi3
__subvti3
__mulvsi3
__mulvdi3
__mulvti3

see #1356

perf for mulo is ~17x faster than the llvm implementation (measured on my skylake laptop), which might make Zig compiler_rt be very worth to use in external code. mulo bench. I did not measure the Zig internal one yet.

The perf gain for wrapping addition and subtraction instead of the simple approach are in the range of 10% for external code and the Zig internal one without pointers will have ~15% improvements for wrapping addition and subtraction. addo benches.

So all in all, its almost finished: https://github.com/ziglang/zig/issues/1290#issuecomment-819899869

compiler_rt version 2.0 would then be to use the hw accelerations for the routines for all tier 1 targets and figure out how to track this in a sane way.

matu3ba commented 2 years ago

Personally I would favor keeping compiler_rt small and readable and accept 5-8% inefficiency (very rough estimate extrapolating from the popcount speed difference) vs hand-rolled assembly. However, in case we want to tune it further or have an "gimme the experimental compiler_rt switch", we can use:

On the side of panic testing to get this finished, I plan

  1. port+fix missing signal handling
  2. port posix_spawn to use instead of fork.
  3. Move pipe parts into pipe.zig with PipeImpl for comptime-configuration of pipes.

EDIT1: progress, but needs benchmarking against realistic workloads to prevent regressions: https://github.com/ziglang/zig/pull/11701 binding to libc posix_spawn need debugging, port of posix_spawn incomplete, porting signal handling is missing a strategy for Zig (Zig libc guarantees vs caller responsibility and to what degree + how to deal with inconsistency/complexity of syscall interaction).

andrewrk commented 1 year ago

Next step to solving this issue: we need an updated checklist of what is still missing.

matu3ba commented 1 year ago

@andrewrk See #13261 for the missing work items. addv,subv,mulv can be trivially copied, if you are okay without testing the panics. I think all tests can be trivially extended or derived from the other implementations except for a^b for complex numbers.

rjzak commented 1 year ago

I just tried to compile 0.10 for powerpc64le-linux-gnu, and found these were missing: __gcc_qmul, __gcc_qdiv, __gcc_qadd when at the last stage compiling zig-bootstrap.

[100%] Building stage3
LLD Link... ld.lld: error: undefined symbol: __gcc_qmul
>>> referenced by hashtable_c++0x.o:(std::__detail::_Prime_rehash_policy::_M_next_bkt(unsigned long) const) in archive /usr/lib64/libstdc++.a
>>> referenced by hashtable_c++0x.o:(std::__detail::_Prime_rehash_policy::_M_next_bkt(unsigned long) const) in archive /usr/lib64/libstdc++.a
>>> referenced by hashtable_c++0x.o:(std::__detail::_Prime_rehash_policy::_M_need_rehash(unsigned long, unsigned long, unsigned long) const) in archive /usr/lib64/libstdc++.a

ld.lld: error: undefined symbol: __gcc_qdiv
>>> referenced by hashtable_c++0x.o:(std::__detail::_Prime_rehash_policy::_M_need_rehash(unsigned long, unsigned long, unsigned long) const) in archive /usr/lib64/libstdc++.a

ld.lld: error: undefined symbol: __gcc_qadd
>>> referenced by hashtable_c++0x.o:(std::__detail::_Prime_rehash_policy::_M_need_rehash(unsigned long, unsigned long, unsigned long) const) in archive /usr/lib64/libstdc++.a

Edit: resolved by using gcc 12.0 which I had compiled from source instead of using gcc 10.2 provided by Void Linux.

matu3ba commented 1 year ago

Suggestion to close this in favor of #15675.

The tracking issues can be searched via the following patterns: compiler_rt: Tracking Issue XYZ Routines.