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
|
da8baf2aa5ce93b958aca90a0ae69f537806324b
|
||||||
# Run clang-format on most sources
|
# Run clang-format on most sources
|
||||||
369f9740de4534c28d0e81ab2afc99decbb9a3e6
|
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 -*-
|
# -*- conf -*-
|
||||||
*.gz binary
|
*.gz binary
|
||||||
*.so binary
|
/build/bootstrap/*.com binary
|
||||||
*.dll binary
|
|
||||||
*.dylib binary
|
|
||||||
/build/bootstrap/* binary
|
|
||||||
/usr/share/terminfo/* binary
|
|
||||||
/usr/share/terminfo/*/* binary
|
|
||||||
/usr/share/zoneinfo/* 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
|
name: build
|
||||||
|
|
||||||
env:
|
|
||||||
COSMOCC_VERSION: 3.9.2
|
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
|
@ -22,48 +19,13 @@ jobs:
|
||||||
matrix:
|
matrix:
|
||||||
mode: ["", tiny, rel, tinylinux, optlinux]
|
mode: ["", tiny, rel, tinylinux, optlinux]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v3
|
||||||
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
|
|
||||||
|
|
||||||
- name: support ape bins 1
|
- 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
|
- name: support ape bins 2
|
||||||
run: sudo sh -c "echo ':APE:M::MZqFpD::/usr/bin/ape:' >/proc/sys/fs/binfmt_misc/register"
|
run: sudo sh -c "echo ':APE:M::MZqFpD::/usr/bin/ape:' >/proc/sys/fs/binfmt_misc/register"
|
||||||
|
|
||||||
- name: make matrix
|
- name: make matrix
|
||||||
run: V=0 make -j2 MODE=${{ matrix.mode }}
|
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
|
/tool/emacs/*.elc
|
||||||
/perf.data
|
/perf.data
|
||||||
/perf.data.old
|
/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)
|
PWD := $(shell pwd)
|
||||||
|
|
||||||
# detect wsl2 running cosmopolitan binaries on the host by checking whether:
|
# detect wsl2 running cosmopolitan binaries on the host by checking whether:
|
||||||
# - user ran .cosmocc/current/bin/make, in which case make's working directory
|
# - user ran build/bootstrap/make, in which case make's working directory is in wsl
|
||||||
# is in wsl
|
|
||||||
# - user ran make, in which case cocmd'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)),)
|
ifneq ($(findstring //wsl.localhost/,$(CURDIR) $(PWD)),)
|
||||||
$(warning wsl2 interop is enabled)
|
$(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
|
# apple still distributes a 17 year old version of gnu make
|
||||||
ifeq ($(MAKE_VERSION), 3.81)
|
ifeq ($(MAKE_VERSION), 3.81)
|
||||||
$(error please use https://cosmo.zip/pub/cosmos/bin/make)
|
$(error please use build/bootstrap/make)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
LC_ALL = C
|
LC_ALL = C
|
||||||
|
@ -102,22 +101,23 @@ XARGS ?= xargs -P4 -rs8000
|
||||||
DOT ?= dot
|
DOT ?= dot
|
||||||
CLANG = clang
|
CLANG = clang
|
||||||
TMPDIR = o/tmp
|
TMPDIR = o/tmp
|
||||||
AR = $(BOOTSTRAP)/ar.ape
|
AR = build/bootstrap/ar
|
||||||
CP = $(BOOTSTRAP)/cp.ape
|
CP = build/bootstrap/cp
|
||||||
RM = $(BOOTSTRAP)/rm.ape -f
|
RM = build/bootstrap/rm -f
|
||||||
GZIP = $(BOOTSTRAP)/gzip.ape
|
GZIP = build/bootstrap/gzip
|
||||||
ECHO = $(BOOTSTRAP)/echo.ape
|
ECHO = build/bootstrap/echo
|
||||||
CHMOD = $(BOOTSTRAP)/chmod.ape
|
CHMOD = build/bootstrap/chmod
|
||||||
TOUCH = $(BOOTSTRAP)/touch.ape
|
TOUCH = build/bootstrap/touch
|
||||||
PKG = $(BOOTSTRAP)/package.ape
|
PKG = build/bootstrap/package
|
||||||
MKDEPS = $(BOOTSTRAP)/mkdeps
|
MKDEPS = build/bootstrap/mkdeps
|
||||||
ZIPOBJ = $(BOOTSTRAP)/zipobj
|
ZIPOBJ = build/bootstrap/zipobj
|
||||||
ZIPCOPY = $(BOOTSTRAP)/zipcopy
|
ZIPCOPY = build/bootstrap/zipcopy
|
||||||
PECHECK = $(BOOTSTRAP)/pecheck
|
PECHECK = build/bootstrap/pecheck
|
||||||
FIXUPOBJ = $(BOOTSTRAP)/fixupobj
|
FIXUPOBJ = build/bootstrap/fixupobj
|
||||||
OBJBINCOPY = $(BOOTSTRAP)/objbincopy
|
MKDIR = build/bootstrap/mkdir -p
|
||||||
MKDIR = $(BOOTSTRAP)/mkdir.ape -p
|
COMPILE = build/bootstrap/compile -V9 -M2048m -P8192 $(QUOTA)
|
||||||
COMPILE = $(BOOTSTRAP)/compile.ape -V9 -M2048m -P8192 $(QUOTA)
|
|
||||||
|
IGNORE := $(shell $(MKDIR) $(TMPDIR))
|
||||||
|
|
||||||
# the default build modes is empty string
|
# the default build modes is empty string
|
||||||
# on x86_64 hosts, MODE= is the same as MODE=x86_64
|
# on x86_64 hosts, MODE= is the same as MODE=x86_64
|
||||||
|
@ -133,13 +133,14 @@ endif
|
||||||
|
|
||||||
ifneq ($(findstring aarch64,$(MODE)),)
|
ifneq ($(findstring aarch64,$(MODE)),)
|
||||||
ARCH = aarch64
|
ARCH = aarch64
|
||||||
HOSTS ?= pi pi5 studio freebsdarm
|
HOSTS ?= pi studio freebsdarm
|
||||||
else
|
else
|
||||||
ARCH = x86_64
|
ARCH = x86_64
|
||||||
HOSTS ?= freebsd rhel7 xnu openbsd netbsd win10 luna
|
HOSTS ?= freebsd rhel7 xnu openbsd netbsd win10
|
||||||
endif
|
endif
|
||||||
|
|
||||||
ZIPOBJ_FLAGS += -a$(ARCH)
|
ZIPOBJ_FLAGS += -a$(ARCH)
|
||||||
|
IGNORE := $(shell $(MKDIR) $(TMPDIR))
|
||||||
|
|
||||||
export ADDR2LINE
|
export ADDR2LINE
|
||||||
export LC_ALL
|
export LC_ALL
|
||||||
|
@ -148,12 +149,9 @@ export MODE
|
||||||
export SOURCE_DATE_EPOCH
|
export SOURCE_DATE_EPOCH
|
||||||
export TMPDIR
|
export TMPDIR
|
||||||
|
|
||||||
COSMOCC = .cosmocc/3.9.2
|
COSMOCC = .cosmocc/3.3.5
|
||||||
BOOTSTRAP = $(COSMOCC)/bin
|
|
||||||
TOOLCHAIN = $(COSMOCC)/bin/$(ARCH)-linux-cosmo-
|
TOOLCHAIN = $(COSMOCC)/bin/$(ARCH)-linux-cosmo-
|
||||||
DOWNLOAD := $(shell build/download-cosmocc.sh $(COSMOCC) 3.9.2 f4ff13af65fcd309f3f1cfd04275996fb7f72a4897726628a8c9cf732e850193)
|
DOWNLOAD := $(shell build/download-cosmocc.sh $(COSMOCC) 3.3.5 db78fd8d3f8706e9dff4be72bf71d37a3f12062f212f407e1c33bc4af3780dd0)
|
||||||
|
|
||||||
IGNORE := $(shell $(MKDIR) $(TMPDIR))
|
|
||||||
|
|
||||||
AS = $(TOOLCHAIN)as
|
AS = $(TOOLCHAIN)as
|
||||||
CC = $(TOOLCHAIN)gcc
|
CC = $(TOOLCHAIN)gcc
|
||||||
|
@ -275,16 +273,10 @@ include libc/BUILD.mk #─┘
|
||||||
include libc/sock/BUILD.mk #─┐
|
include libc/sock/BUILD.mk #─┐
|
||||||
include net/http/BUILD.mk # ├──ONLINE RUNTIME
|
include net/http/BUILD.mk # ├──ONLINE RUNTIME
|
||||||
include third_party/musl/BUILD.mk # │ You can communicate with the network
|
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 libc/x/BUILD.mk # │
|
||||||
include dsp/scale/BUILD.mk # │
|
include dsp/scale/BUILD.mk # │
|
||||||
include dsp/mpeg/BUILD.mk # │
|
include dsp/mpeg/BUILD.mk # │
|
||||||
include dsp/tty/BUILD.mk # │
|
include dsp/tty/BUILD.mk # │
|
||||||
include dsp/audio/BUILD.mk # │
|
|
||||||
include dsp/prog/BUILD.mk # │
|
|
||||||
include dsp/BUILD.mk # │
|
include dsp/BUILD.mk # │
|
||||||
include third_party/stb/BUILD.mk # │
|
include third_party/stb/BUILD.mk # │
|
||||||
include third_party/mbedtls/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/openmp/BUILD.mk # │
|
||||||
include third_party/pcre/BUILD.mk # │
|
include third_party/pcre/BUILD.mk # │
|
||||||
include third_party/less/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/tidy/BUILD.mk
|
||||||
include third_party/BUILD.mk
|
include third_party/BUILD.mk
|
||||||
include third_party/nsync/testing/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/lua/BUILD.mk
|
||||||
include third_party/tree/BUILD.mk
|
include third_party/tree/BUILD.mk
|
||||||
include third_party/zstd/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/awk/BUILD.mk
|
||||||
include third_party/hiredis/BUILD.mk
|
include third_party/hiredis/BUILD.mk
|
||||||
include third_party/make/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/time/BUILD.mk
|
||||||
include test/libc/proc/BUILD.mk
|
include test/libc/proc/BUILD.mk
|
||||||
include test/libc/stdio/BUILD.mk
|
include test/libc/stdio/BUILD.mk
|
||||||
include test/libc/system/BUILD.mk
|
|
||||||
include test/libc/BUILD.mk
|
include test/libc/BUILD.mk
|
||||||
include test/net/http/BUILD.mk
|
include test/net/http/BUILD.mk
|
||||||
include test/net/https/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: private .UNSANDBOXED = 1
|
||||||
loc: o/$(MODE)/tool/build/summy
|
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}' | $<
|
$(XARGS) wc -l | grep total | awk '{print $$1}' | $<
|
||||||
|
|
||||||
COSMOPOLITAN = \
|
# PLEASE: MAINTAIN TOPOLOGICAL ORDER
|
||||||
CTL \
|
# FROM HIGHEST LEVEL TO LOWEST LEVEL
|
||||||
DSP_AUDIO \
|
COSMOPOLITAN_OBJECTS = \
|
||||||
LIBC_CALLS \
|
TOOL_ARGS \
|
||||||
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 \
|
|
||||||
NET_HTTP \
|
NET_HTTP \
|
||||||
THIRD_PARTY_COMPILER_RT \
|
LIBC_SOCK \
|
||||||
THIRD_PARTY_DLMALLOC \
|
LIBC_NT_WS2_32 \
|
||||||
THIRD_PARTY_DOUBLECONVERSION \
|
LIBC_NT_IPHLPAPI \
|
||||||
THIRD_PARTY_GDTOA \
|
LIBC_X \
|
||||||
THIRD_PARTY_GETOPT \
|
THIRD_PARTY_GETOPT \
|
||||||
|
LIBC_LOG \
|
||||||
|
THIRD_PARTY_TZ \
|
||||||
|
THIRD_PARTY_OPENMP \
|
||||||
|
THIRD_PARTY_MUSL \
|
||||||
|
THIRD_PARTY_ZLIB_GZ \
|
||||||
THIRD_PARTY_LIBCXXABI \
|
THIRD_PARTY_LIBCXXABI \
|
||||||
THIRD_PARTY_LIBUNWIND \
|
THIRD_PARTY_LIBUNWIND \
|
||||||
THIRD_PARTY_MUSL \
|
LIBC_STDIO \
|
||||||
THIRD_PARTY_NSYNC \
|
THIRD_PARTY_GDTOA \
|
||||||
THIRD_PARTY_NSYNC_MEM \
|
|
||||||
THIRD_PARTY_OPENMP \
|
|
||||||
THIRD_PARTY_PUFF \
|
|
||||||
THIRD_PARTY_REGEX \
|
THIRD_PARTY_REGEX \
|
||||||
THIRD_PARTY_TZ \
|
LIBC_THREAD \
|
||||||
THIRD_PARTY_XED \
|
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 \
|
||||||
THIRD_PARTY_ZLIB_GZ \
|
THIRD_PARTY_PUFF \
|
||||||
TOOL_ARGS \
|
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 = \
|
COSMOPOLITAN_H_PKGS = \
|
||||||
APE \
|
APE \
|
||||||
DSP_AUDIO \
|
|
||||||
LIBC \
|
LIBC \
|
||||||
LIBC_CALLS \
|
LIBC_CALLS \
|
||||||
LIBC_ELF \
|
LIBC_ELF \
|
||||||
|
@ -541,14 +532,14 @@ COSMOCC_PKGS = \
|
||||||
THIRD_PARTY_INTEL
|
THIRD_PARTY_INTEL
|
||||||
|
|
||||||
o/$(MODE)/cosmopolitan.a: \
|
o/$(MODE)/cosmopolitan.a: \
|
||||||
$(call reverse,$(call uniq,$(foreach x,$(COSMOPOLITAN),$($(x)))))
|
$(foreach x,$(COSMOPOLITAN_OBJECTS),$($(x)_A_OBJS))
|
||||||
|
|
||||||
COSMOCC_HDRS = \
|
COSMOCC_HDRS = \
|
||||||
$(wildcard libc/integral/*) \
|
$(wildcard libc/integral/*) \
|
||||||
$(foreach x,$(COSMOCC_PKGS),$($(x)_HDRS)) \
|
$(foreach x,$(COSMOCC_PKGS),$($(x)_HDRS)) \
|
||||||
$(foreach x,$(COSMOCC_PKGS),$($(x)_INCS))
|
$(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)))
|
$(file >$@, $(call uniq,$(COSMOCC_HDRS)))
|
||||||
|
|
||||||
COSMOPOLITAN_H_ROOT_HDRS = \
|
COSMOPOLITAN_H_ROOT_HDRS = \
|
||||||
|
|
33
README.md
33
README.md
|
@ -3,12 +3,12 @@
|
||||||
[](https://github.com/jart/cosmopolitan/actions/workflows/build.yml)
|
[](https://github.com/jart/cosmopolitan/actions/workflows/build.yml)
|
||||||
# Cosmopolitan
|
# 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
|
a build-once run-anywhere language, like Java, except it doesn't need an
|
||||||
interpreter or virtual machine. Instead, it reconfigures stock GCC and
|
interpreter or virtual machine. Instead, it reconfigures stock GCC and
|
||||||
Clang to output a POSIX-approved polyglot format that runs natively on
|
Clang to output a POSIX-approved polyglot format that runs natively on
|
||||||
Linux + Mac + Windows + FreeBSD + OpenBSD 7.3 + NetBSD + BIOS with the
|
Linux + Mac + Windows + FreeBSD + OpenBSD + NetBSD + BIOS with the best
|
||||||
best possible performance and the tiniest footprint imaginable.
|
possible performance and the tiniest footprint imaginable.
|
||||||
|
|
||||||
## Background
|
## Background
|
||||||
|
|
||||||
|
@ -87,22 +87,15 @@ ape/apeinstall.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
You can now build the mono repo with any modern version of GNU Make. To
|
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:
|
make life easier, we've included one in the cosmocc toolchain, which is
|
||||||
|
guaranteed to be compatible and furthermore includes our extensions for
|
||||||
https://cosmo.zip/pub/cosmos/bin/make
|
doing build system sandboxing.
|
||||||
|
|
||||||
E.g.:
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
curl -LO https://cosmo.zip/pub/cosmos/bin/make
|
build/bootstrap/make -j8
|
||||||
./make -j8
|
|
||||||
o//examples/hello
|
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
|
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
|
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
|
compiled relatively quickly, which is a simple POSIX test that only
|
||||||
|
@ -110,7 +103,7 @@ depends on core LIBC packages.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
rm -rf o//libc o//test
|
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
|
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:
|
all the unit tests in the `TEST_POSIX` package, you could say:
|
||||||
|
|
||||||
```sh
|
```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
|
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:
|
really tiny binaries (as small as 12kb in size) then you'd say:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
.cosmocc/current/bin/make m=tiny
|
build/bootstrap/make m=tiny
|
||||||
```
|
```
|
||||||
|
|
||||||
You can furthermore cut out the bloat of other operating systems, and
|
You can furthermore cut out the bloat of other operating systems, and
|
||||||
have Cosmopolitan become much more similar to Musl Libc.
|
have Cosmopolitan become much more similar to Musl Libc.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
.cosmocc/current/bin/make m=tinylinux
|
build/bootstrap/make m=tinylinux
|
||||||
```
|
```
|
||||||
|
|
||||||
For further details, see [//build/config.mk](build/config.mk).
|
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 |
|
| Platform | Min Version | Circa |
|
||||||
| :--- | ---: | ---: |
|
| :--- | ---: | ---: |
|
||||||
| AMD | K8 | 2003 |
|
| AMD | K8 Venus | 2005 |
|
||||||
| Intel | Core | 2006 |
|
| Intel | Core | 2006 |
|
||||||
| Linux | 2.6.18 | 2007 |
|
| Linux | 2.6.18 | 2007 |
|
||||||
| Windows | 8 [1] | 2012 |
|
| Windows | 8 [1] | 2012 |
|
||||||
| Darwin (macOS) | 23.1.0+ | 2023 |
|
| Darwin (macOS) | 23.1.0+ | 2023 |
|
||||||
| OpenBSD | 7.3 or earlier | 2023 |
|
| OpenBSD | 7 | 2021 |
|
||||||
| FreeBSD | 13 | 2020 |
|
| FreeBSD | 13 | 2020 |
|
||||||
| NetBSD | 9.2 | 2021 |
|
| 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: \
|
o/$(MODE)/ape/aarch64.lds: \
|
||||||
ape/aarch64.lds \
|
ape/aarch64.lds \
|
||||||
libc/zip.h \
|
libc/zip.internal.h \
|
||||||
libc/thread/tls.h \
|
libc/thread/tls.h \
|
||||||
libc/calls/struct/timespec.h \
|
libc/calls/struct/timespec.h \
|
||||||
libc/macros.h \
|
libc/macros.internal.h \
|
||||||
libc/str/str.h
|
libc/str/str.h
|
||||||
|
|
||||||
APE_LOADER_LDFLAGS = \
|
APE_LOADER_LDFLAGS = \
|
||||||
|
@ -162,8 +162,8 @@ o/$(MODE)/ape/ape-no-modify-self.o: \
|
||||||
libc/dce.h \
|
libc/dce.h \
|
||||||
libc/elf/def.h \
|
libc/elf/def.h \
|
||||||
libc/thread/tls.h \
|
libc/thread/tls.h \
|
||||||
libc/macho.h \
|
libc/macho.internal.h \
|
||||||
libc/macros.h \
|
libc/macros.internal.h \
|
||||||
libc/nexgen32e/uart.internal.h \
|
libc/nexgen32e/uart.internal.h \
|
||||||
libc/calls/metalfile.internal.h \
|
libc/calls/metalfile.internal.h \
|
||||||
libc/nt/pedef.internal.h \
|
libc/nt/pedef.internal.h \
|
||||||
|
@ -188,8 +188,8 @@ o/$(MODE)/ape/ape-copy-self.o: \
|
||||||
libc/dce.h \
|
libc/dce.h \
|
||||||
libc/elf/def.h \
|
libc/elf/def.h \
|
||||||
libc/thread/tls.h \
|
libc/thread/tls.h \
|
||||||
libc/macho.h \
|
libc/macho.internal.h \
|
||||||
libc/macros.h \
|
libc/macros.internal.h \
|
||||||
libc/nexgen32e/uart.internal.h \
|
libc/nexgen32e/uart.internal.h \
|
||||||
libc/calls/metalfile.internal.h \
|
libc/calls/metalfile.internal.h \
|
||||||
libc/nt/pedef.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)
|
@$(COMPILE) -AOBJECTIFY.c $(CLANG) -DSUPPORT_VECTOR=8 -S -g0 $(APE_LOADER_FLAGS)
|
||||||
|
|
||||||
o/$(MODE)/ape/ape.elf: o/$(MODE)/ape/ape.elf.dbg
|
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
|
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 = \
|
APE_LOADER_LDFLAGS = \
|
||||||
-static \
|
-static \
|
||||||
|
@ -246,6 +246,8 @@ o/$(MODE)/ape: $(APE_CHECKS) \
|
||||||
o/$(MODE)/ape/ape.lds \
|
o/$(MODE)/ape/ape.lds \
|
||||||
o/$(MODE)/ape/ape.elf \
|
o/$(MODE)/ape/ape.elf \
|
||||||
o/$(MODE)/ape/ape.macho \
|
o/$(MODE)/ape/ape.macho \
|
||||||
|
o/$(MODE)/ape/ape-copy-self.o \
|
||||||
|
o/$(MODE)/ape/ape-no-modify-self.o
|
||||||
|
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
@ -259,8 +261,8 @@ o/$(MODE)/ape/ape.o: \
|
||||||
libc/thread/tls.h \
|
libc/thread/tls.h \
|
||||||
ape/ape.internal.h \
|
ape/ape.internal.h \
|
||||||
ape/macros.internal.h \
|
ape/macros.internal.h \
|
||||||
libc/macho.h \
|
libc/macho.internal.h \
|
||||||
libc/macros.h \
|
libc/macros.internal.h \
|
||||||
libc/sysv/consts/prot.h \
|
libc/sysv/consts/prot.h \
|
||||||
libc/nt/pedef.internal.h \
|
libc/nt/pedef.internal.h \
|
||||||
libc/runtime/pc.internal.h \
|
libc/runtime/pc.internal.h \
|
||||||
|
@ -281,7 +283,7 @@ o/$(MODE)/ape/ape.lds: \
|
||||||
libc/dce.h \
|
libc/dce.h \
|
||||||
libc/elf/def.h \
|
libc/elf/def.h \
|
||||||
libc/elf/pf2prot.internal.h \
|
libc/elf/pf2prot.internal.h \
|
||||||
libc/macros.h \
|
libc/macros.internal.h \
|
||||||
libc/nt/pedef.internal.h \
|
libc/nt/pedef.internal.h \
|
||||||
libc/str/str.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_entry .eh_frame_entry.*)
|
||||||
}
|
}
|
||||||
|
|
||||||
__eh_frame_hdr_start = SIZEOF(.eh_frame_hdr) > 0 ? ADDR(.eh_frame_hdr) : 0;
|
.eh_frame : ONLY_IF_RO {
|
||||||
__eh_frame_hdr_end = SIZEOF(.eh_frame_hdr) > 0 ? . : 0;
|
KEEP(*(.eh_frame))
|
||||||
|
*(.eh_frame.*)
|
||||||
|
}
|
||||||
|
|
||||||
.gcc_except_table : ONLY_IF_RO {
|
.gcc_except_table : ONLY_IF_RO {
|
||||||
*(.gcc_except_table .gcc_except_table.*)
|
*(.gcc_except_table .gcc_except_table.*)
|
||||||
|
@ -125,11 +127,9 @@ SECTIONS {
|
||||||
. += CONSTANT(MAXPAGESIZE);
|
. += CONSTANT(MAXPAGESIZE);
|
||||||
. = DATA_SEGMENT_ALIGN(CONSTANT(MAXPAGESIZE), CONSTANT(COMMONPAGESIZE));
|
. = DATA_SEGMENT_ALIGN(CONSTANT(MAXPAGESIZE), CONSTANT(COMMONPAGESIZE));
|
||||||
|
|
||||||
.eh_frame : {
|
.eh_frame : ONLY_IF_RW {
|
||||||
__eh_frame_start = .;
|
|
||||||
KEEP(*(.eh_frame))
|
KEEP(*(.eh_frame))
|
||||||
*(.eh_frame.*)
|
*(.eh_frame.*)
|
||||||
__eh_frame_end = .;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.gnu_extab : ONLY_IF_RW {
|
.gnu_extab : ONLY_IF_RW {
|
||||||
|
@ -259,9 +259,6 @@ SECTIONS {
|
||||||
.debug_ranges 0 : { *(.debug_ranges) }
|
.debug_ranges 0 : { *(.debug_ranges) }
|
||||||
.debug_macro 0 : { *(.debug_macro) }
|
.debug_macro 0 : { *(.debug_macro) }
|
||||||
.debug_addr 0 : { *(.debug_addr) }
|
.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)) }
|
.ARM.attributes 0 : { KEEP(*(.ARM.attributes)) KEEP(*(.gnu.attributes)) }
|
||||||
.note.gnu.arm.ident 0 : { KEEP(*(.note.gnu.arm.ident)) }
|
.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 │
|
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
|
||||||
│ PERFORMANCE OF THIS SOFTWARE. │
|
│ 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 <assert.h>
|
||||||
#include <dispatch/dispatch.h>
|
#include <dispatch/dispatch.h>
|
||||||
#include <dlfcn.h>
|
#include <dlfcn.h>
|
||||||
|
|
|
@ -37,7 +37,7 @@
|
||||||
#include "libc/calls/metalfile.internal.h"
|
#include "libc/calls/metalfile.internal.h"
|
||||||
#include "libc/dce.h"
|
#include "libc/dce.h"
|
||||||
#include "libc/elf/def.h"
|
#include "libc/elf/def.h"
|
||||||
#include "libc/macho.h"
|
#include "libc/macho.internal.h"
|
||||||
#include "libc/nexgen32e/uart.internal.h"
|
#include "libc/nexgen32e/uart.internal.h"
|
||||||
#include "libc/nt/pedef.internal.h"
|
#include "libc/nt/pedef.internal.h"
|
||||||
#include "libc/runtime/pc.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);
|
. = ALIGN(__privileged_end > __privileged_start ? CONSTANT(COMMONPAGESIZE) : 0);
|
||||||
/*END: morphable code */
|
/*END: morphable code */
|
||||||
__privileged_start = .;
|
__privileged_start = .;
|
||||||
*(.privileged .privileged.*)
|
*(.privileged)
|
||||||
__privileged_end = .;
|
__privileged_end = .;
|
||||||
|
|
||||||
KEEP(*(.ape.pad.text))
|
KEEP(*(.ape.pad.text))
|
||||||
|
@ -329,10 +329,6 @@ SECTIONS {
|
||||||
*(.ubsan.types)
|
*(.ubsan.types)
|
||||||
*(.ubsan.data)
|
*(.ubsan.data)
|
||||||
|
|
||||||
__eh_frame_hdr_start_actual = .;
|
|
||||||
*(.eh_frame_hdr)
|
|
||||||
__eh_frame_hdr_end_actual = .;
|
|
||||||
|
|
||||||
/* Legal Notices */
|
/* Legal Notices */
|
||||||
__notices = .;
|
__notices = .;
|
||||||
KEEP(*(.notice))
|
KEEP(*(.notice))
|
||||||
|
@ -386,13 +382,6 @@ SECTIONS {
|
||||||
_tbss_end = .;
|
_tbss_end = .;
|
||||||
} :Tls
|
} :Tls
|
||||||
|
|
||||||
.eh_frame : {
|
|
||||||
__eh_frame_start = .;
|
|
||||||
KEEP(*(.eh_frame))
|
|
||||||
*(.eh_frame.*)
|
|
||||||
__eh_frame_end = .;
|
|
||||||
} :Ram
|
|
||||||
|
|
||||||
.data . : {
|
.data . : {
|
||||||
/*BEGIN: Read/Write Data */
|
/*BEGIN: Read/Write Data */
|
||||||
#if SupportsWindows()
|
#if SupportsWindows()
|
||||||
|
@ -441,6 +430,7 @@ SECTIONS {
|
||||||
KEEP(*(.piro.pad.data))
|
KEEP(*(.piro.pad.data))
|
||||||
*(.igot.plt)
|
*(.igot.plt)
|
||||||
KEEP(*(.dataepilogue))
|
KEEP(*(.dataepilogue))
|
||||||
|
|
||||||
. = ALIGN(. != 0 ? CONSTANT(COMMONPAGESIZE) : 0);
|
. = ALIGN(. != 0 ? CONSTANT(COMMONPAGESIZE) : 0);
|
||||||
/*END: NT FORK COPYING */
|
/*END: NT FORK COPYING */
|
||||||
_edata = .;
|
_edata = .;
|
||||||
|
@ -520,9 +510,6 @@ SECTIONS {
|
||||||
.debug_rnglists 0 : { *(.debug_rnglists) }
|
.debug_rnglists 0 : { *(.debug_rnglists) }
|
||||||
.debug_macro 0 : { *(.debug_macro) }
|
.debug_macro 0 : { *(.debug_macro) }
|
||||||
.debug_addr 0 : { *(.debug_addr) }
|
.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)) }
|
.gnu.attributes 0 : { KEEP(*(.gnu.attributes)) }
|
||||||
.GCC.command.line 0 : { *(.GCC.command.line) }
|
.GCC.command.line 0 : { *(.GCC.command.line) }
|
||||||
|
|
||||||
|
@ -586,11 +573,11 @@ ape_rom_memsz = ape_rom_filesz;
|
||||||
ape_rom_align = CONSTANT(COMMONPAGESIZE);
|
ape_rom_align = CONSTANT(COMMONPAGESIZE);
|
||||||
ape_rom_rva = RVA(ape_rom_vaddr);
|
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_offset = ape_ram_vaddr - __executable_start;
|
||||||
ape_ram_paddr = LOADADDR(.eh_frame);
|
ape_ram_paddr = LOADADDR(.data);
|
||||||
ape_ram_filesz = ADDR(.bss) - ADDR(.eh_frame);
|
ape_ram_filesz = ADDR(.bss) - ADDR(.data);
|
||||||
ape_ram_memsz = _end - ADDR(.eh_frame);
|
ape_ram_memsz = _end - ADDR(.data);
|
||||||
ape_ram_align = CONSTANT(COMMONPAGESIZE);
|
ape_ram_align = CONSTANT(COMMONPAGESIZE);
|
||||||
ape_ram_rva = RVA(ape_ram_vaddr);
|
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_vaddr = DEFINED(ape_stack_vaddr) ? ape_stack_vaddr : 0x700000000000;
|
||||||
ape_stack_paddr = ape_ram_paddr + ape_ram_filesz;
|
ape_stack_paddr = ape_ram_paddr + ape_ram_filesz;
|
||||||
ape_stack_filesz = 0;
|
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_offset = ape_cod_offset + (ape_note - ape_cod_vaddr);
|
||||||
ape_note_filesz = ape_note_end - ape_note;
|
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_align = CONSTANT(COMMONPAGESIZE);
|
||||||
ape_text_rva = RVA(ape_text_vaddr);
|
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 */
|
/* 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 */
|
/* 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 */
|
/* 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
|
cd "$COSMO" || exit
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -x .cosmocc/current/bin/make ]; then
|
if [ -x build/bootstrap/make ]; then
|
||||||
MAKE=.cosmocc/current/bin/make
|
MAKE=build/bootstrap/make
|
||||||
else
|
else
|
||||||
MAKE=make
|
MAKE=make
|
||||||
fi
|
fi
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
|
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
|
||||||
│ PERFORMANCE OF THIS SOFTWARE. │
|
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||||
#include "libc/macros.h"
|
#include "libc/macros.internal.h"
|
||||||
|
|
||||||
// Calls _start() function of loaded program.
|
// Calls _start() function of loaded program.
|
||||||
//
|
//
|
||||||
|
|
|
@ -16,10 +16,10 @@
|
||||||
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
|
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
|
||||||
│ PERFORMANCE OF THIS SOFTWARE. │
|
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||||
#include "libc/macho.h"
|
#include "libc/macho.internal.h"
|
||||||
#include "libc/sysv/consts/prot.h"
|
#include "libc/sysv/consts/prot.h"
|
||||||
#include "libc/dce.h"
|
#include "libc/dce.h"
|
||||||
#include "libc/macros.h"
|
#include "libc/macros.internal.h"
|
||||||
|
|
||||||
// Apple Mach-O Executable Headers
|
// Apple Mach-O Executable Headers
|
||||||
// Fixups are applied by objbincopy
|
// Fixups are applied by objbincopy
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||||
#ifndef APE_MACROS_H_
|
#ifndef APE_MACROS_H_
|
||||||
#define APE_MACROS_H_
|
#define APE_MACROS_H_
|
||||||
#include "libc/macros.h"
|
#include "libc/macros.internal.h"
|
||||||
#ifdef __ASSEMBLER__
|
#ifdef __ASSEMBLER__
|
||||||
/* clang-format off */
|
/* 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 "libc/dce.h"
|
||||||
#include "ape/ape.h"
|
#include "ape/ape.h"
|
||||||
#include "libc/macros.h"
|
#include "libc/macros.internal.h"
|
||||||
|
|
||||||
#ifdef __aarch64__
|
#ifdef __aarch64__
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
|
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
|
||||||
│ PERFORMANCE OF THIS SOFTWARE. │
|
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||||
#include "libc/macros.h"
|
#include "libc/macros.internal.h"
|
||||||
|
|
||||||
// Invokes system call.
|
// 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_OFLAGS ?= -g -ggdb
|
||||||
CONFIG_CCFLAGS += -O2 $(BACKTRACES)
|
CONFIG_CCFLAGS += -O2 $(BACKTRACES)
|
||||||
CONFIG_CPPFLAGS += -DSYSDEBUG
|
CONFIG_CPPFLAGS += -DSYSDEBUG
|
||||||
|
TARGET_ARCH ?= -msse3
|
||||||
endif
|
endif
|
||||||
ifeq ($(MODE), x86_64)
|
ifeq ($(MODE), x86_64)
|
||||||
ENABLE_FTRACE = 1
|
ENABLE_FTRACE = 1
|
||||||
|
@ -63,6 +64,7 @@ ENABLE_FTRACE = 1
|
||||||
CONFIG_CCFLAGS += $(BACKTRACES) -O
|
CONFIG_CCFLAGS += $(BACKTRACES) -O
|
||||||
CONFIG_CPPFLAGS += -DSYSDEBUG -DDWARFLESS
|
CONFIG_CPPFLAGS += -DSYSDEBUG -DDWARFLESS
|
||||||
CONFIG_LDFLAGS += -S
|
CONFIG_LDFLAGS += -S
|
||||||
|
TARGET_ARCH ?= -msse3
|
||||||
endif
|
endif
|
||||||
|
|
||||||
# Optimized Mode
|
# Optimized Mode
|
||||||
|
@ -82,7 +84,7 @@ ENABLE_FTRACE = 1
|
||||||
CONFIG_OFLAGS ?= -g -ggdb
|
CONFIG_OFLAGS ?= -g -ggdb
|
||||||
CONFIG_CPPFLAGS += -DNDEBUG -DSYSDEBUG
|
CONFIG_CPPFLAGS += -DNDEBUG -DSYSDEBUG
|
||||||
CONFIG_CCFLAGS += $(BACKTRACES) -O3 -fmerge-all-constants
|
CONFIG_CCFLAGS += $(BACKTRACES) -O3 -fmerge-all-constants
|
||||||
CONFIG_TARGET_ARCH ?= -march=native
|
TARGET_ARCH ?= -march=native
|
||||||
endif
|
endif
|
||||||
|
|
||||||
# Optimized Linux Mode
|
# Optimized Linux Mode
|
||||||
|
@ -100,20 +102,7 @@ CONFIG_OFLAGS ?= -g -ggdb
|
||||||
CONFIG_CPPFLAGS += -DNDEBUG -DSYSDEBUG -DSUPPORT_VECTOR=1
|
CONFIG_CPPFLAGS += -DNDEBUG -DSYSDEBUG -DSUPPORT_VECTOR=1
|
||||||
CONFIG_CCFLAGS += -O3 -fmerge-all-constants
|
CONFIG_CCFLAGS += -O3 -fmerge-all-constants
|
||||||
CONFIG_COPTS += -mred-zone
|
CONFIG_COPTS += -mred-zone
|
||||||
CONFIG_TARGET_ARCH ?= -march=native
|
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
|
|
||||||
endif
|
endif
|
||||||
|
|
||||||
# Release Mode
|
# Release Mode
|
||||||
|
@ -134,6 +123,7 @@ endif
|
||||||
ifeq ($(MODE), rel)
|
ifeq ($(MODE), rel)
|
||||||
CONFIG_CPPFLAGS += -DNDEBUG -DDWARFLESS
|
CONFIG_CPPFLAGS += -DNDEBUG -DDWARFLESS
|
||||||
CONFIG_CCFLAGS += $(BACKTRACES) -O2
|
CONFIG_CCFLAGS += $(BACKTRACES) -O2
|
||||||
|
TARGET_ARCH ?= -msse3
|
||||||
PYFLAGS += -O1
|
PYFLAGS += -O1
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
@ -149,32 +139,18 @@ endif
|
||||||
ifeq ($(MODE), dbg)
|
ifeq ($(MODE), dbg)
|
||||||
ENABLE_FTRACE = 1
|
ENABLE_FTRACE = 1
|
||||||
CONFIG_OFLAGS ?= -g -ggdb
|
CONFIG_OFLAGS ?= -g -ggdb
|
||||||
OVERRIDE_CFLAGS += -O0
|
CONFIG_CPPFLAGS += -DMODE_DBG -D__SANITIZE_UNDEFINED__
|
||||||
OVERRIDE_CXXFLAGS += -O0
|
CONFIG_CCFLAGS += $(BACKTRACES) -DSYSDEBUG -O0 -fno-inline
|
||||||
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_COPTS += -fsanitize=undefined
|
CONFIG_COPTS += -fsanitize=undefined
|
||||||
|
TARGET_ARCH ?= -msse3
|
||||||
OVERRIDE_CCFLAGS += -fno-pie
|
OVERRIDE_CCFLAGS += -fno-pie
|
||||||
QUOTA ?= -C64 -L300
|
QUOTA ?= -C64 -L300
|
||||||
endif
|
endif
|
||||||
ifeq ($(MODE), aarch64-dbg)
|
ifeq ($(MODE), aarch64-dbg)
|
||||||
ENABLE_FTRACE = 1
|
ENABLE_FTRACE = 1
|
||||||
CONFIG_OFLAGS ?= -g -ggdb
|
CONFIG_OFLAGS ?= -g -ggdb
|
||||||
OVERRIDE_CFLAGS += -O0 -fdce
|
CONFIG_CPPFLAGS += -DMODE_DBG -D__SANITIZE_UNDEFINED__
|
||||||
OVERRIDE_CXXFLAGS += -O0 -fdce
|
CONFIG_CCFLAGS += $(BACKTRACES) -DSYSDEBUG -O0 -fno-inline -fdce
|
||||||
CONFIG_CPPFLAGS += -DMODE_DBG -D__SANITIZE_UNDEFINED__ -Wno-unused-variable -Wno-unused-but-set-variable
|
|
||||||
CONFIG_CCFLAGS += $(BACKTRACES) -DSYSDEBUG
|
|
||||||
CONFIG_COPTS += -fsanitize=undefined
|
CONFIG_COPTS += -fsanitize=undefined
|
||||||
QUOTA ?= -C64 -L300
|
QUOTA ?= -C64 -L300
|
||||||
endif
|
endif
|
||||||
|
@ -194,6 +170,7 @@ ENABLE_FTRACE = 1
|
||||||
CONFIG_OFLAGS ?= -g -ggdb
|
CONFIG_OFLAGS ?= -g -ggdb
|
||||||
CONFIG_CCFLAGS += $(BACKTRACES) -O2
|
CONFIG_CCFLAGS += $(BACKTRACES) -O2
|
||||||
CONFIG_CPPFLAGS += -DSYSDEBUG -DSUPPORT_VECTOR=121
|
CONFIG_CPPFLAGS += -DSYSDEBUG -DSUPPORT_VECTOR=121
|
||||||
|
TARGET_ARCH ?= -msse3
|
||||||
endif
|
endif
|
||||||
|
|
||||||
# Tiny Mode
|
# Tiny Mode
|
||||||
|
@ -223,6 +200,8 @@ CONFIG_CCFLAGS += \
|
||||||
-momit-leaf-frame-pointer \
|
-momit-leaf-frame-pointer \
|
||||||
-foptimize-sibling-calls \
|
-foptimize-sibling-calls \
|
||||||
-DDWARFLESS
|
-DDWARFLESS
|
||||||
|
TARGET_ARCH ?= \
|
||||||
|
-msse3
|
||||||
PYFLAGS += \
|
PYFLAGS += \
|
||||||
-O2 \
|
-O2 \
|
||||||
-B
|
-B
|
||||||
|
@ -242,6 +221,8 @@ CONFIG_CCFLAGS += \
|
||||||
-momit-leaf-frame-pointer \
|
-momit-leaf-frame-pointer \
|
||||||
-foptimize-sibling-calls \
|
-foptimize-sibling-calls \
|
||||||
-DDWARFLESS
|
-DDWARFLESS
|
||||||
|
TARGET_ARCH ?= \
|
||||||
|
-msse3
|
||||||
PYFLAGS += \
|
PYFLAGS += \
|
||||||
-O2 \
|
-O2 \
|
||||||
-B
|
-B
|
||||||
|
@ -293,6 +274,8 @@ CONFIG_CCFLAGS += \
|
||||||
-fno-align-jumps \
|
-fno-align-jumps \
|
||||||
-fno-align-labels \
|
-fno-align-labels \
|
||||||
-fno-align-loops
|
-fno-align-loops
|
||||||
|
TARGET_ARCH ?= \
|
||||||
|
-msse3
|
||||||
endif
|
endif
|
||||||
|
|
||||||
# Linux+BSD Tiny Mode
|
# Linux+BSD Tiny Mode
|
||||||
|
@ -322,6 +305,8 @@ CONFIG_CCFLAGS += \
|
||||||
-fno-align-jumps \
|
-fno-align-jumps \
|
||||||
-fno-align-labels \
|
-fno-align-labels \
|
||||||
-fno-align-loops
|
-fno-align-loops
|
||||||
|
TARGET_ARCH ?= \
|
||||||
|
-msse3
|
||||||
endif
|
endif
|
||||||
|
|
||||||
# Unix Tiny Mode
|
# Unix Tiny Mode
|
||||||
|
@ -350,6 +335,8 @@ CONFIG_CCFLAGS += \
|
||||||
-fno-align-jumps \
|
-fno-align-jumps \
|
||||||
-fno-align-labels \
|
-fno-align-labels \
|
||||||
-fno-align-loops
|
-fno-align-loops
|
||||||
|
TARGET_ARCH ?= \
|
||||||
|
-msse3
|
||||||
endif
|
endif
|
||||||
|
|
||||||
# Tiny Metallic Unix Mode
|
# Tiny Metallic Unix Mode
|
||||||
|
@ -378,6 +365,8 @@ CONFIG_CCFLAGS += \
|
||||||
-fno-align-jumps \
|
-fno-align-jumps \
|
||||||
-fno-align-labels \
|
-fno-align-labels \
|
||||||
-fno-align-loops
|
-fno-align-loops
|
||||||
|
TARGET_ARCH ?= \
|
||||||
|
-msse3
|
||||||
endif
|
endif
|
||||||
|
|
||||||
# no x87 instructions mode
|
# no x87 instructions mode
|
||||||
|
@ -399,6 +388,7 @@ ENABLE_FTRACE = 1
|
||||||
CONFIG_COPTS += -mlong-double-64
|
CONFIG_COPTS += -mlong-double-64
|
||||||
CONFIG_CCFLAGS += $(BACKTRACES) -O2
|
CONFIG_CCFLAGS += $(BACKTRACES) -O2
|
||||||
CONFIG_CPPFLAGS += -DSYSDEBUG -DNOX87
|
CONFIG_CPPFLAGS += -DSYSDEBUG -DNOX87
|
||||||
|
TARGET_ARCH ?= -msse3
|
||||||
endif
|
endif
|
||||||
|
|
||||||
# LLVM Mode
|
# LLVM Mode
|
||||||
|
@ -411,6 +401,7 @@ endif
|
||||||
#
|
#
|
||||||
ifeq ($(MODE), llvm)
|
ifeq ($(MODE), llvm)
|
||||||
.STRICT = 0
|
.STRICT = 0
|
||||||
|
TARGET_ARCH ?= -msse3
|
||||||
CONFIG_CCFLAGS += $(BACKTRACES) -DSYSDEBUG -O2
|
CONFIG_CCFLAGS += $(BACKTRACES) -DSYSDEBUG -O2
|
||||||
AS = clang
|
AS = clang
|
||||||
CC = clang
|
CC = clang
|
||||||
|
@ -453,6 +444,7 @@ ifeq ($(MODE), ansi)
|
||||||
CONFIG_CFLAGS += -std=c11
|
CONFIG_CFLAGS += -std=c11
|
||||||
#CONFIG_CPPFLAGS += -ansi
|
#CONFIG_CPPFLAGS += -ansi
|
||||||
CONFIG_CXXFLAGS += -std=c++11
|
CONFIG_CXXFLAGS += -std=c++11
|
||||||
|
TARGET_ARCH ?= -msse3
|
||||||
endif
|
endif
|
||||||
|
|
||||||
ifneq ($(ENABLE_FTRACE),)
|
ifneq ($(ENABLE_FTRACE),)
|
||||||
|
@ -510,5 +502,3 @@ ifeq ($(ARCH), aarch64)
|
||||||
CONFIG_CCFLAGS += -fpatchable-function-entry=7,6
|
CONFIG_CCFLAGS += -fpatchable-function-entry=7,6
|
||||||
endif
|
endif
|
||||||
endif
|
endif
|
||||||
|
|
||||||
TARGET_ARCH ?= $(CONFIG_TARGET_ARCH)
|
|
||||||
|
|
|
@ -92,7 +92,10 @@ DEFAULT_COPTS ?= \
|
||||||
-fno-gnu-unique \
|
-fno-gnu-unique \
|
||||||
-fstrict-aliasing \
|
-fstrict-aliasing \
|
||||||
-fstrict-overflow \
|
-fstrict-overflow \
|
||||||
-fno-semantic-interposition
|
-fno-semantic-interposition \
|
||||||
|
-fno-dwarf2-cfi-asm \
|
||||||
|
-fno-unwind-tables \
|
||||||
|
-fno-asynchronous-unwind-tables
|
||||||
|
|
||||||
ifeq ($(ARCH), x86_64)
|
ifeq ($(ARCH), x86_64)
|
||||||
# Microsoft says "[a]ny memory below the stack beyond the red zone
|
# 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
|
# - Cosmopolitan Libc uses x28 for thread-local storage because Apple
|
||||||
# forbids us from using tpidr_el0 too.
|
# forbids us from using tpidr_el0 too.
|
||||||
#
|
#
|
||||||
|
# - Cosmopolitan currently lacks an implementation of the runtime
|
||||||
|
# libraries needed by the -moutline-atomics flag
|
||||||
|
#
|
||||||
DEFAULT_COPTS += \
|
DEFAULT_COPTS += \
|
||||||
-ffixed-x18 \
|
-ffixed-x18 \
|
||||||
-ffixed-x28 \
|
-ffixed-x28 \
|
||||||
-fsigned-char
|
-fsigned-char \
|
||||||
|
-mno-outline-atomics
|
||||||
endif
|
endif
|
||||||
|
|
||||||
MATHEMATICAL = \
|
MATHEMATICAL = \
|
||||||
|
@ -132,10 +139,12 @@ DEFAULT_CPPFLAGS += \
|
||||||
-isystem libc/isystem
|
-isystem libc/isystem
|
||||||
|
|
||||||
DEFAULT_CFLAGS = \
|
DEFAULT_CFLAGS = \
|
||||||
-std=gnu23
|
-std=gnu2x
|
||||||
|
|
||||||
DEFAULT_CXXFLAGS = \
|
DEFAULT_CXXFLAGS = \
|
||||||
-std=gnu++23 \
|
-std=gnu++20 \
|
||||||
|
-fno-rtti \
|
||||||
|
-fno-exceptions \
|
||||||
-fuse-cxa-atexit \
|
-fuse-cxa-atexit \
|
||||||
-Wno-int-in-bool-context \
|
-Wno-int-in-bool-context \
|
||||||
-Wno-narrowing \
|
-Wno-narrowing \
|
||||||
|
|
|
@ -99,8 +99,3 @@ rm -f cosmocc.zip cosmocc.zip.sha256sum
|
||||||
# commit output directory
|
# commit output directory
|
||||||
cd "${OLDPWD}" || die
|
cd "${OLDPWD}" || die
|
||||||
mv "${OUTPUT_TMP}" "${OUTPUT_DIR}" || 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
|
fi
|
||||||
|
|
||||||
find_objdump() {
|
find_objdump() {
|
||||||
if [ -x .cosmocc/3.9.2/bin/$1-linux-cosmo-objdump ]; then
|
if [ -x .cosmocc/3.3.5/bin/$1-linux-cosmo-objdump ]; then
|
||||||
OBJDUMP=.cosmocc/3.9.2/bin/$1-linux-cosmo-objdump
|
OBJDUMP=.cosmocc/3.3.5/bin/$1-linux-cosmo-objdump
|
||||||
elif [ -x .cosmocc/3.9.2/bin/$1-linux-musl-objdump ]; then
|
elif [ -x .cosmocc/3.3.5/bin/$1-linux-musl-objdump ]; then
|
||||||
OBJDUMP=.cosmocc/3.9.2/bin/$1-linux-musl-objdump
|
OBJDUMP=.cosmocc/3.3.5/bin/$1-linux-musl-objdump
|
||||||
elif [ -x "$COSMO/.cosmocc/3.9.2/bin/$1-linux-cosmo-objdump" ]; then
|
elif [ -x "$COSMO/.cosmocc/3.3.5/bin/$1-linux-cosmo-objdump" ]; then
|
||||||
OBJDUMP="$COSMO/.cosmocc/3.9.2/bin/$1-linux-cosmo-objdump"
|
OBJDUMP="$COSMO/.cosmocc/3.3.5/bin/$1-linux-cosmo-objdump"
|
||||||
elif [ -x "$COSMO/.cosmocc/3.9.2/bin/$1-linux-musl-objdump" ]; then
|
elif [ -x "$COSMO/.cosmocc/3.3.5/bin/$1-linux-musl-objdump" ]; then
|
||||||
OBJDUMP="$COSMO/.cosmocc/3.9.2/bin/$1-linux-musl-objdump"
|
OBJDUMP="$COSMO/.cosmocc/3.3.5/bin/$1-linux-musl-objdump"
|
||||||
else
|
else
|
||||||
echo "error: toolchain not found (try running 'cosmocc --update' or 'make' in the cosmo monorepo)" >&2
|
echo "error: toolchain not found (try running 'cosmocc --update' or 'make' in the cosmo monorepo)" >&2
|
||||||
exit 1
|
exit 1
|
||||||
|
|
|
@ -4,5 +4,5 @@ UNAMES=$(uname -s)
|
||||||
if [ x"$UNAMES" = x"Darwin" ] && [ x"$UNAMEM" = x"arm64" ]; then
|
if [ x"$UNAMES" = x"Darwin" ] && [ x"$UNAMEM" = x"arm64" ]; then
|
||||||
exec ape "$@"
|
exec ape "$@"
|
||||||
else
|
else
|
||||||
exec rusage "$@"
|
exec "$@"
|
||||||
fi
|
fi
|
||||||
|
|
|
@ -31,7 +31,7 @@ if [ ! -f /proc/sys/fs/binfmt_misc/status ]; then
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! build/bootstrap/echo -n; then
|
if ! build/bootstrap/echo.com -n; then
|
||||||
cat <<'EOF' >&2
|
cat <<'EOF' >&2
|
||||||
|
|
||||||
ERROR
|
ERROR
|
||||||
|
|
|
@ -17,9 +17,6 @@ struct conditional<false, T, F>
|
||||||
typedef F type;
|
typedef F type;
|
||||||
};
|
};
|
||||||
|
|
||||||
template<bool B, typename T, typename F>
|
|
||||||
using conditional_t = typename conditional<B, T, F>::type;
|
|
||||||
|
|
||||||
} // namespace ctl
|
} // namespace ctl
|
||||||
|
|
||||||
#endif // CTL_CONDITIONAL_H_
|
#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.
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
#include "istream.h"
|
#include "istream.h"
|
||||||
#include "libc/ctype.h"
|
|
||||||
#include "libc/fmt/conv.h"
|
#include "libc/fmt/conv.h"
|
||||||
#include "libc/stdio/stdio.h"
|
#include "libc/stdio/stdio.h"
|
||||||
#include "libc/str/str.h"
|
#include "libc/str/str.h"
|
||||||
|
|
|
@ -17,7 +17,6 @@
|
||||||
// PERFORMANCE OF THIS SOFTWARE.
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
#include "istringstream.h"
|
#include "istringstream.h"
|
||||||
#include "libc/ctype.h"
|
|
||||||
#include "libc/fmt/conv.h"
|
#include "libc/fmt/conv.h"
|
||||||
#include "libc/str/str.h"
|
#include "libc/str/str.h"
|
||||||
|
|
||||||
|
|
253
ctl/set.h
253
ctl/set.h
|
@ -13,34 +13,18 @@ class set
|
||||||
{
|
{
|
||||||
struct rbtree
|
struct rbtree
|
||||||
{
|
{
|
||||||
uintptr_t left_;
|
rbtree* left;
|
||||||
rbtree* right;
|
rbtree* right;
|
||||||
rbtree* parent;
|
rbtree* parent;
|
||||||
|
bool is_red;
|
||||||
Key value;
|
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)
|
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)
|
if (node_ == nullptr)
|
||||||
__builtin_trap();
|
__builtin_trap();
|
||||||
if (node_->left()) {
|
if (node_->left) {
|
||||||
node_ = rightmost(node_->left());
|
node_ = rightmost(node_->left);
|
||||||
} else {
|
} else {
|
||||||
node_type* parent = node_->parent;
|
node_type* parent = node_->parent;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
if (parent == nullptr)
|
if (parent == nullptr)
|
||||||
break;
|
break;
|
||||||
if (node_ == parent->left()) {
|
if (node_ == parent->left) {
|
||||||
node_ = parent;
|
node_ = parent;
|
||||||
parent = parent->parent;
|
parent = parent->parent;
|
||||||
} else {
|
} else {
|
||||||
|
@ -177,11 +161,11 @@ class set
|
||||||
|
|
||||||
reverse_iterator& operator++()
|
reverse_iterator& operator++()
|
||||||
{
|
{
|
||||||
if (node_->left()) {
|
if (node_->left) {
|
||||||
node_ = rightmost(node_->left());
|
node_ = rightmost(node_->left);
|
||||||
} else {
|
} else {
|
||||||
node_type* parent = node_->parent;
|
node_type* parent = node_->parent;
|
||||||
while (parent && node_ == parent->left()) {
|
while (parent && node_ == parent->left) {
|
||||||
node_ = parent;
|
node_ = parent;
|
||||||
parent = parent->parent;
|
parent = parent->parent;
|
||||||
}
|
}
|
||||||
|
@ -241,9 +225,8 @@ class set
|
||||||
private:
|
private:
|
||||||
friend class set;
|
friend class set;
|
||||||
node_type* node_;
|
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()
|
reverse_iterator rbegin()
|
||||||
{
|
{
|
||||||
return reverse_iterator(rightmost(root_), root_);
|
return reverse_iterator(rightmost(root_));
|
||||||
}
|
}
|
||||||
|
|
||||||
const_reverse_iterator rbegin() const
|
const_reverse_iterator rbegin() const
|
||||||
{
|
{
|
||||||
return const_reverse_iterator(rightmost(root_), root_);
|
return const_reverse_iterator(rightmost(root_));
|
||||||
}
|
}
|
||||||
|
|
||||||
const_reverse_iterator crbegin() const
|
const_reverse_iterator crbegin() const
|
||||||
{
|
{
|
||||||
return const_reverse_iterator(rightmost(root_), root_);
|
return const_reverse_iterator(rightmost(root_));
|
||||||
}
|
}
|
||||||
|
|
||||||
iterator end() noexcept
|
iterator end() noexcept
|
||||||
|
@ -378,17 +361,17 @@ class set
|
||||||
|
|
||||||
reverse_iterator rend()
|
reverse_iterator rend()
|
||||||
{
|
{
|
||||||
return reverse_iterator(nullptr, root_);
|
return reverse_iterator(nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
const_reverse_iterator rend() const
|
const_reverse_iterator rend() const
|
||||||
{
|
{
|
||||||
return const_reverse_iterator(nullptr, root_);
|
return const_reverse_iterator(nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
const_reverse_iterator crend() const
|
const_reverse_iterator crend() const
|
||||||
{
|
{
|
||||||
return const_reverse_iterator(nullptr, root_);
|
return const_reverse_iterator(nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
void clear() noexcept
|
void clear() noexcept
|
||||||
|
@ -525,7 +508,7 @@ class set
|
||||||
{
|
{
|
||||||
size_type count = 0;
|
size_type count = 0;
|
||||||
if (root_ != nullptr) {
|
if (root_ != nullptr) {
|
||||||
if (root_->is_red())
|
if (root_->is_red)
|
||||||
// ILLEGAL TREE: root node must be black
|
// ILLEGAL TREE: root node must be black
|
||||||
__builtin_trap();
|
__builtin_trap();
|
||||||
int black_height = -1;
|
int black_height = -1;
|
||||||
|
@ -540,8 +523,8 @@ class set
|
||||||
private:
|
private:
|
||||||
static node_type* leftmost(node_type* node) noexcept
|
static node_type* leftmost(node_type* node) noexcept
|
||||||
{
|
{
|
||||||
while (node && node->left())
|
while (node && node->left)
|
||||||
node = node->left();
|
node = node->left;
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -552,35 +535,35 @@ class set
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
static optimizesize void clearer(node_type* node) noexcept
|
static void clearer(node_type* node) noexcept
|
||||||
{
|
{
|
||||||
node_type* right;
|
node_type* right;
|
||||||
for (; node; node = right) {
|
for (; node; node = right) {
|
||||||
right = node->right;
|
right = node->right;
|
||||||
clearer(node->left());
|
clearer(node->left);
|
||||||
delete node;
|
delete node;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static optimizesize node_type* copier(const node_type* node)
|
static node_type* copier(const node_type* node)
|
||||||
{
|
{
|
||||||
if (node == nullptr)
|
if (node == nullptr)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
node_type* new_node = new node_type(node->value);
|
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);
|
new_node->right = copier(node->right);
|
||||||
if (new_node->left())
|
if (new_node->left)
|
||||||
new_node->left()->parent = new_node;
|
new_node->left->parent = new_node;
|
||||||
if (new_node->right)
|
if (new_node->right)
|
||||||
new_node->right->parent = new_node;
|
new_node->right->parent = new_node;
|
||||||
return 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)
|
if (node == nullptr)
|
||||||
return 0;
|
return 0;
|
||||||
return 1 + tally(node->left()) + tally(node->right);
|
return 1 + tally(node->left) + tally(node->right);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename K>
|
template<typename K>
|
||||||
|
@ -589,7 +572,7 @@ class set
|
||||||
node_type* current = root_;
|
node_type* current = root_;
|
||||||
while (current != nullptr) {
|
while (current != nullptr) {
|
||||||
if (comp_(key, current->value)) {
|
if (comp_(key, current->value)) {
|
||||||
current = current->left();
|
current = current->left;
|
||||||
} else if (comp_(current->value, key)) {
|
} else if (comp_(current->value, key)) {
|
||||||
current = current->right;
|
current = current->right;
|
||||||
} else {
|
} else {
|
||||||
|
@ -607,7 +590,7 @@ class set
|
||||||
while (current != nullptr) {
|
while (current != nullptr) {
|
||||||
if (!comp_(current->value, key)) {
|
if (!comp_(current->value, key)) {
|
||||||
result = current;
|
result = current;
|
||||||
current = current->left();
|
current = current->left;
|
||||||
} else {
|
} else {
|
||||||
current = current->right;
|
current = current->right;
|
||||||
}
|
}
|
||||||
|
@ -623,7 +606,7 @@ class set
|
||||||
while (current != nullptr) {
|
while (current != nullptr) {
|
||||||
if (comp_(key, current->value)) {
|
if (comp_(key, current->value)) {
|
||||||
result = current;
|
result = current;
|
||||||
current = current->left();
|
current = current->left;
|
||||||
} else {
|
} else {
|
||||||
current = current->right;
|
current = current->right;
|
||||||
}
|
}
|
||||||
|
@ -631,11 +614,11 @@ class set
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
optimizesize ctl::pair<iterator, bool> insert_node(node_type* node)
|
ctl::pair<iterator, bool> insert_node(node_type* node)
|
||||||
{
|
{
|
||||||
if (root_ == nullptr) {
|
if (root_ == nullptr) {
|
||||||
root_ = node;
|
root_ = node;
|
||||||
root_->is_red(false);
|
root_->is_red = false;
|
||||||
size_++;
|
size_++;
|
||||||
return { iterator(root_), true };
|
return { iterator(root_), true };
|
||||||
}
|
}
|
||||||
|
@ -644,7 +627,7 @@ class set
|
||||||
while (current != nullptr) {
|
while (current != nullptr) {
|
||||||
parent = current;
|
parent = current;
|
||||||
if (comp_(node->value, current->value)) {
|
if (comp_(node->value, current->value)) {
|
||||||
current = current->left();
|
current = current->left;
|
||||||
} else if (comp_(current->value, node->value)) {
|
} else if (comp_(current->value, node->value)) {
|
||||||
current = current->right;
|
current = current->right;
|
||||||
} else {
|
} else {
|
||||||
|
@ -653,7 +636,7 @@ class set
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (comp_(node->value, parent->value)) {
|
if (comp_(node->value, parent->value)) {
|
||||||
parent->left(node);
|
parent->left = node;
|
||||||
} else {
|
} else {
|
||||||
parent->right = node;
|
parent->right = node;
|
||||||
}
|
}
|
||||||
|
@ -663,23 +646,23 @@ class set
|
||||||
return { iterator(node), true };
|
return { iterator(node), true };
|
||||||
}
|
}
|
||||||
|
|
||||||
optimizesize void erase_node(node_type* node)
|
void erase_node(node_type* node)
|
||||||
{
|
{
|
||||||
node_type* y = node;
|
node_type* y = node;
|
||||||
node_type* x = nullptr;
|
node_type* x = nullptr;
|
||||||
node_type* x_parent = nullptr;
|
node_type* x_parent = nullptr;
|
||||||
bool y_original_color = y->is_red();
|
bool y_original_color = y->is_red;
|
||||||
if (node->left() == nullptr) {
|
if (node->left == nullptr) {
|
||||||
x = node->right;
|
x = node->right;
|
||||||
transplant(node, node->right);
|
transplant(node, node->right);
|
||||||
x_parent = node->parent;
|
x_parent = node->parent;
|
||||||
} else if (node->right == nullptr) {
|
} else if (node->right == nullptr) {
|
||||||
x = node->left();
|
x = node->left;
|
||||||
transplant(node, node->left());
|
transplant(node, node->left);
|
||||||
x_parent = node->parent;
|
x_parent = node->parent;
|
||||||
} else {
|
} else {
|
||||||
y = leftmost(node->right);
|
y = leftmost(node->right);
|
||||||
y_original_color = y->is_red();
|
y_original_color = y->is_red;
|
||||||
x = y->right;
|
x = y->right;
|
||||||
if (y->parent == node) {
|
if (y->parent == node) {
|
||||||
if (x)
|
if (x)
|
||||||
|
@ -692,9 +675,9 @@ class set
|
||||||
x_parent = y->parent;
|
x_parent = y->parent;
|
||||||
}
|
}
|
||||||
transplant(node, y);
|
transplant(node, y);
|
||||||
y->left(node->left());
|
y->left = node->left;
|
||||||
y->left()->parent = y;
|
y->left->parent = y;
|
||||||
y->is_red(node->is_red());
|
y->is_red = node->is_red;
|
||||||
}
|
}
|
||||||
if (!y_original_color)
|
if (!y_original_color)
|
||||||
rebalance_after_erase(x, x_parent);
|
rebalance_after_erase(x, x_parent);
|
||||||
|
@ -702,28 +685,28 @@ class set
|
||||||
--size_;
|
--size_;
|
||||||
}
|
}
|
||||||
|
|
||||||
optimizesize void left_rotate(node_type* x)
|
void left_rotate(node_type* x)
|
||||||
{
|
{
|
||||||
node_type* y = x->right;
|
node_type* y = x->right;
|
||||||
x->right = y->left();
|
x->right = y->left;
|
||||||
if (y->left() != nullptr)
|
if (y->left != nullptr)
|
||||||
y->left()->parent = x;
|
y->left->parent = x;
|
||||||
y->parent = x->parent;
|
y->parent = x->parent;
|
||||||
if (x->parent == nullptr) {
|
if (x->parent == nullptr) {
|
||||||
root_ = y;
|
root_ = y;
|
||||||
} else if (x == x->parent->left()) {
|
} else if (x == x->parent->left) {
|
||||||
x->parent->left(y);
|
x->parent->left = y;
|
||||||
} else {
|
} else {
|
||||||
x->parent->right = y;
|
x->parent->right = y;
|
||||||
}
|
}
|
||||||
y->left(x);
|
y->left = x;
|
||||||
x->parent = y;
|
x->parent = y;
|
||||||
}
|
}
|
||||||
|
|
||||||
optimizesize void right_rotate(node_type* y)
|
void right_rotate(node_type* y)
|
||||||
{
|
{
|
||||||
node_type* x = y->left();
|
node_type* x = y->left;
|
||||||
y->left(x->right);
|
y->left = x->right;
|
||||||
if (x->right != nullptr)
|
if (x->right != nullptr)
|
||||||
x->right->parent = y;
|
x->right->parent = y;
|
||||||
x->parent = y->parent;
|
x->parent = y->parent;
|
||||||
|
@ -732,18 +715,18 @@ class set
|
||||||
} else if (y == y->parent->right) {
|
} else if (y == y->parent->right) {
|
||||||
y->parent->right = x;
|
y->parent->right = x;
|
||||||
} else {
|
} else {
|
||||||
y->parent->left(x);
|
y->parent->left = x;
|
||||||
}
|
}
|
||||||
x->right = y;
|
x->right = y;
|
||||||
y->parent = x;
|
y->parent = x;
|
||||||
}
|
}
|
||||||
|
|
||||||
optimizesize void transplant(node_type* u, node_type* v)
|
void transplant(node_type* u, node_type* v)
|
||||||
{
|
{
|
||||||
if (u->parent == nullptr) {
|
if (u->parent == nullptr) {
|
||||||
root_ = v;
|
root_ = v;
|
||||||
} else if (u == u->parent->left()) {
|
} else if (u == u->parent->left) {
|
||||||
u->parent->left(v);
|
u->parent->left = v;
|
||||||
} else {
|
} else {
|
||||||
u->parent->right = v;
|
u->parent->right = v;
|
||||||
}
|
}
|
||||||
|
@ -751,7 +734,7 @@ class set
|
||||||
v->parent = u->parent;
|
v->parent = u->parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
optimizesize void checker(const node_type* node,
|
void checker(const node_type* node,
|
||||||
const node_type* parent,
|
const node_type* parent,
|
||||||
int black_count,
|
int black_count,
|
||||||
int& black_height) const
|
int& black_height) const
|
||||||
|
@ -770,121 +753,117 @@ class set
|
||||||
// ILLEGAL TREE: Parent link is incorrect
|
// ILLEGAL TREE: Parent link is incorrect
|
||||||
__builtin_trap();
|
__builtin_trap();
|
||||||
if (parent) {
|
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
|
// ILLEGAL TREE: Binary search property violated on left child
|
||||||
__builtin_trap();
|
__builtin_trap();
|
||||||
if (parent->right == node && !comp_(parent->value, node->value))
|
if (parent->right == node && !comp_(parent->value, node->value))
|
||||||
// ILLEGAL TREE: Binary search property violated on right child
|
// ILLEGAL TREE: Binary search property violated on right child
|
||||||
__builtin_trap();
|
__builtin_trap();
|
||||||
}
|
}
|
||||||
if (!node->is_red()) {
|
if (!node->is_red) {
|
||||||
black_count++;
|
black_count++;
|
||||||
} else if (parent != nullptr && parent->is_red()) {
|
} else if (parent != nullptr && parent->is_red) {
|
||||||
// ILLEGAL TREE: Red node has red child
|
// ILLEGAL TREE: Red node has red child
|
||||||
__builtin_trap();
|
__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);
|
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);
|
node->is_red = true;
|
||||||
while (node != root_ && node->parent->is_red()) {
|
while (node != root_ && node->parent->is_red) {
|
||||||
if (node->parent == node->parent->parent->left()) {
|
if (node->parent == node->parent->parent->left) {
|
||||||
node_type* uncle = node->parent->parent->right;
|
node_type* uncle = node->parent->parent->right;
|
||||||
if (uncle && uncle->is_red()) {
|
if (uncle && uncle->is_red) {
|
||||||
node->parent->is_red(false);
|
node->parent->is_red = false;
|
||||||
uncle->is_red(false);
|
uncle->is_red = false;
|
||||||
node->parent->parent->is_red(true);
|
node->parent->parent->is_red = true;
|
||||||
node = node->parent->parent;
|
node = node->parent->parent;
|
||||||
} else {
|
} else {
|
||||||
if (node == node->parent->right) {
|
if (node == node->parent->right) {
|
||||||
node = node->parent;
|
node = node->parent;
|
||||||
left_rotate(node);
|
left_rotate(node);
|
||||||
}
|
}
|
||||||
node->parent->is_red(false);
|
node->parent->is_red = false;
|
||||||
node->parent->parent->is_red(true);
|
node->parent->parent->is_red = true;
|
||||||
right_rotate(node->parent->parent);
|
right_rotate(node->parent->parent);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
node_type* uncle = node->parent->parent->left();
|
node_type* uncle = node->parent->parent->left;
|
||||||
if (uncle && uncle->is_red()) {
|
if (uncle && uncle->is_red) {
|
||||||
node->parent->is_red(false);
|
node->parent->is_red = false;
|
||||||
uncle->is_red(false);
|
uncle->is_red = false;
|
||||||
node->parent->parent->is_red(true);
|
node->parent->parent->is_red = true;
|
||||||
node = node->parent->parent;
|
node = node->parent->parent;
|
||||||
} else {
|
} else {
|
||||||
if (node == node->parent->left()) {
|
if (node == node->parent->left) {
|
||||||
node = node->parent;
|
node = node->parent;
|
||||||
right_rotate(node);
|
right_rotate(node);
|
||||||
}
|
}
|
||||||
node->parent->is_red(false);
|
node->parent->is_red = false;
|
||||||
node->parent->parent->is_red(true);
|
node->parent->parent->is_red = true;
|
||||||
left_rotate(node->parent->parent);
|
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())) {
|
while (node != root_ && (node == nullptr || !node->is_red)) {
|
||||||
if (node == parent->left()) {
|
if (node == parent->left) {
|
||||||
node_type* sibling = parent->right;
|
node_type* sibling = parent->right;
|
||||||
if (sibling->is_red()) {
|
if (sibling->is_red) {
|
||||||
sibling->is_red(false);
|
sibling->is_red = false;
|
||||||
parent->is_red(true);
|
parent->is_red = true;
|
||||||
left_rotate(parent);
|
left_rotate(parent);
|
||||||
sibling = parent->right;
|
sibling = parent->right;
|
||||||
}
|
}
|
||||||
if ((sibling->left() == nullptr ||
|
if ((sibling->left == nullptr || !sibling->left->is_red) &&
|
||||||
!sibling->left()->is_red()) &&
|
(sibling->right == nullptr || !sibling->right->is_red)) {
|
||||||
(sibling->right == nullptr || !sibling->right->is_red())) {
|
sibling->is_red = true;
|
||||||
sibling->is_red(true);
|
|
||||||
node = parent;
|
node = parent;
|
||||||
parent = node->parent;
|
parent = node->parent;
|
||||||
} else {
|
} else {
|
||||||
if (sibling->right == nullptr ||
|
if (sibling->right == nullptr || !sibling->right->is_red) {
|
||||||
!sibling->right->is_red()) {
|
sibling->left->is_red = false;
|
||||||
sibling->left()->is_red(false);
|
sibling->is_red = true;
|
||||||
sibling->is_red(true);
|
|
||||||
right_rotate(sibling);
|
right_rotate(sibling);
|
||||||
sibling = parent->right;
|
sibling = parent->right;
|
||||||
}
|
}
|
||||||
sibling->is_red(parent->is_red());
|
sibling->is_red = parent->is_red;
|
||||||
parent->is_red(false);
|
parent->is_red = false;
|
||||||
sibling->right->is_red(false);
|
sibling->right->is_red = false;
|
||||||
left_rotate(parent);
|
left_rotate(parent);
|
||||||
node = root_;
|
node = root_;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
node_type* sibling = parent->left();
|
node_type* sibling = parent->left;
|
||||||
if (sibling->is_red()) {
|
if (sibling->is_red) {
|
||||||
sibling->is_red(false);
|
sibling->is_red = false;
|
||||||
parent->is_red(true);
|
parent->is_red = true;
|
||||||
right_rotate(parent);
|
right_rotate(parent);
|
||||||
sibling = parent->left();
|
sibling = parent->left;
|
||||||
}
|
}
|
||||||
if ((sibling->right == nullptr || !sibling->right->is_red()) &&
|
if ((sibling->right == nullptr || !sibling->right->is_red) &&
|
||||||
(sibling->left() == nullptr ||
|
(sibling->left == nullptr || !sibling->left->is_red)) {
|
||||||
!sibling->left()->is_red())) {
|
sibling->is_red = true;
|
||||||
sibling->is_red(true);
|
|
||||||
node = parent;
|
node = parent;
|
||||||
parent = node->parent;
|
parent = node->parent;
|
||||||
} else {
|
} else {
|
||||||
if (sibling->left() == nullptr ||
|
if (sibling->left == nullptr || !sibling->left->is_red) {
|
||||||
!sibling->left()->is_red()) {
|
sibling->right->is_red = false;
|
||||||
sibling->right->is_red(false);
|
sibling->is_red = true;
|
||||||
sibling->is_red(true);
|
|
||||||
left_rotate(sibling);
|
left_rotate(sibling);
|
||||||
sibling = parent->left();
|
sibling = parent->left;
|
||||||
}
|
}
|
||||||
sibling->is_red(parent->is_red());
|
sibling->is_red = parent->is_red;
|
||||||
parent->is_red(false);
|
parent->is_red = false;
|
||||||
sibling->left()->is_red(false);
|
sibling->left->is_red = false;
|
||||||
right_rotate(parent);
|
right_rotate(parent);
|
||||||
node = root_;
|
node = root_;
|
||||||
break;
|
break;
|
||||||
|
@ -892,7 +871,7 @@ class set
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (node != nullptr)
|
if (node != nullptr)
|
||||||
node->is_red(false);
|
node->is_red = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
node_type* root_;
|
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 <__atomic/fence.h>
|
||||||
#include <stdckdint.h>
|
#include <stdckdint.h>
|
||||||
|
|
||||||
#include "libc/mem/mem.h"
|
|
||||||
#include "libc/str/str.h"
|
|
||||||
|
|
||||||
namespace ctl {
|
namespace ctl {
|
||||||
|
|
||||||
void
|
void
|
||||||
|
@ -383,72 +380,4 @@ string::erase(const size_t pos, size_t count) noexcept
|
||||||
return *this;
|
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
|
} // namespace ctl
|
||||||
|
|
|
@ -125,7 +125,6 @@ class string
|
||||||
void append(char, size_t) noexcept;
|
void append(char, size_t) noexcept;
|
||||||
void append(unsigned long) noexcept;
|
void append(unsigned long) noexcept;
|
||||||
void append(const void*, size_t) 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& insert(size_t, ctl::string_view) noexcept;
|
||||||
string& erase(size_t = 0, size_t = npos) noexcept;
|
string& erase(size_t = 0, size_t = npos) noexcept;
|
||||||
string substr(size_t = 0, size_t = npos) const 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;
|
bool starts_with(ctl::string_view) const noexcept;
|
||||||
size_t find(char, size_t = 0) 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(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
|
void swap(string& s) noexcept
|
||||||
{
|
{
|
||||||
|
@ -307,7 +302,7 @@ class string
|
||||||
append(ch);
|
append(ch);
|
||||||
}
|
}
|
||||||
|
|
||||||
void append(const ctl::string_view& s) noexcept
|
void append(const ctl::string_view s) noexcept
|
||||||
{
|
{
|
||||||
append(s.p, s.n);
|
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);
|
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
|
} // namespace ctl
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// -*-mode:c++;indent-tabs-mode:nil;c-basic-offset:4;tab-width:8;coding:utf-8-*-
|
// -*-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
|
// vi: set et ft=cpp ts=4 sts=4 sw=4 fenc=utf-8 :vi
|
||||||
#ifndef CTL_STRINGVIEW_H_
|
#ifndef COSMOPOLITAN_CTL_STRINGVIEW_H_
|
||||||
#define CTL_STRINGVIEW_H_
|
#define COSMOPOLITAN_CTL_STRINGVIEW_H_
|
||||||
#include "utility.h"
|
#include "utility.h"
|
||||||
|
|
||||||
namespace ctl {
|
namespace ctl {
|
||||||
|
@ -45,10 +45,6 @@ struct string_view
|
||||||
string_view substr(size_t = 0, size_t = npos) const noexcept;
|
string_view substr(size_t = 0, size_t = npos) const noexcept;
|
||||||
size_t find(char, size_t = 0) const noexcept;
|
size_t find(char, size_t = 0) const noexcept;
|
||||||
size_t find(string_view, 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
|
constexpr string_view& operator=(const string_view s) noexcept
|
||||||
{
|
{
|
||||||
|
@ -113,12 +109,12 @@ struct string_view
|
||||||
return p[n - 1];
|
return p[n - 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr const_iterator begin() const noexcept
|
constexpr const_iterator begin() noexcept
|
||||||
{
|
{
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr const_iterator end() const noexcept
|
constexpr const_iterator end() noexcept
|
||||||
{
|
{
|
||||||
return p + n;
|
return p + n;
|
||||||
}
|
}
|
||||||
|
@ -161,4 +157,4 @@ struct string_view
|
||||||
|
|
||||||
} // namespace ctl
|
} // namespace ctl
|
||||||
|
|
||||||
#endif // CTL_STRINGVIEW_H_
|
#endif // COSMOPOLITAN_CTL_STRINGVIEW_H_
|
||||||
|
|
|
@ -525,7 +525,7 @@ class vector
|
||||||
capacity_ = new_capacity;
|
capacity_ = new_capacity;
|
||||||
}
|
}
|
||||||
|
|
||||||
[[no_unique_address]] Allocator alloc_;
|
Allocator alloc_;
|
||||||
pointer data_;
|
pointer data_;
|
||||||
size_type size_;
|
size_type size_;
|
||||||
size_type capacity_;
|
size_type capacity_;
|
||||||
|
|
|
@ -2,9 +2,7 @@
|
||||||
#── vi: set noet ft=make ts=8 sw=8 fenc=utf-8 :vi ────────────────────┘
|
#── vi: set noet ft=make ts=8 sw=8 fenc=utf-8 :vi ────────────────────┘
|
||||||
|
|
||||||
.PHONY: o/$(MODE)/dsp
|
.PHONY: o/$(MODE)/dsp
|
||||||
o/$(MODE)/dsp: o/$(MODE)/dsp/audio \
|
o/$(MODE)/dsp: o/$(MODE)/dsp/core \
|
||||||
o/$(MODE)/dsp/core \
|
|
||||||
o/$(MODE)/dsp/mpeg \
|
o/$(MODE)/dsp/mpeg \
|
||||||
o/$(MODE)/dsp/scale \
|
o/$(MODE)/dsp/scale \
|
||||||
o/$(MODE)/dsp/prog \
|
|
||||||
o/$(MODE)/dsp/tty
|
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_
|
#ifndef COSMOPOLITAN_DSP_CORE_C161_H_
|
||||||
#define COSMOPOLITAN_DSP_CORE_C161_H_
|
#define COSMOPOLITAN_DSP_CORE_C161_H_
|
||||||
#include "libc/macros.h"
|
#include "libc/macros.internal.h"
|
||||||
|
|
||||||
#define EXTRA_SHARP 2
|
#define EXTRA_SHARP 2
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
#ifndef COSMOPOLITAN_DSP_CORE_C161S_H_
|
#ifndef COSMOPOLITAN_DSP_CORE_C161S_H_
|
||||||
#define COSMOPOLITAN_DSP_CORE_C161S_H_
|
#define COSMOPOLITAN_DSP_CORE_C161S_H_
|
||||||
#include "dsp/core/c161.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) {
|
__funline signed char C161S(signed char al, signed char bl, signed char cl) {
|
||||||
short ax, bx, cx;
|
short ax, bx, cx;
|
||||||
|
|
|
@ -9,13 +9,14 @@ int mulaw(int);
|
||||||
int unmulaw(int);
|
int unmulaw(int);
|
||||||
void *double2byte(long, const void *, double, double) vallocesque;
|
void *double2byte(long, const void *, double, double) vallocesque;
|
||||||
void *byte2double(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 *dct(float[restrict hasatleast 8][8], unsigned, float, float, float, float,
|
||||||
void *dctjpeg(float[hasatleast 8][8], unsigned);
|
float);
|
||||||
|
void *dctjpeg(float[restrict hasatleast 8][8], unsigned);
|
||||||
double det3(const double[3][3]) nosideeffect;
|
double det3(const double[3][3]) nosideeffect;
|
||||||
void *inv3(double[3][3], const double[3][3], double);
|
void *inv3(double[restrict 3][3], const double[restrict 3][3], double);
|
||||||
void *matmul3(double[3][3], const double[3][3], const double[3][3]);
|
void *matmul3(double[restrict 3][3], const double[3][3], const double[3][3]);
|
||||||
void *vmatmul3(double[3], const double[3], const double[3][3]);
|
void *vmatmul3(double[restrict 3], const double[3], const double[3][3]);
|
||||||
void *matvmul3(double[3], const double[3][3], const double[3]);
|
void *matvmul3(double[restrict 3], const double[3][3], const double[3]);
|
||||||
double rgb2stdtv(double) pureconst;
|
double rgb2stdtv(double) pureconst;
|
||||||
double rgb2lintv(double) pureconst;
|
double rgb2lintv(double) pureconst;
|
||||||
double rgb2stdpc(double, double) pureconst;
|
double rgb2stdpc(double, double) pureconst;
|
||||||
|
|
|
@ -65,8 +65,8 @@
|
||||||
*
|
*
|
||||||
* @cost ~100ns
|
* @cost ~100ns
|
||||||
*/
|
*/
|
||||||
void *dct(float M[hasatleast 8][8], unsigned stride, float c0, float c1,
|
void *dct(float M[restrict hasatleast 8][8], unsigned stride, float c0,
|
||||||
float c2, float c3, float c4) {
|
float c1, float c2, float c3, float c4) {
|
||||||
unsigned y, x;
|
unsigned y, x;
|
||||||
for (y = 0; y < stride * 8; y += stride) {
|
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],
|
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;
|
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,
|
return dct(M, stride, .707106781f, .382683433f, .541196100f, 1.306562965f,
|
||||||
.707106781f);
|
.707106781f);
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
│ PERFORMANCE OF THIS SOFTWARE. │
|
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||||
#include "dsp/core/core.h"
|
#include "dsp/core/core.h"
|
||||||
#include "libc/macros.h"
|
#include "libc/macros.internal.h"
|
||||||
#include "libc/math.h"
|
#include "libc/math.h"
|
||||||
#include "libc/mem/mem.h"
|
#include "libc/mem/mem.h"
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
│ PERFORMANCE OF THIS SOFTWARE. │
|
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||||
#include "libc/limits.h"
|
#include "libc/limits.h"
|
||||||
#include "libc/macros.h"
|
#include "libc/macros.internal.h"
|
||||||
#include "libc/math.h"
|
#include "libc/math.h"
|
||||||
#include "libc/str/str.h"
|
#include "libc/str/str.h"
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
#include "libc/assert.h"
|
#include "libc/assert.h"
|
||||||
#include "libc/dce.h"
|
#include "libc/dce.h"
|
||||||
#include "libc/limits.h"
|
#include "libc/limits.h"
|
||||||
#include "libc/macros.h"
|
#include "libc/macros.internal.h"
|
||||||
#include "libc/math.h"
|
#include "libc/math.h"
|
||||||
#include "libc/str/str.h"
|
#include "libc/str/str.h"
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
#include "dsp/core/core.h"
|
#include "dsp/core/core.h"
|
||||||
#include "dsp/core/q.h"
|
#include "dsp/core/q.h"
|
||||||
#include "libc/dce.h"
|
#include "libc/dce.h"
|
||||||
#include "libc/macros.h"
|
#include "libc/macros.internal.h"
|
||||||
#include "libc/math.h"
|
#include "libc/math.h"
|
||||||
#include "libc/str/str.h"
|
#include "libc/str/str.h"
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
#ifndef COSMOPOLITAN_DSP_CORE_HALF_H_
|
#ifndef COSMOPOLITAN_DSP_CORE_HALF_H_
|
||||||
#define 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.
|
* Divides integer in half w/ rounding.
|
||||||
|
|
|
@ -30,7 +30,7 @@
|
||||||
* @define 𝐀⁻¹=𝐁 such that 𝐀×𝐁=𝐁×𝐀=𝐈ₙ
|
* @define 𝐀⁻¹=𝐁 such that 𝐀×𝐁=𝐁×𝐀=𝐈ₙ
|
||||||
* @see det3()
|
* @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;
|
d = d ? 1 / d : NAN;
|
||||||
B[0][0] = (A[1][1] * A[2][2] - A[2][1] * A[1][2]) * d;
|
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;
|
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_
|
#ifndef COSMOPOLITAN_DSP_CORE_KS8_H_
|
||||||
#define 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.
|
* Performs 16-bit scaled rounded madd w/ eight coefficients or fewer.
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
#ifndef COSMOPOLITAN_DSP_CORE_KSS8_H_
|
#ifndef COSMOPOLITAN_DSP_CORE_KSS8_H_
|
||||||
#define COSMOPOLITAN_DSP_CORE_KSS8_H_
|
#define COSMOPOLITAN_DSP_CORE_KSS8_H_
|
||||||
#include "libc/limits.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.
|
* Performs 16-bit scaled rounded saturated madd w/ eight coefficients or fewer.
|
||||||
|
|
|
@ -22,7 +22,8 @@
|
||||||
/**
|
/**
|
||||||
* Multiplies 3×3 matrices.
|
* 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;
|
int i, j, k;
|
||||||
memset(R, 0, sizeof(double) * 3 * 3);
|
memset(R, 0, sizeof(double) * 3 * 3);
|
||||||
for (i = 0; i < 3; ++i) {
|
for (i = 0; i < 3; ++i) {
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
*
|
*
|
||||||
* @see vmatmul3() for noncommutative corollary
|
* @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[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[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];
|
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_
|
#ifndef COSMOPOLITAN_DSP_CORE_Q_H_
|
||||||
#define COSMOPOLITAN_DSP_CORE_Q_H_
|
#define COSMOPOLITAN_DSP_CORE_Q_H_
|
||||||
#include "libc/limits.h"
|
#include "libc/limits.h"
|
||||||
#include "libc/macros.h"
|
#include "libc/macros.internal.h"
|
||||||
#include "libc/math.h"
|
#include "libc/math.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||||
#include "dsp/core/core.h"
|
#include "dsp/core/core.h"
|
||||||
#include "libc/limits.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/aarch64/arm_neon.internal.h"
|
||||||
#include "third_party/intel/emmintrin.internal.h"
|
#include "third_party/intel/emmintrin.internal.h"
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
│ PERFORMANCE OF THIS SOFTWARE. │
|
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||||
#include "dsp/core/core.h"
|
#include "dsp/core/core.h"
|
||||||
#include "libc/intrin/safemacros.h"
|
#include "libc/intrin/safemacros.internal.h"
|
||||||
#include "libc/limits.h"
|
#include "libc/limits.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
*
|
*
|
||||||
* @see matvmul3() for noncommutative corollary
|
* @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[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[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];
|
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 = \
|
DSP_MPEG_A_DIRECTDEPS = \
|
||||||
LIBC_CALLS \
|
LIBC_CALLS \
|
||||||
|
LIBC_FMT \
|
||||||
LIBC_INTRIN \
|
LIBC_INTRIN \
|
||||||
|
LIBC_LOG \
|
||||||
|
LIBC_LOG \
|
||||||
LIBC_MEM \
|
LIBC_MEM \
|
||||||
LIBC_NEXGEN32E \
|
LIBC_NEXGEN32E \
|
||||||
|
LIBC_RUNTIME \
|
||||||
LIBC_STDIO \
|
LIBC_STDIO \
|
||||||
LIBC_STR \
|
LIBC_STR \
|
||||||
|
LIBC_SYSV \
|
||||||
LIBC_TINYMATH \
|
LIBC_TINYMATH \
|
||||||
THIRD_PARTY_COMPILER_RT \
|
THIRD_PARTY_COMPILER_RT
|
||||||
|
|
||||||
DSP_MPEG_A_DEPS := \
|
DSP_MPEG_A_DEPS := \
|
||||||
$(call uniq,$(foreach x,$(DSP_MPEG_A_DIRECTDEPS),$($(x))))
|
$(call uniq,$(foreach x,$(DSP_MPEG_A_DIRECTDEPS),$($(x))))
|
||||||
|
@ -44,10 +49,9 @@ $(DSP_MPEG_A).pkg: \
|
||||||
$(DSP_MPEG_A_OBJS) \
|
$(DSP_MPEG_A_OBJS) \
|
||||||
$(foreach x,$(DSP_MPEG_A_DIRECTDEPS),$($(x)_A).pkg)
|
$(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 += \
|
CFLAGS += \
|
||||||
-ffunction-sections \
|
-Os
|
||||||
-fdata-sections
|
|
||||||
|
|
||||||
DSP_MPEG_LIBS = $(foreach x,$(DSP_MPEG_ARTIFACTS),$($(x)))
|
DSP_MPEG_LIBS = $(foreach x,$(DSP_MPEG_ARTIFACTS),$($(x)))
|
||||||
DSP_MPEG_SRCS = $(foreach x,$(DSP_MPEG_ARTIFACTS),$($(x)_SRCS))
|
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