Runtime libraries providing compiler builtins and math functions for the TMS9900 LLVM backend.
libtms9900/
├── builtins/ # Compiler intrinsics (__mulsi3, __addsf3, etc.)
│ ├── mul32.asm # 32-bit multiply
│ ├── div32.asm # 32-bit divide/modulo
│ ├── shift32.asm # 32-bit shifts
│ └── softfloat/ # IEEE 754 soft-float (from compiler-rt)
├── libm/ # Math library
│ ├── sincosf_tiny.c # Compact sin/cos (our implementation)
│ ├── include/ # Configuration headers
│ └── Makefile # Builds libm.a
└── picolibc/ # Embedded C library (upstream source)
TODO: Convert picolibc to a git submodule pointing to https://github.com/apullin/picolibc
cd libm
make # Build with -Os (default, smallest)
make cleanTo build with different optimization, edit CFLAGS in the Makefile.
Note: -O0 currently fails due to branch relaxation issues in the compiler.
All functions follow the TMS9900 LLVM calling convention:
- Arguments in R0, R1, R2, R3 (first 4 words)
- 32-bit values: high word first (R0:R1 or R2:R3)
- Return in R0 (16-bit) or R0:R1 (32-bit)
- R11 = link register
- Callee-saved: R9, R10 (R10 = stack pointer)
| Symbol | Purpose |
|---|---|
__mulsi3 |
32-bit multiply |
__divsi3 |
32-bit signed divide |
__udivsi3 |
32-bit unsigned divide |
__modsi3 |
32-bit signed modulo |
__umodsi3 |
32-bit unsigned modulo |
__ashlsi3 |
32-bit left shift |
__ashrsi3 |
32-bit arithmetic right shift |
__lshrsi3 |
32-bit logical right shift |
IEEE 754 single-precision floating-point operations, provided by LLVM's compiler-rt.
Located in builtins/softfloat/.
| Symbol | Purpose |
|---|---|
__addsf3 |
float32 add |
__subsf3 |
float32 subtract |
__mulsf3 |
float32 multiply |
__divsf3 |
float32 divide |
__eqsf2, __nesf2 |
float32 equality |
__ltsf2, __lesf2 |
float32 less-than |
__gtsf2, __gesf2 |
float32 greater-than |
__fixsfsi |
float32 → int32 |
__floatsisf |
int32 → float32 |
The picolibc/ directory contains upstream picolibc source. We currently use only the libm components, building selected files via libm/Makefile. This will likely expand to include libc components as the project matures.
We provide a size-optimized sinf/cosf that replaces picolibc's 7KB+ implementation with a 1.2KB version.
Standard libm sin/cos implementations use Payne-Hanek range reduction, which requires a ~200 byte table of 2/π digits and complex multi-precision arithmetic to handle sin(1e10) accurately. This pulls in 8KB+ of code.
Our implementation uses Cody-Waite style range reduction with extended-precision π/2 constants:
pio2_hi= high part of π/2pio2_lo= low part (correction term)
| Property | Our Implementation | Full picolibc |
|---|---|---|
| Size | 1.2KB | 7KB+ |
| Accuracy for |x| < 10^4 | Full precision | Full precision |
| Accuracy for |x| > 10^6 | Reduced (~4 digits) | Full precision |
| ULP error (small x) | < 1 ULP | < 1 ULP |
When to use full picolibc sin/cos: If you need sin(1e10) to be accurate to full float precision, rebuild with picolibc's sf_sin.c/sf_cos.c instead of sincosf_tiny.c.
For typical embedded use (angles in radians, game physics, signal processing), our compact version is accurate and 6x smaller.
All sizes in bytes. Note: These sizes will change as the compiler improves.
| Symbol | Function | -O1 | -Os | -O2 | -O3 |
|---|---|---|---|---|---|
acoshf |
Inverse hyperbolic cosine | 364 | 360 | 358 | 358 |
acosf |
Inverse cosine | 1,462 | 1,462 | 1,462 | 1,472 |
asinhf |
Inverse hyperbolic sine | 588 | 580 | 588 | 648 |
asinf |
Inverse sine | 1,428 | 1,420 | 1,428 | 1,482 |
atan2f |
Two-argument arctangent | 718 | 704 | 718 | 740 |
atanf |
Arctangent | 998 | 990 | 998 | 1,002 |
atanhf |
Inverse hyperbolic tangent | 336 | 346 | 336 | 342 |
ceilf |
Ceiling | 352 | 350 | 352 | 368 |
copysignf |
Copy sign | 14 | 14 | 14 | 14 |
coshf |
Hyperbolic cosine | 450 | 458 | 450 | 480 |
cosf |
Cosine (compact impl) | incl. | incl. | incl. | incl. |
expf |
Exponential | 1,208 | 1,216 | 1,208 | 1,254 |
exp2f |
Base-2 exponential (wrapper) | 22 | 22 | 22 | 22 |
fabsf |
Absolute value | 6 | 6 | 6 | 6 |
finitef |
Finite check | 26 | 26 | 26 | 32 |
floorf |
Floor | 314 | 306 | 314 | 314 |
fmodf |
Floating-point modulo | 1,254 | 1,168 | 1,230 | 1,240 |
frexpf |
Extract mantissa/exponent | 158 | 146 | 154 | 164 |
hypotf |
Hypotenuse | 1,256 | 1,238 | 1,256 | 1,318 |
isnanf |
NaN check | 62 | 58 | 62 | 62 |
log10f |
Base-10 logarithm | 326 | 326 | 318 | 330 |
log2f |
Base-2 logarithm (wrapper) | 24 | 24 | 24 | 24 |
logf |
Natural logarithm | 1,436 | 1,404 | 1,428 | 1,448 |
modff |
Integer/fraction split | 244 | 264 | 244 | 244 |
powf |
Power function | 5,312 | 5,100 | 5,326 | 5,428 |
scalbnf |
Scale by power of 2 | 398 | 350 | 376 | 402 |
sinf |
Sine (compact impl) | incl. | incl. | incl. | incl. |
sinhf |
Hyperbolic sine | 430 | 424 | 430 | 456 |
sqrtf |
Square root | 740 | 736 | 740 | 4,416 |
tanhf |
Hyperbolic tangent | 282 | 282 | 282 | 294 |
| sincosf_tiny.o | sinf + cosf combined | 1,314 | 1,200 | 1,396 | 1,456 |
| TOTAL | 21,522 | 20,980 | 21,546 | 25,816 |
Note: sinf and cosf are both in sincosf_tiny.o; linker pulls the whole object if either is used.
- -Os is the clear winner for size at 20,980 bytes
- -O1, -O2 are nearly identical (~21.5KB) - only ~500 bytes apart
- -O3 explodes to 25.8KB due to aggressive inlining (sqrtf alone: 4.4KB vs 736B)
- Speed is critical on TMS9900; -O3 may be worthwhile once we have cycle-count benchmarks
- powf is surprisingly large at 5.1KB (24% of total library size)
TODO: Benchmark -Os vs -O2 vs -O3 for runtime performance. The eventual cycle-count scheduler (with memory latency modeling) will help determine the best tradeoff.
TODO: Investigate powf size/performance. Proposed improvements:
- Special-case small integer exponents (|y| ≤ 10) with multiply loop (faster AND more precise)
- Handle negative small integers as 1/(x^n)
- Benchmark current implementation to determine if 126 soft-float calls are acceptable
Part of the llvm-99 project. See main project LICENSE. picolibc is BSD-licensed; see picolibc/COPYING.picolibc.