mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-08-04 08:50:27 +00:00
Compare commits
No commits in common. "master" and "3.5.4" have entirely different histories.
5271 changed files with 316906 additions and 478993 deletions
|
@ -25,7 +25,3 @@ c0eacf2eb1e1c0b3bd4f71f12fef258f5b249c3f
|
|||
da8baf2aa5ce93b958aca90a0ae69f537806324b
|
||||
# Run clang-format on most sources
|
||||
369f9740de4534c28d0e81ab2afc99decbb9a3e6
|
||||
# Get rid of .internal.h convention in LIBC_INTRIN
|
||||
86d884cce24d773e298a2714c1e3d91ecab9be45
|
||||
# Remove .internal from more header filenames
|
||||
31194165d2afca36c2315a6e7ca2f0797dde09e3
|
||||
|
|
8
.gitattributes
vendored
8
.gitattributes
vendored
|
@ -1,10 +1,4 @@
|
|||
# -*- conf -*-
|
||||
*.gz binary
|
||||
*.so binary
|
||||
*.dll binary
|
||||
*.dylib binary
|
||||
/build/bootstrap/* binary
|
||||
/usr/share/terminfo/* binary
|
||||
/usr/share/terminfo/*/* binary
|
||||
/build/bootstrap/*.com binary
|
||||
/usr/share/zoneinfo/* binary
|
||||
/usr/share/zoneinfo/*/* binary
|
||||
|
|
42
.github/workflows/build.yml
vendored
42
.github/workflows/build.yml
vendored
|
@ -1,8 +1,5 @@
|
|||
name: build
|
||||
|
||||
env:
|
||||
COSMOCC_VERSION: 3.9.2
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
|
@ -22,48 +19,13 @@ jobs:
|
|||
matrix:
|
||||
mode: ["", tiny, rel, tinylinux, optlinux]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
# Full checkout needed for git-restore-mtime-bare.
|
||||
fetch-depth: 0
|
||||
|
||||
# TODO(jart): fork this action.
|
||||
- uses: chetan/git-restore-mtime-action@v2
|
||||
|
||||
- uses: actions/cache/restore@v4
|
||||
id: cache
|
||||
with:
|
||||
path: |
|
||||
.cosmocc
|
||||
o
|
||||
key: ${{ env.COSMOCC_VERSION }}-${{ matrix.mode }}-${{ github.sha }}
|
||||
restore-keys: |
|
||||
${{ env.COSMOCC_VERSION }}-${{ matrix.mode }}-
|
||||
${{ env.COSMOCC_VERSION }}-
|
||||
|
||||
- name: Restore mtimes
|
||||
if: steps.cache.outputs.cache-hit == 'true'
|
||||
run: |
|
||||
while read mtime file; do
|
||||
[ -f "$file" ] && touch -d "@$mtime" "$file"
|
||||
done < o/.mtimes
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: support ape bins 1
|
||||
run: sudo cp -a build/bootstrap/ape.elf /usr/bin/ape
|
||||
run: sudo cp build/bootstrap/ape.elf /usr/bin/ape
|
||||
|
||||
- name: support ape bins 2
|
||||
run: sudo sh -c "echo ':APE:M::MZqFpD::/usr/bin/ape:' >/proc/sys/fs/binfmt_misc/register"
|
||||
|
||||
- name: make matrix
|
||||
run: V=0 make -j2 MODE=${{ matrix.mode }}
|
||||
|
||||
- name: Save mtimes
|
||||
run: |
|
||||
find o -type f -exec stat -c "%Y %n" {} \; > o/.mtimes
|
||||
|
||||
- uses: actions/cache/save@v4
|
||||
with:
|
||||
path: |
|
||||
.cosmocc
|
||||
o
|
||||
key: ${{ env.COSMOCC_VERSION }}-${{ matrix.mode }}-${{ github.sha }}
|
||||
|
|
24
.github/workflows/nightly-cosmocc.yml
vendored
24
.github/workflows/nightly-cosmocc.yml
vendored
|
@ -1,24 +0,0 @@
|
|||
name: Nightly cosmocc
|
||||
on:
|
||||
schedule:
|
||||
# https://crontab.guru/#37_4_*_*_*
|
||||
- cron: "37 4 * * *"
|
||||
workflow_dispatch:
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}
|
||||
cancel-in-progress: true
|
||||
jobs:
|
||||
build-cosmocc:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- run: |
|
||||
sudo cp build/bootstrap/ape.elf /usr/bin/ape
|
||||
sudo sh -c "echo ':APE:M::MZqFpD::/usr/bin/ape:' >/proc/sys/fs/binfmt_misc/register"
|
||||
- run: tool/cosmocc/package.sh
|
||||
# https://github.com/actions/upload-artifact/issues/590
|
||||
- uses: actions/upload-artifact@v4.3.5
|
||||
with:
|
||||
name: cosmocc
|
||||
path: cosmocc
|
||||
compression-level: 9
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -15,4 +15,3 @@ __pycache__
|
|||
/tool/emacs/*.elc
|
||||
/perf.data
|
||||
/perf.data.old
|
||||
/qemu*core
|
||||
|
|
36
.vscode/settings.json
vendored
36
.vscode/settings.json
vendored
|
@ -1,36 +0,0 @@
|
|||
{
|
||||
"C_Cpp.default.compilerPath": ".cosmocc/3.9.2/bin/aarch64-linux-cosmo-c++",
|
||||
"C_Cpp.default.compilerArgs": [
|
||||
"-nostdinc",
|
||||
"-nostdlib",
|
||||
"-iquote.",
|
||||
"-isystemlibc/isystem",
|
||||
"-isystemthird_party/libcxx",
|
||||
"-includelibc/integral/normalize.inc",
|
||||
"-D_COSMO_SOURCE",
|
||||
"-D__aarch64__"
|
||||
],
|
||||
"[c]": {
|
||||
"editor.tabSize": 2,
|
||||
"editor.insertSpaces": true
|
||||
},
|
||||
"[cpp]": {
|
||||
"editor.tabSize": 2,
|
||||
"editor.insertSpaces": true
|
||||
},
|
||||
"[makefile]": {
|
||||
"editor.tabSize": 8,
|
||||
"editor.insertSpaces": false
|
||||
},
|
||||
"[make]": {
|
||||
"editor.tabSize": 8,
|
||||
"editor.insertSpaces": false
|
||||
},
|
||||
"[assembly]": {
|
||||
"editor.tabSize": 8,
|
||||
"editor.insertSpaces": true
|
||||
},
|
||||
"files.associations": {
|
||||
"log.h": "c"
|
||||
}
|
||||
}
|
171
Makefile
171
Makefile
|
@ -77,8 +77,7 @@ COMMA := ,
|
|||
PWD := $(shell pwd)
|
||||
|
||||
# detect wsl2 running cosmopolitan binaries on the host by checking whether:
|
||||
# - user ran .cosmocc/current/bin/make, in which case make's working directory
|
||||
# is in wsl
|
||||
# - user ran build/bootstrap/make, in which case make's working directory is in wsl
|
||||
# - user ran make, in which case cocmd's working directory is in wsl
|
||||
ifneq ($(findstring //wsl.localhost/,$(CURDIR) $(PWD)),)
|
||||
$(warning wsl2 interop is enabled)
|
||||
|
@ -90,7 +89,7 @@ UNAME_S := $(shell uname -s)
|
|||
|
||||
# apple still distributes a 17 year old version of gnu make
|
||||
ifeq ($(MAKE_VERSION), 3.81)
|
||||
$(error please use https://cosmo.zip/pub/cosmos/bin/make)
|
||||
$(error please use build/bootstrap/make)
|
||||
endif
|
||||
|
||||
LC_ALL = C
|
||||
|
@ -102,22 +101,23 @@ XARGS ?= xargs -P4 -rs8000
|
|||
DOT ?= dot
|
||||
CLANG = clang
|
||||
TMPDIR = o/tmp
|
||||
AR = $(BOOTSTRAP)/ar.ape
|
||||
CP = $(BOOTSTRAP)/cp.ape
|
||||
RM = $(BOOTSTRAP)/rm.ape -f
|
||||
GZIP = $(BOOTSTRAP)/gzip.ape
|
||||
ECHO = $(BOOTSTRAP)/echo.ape
|
||||
CHMOD = $(BOOTSTRAP)/chmod.ape
|
||||
TOUCH = $(BOOTSTRAP)/touch.ape
|
||||
PKG = $(BOOTSTRAP)/package.ape
|
||||
MKDEPS = $(BOOTSTRAP)/mkdeps
|
||||
ZIPOBJ = $(BOOTSTRAP)/zipobj
|
||||
ZIPCOPY = $(BOOTSTRAP)/zipcopy
|
||||
PECHECK = $(BOOTSTRAP)/pecheck
|
||||
FIXUPOBJ = $(BOOTSTRAP)/fixupobj
|
||||
OBJBINCOPY = $(BOOTSTRAP)/objbincopy
|
||||
MKDIR = $(BOOTSTRAP)/mkdir.ape -p
|
||||
COMPILE = $(BOOTSTRAP)/compile.ape -V9 -M2048m -P8192 $(QUOTA)
|
||||
AR = build/bootstrap/ar
|
||||
CP = build/bootstrap/cp
|
||||
RM = build/bootstrap/rm -f
|
||||
GZIP = build/bootstrap/gzip
|
||||
ECHO = build/bootstrap/echo
|
||||
CHMOD = build/bootstrap/chmod
|
||||
TOUCH = build/bootstrap/touch
|
||||
PKG = build/bootstrap/package
|
||||
MKDEPS = build/bootstrap/mkdeps
|
||||
ZIPOBJ = build/bootstrap/zipobj
|
||||
ZIPCOPY = build/bootstrap/zipcopy
|
||||
PECHECK = build/bootstrap/pecheck
|
||||
FIXUPOBJ = build/bootstrap/fixupobj
|
||||
MKDIR = build/bootstrap/mkdir -p
|
||||
COMPILE = build/bootstrap/compile -V9 -M2048m -P8192 $(QUOTA)
|
||||
|
||||
IGNORE := $(shell $(MKDIR) $(TMPDIR))
|
||||
|
||||
# the default build modes is empty string
|
||||
# on x86_64 hosts, MODE= is the same as MODE=x86_64
|
||||
|
@ -133,13 +133,14 @@ endif
|
|||
|
||||
ifneq ($(findstring aarch64,$(MODE)),)
|
||||
ARCH = aarch64
|
||||
HOSTS ?= pi pi5 studio freebsdarm
|
||||
HOSTS ?= pi studio freebsdarm
|
||||
else
|
||||
ARCH = x86_64
|
||||
HOSTS ?= freebsd rhel7 xnu openbsd netbsd win10 luna
|
||||
HOSTS ?= freebsd rhel7 xnu openbsd netbsd win10
|
||||
endif
|
||||
|
||||
ZIPOBJ_FLAGS += -a$(ARCH)
|
||||
IGNORE := $(shell $(MKDIR) $(TMPDIR))
|
||||
|
||||
export ADDR2LINE
|
||||
export LC_ALL
|
||||
|
@ -148,12 +149,9 @@ export MODE
|
|||
export SOURCE_DATE_EPOCH
|
||||
export TMPDIR
|
||||
|
||||
COSMOCC = .cosmocc/3.9.2
|
||||
BOOTSTRAP = $(COSMOCC)/bin
|
||||
COSMOCC = .cosmocc/3.3.5
|
||||
TOOLCHAIN = $(COSMOCC)/bin/$(ARCH)-linux-cosmo-
|
||||
DOWNLOAD := $(shell build/download-cosmocc.sh $(COSMOCC) 3.9.2 f4ff13af65fcd309f3f1cfd04275996fb7f72a4897726628a8c9cf732e850193)
|
||||
|
||||
IGNORE := $(shell $(MKDIR) $(TMPDIR))
|
||||
DOWNLOAD := $(shell build/download-cosmocc.sh $(COSMOCC) 3.3.5 db78fd8d3f8706e9dff4be72bf71d37a3f12062f212f407e1c33bc4af3780dd0)
|
||||
|
||||
AS = $(TOOLCHAIN)as
|
||||
CC = $(TOOLCHAIN)gcc
|
||||
|
@ -275,16 +273,10 @@ include libc/BUILD.mk #─┘
|
|||
include libc/sock/BUILD.mk #─┐
|
||||
include net/http/BUILD.mk # ├──ONLINE RUNTIME
|
||||
include third_party/musl/BUILD.mk # │ You can communicate with the network
|
||||
include third_party/regex/BUILD.mk # │
|
||||
include third_party/tr/BUILD.mk # │
|
||||
include third_party/sed/BUILD.mk # │
|
||||
include libc/system/BUILD.mk # │
|
||||
include libc/x/BUILD.mk # │
|
||||
include dsp/scale/BUILD.mk # │
|
||||
include dsp/mpeg/BUILD.mk # │
|
||||
include dsp/tty/BUILD.mk # │
|
||||
include dsp/audio/BUILD.mk # │
|
||||
include dsp/prog/BUILD.mk # │
|
||||
include dsp/BUILD.mk # │
|
||||
include third_party/stb/BUILD.mk # │
|
||||
include third_party/mbedtls/BUILD.mk # │
|
||||
|
@ -298,7 +290,9 @@ include third_party/libcxx/BUILD.mk # │
|
|||
include third_party/openmp/BUILD.mk # │
|
||||
include third_party/pcre/BUILD.mk # │
|
||||
include third_party/less/BUILD.mk # │
|
||||
include net/https/BUILD.mk #─┘
|
||||
include net/https/BUILD.mk # │
|
||||
include third_party/regex/BUILD.mk # │
|
||||
include third_party/bash/BUILD.mk #─┘
|
||||
include third_party/tidy/BUILD.mk
|
||||
include third_party/BUILD.mk
|
||||
include third_party/nsync/testing/BUILD.mk
|
||||
|
@ -317,6 +311,8 @@ include third_party/double-conversion/test/BUILD.mk
|
|||
include third_party/lua/BUILD.mk
|
||||
include third_party/tree/BUILD.mk
|
||||
include third_party/zstd/BUILD.mk
|
||||
include third_party/tr/BUILD.mk
|
||||
include third_party/sed/BUILD.mk
|
||||
include third_party/awk/BUILD.mk
|
||||
include third_party/hiredis/BUILD.mk
|
||||
include third_party/make/BUILD.mk
|
||||
|
@ -369,7 +365,6 @@ include test/libc/fmt/BUILD.mk
|
|||
include test/libc/time/BUILD.mk
|
||||
include test/libc/proc/BUILD.mk
|
||||
include test/libc/stdio/BUILD.mk
|
||||
include test/libc/system/BUILD.mk
|
||||
include test/libc/BUILD.mk
|
||||
include test/net/http/BUILD.mk
|
||||
include test/net/https/BUILD.mk
|
||||
|
@ -433,71 +428,67 @@ HTAGS: o/$(MODE)/hdrs-old.txt $(filter-out third_party/libcxx/%,$(HDRS)) #o/$(MO
|
|||
|
||||
loc: private .UNSANDBOXED = 1
|
||||
loc: o/$(MODE)/tool/build/summy
|
||||
find -name \*.h -or -name \*.hpp -or -name \*.c -or -name \*.cc -or -name \*.cpp -or -name \*.S -or -name \*.mk | \
|
||||
find -name \*.h -or -name \*.c -or -name \*.S | \
|
||||
$(XARGS) wc -l | grep total | awk '{print $$1}' | $<
|
||||
|
||||
COSMOPOLITAN = \
|
||||
CTL \
|
||||
DSP_AUDIO \
|
||||
LIBC_CALLS \
|
||||
LIBC_DLOPEN \
|
||||
LIBC_ELF \
|
||||
LIBC_FMT \
|
||||
LIBC_INTRIN \
|
||||
LIBC_IRQ \
|
||||
LIBC_LOG \
|
||||
LIBC_MEM \
|
||||
LIBC_NEXGEN32E \
|
||||
LIBC_NT_ADVAPI32 \
|
||||
LIBC_NT_BCRYPTPRIMITIVES \
|
||||
LIBC_NT_COMDLG32 \
|
||||
LIBC_NT_GDI32 \
|
||||
LIBC_NT_IPHLPAPI \
|
||||
LIBC_NT_KERNEL32 \
|
||||
LIBC_NT_NTDLL \
|
||||
LIBC_NT_PDH \
|
||||
LIBC_NT_POWRPROF \
|
||||
LIBC_NT_PSAPI \
|
||||
LIBC_NT_REALTIME \
|
||||
LIBC_NT_SHELL32 \
|
||||
LIBC_NT_SYNCHRONIZATION \
|
||||
LIBC_NT_USER32 \
|
||||
LIBC_NT_WS2_32 \
|
||||
LIBC_PROC \
|
||||
LIBC_RUNTIME \
|
||||
LIBC_SOCK \
|
||||
LIBC_STDIO \
|
||||
LIBC_STR \
|
||||
LIBC_SYSTEM \
|
||||
LIBC_SYSV \
|
||||
LIBC_SYSV_CALLS \
|
||||
LIBC_THREAD \
|
||||
LIBC_TINYMATH \
|
||||
LIBC_VGA \
|
||||
LIBC_X \
|
||||
# PLEASE: MAINTAIN TOPOLOGICAL ORDER
|
||||
# FROM HIGHEST LEVEL TO LOWEST LEVEL
|
||||
COSMOPOLITAN_OBJECTS = \
|
||||
TOOL_ARGS \
|
||||
NET_HTTP \
|
||||
THIRD_PARTY_COMPILER_RT \
|
||||
THIRD_PARTY_DLMALLOC \
|
||||
THIRD_PARTY_DOUBLECONVERSION \
|
||||
THIRD_PARTY_GDTOA \
|
||||
LIBC_SOCK \
|
||||
LIBC_NT_WS2_32 \
|
||||
LIBC_NT_IPHLPAPI \
|
||||
LIBC_X \
|
||||
THIRD_PARTY_GETOPT \
|
||||
LIBC_LOG \
|
||||
THIRD_PARTY_TZ \
|
||||
THIRD_PARTY_OPENMP \
|
||||
THIRD_PARTY_MUSL \
|
||||
THIRD_PARTY_ZLIB_GZ \
|
||||
THIRD_PARTY_LIBCXXABI \
|
||||
THIRD_PARTY_LIBUNWIND \
|
||||
THIRD_PARTY_MUSL \
|
||||
THIRD_PARTY_NSYNC \
|
||||
THIRD_PARTY_NSYNC_MEM \
|
||||
THIRD_PARTY_OPENMP \
|
||||
THIRD_PARTY_PUFF \
|
||||
LIBC_STDIO \
|
||||
THIRD_PARTY_GDTOA \
|
||||
THIRD_PARTY_REGEX \
|
||||
THIRD_PARTY_TZ \
|
||||
THIRD_PARTY_XED \
|
||||
LIBC_THREAD \
|
||||
LIBC_PROC \
|
||||
THIRD_PARTY_NSYNC_MEM \
|
||||
CTL \
|
||||
LIBC_MEM \
|
||||
THIRD_PARTY_DLMALLOC \
|
||||
LIBC_DLOPEN \
|
||||
LIBC_RUNTIME \
|
||||
THIRD_PARTY_NSYNC \
|
||||
LIBC_ELF \
|
||||
LIBC_IRQ \
|
||||
LIBC_CALLS \
|
||||
LIBC_SYSV_CALLS \
|
||||
LIBC_VGA \
|
||||
LIBC_NT_PSAPI \
|
||||
LIBC_NT_POWRPROF \
|
||||
LIBC_NT_PDH \
|
||||
LIBC_NT_GDI32 \
|
||||
LIBC_NT_COMDLG32 \
|
||||
LIBC_NT_USER32 \
|
||||
LIBC_NT_NTDLL \
|
||||
LIBC_NT_ADVAPI32 \
|
||||
LIBC_NT_SYNCHRONIZATION \
|
||||
LIBC_FMT \
|
||||
THIRD_PARTY_ZLIB \
|
||||
THIRD_PARTY_ZLIB_GZ \
|
||||
TOOL_ARGS \
|
||||
THIRD_PARTY_PUFF \
|
||||
THIRD_PARTY_COMPILER_RT \
|
||||
LIBC_TINYMATH \
|
||||
THIRD_PARTY_XED \
|
||||
LIBC_STR \
|
||||
LIBC_SYSV \
|
||||
LIBC_INTRIN \
|
||||
LIBC_NT_BCRYPTPRIMITIVES \
|
||||
LIBC_NT_KERNEL32 \
|
||||
LIBC_NEXGEN32E
|
||||
|
||||
COSMOPOLITAN_H_PKGS = \
|
||||
APE \
|
||||
DSP_AUDIO \
|
||||
LIBC \
|
||||
LIBC_CALLS \
|
||||
LIBC_ELF \
|
||||
|
@ -541,14 +532,14 @@ COSMOCC_PKGS = \
|
|||
THIRD_PARTY_INTEL
|
||||
|
||||
o/$(MODE)/cosmopolitan.a: \
|
||||
$(call reverse,$(call uniq,$(foreach x,$(COSMOPOLITAN),$($(x)))))
|
||||
$(foreach x,$(COSMOPOLITAN_OBJECTS),$($(x)_A_OBJS))
|
||||
|
||||
COSMOCC_HDRS = \
|
||||
$(wildcard libc/integral/*) \
|
||||
$(foreach x,$(COSMOCC_PKGS),$($(x)_HDRS)) \
|
||||
$(foreach x,$(COSMOCC_PKGS),$($(x)_INCS))
|
||||
|
||||
o/cosmocc.h.txt: Makefile libc $(MAKEFILES) $(call uniq,$(foreach x,$(HDRS) $(INCS),$(dir $(x)))) $(HDRS) $(INCS)
|
||||
o/cosmocc.h.txt: Makefile
|
||||
$(file >$@, $(call uniq,$(COSMOCC_HDRS)))
|
||||
|
||||
COSMOPOLITAN_H_ROOT_HDRS = \
|
||||
|
|
33
README.md
33
README.md
|
@ -3,12 +3,12 @@
|
|||
[](https://github.com/jart/cosmopolitan/actions/workflows/build.yml)
|
||||
# Cosmopolitan
|
||||
|
||||
[Cosmopolitan Libc](https://justine.lol/cosmopolitan/index.html) makes C/C++
|
||||
[Cosmopolitan Libc](https://justine.lol/cosmopolitan/index.html) makes C
|
||||
a build-once run-anywhere language, like Java, except it doesn't need an
|
||||
interpreter or virtual machine. Instead, it reconfigures stock GCC and
|
||||
Clang to output a POSIX-approved polyglot format that runs natively on
|
||||
Linux + Mac + Windows + FreeBSD + OpenBSD 7.3 + NetBSD + BIOS with the
|
||||
best possible performance and the tiniest footprint imaginable.
|
||||
Linux + Mac + Windows + FreeBSD + OpenBSD + NetBSD + BIOS with the best
|
||||
possible performance and the tiniest footprint imaginable.
|
||||
|
||||
## Background
|
||||
|
||||
|
@ -87,22 +87,15 @@ ape/apeinstall.sh
|
|||
```
|
||||
|
||||
You can now build the mono repo with any modern version of GNU Make. To
|
||||
bootstrap your build, you can install Cosmopolitan Make from this site:
|
||||
|
||||
https://cosmo.zip/pub/cosmos/bin/make
|
||||
|
||||
E.g.:
|
||||
make life easier, we've included one in the cosmocc toolchain, which is
|
||||
guaranteed to be compatible and furthermore includes our extensions for
|
||||
doing build system sandboxing.
|
||||
|
||||
```sh
|
||||
curl -LO https://cosmo.zip/pub/cosmos/bin/make
|
||||
./make -j8
|
||||
build/bootstrap/make -j8
|
||||
o//examples/hello
|
||||
```
|
||||
|
||||
After you've built the repo once, you can also use the make from your
|
||||
cosmocc at `.cosmocc/current/bin/make`. You might even prefer to alias
|
||||
make to `$COSMO/.cosmocc/current/bin/make`.
|
||||
|
||||
Since the Cosmopolitan repository is very large, you might only want to
|
||||
build one particular thing. Here's an example of a target that can be
|
||||
compiled relatively quickly, which is a simple POSIX test that only
|
||||
|
@ -110,7 +103,7 @@ depends on core LIBC packages.
|
|||
|
||||
```sh
|
||||
rm -rf o//libc o//test
|
||||
.cosmocc/current/bin/make o//test/posix/signal_test
|
||||
build/bootstrap/make o//test/posix/signal_test
|
||||
o//test/posix/signal_test
|
||||
```
|
||||
|
||||
|
@ -119,21 +112,21 @@ list out each individual one. For example if you wanted to build and run
|
|||
all the unit tests in the `TEST_POSIX` package, you could say:
|
||||
|
||||
```sh
|
||||
.cosmocc/current/bin/make o//test/posix
|
||||
build/bootstrap/make o//test/posix
|
||||
```
|
||||
|
||||
Cosmopolitan provides a variety of build modes. For example, if you want
|
||||
really tiny binaries (as small as 12kb in size) then you'd say:
|
||||
|
||||
```sh
|
||||
.cosmocc/current/bin/make m=tiny
|
||||
build/bootstrap/make m=tiny
|
||||
```
|
||||
|
||||
You can furthermore cut out the bloat of other operating systems, and
|
||||
have Cosmopolitan become much more similar to Musl Libc.
|
||||
|
||||
```sh
|
||||
.cosmocc/current/bin/make m=tinylinux
|
||||
build/bootstrap/make m=tinylinux
|
||||
```
|
||||
|
||||
For further details, see [//build/config.mk](build/config.mk).
|
||||
|
@ -251,12 +244,12 @@ server. You're welcome to join us! <https://discord.gg/FwAVVu7eJ4>
|
|||
|
||||
| Platform | Min Version | Circa |
|
||||
| :--- | ---: | ---: |
|
||||
| AMD | K8 | 2003 |
|
||||
| AMD | K8 Venus | 2005 |
|
||||
| Intel | Core | 2006 |
|
||||
| Linux | 2.6.18 | 2007 |
|
||||
| Windows | 8 [1] | 2012 |
|
||||
| Darwin (macOS) | 23.1.0+ | 2023 |
|
||||
| OpenBSD | 7.3 or earlier | 2023 |
|
||||
| OpenBSD | 7 | 2021 |
|
||||
| FreeBSD | 13 | 2020 |
|
||||
| NetBSD | 9.2 | 2021 |
|
||||
|
||||
|
|
26
ape/BUILD.mk
26
ape/BUILD.mk
|
@ -45,10 +45,10 @@ o/$(MODE)/ape: $(APE)
|
|||
|
||||
o/$(MODE)/ape/aarch64.lds: \
|
||||
ape/aarch64.lds \
|
||||
libc/zip.h \
|
||||
libc/zip.internal.h \
|
||||
libc/thread/tls.h \
|
||||
libc/calls/struct/timespec.h \
|
||||
libc/macros.h \
|
||||
libc/macros.internal.h \
|
||||
libc/str/str.h
|
||||
|
||||
APE_LOADER_LDFLAGS = \
|
||||
|
@ -162,8 +162,8 @@ o/$(MODE)/ape/ape-no-modify-self.o: \
|
|||
libc/dce.h \
|
||||
libc/elf/def.h \
|
||||
libc/thread/tls.h \
|
||||
libc/macho.h \
|
||||
libc/macros.h \
|
||||
libc/macho.internal.h \
|
||||
libc/macros.internal.h \
|
||||
libc/nexgen32e/uart.internal.h \
|
||||
libc/calls/metalfile.internal.h \
|
||||
libc/nt/pedef.internal.h \
|
||||
|
@ -188,8 +188,8 @@ o/$(MODE)/ape/ape-copy-self.o: \
|
|||
libc/dce.h \
|
||||
libc/elf/def.h \
|
||||
libc/thread/tls.h \
|
||||
libc/macho.h \
|
||||
libc/macros.h \
|
||||
libc/macho.internal.h \
|
||||
libc/macros.internal.h \
|
||||
libc/nexgen32e/uart.internal.h \
|
||||
libc/calls/metalfile.internal.h \
|
||||
libc/nt/pedef.internal.h \
|
||||
|
@ -218,10 +218,10 @@ o/$(MODE)/ape/loader-xnu-clang.asm: ape/loader.c
|
|||
@$(COMPILE) -AOBJECTIFY.c $(CLANG) -DSUPPORT_VECTOR=8 -S -g0 $(APE_LOADER_FLAGS)
|
||||
|
||||
o/$(MODE)/ape/ape.elf: o/$(MODE)/ape/ape.elf.dbg
|
||||
@$(COMPILE) -AOBJBINCOPY -w $(OBJBINCOPY) -f -o $@ $<
|
||||
@$(COMPILE) -AOBJBINCOPY -w build/bootstrap/objbincopy -f -o $@ $<
|
||||
|
||||
o/$(MODE)/ape/ape.macho: o/$(MODE)/ape/ape.elf.dbg
|
||||
@$(COMPILE) -AOBJBINCOPY -w $(OBJBINCOPY) -fm -o $@ $<
|
||||
@$(COMPILE) -AOBJBINCOPY -w build/bootstrap/objbincopy -fm -o $@ $<
|
||||
|
||||
APE_LOADER_LDFLAGS = \
|
||||
-static \
|
||||
|
@ -246,6 +246,8 @@ o/$(MODE)/ape: $(APE_CHECKS) \
|
|||
o/$(MODE)/ape/ape.lds \
|
||||
o/$(MODE)/ape/ape.elf \
|
||||
o/$(MODE)/ape/ape.macho \
|
||||
o/$(MODE)/ape/ape-copy-self.o \
|
||||
o/$(MODE)/ape/ape-no-modify-self.o
|
||||
|
||||
endif
|
||||
|
||||
|
@ -259,8 +261,8 @@ o/$(MODE)/ape/ape.o: \
|
|||
libc/thread/tls.h \
|
||||
ape/ape.internal.h \
|
||||
ape/macros.internal.h \
|
||||
libc/macho.h \
|
||||
libc/macros.h \
|
||||
libc/macho.internal.h \
|
||||
libc/macros.internal.h \
|
||||
libc/sysv/consts/prot.h \
|
||||
libc/nt/pedef.internal.h \
|
||||
libc/runtime/pc.internal.h \
|
||||
|
@ -281,7 +283,7 @@ o/$(MODE)/ape/ape.lds: \
|
|||
libc/dce.h \
|
||||
libc/elf/def.h \
|
||||
libc/elf/pf2prot.internal.h \
|
||||
libc/macros.h \
|
||||
libc/macros.internal.h \
|
||||
libc/nt/pedef.internal.h \
|
||||
libc/str/str.h \
|
||||
libc/zip.h
|
||||
libc/zip.internal.h
|
||||
|
|
|
@ -103,8 +103,10 @@ SECTIONS {
|
|||
*(.eh_frame_entry .eh_frame_entry.*)
|
||||
}
|
||||
|
||||
__eh_frame_hdr_start = SIZEOF(.eh_frame_hdr) > 0 ? ADDR(.eh_frame_hdr) : 0;
|
||||
__eh_frame_hdr_end = SIZEOF(.eh_frame_hdr) > 0 ? . : 0;
|
||||
.eh_frame : ONLY_IF_RO {
|
||||
KEEP(*(.eh_frame))
|
||||
*(.eh_frame.*)
|
||||
}
|
||||
|
||||
.gcc_except_table : ONLY_IF_RO {
|
||||
*(.gcc_except_table .gcc_except_table.*)
|
||||
|
@ -125,11 +127,9 @@ SECTIONS {
|
|||
. += CONSTANT(MAXPAGESIZE);
|
||||
. = DATA_SEGMENT_ALIGN(CONSTANT(MAXPAGESIZE), CONSTANT(COMMONPAGESIZE));
|
||||
|
||||
.eh_frame : {
|
||||
__eh_frame_start = .;
|
||||
.eh_frame : ONLY_IF_RW {
|
||||
KEEP(*(.eh_frame))
|
||||
*(.eh_frame.*)
|
||||
__eh_frame_end = .;
|
||||
}
|
||||
|
||||
.gnu_extab : ONLY_IF_RW {
|
||||
|
@ -259,9 +259,6 @@ SECTIONS {
|
|||
.debug_ranges 0 : { *(.debug_ranges) }
|
||||
.debug_macro 0 : { *(.debug_macro) }
|
||||
.debug_addr 0 : { *(.debug_addr) }
|
||||
.debug_names 0 : { *(.debug_names) }
|
||||
.debug_loclists 0 : { *(.debug_loclists) }
|
||||
.debug_str_offsets 0 : { *(.debug_str_offsets) }
|
||||
.ARM.attributes 0 : { KEEP(*(.ARM.attributes)) KEEP(*(.gnu.attributes)) }
|
||||
.note.gnu.arm.ident 0 : { KEEP(*(.note.gnu.arm.ident)) }
|
||||
|
||||
|
|
|
@ -16,12 +16,6 @@
|
|||
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
|
||||
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#ifndef __APPLE__
|
||||
#error "ape/ape-m1.c is for apple silicon. chances you want ape/loader.c"
|
||||
#endif
|
||||
#ifndef __aarch64__
|
||||
#error "ape/ape-m1.c is for apple silicon; you want: make o//ape/ape.macho"
|
||||
#endif
|
||||
#include <assert.h>
|
||||
#include <dispatch/dispatch.h>
|
||||
#include <dlfcn.h>
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
#include "libc/calls/metalfile.internal.h"
|
||||
#include "libc/dce.h"
|
||||
#include "libc/elf/def.h"
|
||||
#include "libc/macho.h"
|
||||
#include "libc/macho.internal.h"
|
||||
#include "libc/nexgen32e/uart.internal.h"
|
||||
#include "libc/nt/pedef.internal.h"
|
||||
#include "libc/runtime/pc.internal.h"
|
||||
|
|
30
ape/ape.lds
30
ape/ape.lds
|
@ -310,7 +310,7 @@ SECTIONS {
|
|||
. = ALIGN(__privileged_end > __privileged_start ? CONSTANT(COMMONPAGESIZE) : 0);
|
||||
/*END: morphable code */
|
||||
__privileged_start = .;
|
||||
*(.privileged .privileged.*)
|
||||
*(.privileged)
|
||||
__privileged_end = .;
|
||||
|
||||
KEEP(*(.ape.pad.text))
|
||||
|
@ -329,10 +329,6 @@ SECTIONS {
|
|||
*(.ubsan.types)
|
||||
*(.ubsan.data)
|
||||
|
||||
__eh_frame_hdr_start_actual = .;
|
||||
*(.eh_frame_hdr)
|
||||
__eh_frame_hdr_end_actual = .;
|
||||
|
||||
/* Legal Notices */
|
||||
__notices = .;
|
||||
KEEP(*(.notice))
|
||||
|
@ -386,13 +382,6 @@ SECTIONS {
|
|||
_tbss_end = .;
|
||||
} :Tls
|
||||
|
||||
.eh_frame : {
|
||||
__eh_frame_start = .;
|
||||
KEEP(*(.eh_frame))
|
||||
*(.eh_frame.*)
|
||||
__eh_frame_end = .;
|
||||
} :Ram
|
||||
|
||||
.data . : {
|
||||
/*BEGIN: Read/Write Data */
|
||||
#if SupportsWindows()
|
||||
|
@ -441,6 +430,7 @@ SECTIONS {
|
|||
KEEP(*(.piro.pad.data))
|
||||
*(.igot.plt)
|
||||
KEEP(*(.dataepilogue))
|
||||
|
||||
. = ALIGN(. != 0 ? CONSTANT(COMMONPAGESIZE) : 0);
|
||||
/*END: NT FORK COPYING */
|
||||
_edata = .;
|
||||
|
@ -520,9 +510,6 @@ SECTIONS {
|
|||
.debug_rnglists 0 : { *(.debug_rnglists) }
|
||||
.debug_macro 0 : { *(.debug_macro) }
|
||||
.debug_addr 0 : { *(.debug_addr) }
|
||||
.debug_names 0 : { *(.debug_names) }
|
||||
.debug_loclists 0 : { *(.debug_loclists) }
|
||||
.debug_str_offsets 0 : { *(.debug_str_offsets) }
|
||||
.gnu.attributes 0 : { KEEP(*(.gnu.attributes)) }
|
||||
.GCC.command.line 0 : { *(.GCC.command.line) }
|
||||
|
||||
|
@ -586,11 +573,11 @@ ape_rom_memsz = ape_rom_filesz;
|
|||
ape_rom_align = CONSTANT(COMMONPAGESIZE);
|
||||
ape_rom_rva = RVA(ape_rom_vaddr);
|
||||
|
||||
ape_ram_vaddr = ADDR(.eh_frame);
|
||||
ape_ram_vaddr = ADDR(.data);
|
||||
ape_ram_offset = ape_ram_vaddr - __executable_start;
|
||||
ape_ram_paddr = LOADADDR(.eh_frame);
|
||||
ape_ram_filesz = ADDR(.bss) - ADDR(.eh_frame);
|
||||
ape_ram_memsz = _end - ADDR(.eh_frame);
|
||||
ape_ram_paddr = LOADADDR(.data);
|
||||
ape_ram_filesz = ADDR(.bss) - ADDR(.data);
|
||||
ape_ram_memsz = _end - ADDR(.data);
|
||||
ape_ram_align = CONSTANT(COMMONPAGESIZE);
|
||||
ape_ram_rva = RVA(ape_ram_vaddr);
|
||||
|
||||
|
@ -600,7 +587,7 @@ ape_stack_offset = 0;
|
|||
ape_stack_vaddr = DEFINED(ape_stack_vaddr) ? ape_stack_vaddr : 0x700000000000;
|
||||
ape_stack_paddr = ape_ram_paddr + ape_ram_filesz;
|
||||
ape_stack_filesz = 0;
|
||||
ape_stack_memsz = DEFINED(ape_stack_memsz) ? ape_stack_memsz : 4 * 1024 * 1024;
|
||||
ape_stack_memsz = DEFINED(ape_stack_memsz) ? ape_stack_memsz : 8 * 1024 * 1024;
|
||||
|
||||
ape_note_offset = ape_cod_offset + (ape_note - ape_cod_vaddr);
|
||||
ape_note_filesz = ape_note_end - ape_note;
|
||||
|
@ -614,9 +601,6 @@ ape_text_memsz = ape_text_filesz;
|
|||
ape_text_align = CONSTANT(COMMONPAGESIZE);
|
||||
ape_text_rva = RVA(ape_text_vaddr);
|
||||
|
||||
__eh_frame_hdr_start = __eh_frame_hdr_end_actual > __eh_frame_hdr_start_actual ? __eh_frame_hdr_start_actual : 0;
|
||||
__eh_frame_hdr_end = __eh_frame_hdr_end_actual > __eh_frame_hdr_start_actual ? __eh_frame_hdr_end_actual : 0;
|
||||
|
||||
/* we roundup here because xnu wants the file load segments page-aligned */
|
||||
/* but we don't want to add the nop padding to the ape program, so we'll */
|
||||
/* let ape.S dd read past the end of the file into the wrapping binaries */
|
||||
|
|
|
@ -10,8 +10,8 @@ if [ ! -f ape/loader.c ]; then
|
|||
cd "$COSMO" || exit
|
||||
fi
|
||||
|
||||
if [ -x .cosmocc/current/bin/make ]; then
|
||||
MAKE=.cosmocc/current/bin/make
|
||||
if [ -x build/bootstrap/make ]; then
|
||||
MAKE=build/bootstrap/make
|
||||
else
|
||||
MAKE=make
|
||||
fi
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
|
||||
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/macros.h"
|
||||
#include "libc/macros.internal.h"
|
||||
|
||||
// Calls _start() function of loaded program.
|
||||
//
|
||||
|
|
|
@ -16,10 +16,10 @@
|
|||
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
|
||||
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/macho.h"
|
||||
#include "libc/macho.internal.h"
|
||||
#include "libc/sysv/consts/prot.h"
|
||||
#include "libc/dce.h"
|
||||
#include "libc/macros.h"
|
||||
#include "libc/macros.internal.h"
|
||||
|
||||
// Apple Mach-O Executable Headers
|
||||
// Fixups are applied by objbincopy
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#ifndef APE_MACROS_H_
|
||||
#define APE_MACROS_H_
|
||||
#include "libc/macros.h"
|
||||
#include "libc/macros.internal.h"
|
||||
#ifdef __ASSEMBLER__
|
||||
/* clang-format off */
|
||||
|
||||
|
|
|
@ -1,703 +0,0 @@
|
|||
# Actually Portable Executable Specification v0.1
|
||||
|
||||
Actually Portable Executable (APE) is an executable file format that
|
||||
polyglots the Windows Portable Executable (PE) format with a UNIX Sixth
|
||||
Edition style shell script that doesn't have a shebang. This makes it
|
||||
possible to produce a single file binary that executes on the stock
|
||||
installations of the many OSes and architectures.
|
||||
|
||||
## Supported OSes and Architectures
|
||||
|
||||
- AMD64
|
||||
- Linux
|
||||
- MacOS
|
||||
- Windows
|
||||
- FreeBSD
|
||||
- OpenBSD
|
||||
- NetBSD
|
||||
- BIOS
|
||||
|
||||
- ARM64
|
||||
- Linux
|
||||
- MacOS
|
||||
- FreeBSD
|
||||
- Windows (non-native)
|
||||
|
||||
## File Header
|
||||
|
||||
APE defines three separate file magics, all of which are 8 characters
|
||||
long. Any file that starts with one of these magic values can be
|
||||
considered an APE program.
|
||||
|
||||
### (1) APE MZ Magic
|
||||
|
||||
- ASCII: `MZqFpD='`
|
||||
- Hex: 4d 5a 71 46 70 44 3d 27
|
||||
|
||||
This is the canonical magic used by almost all APE programs. It enables
|
||||
maximum portability between OSes. When interpreted as a shell script, it
|
||||
is assigning a single quoted string to an unused variable. The shell
|
||||
will then ignore subsequent binary content that's placed inside the
|
||||
string.
|
||||
|
||||
It is strongly recommended that this magic value be immediately followed
|
||||
by a newline (\n or hex 0a) character. Some shells, e.g. FreeBSD SH and
|
||||
Zsh impose a binary safety check before handing off files that don't
|
||||
have a shebang to `/bin/sh`. That check applies to the first line, which
|
||||
can't contain NUL characters.
|
||||
|
||||
The letters were carefully chosen so as to be valid x86 instructions in
|
||||
all operating modes. This makes it possible to store a BIOS bootloader
|
||||
disk image inside an APE binary. For example, simple CLI programs built
|
||||
with Cosmopolitan Libc will boot from BIOS into long mode if they're
|
||||
treated as a floppy disk image.
|
||||
|
||||
The letters also allow for the possibility of being treated on x86-64 as
|
||||
a flat executable, where the PE / ELF / Mach-O executable structures are
|
||||
ignored, and execution simply begins at the beginning of the file,
|
||||
similar to how MS-DOS .COM binaries work.
|
||||
|
||||
The 0x4a relative offset of the magic causes execution to jump into the
|
||||
MS-DOS stub defined by Portable Executable. APE binaries built by Cosmo
|
||||
Libc use tricks in the MS-DOS stub to check the operating mode and then
|
||||
jump to the appropriate entrypoint, e.g. `_start()`.
|
||||
|
||||
#### Decoded as i8086
|
||||
|
||||
```asm
|
||||
dec %bp
|
||||
pop %dx
|
||||
jno 0x4a
|
||||
jo 0x4a
|
||||
```
|
||||
|
||||
#### Decoded as i386
|
||||
|
||||
```asm
|
||||
push %ebp
|
||||
pop %edx
|
||||
jno 0x4a
|
||||
jo 0x4a
|
||||
```
|
||||
|
||||
#### Decoded as x86-64
|
||||
|
||||
```asm
|
||||
rex.WRB
|
||||
pop %r10
|
||||
jno 0x4a
|
||||
jo 0x4a
|
||||
```
|
||||
|
||||
### (2) APE UNIX-Only Magic
|
||||
|
||||
- ASCII: `jartsr='`
|
||||
- Hex: 6a 61 72 74 73 72 3d 27
|
||||
|
||||
Being a novel executable format that was first published in 2020, the
|
||||
APE file format is less understood by industry tools compared to the PE,
|
||||
ELF, and Mach-O executable file formats, which have been around for
|
||||
decades. For this reason, APE programs that use the MZ magic above can
|
||||
attract attention from Windows AV software, which may be unwanted by
|
||||
developers who aren't interested in targeting the Windows platform.
|
||||
Therefore the `jartsr='` magic is defined which enables the creation of
|
||||
APE binaries that can safely target all non-Windows platforms. Even
|
||||
though this magic is less common, APE interpreters and binfmt-misc
|
||||
installations MUST support this.
|
||||
|
||||
It is strongly recommended that this magic value be immediately followed
|
||||
by a newline (\n or hex 0a) character. Some shells, e.g. FreeBSD SH and
|
||||
Zsh impose a binary safety check before handing off files that don't
|
||||
have a shebang to `/bin/sh`. That check applies to the first line, which
|
||||
can't contain NUL characters.
|
||||
|
||||
The letters were carefully chosen so as to be valid x86 instructions in
|
||||
all operating modes. This makes it possible to store a BIOS bootloader
|
||||
disk image inside an APE binary. For example, simple CLI programs built
|
||||
with Cosmopolitan Libc will boot from BIOS into long mode if they're
|
||||
treated as a floppy disk image.
|
||||
|
||||
The letters also allow for the possibility of being treated on x86-64 as
|
||||
a flat executable, where the PE / ELF / Mach-O executable structures are
|
||||
ignored, and execution simply begins at the beginning of the file,
|
||||
similar to how MS-DOS .COM binaries work.
|
||||
|
||||
The 0x78 relative offset of the magic causes execution to jump into the
|
||||
MS-DOS stub defined by Portable Executable. APE binaries built by Cosmo
|
||||
Libc use tricks in the MS-DOS stub to check the operating mode and then
|
||||
jump to the appropriate entrypoint, e.g. `_start()`.
|
||||
|
||||
#### Decoded as i8086 / i386 / x86-64
|
||||
|
||||
```asm
|
||||
push $0x61
|
||||
jb 0x78
|
||||
jae 0x78
|
||||
```
|
||||
|
||||
### (3) APE Debug Magic
|
||||
|
||||
- ASCII: `APEDBG='`
|
||||
- Hex: 41 50 45 44 42 47 3d 27
|
||||
|
||||
While APE files must be valid shell scripts, in practice, UNIX systems
|
||||
will oftentimes be configured to provide a faster safer alternative to
|
||||
loading an APE binary through `/bin/sh`. The Linux Kernel can be patched
|
||||
to have execve() recognize the APE format and directly load its embedded
|
||||
ELF header. Linux systems can also use binfmt-misc to recognize APE's MZ
|
||||
and jartsr magic, and pass them to a userspace program named `ape` that
|
||||
acts as an interpreter. In such environments, the need sometimes arises
|
||||
to be able to test that the `/bin/sh` is working correctly, in which
|
||||
case the `APEDBG='` magic is RECOMMENDED.
|
||||
|
||||
APE interpreters, execve() implementations, and binfmt-misc installs
|
||||
MUST ignore this magic. If necessary, steps can be taken to help files
|
||||
with this magic be passed to `/bin/sh` like a normal shebang-less shell
|
||||
script for execution.
|
||||
|
||||
## Embedded ELF Header
|
||||
|
||||
APE binaries MAY embed an ELF header inside them. Unlike conventional
|
||||
executable file formats, this header is not stored at a fixed offset.
|
||||
It's instead encoded as octal escape codes in a shell script `printf`
|
||||
statement. For example:
|
||||
|
||||
```
|
||||
printf '\177ELF\2\1\1\011\0\0\0\0\0\0\0\0\2\0\076\0\1\0\0\0\166\105\100\000\000\000\000\000\060\013\000\000\000\000\000\000\000\000\000\000\000\000\000\000\165\312\1\1\100\0\070\0\005\000\0\0\000\000\000\000'
|
||||
```
|
||||
|
||||
This `printf` statement MUST appear in the first 8192 bytes of the APE
|
||||
executable, so as to limit how much of the initial portion of a file an
|
||||
interpreter must load.
|
||||
|
||||
Multiple such `printf` statements MAY appear in the first 8192 bytes, in
|
||||
order to specify multiple architectures. For example, fat binaries built
|
||||
by the `apelink` program (provided by Cosmo Libc) will have two encoded
|
||||
ELF headers, for AMD64 and ARM64, each of which point into the proper
|
||||
file offsets for their respective native code. Therefore, kernels and
|
||||
interpreters which load the APE format directly MUST check the
|
||||
`e_machine` field of the `Elf64_Ehdr` that's decoded from the octal
|
||||
codes, before accepting a `printf` shell statement as valid.
|
||||
|
||||
These printf statements MUST always use only unescaped ASCII characters
|
||||
or octal escape codes. These printf statements MUST NOT use space saving
|
||||
escape codes such as `\n`. For example, rather than saying `\n` it would
|
||||
be valid to say `\012` instead. It's also valid to say `\12` but only if
|
||||
the encoded characters that follow aren't an octal digit.
|
||||
|
||||
For example, the following algorithm may be used for parsing octal:
|
||||
|
||||
```c
|
||||
static int ape_parse_octal(const unsigned char page[8192], int i, int *pc)
|
||||
{
|
||||
int c;
|
||||
if ('0' <= page[i] && page[i] <= '7') {
|
||||
c = page[i++] - '0';
|
||||
if ('0' <= page[i] && page[i] <= '7') {
|
||||
c *= 8;
|
||||
c += page[i++] - '0';
|
||||
if ('0' <= page[i] && page[i] <= '7') {
|
||||
c *= 8;
|
||||
c += page[i++] - '0';
|
||||
}
|
||||
}
|
||||
*pc = c;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
```
|
||||
|
||||
APE aware interpreters SHOULD only take `e_machine` into consideration.
|
||||
It is the responsibility of the `_start()` function to detect the OS.
|
||||
Therefore, multiple `printf` statements are only embedded in the shell
|
||||
script for different CPU architectures.
|
||||
|
||||
The OS ABI field of an APE embedded `Elf64_Ehdr` SHOULD be set to
|
||||
`ELFOSABI_FREEBSD`, since it's the only UNIX OS APE supports that
|
||||
actually checks the field. However different values MAY be chosen for
|
||||
binaries that don't intend to have FreeBSD in their support vector.
|
||||
|
||||
Counter-intuitively, the ARM64 ELF header is used on the MacOS ARM64
|
||||
platform when loading from fat binaries.
|
||||
|
||||
## Embedded Mach-O Header (x86-64 only)
|
||||
|
||||
APE shell scripts that support MacOS on AMD64 must use the `dd` command
|
||||
in a very specific way to specify how the embedded binary Macho-O header
|
||||
is copied backward to the start of the file. For example:
|
||||
|
||||
```
|
||||
dd if="$o" of="$o" bs=8 skip=433 count=66 conv=notrunc
|
||||
```
|
||||
|
||||
These `dd` statements have traditionally been generated by the GNU as
|
||||
and ld.bfd programs by encoding ASCII into 64-bit linker relocations,
|
||||
which necessitated a fixed width for integer values. It took several
|
||||
iterations over APE's history before we eventually got it right:
|
||||
|
||||
- `arg=" 9293"` is how we originally had ape do it
|
||||
- `arg=$(( 9293))` b/c busybox sh disliked quoted space
|
||||
- `arg=9293 ` is generated by modern apelink program
|
||||
|
||||
Software that parses the APE file format, which needs to extract the
|
||||
Macho-O x86-64 header SHOULD support the old binaries that use the
|
||||
previous encodings. To make backwards compatibility simple the following
|
||||
regular expression may be used, which generalizes to all defined
|
||||
formats:
|
||||
|
||||
```c
|
||||
regcomp(&rx,
|
||||
"bs=" // dd block size arg
|
||||
"(['\"] *)?" // #1 optional quote w/ space
|
||||
"(\\$\\(\\( *)?" // #2 optional math w/ space
|
||||
"([[:digit:]]+)" // #3
|
||||
"( *\\)\\))?" // #4 optional math w/ space
|
||||
"( *['\"])?" // #5 optional quote w/ space
|
||||
" +" //
|
||||
"skip=" // dd skip arg
|
||||
"(['\"] *)?" // #6 optional quote w/ space
|
||||
"(\\$\\(\\( *)?" // #7 optional math w/ space
|
||||
"([[:digit:]]+)" // #8
|
||||
"( *\\)\\))?" // #9 optional math w/ space
|
||||
"( *['\"])?" // #10 optional quote w/ space
|
||||
" +" //
|
||||
"count=" // dd count arg
|
||||
"(['\"] *)?" // #11 optional quote w/ space
|
||||
"(\\$\\(\\( *)?" // #12 optional math w/ space
|
||||
"([[:digit:]]+)", // #13
|
||||
REG_EXTENDED);
|
||||
```
|
||||
|
||||
For further details, see the canonical implementation in
|
||||
`cosmopolitan/tool/build/assimilate.c`.
|
||||
|
||||
## Static Linking
|
||||
|
||||
Actually Portable Executables are always statically linked. This
|
||||
revision of the specification does not define any facility for storing
|
||||
code in dynamic shared objects.
|
||||
|
||||
Cosmopolitan Libc provides a solution that enables APE binaries have
|
||||
limited access to dlopen(). By manually loading a platform-specific
|
||||
executable and asking the OS-specific libc's dlopen() to load
|
||||
OS-specific libraries, it becomes possible to use GPUs and GUIs. This
|
||||
has worked great for AI projects like llamafile.
|
||||
|
||||
There is no way for an Actually Portable Executable to interact with
|
||||
OS-specific dynamic shared object extension modules to programming
|
||||
languages. For example, a Lua interpreter compiled as an Actually
|
||||
Portable Executable would have no way of linking extension libraries
|
||||
downloaded from the Lua Rocks package manager. This is primarily because
|
||||
different OSes define incompatible ABIs.
|
||||
|
||||
While it was possible to polyglot PE+ELF+MachO to create multi-OS
|
||||
executables, it simply isn't possible to do that same thing for
|
||||
DLL+DYLIB+SO. Therefore, in order to have DSOs, APE would need to either
|
||||
choose one of the existing formats or invent one of its own, and then
|
||||
develop its own parallel ecosystem of extension software. In the future,
|
||||
the APE specification may expand to encompass this. However the focus to
|
||||
date has been exclusively on executables with limited dlopen() support.
|
||||
|
||||
## Application Binary Interface (ABI)
|
||||
|
||||
APE binaries use the System V ABI, as defined by:
|
||||
|
||||
- [System V ABI - AMD64 Architecture Processor Supplement](https://gitlab.com/x86-psABIs/x86-64-ABI)
|
||||
- AARCH64 has a uniform consensus defined by ARM Limited
|
||||
|
||||
There are however a few changes we've had to make.
|
||||
|
||||
### No Red Zone
|
||||
|
||||
Actually Portable Executables that have Windows and/or bare metal in
|
||||
their support vector MUST be compiled using `-mno-red-zone`. This is
|
||||
because, on Windows, DLLs and other software lurking in the va-space
|
||||
might use tricks like SetThreadContext() to take control of a thread
|
||||
whereas on bare metal, it's also generally accepted that kernel-mode
|
||||
code cannot assume a red zone either due to hardware interrupts that
|
||||
pull the exact same kinds of stunts.
|
||||
|
||||
APE software that only has truly System V ABI conformant OSes (e.g.
|
||||
Linux) in their support vector MAY use the red zone optimization.
|
||||
|
||||
### Thread Local Storage
|
||||
|
||||
#### aarch64
|
||||
|
||||
Here's the TLS memory layout on aarch64:
|
||||
|
||||
```
|
||||
x28
|
||||
%tpidr_el0
|
||||
│
|
||||
│ _Thread_local
|
||||
┌───┼───┬──────────┬──────────┐
|
||||
│tib│dtv│ .tdata │ .tbss │
|
||||
├───┴───┴──────────┴──────────┘
|
||||
│
|
||||
__get_tls()
|
||||
```
|
||||
|
||||
The ARM64 code in actually portable executables use the `x28` register
|
||||
to store the address of the thread information block. All aarch64 code
|
||||
linked into these executables SHOULD be compiled with `-ffixed-x28`
|
||||
which is supported by both Clang and GCC.
|
||||
|
||||
The runtime library for an actually portable executables MAY choose to
|
||||
use `tpidr_el0` instead, if OSes like MacOS aren't being targeted. For
|
||||
example, if the goal is to create a Linux-only fat binary linker program
|
||||
for Musl Libc, then choosing to use the existing `tpidr_el0` convention
|
||||
would be friction-free alternative.
|
||||
|
||||
It's not possible for an APE runtime that targets the full range of OSes
|
||||
to use the `tpidr_el0` register for TLS because Apple won't allow it. On
|
||||
MacOS ARM64 systems, this register can only be used by a runtime to
|
||||
implement the `sched_getcpu()` system call. It's reserved by MacOS.
|
||||
|
||||
#### x86-64
|
||||
|
||||
Here's the TLS memory layout on x86_64:
|
||||
|
||||
```
|
||||
__get_tls()
|
||||
│
|
||||
%fs OpenBSD/NetBSD
|
||||
_Thread_local │
|
||||
┌───┬──────────┬──────────┼───┐
|
||||
│pad│ .tdata │ .tbss │tib│
|
||||
└───┴──────────┴──────────┼───┘
|
||||
│
|
||||
Linux/FreeBSD/Windows/Mac %gs
|
||||
```
|
||||
|
||||
Quite possibly the greatest challenge in Actually Portable Executable
|
||||
working, has been overcoming the incompatibilities between OSes in how
|
||||
thread-local storage works on x86-64. The AMD64 architecture defines two
|
||||
special segment registers. Every OS uses one of these segment registers
|
||||
to implement TLS support. However not all OSes agree on which register
|
||||
to use. Some OSes grant userspace the power to define either of these
|
||||
registers to hold any value that is desired. Some OSes only effectively
|
||||
allow a single one of them to be changed. Lastly, some OSes, e.g.
|
||||
Windows, claim ownership of the memory layout these registers point
|
||||
towards too.
|
||||
|
||||
Here's a breakdown on how much power is granted to userspace runtimes by
|
||||
each OS when it comes to changing amd64 segment registers.
|
||||
|
||||
| | %fs | %gs |
|
||||
|---------|--------------|--------------|
|
||||
| Linux | unrestricted | unrestricted |
|
||||
| MacOS | inaccessible | unrestricted |
|
||||
| Windows | inaccessible | restricted |
|
||||
| FreeBSD | unrestricted | unrestricted |
|
||||
| NetBSD | unrestricted | broken |
|
||||
| OpenBSD | unrestricted | inaccessible |
|
||||
|
||||
Therefore, regardless of which register one we choose, some OSes are
|
||||
going to be incompatible.
|
||||
|
||||
APE binaries are always built with a Linux compiler. So another issue
|
||||
arises in the fact that our Linux-flavored GCC and Clang toolchains
|
||||
(which are used to produce cross-OS binaries) are also only capable of
|
||||
producing TLS instructions that use the %fs convention.
|
||||
|
||||
To solve these challenges, the `cosmocc` compiler will rewrite binary
|
||||
objects after they've been compiled by GCC, so that the `%gs` register
|
||||
is used, rather than `%fs`. Morphing x86-64 binaries after they've been
|
||||
compiled is normally difficult, due to the complexity of the machine
|
||||
instruction language. However GCC provides `-mno-tls-direct-seg-refs`
|
||||
which greatly reduces the complexity of this task. This flag forgoes
|
||||
some optimizations to make the generated code simpler. Rather than doing
|
||||
clever arithmetic with `%fs` prefixes, the compiler will always generate
|
||||
the thread information block address load as a separate instruction.
|
||||
|
||||
```c
|
||||
// Change AMD code to use %gs:0x30 instead of %fs:0
|
||||
// We assume -mno-tls-direct-seg-refs has been used
|
||||
static void ChangeTlsFsToGs(unsigned char *p, size_t n) {
|
||||
unsigned char *e = p + n - 9;
|
||||
while (p <= e) {
|
||||
// we're checking for the following expression:
|
||||
// 0144 == p[0] && // %fs
|
||||
// 0110 == (p[1] & 0373) && // rex.w (and ignore rex.r)
|
||||
// (0213 == p[2] || // mov reg/mem → reg (word-sized)
|
||||
// 0003 == p[2]) && // add reg/mem → reg (word-sized)
|
||||
// 0004 == (p[3] & 0307) && // mod/rm (4,reg,0) means sib → reg
|
||||
// 0045 == p[4] && // sib (5,4,0) → (rbp,rsp,0) → disp32
|
||||
// 0000 == p[5] && // displacement (von Neumann endian)
|
||||
// 0000 == p[6] && // displacement
|
||||
// 0000 == p[7] && // displacement
|
||||
// 0000 == p[8] // displacement
|
||||
uint64_t w = READ64LE(p) & READ64LE("\377\373\377\307\377\377\377\377");
|
||||
if ((w == READ64LE("\144\110\213\004\045\000\000\000") ||
|
||||
w == READ64LE("\144\110\003\004\045\000\000\000")) &&
|
||||
!p[8]) {
|
||||
p[0] = 0145; // change %fs to %gs
|
||||
p[5] = 0x30; // change 0 to 0x30
|
||||
p += 9;
|
||||
} else {
|
||||
++p;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
By favoring `%gs` we've now ensured friction-free compatibility for the
|
||||
APE runtime on MacOS, Linux, and FreeBSD which are all able to conform
|
||||
easily to this convention. However additional work needs to be done at
|
||||
runtime when an APE program is started on Windows, OpenBSD, and NetBSD.
|
||||
On these platforms, all executable pages must be faulted and morphed to
|
||||
fixup the TLS instructions.
|
||||
|
||||
On OpenBSD and NetBSD, this is as simple as undoing the example
|
||||
operation above. Earlier at compile-time we turned `%fs` into `%gs`.
|
||||
Now, at runtime, `%gs` must be turned back into `%fs`. Since the
|
||||
executable is morphing itself, this is easier said than done.
|
||||
|
||||
OpenBSD for example enforces a `W^X` invariant. Code that's executing
|
||||
can't modify itself at the same time. The way Cosmopolitan solves this
|
||||
is by defining a special part of the binary called `.text.privileged`.
|
||||
This section is aligned to page boundaries. A GNU ld linker script is
|
||||
used to ensure that code which morphs code is placed into this section,
|
||||
through the use of a header-defined cosmo-specific keyword `privileged`.
|
||||
Additionally, the `fixupobj` program is used by the Cosmo build system
|
||||
to ensure that compiled objects don't contain privileged functions that
|
||||
call non-privileged functions. Needless to say, `mprotect()` needs to be
|
||||
a privileged function, so that it can be used to disable the execute bit
|
||||
on all other parts of the executable except for the privileged section,
|
||||
thereby making it writable. Once this has been done, code can change.
|
||||
|
||||
On Windows the displacement bytes of the TLS instruction are changed to
|
||||
use the `%gs:0x1480+i*8` ABI where `i` is a number assigned by the WIN32
|
||||
`TlsAlloc()` API. This avoids the need to call `TlsGetValue()` which is
|
||||
implemented this exact same way under the hood. Even though 0x1480 isn't
|
||||
explicitly documented by MSDN, this ABI is believed to be stable because
|
||||
MSVC generates binaries that use this offset directly. The only caveat
|
||||
is that `TlsAlloc()` must be called as early in the runtime init as
|
||||
possible, to ensure an index less than 64 is returned.
|
||||
|
||||
### Thread Information Block (TIB)
|
||||
|
||||
The Actually Portable Executable Thread Information Block (TIB) is
|
||||
defined by this version of the specification as follows:
|
||||
|
||||
- The 64-bit TIB self-pointer is stored at offset 0x00.
|
||||
- The 64-bit TIB self-pointer is also stored at offset 0x30.
|
||||
- The 32-bit `errno` value is stored at offset 0x3c.
|
||||
|
||||
All other parts of the thread information block should be considered
|
||||
unspecified and therefore reserved for future specifications.
|
||||
|
||||
The APE thread information block is aligned on a 64-byte boundary.
|
||||
|
||||
Cosmopolitan Libc v3.5.8 (c. 2024-07-21) currently implements a thread
|
||||
information block that's 512 bytes in size.
|
||||
|
||||
### Foreign Function Calls
|
||||
|
||||
Even though APE programs always use the System V ABI, there arises the
|
||||
occasional need to interface with foreign functions, e.g. WIN32. The
|
||||
`__attribute__((__ms_abi__))` annotation introduced by GCC v6 is used
|
||||
for this purpose.
|
||||
|
||||
The ability to change a function's ABI on a case-by-case basis is
|
||||
surprisingly enough supported by GCC, Clang, NVCC, and even the AMD HIP
|
||||
compilers for both UNIX systems and Windows. All of these compilers
|
||||
support both the System V ABI and the Microsoft x64 ABI.
|
||||
|
||||
APE binaries will actually favor the Microsoft ABI even when running on
|
||||
UNIX OSes for certain dlopen() use-cases. For example, if we control the
|
||||
code to a CUDA module, which we compile on each OS separately from our
|
||||
main APE binary, then any function that's inside the APE binary whose
|
||||
pointer may be passed into a foreign module SHOULD be compiled to use
|
||||
the Microsoft ABI. This is because in practice the OS-specific module
|
||||
may need to be compiled by MSVC, where MS ABI is the *only* ABI, which
|
||||
forces our UNIX programs to partially conform. Thankfully, all UNIX
|
||||
compilers support doing it on a case-by-case basis.
|
||||
|
||||
### Char Signedness
|
||||
|
||||
Actually Portable Executable defines `char` as signed.
|
||||
|
||||
Therefore conformant APE software MUST use `-fsigned-char` when building
|
||||
code for aarch64, as well as any other architecture that (unlike x86-64)
|
||||
would otherwise define `char` as being `unsigned char` by default.
|
||||
|
||||
This decision was one of the cases where it made sense to offer a more
|
||||
consistent runtime experience for fat multi-arch binaries. However you
|
||||
SHOULD still write code to assume `char` can go either way. But if all
|
||||
you care about is using APE, then you CAN assume `char` is signed.
|
||||
|
||||
### Long Double
|
||||
|
||||
On AMD64 platforms, APE binaries define `long double` as 80-bit.
|
||||
|
||||
On ARM64 platforms, APE binaries define `long double` as 128-bit.
|
||||
|
||||
We accept inconsistency in this case, because hardware acceleration is
|
||||
far more valuable than stylistic consistency in the case of mathematics.
|
||||
|
||||
One challenge arises on AMD64 for supporting `long double` across OSes.
|
||||
Unlike UNIX systems, the Windows Executive on x86-64 initializes the x87
|
||||
FPU to have double (64-bit) precision rather than 80-bit. That's because
|
||||
code compiled by MSVC treats `long double` as though it were `double` to
|
||||
prefer always using the more modern SSE instructions. However System V
|
||||
requires genuine 80-bit `long double` support on AMD64.
|
||||
|
||||
Therefore, if an APE program detects that it's been started on a Windows
|
||||
x86-64 system, then it SHOULD use the following assembly to initialize
|
||||
the x87 FPU in System V ABI mode.
|
||||
|
||||
```asm
|
||||
fldcw 1f(%rip)
|
||||
.rodata
|
||||
.balign 2
|
||||
// 8087 FPU Control Word
|
||||
// IM: Invalid Operation ───────────────┐
|
||||
// DM: Denormal Operand ───────────────┐│
|
||||
// ZM: Zero Divide ───────────────────┐││
|
||||
// OM: Overflow ─────────────────────┐│││
|
||||
// UM: Underflow ───────────────────┐││││
|
||||
// PM: Precision ──────────────────┐│││││
|
||||
// PC: Precision Control ───────┐ ││││││
|
||||
// {float,∅,double,long double}│ ││││││
|
||||
// RC: Rounding Control ──────┐ │ ││││││
|
||||
// {even, →-∞, →+∞, →0} │┌┤ ││││││
|
||||
// ┌┤││ ││││││
|
||||
// d││││rr││││││
|
||||
1: .short 0b00000000000000000001101111111
|
||||
.previous
|
||||
```
|
||||
|
||||
## Executable File Alignment
|
||||
|
||||
Actually Portable Executable is a statically-linked flat executable file
|
||||
format that is, as a thing in itself, agnostic to file alignments. For
|
||||
example, the shell script payload at the beginning of the file and its
|
||||
statements have no such requirements. Alignment requirements are however
|
||||
imposed by the executable formats that APE wraps.
|
||||
|
||||
1. ELF requires that file offsets be congruent with virtual addresses
|
||||
modulo the CPU page size. So when we add a shell script to the start
|
||||
of an executable, we need to round up to the page size in order to
|
||||
maintain ELF's invariant. Although no such roundup is required on the
|
||||
program segments once the invariant is restored. ELF loaders will
|
||||
happily map program headers from arbitrary file intervals (which may
|
||||
overlap) onto arbitrarily virtual intervals (which don't need to be
|
||||
contiguous). In order to do that, the loaders will generally use
|
||||
UNIX's mmap() function which is more restrictive and only accepts
|
||||
addresses and offsets that are page aligned. To make it possible to
|
||||
map an unaligned ELF program header that could potentially start and
|
||||
stop at any byte, ELF loaders round-out the intervals, which means
|
||||
adjacent unrelated data might also get mapped, which may need to be
|
||||
explicitly zero'd. Thanks to the cleverness of ELF, it's possible to
|
||||
have an executable file be very tiny, without needing any alignment
|
||||
bytes, and it'll be loaded into a properly aligned virtual space
|
||||
where segments can be as sparse as we want them to be.
|
||||
|
||||
2. PE doesn't care about congruence and instead defines two separate
|
||||
kinds of alignment. First, PE requires that the layout of segment
|
||||
memory inside the file be aligned on at minimum the classic 512 byte
|
||||
MS-DOS page size. This means that, unlike ELF, some alignment padding
|
||||
may need to be encoded into the file, making it slightly larger. Next
|
||||
PE imposes an alignment restriction on segments once they've been
|
||||
mapped into the virtual address space, which must be rounded to the
|
||||
system page size. Like ELF, PE segments need to be properly ordered
|
||||
but they're allowed to drift apart once mapped in a non-contiguous
|
||||
sparsely mapped way. When inserting shell script content at the start
|
||||
of a PE file, the most problematic thing is the need to round up to
|
||||
the 64kb system granularity, which results in a lot of needless bytes
|
||||
of padding being inserted by a naive second-pass linker.
|
||||
|
||||
3. Apple's Mach-O format is the strictest of them all. While both ELF
|
||||
and PE are defined in such a way that invites great creativity, XNU
|
||||
will simply refuse to an executable that does anything creative with
|
||||
alignment. All loaded segments need to both start and end on a page
|
||||
aligned address. XNU also wants segments to be contiguous similar to
|
||||
portable executable, except it applies to both the file and virtual
|
||||
spaces, which must follow the same structure.
|
||||
|
||||
Actually Portable Executables must conform to the strictest requirements
|
||||
demanded by the support vector. Therefore an APE binary that has headers
|
||||
for all three of the above executable formats MUST conform to the Apple
|
||||
way of doing things. GNU ld linker scripts aren't very good at producing
|
||||
ELF binaries that rigidly conform to this simple naive layout. There are
|
||||
so many ways things can go wrong, where third party code might slip its
|
||||
own custom section name in-between the linker script sections that are
|
||||
explicitly defined, thereby causing ELF's powerful features to manifest
|
||||
and the resulting content overlapping. The best `ld` flag that helps is
|
||||
`--orphan-handling=error` which can help with explaining such mysteries.
|
||||
|
||||
While Cosmopolitan was originally defined to just use stock GNU tools,
|
||||
this proved intractable over time, and the project has been evolving in
|
||||
the direction of building its own. Inventing the `apelink` program was
|
||||
what enabled the project to achieve multi-architecture binaries whereas
|
||||
previously it was only possible to do multi-OS binaries. In the future,
|
||||
our hope is that a fast power linker like Mold can be adapted to produce
|
||||
fat APE binaries directly from object files in one pass.
|
||||
|
||||
## Position Independent Code
|
||||
|
||||
APE doesn't currently support position independent executable formats.
|
||||
This is because APE was originally written for the GNU linker, where PIC
|
||||
and PIE were after-thoughts and never fully incorporated with the older
|
||||
more powerful linker script techniques upon which APE relies. Future
|
||||
iterations of this specification are intended to converge on modern
|
||||
standards, as our tooling becomes developed enough to support it.
|
||||
|
||||
However this only applies to the wrapped executable formats themselves.
|
||||
While our convention to date has been to always load ELF programs at the
|
||||
4mb mark, this is not guaranteed across OSes and architectures. Programs
|
||||
should have no expectations that a program will be loaded to any given
|
||||
address. For example, Cosmo currently implements APE on AARCH64 as
|
||||
loading executables to a starting address of 0x000800000000. This
|
||||
address occupies a sweet spot of requirements.
|
||||
|
||||
## Address Space
|
||||
|
||||
In order to create a single binary that supports as many platforms as
|
||||
possible without needing to be recompiled, there's a very narrow range
|
||||
of addresses that can be used. That range is somewhere between 32 bits
|
||||
and 39 bits.
|
||||
|
||||
- Embedded devices that claim to be 64-bit will oftentimes only support
|
||||
a virtual address space that's 39 bits in size.
|
||||
|
||||
- We can't load executable images on AARCH64 beneath 0x100000000 (4gb)
|
||||
because Apple forbids doing that, possibly in an effort to enforce a
|
||||
best practice for spotting 32-bit to 64-bit transition bugs. Please
|
||||
note that this restriction only applies to Apple ARM64 systems. The
|
||||
x86-64 version of XNU will happily load APE binaries to 0x00400000.
|
||||
|
||||
- The AMD64 architecture on desktops and servers can usually be counted
|
||||
upon to provide a 47-bit address space. The Linux Kernel for instance
|
||||
grants each userspace program full dominion over addresses 0x00200000
|
||||
through 0x00007fffffffffff provided the hardware supports this. On
|
||||
modern workstations supporting Intel and AMD's new PML5T feature which
|
||||
virtualizes memory using a radix trie that's five layers deep, Linux
|
||||
is able to offer userspace its choice of fixed addresses from
|
||||
0x00200000 through 0x00ffffffffffffff. The only exception to this rule
|
||||
we've encountered so far is that Windows 7 and Windows Vista behaved
|
||||
similar to embedded devices in reducing the number of va bits.
|
||||
|
||||
## Page Size
|
||||
|
||||
APE software MUST be page size agnostic. For many years the industry had
|
||||
converged on a strong consensus of having a page size that's 4096 bytes.
|
||||
However this convention was never guaranteed. New computers have become
|
||||
extremely popular, such as Apple Silicon, that use a 16kb page size.
|
||||
|
||||
By convention, Cosmopolitan Libc currently generates ELF headers for
|
||||
x86-64 that are strictly aligned on a 4096-byte page size. On ARM64
|
||||
Cosmopolitan is currently implemented to always generate ELF headers
|
||||
aligned on a 16kb page size.
|
||||
|
||||
In addition to being page size agnostic, APE software that cares about
|
||||
working correctly on Windows needs to be aware of the concept of
|
||||
allocation granularity. While the page size on Windows is generally 4kb
|
||||
in size, memory mappings can only be created on addresses that aligned
|
||||
to the system allocation granularity, which is generally 64kb. If you
|
||||
use a function like mmap() with Cosmopolitan Libc, then the `addr` and
|
||||
`offset` parameters need to be aligned to `sysconf(_SC_GRANSIZE)` or
|
||||
else your software won't work on Windows. Windows has other limitations
|
||||
too, such as lacking the ability to carve or punch holes in mappings.
|
|
@ -18,7 +18,7 @@
|
|||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/dce.h"
|
||||
#include "ape/ape.h"
|
||||
#include "libc/macros.h"
|
||||
#include "libc/macros.internal.h"
|
||||
|
||||
#ifdef __aarch64__
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
|
||||
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/macros.h"
|
||||
#include "libc/macros.internal.h"
|
||||
|
||||
// Invokes system call.
|
||||
//
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
build/bootstrap/ar
Executable file
BIN
build/bootstrap/ar
Executable file
Binary file not shown.
BIN
build/bootstrap/chmod
Executable file
BIN
build/bootstrap/chmod
Executable file
Binary file not shown.
Binary file not shown.
BIN
build/bootstrap/compile
Executable file
BIN
build/bootstrap/compile
Executable file
Binary file not shown.
BIN
build/bootstrap/cp
Executable file
BIN
build/bootstrap/cp
Executable file
Binary file not shown.
BIN
build/bootstrap/echo
Executable file
BIN
build/bootstrap/echo
Executable file
Binary file not shown.
BIN
build/bootstrap/fixupobj
Executable file
BIN
build/bootstrap/fixupobj
Executable file
Binary file not shown.
BIN
build/bootstrap/gzip
Executable file
BIN
build/bootstrap/gzip
Executable file
Binary file not shown.
BIN
build/bootstrap/make
Executable file
BIN
build/bootstrap/make
Executable file
Binary file not shown.
BIN
build/bootstrap/mkdeps
Executable file
BIN
build/bootstrap/mkdeps
Executable file
Binary file not shown.
BIN
build/bootstrap/mkdir
Executable file
BIN
build/bootstrap/mkdir
Executable file
Binary file not shown.
BIN
build/bootstrap/objbincopy
Executable file
BIN
build/bootstrap/objbincopy
Executable file
Binary file not shown.
BIN
build/bootstrap/package
Executable file
BIN
build/bootstrap/package
Executable file
Binary file not shown.
BIN
build/bootstrap/pecheck
Executable file
BIN
build/bootstrap/pecheck
Executable file
Binary file not shown.
BIN
build/bootstrap/rm
Executable file
BIN
build/bootstrap/rm
Executable file
Binary file not shown.
BIN
build/bootstrap/touch
Executable file
BIN
build/bootstrap/touch
Executable file
Binary file not shown.
BIN
build/bootstrap/zipcopy
Executable file
BIN
build/bootstrap/zipcopy
Executable file
Binary file not shown.
BIN
build/bootstrap/zipobj
Executable file
BIN
build/bootstrap/zipobj
Executable file
Binary file not shown.
|
@ -14,6 +14,7 @@ ENABLE_FTRACE = 1
|
|||
CONFIG_OFLAGS ?= -g -ggdb
|
||||
CONFIG_CCFLAGS += -O2 $(BACKTRACES)
|
||||
CONFIG_CPPFLAGS += -DSYSDEBUG
|
||||
TARGET_ARCH ?= -msse3
|
||||
endif
|
||||
ifeq ($(MODE), x86_64)
|
||||
ENABLE_FTRACE = 1
|
||||
|
@ -63,6 +64,7 @@ ENABLE_FTRACE = 1
|
|||
CONFIG_CCFLAGS += $(BACKTRACES) -O
|
||||
CONFIG_CPPFLAGS += -DSYSDEBUG -DDWARFLESS
|
||||
CONFIG_LDFLAGS += -S
|
||||
TARGET_ARCH ?= -msse3
|
||||
endif
|
||||
|
||||
# Optimized Mode
|
||||
|
@ -82,7 +84,7 @@ ENABLE_FTRACE = 1
|
|||
CONFIG_OFLAGS ?= -g -ggdb
|
||||
CONFIG_CPPFLAGS += -DNDEBUG -DSYSDEBUG
|
||||
CONFIG_CCFLAGS += $(BACKTRACES) -O3 -fmerge-all-constants
|
||||
CONFIG_TARGET_ARCH ?= -march=native
|
||||
TARGET_ARCH ?= -march=native
|
||||
endif
|
||||
|
||||
# Optimized Linux Mode
|
||||
|
@ -100,20 +102,7 @@ CONFIG_OFLAGS ?= -g -ggdb
|
|||
CONFIG_CPPFLAGS += -DNDEBUG -DSYSDEBUG -DSUPPORT_VECTOR=1
|
||||
CONFIG_CCFLAGS += -O3 -fmerge-all-constants
|
||||
CONFIG_COPTS += -mred-zone
|
||||
CONFIG_TARGET_ARCH ?= -march=native
|
||||
endif
|
||||
ifeq ($(MODE), x86_64-optlinux)
|
||||
CONFIG_OFLAGS ?= -g -ggdb
|
||||
CONFIG_CPPFLAGS += -DNDEBUG -DSYSDEBUG -DSUPPORT_VECTOR=1
|
||||
CONFIG_CCFLAGS += -O3 -fmerge-all-constants
|
||||
CONFIG_COPTS += -mred-zone
|
||||
CONFIG_TARGET_ARCH ?= -march=native
|
||||
endif
|
||||
ifeq ($(MODE), aarch64-optlinux)
|
||||
CONFIG_OFLAGS ?= -g -ggdb
|
||||
CONFIG_CPPFLAGS += -DNDEBUG -DSYSDEBUG -DSUPPORT_VECTOR=1
|
||||
CONFIG_CCFLAGS += -O3 -fmerge-all-constants
|
||||
CONFIG_COPTS += -mred-zone
|
||||
TARGET_ARCH ?= -march=native
|
||||
endif
|
||||
|
||||
# Release Mode
|
||||
|
@ -134,6 +123,7 @@ endif
|
|||
ifeq ($(MODE), rel)
|
||||
CONFIG_CPPFLAGS += -DNDEBUG -DDWARFLESS
|
||||
CONFIG_CCFLAGS += $(BACKTRACES) -O2
|
||||
TARGET_ARCH ?= -msse3
|
||||
PYFLAGS += -O1
|
||||
endif
|
||||
|
||||
|
@ -149,32 +139,18 @@ endif
|
|||
ifeq ($(MODE), dbg)
|
||||
ENABLE_FTRACE = 1
|
||||
CONFIG_OFLAGS ?= -g -ggdb
|
||||
OVERRIDE_CFLAGS += -O0
|
||||
OVERRIDE_CXXFLAGS += -O0
|
||||
CONFIG_CPPFLAGS += -DMODE_DBG -D__SANITIZE_UNDEFINED__ -Wno-unused-variable -Wno-unused-but-set-variable
|
||||
CONFIG_CCFLAGS += $(BACKTRACES) -DSYSDEBUG
|
||||
CONFIG_COPTS += -fsanitize=undefined
|
||||
OVERRIDE_CCFLAGS += -fno-pie
|
||||
QUOTA ?= -C64 -L300
|
||||
endif
|
||||
ifeq ($(MODE), x86_64-dbg)
|
||||
ENABLE_FTRACE = 1
|
||||
CONFIG_OFLAGS ?= -g -ggdb
|
||||
OVERRIDE_CFLAGS += -O0
|
||||
OVERRIDE_CXXFLAGS += -O0
|
||||
CONFIG_CPPFLAGS += -DMODE_DBG -D__SANITIZE_UNDEFINED__ -Wno-unused-variable -Wno-unused-but-set-variable
|
||||
CONFIG_CCFLAGS += $(BACKTRACES) -DSYSDEBUG
|
||||
CONFIG_CPPFLAGS += -DMODE_DBG -D__SANITIZE_UNDEFINED__
|
||||
CONFIG_CCFLAGS += $(BACKTRACES) -DSYSDEBUG -O0 -fno-inline
|
||||
CONFIG_COPTS += -fsanitize=undefined
|
||||
TARGET_ARCH ?= -msse3
|
||||
OVERRIDE_CCFLAGS += -fno-pie
|
||||
QUOTA ?= -C64 -L300
|
||||
endif
|
||||
ifeq ($(MODE), aarch64-dbg)
|
||||
ENABLE_FTRACE = 1
|
||||
CONFIG_OFLAGS ?= -g -ggdb
|
||||
OVERRIDE_CFLAGS += -O0 -fdce
|
||||
OVERRIDE_CXXFLAGS += -O0 -fdce
|
||||
CONFIG_CPPFLAGS += -DMODE_DBG -D__SANITIZE_UNDEFINED__ -Wno-unused-variable -Wno-unused-but-set-variable
|
||||
CONFIG_CCFLAGS += $(BACKTRACES) -DSYSDEBUG
|
||||
CONFIG_CPPFLAGS += -DMODE_DBG -D__SANITIZE_UNDEFINED__
|
||||
CONFIG_CCFLAGS += $(BACKTRACES) -DSYSDEBUG -O0 -fno-inline -fdce
|
||||
CONFIG_COPTS += -fsanitize=undefined
|
||||
QUOTA ?= -C64 -L300
|
||||
endif
|
||||
|
@ -194,6 +170,7 @@ ENABLE_FTRACE = 1
|
|||
CONFIG_OFLAGS ?= -g -ggdb
|
||||
CONFIG_CCFLAGS += $(BACKTRACES) -O2
|
||||
CONFIG_CPPFLAGS += -DSYSDEBUG -DSUPPORT_VECTOR=121
|
||||
TARGET_ARCH ?= -msse3
|
||||
endif
|
||||
|
||||
# Tiny Mode
|
||||
|
@ -223,6 +200,8 @@ CONFIG_CCFLAGS += \
|
|||
-momit-leaf-frame-pointer \
|
||||
-foptimize-sibling-calls \
|
||||
-DDWARFLESS
|
||||
TARGET_ARCH ?= \
|
||||
-msse3
|
||||
PYFLAGS += \
|
||||
-O2 \
|
||||
-B
|
||||
|
@ -242,6 +221,8 @@ CONFIG_CCFLAGS += \
|
|||
-momit-leaf-frame-pointer \
|
||||
-foptimize-sibling-calls \
|
||||
-DDWARFLESS
|
||||
TARGET_ARCH ?= \
|
||||
-msse3
|
||||
PYFLAGS += \
|
||||
-O2 \
|
||||
-B
|
||||
|
@ -293,6 +274,8 @@ CONFIG_CCFLAGS += \
|
|||
-fno-align-jumps \
|
||||
-fno-align-labels \
|
||||
-fno-align-loops
|
||||
TARGET_ARCH ?= \
|
||||
-msse3
|
||||
endif
|
||||
|
||||
# Linux+BSD Tiny Mode
|
||||
|
@ -322,6 +305,8 @@ CONFIG_CCFLAGS += \
|
|||
-fno-align-jumps \
|
||||
-fno-align-labels \
|
||||
-fno-align-loops
|
||||
TARGET_ARCH ?= \
|
||||
-msse3
|
||||
endif
|
||||
|
||||
# Unix Tiny Mode
|
||||
|
@ -350,6 +335,8 @@ CONFIG_CCFLAGS += \
|
|||
-fno-align-jumps \
|
||||
-fno-align-labels \
|
||||
-fno-align-loops
|
||||
TARGET_ARCH ?= \
|
||||
-msse3
|
||||
endif
|
||||
|
||||
# Tiny Metallic Unix Mode
|
||||
|
@ -378,6 +365,8 @@ CONFIG_CCFLAGS += \
|
|||
-fno-align-jumps \
|
||||
-fno-align-labels \
|
||||
-fno-align-loops
|
||||
TARGET_ARCH ?= \
|
||||
-msse3
|
||||
endif
|
||||
|
||||
# no x87 instructions mode
|
||||
|
@ -399,6 +388,7 @@ ENABLE_FTRACE = 1
|
|||
CONFIG_COPTS += -mlong-double-64
|
||||
CONFIG_CCFLAGS += $(BACKTRACES) -O2
|
||||
CONFIG_CPPFLAGS += -DSYSDEBUG -DNOX87
|
||||
TARGET_ARCH ?= -msse3
|
||||
endif
|
||||
|
||||
# LLVM Mode
|
||||
|
@ -411,6 +401,7 @@ endif
|
|||
#
|
||||
ifeq ($(MODE), llvm)
|
||||
.STRICT = 0
|
||||
TARGET_ARCH ?= -msse3
|
||||
CONFIG_CCFLAGS += $(BACKTRACES) -DSYSDEBUG -O2
|
||||
AS = clang
|
||||
CC = clang
|
||||
|
@ -453,6 +444,7 @@ ifeq ($(MODE), ansi)
|
|||
CONFIG_CFLAGS += -std=c11
|
||||
#CONFIG_CPPFLAGS += -ansi
|
||||
CONFIG_CXXFLAGS += -std=c++11
|
||||
TARGET_ARCH ?= -msse3
|
||||
endif
|
||||
|
||||
ifneq ($(ENABLE_FTRACE),)
|
||||
|
@ -510,5 +502,3 @@ ifeq ($(ARCH), aarch64)
|
|||
CONFIG_CCFLAGS += -fpatchable-function-entry=7,6
|
||||
endif
|
||||
endif
|
||||
|
||||
TARGET_ARCH ?= $(CONFIG_TARGET_ARCH)
|
||||
|
|
|
@ -92,7 +92,10 @@ DEFAULT_COPTS ?= \
|
|||
-fno-gnu-unique \
|
||||
-fstrict-aliasing \
|
||||
-fstrict-overflow \
|
||||
-fno-semantic-interposition
|
||||
-fno-semantic-interposition \
|
||||
-fno-dwarf2-cfi-asm \
|
||||
-fno-unwind-tables \
|
||||
-fno-asynchronous-unwind-tables
|
||||
|
||||
ifeq ($(ARCH), x86_64)
|
||||
# Microsoft says "[a]ny memory below the stack beyond the red zone
|
||||
|
@ -112,10 +115,14 @@ ifeq ($(ARCH), aarch64)
|
|||
# - Cosmopolitan Libc uses x28 for thread-local storage because Apple
|
||||
# forbids us from using tpidr_el0 too.
|
||||
#
|
||||
# - Cosmopolitan currently lacks an implementation of the runtime
|
||||
# libraries needed by the -moutline-atomics flag
|
||||
#
|
||||
DEFAULT_COPTS += \
|
||||
-ffixed-x18 \
|
||||
-ffixed-x28 \
|
||||
-fsigned-char
|
||||
-fsigned-char \
|
||||
-mno-outline-atomics
|
||||
endif
|
||||
|
||||
MATHEMATICAL = \
|
||||
|
@ -132,10 +139,12 @@ DEFAULT_CPPFLAGS += \
|
|||
-isystem libc/isystem
|
||||
|
||||
DEFAULT_CFLAGS = \
|
||||
-std=gnu23
|
||||
-std=gnu2x
|
||||
|
||||
DEFAULT_CXXFLAGS = \
|
||||
-std=gnu++23 \
|
||||
-std=gnu++20 \
|
||||
-fno-rtti \
|
||||
-fno-exceptions \
|
||||
-fuse-cxa-atexit \
|
||||
-Wno-int-in-bool-context \
|
||||
-Wno-narrowing \
|
||||
|
|
|
@ -99,8 +99,3 @@ rm -f cosmocc.zip cosmocc.zip.sha256sum
|
|||
# commit output directory
|
||||
cd "${OLDPWD}" || die
|
||||
mv "${OUTPUT_TMP}" "${OUTPUT_DIR}" || die
|
||||
|
||||
# update current symlink
|
||||
BASE=$(basename "${OUTPUT_DIR}")
|
||||
DIR=$(dirname "${OUTPUT_DIR}")
|
||||
ln -sfn "$BASE" "$DIR/current"
|
||||
|
|
|
@ -6,14 +6,14 @@ if [ -n "$OBJDUMP" ]; then
|
|||
fi
|
||||
|
||||
find_objdump() {
|
||||
if [ -x .cosmocc/3.9.2/bin/$1-linux-cosmo-objdump ]; then
|
||||
OBJDUMP=.cosmocc/3.9.2/bin/$1-linux-cosmo-objdump
|
||||
elif [ -x .cosmocc/3.9.2/bin/$1-linux-musl-objdump ]; then
|
||||
OBJDUMP=.cosmocc/3.9.2/bin/$1-linux-musl-objdump
|
||||
elif [ -x "$COSMO/.cosmocc/3.9.2/bin/$1-linux-cosmo-objdump" ]; then
|
||||
OBJDUMP="$COSMO/.cosmocc/3.9.2/bin/$1-linux-cosmo-objdump"
|
||||
elif [ -x "$COSMO/.cosmocc/3.9.2/bin/$1-linux-musl-objdump" ]; then
|
||||
OBJDUMP="$COSMO/.cosmocc/3.9.2/bin/$1-linux-musl-objdump"
|
||||
if [ -x .cosmocc/3.3.5/bin/$1-linux-cosmo-objdump ]; then
|
||||
OBJDUMP=.cosmocc/3.3.5/bin/$1-linux-cosmo-objdump
|
||||
elif [ -x .cosmocc/3.3.5/bin/$1-linux-musl-objdump ]; then
|
||||
OBJDUMP=.cosmocc/3.3.5/bin/$1-linux-musl-objdump
|
||||
elif [ -x "$COSMO/.cosmocc/3.3.5/bin/$1-linux-cosmo-objdump" ]; then
|
||||
OBJDUMP="$COSMO/.cosmocc/3.3.5/bin/$1-linux-cosmo-objdump"
|
||||
elif [ -x "$COSMO/.cosmocc/3.3.5/bin/$1-linux-musl-objdump" ]; then
|
||||
OBJDUMP="$COSMO/.cosmocc/3.3.5/bin/$1-linux-musl-objdump"
|
||||
else
|
||||
echo "error: toolchain not found (try running 'cosmocc --update' or 'make' in the cosmo monorepo)" >&2
|
||||
exit 1
|
||||
|
|
|
@ -4,5 +4,5 @@ UNAMES=$(uname -s)
|
|||
if [ x"$UNAMES" = x"Darwin" ] && [ x"$UNAMEM" = x"arm64" ]; then
|
||||
exec ape "$@"
|
||||
else
|
||||
exec rusage "$@"
|
||||
exec "$@"
|
||||
fi
|
||||
|
|
|
@ -31,7 +31,7 @@ if [ ! -f /proc/sys/fs/binfmt_misc/status ]; then
|
|||
exit 0
|
||||
fi
|
||||
|
||||
if ! build/bootstrap/echo -n; then
|
||||
if ! build/bootstrap/echo.com -n; then
|
||||
cat <<'EOF' >&2
|
||||
|
||||
ERROR
|
||||
|
|
|
@ -17,9 +17,6 @@ struct conditional<false, T, F>
|
|||
typedef F type;
|
||||
};
|
||||
|
||||
template<bool B, typename T, typename F>
|
||||
using conditional_t = typename conditional<B, T, F>::type;
|
||||
|
||||
} // namespace ctl
|
||||
|
||||
#endif // CTL_CONDITIONAL_H_
|
||||
|
|
|
@ -1,27 +0,0 @@
|
|||
// -*-mode:c++;indent-tabs-mode:nil;c-basic-offset:4;tab-width:8;coding:utf-8-*-
|
||||
// vi: set et ft=cpp ts=4 sts=4 sw=4 fenc=utf-8 :vi
|
||||
#ifndef CTL_IS_VOID_H_
|
||||
#define CTL_IS_VOID_H_
|
||||
#include "integral_constant.h"
|
||||
#include "remove_cv.h"
|
||||
|
||||
namespace ctl {
|
||||
|
||||
template<typename>
|
||||
struct is_void_ : public ctl::false_type
|
||||
{};
|
||||
|
||||
template<>
|
||||
struct is_void_<void> : public ctl::true_type
|
||||
{};
|
||||
|
||||
template<typename _Tp>
|
||||
struct is_void : public is_void_<typename ctl::remove_cv<_Tp>::type>::type
|
||||
{};
|
||||
|
||||
template<typename T>
|
||||
inline constexpr bool is_void_v = is_void<T>::value;
|
||||
|
||||
} // namespace ctl
|
||||
|
||||
#endif // CTL_IS_VOID_H_
|
|
@ -17,7 +17,6 @@
|
|||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
#include "istream.h"
|
||||
#include "libc/ctype.h"
|
||||
#include "libc/fmt/conv.h"
|
||||
#include "libc/stdio/stdio.h"
|
||||
#include "libc/str/str.h"
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
#include "istringstream.h"
|
||||
#include "libc/ctype.h"
|
||||
#include "libc/fmt/conv.h"
|
||||
#include "libc/str/str.h"
|
||||
|
||||
|
|
253
ctl/set.h
253
ctl/set.h
|
@ -13,34 +13,18 @@ class set
|
|||
{
|
||||
struct rbtree
|
||||
{
|
||||
uintptr_t left_;
|
||||
rbtree* left;
|
||||
rbtree* right;
|
||||
rbtree* parent;
|
||||
bool is_red;
|
||||
Key value;
|
||||
|
||||
rbtree* left() const
|
||||
{
|
||||
return (rbtree*)(left_ & -2);
|
||||
}
|
||||
|
||||
void left(rbtree* val)
|
||||
{
|
||||
left_ = (uintptr_t)val | (left_ & 1);
|
||||
}
|
||||
|
||||
bool is_red() const
|
||||
{
|
||||
return left_ & 1;
|
||||
}
|
||||
|
||||
void is_red(bool val)
|
||||
{
|
||||
left_ &= -2;
|
||||
left_ |= val;
|
||||
}
|
||||
|
||||
rbtree(const Key& val)
|
||||
: left_(1), right(nullptr), parent(nullptr), value(val)
|
||||
: left(nullptr)
|
||||
, right(nullptr)
|
||||
, parent(nullptr)
|
||||
, is_red(true)
|
||||
, value(val)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
@ -101,14 +85,14 @@ class set
|
|||
{
|
||||
if (node_ == nullptr)
|
||||
__builtin_trap();
|
||||
if (node_->left()) {
|
||||
node_ = rightmost(node_->left());
|
||||
if (node_->left) {
|
||||
node_ = rightmost(node_->left);
|
||||
} else {
|
||||
node_type* parent = node_->parent;
|
||||
for (;;) {
|
||||
if (parent == nullptr)
|
||||
break;
|
||||
if (node_ == parent->left()) {
|
||||
if (node_ == parent->left) {
|
||||
node_ = parent;
|
||||
parent = parent->parent;
|
||||
} else {
|
||||
|
@ -177,11 +161,11 @@ class set
|
|||
|
||||
reverse_iterator& operator++()
|
||||
{
|
||||
if (node_->left()) {
|
||||
node_ = rightmost(node_->left());
|
||||
if (node_->left) {
|
||||
node_ = rightmost(node_->left);
|
||||
} else {
|
||||
node_type* parent = node_->parent;
|
||||
while (parent && node_ == parent->left()) {
|
||||
while (parent && node_ == parent->left) {
|
||||
node_ = parent;
|
||||
parent = parent->parent;
|
||||
}
|
||||
|
@ -241,9 +225,8 @@ class set
|
|||
private:
|
||||
friend class set;
|
||||
node_type* node_;
|
||||
node_type* root_;
|
||||
|
||||
explicit reverse_iterator(node_type* node, node_type* root) : node_(node), root_(root)
|
||||
explicit reverse_iterator(node_type* node) : node_(node)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
@ -348,17 +331,17 @@ class set
|
|||
|
||||
reverse_iterator rbegin()
|
||||
{
|
||||
return reverse_iterator(rightmost(root_), root_);
|
||||
return reverse_iterator(rightmost(root_));
|
||||
}
|
||||
|
||||
const_reverse_iterator rbegin() const
|
||||
{
|
||||
return const_reverse_iterator(rightmost(root_), root_);
|
||||
return const_reverse_iterator(rightmost(root_));
|
||||
}
|
||||
|
||||
const_reverse_iterator crbegin() const
|
||||
{
|
||||
return const_reverse_iterator(rightmost(root_), root_);
|
||||
return const_reverse_iterator(rightmost(root_));
|
||||
}
|
||||
|
||||
iterator end() noexcept
|
||||
|
@ -378,17 +361,17 @@ class set
|
|||
|
||||
reverse_iterator rend()
|
||||
{
|
||||
return reverse_iterator(nullptr, root_);
|
||||
return reverse_iterator(nullptr);
|
||||
}
|
||||
|
||||
const_reverse_iterator rend() const
|
||||
{
|
||||
return const_reverse_iterator(nullptr, root_);
|
||||
return const_reverse_iterator(nullptr);
|
||||
}
|
||||
|
||||
const_reverse_iterator crend() const
|
||||
{
|
||||
return const_reverse_iterator(nullptr, root_);
|
||||
return const_reverse_iterator(nullptr);
|
||||
}
|
||||
|
||||
void clear() noexcept
|
||||
|
@ -525,7 +508,7 @@ class set
|
|||
{
|
||||
size_type count = 0;
|
||||
if (root_ != nullptr) {
|
||||
if (root_->is_red())
|
||||
if (root_->is_red)
|
||||
// ILLEGAL TREE: root node must be black
|
||||
__builtin_trap();
|
||||
int black_height = -1;
|
||||
|
@ -540,8 +523,8 @@ class set
|
|||
private:
|
||||
static node_type* leftmost(node_type* node) noexcept
|
||||
{
|
||||
while (node && node->left())
|
||||
node = node->left();
|
||||
while (node && node->left)
|
||||
node = node->left;
|
||||
return node;
|
||||
}
|
||||
|
||||
|
@ -552,35 +535,35 @@ class set
|
|||
return node;
|
||||
}
|
||||
|
||||
static optimizesize void clearer(node_type* node) noexcept
|
||||
static void clearer(node_type* node) noexcept
|
||||
{
|
||||
node_type* right;
|
||||
for (; node; node = right) {
|
||||
right = node->right;
|
||||
clearer(node->left());
|
||||
clearer(node->left);
|
||||
delete node;
|
||||
}
|
||||
}
|
||||
|
||||
static optimizesize node_type* copier(const node_type* node)
|
||||
static node_type* copier(const node_type* node)
|
||||
{
|
||||
if (node == nullptr)
|
||||
return nullptr;
|
||||
node_type* new_node = new node_type(node->value);
|
||||
new_node->left(copier(node->left()));
|
||||
new_node->left = copier(node->left);
|
||||
new_node->right = copier(node->right);
|
||||
if (new_node->left())
|
||||
new_node->left()->parent = new_node;
|
||||
if (new_node->left)
|
||||
new_node->left->parent = new_node;
|
||||
if (new_node->right)
|
||||
new_node->right->parent = new_node;
|
||||
return new_node;
|
||||
}
|
||||
|
||||
static optimizesize size_type tally(const node_type* node)
|
||||
static size_type tally(const node_type* node)
|
||||
{
|
||||
if (node == nullptr)
|
||||
return 0;
|
||||
return 1 + tally(node->left()) + tally(node->right);
|
||||
return 1 + tally(node->left) + tally(node->right);
|
||||
}
|
||||
|
||||
template<typename K>
|
||||
|
@ -589,7 +572,7 @@ class set
|
|||
node_type* current = root_;
|
||||
while (current != nullptr) {
|
||||
if (comp_(key, current->value)) {
|
||||
current = current->left();
|
||||
current = current->left;
|
||||
} else if (comp_(current->value, key)) {
|
||||
current = current->right;
|
||||
} else {
|
||||
|
@ -607,7 +590,7 @@ class set
|
|||
while (current != nullptr) {
|
||||
if (!comp_(current->value, key)) {
|
||||
result = current;
|
||||
current = current->left();
|
||||
current = current->left;
|
||||
} else {
|
||||
current = current->right;
|
||||
}
|
||||
|
@ -623,7 +606,7 @@ class set
|
|||
while (current != nullptr) {
|
||||
if (comp_(key, current->value)) {
|
||||
result = current;
|
||||
current = current->left();
|
||||
current = current->left;
|
||||
} else {
|
||||
current = current->right;
|
||||
}
|
||||
|
@ -631,11 +614,11 @@ class set
|
|||
return result;
|
||||
}
|
||||
|
||||
optimizesize ctl::pair<iterator, bool> insert_node(node_type* node)
|
||||
ctl::pair<iterator, bool> insert_node(node_type* node)
|
||||
{
|
||||
if (root_ == nullptr) {
|
||||
root_ = node;
|
||||
root_->is_red(false);
|
||||
root_->is_red = false;
|
||||
size_++;
|
||||
return { iterator(root_), true };
|
||||
}
|
||||
|
@ -644,7 +627,7 @@ class set
|
|||
while (current != nullptr) {
|
||||
parent = current;
|
||||
if (comp_(node->value, current->value)) {
|
||||
current = current->left();
|
||||
current = current->left;
|
||||
} else if (comp_(current->value, node->value)) {
|
||||
current = current->right;
|
||||
} else {
|
||||
|
@ -653,7 +636,7 @@ class set
|
|||
}
|
||||
}
|
||||
if (comp_(node->value, parent->value)) {
|
||||
parent->left(node);
|
||||
parent->left = node;
|
||||
} else {
|
||||
parent->right = node;
|
||||
}
|
||||
|
@ -663,23 +646,23 @@ class set
|
|||
return { iterator(node), true };
|
||||
}
|
||||
|
||||
optimizesize void erase_node(node_type* node)
|
||||
void erase_node(node_type* node)
|
||||
{
|
||||
node_type* y = node;
|
||||
node_type* x = nullptr;
|
||||
node_type* x_parent = nullptr;
|
||||
bool y_original_color = y->is_red();
|
||||
if (node->left() == nullptr) {
|
||||
bool y_original_color = y->is_red;
|
||||
if (node->left == nullptr) {
|
||||
x = node->right;
|
||||
transplant(node, node->right);
|
||||
x_parent = node->parent;
|
||||
} else if (node->right == nullptr) {
|
||||
x = node->left();
|
||||
transplant(node, node->left());
|
||||
x = node->left;
|
||||
transplant(node, node->left);
|
||||
x_parent = node->parent;
|
||||
} else {
|
||||
y = leftmost(node->right);
|
||||
y_original_color = y->is_red();
|
||||
y_original_color = y->is_red;
|
||||
x = y->right;
|
||||
if (y->parent == node) {
|
||||
if (x)
|
||||
|
@ -692,9 +675,9 @@ class set
|
|||
x_parent = y->parent;
|
||||
}
|
||||
transplant(node, y);
|
||||
y->left(node->left());
|
||||
y->left()->parent = y;
|
||||
y->is_red(node->is_red());
|
||||
y->left = node->left;
|
||||
y->left->parent = y;
|
||||
y->is_red = node->is_red;
|
||||
}
|
||||
if (!y_original_color)
|
||||
rebalance_after_erase(x, x_parent);
|
||||
|
@ -702,28 +685,28 @@ class set
|
|||
--size_;
|
||||
}
|
||||
|
||||
optimizesize void left_rotate(node_type* x)
|
||||
void left_rotate(node_type* x)
|
||||
{
|
||||
node_type* y = x->right;
|
||||
x->right = y->left();
|
||||
if (y->left() != nullptr)
|
||||
y->left()->parent = x;
|
||||
x->right = y->left;
|
||||
if (y->left != nullptr)
|
||||
y->left->parent = x;
|
||||
y->parent = x->parent;
|
||||
if (x->parent == nullptr) {
|
||||
root_ = y;
|
||||
} else if (x == x->parent->left()) {
|
||||
x->parent->left(y);
|
||||
} else if (x == x->parent->left) {
|
||||
x->parent->left = y;
|
||||
} else {
|
||||
x->parent->right = y;
|
||||
}
|
||||
y->left(x);
|
||||
y->left = x;
|
||||
x->parent = y;
|
||||
}
|
||||
|
||||
optimizesize void right_rotate(node_type* y)
|
||||
void right_rotate(node_type* y)
|
||||
{
|
||||
node_type* x = y->left();
|
||||
y->left(x->right);
|
||||
node_type* x = y->left;
|
||||
y->left = x->right;
|
||||
if (x->right != nullptr)
|
||||
x->right->parent = y;
|
||||
x->parent = y->parent;
|
||||
|
@ -732,18 +715,18 @@ class set
|
|||
} else if (y == y->parent->right) {
|
||||
y->parent->right = x;
|
||||
} else {
|
||||
y->parent->left(x);
|
||||
y->parent->left = x;
|
||||
}
|
||||
x->right = y;
|
||||
y->parent = x;
|
||||
}
|
||||
|
||||
optimizesize void transplant(node_type* u, node_type* v)
|
||||
void transplant(node_type* u, node_type* v)
|
||||
{
|
||||
if (u->parent == nullptr) {
|
||||
root_ = v;
|
||||
} else if (u == u->parent->left()) {
|
||||
u->parent->left(v);
|
||||
} else if (u == u->parent->left) {
|
||||
u->parent->left = v;
|
||||
} else {
|
||||
u->parent->right = v;
|
||||
}
|
||||
|
@ -751,7 +734,7 @@ class set
|
|||
v->parent = u->parent;
|
||||
}
|
||||
|
||||
optimizesize void checker(const node_type* node,
|
||||
void checker(const node_type* node,
|
||||
const node_type* parent,
|
||||
int black_count,
|
||||
int& black_height) const
|
||||
|
@ -770,121 +753,117 @@ class set
|
|||
// ILLEGAL TREE: Parent link is incorrect
|
||||
__builtin_trap();
|
||||
if (parent) {
|
||||
if (parent->left() == node && !comp_(node->value, parent->value))
|
||||
if (parent->left == node && !comp_(node->value, parent->value))
|
||||
// ILLEGAL TREE: Binary search property violated on left child
|
||||
__builtin_trap();
|
||||
if (parent->right == node && !comp_(parent->value, node->value))
|
||||
// ILLEGAL TREE: Binary search property violated on right child
|
||||
__builtin_trap();
|
||||
}
|
||||
if (!node->is_red()) {
|
||||
if (!node->is_red) {
|
||||
black_count++;
|
||||
} else if (parent != nullptr && parent->is_red()) {
|
||||
} else if (parent != nullptr && parent->is_red) {
|
||||
// ILLEGAL TREE: Red node has red child
|
||||
__builtin_trap();
|
||||
}
|
||||
checker(node->left(), node, black_count, black_height);
|
||||
checker(node->left, node, black_count, black_height);
|
||||
checker(node->right, node, black_count, black_height);
|
||||
}
|
||||
|
||||
optimizesize void rebalance_after_insert(node_type* node)
|
||||
void rebalance_after_insert(node_type* node)
|
||||
{
|
||||
node->is_red(true);
|
||||
while (node != root_ && node->parent->is_red()) {
|
||||
if (node->parent == node->parent->parent->left()) {
|
||||
node->is_red = true;
|
||||
while (node != root_ && node->parent->is_red) {
|
||||
if (node->parent == node->parent->parent->left) {
|
||||
node_type* uncle = node->parent->parent->right;
|
||||
if (uncle && uncle->is_red()) {
|
||||
node->parent->is_red(false);
|
||||
uncle->is_red(false);
|
||||
node->parent->parent->is_red(true);
|
||||
if (uncle && uncle->is_red) {
|
||||
node->parent->is_red = false;
|
||||
uncle->is_red = false;
|
||||
node->parent->parent->is_red = true;
|
||||
node = node->parent->parent;
|
||||
} else {
|
||||
if (node == node->parent->right) {
|
||||
node = node->parent;
|
||||
left_rotate(node);
|
||||
}
|
||||
node->parent->is_red(false);
|
||||
node->parent->parent->is_red(true);
|
||||
node->parent->is_red = false;
|
||||
node->parent->parent->is_red = true;
|
||||
right_rotate(node->parent->parent);
|
||||
}
|
||||
} else {
|
||||
node_type* uncle = node->parent->parent->left();
|
||||
if (uncle && uncle->is_red()) {
|
||||
node->parent->is_red(false);
|
||||
uncle->is_red(false);
|
||||
node->parent->parent->is_red(true);
|
||||
node_type* uncle = node->parent->parent->left;
|
||||
if (uncle && uncle->is_red) {
|
||||
node->parent->is_red = false;
|
||||
uncle->is_red = false;
|
||||
node->parent->parent->is_red = true;
|
||||
node = node->parent->parent;
|
||||
} else {
|
||||
if (node == node->parent->left()) {
|
||||
if (node == node->parent->left) {
|
||||
node = node->parent;
|
||||
right_rotate(node);
|
||||
}
|
||||
node->parent->is_red(false);
|
||||
node->parent->parent->is_red(true);
|
||||
node->parent->is_red = false;
|
||||
node->parent->parent->is_red = true;
|
||||
left_rotate(node->parent->parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
root_->is_red(false);
|
||||
root_->is_red = false;
|
||||
}
|
||||
|
||||
optimizesize void rebalance_after_erase(node_type* node, node_type* parent)
|
||||
void rebalance_after_erase(node_type* node, node_type* parent)
|
||||
{
|
||||
while (node != root_ && (node == nullptr || !node->is_red())) {
|
||||
if (node == parent->left()) {
|
||||
while (node != root_ && (node == nullptr || !node->is_red)) {
|
||||
if (node == parent->left) {
|
||||
node_type* sibling = parent->right;
|
||||
if (sibling->is_red()) {
|
||||
sibling->is_red(false);
|
||||
parent->is_red(true);
|
||||
if (sibling->is_red) {
|
||||
sibling->is_red = false;
|
||||
parent->is_red = true;
|
||||
left_rotate(parent);
|
||||
sibling = parent->right;
|
||||
}
|
||||
if ((sibling->left() == nullptr ||
|
||||
!sibling->left()->is_red()) &&
|
||||
(sibling->right == nullptr || !sibling->right->is_red())) {
|
||||
sibling->is_red(true);
|
||||
if ((sibling->left == nullptr || !sibling->left->is_red) &&
|
||||
(sibling->right == nullptr || !sibling->right->is_red)) {
|
||||
sibling->is_red = true;
|
||||
node = parent;
|
||||
parent = node->parent;
|
||||
} else {
|
||||
if (sibling->right == nullptr ||
|
||||
!sibling->right->is_red()) {
|
||||
sibling->left()->is_red(false);
|
||||
sibling->is_red(true);
|
||||
if (sibling->right == nullptr || !sibling->right->is_red) {
|
||||
sibling->left->is_red = false;
|
||||
sibling->is_red = true;
|
||||
right_rotate(sibling);
|
||||
sibling = parent->right;
|
||||
}
|
||||
sibling->is_red(parent->is_red());
|
||||
parent->is_red(false);
|
||||
sibling->right->is_red(false);
|
||||
sibling->is_red = parent->is_red;
|
||||
parent->is_red = false;
|
||||
sibling->right->is_red = false;
|
||||
left_rotate(parent);
|
||||
node = root_;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
node_type* sibling = parent->left();
|
||||
if (sibling->is_red()) {
|
||||
sibling->is_red(false);
|
||||
parent->is_red(true);
|
||||
node_type* sibling = parent->left;
|
||||
if (sibling->is_red) {
|
||||
sibling->is_red = false;
|
||||
parent->is_red = true;
|
||||
right_rotate(parent);
|
||||
sibling = parent->left();
|
||||
sibling = parent->left;
|
||||
}
|
||||
if ((sibling->right == nullptr || !sibling->right->is_red()) &&
|
||||
(sibling->left() == nullptr ||
|
||||
!sibling->left()->is_red())) {
|
||||
sibling->is_red(true);
|
||||
if ((sibling->right == nullptr || !sibling->right->is_red) &&
|
||||
(sibling->left == nullptr || !sibling->left->is_red)) {
|
||||
sibling->is_red = true;
|
||||
node = parent;
|
||||
parent = node->parent;
|
||||
} else {
|
||||
if (sibling->left() == nullptr ||
|
||||
!sibling->left()->is_red()) {
|
||||
sibling->right->is_red(false);
|
||||
sibling->is_red(true);
|
||||
if (sibling->left == nullptr || !sibling->left->is_red) {
|
||||
sibling->right->is_red = false;
|
||||
sibling->is_red = true;
|
||||
left_rotate(sibling);
|
||||
sibling = parent->left();
|
||||
sibling = parent->left;
|
||||
}
|
||||
sibling->is_red(parent->is_red());
|
||||
parent->is_red(false);
|
||||
sibling->left()->is_red(false);
|
||||
sibling->is_red = parent->is_red;
|
||||
parent->is_red = false;
|
||||
sibling->left->is_red = false;
|
||||
right_rotate(parent);
|
||||
node = root_;
|
||||
break;
|
||||
|
@ -892,7 +871,7 @@ class set
|
|||
}
|
||||
}
|
||||
if (node != nullptr)
|
||||
node->is_red(false);
|
||||
node->is_red = false;
|
||||
}
|
||||
|
||||
node_type* root_;
|
||||
|
|
618
ctl/shared_ptr.h
618
ctl/shared_ptr.h
|
@ -1,618 +0,0 @@
|
|||
// -*-mode:c++;indent-tabs-mode:nil;c-basic-offset:4;tab-width:8;coding:utf-8-*-
|
||||
// vi: set et ft=cpp ts=4 sts=4 sw=4 fenc=utf-8 :vi
|
||||
#ifndef CTL_SHARED_PTR_H_
|
||||
#define CTL_SHARED_PTR_H_
|
||||
|
||||
#include "exception.h"
|
||||
#include "is_base_of.h"
|
||||
#include "is_constructible.h"
|
||||
#include "is_convertible.h"
|
||||
#include "remove_extent.h"
|
||||
#include "unique_ptr.h"
|
||||
|
||||
// XXX currently needed to use placement-new syntax (move to cxx.inc?)
|
||||
void*
|
||||
operator new(size_t, void*) noexcept;
|
||||
|
||||
namespace ctl {
|
||||
|
||||
class bad_weak_ptr : public exception
|
||||
{
|
||||
public:
|
||||
const char* what() const noexcept override
|
||||
{
|
||||
return "ctl::bad_weak_ptr";
|
||||
}
|
||||
};
|
||||
|
||||
namespace __ {
|
||||
|
||||
template<typename T>
|
||||
struct ptr_ref
|
||||
{
|
||||
using type = T&;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct ptr_ref<void>
|
||||
{
|
||||
using type = void;
|
||||
};
|
||||
|
||||
static inline __attribute__((always_inline)) void
|
||||
incref(size_t* r) noexcept
|
||||
{
|
||||
#ifdef NDEBUG
|
||||
__atomic_fetch_add(r, 1, __ATOMIC_RELAXED);
|
||||
#else
|
||||
ssize_t refs = __atomic_fetch_add(r, 1, __ATOMIC_RELAXED);
|
||||
if (refs < 0)
|
||||
__builtin_trap();
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline __attribute__((always_inline)) bool
|
||||
decref(size_t* r) noexcept
|
||||
{
|
||||
if (!__atomic_fetch_sub(r, 1, __ATOMIC_RELEASE)) {
|
||||
__atomic_thread_fence(__ATOMIC_ACQUIRE);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
class shared_ref
|
||||
{
|
||||
public:
|
||||
constexpr shared_ref() noexcept = default;
|
||||
shared_ref(const shared_ref&) = delete;
|
||||
shared_ref& operator=(const shared_ref&) = delete;
|
||||
|
||||
virtual ~shared_ref() = default;
|
||||
|
||||
void keep_shared() noexcept
|
||||
{
|
||||
incref(&shared);
|
||||
}
|
||||
|
||||
void drop_shared() noexcept
|
||||
{
|
||||
if (decref(&shared)) {
|
||||
dispose();
|
||||
drop_weak();
|
||||
}
|
||||
}
|
||||
|
||||
void keep_weak() noexcept
|
||||
{
|
||||
incref(&weak);
|
||||
}
|
||||
|
||||
void drop_weak() noexcept
|
||||
{
|
||||
if (decref(&weak)) {
|
||||
delete this;
|
||||
}
|
||||
}
|
||||
|
||||
size_t use_count() const noexcept
|
||||
{
|
||||
return __atomic_load_n(&shared, __ATOMIC_RELAXED) + 1;
|
||||
}
|
||||
|
||||
size_t weak_count() const noexcept
|
||||
{
|
||||
return __atomic_load_n(&weak, __ATOMIC_RELAXED);
|
||||
}
|
||||
|
||||
private:
|
||||
virtual void dispose() noexcept = 0;
|
||||
|
||||
size_t shared = 0;
|
||||
size_t weak = 0;
|
||||
};
|
||||
|
||||
template<typename T, typename D>
|
||||
class shared_pointer : public shared_ref
|
||||
{
|
||||
public:
|
||||
static shared_pointer* make(T* const p, D d)
|
||||
{
|
||||
return make(unique_ptr<T, D>(p, move(d)));
|
||||
}
|
||||
|
||||
static shared_pointer* make(unique_ptr<T, D> p)
|
||||
{
|
||||
return new shared_pointer(p.release(), move(p.get_deleter()));
|
||||
}
|
||||
|
||||
private:
|
||||
shared_pointer(T* const p, D d) noexcept : p(p), d(move(d))
|
||||
{
|
||||
}
|
||||
|
||||
void dispose() noexcept override
|
||||
{
|
||||
move(d)(p);
|
||||
}
|
||||
|
||||
T* const p;
|
||||
[[no_unique_address]] D d;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
class shared_emplace : public shared_ref
|
||||
{
|
||||
public:
|
||||
union
|
||||
{
|
||||
T t;
|
||||
};
|
||||
|
||||
~shared_emplace() override
|
||||
{
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
void construct(Args&&... args)
|
||||
{
|
||||
::new (&t) T(forward<Args>(args)...);
|
||||
}
|
||||
|
||||
static unique_ptr<shared_emplace> make()
|
||||
{
|
||||
return unique_ptr(new shared_emplace());
|
||||
}
|
||||
|
||||
private:
|
||||
explicit constexpr shared_emplace() noexcept
|
||||
{
|
||||
}
|
||||
|
||||
void dispose() noexcept override
|
||||
{
|
||||
t.~T();
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T, typename U>
|
||||
concept shared_ptr_compatible = is_convertible_v<U*, T*>;
|
||||
|
||||
} // namespace __
|
||||
|
||||
template<typename T>
|
||||
class weak_ptr;
|
||||
|
||||
template<typename T>
|
||||
class shared_ptr
|
||||
{
|
||||
public:
|
||||
using element_type = remove_extent_t<T>;
|
||||
using weak_type = weak_ptr<T>;
|
||||
|
||||
constexpr shared_ptr() noexcept = default;
|
||||
constexpr shared_ptr(nullptr_t) noexcept
|
||||
{
|
||||
}
|
||||
|
||||
template<typename U>
|
||||
requires __::shared_ptr_compatible<T, U>
|
||||
explicit shared_ptr(U* const p) : shared_ptr(p, default_delete<U>())
|
||||
{
|
||||
}
|
||||
|
||||
template<typename U, typename D>
|
||||
requires __::shared_ptr_compatible<T, U>
|
||||
shared_ptr(U*, D);
|
||||
|
||||
template<typename U>
|
||||
shared_ptr(const shared_ptr<U>& r, element_type* p) noexcept
|
||||
: p(p), rc(r.rc)
|
||||
{
|
||||
if (rc)
|
||||
rc->keep_shared();
|
||||
}
|
||||
|
||||
template<typename U>
|
||||
shared_ptr(shared_ptr<U>&& r, element_type* p) noexcept : p(p), rc(r.rc)
|
||||
{
|
||||
r.p = nullptr;
|
||||
r.rc = nullptr;
|
||||
}
|
||||
|
||||
template<typename U>
|
||||
requires __::shared_ptr_compatible<T, U>
|
||||
shared_ptr(const shared_ptr<U>& r) noexcept : p(r.p), rc(r.rc)
|
||||
{
|
||||
if (rc)
|
||||
rc->keep_shared();
|
||||
}
|
||||
|
||||
template<typename U>
|
||||
requires __::shared_ptr_compatible<T, U>
|
||||
shared_ptr(shared_ptr<U>&& r) noexcept : p(r.p), rc(r.rc)
|
||||
{
|
||||
r.p = nullptr;
|
||||
r.rc = nullptr;
|
||||
}
|
||||
|
||||
shared_ptr(const shared_ptr& r) noexcept : p(r.p), rc(r.rc)
|
||||
{
|
||||
if (rc)
|
||||
rc->keep_shared();
|
||||
}
|
||||
|
||||
shared_ptr(shared_ptr&& r) noexcept : p(r.p), rc(r.rc)
|
||||
{
|
||||
r.p = nullptr;
|
||||
r.rc = nullptr;
|
||||
}
|
||||
|
||||
template<typename U>
|
||||
requires __::shared_ptr_compatible<T, U>
|
||||
explicit shared_ptr(const weak_ptr<U>& r) : p(r.p), rc(r.rc)
|
||||
{
|
||||
if (r.expired()) {
|
||||
throw bad_weak_ptr();
|
||||
}
|
||||
rc->keep_shared();
|
||||
}
|
||||
|
||||
template<typename U, typename D>
|
||||
requires __::shared_ptr_compatible<T, U>
|
||||
shared_ptr(unique_ptr<U, D>&& r)
|
||||
: p(r.p), rc(__::shared_pointer<U, D>::make(move(r)))
|
||||
{
|
||||
}
|
||||
|
||||
~shared_ptr()
|
||||
{
|
||||
if (rc)
|
||||
rc->drop_shared();
|
||||
}
|
||||
|
||||
shared_ptr& operator=(shared_ptr r) noexcept
|
||||
{
|
||||
swap(r);
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename U>
|
||||
requires __::shared_ptr_compatible<T, U>
|
||||
shared_ptr& operator=(shared_ptr<U> r) noexcept
|
||||
{
|
||||
shared_ptr<T>(move(r)).swap(*this);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void reset() noexcept
|
||||
{
|
||||
shared_ptr().swap(*this);
|
||||
}
|
||||
|
||||
template<typename U>
|
||||
requires __::shared_ptr_compatible<T, U>
|
||||
void reset(U* const p2)
|
||||
{
|
||||
shared_ptr<T>(p2).swap(*this);
|
||||
}
|
||||
|
||||
template<typename U, typename D>
|
||||
requires __::shared_ptr_compatible<T, U>
|
||||
void reset(U* const p2, D d)
|
||||
{
|
||||
shared_ptr<T>(p2, d).swap(*this);
|
||||
}
|
||||
|
||||
void swap(shared_ptr& r) noexcept
|
||||
{
|
||||
using ctl::swap;
|
||||
swap(p, r.p);
|
||||
swap(rc, r.rc);
|
||||
}
|
||||
|
||||
element_type* get() const noexcept
|
||||
{
|
||||
return p;
|
||||
}
|
||||
|
||||
typename __::ptr_ref<T>::type operator*() const noexcept
|
||||
{
|
||||
if (!p)
|
||||
__builtin_trap();
|
||||
return *p;
|
||||
}
|
||||
|
||||
T* operator->() const noexcept
|
||||
{
|
||||
if (!p)
|
||||
__builtin_trap();
|
||||
return p;
|
||||
}
|
||||
|
||||
long use_count() const noexcept
|
||||
{
|
||||
return rc ? rc->use_count() : 0;
|
||||
}
|
||||
|
||||
explicit operator bool() const noexcept
|
||||
{
|
||||
return p;
|
||||
}
|
||||
|
||||
template<typename U>
|
||||
bool owner_before(const shared_ptr<U>& r) const noexcept
|
||||
{
|
||||
return rc < r.rc;
|
||||
}
|
||||
|
||||
template<typename U>
|
||||
bool owner_before(const weak_ptr<U>& r) const noexcept
|
||||
{
|
||||
return rc < r.rc;
|
||||
}
|
||||
|
||||
private:
|
||||
template<typename U>
|
||||
friend class weak_ptr;
|
||||
|
||||
template<typename U>
|
||||
friend class shared_ptr;
|
||||
|
||||
template<typename U, typename... Args>
|
||||
friend shared_ptr<U> make_shared(Args&&... args);
|
||||
|
||||
element_type* p = nullptr;
|
||||
__::shared_ref* rc = nullptr;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
class weak_ptr
|
||||
{
|
||||
public:
|
||||
using element_type = remove_extent_t<T>;
|
||||
|
||||
constexpr weak_ptr() noexcept = default;
|
||||
|
||||
template<typename U>
|
||||
requires __::shared_ptr_compatible<T, U>
|
||||
weak_ptr(const shared_ptr<U>& r) noexcept : p(r.p), rc(r.rc)
|
||||
{
|
||||
if (rc)
|
||||
rc->keep_weak();
|
||||
}
|
||||
|
||||
weak_ptr(const weak_ptr& r) noexcept : p(r.p), rc(r.rc)
|
||||
{
|
||||
if (rc)
|
||||
rc->keep_weak();
|
||||
}
|
||||
|
||||
template<typename U>
|
||||
requires __::shared_ptr_compatible<T, U>
|
||||
weak_ptr(const weak_ptr<U>& r) noexcept : p(r.p), rc(r.rc)
|
||||
{
|
||||
if (rc)
|
||||
rc->keep_weak();
|
||||
}
|
||||
|
||||
weak_ptr(weak_ptr&& r) noexcept : p(r.p), rc(r.rc)
|
||||
{
|
||||
r.p = nullptr;
|
||||
r.rc = nullptr;
|
||||
}
|
||||
|
||||
template<typename U>
|
||||
requires __::shared_ptr_compatible<T, U>
|
||||
weak_ptr(weak_ptr<U>&& r) noexcept : p(r.p), rc(r.rc)
|
||||
{
|
||||
r.p = nullptr;
|
||||
r.rc = nullptr;
|
||||
}
|
||||
|
||||
~weak_ptr()
|
||||
{
|
||||
if (rc)
|
||||
rc->drop_weak();
|
||||
}
|
||||
|
||||
long use_count() const noexcept
|
||||
{
|
||||
return rc ? rc->use_count() : 0;
|
||||
}
|
||||
|
||||
bool expired() const noexcept
|
||||
{
|
||||
return !use_count();
|
||||
}
|
||||
|
||||
void reset() noexcept
|
||||
{
|
||||
weak_ptr().swap(*this);
|
||||
}
|
||||
|
||||
void swap(weak_ptr& r) noexcept
|
||||
{
|
||||
using ctl::swap;
|
||||
swap(p, r.p);
|
||||
swap(rc, r.rc);
|
||||
}
|
||||
|
||||
weak_ptr& operator=(weak_ptr r) noexcept
|
||||
{
|
||||
swap(r);
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename U>
|
||||
requires __::shared_ptr_compatible<T, U>
|
||||
weak_ptr& operator=(weak_ptr<U> r) noexcept
|
||||
{
|
||||
weak_ptr<T>(move(r)).swap(*this);
|
||||
}
|
||||
|
||||
shared_ptr<T> lock() const noexcept
|
||||
{
|
||||
if (expired())
|
||||
return nullptr;
|
||||
shared_ptr<T> r;
|
||||
r.p = p;
|
||||
r.rc = rc;
|
||||
if (rc)
|
||||
rc->keep_shared();
|
||||
return r;
|
||||
}
|
||||
|
||||
template<typename U>
|
||||
bool owner_before(const weak_ptr<U>& r) const noexcept
|
||||
{
|
||||
return rc < r.rc;
|
||||
}
|
||||
|
||||
template<typename U>
|
||||
bool owner_before(const shared_ptr<U>& r) const noexcept
|
||||
{
|
||||
return rc < r.rc;
|
||||
}
|
||||
|
||||
private:
|
||||
template<typename U>
|
||||
friend class shared_ptr;
|
||||
|
||||
template<typename U, typename... Args>
|
||||
friend shared_ptr<U> make_shared(Args&&...);
|
||||
|
||||
element_type* p = nullptr;
|
||||
__::shared_ref* rc = nullptr;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
class enable_shared_from_this
|
||||
{
|
||||
public:
|
||||
shared_ptr<T> shared_from_this()
|
||||
{
|
||||
return shared_ptr<T>(weak_this);
|
||||
}
|
||||
shared_ptr<T const> shared_from_this() const
|
||||
{
|
||||
return shared_ptr<T>(weak_this);
|
||||
}
|
||||
|
||||
weak_ptr<T> weak_from_this()
|
||||
{
|
||||
return weak_this;
|
||||
}
|
||||
weak_ptr<T const> weak_from_this() const
|
||||
{
|
||||
return weak_this;
|
||||
}
|
||||
|
||||
protected:
|
||||
constexpr enable_shared_from_this() noexcept = default;
|
||||
enable_shared_from_this(const enable_shared_from_this& r) noexcept
|
||||
{
|
||||
}
|
||||
~enable_shared_from_this() = default;
|
||||
|
||||
enable_shared_from_this& operator=(
|
||||
const enable_shared_from_this& r) noexcept
|
||||
{
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
template<typename U, typename... Args>
|
||||
friend shared_ptr<U> make_shared(Args&&...);
|
||||
|
||||
template<typename U>
|
||||
friend class shared_ptr;
|
||||
|
||||
weak_ptr<T> weak_this;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
template<typename U, typename D>
|
||||
requires __::shared_ptr_compatible<T, U>
|
||||
shared_ptr<T>::shared_ptr(U* const p, D d)
|
||||
: p(p), rc(__::shared_pointer<U, D>::make(p, move(d)))
|
||||
{
|
||||
if constexpr (is_base_of_v<enable_shared_from_this<U>, U>) {
|
||||
p->weak_this = *this;
|
||||
}
|
||||
}
|
||||
|
||||
// Our make_shared supports passing a weak self reference as the first parameter
|
||||
// to your constructor, e.g.:
|
||||
//
|
||||
// struct Tree : ctl::weak_self_base
|
||||
// {
|
||||
// ctl::shared_ptr<Tree> l, r;
|
||||
// ctl::weak_ptr<Tree> parent;
|
||||
// Tree(weak_ptr<Tree> const& self, auto&& l2, auto&& r2)
|
||||
// : l(ctl::forward<decltype(l2)>(l2)),
|
||||
// r(ctl::forward<decltype(r2)>(r2))
|
||||
// {
|
||||
// if (l) l->parent = self;
|
||||
// if (r) r->parent = self;
|
||||
// }
|
||||
// };
|
||||
//
|
||||
// int main() {
|
||||
// auto t = ctl::make_shared<Tree>(
|
||||
// ctl::make_shared<Tree>(nullptr, nullptr), nullptr);
|
||||
// return t->l->parent.lock().get() == t.get() ? 0 : 1;
|
||||
// }
|
||||
//
|
||||
// As shown, passing the parameter at object construction time lets you complete
|
||||
// object construction without needing a separate Init method. But because we go
|
||||
// off spec as far as the STL is concerned, there is a potential ambiguity where
|
||||
// you might have a constructor with a weak_ptr first parameter that is intended
|
||||
// to be something other than a self-reference. So this feature is opt-in by way
|
||||
// of inheriting from the following struct.
|
||||
struct weak_self_base
|
||||
{};
|
||||
|
||||
template<typename T, typename... Args>
|
||||
shared_ptr<T>
|
||||
make_shared(Args&&... args)
|
||||
{
|
||||
unique_ptr rc = __::shared_emplace<T>::make();
|
||||
if constexpr (is_base_of_v<weak_self_base, T> &&
|
||||
is_constructible_v<T, const weak_ptr<T>&, Args...>) {
|
||||
// A __::shared_ref has a virtual weak reference that is owned by all of
|
||||
// the shared references. We can avoid some unnecessary refcount changes
|
||||
// by "borrowing" that reference and passing it to the constructor, then
|
||||
// promoting it to a shared reference by swapping it with the shared_ptr
|
||||
// that we return.
|
||||
weak_ptr<T> w;
|
||||
w.p = &rc->t;
|
||||
w.rc = rc.get();
|
||||
try {
|
||||
rc->construct(const_cast<const weak_ptr<T>&>(w),
|
||||
forward<Args>(args)...);
|
||||
} catch (...) {
|
||||
w.p = nullptr;
|
||||
w.rc = nullptr;
|
||||
throw;
|
||||
}
|
||||
rc.release();
|
||||
shared_ptr<T> r;
|
||||
swap(r.p, w.p);
|
||||
swap(r.rc, w.rc);
|
||||
return r;
|
||||
} else {
|
||||
rc->construct(forward<Args>(args)...);
|
||||
shared_ptr<T> r;
|
||||
r.p = &rc->t;
|
||||
r.rc = rc.release();
|
||||
if constexpr (is_base_of_v<enable_shared_from_this<T>, T>) {
|
||||
r->weak_this = r;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ctl
|
||||
|
||||
#endif // CTL_SHARED_PTR_H_
|
|
@ -21,9 +21,6 @@
|
|||
#include <__atomic/fence.h>
|
||||
#include <stdckdint.h>
|
||||
|
||||
#include "libc/mem/mem.h"
|
||||
#include "libc/str/str.h"
|
||||
|
||||
namespace ctl {
|
||||
|
||||
void
|
||||
|
@ -383,72 +380,4 @@ string::erase(const size_t pos, size_t count) noexcept
|
|||
return *this;
|
||||
}
|
||||
|
||||
void
|
||||
string::append(const ctl::string_view& s, size_t pos, size_t count) noexcept
|
||||
{
|
||||
append(s.substr(pos, count));
|
||||
}
|
||||
|
||||
size_t
|
||||
string::find_last_of(char c, size_t pos) const noexcept
|
||||
{
|
||||
const char* b = data();
|
||||
size_t n = size();
|
||||
if (pos > n)
|
||||
pos = n;
|
||||
const char* p = (const char*)memrchr(b, c, pos);
|
||||
return p ? p - b : npos;
|
||||
}
|
||||
|
||||
size_t
|
||||
string::find_last_of(ctl::string_view set, size_t pos) const noexcept
|
||||
{
|
||||
if (empty() || set.empty())
|
||||
return npos;
|
||||
bool lut[256] = {};
|
||||
for (char c : set)
|
||||
lut[c & 255] = true;
|
||||
const char* b = data();
|
||||
size_t last = size() - 1;
|
||||
if (pos > last)
|
||||
pos = last;
|
||||
for (;;) {
|
||||
if (lut[b[pos] & 255])
|
||||
return pos;
|
||||
if (!pos)
|
||||
return npos;
|
||||
--pos;
|
||||
}
|
||||
}
|
||||
|
||||
size_t
|
||||
string::find_first_of(char c, size_t pos) const noexcept
|
||||
{
|
||||
size_t n = size();
|
||||
if (pos >= n)
|
||||
return npos;
|
||||
const char* b = data();
|
||||
const char* p = (const char*)memchr(b + pos, c, n - pos);
|
||||
return p ? p - b : npos;
|
||||
}
|
||||
|
||||
size_t
|
||||
string::find_first_of(ctl::string_view set, size_t pos) const noexcept
|
||||
{
|
||||
if (set.empty())
|
||||
return npos;
|
||||
bool lut[256] = {};
|
||||
for (char c : set)
|
||||
lut[c & 255] = true;
|
||||
const char* b = data();
|
||||
size_t n = size();
|
||||
for (;;) {
|
||||
if (pos >= n)
|
||||
return npos;
|
||||
if (lut[b[pos] & 255])
|
||||
return pos;
|
||||
++pos;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ctl
|
||||
|
|
|
@ -125,7 +125,6 @@ class string
|
|||
void append(char, size_t) noexcept;
|
||||
void append(unsigned long) noexcept;
|
||||
void append(const void*, size_t) noexcept;
|
||||
void append(const ctl::string_view&, size_t, size_t = npos) noexcept;
|
||||
string& insert(size_t, ctl::string_view) noexcept;
|
||||
string& erase(size_t = 0, size_t = npos) noexcept;
|
||||
string substr(size_t = 0, size_t = npos) const noexcept;
|
||||
|
@ -137,10 +136,6 @@ class string
|
|||
bool starts_with(ctl::string_view) const noexcept;
|
||||
size_t find(char, size_t = 0) const noexcept;
|
||||
size_t find(ctl::string_view, size_t = 0) const noexcept;
|
||||
size_t find_first_of(char, size_t = 0) const noexcept;
|
||||
size_t find_first_of(ctl::string_view, size_t = 0) const noexcept;
|
||||
size_t find_last_of(char, size_t = npos) const noexcept;
|
||||
size_t find_last_of(ctl::string_view, size_t = npos) const noexcept;
|
||||
|
||||
void swap(string& s) noexcept
|
||||
{
|
||||
|
@ -307,7 +302,7 @@ class string
|
|||
append(ch);
|
||||
}
|
||||
|
||||
void append(const ctl::string_view& s) noexcept
|
||||
void append(const ctl::string_view s) noexcept
|
||||
{
|
||||
append(s.p, s.n);
|
||||
}
|
||||
|
|
|
@ -108,66 +108,4 @@ string_view::starts_with(const string_view s) const noexcept
|
|||
return !memcmp(p, s.p, s.n);
|
||||
}
|
||||
|
||||
size_t
|
||||
string_view::find_last_of(char c, size_t pos) const noexcept
|
||||
{
|
||||
const char* b = data();
|
||||
size_t n = size();
|
||||
if (pos > n)
|
||||
pos = n;
|
||||
const char* p = (const char*)memrchr(b, c, pos);
|
||||
return p ? p - b : npos;
|
||||
}
|
||||
|
||||
size_t
|
||||
string_view::find_last_of(ctl::string_view set, size_t pos) const noexcept
|
||||
{
|
||||
if (empty() || set.empty())
|
||||
return npos;
|
||||
bool lut[256] = {};
|
||||
for (char c : set)
|
||||
lut[c & 255] = true;
|
||||
const char* b = data();
|
||||
size_t last = size() - 1;
|
||||
if (pos > last)
|
||||
pos = last;
|
||||
for (;;) {
|
||||
if (lut[b[pos] & 255])
|
||||
return pos;
|
||||
if (!pos)
|
||||
return npos;
|
||||
--pos;
|
||||
}
|
||||
}
|
||||
|
||||
size_t
|
||||
string_view::find_first_of(char c, size_t pos) const noexcept
|
||||
{
|
||||
size_t n = size();
|
||||
if (pos >= n)
|
||||
return npos;
|
||||
const char* b = data();
|
||||
const char* p = (const char*)memchr(b + pos, c, n - pos);
|
||||
return p ? p - b : npos;
|
||||
}
|
||||
|
||||
size_t
|
||||
string_view::find_first_of(ctl::string_view set, size_t pos) const noexcept
|
||||
{
|
||||
if (set.empty())
|
||||
return npos;
|
||||
bool lut[256] = {};
|
||||
for (char c : set)
|
||||
lut[c & 255] = true;
|
||||
const char* b = data();
|
||||
size_t n = size();
|
||||
for (;;) {
|
||||
if (pos >= n)
|
||||
return npos;
|
||||
if (lut[b[pos] & 255])
|
||||
return pos;
|
||||
++pos;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ctl
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// -*-mode:c++;indent-tabs-mode:nil;c-basic-offset:4;tab-width:8;coding:utf-8-*-
|
||||
// vi: set et ft=cpp ts=4 sts=4 sw=4 fenc=utf-8 :vi
|
||||
#ifndef CTL_STRINGVIEW_H_
|
||||
#define CTL_STRINGVIEW_H_
|
||||
#ifndef COSMOPOLITAN_CTL_STRINGVIEW_H_
|
||||
#define COSMOPOLITAN_CTL_STRINGVIEW_H_
|
||||
#include "utility.h"
|
||||
|
||||
namespace ctl {
|
||||
|
@ -45,10 +45,6 @@ struct string_view
|
|||
string_view substr(size_t = 0, size_t = npos) const noexcept;
|
||||
size_t find(char, size_t = 0) const noexcept;
|
||||
size_t find(string_view, size_t = 0) const noexcept;
|
||||
size_t find_first_of(char, size_t = 0) const noexcept;
|
||||
size_t find_first_of(ctl::string_view, size_t = 0) const noexcept;
|
||||
size_t find_last_of(char, size_t = npos) const noexcept;
|
||||
size_t find_last_of(ctl::string_view, size_t = npos) const noexcept;
|
||||
|
||||
constexpr string_view& operator=(const string_view s) noexcept
|
||||
{
|
||||
|
@ -113,12 +109,12 @@ struct string_view
|
|||
return p[n - 1];
|
||||
}
|
||||
|
||||
constexpr const_iterator begin() const noexcept
|
||||
constexpr const_iterator begin() noexcept
|
||||
{
|
||||
return p;
|
||||
}
|
||||
|
||||
constexpr const_iterator end() const noexcept
|
||||
constexpr const_iterator end() noexcept
|
||||
{
|
||||
return p + n;
|
||||
}
|
||||
|
@ -161,4 +157,4 @@ struct string_view
|
|||
|
||||
} // namespace ctl
|
||||
|
||||
#endif // CTL_STRINGVIEW_H_
|
||||
#endif // COSMOPOLITAN_CTL_STRINGVIEW_H_
|
||||
|
|
|
@ -525,7 +525,7 @@ class vector
|
|||
capacity_ = new_capacity;
|
||||
}
|
||||
|
||||
[[no_unique_address]] Allocator alloc_;
|
||||
Allocator alloc_;
|
||||
pointer data_;
|
||||
size_type size_;
|
||||
size_type capacity_;
|
||||
|
|
|
@ -2,9 +2,7 @@
|
|||
#── vi: set noet ft=make ts=8 sw=8 fenc=utf-8 :vi ────────────────────┘
|
||||
|
||||
.PHONY: o/$(MODE)/dsp
|
||||
o/$(MODE)/dsp: o/$(MODE)/dsp/audio \
|
||||
o/$(MODE)/dsp/core \
|
||||
o/$(MODE)/dsp: o/$(MODE)/dsp/core \
|
||||
o/$(MODE)/dsp/mpeg \
|
||||
o/$(MODE)/dsp/scale \
|
||||
o/$(MODE)/dsp/prog \
|
||||
o/$(MODE)/dsp/tty
|
||||
|
|
|
@ -1,56 +0,0 @@
|
|||
#-*-mode:makefile-gmake;indent-tabs-mode:t;tab-width:8;coding:utf-8-*-┐
|
||||
#── vi: set noet ft=make ts=8 sw=8 fenc=utf-8 :vi ────────────────────┘
|
||||
|
||||
PKGS += DSP_AUDIO
|
||||
|
||||
DSP_AUDIO_ARTIFACTS += DSP_AUDIO_A
|
||||
DSP_AUDIO = $(DSP_AUDIO_A_DEPS) $(DSP_AUDIO_A)
|
||||
DSP_AUDIO_A = o/$(MODE)/dsp/audio/audio.a
|
||||
DSP_AUDIO_A_FILES := $(wildcard dsp/audio/*)
|
||||
DSP_AUDIO_A_HDRS = $(filter %.h,$(DSP_AUDIO_A_FILES)) dsp/audio/cosmoaudio/cosmoaudio.h
|
||||
DSP_AUDIO_A_SRCS = $(filter %.c,$(DSP_AUDIO_A_FILES))
|
||||
|
||||
DSP_AUDIO_A_DATA = \
|
||||
dsp/audio/cosmoaudio/miniaudio.h \
|
||||
dsp/audio/cosmoaudio/cosmoaudio.c \
|
||||
dsp/audio/cosmoaudio/cosmoaudio.h \
|
||||
dsp/audio/cosmoaudio/cosmoaudio.dll \
|
||||
|
||||
DSP_AUDIO_A_OBJS = \
|
||||
$(DSP_AUDIO_A_SRCS:%.c=o/$(MODE)/%.o) \
|
||||
$(DSP_AUDIO_A_DATA:%=o/$(MODE)/%.zip.o) \
|
||||
|
||||
DSP_AUDIO_A_CHECKS = \
|
||||
$(DSP_AUDIO_A).pkg \
|
||||
$(DSP_AUDIO_A_HDRS:%=o/$(MODE)/%.ok)
|
||||
|
||||
DSP_AUDIO_A_DIRECTDEPS = \
|
||||
LIBC_CALLS \
|
||||
LIBC_DLOPEN \
|
||||
LIBC_INTRIN \
|
||||
LIBC_NEXGEN32E \
|
||||
LIBC_STR \
|
||||
LIBC_SYSV \
|
||||
LIBC_PROC \
|
||||
LIBC_THREAD \
|
||||
|
||||
DSP_AUDIO_A_DEPS := \
|
||||
$(call uniq,$(foreach x,$(DSP_AUDIO_A_DIRECTDEPS),$($(x))))
|
||||
|
||||
$(DSP_AUDIO_A): dsp/audio/ \
|
||||
$(DSP_AUDIO_A).pkg \
|
||||
$(DSP_AUDIO_A_OBJS)
|
||||
|
||||
$(DSP_AUDIO_A).pkg: \
|
||||
$(DSP_AUDIO_A_OBJS) \
|
||||
$(foreach x,$(DSP_AUDIO_A_DIRECTDEPS),$($(x)_A).pkg)
|
||||
|
||||
DSP_AUDIO_LIBS = $(foreach x,$(DSP_AUDIO_ARTIFACTS),$($(x)))
|
||||
DSP_AUDIO_SRCS = $(foreach x,$(DSP_AUDIO_ARTIFACTS),$($(x)_SRCS))
|
||||
DSP_AUDIO_HDRS = $(foreach x,$(DSP_AUDIO_ARTIFACTS),$($(x)_HDRS))
|
||||
DSP_AUDIO_CHECKS = $(foreach x,$(DSP_AUDIO_ARTIFACTS),$($(x)_CHECKS))
|
||||
DSP_AUDIO_OBJS = $(foreach x,$(DSP_AUDIO_ARTIFACTS),$($(x)_OBJS))
|
||||
$(DSP_AUDIO_OBJS): $(BUILD_FILES) dsp/audio/BUILD.mk
|
||||
|
||||
.PHONY: o/$(MODE)/dsp/audio
|
||||
o/$(MODE)/dsp/audio: $(DSP_AUDIO_CHECKS)
|
|
@ -1,358 +0,0 @@
|
|||
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
|
||||
│ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi │
|
||||
╞══════════════════════════════════════════════════════════════════════════════╡
|
||||
│ Copyright 2024 Justine Alexandra Roberts Tunney │
|
||||
│ │
|
||||
│ Permission to use, copy, modify, and/or distribute this software for │
|
||||
│ any purpose with or without fee is hereby granted, provided that the │
|
||||
│ above copyright notice and this permission notice appear in all copies. │
|
||||
│ │
|
||||
│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │
|
||||
│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │
|
||||
│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │
|
||||
│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │
|
||||
│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │
|
||||
│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │
|
||||
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
|
||||
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "dsp/audio/cosmoaudio/cosmoaudio.h"
|
||||
#include "dsp/audio/describe.h"
|
||||
#include "libc/calls/blockcancel.internal.h"
|
||||
#include "libc/calls/calls.h"
|
||||
#include "libc/calls/struct/sigset.internal.h"
|
||||
#include "libc/calls/struct/stat.h"
|
||||
#include "libc/calls/struct/timespec.h"
|
||||
#include "libc/dce.h"
|
||||
#include "libc/dlopen/dlfcn.h"
|
||||
#include "libc/errno.h"
|
||||
#include "libc/intrin/describeflags.h"
|
||||
#include "libc/intrin/strace.h"
|
||||
#include "libc/limits.h"
|
||||
#include "libc/macros.h"
|
||||
#include "libc/proc/posix_spawn.h"
|
||||
#include "libc/runtime/runtime.h"
|
||||
#include "libc/str/str.h"
|
||||
#include "libc/sysv/consts/o.h"
|
||||
#include "libc/temp.h"
|
||||
#include "libc/thread/thread.h"
|
||||
|
||||
#define COSMOAUDIO_MINIMUM_VERISON 1
|
||||
|
||||
#define COSMOAUDIO_DSO_NAME "cosmoaudio." STRINGIFY(COSMOAUDIO_MINIMUM_VERISON)
|
||||
|
||||
__static_yoink("dsp/audio/cosmoaudio/miniaudio.h");
|
||||
__static_yoink("dsp/audio/cosmoaudio/cosmoaudio.h");
|
||||
__static_yoink("dsp/audio/cosmoaudio/cosmoaudio.c");
|
||||
__static_yoink("dsp/audio/cosmoaudio/cosmoaudio.dll");
|
||||
|
||||
static const struct Source {
|
||||
const char *zip;
|
||||
const char *name;
|
||||
} srcs[] = {
|
||||
{"/zip/dsp/audio/cosmoaudio/miniaudio.h", "miniaudio.h"},
|
||||
{"/zip/dsp/audio/cosmoaudio/cosmoaudio.h", "cosmoaudio.h"},
|
||||
{"/zip/dsp/audio/cosmoaudio/cosmoaudio.c", "cosmoaudio.c"}, // must last
|
||||
};
|
||||
|
||||
static struct {
|
||||
pthread_once_t once;
|
||||
typeof(cosmoaudio_open) *open;
|
||||
typeof(cosmoaudio_close) *close;
|
||||
typeof(cosmoaudio_write) *write;
|
||||
typeof(cosmoaudio_flush) *flush;
|
||||
typeof(cosmoaudio_read) *read;
|
||||
typeof(cosmoaudio_poll) *poll;
|
||||
} g_audio;
|
||||
|
||||
static const char *cosmoaudio_tmp_dir(void) {
|
||||
const char *tmpdir;
|
||||
if (!(tmpdir = getenv("TMPDIR")) || !*tmpdir)
|
||||
if (!(tmpdir = getenv("HOME")) || !*tmpdir)
|
||||
tmpdir = ".";
|
||||
return tmpdir;
|
||||
}
|
||||
|
||||
static bool cosmoaudio_app_dir(char *path, size_t size) {
|
||||
strlcpy(path, cosmoaudio_tmp_dir(), size);
|
||||
strlcat(path, "/.cosmo/", size);
|
||||
if (makedirs(path, 0755))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool cosmoaudio_dso_path(char *path, size_t size) {
|
||||
if (!cosmoaudio_app_dir(path, size))
|
||||
return false;
|
||||
strlcat(path, COSMOAUDIO_DSO_NAME, size);
|
||||
if (IsWindows()) {
|
||||
strlcat(path, ".dll", size);
|
||||
} else if (IsXnu()) {
|
||||
strlcat(path, ".dylib", size);
|
||||
} else {
|
||||
strlcat(path, ".so", size);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool cosmoaudio_extract(const char *zip, const char *to) {
|
||||
int fdin, fdout;
|
||||
char stage[PATH_MAX];
|
||||
strlcpy(stage, to, sizeof(stage));
|
||||
if (strlcat(stage, ".XXXXXX", sizeof(stage)) >= sizeof(stage)) {
|
||||
errno = ENAMETOOLONG;
|
||||
return false;
|
||||
}
|
||||
if ((fdout = mkostemp(stage, O_CLOEXEC)) == -1)
|
||||
return false;
|
||||
if ((fdin = open(zip, O_RDONLY | O_CLOEXEC)) == -1) {
|
||||
close(fdout);
|
||||
unlink(stage);
|
||||
return false;
|
||||
}
|
||||
if (copyfd(fdin, fdout, -1) == -1) {
|
||||
close(fdin);
|
||||
close(fdout);
|
||||
unlink(stage);
|
||||
return false;
|
||||
}
|
||||
if (close(fdout)) {
|
||||
close(fdin);
|
||||
unlink(stage);
|
||||
return false;
|
||||
}
|
||||
if (close(fdin)) {
|
||||
unlink(stage);
|
||||
return false;
|
||||
}
|
||||
if (rename(stage, to)) {
|
||||
unlink(stage);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool cosmoaudio_build(const char *dso) {
|
||||
|
||||
// extract sauce
|
||||
char src[PATH_MAX];
|
||||
for (int i = 0; i < sizeof(srcs) / sizeof(*srcs); ++i) {
|
||||
if (!cosmoaudio_app_dir(src, PATH_MAX))
|
||||
return false;
|
||||
strlcat(src, srcs[i].name, sizeof(src));
|
||||
if (!cosmoaudio_extract(srcs[i].zip, src))
|
||||
return false;
|
||||
}
|
||||
|
||||
// create temporary name for compiled dso
|
||||
// it'll ensure build operation is atomic
|
||||
int fd;
|
||||
char tmpdso[PATH_MAX];
|
||||
strlcpy(tmpdso, dso, sizeof(tmpdso));
|
||||
strlcat(tmpdso, ".XXXXXX", sizeof(tmpdso));
|
||||
if ((fd = mkostemp(tmpdso, O_CLOEXEC)) != -1) {
|
||||
close(fd);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
// build cosmoaudio with host c compiler
|
||||
char *args[] = {
|
||||
"cc", //
|
||||
"-w", //
|
||||
"-I.", //
|
||||
"-O2", //
|
||||
"-fPIC", //
|
||||
"-shared", //
|
||||
"-pthread", //
|
||||
"-DNDEBUG", //
|
||||
IsAarch64() ? "-ffixed-x28" : "-DIGNORE1", //
|
||||
src, //
|
||||
"-o", //
|
||||
tmpdso, //
|
||||
"-lm", //
|
||||
IsNetbsd() ? 0 : "-ldl", //
|
||||
NULL,
|
||||
};
|
||||
int pid, ws;
|
||||
errno_t err = posix_spawnp(&pid, args[0], NULL, NULL, args, environ);
|
||||
if (err)
|
||||
return false;
|
||||
while (waitpid(pid, &ws, 0) == -1)
|
||||
if (errno != EINTR)
|
||||
return false;
|
||||
if (ws)
|
||||
return false;
|
||||
|
||||
// move dso to its final destination
|
||||
if (rename(tmpdso, dso))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void *cosmoaudio_dlopen(const char *name) {
|
||||
void *handle;
|
||||
if ((handle = cosmo_dlopen(name, RTLD_NOW))) {
|
||||
typeof(cosmoaudio_version) *version;
|
||||
if ((version = cosmo_dlsym(handle, "cosmoaudio_version")))
|
||||
if (version() >= COSMOAUDIO_MINIMUM_VERISON)
|
||||
return handle;
|
||||
cosmo_dlclose(handle);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void cosmoaudio_setup_impl(void) {
|
||||
void *handle;
|
||||
if (IsOpenbsd())
|
||||
return; // no dlopen support yet
|
||||
if (IsXnu() && !IsXnuSilicon())
|
||||
return; // no dlopen support yet
|
||||
if (!(handle = cosmoaudio_dlopen(COSMOAUDIO_DSO_NAME ".so")) &&
|
||||
!(handle = cosmoaudio_dlopen("lib" COSMOAUDIO_DSO_NAME ".so")) &&
|
||||
!(handle = cosmoaudio_dlopen("cosmoaudio.so")) &&
|
||||
!(handle = cosmoaudio_dlopen("libcosmoaudio.so"))) {
|
||||
char dso[PATH_MAX];
|
||||
if (!cosmoaudio_dso_path(dso, sizeof(dso)))
|
||||
return;
|
||||
if ((handle = cosmoaudio_dlopen(dso)))
|
||||
goto WeAreGood;
|
||||
if (IsWindows()) {
|
||||
if (cosmoaudio_extract("/zip/dsp/audio/cosmoaudio/cosmoaudio.dll", dso)) {
|
||||
if ((handle = cosmoaudio_dlopen(dso))) {
|
||||
goto WeAreGood;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!cosmoaudio_build(dso))
|
||||
return;
|
||||
if (!(handle = cosmoaudio_dlopen(dso)))
|
||||
return;
|
||||
}
|
||||
WeAreGood:
|
||||
g_audio.open = cosmo_dlsym(handle, "cosmoaudio_open");
|
||||
g_audio.close = cosmo_dlsym(handle, "cosmoaudio_close");
|
||||
g_audio.write = cosmo_dlsym(handle, "cosmoaudio_write");
|
||||
g_audio.flush = cosmo_dlsym(handle, "cosmoaudio_flush");
|
||||
g_audio.read = cosmo_dlsym(handle, "cosmoaudio_read");
|
||||
g_audio.poll = cosmo_dlsym(handle, "cosmoaudio_poll");
|
||||
}
|
||||
|
||||
static void cosmoaudio_setup(void) {
|
||||
BLOCK_CANCELATION;
|
||||
cosmoaudio_setup_impl();
|
||||
ALLOW_CANCELATION;
|
||||
}
|
||||
|
||||
static void cosmoaudio_init(void) {
|
||||
pthread_once(&g_audio.once, cosmoaudio_setup);
|
||||
}
|
||||
|
||||
COSMOAUDIO_ABI int cosmoaudio_open(
|
||||
struct CosmoAudio **out_ca, const struct CosmoAudioOpenOptions *options) {
|
||||
int status;
|
||||
char sbuf[32];
|
||||
char dbuf[256];
|
||||
cosmoaudio_init();
|
||||
if (g_audio.open) {
|
||||
BLOCK_SIGNALS;
|
||||
status = g_audio.open(out_ca, options);
|
||||
ALLOW_SIGNALS;
|
||||
} else {
|
||||
status = COSMOAUDIO_ELINK;
|
||||
}
|
||||
STRACE("cosmoaudio_open([%p], %s) → %s",
|
||||
out_ca ? *out_ca : (struct CosmoAudio *)-1,
|
||||
cosmoaudio_describe_open_options(dbuf, sizeof(dbuf), options),
|
||||
cosmoaudio_describe_status(sbuf, sizeof(sbuf), status));
|
||||
return status;
|
||||
}
|
||||
|
||||
COSMOAUDIO_ABI int cosmoaudio_close(struct CosmoAudio *ca) {
|
||||
int status;
|
||||
char sbuf[32];
|
||||
if (g_audio.close) {
|
||||
BLOCK_SIGNALS;
|
||||
status = g_audio.close(ca);
|
||||
ALLOW_SIGNALS;
|
||||
} else {
|
||||
status = COSMOAUDIO_ELINK;
|
||||
}
|
||||
STRACE("cosmoaudio_close(%p) → %s", ca,
|
||||
cosmoaudio_describe_status(sbuf, sizeof(sbuf), status));
|
||||
return status;
|
||||
}
|
||||
|
||||
COSMOAUDIO_ABI int cosmoaudio_write(struct CosmoAudio *ca, const float *data,
|
||||
int frames) {
|
||||
int status;
|
||||
char sbuf[32];
|
||||
if (g_audio.write) {
|
||||
BLOCK_SIGNALS;
|
||||
status = g_audio.write(ca, data, frames);
|
||||
ALLOW_SIGNALS;
|
||||
} else {
|
||||
status = COSMOAUDIO_ELINK;
|
||||
}
|
||||
if (frames <= 0 || frames >= 160)
|
||||
DATATRACE("cosmoaudio_write(%p, %p, %d) → %s", ca, data, frames,
|
||||
cosmoaudio_describe_status(sbuf, sizeof(sbuf), status));
|
||||
return status;
|
||||
}
|
||||
|
||||
COSMOAUDIO_ABI int cosmoaudio_read(struct CosmoAudio *ca, float *data,
|
||||
int frames) {
|
||||
int status;
|
||||
char sbuf[32];
|
||||
if (g_audio.read) {
|
||||
BLOCK_SIGNALS;
|
||||
status = g_audio.read(ca, data, frames);
|
||||
ALLOW_SIGNALS;
|
||||
} else {
|
||||
status = COSMOAUDIO_ELINK;
|
||||
}
|
||||
if (frames <= 0 || frames >= 160)
|
||||
DATATRACE("cosmoaudio_read(%p, %p, %d) → %s", ca, data, frames,
|
||||
cosmoaudio_describe_status(sbuf, sizeof(sbuf), status));
|
||||
return status;
|
||||
}
|
||||
|
||||
COSMOAUDIO_ABI int cosmoaudio_flush(struct CosmoAudio *ca) {
|
||||
int status;
|
||||
char sbuf[32];
|
||||
if (g_audio.flush) {
|
||||
BLOCK_SIGNALS;
|
||||
status = g_audio.flush(ca);
|
||||
ALLOW_SIGNALS;
|
||||
} else {
|
||||
status = COSMOAUDIO_ELINK;
|
||||
}
|
||||
DATATRACE("cosmoaudio_flush(%p) → %s", ca,
|
||||
cosmoaudio_describe_status(sbuf, sizeof(sbuf), status));
|
||||
return status;
|
||||
}
|
||||
|
||||
COSMOAUDIO_ABI int cosmoaudio_poll(struct CosmoAudio *ca,
|
||||
int *in_out_readFrames,
|
||||
int *in_out_writeFrames) {
|
||||
int status;
|
||||
char sbuf[32];
|
||||
char fbuf[2][20];
|
||||
if (g_audio.poll) {
|
||||
BLOCK_SIGNALS;
|
||||
status = g_audio.poll(ca, in_out_readFrames, in_out_writeFrames);
|
||||
ALLOW_SIGNALS;
|
||||
} else {
|
||||
status = COSMOAUDIO_ELINK;
|
||||
}
|
||||
DATATRACE("cosmoaudio_poll(%p, %s, %s) → %s", ca,
|
||||
cosmoaudio_describe_poll_frames(fbuf[0], sizeof(fbuf[0]),
|
||||
in_out_readFrames),
|
||||
cosmoaudio_describe_poll_frames(fbuf[1], sizeof(fbuf[1]),
|
||||
in_out_writeFrames),
|
||||
cosmoaudio_describe_status(sbuf, sizeof(sbuf), status));
|
||||
return status;
|
||||
}
|
3
dsp/audio/cosmoaudio/.gitignore
vendored
3
dsp/audio/cosmoaudio/.gitignore
vendored
|
@ -1,3 +0,0 @@
|
|||
*.o
|
||||
/Debug
|
||||
/Release
|
|
@ -1,87 +0,0 @@
|
|||
# Makefile for MSVC x64 Command Line Developer Tools
|
||||
#
|
||||
# nmake /f Makefile.msvc check
|
||||
# nmake /f Makefile.msvc MODE=debug check
|
||||
#
|
||||
# Note: MSVC 2019 makes the DLL 64kb smaller than MSVC 2022.
|
||||
|
||||
# Compiler and linker
|
||||
CC=cl
|
||||
LINK=link
|
||||
|
||||
# Build mode (can be overridden from command line)
|
||||
!IFNDEF MODE
|
||||
MODE=release
|
||||
!ENDIF
|
||||
|
||||
# Library dependencies.
|
||||
TEST_LIBS=OneCore.lib
|
||||
|
||||
# Compiler flags
|
||||
CFLAGS_COMMON=/nologo /W4 /Gy /EHsc
|
||||
CFLAGS_DEBUG=/Od /Zi /MDd /D_DEBUG
|
||||
CFLAGS_RELEASE=/O2 /MT /DNDEBUG
|
||||
|
||||
!IF "$(MODE)"=="debug"
|
||||
CFLAGS=$(CFLAGS_COMMON) $(CFLAGS_DEBUG)
|
||||
LDFLAGS=/DEBUG
|
||||
OUT_DIR=Debug
|
||||
!ELSE
|
||||
CFLAGS=$(CFLAGS_COMMON) $(CFLAGS_RELEASE) /GL
|
||||
LDFLAGS=/RELEASE /OPT:REF /OPT:ICF /LTCG /INCREMENTAL:NO
|
||||
OUT_DIR=Release
|
||||
!ENDIF
|
||||
|
||||
# Additional flags for DLL
|
||||
DLL_CFLAGS=$(CFLAGS) /D_USRDLL /D_WINDLL
|
||||
|
||||
# Linker flags
|
||||
LDFLAGS=/NOLOGO /SUBSYSTEM:CONSOLE $(LDFLAGS)
|
||||
|
||||
# Output file names
|
||||
DLL_TARGET=$(OUT_DIR)\cosmoaudio.dll
|
||||
TEST_TARGET=$(OUT_DIR)\test.exe
|
||||
|
||||
# Source files
|
||||
DLL_SOURCES=cosmoaudio.c
|
||||
TEST_SOURCES=test.c
|
||||
|
||||
# Object files
|
||||
DLL_OBJECTS=$(OUT_DIR)\cosmoaudio.obj
|
||||
TEST_OBJECTS=$(OUT_DIR)\test.obj
|
||||
|
||||
# Default target
|
||||
all: $(OUT_DIR) $(DLL_TARGET) $(TEST_TARGET)
|
||||
|
||||
# Create output directory
|
||||
$(OUT_DIR):
|
||||
if not exist $(OUT_DIR) mkdir $(OUT_DIR)
|
||||
|
||||
# Rule to build the DLL
|
||||
$(DLL_TARGET): $(OUT_DIR) $(DLL_OBJECTS)
|
||||
$(LINK) /DLL $(LDFLAGS) /OUT:$(DLL_TARGET) $(DLL_OBJECTS)
|
||||
|
||||
# Rule to build the test program
|
||||
$(TEST_TARGET): $(OUT_DIR) $(TEST_OBJECTS) $(DLL_TARGET)
|
||||
$(LINK) $(LDFLAGS) /OUT:$(TEST_TARGET) $(TEST_OBJECTS) $(DLL_TARGET:.dll=.lib) $(TEST_LIBS)
|
||||
|
||||
# Rules to compile .c files to .obj files with header dependencies
|
||||
{.}.c{$(OUT_DIR)}.obj:
|
||||
$(CC) $(DLL_CFLAGS) /c /Fo$(OUT_DIR)\ $<
|
||||
|
||||
$(OUT_DIR)\test.obj: $(OUT_DIR) test.c cosmoaudio.h
|
||||
$(CC) $(CFLAGS) /c /Fo$(OUT_DIR)\ test.c
|
||||
|
||||
$(OUT_DIR)\cosmoaudio.obj: $(OUT_DIR) cosmoaudio.c miniaudio.h cosmoaudio.h
|
||||
$(CC) $(DLL_CFLAGS) /c /Fo$(OUT_DIR)\ cosmoaudio.c
|
||||
|
||||
# Clean target
|
||||
clean:
|
||||
if exist $(OUT_DIR) rmdir /s /q $(OUT_DIR)
|
||||
|
||||
# Run tests (now called 'check')
|
||||
check: $(TEST_TARGET)
|
||||
$(TEST_TARGET)
|
||||
|
||||
# Phony targets
|
||||
.PHONY: all clean check
|
|
@ -1,519 +0,0 @@
|
|||
// Copyright 2024 Justine Alexandra Roberts Tunney
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for
|
||||
// any purpose with or without fee is hereby granted, provided that the
|
||||
// above copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||
// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||
// WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||
// AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
|
||||
// DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
|
||||
// PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
|
||||
// TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
#define COSMOAUDIO_BUILD
|
||||
#include "cosmoaudio.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define MA_DEBUG_OUTPUT
|
||||
#define MA_DR_MP3_NO_STDIO
|
||||
#define MA_NO_DECODING
|
||||
#define MA_NO_ENCODING
|
||||
#define MA_NO_ENGINE
|
||||
#define MA_NO_GENERATION
|
||||
#define MA_NO_NODE_GRAPH
|
||||
#define MA_NO_RESOURCE_MANAGER
|
||||
#define MA_STATIC
|
||||
|
||||
#define MINIAUDIO_IMPLEMENTATION
|
||||
#include "miniaudio.h"
|
||||
|
||||
struct CosmoAudio {
|
||||
enum CosmoAudioDeviceType deviceType;
|
||||
ma_uint32 outputBufferFrames;
|
||||
ma_uint32 inputBufferFrames;
|
||||
int sampleRate;
|
||||
int channels;
|
||||
int isLeft;
|
||||
ma_context context;
|
||||
ma_device device;
|
||||
ma_pcm_rb output;
|
||||
ma_pcm_rb input;
|
||||
ma_event event;
|
||||
ma_log log;
|
||||
};
|
||||
|
||||
static int read_ring_buffer(ma_log* log, ma_pcm_rb* rb, float* pOutput,
|
||||
ma_uint32 frameCount, ma_uint32 channels) {
|
||||
ma_result result;
|
||||
ma_uint32 framesRead;
|
||||
ma_uint32 framesToRead;
|
||||
for (framesRead = 0; framesRead < frameCount; framesRead += framesToRead) {
|
||||
framesToRead = frameCount - framesRead;
|
||||
void* pMappedBuffer;
|
||||
result = ma_pcm_rb_acquire_read(rb, &framesToRead, &pMappedBuffer);
|
||||
if (result != MA_SUCCESS) {
|
||||
ma_log_postf(log, MA_LOG_LEVEL_WARNING,
|
||||
"ma_pcm_rb_acquire_read failed: %s\n",
|
||||
ma_result_description(result));
|
||||
return COSMOAUDIO_ERROR;
|
||||
}
|
||||
if (!framesToRead)
|
||||
break;
|
||||
memcpy(pOutput + framesRead * channels, pMappedBuffer,
|
||||
framesToRead * channels * sizeof(float));
|
||||
result = ma_pcm_rb_commit_read(rb, framesToRead);
|
||||
if (result != MA_SUCCESS) {
|
||||
if (result == MA_AT_END) {
|
||||
framesRead += framesToRead;
|
||||
break;
|
||||
}
|
||||
ma_log_postf(log, MA_LOG_LEVEL_WARNING,
|
||||
"ma_pcm_rb_commit_read failed: %s\n",
|
||||
ma_result_description(result));
|
||||
return COSMOAUDIO_ERROR;
|
||||
}
|
||||
}
|
||||
return framesRead;
|
||||
}
|
||||
|
||||
static int write_ring_buffer(ma_log* log, ma_pcm_rb* rb, const float* pInput,
|
||||
ma_uint32 frameCount, ma_uint32 channels) {
|
||||
ma_result result;
|
||||
ma_uint32 framesWritten;
|
||||
ma_uint32 framesToWrite;
|
||||
for (framesWritten = 0; framesWritten < frameCount;
|
||||
framesWritten += framesToWrite) {
|
||||
framesToWrite = frameCount - framesWritten;
|
||||
void* pMappedBuffer;
|
||||
result = ma_pcm_rb_acquire_write(rb, &framesToWrite, &pMappedBuffer);
|
||||
if (result != MA_SUCCESS) {
|
||||
ma_log_postf(log, MA_LOG_LEVEL_WARNING,
|
||||
"ma_pcm_rb_acquire_write failed: %s\n",
|
||||
ma_result_description(result));
|
||||
return COSMOAUDIO_ERROR;
|
||||
}
|
||||
if (!framesToWrite)
|
||||
break;
|
||||
memcpy(pMappedBuffer, pInput + framesWritten * channels,
|
||||
framesToWrite * channels * sizeof(float));
|
||||
result = ma_pcm_rb_commit_write(rb, framesToWrite);
|
||||
if (result != MA_SUCCESS) {
|
||||
if (result == MA_AT_END) {
|
||||
framesWritten += framesToWrite;
|
||||
break;
|
||||
}
|
||||
ma_log_postf(log, MA_LOG_LEVEL_WARNING,
|
||||
"ma_pcm_rb_commit_write failed: %s\n",
|
||||
ma_result_description(result));
|
||||
return COSMOAUDIO_ERROR;
|
||||
}
|
||||
}
|
||||
return framesWritten;
|
||||
}
|
||||
|
||||
static void data_callback_f32(ma_device* pDevice, float* pOutput,
|
||||
const float* pInput, ma_uint32 frameCount) {
|
||||
struct CosmoAudio* ca = (struct CosmoAudio*)pDevice->pUserData;
|
||||
if (ca->deviceType & kCosmoAudioDeviceTypePlayback) {
|
||||
//
|
||||
// "By default, miniaudio will pre-silence the data callback's
|
||||
// output buffer. If you know that you will always write valid data
|
||||
// to the output buffer you can disable pre-silencing by setting
|
||||
// the noPreSilence config option in the device config to true."
|
||||
//
|
||||
// —Quoth miniaudio documentation § 16.1. Low Level API
|
||||
//
|
||||
if (ca->isLeft) {
|
||||
int framesCopied = read_ring_buffer(&ca->log, &ca->output, pOutput,
|
||||
frameCount, ca->channels);
|
||||
if (framesCopied < (int)frameCount)
|
||||
ca->isLeft = 0;
|
||||
} else {
|
||||
// TODO(jart): Maybe we should stretch the audio too short?
|
||||
int frameOffset;
|
||||
int availableFrames = ma_pcm_rb_available_read(&ca->output);
|
||||
if (availableFrames >= (int)frameCount) {
|
||||
frameOffset = 0;
|
||||
} else {
|
||||
frameOffset = frameCount - availableFrames;
|
||||
frameCount = availableFrames;
|
||||
}
|
||||
read_ring_buffer(&ca->log, &ca->output,
|
||||
pOutput + frameOffset * ca->channels, frameCount,
|
||||
ca->channels);
|
||||
ca->isLeft = 1;
|
||||
}
|
||||
}
|
||||
if (ca->deviceType & kCosmoAudioDeviceTypeCapture)
|
||||
write_ring_buffer(&ca->log, &ca->input, pInput, frameCount, ca->channels);
|
||||
ma_event_signal(&ca->event);
|
||||
}
|
||||
|
||||
static void data_callback(ma_device* pDevice, void* pOutput, const void* pInput,
|
||||
ma_uint32 frameCount) {
|
||||
data_callback_f32(pDevice, (float*)pOutput, (const float*)pInput, frameCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns current version of cosmo audio library.
|
||||
*/
|
||||
COSMOAUDIO_ABI int cosmoaudio_version(void) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens access to speaker and microphone.
|
||||
*
|
||||
* @param out_ca will receive pointer to allocated CosmoAudio object,
|
||||
* which must be freed by caller with cosmoaudio_close(); if this
|
||||
* function fails, then this will receive a NULL pointer value so
|
||||
* that cosmoaudio_close(), cosmoaudio_write() etc. can be called
|
||||
* without crashing if no error checking is performed
|
||||
* @return 0 on success, or negative error code on failure
|
||||
*/
|
||||
COSMOAUDIO_ABI int cosmoaudio_open( //
|
||||
struct CosmoAudio** out_ca, //
|
||||
const struct CosmoAudioOpenOptions* options) {
|
||||
|
||||
// Validate arguments.
|
||||
if (!out_ca)
|
||||
return COSMOAUDIO_EINVAL;
|
||||
*out_ca = NULL;
|
||||
if (!options)
|
||||
return COSMOAUDIO_EINVAL;
|
||||
if (options->sizeofThis < (int)sizeof(struct CosmoAudioOpenOptions))
|
||||
return COSMOAUDIO_EINVAL;
|
||||
if (options->bufferFrames < 0)
|
||||
return COSMOAUDIO_EINVAL;
|
||||
if (options->sampleRate < 8000)
|
||||
return COSMOAUDIO_EINVAL;
|
||||
if (options->channels < 1)
|
||||
return COSMOAUDIO_EINVAL;
|
||||
if (!options->deviceType)
|
||||
return COSMOAUDIO_EINVAL;
|
||||
if (options->deviceType &
|
||||
~(kCosmoAudioDeviceTypePlayback | kCosmoAudioDeviceTypeCapture))
|
||||
return COSMOAUDIO_EINVAL;
|
||||
|
||||
// Allocate cosmo audio object.
|
||||
struct CosmoAudio* ca;
|
||||
ca = (struct CosmoAudio*)calloc(1, sizeof(struct CosmoAudio));
|
||||
if (!ca)
|
||||
return COSMOAUDIO_ERROR;
|
||||
ca->channels = options->channels;
|
||||
ca->sampleRate = options->sampleRate;
|
||||
ca->deviceType = options->deviceType;
|
||||
|
||||
// Create win32-style condition variable.
|
||||
if (ma_event_init(&ca->event) != MA_SUCCESS) {
|
||||
free(ca);
|
||||
return COSMOAUDIO_ERROR;
|
||||
}
|
||||
|
||||
// Create audio log.
|
||||
if (ma_log_init(NULL, &ca->log) != MA_SUCCESS) {
|
||||
ma_event_uninit(&ca->event);
|
||||
free(ca);
|
||||
return COSMOAUDIO_ERROR;
|
||||
}
|
||||
if (!options->debugLog)
|
||||
ca->log.callbackCount = 0;
|
||||
|
||||
// Create audio context.
|
||||
ma_context_config contextConfig = ma_context_config_init();
|
||||
contextConfig.pLog = &ca->log;
|
||||
if (ma_context_init(NULL, 0, &contextConfig, &ca->context) != MA_SUCCESS) {
|
||||
ma_event_uninit(&ca->event);
|
||||
ma_log_uninit(&ca->log);
|
||||
free(ca);
|
||||
return COSMOAUDIO_ERROR;
|
||||
}
|
||||
|
||||
// Initialize device.
|
||||
ma_result result;
|
||||
ma_device_config deviceConfig;
|
||||
deviceConfig = ma_device_config_init(ca->deviceType);
|
||||
deviceConfig.sampleRate = ca->sampleRate;
|
||||
if (ca->deviceType & kCosmoAudioDeviceTypeCapture) {
|
||||
deviceConfig.capture.channels = ca->channels;
|
||||
deviceConfig.capture.format = ma_format_f32;
|
||||
deviceConfig.capture.shareMode = ma_share_mode_shared;
|
||||
}
|
||||
if (ca->deviceType & kCosmoAudioDeviceTypePlayback) {
|
||||
deviceConfig.playback.channels = ca->channels;
|
||||
deviceConfig.playback.format = ma_format_f32;
|
||||
}
|
||||
deviceConfig.dataCallback = data_callback;
|
||||
deviceConfig.pUserData = ca;
|
||||
result = ma_device_init(&ca->context, &deviceConfig, &ca->device);
|
||||
if (result != MA_SUCCESS) {
|
||||
ma_context_uninit(&ca->context);
|
||||
ma_event_uninit(&ca->event);
|
||||
ma_log_uninit(&ca->log);
|
||||
free(ca);
|
||||
return COSMOAUDIO_ERROR;
|
||||
}
|
||||
|
||||
// Initialize the speaker ring buffer.
|
||||
int period = ca->device.playback.internalPeriodSizeInFrames;
|
||||
if (!options->bufferFrames) {
|
||||
ca->outputBufferFrames = period * 10;
|
||||
} else if (options->bufferFrames < period * 2) {
|
||||
ca->outputBufferFrames = period * 2;
|
||||
} else {
|
||||
ca->outputBufferFrames = options->bufferFrames;
|
||||
}
|
||||
if (ca->deviceType & kCosmoAudioDeviceTypePlayback) {
|
||||
result = ma_pcm_rb_init(ma_format_f32, ca->channels, ca->outputBufferFrames,
|
||||
NULL, NULL, &ca->output);
|
||||
if (result != MA_SUCCESS) {
|
||||
ma_device_uninit(&ca->device);
|
||||
ma_context_uninit(&ca->context);
|
||||
ma_event_uninit(&ca->event);
|
||||
ma_log_uninit(&ca->log);
|
||||
free(ca);
|
||||
return COSMOAUDIO_ERROR;
|
||||
}
|
||||
ma_pcm_rb_set_sample_rate(&ca->output, ca->sampleRate);
|
||||
}
|
||||
|
||||
// Initialize the microphone ring buffer.
|
||||
period = ca->device.capture.internalPeriodSizeInFrames;
|
||||
if (!options->bufferFrames) {
|
||||
ca->inputBufferFrames = period * 10;
|
||||
} else if (options->bufferFrames < period * 2) {
|
||||
ca->inputBufferFrames = period * 2;
|
||||
} else {
|
||||
ca->inputBufferFrames = options->bufferFrames;
|
||||
}
|
||||
if (ca->deviceType & kCosmoAudioDeviceTypeCapture) {
|
||||
result = ma_pcm_rb_init(ma_format_f32, ca->channels, ca->inputBufferFrames,
|
||||
NULL, NULL, &ca->input);
|
||||
if (result != MA_SUCCESS) {
|
||||
ma_device_uninit(&ca->device);
|
||||
if (ca->deviceType & kCosmoAudioDeviceTypePlayback)
|
||||
ma_pcm_rb_uninit(&ca->output);
|
||||
ma_context_uninit(&ca->context);
|
||||
ma_event_uninit(&ca->event);
|
||||
ma_log_uninit(&ca->log);
|
||||
free(ca);
|
||||
return COSMOAUDIO_ERROR;
|
||||
}
|
||||
ma_pcm_rb_set_sample_rate(&ca->output, ca->sampleRate);
|
||||
}
|
||||
|
||||
// Start audio playback.
|
||||
if (ma_device_start(&ca->device) != MA_SUCCESS) {
|
||||
ma_device_uninit(&ca->device);
|
||||
if (ca->deviceType & kCosmoAudioDeviceTypePlayback)
|
||||
ma_pcm_rb_uninit(&ca->output);
|
||||
if (ca->deviceType & kCosmoAudioDeviceTypeCapture)
|
||||
ma_pcm_rb_uninit(&ca->input);
|
||||
ma_context_uninit(&ca->context);
|
||||
ma_event_uninit(&ca->event);
|
||||
ma_log_uninit(&ca->log);
|
||||
free(ca);
|
||||
return COSMOAUDIO_ERROR;
|
||||
}
|
||||
|
||||
*out_ca = ca;
|
||||
return COSMOAUDIO_SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes audio device and frees all associated resources.
|
||||
*
|
||||
* This function is non-blocking and will drop buffered audio. In
|
||||
* playback mode, you need to call cosmoaudio_flush() to ensure data
|
||||
* supplied by cosmoaudio_write() gets played on your speaker.
|
||||
*
|
||||
* Calling this function twice on the same object will result in
|
||||
* undefined behavior. Even if this function fails, the `ca` will be
|
||||
* freed to the greatest extent possible.
|
||||
*
|
||||
* @param ca is CosmoAudio object returned earlier by cosmoaudio_open()
|
||||
* @return 0 on success, or negative error code on failure
|
||||
*/
|
||||
COSMOAUDIO_ABI int cosmoaudio_close(struct CosmoAudio* ca) {
|
||||
if (!ca)
|
||||
return COSMOAUDIO_EINVAL;
|
||||
ma_device_uninit(&ca->device); // do this first
|
||||
if (ca->deviceType & kCosmoAudioDeviceTypePlayback)
|
||||
ma_pcm_rb_uninit(&ca->output);
|
||||
if (ca->deviceType & kCosmoAudioDeviceTypeCapture)
|
||||
ma_pcm_rb_uninit(&ca->input);
|
||||
ma_context_uninit(&ca->context);
|
||||
ma_event_uninit(&ca->event);
|
||||
ma_log_uninit(&ca->log);
|
||||
free(ca);
|
||||
return COSMOAUDIO_SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes raw audio data to speaker.
|
||||
*
|
||||
* The data is written to a ring buffer in real-time, which is then
|
||||
* played back very soon on the audio device. This has tolerence for
|
||||
* a certain amount of buffering, but expects that this function is
|
||||
* repeatedly called at a regular time interval. The caller should
|
||||
* have its own sleep loop for this purpose.
|
||||
*
|
||||
* This function never blocks. Programs that don't have their own timer
|
||||
* can use cosmoaudio_poll() to wait until audio may be written.
|
||||
*
|
||||
* For any given CosmoAudio object, it's assumed that only a single
|
||||
* thread will call this function.
|
||||
*
|
||||
* @param ca is CosmoAudio object returned earlier by cosmoaudio_open()
|
||||
* @param data is pointer to raw audio samples, expected to be in the range
|
||||
* -1.0 to 1.0, where channels are interleaved
|
||||
* @param frames is the number of frames (i.e. number of samples divided by
|
||||
* number of channels) from `data` to write to audio device
|
||||
* @return number of frames written, or negative error code on failure
|
||||
*/
|
||||
COSMOAUDIO_ABI int cosmoaudio_write(struct CosmoAudio* ca, const float* data,
|
||||
int frames) {
|
||||
if (!ca)
|
||||
return COSMOAUDIO_EINVAL;
|
||||
if (frames < 0)
|
||||
return COSMOAUDIO_EINVAL;
|
||||
if (!(ca->deviceType & kCosmoAudioDeviceTypePlayback))
|
||||
return COSMOAUDIO_EINVAL;
|
||||
if (1u + frames > ca->outputBufferFrames)
|
||||
return COSMOAUDIO_ENOBUF;
|
||||
if (!frames)
|
||||
return 0;
|
||||
if (!data)
|
||||
return COSMOAUDIO_EINVAL;
|
||||
return write_ring_buffer(&ca->log, &ca->output, data, frames, ca->channels);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads raw audio data from microphone.
|
||||
*
|
||||
* The data is read from a ring buffer in real-time, which is then
|
||||
* played back on the audio device. This has tolerence for a certain
|
||||
* amount of buffering (based on the `bufferFrames` parameter passed to
|
||||
* cosmoaudio_open(), which by default assumes this function will be
|
||||
* called at at a regular time interval.
|
||||
*
|
||||
* This function never blocks. Programs that don't have their own timer
|
||||
* can use cosmoaudio_poll() to wait until audio may be read.
|
||||
*
|
||||
* For any given CosmoAudio object, it's assumed that only a single
|
||||
* thread will call this function.
|
||||
*
|
||||
* @param ca is CosmoAudio object returned earlier by cosmoaudio_open()
|
||||
* @param data is pointer to raw audio samples, expected to be in the range
|
||||
* -1.0 to 1.0, where channels are interleaved
|
||||
* @param frames is the number of frames (i.e. number of samples divided by
|
||||
* number of channels) from `data` to read from microphone
|
||||
* @return number of frames read, or negative error code on failure
|
||||
*/
|
||||
COSMOAUDIO_ABI int cosmoaudio_read(struct CosmoAudio* ca, float* data,
|
||||
int frames) {
|
||||
if (!ca)
|
||||
return COSMOAUDIO_EINVAL;
|
||||
if (frames < 0)
|
||||
return COSMOAUDIO_EINVAL;
|
||||
if (!(ca->deviceType & kCosmoAudioDeviceTypeCapture))
|
||||
return COSMOAUDIO_EINVAL;
|
||||
if (!frames)
|
||||
return 0;
|
||||
if (!data)
|
||||
return COSMOAUDIO_EINVAL;
|
||||
return read_ring_buffer(&ca->log, &ca->input, data, frames, ca->channels);
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits until it's possible to read/write audio.
|
||||
*
|
||||
* This function is uninterruptible. All signals are masked throughout
|
||||
* the duration of time this function may block, including cancelation
|
||||
* signals, because this is not a cancelation point. Cosmopolitan Libc
|
||||
* applies this masking in its dlopen wrapper.
|
||||
*
|
||||
* @param ca is CosmoAudio object returned earlier by cosmoaudio_open()
|
||||
* @param in_out_readFrames if non-NULL specifies how many frames of
|
||||
* capture data be immediately readable by cosmoaudio_read() before
|
||||
* this can return; it must not exceed the buffer size; on return
|
||||
* this will be set to the actual number of frames in the buffer;
|
||||
* if the caller supplies a zero then this call is a non-blocking
|
||||
* way to query buffer sizes
|
||||
* @param in_out_writeFrames if non-NULL specifies how many frames of
|
||||
* capture data be immediately writable by cosmoaudio_write() before
|
||||
* this can return; it must not exceed the buffer size; on return
|
||||
* this will be set to the actual number of frames in the buffer;
|
||||
* if the caller supplies a zero then this call is a non-blocking
|
||||
* way to query buffer sizes
|
||||
* @return 0 on success, or negative error code on error
|
||||
*/
|
||||
COSMOAUDIO_ABI int cosmoaudio_poll(struct CosmoAudio* ca,
|
||||
int* in_out_readFrames,
|
||||
int* in_out_writeFrames) {
|
||||
if (!ca)
|
||||
return COSMOAUDIO_EINVAL;
|
||||
if (!in_out_readFrames && !in_out_writeFrames)
|
||||
return COSMOAUDIO_EINVAL;
|
||||
if (in_out_readFrames && !(ca->deviceType & kCosmoAudioDeviceTypeCapture))
|
||||
return COSMOAUDIO_EINVAL;
|
||||
if (in_out_writeFrames && !(ca->deviceType & kCosmoAudioDeviceTypePlayback))
|
||||
return COSMOAUDIO_EINVAL;
|
||||
if (in_out_readFrames && 1u + *in_out_readFrames > ca->inputBufferFrames)
|
||||
return COSMOAUDIO_ENOBUF;
|
||||
if (in_out_writeFrames && 1u + *in_out_writeFrames > ca->outputBufferFrames)
|
||||
return COSMOAUDIO_ENOBUF;
|
||||
for (;;) {
|
||||
int done = 1;
|
||||
ma_uint32 readable = 0;
|
||||
ma_uint32 writable = 0;
|
||||
if (in_out_readFrames) {
|
||||
readable = ma_pcm_rb_available_read(&ca->input);
|
||||
done &= readable >= (ma_uint32)*in_out_readFrames;
|
||||
}
|
||||
if (in_out_writeFrames) {
|
||||
writable = ma_pcm_rb_available_write(&ca->output);
|
||||
done &= writable >= (ma_uint32)*in_out_writeFrames;
|
||||
}
|
||||
if (done) {
|
||||
if (in_out_readFrames)
|
||||
*in_out_readFrames = readable;
|
||||
if (in_out_writeFrames)
|
||||
*in_out_writeFrames = writable;
|
||||
return COSMOAUDIO_SUCCESS;
|
||||
}
|
||||
if (ma_event_wait(&ca->event) != MA_SUCCESS)
|
||||
return COSMOAUDIO_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for written samples to be sent to device.
|
||||
*
|
||||
* This function is only valid to call in playback or duplex mode.
|
||||
*
|
||||
* This function is uninterruptible. All signals are masked throughout
|
||||
* the duration of time this function may block, including cancelation
|
||||
* signals, because this is not a cancelation point. Cosmopolitan Libc
|
||||
* applies this masking in its dlopen wrapper.
|
||||
*
|
||||
* @param ca is CosmoAudio object returned earlier by cosmoaudio_open()
|
||||
* @return 0 on success, or negative error code on failure
|
||||
*/
|
||||
COSMOAUDIO_ABI int cosmoaudio_flush(struct CosmoAudio* ca) {
|
||||
if (!ca)
|
||||
return COSMOAUDIO_EINVAL;
|
||||
if (!(ca->deviceType & kCosmoAudioDeviceTypePlayback))
|
||||
return COSMOAUDIO_EINVAL;
|
||||
for (;;) {
|
||||
if (!ma_pcm_rb_available_read(&ca->output))
|
||||
return COSMOAUDIO_SUCCESS;
|
||||
if (ma_event_wait(&ca->event) != MA_SUCCESS)
|
||||
return COSMOAUDIO_ERROR;
|
||||
}
|
||||
}
|
Binary file not shown.
|
@ -1,104 +0,0 @@
|
|||
#ifndef COSMOAUDIO_H_
|
||||
#define COSMOAUDIO_H_
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define COSMOAUDIO_ABI
|
||||
#ifdef COSMOAUDIO_BUILD
|
||||
#define COSMOAUDIO_API __declspec(dllexport)
|
||||
#else
|
||||
#define COSMOAUDIO_API __declspec(dllimport)
|
||||
#endif
|
||||
#else
|
||||
#define COSMOAUDIO_API
|
||||
#ifdef __x86_64__
|
||||
#define COSMOAUDIO_ABI __attribute__((__ms_abi__, __visibility__("default")))
|
||||
#else
|
||||
#define COSMOAUDIO_ABI __attribute__((__visibility__("default")))
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#define COSMOAUDIO_SUCCESS -0 // no error or nothing written
|
||||
#define COSMOAUDIO_ERROR -1 // unspecified error
|
||||
#define COSMOAUDIO_EINVAL -2 // invalid parameters passed to api
|
||||
#define COSMOAUDIO_ELINK -3 // loading cosmoaudio dso failed
|
||||
#define COSMOAUDIO_ENOBUF -4 // invalid buffering parameters
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct CosmoAudio;
|
||||
|
||||
enum CosmoAudioDeviceType {
|
||||
kCosmoAudioDeviceTypePlayback = 1,
|
||||
kCosmoAudioDeviceTypeCapture = 2,
|
||||
kCosmoAudioDeviceTypeDuplex =
|
||||
kCosmoAudioDeviceTypePlayback | kCosmoAudioDeviceTypeCapture,
|
||||
};
|
||||
|
||||
struct CosmoAudioOpenOptions {
|
||||
|
||||
// This field must be set to sizeof(struct CosmoAudioOpenOptions) or
|
||||
// cosmoaudio_open() will return COSMOAUDIO_EINVAL.
|
||||
int sizeofThis;
|
||||
|
||||
// Whether you want this object to open the speaker or microphone.
|
||||
// Please note that asking for microphone access may cause some OSes
|
||||
// like MacOS to show a popup asking the user for permission.
|
||||
enum CosmoAudioDeviceType deviceType;
|
||||
|
||||
// The sample rate can be 44100 for CD quality, 8000 for telephone
|
||||
// quality, etc. Values below 8000 are currently not supported.
|
||||
int sampleRate;
|
||||
|
||||
// The number of audio channels in each interleaved frame. Should be 1
|
||||
// for mono or 2 for stereo.
|
||||
int channels;
|
||||
|
||||
// Number of frames in each ring buffer. A frame consists of a PCM
|
||||
// sample for each channel. Set to 0 for default. If this is less than
|
||||
// the device period size times two, it'll be increased to that value.
|
||||
int bufferFrames;
|
||||
|
||||
// Enables debug logging if non-zero.
|
||||
int debugLog;
|
||||
};
|
||||
|
||||
COSMOAUDIO_API int cosmoaudio_version(void) COSMOAUDIO_ABI;
|
||||
|
||||
COSMOAUDIO_API int cosmoaudio_open( //
|
||||
struct CosmoAudio **out_ca, //
|
||||
const struct CosmoAudioOpenOptions *options //
|
||||
) COSMOAUDIO_ABI;
|
||||
|
||||
COSMOAUDIO_API int cosmoaudio_close( //
|
||||
struct CosmoAudio *ca //
|
||||
) COSMOAUDIO_ABI;
|
||||
|
||||
COSMOAUDIO_API int cosmoaudio_write( //
|
||||
struct CosmoAudio *ca, //
|
||||
const float *samples, //
|
||||
int frameCount //
|
||||
) COSMOAUDIO_ABI;
|
||||
|
||||
COSMOAUDIO_API int cosmoaudio_flush( //
|
||||
struct CosmoAudio *ca //
|
||||
) COSMOAUDIO_ABI;
|
||||
|
||||
COSMOAUDIO_API int cosmoaudio_read( //
|
||||
struct CosmoAudio *ca, //
|
||||
float *out_samples, //
|
||||
int frameCount //
|
||||
) COSMOAUDIO_ABI;
|
||||
|
||||
COSMOAUDIO_API int cosmoaudio_poll( //
|
||||
struct CosmoAudio *ca, //
|
||||
int *in_out_readFrames, //
|
||||
int *in_out_writeFrames //
|
||||
) COSMOAUDIO_ABI;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* COSMOAUDIO_H_ */
|
File diff suppressed because it is too large
Load diff
|
@ -1,76 +0,0 @@
|
|||
#if 0
|
||||
/*─────────────────────────────────────────────────────────────────╗
|
||||
│ To the extent possible under law, Justine Tunney has waived │
|
||||
│ all copyright and related or neighboring rights to this file, │
|
||||
│ as it is written in the following disclaimers: │
|
||||
│ • http://unlicense.org/ │
|
||||
│ • http://creativecommons.org/publicdomain/zero/1.0/ │
|
||||
╚─────────────────────────────────────────────────────────────────*/
|
||||
#endif
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
#include "cosmoaudio.h"
|
||||
|
||||
#define SAMPLING_RATE 44100
|
||||
#define WAVE_INTERVAL 440
|
||||
#define CHANNELS 2
|
||||
|
||||
#ifndef M_PIf
|
||||
#define M_PIf 3.14159265358979323846f
|
||||
#endif
|
||||
|
||||
int main() {
|
||||
|
||||
struct CosmoAudioOpenOptions cao = {0};
|
||||
cao.sizeofThis = sizeof(struct CosmoAudioOpenOptions);
|
||||
cao.deviceType = kCosmoAudioDeviceTypePlayback;
|
||||
cao.sampleRate = SAMPLING_RATE;
|
||||
cao.channels = CHANNELS;
|
||||
|
||||
int status;
|
||||
struct CosmoAudio *ca;
|
||||
status = cosmoaudio_open(&ca, &cao);
|
||||
if (status != COSMOAUDIO_SUCCESS) {
|
||||
fprintf(stderr, "failed to open audio: %d\n", status);
|
||||
return 1;
|
||||
}
|
||||
|
||||
float buf[256 * CHANNELS];
|
||||
for (int g = 0; g < SAMPLING_RATE;) {
|
||||
int frames = 1;
|
||||
status = cosmoaudio_poll(ca, NULL, &frames);
|
||||
if (status != COSMOAUDIO_SUCCESS) {
|
||||
fprintf(stderr, "failed to poll output: %d\n", status);
|
||||
return 2;
|
||||
}
|
||||
if (frames > 256)
|
||||
frames = 256;
|
||||
if (frames > SAMPLING_RATE - g)
|
||||
frames = SAMPLING_RATE - g;
|
||||
for (int f = 0; f < frames; ++f) {
|
||||
float t = (float)g++ / SAMPLING_RATE;
|
||||
float s = sinf(2 * M_PIf * WAVE_INTERVAL * t);
|
||||
for (int c = 0; c < CHANNELS; c++)
|
||||
buf[f * CHANNELS + c] = s * .3f;
|
||||
}
|
||||
status = cosmoaudio_write(ca, buf, frames);
|
||||
if (status != frames) {
|
||||
fprintf(stderr, "failed to write output: %d\n", status);
|
||||
return 3;
|
||||
}
|
||||
}
|
||||
|
||||
status = cosmoaudio_flush(ca);
|
||||
if (status != COSMOAUDIO_SUCCESS) {
|
||||
fprintf(stderr, "failed to flush output: %d\n", status);
|
||||
return 4;
|
||||
}
|
||||
|
||||
status = cosmoaudio_close(ca);
|
||||
if (status != COSMOAUDIO_SUCCESS) {
|
||||
fprintf(stderr, "failed to close audio: %d\n", status);
|
||||
return 5;
|
||||
}
|
||||
}
|
|
@ -1,121 +0,0 @@
|
|||
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
|
||||
│ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi │
|
||||
╞══════════════════════════════════════════════════════════════════════════════╡
|
||||
│ Copyright 2024 Justine Alexandra Roberts Tunney │
|
||||
│ │
|
||||
│ Permission to use, copy, modify, and/or distribute this software for │
|
||||
│ any purpose with or without fee is hereby granted, provided that the │
|
||||
│ above copyright notice and this permission notice appear in all copies. │
|
||||
│ │
|
||||
│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │
|
||||
│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │
|
||||
│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │
|
||||
│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │
|
||||
│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │
|
||||
│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │
|
||||
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
|
||||
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "dsp/audio/describe.h"
|
||||
#include "dsp/audio/cosmoaudio/cosmoaudio.h"
|
||||
#include "libc/intrin/describeflags.h"
|
||||
#include "libc/intrin/kprintf.h"
|
||||
#include "libc/macros.h"
|
||||
|
||||
#define append(...) o += ksnprintf(buf + o, n - o, __VA_ARGS__)
|
||||
|
||||
const char *cosmoaudio_describe_status(char *buf, int n, int status) {
|
||||
switch (status) {
|
||||
case COSMOAUDIO_SUCCESS:
|
||||
return "COSMOAUDIO_SUCCESS";
|
||||
case COSMOAUDIO_ERROR:
|
||||
return "COSMOAUDIO_ERROR";
|
||||
case COSMOAUDIO_EINVAL:
|
||||
return "COSMOAUDIO_EINVAL";
|
||||
case COSMOAUDIO_ELINK:
|
||||
return "COSMOAUDIO_ELINK";
|
||||
case COSMOAUDIO_ENOBUF:
|
||||
return "COSMOAUDIO_ENOBUF";
|
||||
default:
|
||||
ksnprintf(buf, n, "%d", status);
|
||||
return buf;
|
||||
}
|
||||
}
|
||||
|
||||
const char *cosmoaudio_describe_open_options(
|
||||
char *buf, int n, const struct CosmoAudioOpenOptions *options) {
|
||||
int o = 0;
|
||||
char b128[128];
|
||||
bool gotsome = false;
|
||||
if (!options)
|
||||
return "NULL";
|
||||
if (kisdangerous(options)) {
|
||||
ksnprintf(buf, n, "%p", options);
|
||||
return buf;
|
||||
}
|
||||
append("{");
|
||||
|
||||
if (options->sampleRate) {
|
||||
if (gotsome)
|
||||
append(", ");
|
||||
append(".sampleRate=%d", options->sampleRate);
|
||||
gotsome = true;
|
||||
}
|
||||
|
||||
if (options->channels) {
|
||||
if (gotsome)
|
||||
append(", ");
|
||||
append(".channels=%d", options->channels);
|
||||
gotsome = true;
|
||||
}
|
||||
|
||||
if (options->deviceType) {
|
||||
if (gotsome)
|
||||
append(", ");
|
||||
static struct DescribeFlags kDeviceType[] = {
|
||||
{kCosmoAudioDeviceTypeDuplex, "Duplex"}, //
|
||||
{kCosmoAudioDeviceTypeCapture, "Capture"}, //
|
||||
{kCosmoAudioDeviceTypePlayback, "Playback"}, //
|
||||
};
|
||||
append(".deviceType=%s",
|
||||
_DescribeFlags(b128, 128, kDeviceType, ARRAYLEN(kDeviceType),
|
||||
"kCosmoAudioDeviceType", options->deviceType));
|
||||
gotsome = true;
|
||||
}
|
||||
|
||||
if (options->bufferFrames) {
|
||||
if (gotsome)
|
||||
append(", ");
|
||||
append(".bufferFrames=%d", options->bufferFrames);
|
||||
gotsome = true;
|
||||
}
|
||||
|
||||
if (options->debugLog) {
|
||||
if (gotsome)
|
||||
append(", ");
|
||||
append(".debugLog=%d", options->debugLog);
|
||||
gotsome = true;
|
||||
}
|
||||
|
||||
if (options->sizeofThis) {
|
||||
if (gotsome)
|
||||
append(", ");
|
||||
append(".sizeofThis=%d", options->sizeofThis);
|
||||
gotsome = true;
|
||||
}
|
||||
|
||||
append("}");
|
||||
return buf;
|
||||
}
|
||||
|
||||
const char *cosmoaudio_describe_poll_frames(char *buf, int n,
|
||||
int *in_out_frames) {
|
||||
if (!in_out_frames)
|
||||
return "NULL";
|
||||
if (kisdangerous(in_out_frames)) {
|
||||
ksnprintf(buf, n, "%p", in_out_frames);
|
||||
return buf;
|
||||
}
|
||||
ksnprintf(buf, n, "[%d]", *in_out_frames);
|
||||
return buf;
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
#ifndef COSMOPOLITAN_DSP_AUDIO_DESCRIBE_H_
|
||||
#define COSMOPOLITAN_DSP_AUDIO_DESCRIBE_H_
|
||||
#include "dsp/audio/cosmoaudio/cosmoaudio.h"
|
||||
COSMOPOLITAN_C_START_
|
||||
|
||||
const char *cosmoaudio_describe_status(char *, int, int);
|
||||
const char *cosmoaudio_describe_open_options(
|
||||
char *, int, const struct CosmoAudioOpenOptions *);
|
||||
const char *cosmoaudio_describe_poll_frames(char *, int, int *);
|
||||
|
||||
COSMOPOLITAN_C_END_
|
||||
#endif /* COSMOPOLITAN_DSP_AUDIO_DESCRIBE_H_ */
|
|
@ -1,6 +1,6 @@
|
|||
#ifndef COSMOPOLITAN_DSP_CORE_C161_H_
|
||||
#define COSMOPOLITAN_DSP_CORE_C161_H_
|
||||
#include "libc/macros.h"
|
||||
#include "libc/macros.internal.h"
|
||||
|
||||
#define EXTRA_SHARP 2
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#ifndef COSMOPOLITAN_DSP_CORE_C161S_H_
|
||||
#define COSMOPOLITAN_DSP_CORE_C161S_H_
|
||||
#include "dsp/core/c161.h"
|
||||
#include "libc/macros.h"
|
||||
#include "libc/macros.internal.h"
|
||||
|
||||
__funline signed char C161S(signed char al, signed char bl, signed char cl) {
|
||||
short ax, bx, cx;
|
||||
|
|
|
@ -9,13 +9,14 @@ int mulaw(int);
|
|||
int unmulaw(int);
|
||||
void *double2byte(long, const void *, double, double) vallocesque;
|
||||
void *byte2double(long, const void *, double, double) vallocesque;
|
||||
void *dct(float[hasatleast 8][8], unsigned, float, float, float, float, float);
|
||||
void *dctjpeg(float[hasatleast 8][8], unsigned);
|
||||
void *dct(float[restrict hasatleast 8][8], unsigned, float, float, float, float,
|
||||
float);
|
||||
void *dctjpeg(float[restrict hasatleast 8][8], unsigned);
|
||||
double det3(const double[3][3]) nosideeffect;
|
||||
void *inv3(double[3][3], const double[3][3], double);
|
||||
void *matmul3(double[3][3], const double[3][3], const double[3][3]);
|
||||
void *vmatmul3(double[3], const double[3], const double[3][3]);
|
||||
void *matvmul3(double[3], const double[3][3], const double[3]);
|
||||
void *inv3(double[restrict 3][3], const double[restrict 3][3], double);
|
||||
void *matmul3(double[restrict 3][3], const double[3][3], const double[3][3]);
|
||||
void *vmatmul3(double[restrict 3], const double[3], const double[3][3]);
|
||||
void *matvmul3(double[restrict 3], const double[3][3], const double[3]);
|
||||
double rgb2stdtv(double) pureconst;
|
||||
double rgb2lintv(double) pureconst;
|
||||
double rgb2stdpc(double, double) pureconst;
|
||||
|
|
|
@ -65,8 +65,8 @@
|
|||
*
|
||||
* @cost ~100ns
|
||||
*/
|
||||
void *dct(float M[hasatleast 8][8], unsigned stride, float c0, float c1,
|
||||
float c2, float c3, float c4) {
|
||||
void *dct(float M[restrict hasatleast 8][8], unsigned stride, float c0,
|
||||
float c1, float c2, float c3, float c4) {
|
||||
unsigned y, x;
|
||||
for (y = 0; y < stride * 8; y += stride) {
|
||||
DCT(M[y][0], M[y][1], M[y][2], M[y][3], M[y][4], M[y][5], M[y][6], M[y][7],
|
||||
|
@ -79,7 +79,7 @@ void *dct(float M[hasatleast 8][8], unsigned stride, float c0, float c1,
|
|||
return M;
|
||||
}
|
||||
|
||||
void *dctjpeg(float M[hasatleast 8][8], unsigned stride) {
|
||||
void *dctjpeg(float M[restrict hasatleast 8][8], unsigned stride) {
|
||||
return dct(M, stride, .707106781f, .382683433f, .541196100f, 1.306562965f,
|
||||
.707106781f);
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "dsp/core/core.h"
|
||||
#include "libc/macros.h"
|
||||
#include "libc/macros.internal.h"
|
||||
#include "libc/math.h"
|
||||
#include "libc/mem/mem.h"
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/limits.h"
|
||||
#include "libc/macros.h"
|
||||
#include "libc/macros.internal.h"
|
||||
#include "libc/math.h"
|
||||
#include "libc/str/str.h"
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
#include "libc/assert.h"
|
||||
#include "libc/dce.h"
|
||||
#include "libc/limits.h"
|
||||
#include "libc/macros.h"
|
||||
#include "libc/macros.internal.h"
|
||||
#include "libc/math.h"
|
||||
#include "libc/str/str.h"
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
#include "dsp/core/core.h"
|
||||
#include "dsp/core/q.h"
|
||||
#include "libc/dce.h"
|
||||
#include "libc/macros.h"
|
||||
#include "libc/macros.internal.h"
|
||||
#include "libc/math.h"
|
||||
#include "libc/str/str.h"
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#ifndef COSMOPOLITAN_DSP_CORE_HALF_H_
|
||||
#define COSMOPOLITAN_DSP_CORE_HALF_H_
|
||||
#include "libc/macros.h"
|
||||
#include "libc/macros.internal.h"
|
||||
|
||||
/**
|
||||
* Divides integer in half w/ rounding.
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
* @define 𝐀⁻¹=𝐁 such that 𝐀×𝐁=𝐁×𝐀=𝐈ₙ
|
||||
* @see det3()
|
||||
*/
|
||||
void *inv3(double B[3][3], const double A[3][3], double d) {
|
||||
void *inv3(double B[restrict 3][3], const double A[restrict 3][3], double d) {
|
||||
d = d ? 1 / d : NAN;
|
||||
B[0][0] = (A[1][1] * A[2][2] - A[2][1] * A[1][2]) * d;
|
||||
B[0][1] = (A[2][1] * A[0][2] - A[0][1] * A[2][2]) * d;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#ifndef COSMOPOLITAN_DSP_CORE_KS8_H_
|
||||
#define COSMOPOLITAN_DSP_CORE_KS8_H_
|
||||
#include "libc/macros.h"
|
||||
#include "libc/macros.internal.h"
|
||||
|
||||
/**
|
||||
* Performs 16-bit scaled rounded madd w/ eight coefficients or fewer.
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#ifndef COSMOPOLITAN_DSP_CORE_KSS8_H_
|
||||
#define COSMOPOLITAN_DSP_CORE_KSS8_H_
|
||||
#include "libc/limits.h"
|
||||
#include "libc/macros.h"
|
||||
#include "libc/macros.internal.h"
|
||||
|
||||
/**
|
||||
* Performs 16-bit scaled rounded saturated madd w/ eight coefficients or fewer.
|
||||
|
|
|
@ -22,7 +22,8 @@
|
|||
/**
|
||||
* Multiplies 3×3 matrices.
|
||||
*/
|
||||
void *matmul3(double R[3][3], const double A[3][3], const double B[3][3]) {
|
||||
void *matmul3(double R[restrict 3][3], const double A[3][3],
|
||||
const double B[3][3]) {
|
||||
int i, j, k;
|
||||
memset(R, 0, sizeof(double) * 3 * 3);
|
||||
for (i = 0; i < 3; ++i) {
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
*
|
||||
* @see vmatmul3() for noncommutative corollary
|
||||
*/
|
||||
void *matvmul3(double R[3], const double M[3][3], const double V[3]) {
|
||||
void *matvmul3(double R[restrict 3], const double M[3][3], const double V[3]) {
|
||||
R[0] = V[0] * M[0][0] + V[1] * M[0][1] + V[2] * M[0][2];
|
||||
R[1] = V[0] * M[1][0] + V[1] * M[1][1] + V[2] * M[1][2];
|
||||
R[2] = V[0] * M[2][0] + V[1] * M[2][1] + V[2] * M[2][2];
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#ifndef COSMOPOLITAN_DSP_CORE_Q_H_
|
||||
#define COSMOPOLITAN_DSP_CORE_Q_H_
|
||||
#include "libc/limits.h"
|
||||
#include "libc/macros.h"
|
||||
#include "libc/macros.internal.h"
|
||||
#include "libc/math.h"
|
||||
|
||||
/**
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "dsp/core/core.h"
|
||||
#include "libc/limits.h"
|
||||
#include "libc/macros.h"
|
||||
#include "libc/macros.internal.h"
|
||||
#include "third_party/aarch64/arm_neon.internal.h"
|
||||
#include "third_party/intel/emmintrin.internal.h"
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "dsp/core/core.h"
|
||||
#include "libc/intrin/safemacros.h"
|
||||
#include "libc/intrin/safemacros.internal.h"
|
||||
#include "libc/limits.h"
|
||||
|
||||
/**
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
*
|
||||
* @see matvmul3() for noncommutative corollary
|
||||
*/
|
||||
void *vmatmul3(double R[3], const double V[3], const double M[3][3]) {
|
||||
void *vmatmul3(double R[restrict 3], const double V[3], const double M[3][3]) {
|
||||
R[0] = V[0] * M[0][0] + V[1] * M[1][0] + V[2] * M[2][0];
|
||||
R[1] = V[0] * M[0][1] + V[1] * M[1][1] + V[2] * M[2][1];
|
||||
R[2] = V[0] * M[0][2] + V[1] * M[1][2] + V[2] * M[2][2];
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
DisableFormat: true
|
||||
SortIncludes: Never
|
|
@ -25,13 +25,18 @@ DSP_MPEG_A_CHECKS = \
|
|||
|
||||
DSP_MPEG_A_DIRECTDEPS = \
|
||||
LIBC_CALLS \
|
||||
LIBC_FMT \
|
||||
LIBC_INTRIN \
|
||||
LIBC_LOG \
|
||||
LIBC_LOG \
|
||||
LIBC_MEM \
|
||||
LIBC_NEXGEN32E \
|
||||
LIBC_RUNTIME \
|
||||
LIBC_STDIO \
|
||||
LIBC_STR \
|
||||
LIBC_SYSV \
|
||||
LIBC_TINYMATH \
|
||||
THIRD_PARTY_COMPILER_RT \
|
||||
THIRD_PARTY_COMPILER_RT
|
||||
|
||||
DSP_MPEG_A_DEPS := \
|
||||
$(call uniq,$(foreach x,$(DSP_MPEG_A_DIRECTDEPS),$($(x))))
|
||||
|
@ -44,10 +49,9 @@ $(DSP_MPEG_A).pkg: \
|
|||
$(DSP_MPEG_A_OBJS) \
|
||||
$(foreach x,$(DSP_MPEG_A_DIRECTDEPS),$($(x)_A).pkg)
|
||||
|
||||
o/$(MODE)/dsp/mpeg/pl_mpeg.o: private \
|
||||
o/$(MODE)/dsp/mpeg/clamp4int256-k8.o: private \
|
||||
CFLAGS += \
|
||||
-ffunction-sections \
|
||||
-fdata-sections
|
||||
-Os
|
||||
|
||||
DSP_MPEG_LIBS = $(foreach x,$(DSP_MPEG_ARTIFACTS),$($(x)))
|
||||
DSP_MPEG_SRCS = $(foreach x,$(DSP_MPEG_ARTIFACTS),$($(x)_SRCS))
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
DESCRIPTION
|
||||
|
||||
pl_mpeg lets you decode .mpg files
|
||||
|
||||
ORIGIN
|
||||
|
||||
https://github.com/phoboslab/pl_mpeg/
|
||||
9e40dd6536269d788728e32c39bfacf2ab7a0866
|
||||
|
||||
LICENSE
|
||||
|
||||
MIT
|
||||
|
||||
LOCAL CHANGES
|
||||
|
||||
- Added API for extracting pixel aspect ratio
|
||||
https://github.com/phoboslab/pl_mpeg/pull/42
|
|
@ -1,68 +0,0 @@
|
|||
# PL_MPEG - MPEG1 Video decoder, MP2 Audio decoder, MPEG-PS demuxer
|
||||
|
||||
Single-file MIT licensed library for C/C++
|
||||
|
||||
See [pl_mpeg.h](https://github.com/phoboslab/pl_mpeg/blob/master/pl_mpeg.h) for
|
||||
the documentation.
|
||||
|
||||
|
||||
## Why?
|
||||
|
||||
This is meant as a simple way to get video playback into your app or game. Other
|
||||
solutions, such as ffmpeg require huge libraries and a lot of glue code.
|
||||
|
||||
MPEG1 is an old and inefficient codec, but it's still good enough for many use
|
||||
cases. All patents related to MPEG1 and MP2 have expired, so it's completely
|
||||
free now.
|
||||
|
||||
This library does not make use of any SIMD instructions, but because of
|
||||
the relative simplicity of the codec it still manages to decode 4k60fps video
|
||||
on a single CPU core (on my i7-6700k at least).
|
||||
|
||||
## Compilation on Linux
|
||||
|
||||
Use a GCC invocation like the following to build the example `pl_mpeg_player`
|
||||
program:
|
||||
|
||||
```shell
|
||||
gcc -o pl_mpeg_player pl_mpeg_player.c $(pkg-config --cflags --libs sdl2 glew)
|
||||
```
|
||||
|
||||
## Example Usage
|
||||
|
||||
- [pl_mpeg_extract_frames.c](https://github.com/phoboslab/pl_mpeg/blob/master/pl_mpeg_extract_frames.c)
|
||||
extracts all frames from a video and saves them as PNG.
|
||||
- [pl_mpeg_player.c](https://github.com/phoboslab/pl_mpeg/blob/master/pl_mpeg_player.c)
|
||||
implements a video player using SDL2 and OpenGL for rendering.
|
||||
|
||||
|
||||
|
||||
## Encoding for PL_MPEG
|
||||
|
||||
Most [MPEG-PS](https://en.wikipedia.org/wiki/MPEG_program_stream) (`.mpg`) files
|
||||
containing MPEG1 Video ("mpeg1") and MPEG1 Audio Layer II ("mp2") streams should
|
||||
work with PL_MPEG. Note that `.mpg` files can also contain MPEG2 Video, which is
|
||||
not supported by this library.
|
||||
|
||||
You can encode video in a suitable format using ffmpeg:
|
||||
|
||||
```
|
||||
ffmpeg -i input.mp4 -c:v mpeg1video -q:v 0 -c:a mp2 -format mpeg output.mpg
|
||||
```
|
||||
|
||||
`-q:v` sets a fixed video quality with a variable bitrate, where `0` is the
|
||||
highest. You may use `-b:v` to set a fixed bitrate instead; e.g.
|
||||
`-b:v 2000k` for 2000 kbit/s. Please refer to the
|
||||
[ffmpeg documentation](http://ffmpeg.org/ffmpeg.html#Options) for more details.
|
||||
|
||||
If you just want to quickly test the library, try this file:
|
||||
|
||||
https://phoboslab.org/files/bjork-all-is-full-of-love.mpg
|
||||
|
||||
|
||||
## Limitations
|
||||
|
||||
- no error reporting. PL_MPEG will silently ignore any invalid data.
|
||||
- the pts (presentation time stamp) for packets in the MPEG-PS container is
|
||||
ignored. This may cause sync issues with some files.
|
||||
- bugs, probably.
|
92
dsp/mpeg/README.txt
Normal file
92
dsp/mpeg/README.txt
Normal file
|
@ -0,0 +1,92 @@
|
|||
PL_MPEG - MPEG1 Video decoder, MP2 Audio decoder, MPEG-PS demuxer
|
||||
Dominic Szablewski - https://phoboslab.org
|
||||
|
||||
-- Synopsis
|
||||
|
||||
// This function gets called for each decoded video frame
|
||||
void my_video_callback(plm_t *plm, plm_frame_t *frame, void *user) {
|
||||
// Do something with frame->y.data, frame->cr.data, frame->cb.data
|
||||
}
|
||||
|
||||
// This function gets called for each decoded audio frame
|
||||
void my_audio_callback(plm_t *plm, plm_samples_t *frame, void *user) {
|
||||
// Do something with samples->interleaved
|
||||
}
|
||||
|
||||
// Load a .mpg (MPEG Program Stream) file
|
||||
plm_t *plm = plm_create_with_filename("some-file.mpg");
|
||||
|
||||
// Install the video & audio decode callbacks
|
||||
plm_set_video_decode_callback(plm, my_video_callback, my_data);
|
||||
plm_set_audio_decode_callback(plm, my_audio_callback, my_data);
|
||||
|
||||
|
||||
// Decode
|
||||
do {
|
||||
plm_decode(plm, time_since_last_call);
|
||||
} while (!plm_has_ended(plm));
|
||||
|
||||
// All done
|
||||
plm_destroy(plm);
|
||||
|
||||
|
||||
|
||||
-- Documentation
|
||||
|
||||
This library provides several interfaces to load, demux and decode MPEG video
|
||||
and audio data. A high-level API combines the demuxer, video & audio decoders
|
||||
in an easy to use wrapper.
|
||||
|
||||
Lower-level APIs for accessing the demuxer, video decoder and audio decoder,
|
||||
as well as providing different data sources are also available.
|
||||
|
||||
Interfaces are written in an object orientet style, meaning you create object
|
||||
instances via various different constructor functions (plm_*create()),
|
||||
do some work on them and later dispose them via plm_*destroy().
|
||||
|
||||
plm_* -- the high-level interface, combining demuxer and decoders
|
||||
plm_buffer_* -- the data source used by all interfaces
|
||||
plm_demux_* -- the MPEG-PS demuxer
|
||||
plm_video_* -- the MPEG1 Video ("mpeg1") decoder
|
||||
plm_audio_* -- the MPEG1 Audio Layer II ("mp2") decoder
|
||||
|
||||
|
||||
This library uses malloc(), realloc() and free() to manage memory. Typically
|
||||
all allocation happens up-front when creating the interface. However, the
|
||||
default buffer size may be too small for certain inputs. In these cases plmpeg
|
||||
will realloc() the buffer with a larger size whenever needed. You can configure
|
||||
the default buffer size by defining PLM_BUFFER_DEFAULT_SIZE *before*
|
||||
including this library.
|
||||
|
||||
With the high-level interface you have two options to decode video & audio:
|
||||
|
||||
1) Use plm_decode() and just hand over the delta time since the last call.
|
||||
It will decode everything needed and call your callbacks (specified through
|
||||
plm_set_{video|audio}_decode_callback()) any number of times.
|
||||
|
||||
2) Use plm_decode_video() and plm_decode_audio() to decode exactly one
|
||||
frame of video or audio data at a time. How you handle the synchronization of
|
||||
both streams is up to you.
|
||||
|
||||
If you only want to decode video *or* audio through these functions, you should
|
||||
disable the other stream (plm_set_{video|audio}_enabled(false))
|
||||
|
||||
|
||||
Video data is decoded into a struct with all 3 planes (Y, Cr, Cb) stored in
|
||||
separate buffers. You can either convert this to RGB on the CPU (slow) via the
|
||||
plm_frame_to_rgb() function or do it on the GPU with the following matrix:
|
||||
|
||||
mat4 rec601 = mat4(
|
||||
1.16438, 0.00000, 1.59603, -0.87079,
|
||||
1.16438, -0.39176, -0.81297, 0.52959,
|
||||
1.16438, 2.01723, 0.00000, -1.08139,
|
||||
0, 0, 0, 1
|
||||
);
|
||||
gl_FragColor = vec4(y, cb, cr, 1.0) * rec601;
|
||||
|
||||
Audio data is decoded into a struct with either one single float array with the
|
||||
samples for the left and right channel interleaved, or if the
|
||||
PLM_AUDIO_SEPARATE_CHANNELS is defined *before* including this library, into
|
||||
two separate float arrays - one for each channel.
|
||||
|
||||
See below for detailed the API documentation.
|
20
dsp/mpeg/blockset.h
Normal file
20
dsp/mpeg/blockset.h
Normal file
|
@ -0,0 +1,20 @@
|
|||
#ifndef COSMOPOLITAN_DSP_MPEG_BLOCKSET_H_
|
||||
#define COSMOPOLITAN_DSP_MPEG_BLOCKSET_H_
|
||||
|
||||
#define PLM_BLOCK_SET(DEST, DEST_INDEX, DEST_WIDTH, SOURCE_INDEX, \
|
||||
SOURCE_WIDTH, BLOCK_SIZE, OP) \
|
||||
do { \
|
||||
int dest_scan = DEST_WIDTH - BLOCK_SIZE; \
|
||||
int source_scan = SOURCE_WIDTH - BLOCK_SIZE; \
|
||||
for (int y = 0; y < BLOCK_SIZE; y++) { \
|
||||
for (int x = 0; x < BLOCK_SIZE; x++) { \
|
||||
DEST[DEST_INDEX] = OP; \
|
||||
SOURCE_INDEX++; \
|
||||
DEST_INDEX++; \
|
||||
} \
|
||||
SOURCE_INDEX += source_scan; \
|
||||
DEST_INDEX += dest_scan; \
|
||||
} \
|
||||
} while (false)
|
||||
|
||||
#endif /* COSMOPOLITAN_DSP_MPEG_BLOCKSET_H_ */
|
153
dsp/mpeg/buffer.c
Normal file
153
dsp/mpeg/buffer.c
Normal file
|
@ -0,0 +1,153 @@
|
|||
/*-*- mode:c;indent-tabs-mode:t;c-basic-offset:4;tab-width:4;coding:utf-8 -*-│
|
||||
│ vi: set noet ft=c ts=4 sw=4 fenc=utf-8 :vi │
|
||||
╞══════════════════════════════════════════════════════════════════════════════╡
|
||||
│ PL_MPEG - MPEG1 Video decoder, MP2 Audio decoder, MPEG-PS demuxer │
|
||||
│ Dominic Szablewski - https://phoboslab.org │
|
||||
│ │
|
||||
│ The MIT License(MIT) │
|
||||
│ Copyright(c) 2019 Dominic Szablewski │
|
||||
│ │
|
||||
│ Permission is hereby granted, free of charge, to any person obtaining │
|
||||
│ a copy of this software and associated documentation files(the │
|
||||
│ "Software"), to deal in the Software without restriction, including │
|
||||
│ without limitation the rights to use, copy, modify, merge, publish, │
|
||||
│ distribute, sublicense, and / or sell copies of the Software, and to │
|
||||
│ permit persons to whom the Software is furnished to do so, subject to │
|
||||
│ the following conditions: │
|
||||
│ │
|
||||
│ The above copyright notice and this permission notice shall be │
|
||||
│ included in all copies or substantial portions of the Software. │
|
||||
│ │
|
||||
│ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, │
|
||||
│ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF │
|
||||
│ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND │
|
||||
│ NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE │
|
||||
│ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN │
|
||||
│ ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN │
|
||||
│ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE │
|
||||
│ SOFTWARE. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "dsp/mpeg/buffer.h"
|
||||
#include "dsp/mpeg/mpeg.h"
|
||||
#include "libc/calls/calls.h"
|
||||
#include "libc/log/check.h"
|
||||
#include "libc/mem/mem.h"
|
||||
#include "libc/stdio/stdio.h"
|
||||
#include "libc/str/str.h"
|
||||
#include "libc/sysv/consts/madv.h"
|
||||
__static_yoink("pl_mpeg_notice");
|
||||
|
||||
/* clang-format off */
|
||||
// -----------------------------------------------------------------------------
|
||||
// plm_buffer implementation
|
||||
|
||||
plm_buffer_t *plm_buffer_create_with_filename(const char *filename) {
|
||||
FILE *fh = fopen(filename, "rb");
|
||||
if (!fh) {
|
||||
return NULL;
|
||||
}
|
||||
fadvise(fileno(fh), 0, 0, MADV_SEQUENTIAL);
|
||||
return plm_buffer_create_with_file(fh, true);
|
||||
}
|
||||
|
||||
plm_buffer_t *plm_buffer_create_with_file(FILE *fh, int close_when_done) {
|
||||
plm_buffer_t *b;
|
||||
b = plm_buffer_create_with_capacity(PLM_BUFFER_DEFAULT_SIZE);
|
||||
b->fh = fh;
|
||||
b->close_when_done = close_when_done;
|
||||
b->mode = PLM_BUFFER_MODE_FILE;
|
||||
plm_buffer_set_load_callback(b, plm_buffer_load_file_callback, NULL);
|
||||
return b;
|
||||
}
|
||||
|
||||
plm_buffer_t *plm_buffer_create_with_memory(unsigned char *bytes, size_t length, int free_when_done) {
|
||||
plm_buffer_t *b;
|
||||
b = memalign(_Alignof(plm_buffer_t), sizeof(plm_buffer_t));
|
||||
memset(b, 0, sizeof(plm_buffer_t));
|
||||
b->capacity = length;
|
||||
b->length = length;
|
||||
b->free_when_done = free_when_done;
|
||||
b->bytes = bytes;
|
||||
b->mode = PLM_BUFFER_MODE_FIXED_MEM;
|
||||
return b;
|
||||
}
|
||||
|
||||
plm_buffer_t * plm_buffer_create_with_capacity(size_t capacity) {
|
||||
plm_buffer_t *b;
|
||||
b = memalign(_Alignof(plm_buffer_t), sizeof(plm_buffer_t));
|
||||
memset(b, 0, sizeof(plm_buffer_t));
|
||||
b->capacity = capacity;
|
||||
b->free_when_done = true;
|
||||
b->bytes = (unsigned char *)malloc(capacity);
|
||||
b->mode = PLM_BUFFER_MODE_DYNAMIC_MEM;
|
||||
return b;
|
||||
}
|
||||
|
||||
void plm_buffer_destroy(plm_buffer_t *self) {
|
||||
if (self->fh && self->close_when_done) {
|
||||
fclose(self->fh);
|
||||
}
|
||||
if (self->free_when_done) {
|
||||
free(self->bytes);
|
||||
}
|
||||
free(self);
|
||||
}
|
||||
|
||||
size_t plm_buffer_write(plm_buffer_t *self, unsigned char *bytes, size_t length) {
|
||||
if (self->mode == PLM_BUFFER_MODE_FIXED_MEM) {
|
||||
return 0;
|
||||
}
|
||||
// This should be a ring buffer, but instead it just shifts all unread data
|
||||
// to the beginning of the buffer and appends new data at the end. Seems
|
||||
// to be good enough.
|
||||
plm_buffer_discard_read_bytes(self);
|
||||
// Do we have to resize to fit the new data?
|
||||
size_t bytes_available = self->capacity - self->length;
|
||||
if (bytes_available < length) {
|
||||
size_t new_size = self->capacity;
|
||||
do {
|
||||
new_size *= 2;
|
||||
} while (new_size - self->length < length);
|
||||
self->bytes = (unsigned char *)realloc(self->bytes, new_size);
|
||||
self->capacity = new_size;
|
||||
}
|
||||
memcpy(self->bytes + self->length, bytes, length);
|
||||
self->length += length;
|
||||
return length;
|
||||
}
|
||||
|
||||
void plm_buffer_set_load_callback(plm_buffer_t *self, plm_buffer_load_callback fp, void *user) {
|
||||
self->load_callback = fp;
|
||||
self->load_callback_user_data = user;
|
||||
}
|
||||
|
||||
void plm_buffer_rewind(plm_buffer_t *self) {
|
||||
if (self->fh) {
|
||||
fseek(self->fh, 0, SEEK_SET);
|
||||
self->length = 0;
|
||||
}
|
||||
if (self->mode != PLM_BUFFER_MODE_FIXED_MEM) {
|
||||
self->length = 0;
|
||||
}
|
||||
self->bit_index = 0;
|
||||
}
|
||||
|
||||
void plm_buffer_discard_read_bytes(plm_buffer_t *self) {
|
||||
size_t byte_pos = self->bit_index >> 3;
|
||||
if (byte_pos == self->length) {
|
||||
self->bit_index = 0;
|
||||
self->length = 0;
|
||||
}
|
||||
else if (byte_pos > 0) {
|
||||
memmove(self->bytes, self->bytes + byte_pos, self->length - byte_pos);
|
||||
self->bit_index -= byte_pos << 3;
|
||||
self->length -= byte_pos;
|
||||
}
|
||||
}
|
||||
|
||||
void plm_buffer_load_file_callback(plm_buffer_t *self, void *user) {
|
||||
plm_buffer_discard_read_bytes(self);
|
||||
unsigned bytes_available = self->capacity - self->length;
|
||||
unsigned bytes_read = fread(self->bytes + self->length, 1, bytes_available, self->fh);
|
||||
self->length += bytes_read;
|
||||
}
|
160
dsp/mpeg/buffer.h
Normal file
160
dsp/mpeg/buffer.h
Normal file
|
@ -0,0 +1,160 @@
|
|||
#ifndef COSMOPOLITAN_DSP_MPEG_BUFFER_H_
|
||||
#define COSMOPOLITAN_DSP_MPEG_BUFFER_H_
|
||||
#include "dsp/mpeg/mpeg.h"
|
||||
COSMOPOLITAN_C_START_
|
||||
|
||||
enum plm_buffer_mode {
|
||||
PLM_BUFFER_MODE_FILE,
|
||||
PLM_BUFFER_MODE_FIXED_MEM,
|
||||
PLM_BUFFER_MODE_DYNAMIC_MEM
|
||||
};
|
||||
|
||||
typedef struct plm_buffer_t {
|
||||
unsigned bit_index;
|
||||
unsigned capacity;
|
||||
unsigned length;
|
||||
int free_when_done;
|
||||
int close_when_done;
|
||||
FILE *fh;
|
||||
plm_buffer_load_callback load_callback;
|
||||
void *load_callback_user_data;
|
||||
unsigned char *bytes;
|
||||
enum plm_buffer_mode mode;
|
||||
} plm_buffer_t;
|
||||
|
||||
typedef struct {
|
||||
int16_t index;
|
||||
int16_t value;
|
||||
} plm_vlc_t;
|
||||
|
||||
typedef struct {
|
||||
int16_t index;
|
||||
uint16_t value;
|
||||
} plm_vlc_uint_t;
|
||||
|
||||
/* bool plm_buffer_has(plm_buffer_t *, size_t); */
|
||||
/* int plm_buffer_read(plm_buffer_t *, int); */
|
||||
/* void plm_buffer_align(plm_buffer_t *); */
|
||||
/* void plm_buffer_skip(plm_buffer_t *, size_t); */
|
||||
/* int plm_buffer_skip_bytes(plm_buffer_t *, unsigned char); */
|
||||
/* int plm_buffer_next_start_code(plm_buffer_t *); */
|
||||
/* int plm_buffer_find_start_code(plm_buffer_t *, int); */
|
||||
/* int plm_buffer_no_start_code(plm_buffer_t *); */
|
||||
/* int16_t plm_buffer_read_vlc(plm_buffer_t *, const plm_vlc_t *); */
|
||||
/* uint16_t plm_buffer_read_vlc_uint(plm_buffer_t *, const plm_vlc_uint_t *); */
|
||||
|
||||
void plm_buffer_discard_read_bytes(plm_buffer_t *);
|
||||
relegated void plm_buffer_load_file_callback(plm_buffer_t *, void *);
|
||||
|
||||
forceinline bool plm_buffer_has(plm_buffer_t *b, size_t bits) {
|
||||
unsigned have;
|
||||
have = b->length;
|
||||
have <<= 3;
|
||||
have -= b->bit_index;
|
||||
if (bits <= have) {
|
||||
return true;
|
||||
} else {
|
||||
if (b->load_callback) {
|
||||
b->load_callback(b, b->load_callback_user_data);
|
||||
return ((b->length << 3) - b->bit_index) >= bits;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
forceinline int plm_buffer_read(plm_buffer_t *self, int count) {
|
||||
if (!plm_buffer_has(self, count))
|
||||
return 0;
|
||||
int value = 0;
|
||||
while (count) {
|
||||
int current_byte = self->bytes[self->bit_index >> 3];
|
||||
int remaining = 8 - (self->bit_index & 7); // Remaining bits in byte
|
||||
int read = remaining < count ? remaining : count; // Bits in self run
|
||||
int shift = remaining - read;
|
||||
int mask = (0xff >> (8 - read));
|
||||
value = (value << read) | ((current_byte & (mask << shift)) >> shift);
|
||||
self->bit_index += read;
|
||||
count -= read;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
forceinline void plm_buffer_align(plm_buffer_t *self) {
|
||||
self->bit_index = ((self->bit_index + 7) >> 3) << 3;
|
||||
}
|
||||
|
||||
forceinline void plm_buffer_skip(plm_buffer_t *self, size_t count) {
|
||||
if (plm_buffer_has(self, count)) {
|
||||
self->bit_index += count;
|
||||
}
|
||||
}
|
||||
|
||||
forceinline int plm_buffer_skip_bytes(plm_buffer_t *self, unsigned char v) {
|
||||
unsigned skipped;
|
||||
plm_buffer_align(self);
|
||||
skipped = 0;
|
||||
while (plm_buffer_has(self, 8)) {
|
||||
if (v == self->bytes[self->bit_index >> 3]) {
|
||||
self->bit_index += 8;
|
||||
++skipped;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return skipped;
|
||||
}
|
||||
|
||||
forceinline int plm_buffer_next_start_code(plm_buffer_t *self) {
|
||||
plm_buffer_align(self);
|
||||
while (plm_buffer_has(self, (5 << 3))) {
|
||||
size_t byte_index = (self->bit_index) >> 3;
|
||||
if (self->bytes[byte_index] == 0x00 &&
|
||||
self->bytes[byte_index + 1] == 0x00 &&
|
||||
self->bytes[byte_index + 2] == 0x01) {
|
||||
self->bit_index = (byte_index + 4) << 3;
|
||||
return self->bytes[byte_index + 3];
|
||||
}
|
||||
self->bit_index += 8;
|
||||
}
|
||||
self->bit_index = (self->length << 3);
|
||||
return -1;
|
||||
}
|
||||
|
||||
forceinline int plm_buffer_find_start_code(plm_buffer_t *self, int code) {
|
||||
int current = 0;
|
||||
while (true) {
|
||||
current = plm_buffer_next_start_code(self);
|
||||
if (current == code || current == -1) {
|
||||
return current;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
forceinline int plm_buffer_no_start_code(plm_buffer_t *self) {
|
||||
if (!plm_buffer_has(self, (5 << 3))) {
|
||||
return false;
|
||||
}
|
||||
size_t byte_index = ((self->bit_index + 7) >> 3);
|
||||
return !(self->bytes[byte_index] == 0x00 &&
|
||||
self->bytes[byte_index + 1] == 0x00 &&
|
||||
self->bytes[byte_index + 2] == 0x01);
|
||||
}
|
||||
|
||||
forceinline int16_t plm_buffer_read_vlc(plm_buffer_t *self,
|
||||
const plm_vlc_t *table) {
|
||||
plm_vlc_t state = {0, 0};
|
||||
do {
|
||||
state = table[state.index + plm_buffer_read(self, 1)];
|
||||
} while (state.index > 0);
|
||||
return state.value;
|
||||
}
|
||||
|
||||
forceinline uint16_t plm_buffer_read_vlc_uint(plm_buffer_t *self,
|
||||
const plm_vlc_uint_t *table) {
|
||||
return (uint16_t)plm_buffer_read_vlc(self, (plm_vlc_t *)table);
|
||||
}
|
||||
|
||||
COSMOPOLITAN_C_END_
|
||||
#endif /* COSMOPOLITAN_DSP_MPEG_BUFFER_H_ */
|
30
dsp/mpeg/clamp4int256-core.S
Normal file
30
dsp/mpeg/clamp4int256-core.S
Normal file
|
@ -0,0 +1,30 @@
|
|||
/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│
|
||||
│ vi: set noet ft=asm ts=8 sw=8 fenc=utf-8 :vi │
|
||||
╞══════════════════════════════════════════════════════════════════════════════╡
|
||||
│ Copyright 2020 Justine Alexandra Roberts Tunney │
|
||||
│ │
|
||||
│ Permission to use, copy, modify, and/or distribute this software for │
|
||||
│ any purpose with or without fee is hereby granted, provided that the │
|
||||
│ above copyright notice and this permission notice appear in all copies. │
|
||||
│ │
|
||||
│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │
|
||||
│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │
|
||||
│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │
|
||||
│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │
|
||||
│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │
|
||||
│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │
|
||||
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
|
||||
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/macros.internal.h"
|
||||
|
||||
clamp4int256$core:
|
||||
.leafprologue
|
||||
pxor %xmm1,%xmm1
|
||||
pmaxsd %xmm1,%xmm0
|
||||
pminsd 0f(%rip),%xmm0
|
||||
.leafepilogue
|
||||
.endfn clamp4int256$core,globl
|
||||
|
||||
.rodata.cst16
|
||||
0: .long 255,255,255,255
|
203
dsp/mpeg/demux.c
Normal file
203
dsp/mpeg/demux.c
Normal file
|
@ -0,0 +1,203 @@
|
|||
/*-*- mode:c;indent-tabs-mode:t;c-basic-offset:4;tab-width:4;coding:utf-8 -*-│
|
||||
│ vi: set noet ft=c ts=4 sw=4 fenc=utf-8 :vi │
|
||||
╞══════════════════════════════════════════════════════════════════════════════╡
|
||||
│ PL_MPEG - MPEG1 Video decoder, MP2 Audio decoder, MPEG-PS demuxer │
|
||||
│ Dominic Szablewski - https://phoboslab.org │
|
||||
│ │
|
||||
│ The MIT License(MIT) │
|
||||
│ Copyright(c) 2019 Dominic Szablewski │
|
||||
│ │
|
||||
│ Permission is hereby granted, free of charge, to any person obtaining │
|
||||
│ a copy of this software and associated documentation files(the │
|
||||
│ "Software"), to deal in the Software without restriction, including │
|
||||
│ without limitation the rights to use, copy, modify, merge, publish, │
|
||||
│ distribute, sublicense, and / or sell copies of the Software, and to │
|
||||
│ permit persons to whom the Software is furnished to do so, subject to │
|
||||
│ the following conditions: │
|
||||
│ │
|
||||
│ The above copyright notice and this permission notice shall be │
|
||||
│ included in all copies or substantial portions of the Software. │
|
||||
│ │
|
||||
│ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, │
|
||||
│ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF │
|
||||
│ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND │
|
||||
│ NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE │
|
||||
│ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN │
|
||||
│ ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN │
|
||||
│ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE │
|
||||
│ SOFTWARE. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "dsp/mpeg/demux.h"
|
||||
#include "dsp/mpeg/buffer.h"
|
||||
#include "dsp/mpeg/mpeg.h"
|
||||
#include "libc/mem/mem.h"
|
||||
#include "libc/str/str.h"
|
||||
__static_yoink("pl_mpeg_notice");
|
||||
|
||||
/* clang-format off */
|
||||
// ----------------------------------------------------------------------------
|
||||
// plm_demux implementation
|
||||
|
||||
plm_demux_t *plm_demux_create(plm_buffer_t *buffer, int destroy_when_done) {
|
||||
plm_demux_t *self = (plm_demux_t *)malloc(sizeof(plm_demux_t));
|
||||
memset(self, 0, sizeof(plm_demux_t));
|
||||
|
||||
self->buffer = buffer;
|
||||
self->destroy_buffer_when_done = destroy_when_done;
|
||||
|
||||
if (plm_buffer_find_start_code(self->buffer, START_PACK) != -1) {
|
||||
plm_demux_decode_pack_header(self);
|
||||
}
|
||||
if (plm_buffer_find_start_code(self->buffer, START_SYSTEM) != -1) {
|
||||
plm_demux_decode_system_header(self);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
void plm_demux_destroy(plm_demux_t *self) {
|
||||
if (self->destroy_buffer_when_done) {
|
||||
plm_buffer_destroy(self->buffer);
|
||||
}
|
||||
free(self);
|
||||
}
|
||||
|
||||
int plm_demux_get_num_video_streams(plm_demux_t *self) {
|
||||
return self->num_video_streams;
|
||||
}
|
||||
|
||||
int plm_demux_get_num_audio_streams(plm_demux_t *self) {
|
||||
return self->num_audio_streams;
|
||||
}
|
||||
|
||||
void plm_demux_rewind(plm_demux_t *self) {
|
||||
plm_buffer_rewind(self->buffer);
|
||||
}
|
||||
|
||||
plm_packet_t *plm_demux_decode(plm_demux_t *self) {
|
||||
if (self->current_packet.length) {
|
||||
size_t bits_till_next_packet = self->current_packet.length << 3;
|
||||
if (!plm_buffer_has(self->buffer, bits_till_next_packet)) {
|
||||
return NULL;
|
||||
}
|
||||
plm_buffer_skip(self->buffer, bits_till_next_packet);
|
||||
self->current_packet.length = 0;
|
||||
}
|
||||
|
||||
if (!self->has_pack_header) {
|
||||
if (plm_buffer_find_start_code(self->buffer, START_PACK) != -1) {
|
||||
plm_demux_decode_pack_header(self);
|
||||
}
|
||||
else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (!self->has_system_header) {
|
||||
if (plm_buffer_find_start_code(self->buffer, START_SYSTEM) != -1) {
|
||||
plm_demux_decode_system_header(self);
|
||||
}
|
||||
else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// pending packet just waiting for data?
|
||||
if (self->next_packet.length) {
|
||||
return plm_demux_get_packet(self);
|
||||
}
|
||||
|
||||
int code;
|
||||
do {
|
||||
code = plm_buffer_next_start_code(self->buffer);
|
||||
if (
|
||||
code == PLM_DEMUX_PACKET_VIDEO_1 ||
|
||||
code == PLM_DEMUX_PACKET_PRIVATE ||
|
||||
(code >= PLM_DEMUX_PACKET_AUDIO_1 && code <= PLM_DEMUX_PACKET_AUDIO_4)
|
||||
) {
|
||||
return plm_demux_decode_packet(self, code);
|
||||
}
|
||||
} while (code != -1);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
double plm_demux_read_time(plm_demux_t *self) {
|
||||
int64_t clock = plm_buffer_read(self->buffer, 3) << 30;
|
||||
plm_buffer_skip(self->buffer, 1);
|
||||
clock |= plm_buffer_read(self->buffer, 15) << 15;
|
||||
plm_buffer_skip(self->buffer, 1);
|
||||
clock |= plm_buffer_read(self->buffer, 15);
|
||||
plm_buffer_skip(self->buffer, 1);
|
||||
return (double)clock / 90000.0;
|
||||
}
|
||||
|
||||
void plm_demux_decode_pack_header(plm_demux_t *self) {
|
||||
if (plm_buffer_read(self->buffer, 4) != 0x02) {
|
||||
return; // invalid
|
||||
}
|
||||
self->system_clock_ref = plm_demux_read_time(self);
|
||||
plm_buffer_skip(self->buffer, 1);
|
||||
plm_buffer_skip(self->buffer, 22); // mux_rate * 50
|
||||
plm_buffer_skip(self->buffer, 1);
|
||||
|
||||
self->has_pack_header = true;
|
||||
}
|
||||
|
||||
void plm_demux_decode_system_header(plm_demux_t *self) {
|
||||
plm_buffer_skip(self->buffer, 16); // header_length
|
||||
plm_buffer_skip(self->buffer, 24); // rate bound
|
||||
self->num_audio_streams = plm_buffer_read(self->buffer, 6);
|
||||
plm_buffer_skip(self->buffer, 5); // misc flags
|
||||
self->num_video_streams = plm_buffer_read(self->buffer, 5);
|
||||
|
||||
self->has_system_header = true;
|
||||
}
|
||||
|
||||
plm_packet_t *plm_demux_decode_packet(plm_demux_t *self, int start_code) {
|
||||
if (!plm_buffer_has(self->buffer, 8 << 3)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
self->next_packet.type = start_code;
|
||||
self->next_packet.length = plm_buffer_read(self->buffer, 16);
|
||||
self->next_packet.length -= plm_buffer_skip_bytes(self->buffer, 0xff); // stuffing
|
||||
|
||||
// skip P-STD
|
||||
if (plm_buffer_read(self->buffer, 2) == 0x01) {
|
||||
plm_buffer_skip(self->buffer, 16);
|
||||
self->next_packet.length -= 2;
|
||||
}
|
||||
|
||||
int pts_dts_marker = plm_buffer_read(self->buffer, 2);
|
||||
if (pts_dts_marker == 0x03) {
|
||||
self->next_packet.pts = plm_demux_read_time(self);
|
||||
plm_buffer_skip(self->buffer, 40); // skip dts
|
||||
self->next_packet.length -= 10;
|
||||
}
|
||||
else if (pts_dts_marker == 0x02) {
|
||||
self->next_packet.pts = plm_demux_read_time(self);
|
||||
self->next_packet.length -= 5;
|
||||
}
|
||||
else if (pts_dts_marker == 0x00) {
|
||||
self->next_packet.pts = 0;
|
||||
plm_buffer_skip(self->buffer, 4);
|
||||
self->next_packet.length -= 1;
|
||||
}
|
||||
else {
|
||||
return NULL; // invalid
|
||||
}
|
||||
|
||||
return plm_demux_get_packet(self);
|
||||
}
|
||||
|
||||
plm_packet_t *plm_demux_get_packet(plm_demux_t *self) {
|
||||
if (!plm_buffer_has(self->buffer, self->next_packet.length << 3)) {
|
||||
return NULL;
|
||||
}
|
||||
self->current_packet.data = self->buffer->bytes + (self->buffer->bit_index >> 3);
|
||||
self->current_packet.length = self->next_packet.length;
|
||||
self->current_packet.type = self->next_packet.type;
|
||||
self->current_packet.pts = self->next_packet.pts;
|
||||
self->next_packet.length = 0;
|
||||
return &self->current_packet;
|
||||
}
|
29
dsp/mpeg/demux.h
Normal file
29
dsp/mpeg/demux.h
Normal file
|
@ -0,0 +1,29 @@
|
|||
#ifndef COSMOPOLITAN_DSP_MPEG_DEMUX_H_
|
||||
#define COSMOPOLITAN_DSP_MPEG_DEMUX_H_
|
||||
#include "dsp/mpeg/mpeg.h"
|
||||
COSMOPOLITAN_C_START_
|
||||
|
||||
#define START_PACK 0xBA
|
||||
#define START_END 0xB9
|
||||
#define START_SYSTEM 0xBB
|
||||
|
||||
typedef struct plm_demux_t {
|
||||
plm_buffer_t *buffer;
|
||||
int destroy_buffer_when_done;
|
||||
double system_clock_ref;
|
||||
int has_pack_header;
|
||||
int has_system_header;
|
||||
int num_audio_streams;
|
||||
int num_video_streams;
|
||||
plm_packet_t current_packet;
|
||||
plm_packet_t next_packet;
|
||||
} plm_demux_t;
|
||||
|
||||
double plm_demux_read_time(plm_demux_t *self);
|
||||
void plm_demux_decode_pack_header(plm_demux_t *self);
|
||||
void plm_demux_decode_system_header(plm_demux_t *self);
|
||||
plm_packet_t *plm_demux_decode_packet(plm_demux_t *self, int start_code);
|
||||
plm_packet_t *plm_demux_get_packet(plm_demux_t *self);
|
||||
|
||||
COSMOPOLITAN_C_END_
|
||||
#endif /* COSMOPOLITAN_DSP_MPEG_DEMUX_H_ */
|
101
dsp/mpeg/idct.c
Normal file
101
dsp/mpeg/idct.c
Normal file
|
@ -0,0 +1,101 @@
|
|||
/*-*- mode:c;indent-tabs-mode:t;c-basic-offset:4;tab-width:4;coding:utf-8 -*-│
|
||||
│ vi: set et ft=c ts=4 sw=4 fenc=utf-8 :vi │
|
||||
╞══════════════════════════════════════════════════════════════════════════════╡
|
||||
│ PL_MPEG - MPEG1 Video decoder, MP2 Audio decoder, MPEG-PS demuxer │
|
||||
│ Dominic Szablewski - https://phoboslab.org │
|
||||
│ │
|
||||
│ The MIT License(MIT) │
|
||||
│ Copyright(c) 2019 Dominic Szablewski │
|
||||
│ │
|
||||
│ Permission is hereby granted, free of charge, to any person obtaining │
|
||||
│ a copy of this software and associated documentation files(the │
|
||||
│ "Software"), to deal in the Software without restriction, including │
|
||||
│ without limitation the rights to use, copy, modify, merge, publish, │
|
||||
│ distribute, sublicense, and / or sell copies of the Software, and to │
|
||||
│ permit persons to whom the Software is furnished to do so, subject to │
|
||||
│ the following conditions: │
|
||||
│ │
|
||||
│ The above copyright notice and this permission notice shall be │
|
||||
│ included in all copies or substantial portions of the Software. │
|
||||
│ │
|
||||
│ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, │
|
||||
│ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF │
|
||||
│ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND │
|
||||
│ NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE │
|
||||
│ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN │
|
||||
│ ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN │
|
||||
│ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE │
|
||||
│ SOFTWARE. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "dsp/core/half.h"
|
||||
__static_yoink("pl_mpeg_notice");
|
||||
|
||||
/**
|
||||
* Computes Fixed-Point 8x8 Inverse Discrete Cosine Transform.
|
||||
*
|
||||
* @note discovered by Nasir Ahmed
|
||||
*/
|
||||
void plm_video_idct(int block[8][8]) {
|
||||
int i, t1, t2, m0;
|
||||
int b1, b3, b4, b6, b7;
|
||||
int y3, y4, y5, y6, y7;
|
||||
int x0, x1, x2, x3, x4;
|
||||
|
||||
for (i = 0; i < 8; ++i) {
|
||||
b1 = block[4][i];
|
||||
b3 = block[2][i] + block[6][i];
|
||||
b4 = block[5][i] - block[3][i];
|
||||
t1 = block[1][i] + block[7][i];
|
||||
t2 = block[3][i] + block[5][i];
|
||||
b6 = block[1][i] - block[7][i];
|
||||
b7 = t1 + t2;
|
||||
m0 = block[0][i];
|
||||
x4 = ((b6 * 473 - b4 * 196 + 128) >> 8) - b7;
|
||||
x0 = x4 - (((t1 - t2) * 362 + 128) >> 8);
|
||||
x1 = m0 - b1;
|
||||
x2 = (((block[2][i] - block[6][i]) * 362 + 128) >> 8) - b3;
|
||||
x3 = m0 + b1;
|
||||
y3 = x1 + x2;
|
||||
y4 = x3 + b3;
|
||||
y5 = x1 - x2;
|
||||
y6 = x3 - b3;
|
||||
y7 = -x0 - ((b4 * 473 + b6 * 196 + 128) >> 8);
|
||||
block[0][i] = b7 + y4;
|
||||
block[1][i] = x4 + y3;
|
||||
block[2][i] = y5 - x0;
|
||||
block[3][i] = y6 - y7;
|
||||
block[4][i] = y6 + y7;
|
||||
block[5][i] = x0 + y5;
|
||||
block[6][i] = y3 - x4;
|
||||
block[7][i] = y4 - b7;
|
||||
}
|
||||
|
||||
for (i = 0; i < 8; ++i) {
|
||||
b1 = block[i][4];
|
||||
b3 = block[i][2] + block[i][6];
|
||||
b4 = block[i][5] - block[i][3];
|
||||
t1 = block[i][1] + block[i][7];
|
||||
t2 = block[i][3] + block[i][5];
|
||||
b6 = block[i][1] - block[i][7];
|
||||
b7 = t1 + t2;
|
||||
m0 = block[i][0];
|
||||
x4 = ((b6 * 473 - b4 * 196 + 128) >> 8) - b7;
|
||||
x0 = x4 - (((t1 - t2) * 362 + 128) >> 8);
|
||||
x1 = m0 - b1;
|
||||
x2 = (((block[i][2] - block[i][6]) * 362 + 128) >> 8) - b3;
|
||||
x3 = m0 + b1;
|
||||
y3 = x1 + x2;
|
||||
y4 = x3 + b3;
|
||||
y5 = x1 - x2;
|
||||
y6 = x3 - b3;
|
||||
y7 = -x0 - ((b4 * 473 + b6 * 196 + 128) >> 8);
|
||||
block[i][0] = (b7 + y4 + 128) >> 8;
|
||||
block[i][1] = (x4 + y3 + 128) >> 8;
|
||||
block[i][2] = (y5 - x0 + 128) >> 8;
|
||||
block[i][3] = (y6 - y7 + 128) >> 8;
|
||||
block[i][4] = (y6 + y7 + 128) >> 8;
|
||||
block[i][5] = (x0 + y5 + 128) >> 8;
|
||||
block[i][6] = (y3 - x4 + 128) >> 8;
|
||||
block[i][7] = (y4 - b7 + 128) >> 8;
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue