From f4f4caab0edc02ea6edd22fa74321a249560d5b6 Mon Sep 17 00:00:00 2001 From: Justine Tunney Date: Tue, 25 Aug 2020 04:23:25 -0700 Subject: [PATCH] Add x86_64-linux-gnu emulator I wanted a tiny scriptable meltdown proof way to run userspace programs and visualize how program execution impacts memory. It helps to explain how things like Actually Portable Executable works. It can show you how the GCC generated code is going about manipulating matrices and more. I didn't feel fully comfortable with Qemu and Bochs because I'm not smart enough to understand them. I wanted something like gVisor but with much stronger levels of assurances. I wanted a single binary that'll run, on all major operating systems with an embedded GPL barrier ZIP filesystem that is tiny enough to transpile to JavaScript and run in browsers too. https://justine.storage.googleapis.com/emulator625.mp4 --- .clang-format | 3 +- Makefile | 82 +- ape/ape.S | 47 +- ape/ape.lds | 60 +- ape/ape.mk | 18 +- ape/idata.h | 32 +- ape/lib/pc.h | 5 +- build/archive | 1 + build/bootstrap/mkdeps.com | Bin 86016 -> 49152 bytes build/compile | 5 + build/config.mk | 20 +- build/definitions.mk | 53 +- build/htags | 15 +- build/rollup | 6 + build/rules.mk | 13 +- dsp/mpeg/mpeg1.c | 25 +- dsp/mpeg/ycbcrio.h | 2 +- dsp/scale/gyarados.c | 16 +- dsp/scale/scale.mk | 7 + dsp/tty/ident.c | 2 +- dsp/tty/itoa8.c | 8 +- dsp/tty/quant.h | 5 +- dsp/tty/rgb2ansi.c | 18 +- dsp/tty/rgb2xterm256.c | 71 - dsp/tty/rgb2xterm256.h | 11 - dsp/tty/{quantinit.c => ttyquant.c} | 14 +- dsp/tty/ttyraw.c | 14 +- dsp/tty/xtermname.c | 282 ++ dsp/tty/xtermname.h | 10 + examples/bigmem.c | 3 +- examples/cp.c | 96 + examples/examples.mk | 191 +- examples/findprime.c | 1 + examples/hello.c | 3 +- examples/kilo.c | 4 +- examples/nesemu1.cc | 530 ++- examples/printargs.c | 15 +- examples/unbourne.c | 217 +- examples/x86split.c | 2 +- libc/README.md | 4 - libc/alg/alg.h | 5 +- libc/alg/arraylist2.h | 5 + libc/alg/qsort.c | 12 +- libc/alg/replacestr16.c | 2 + libc/alg/tarjan.c | 190 +- libc/bits/bitreverse32.c | 1 + libc/bits/bitreverse64.c | 1 + libc/bits/bits.h | 112 - libc/bits/bswap.h | 48 + libc/bits/ezlea.h | 12 + libc/bits/initializer.h | 18 + libc/bits/popcnt.c | 23 +- libc/bits/popcnt.h | 2 +- libc/bits/pushpop.h | 2 +- libc/bits/safemacros.h | 20 +- libc/bits/segmentation.h | 23 + libc/bits/weaken.h | 28 + libc/calls/calls.h | 25 +- libc/calls/chdir.c | 2 + libc/calls/chmod.c | 1 + libc/calls/chown.c | 2 + libc/calls/close.c | 5 +- libc/calls/fstat-nt.c | 6 +- libc/calls/fstat-sysv.c | 2 +- libc/calls/fstat.c | 2 +- libc/calls/g_fds.c | 11 +- .../mapanon-thunk.S => calls/g_fds_init.S} | 20 +- libc/calls/getcwd.c | 33 +- libc/calls/getenv.c | 11 +- .../calls/getrusage-nt.c | 49 +- libc/calls/getrusage.c | 27 - libc/calls/growfds.c | 2 +- libc/calls/hefty/access.c | 6 +- libc/calls/hefty/copyfile.c | 108 + libc/calls/hefty/copyfile.h | 15 + libc/calls/hefty/faccessat-nt.c | 30 + libc/calls/hefty/faccessat.c | 7 +- libc/calls/hefty/hefty.mk | 11 +- libc/calls/hefty/internal.h | 1 + libc/calls/hefty/mkntenvblock.c | 48 +- libc/calls/internal.h | 23 +- libc/calls/ioctl-default.c | 2 +- libc/calls/ioctl-tiocgwinsz-nt.c | 17 +- libc/calls/ioctl.c | 3 + libc/calls/ischardev.c | 2 +- libc/calls/isdebuggerpresent.c | 27 +- libc/calls/isdirectory.c | 6 +- libc/calls/kntprioritycombos.c | 54 +- libc/calls/{mprotect.c => mkdirat.c} | 18 +- libc/calls/mkntpath.ncabi.c | 5 +- libc/calls/mprotect.greg.c | 9 +- libc/calls/open.c | 2 +- libc/calls/openat.c | 49 + libc/calls/pipe.c | 2 + libc/calls/pipe2.c | 2 + libc/calls/pread.c | 2 +- libc/calls/read.c | 2 +- libc/calls/readv.c | 1 + libc/calls/rename.c | 7 +- libc/calls/renameat.c | 32 + libc/calls/sched_setaffinity.c | 12 +- libc/calls/sigaction.c | 58 +- libc/calls/sigsuspend.c | 5 +- libc/calls/stat.c | 2 +- libc/calls/stat2linux.c | 2 +- libc/calls/struct/metastat.h | 2 + libc/calls/struct/metatermios.h | 2 + libc/calls/struct/sigaction-freebsd.h | 14 + libc/calls/struct/sigaction-linux.h | 15 + libc/calls/struct/sigaction-openbsd.h | 14 + libc/calls/struct/sigaction-xnu.h | 28 + libc/calls/struct/sigaltstack.h | 2 - libc/calls/struct/sigset.h | 2 +- libc/calls/termios-internal.h | 2 + libc/calls/termios.h | 8 +- libc/calls/truncate.c | 2 + libc/calls/ucontext.h | 48 +- libc/calls/unlink.c | 6 +- libc/calls/unlink_s.c | 1 - libc/calls/unlinkat.c | 30 + libc/calls/vdprintf.c | 51 +- libc/calls/write.c | 2 +- libc/calls/writev.c | 1 + libc/complex.h | 32 +- libc/conv/basename.c | 4 +- libc/conv/basename_n.c | 2 +- libc/conv/conv.h | 19 +- libc/conv/conv.mk | 9 +- libc/conv/filetimetotimespec.c | 12 +- libc/conv/itoa.h | 8 +- libc/conv/itoa128radix10.greg.c | 29 +- libc/conv/itoa64fixed16.greg.c | 38 + libc/conv/itoa64radix10.greg.c | 23 +- libc/conv/itoa64radix16.greg.c | 4 +- libc/conv/strtoimax.c | 4 + libc/conv/strtoumax.c | 11 +- libc/conv/timespectofiletime.c | 34 + libc/conv/unsleb128.c | 45 + libc/crt/crt.S | 33 +- libc/crypto/rijndael.h | 6 +- libc/dce.h | 18 +- libc/dns/dnsquestion.h | 2 + libc/dns/getaddrinfo.c | 30 +- libc/dns/getresolvconf.c | 4 +- libc/dns/parseresolvconf.c | 4 +- libc/elf/elf.h | 4 +- libc/elf/struct/sym.h | 5 +- libc/errno.h | 258 +- libc/fmt/bing.h | 4 +- libc/fmt/fmt.mk | 3 +- libc/fmt/paland.inc | 14 - libc/fmt/palandftoa.c | 8 +- libc/fmt/palandntoa.c | 46 +- libc/fmt/palandprintf.c | 53 +- libc/fmt/palandprintf.h | 6 +- libc/fmt/sscanf.c | 8 +- libc/fmt/stoa.c | 2 + libc/fmt/strerror.c | 2 +- libc/fmt/strerror_r.c | 20 +- libc/fmt/vcscanf.c | 273 +- libc/fmt/vcscanf.h | 297 -- libc/fmt/vsscanf.c | 22 +- libc/fmt/vsscanf.h | 39 - libc/integral/c.inc | 29 +- libc/integral/normalize.inc | 2 +- libc/internal/notice.h | 2 + libc/intrin/intrin.mk | 4 + libc/intrin/macros.h | 41 +- libc/intrin/pabsb.c | 35 + libc/intrin/pabsb.h | 13 + libc/intrin/pabsd.c | 35 + libc/intrin/pabsd.h | 13 + libc/intrin/pabsw.c | 35 + libc/intrin/pabsw.h | 13 + .../tinystrncmp.ncabi.c => intrin/packssdw.c} | 19 +- libc/intrin/packssdw.h | 15 + .../intrin/packsswb.c | 28 +- libc/intrin/packsswb.h | 21 +- libc/intrin/packusdw.c | 35 + libc/intrin/packusdw.h | 15 + libc/intrin/packuswb.c | 39 + libc/intrin/packuswb.h | 21 +- libc/intrin/paddb.c | 36 + libc/intrin/paddb.h | 14 + libc/intrin/paddd.c | 36 + libc/intrin/paddd.h | 14 + libc/intrin/paddq.c | 36 + libc/intrin/paddq.h | 14 + libc/intrin/paddsb.c | 40 + libc/intrin/paddsb.h | 14 + libc/intrin/paddsw.c | 40 + libc/intrin/paddsw.h | 20 +- libc/intrin/paddusb.c | 40 + libc/intrin/paddusb.h | 14 + libc/intrin/paddusw.c | 40 + libc/intrin/paddusw.h | 14 + libc/intrin/paddw.c | 40 + libc/intrin/paddw.h | 20 +- libc/intrin/palignr.c | 44 + libc/intrin/palignr.h | 75 +- libc/intrin/palignrs.S | 126 + libc/intrin/pand.c | 35 + libc/intrin/pand.h | 14 + libc/intrin/pandn.c | 35 + libc/intrin/pandn.h | 14 + libc/intrin/pavgb.c | 38 + libc/intrin/pavgb.h | 14 + libc/intrin/pavgw.c | 38 + libc/intrin/pavgw.h | 14 + libc/intrin/pcmpeqb.c | 36 + libc/intrin/pcmpeqb.h | 14 + libc/intrin/pcmpeqd.c | 36 + libc/intrin/pcmpeqd.h | 14 + libc/intrin/pcmpeqw.c | 36 + libc/intrin/pcmpeqw.h | 14 + libc/intrin/pcmpgtb.c | 36 + libc/intrin/pcmpgtb.h | 14 + libc/intrin/pcmpgtd.c | 36 + libc/intrin/pcmpgtd.h | 14 + libc/intrin/pcmpgtw.c | 36 + libc/intrin/pcmpgtw.h | 14 + libc/intrin/pdep.h | 2 + libc/intrin/pext.h | 2 + libc/intrin/phaddd.c | 39 + libc/intrin/phaddd.h | 14 + libc/intrin/phaddsw.c | 2 +- libc/intrin/phaddsw.h | 4 +- libc/intrin/phaddw.c | 7 +- libc/intrin/phaddw.h | 5 +- libc/intrin/phsubd.c | 39 + libc/intrin/phsubd.h | 14 + .../paddw_test.c => libc/intrin/phsubsw.c | 57 +- libc/intrin/phsubsw.h | 15 + libc/intrin/phsubw.c | 43 + libc/intrin/phsubw.h | 14 + libc/intrin/pmaddubsw.c | 6 +- libc/intrin/pmaddubsw.h | 4 +- libc/intrin/pmaddwd.c | 35 + libc/intrin/pmaddwd.h | 14 + libc/intrin/pmaxsw.c | 39 + libc/intrin/pmaxsw.h | 14 + libc/intrin/pmaxub.c | 37 + libc/intrin/pmaxub.h | 15 + libc/intrin/pminsw.c | 39 + libc/intrin/pminsw.h | 14 + libc/intrin/pminub.c | 37 + libc/intrin/pminub.h | 15 + libc/{runtime/exit.c => intrin/pmovmskb.c} | 16 +- libc/intrin/pmovmskb.h | 27 + libc/intrin/pmulhrsw.c | 12 +- libc/intrin/pmulhrsw.h | 4 +- libc/intrin/pmulhuw.c | 38 + libc/intrin/pmulhuw.h | 14 + libc/intrin/pmulhw.c | 38 + libc/intrin/pmulhw.h | 14 + libc/intrin/pmulld.c | 39 + libc/intrin/pmulld.h | 14 + libc/intrin/pmullw.c | 38 + libc/intrin/pmullw.h | 14 + libc/intrin/pmuludq.c | 37 + libc/intrin/pmuludq.h | 14 + libc/intrin/por.c | 35 + libc/intrin/por.h | 14 + libc/intrin/psadbw.c | 37 + libc/intrin/psadbw.h | 14 + libc/intrin/pshufb.c | 38 + libc/intrin/pshufb.h | 13 + libc/intrin/pshufd.c | 37 + libc/intrin/pshufd.h | 13 + libc/intrin/pshufhw.c | 41 + libc/intrin/pshufhw.h | 13 + libc/intrin/pshuflw.c | 41 + libc/intrin/pshuflw.h | 13 + libc/intrin/pshufw.c | 37 + libc/intrin/pshufw.h | 10 + libc/intrin/psignb.c | 38 + libc/intrin/psignb.h | 14 + libc/intrin/psignd.c | 38 + libc/intrin/psignd.h | 14 + libc/intrin/psignw.c | 38 + libc/intrin/psignw.h | 14 + libc/intrin/pslld.c | 38 + libc/intrin/pslld.h | 16 + libc/intrin/pslldq.c | 35 + libc/intrin/pslldq.h | 37 + libc/intrin/pslldqs.S | 94 + libc/intrin/pslldv.c | 36 + libc/intrin/psllq.c | 38 + libc/intrin/psllq.h | 16 + libc/intrin/psllqv.c | 36 + libc/intrin/psllw.c | 38 + libc/intrin/psllw.h | 16 + libc/intrin/psllwv.c | 38 + libc/intrin/psrad.c | 40 + libc/intrin/psrad.h | 16 + libc/intrin/psradv.c | 40 + libc/intrin/psraw.c | 40 + libc/intrin/psraw.h | 21 +- .../psraw_test.c => libc/intrin/psrawv.c | 34 +- libc/intrin/psrld.c | 39 + libc/intrin/psrld.h | 16 + libc/intrin/psrldq.c | 35 + libc/intrin/psrldq.h | 37 + libc/intrin/psrldqs.S | 94 + libc/intrin/psrldv.c | 38 + libc/intrin/psrlq.c | 39 + libc/intrin/psrlq.h | 16 + libc/intrin/psrlqv.c | 38 + libc/intrin/psrlw.c | 39 + libc/intrin/psrlw.h | 16 + libc/intrin/psrlwv.c | 38 + libc/intrin/psubb.c | 38 + libc/intrin/psubb.h | 14 + libc/intrin/psubd.c | 38 + libc/intrin/psubd.h | 14 + libc/intrin/psubq.c | 38 + libc/intrin/psubq.h | 14 + libc/intrin/psubsb.c | 38 + libc/intrin/psubsb.h | 14 + libc/intrin/psubsw.c | 38 + libc/intrin/psubsw.h | 14 + .../findmapping.c => intrin/psubusb.c} | 45 +- libc/intrin/psubusb.h | 14 + libc/intrin/psubusw.c | 40 + libc/intrin/psubusw.h | 14 + libc/intrin/psubw.c | 38 + libc/intrin/psubw.h | 14 + libc/intrin/punpckhbw.c | 47 + libc/intrin/punpckhbw.h | 15 + libc/intrin/punpckhdq.c | 43 + libc/intrin/punpckhdq.h | 15 + libc/intrin/punpckhqdq.c | 33 + libc/intrin/punpckhqdq.h | 15 + libc/intrin/punpckhwd.c | 50 + libc/intrin/punpckhwd.h | 15 + libc/intrin/punpcklbw.c | 57 + libc/intrin/punpcklbw.h | 15 + libc/intrin/punpckldq.c | 43 + libc/intrin/punpckldq.h | 15 + libc/intrin/punpcklqdq.c | 33 + libc/intrin/punpcklqdq.h | 15 + libc/intrin/punpcklwd.c | 49 + libc/intrin/punpcklwd.h | 15 + libc/intrin/pxor.c | 35 + libc/intrin/pxor.h | 14 + libc/intrin/repstosb.h | 25 + libc/intrin/shufpd.c | 33 + libc/intrin/shufpd.h | 10 + libc/intrin/shufps.c | 37 + libc/intrin/shufps.h | 10 + libc/intrin/vpalignr.c | 131 - libc/leb128.h | 29 - libc/libc.mk | 14 +- libc/limits.h | 4 + libc/linux/exit.h | 16 + libc/linux/linux.mk | 11 + libc/linux/read.h | 15 + libc/linux/write.h | 18 + libc/log/addr2linepath.c | 24 + libc/log/attachdebugger.c | 5 +- libc/log/backtrace.c | 2 +- libc/log/backtrace2.c | 41 +- libc/log/cancolor.c | 1 + libc/log/check.h | 21 +- libc/log/checkaligned.c | 11 +- libc/log/checkfail.c | 6 +- libc/log/gdbexec.c | 5 +- libc/{runtime/mappings.c => log/gdbpath.c} | 6 +- libc/log/internal.h | 15 +- libc/log/log.h | 11 +- libc/log/{logfile.c => logfile.initabi.c} | 3 +- libc/log/meminfo.c | 13 +- libc/log/memsummary.c | 9 +- libc/log/oncrash.c | 74 +- libc/log/showcrashreports.c | 8 +- libc/log/vflogf.c | 11 +- libc/macho.h | 206 +- libc/macros-cpp.inc | 51 +- libc/macros.inc | 18 +- libc/math.h | 19 +- libc/{runtime => mem}/balloc.c | 55 +- libc/{runtime => mem}/bfree.c | 19 +- libc/mem/mem.mk | 1 + libc/mem/reallocarray.c | 5 +- libc/mem/unhexstr.c | 2 +- libc/ncabi.h | 87 - libc/nexgen32e/bcmp.S | 2 +- libc/nexgen32e/bsf.S | 43 - libc/nexgen32e/bsf.h | 29 +- libc/nexgen32e/bsfl.S | 44 - libc/nexgen32e/bsr.S | 43 - libc/nexgen32e/bsr.h | 37 +- libc/nexgen32e/bsrl.S | 44 - libc/nexgen32e/bzero.S | 2 +- libc/nexgen32e/cmpsl.S | 5 +- libc/nexgen32e/crc32.h | 17 + libc/nexgen32e/crc32c-pure.c | 2 +- libc/nexgen32e/crc32c-sse42.c | 6 +- libc/nexgen32e/crc32c.S | 2 - libc/nexgen32e/crc32init.S | 2 +- libc/nexgen32e/crc32z.c | 3 +- libc/nexgen32e/div10.greg.S | 119 - ...00000int64.greg.S => div1000000000int64.S} | 4 +- ...v1000000int64.greg.S => div1000000int64.S} | 4 +- libc/nexgen32e/div10000int64.S | 31 + .../{div1000int64.greg.S => div1000int64.S} | 4 +- libc/nexgen32e/div100int64.S | 36 + .../{div10int64.greg.S => div10int64.S} | 6 +- libc/nexgen32e/doc/memrchr.c | 2 - libc/nexgen32e/explicit_bzero.S | 12 +- libc/nexgen32e/ffs.S | 2 +- libc/nexgen32e/ffsl.S | 2 +- libc/nexgen32e/gc.S | 30 +- libc/nexgen32e/gc.h | 4 +- libc/nexgen32e/gclongjmp.S | 4 +- libc/nexgen32e/kcp437.S | 4 +- libc/nexgen32e/kcpuids.S | 4 +- libc/nexgen32e/kcpuids.h | 4 +- libc/nexgen32e/kstarttsc.S | 1 + libc/nexgen32e/memcmp-avx2.S | 3 +- libc/nexgen32e/memcmp-hook.S | 4 +- libc/nexgen32e/memcmp-sse2.S | 2 +- libc/nexgen32e/memcmp.S | 2 +- libc/nexgen32e/memcpy.S | 29 +- libc/nexgen32e/memjmpinit.S | 3 +- libc/nexgen32e/memmove.S | 7 +- libc/nexgen32e/mempcpy.S | 7 +- libc/nexgen32e/memset.S | 10 +- libc/nexgen32e/nexgen32e.h | 12 +- libc/nexgen32e/nontemporal.h | 66 - ...00000int64.greg.S => rem1000000000int64.S} | 6 +- ...d1000000int64.greg.S => rem1000000int64.S} | 8 +- libc/nexgen32e/rem10000int64.S | 40 + .../{mod1000int64.greg.S => rem1000int64.S} | 8 +- libc/nexgen32e/rem100int64.S | 40 + .../{mod10int64.greg.S => rem10int64.S} | 8 +- libc/nexgen32e/slowcall.h | 24 + libc/nexgen32e/strcaseconv.S | 23 +- libc/nexgen32e/strcmp-avx.S | 2 +- libc/nexgen32e/strsak.S | 25 +- libc/nexgen32e/tinydivsi.greg.S | 9 +- libc/nexgen32e/tinystrcmp.S | 6 +- libc/nexgen32e/tinystrlen.h | 11 + libc/nexgen32e/tinystrncmp.ncabi.S | 56 + libc/nexgen32e/vendor.h | 9 +- libc/nexgen32e/x86feature.h | 2 +- libc/nt/accounting.h | 4 + libc/nt/files.h | 6 +- libc/nt/master.sh | 4 +- libc/nt/memory.h | 4 + libc/nt/nt.mk | 33 +- libc/nt/nt/systemthreads.h | 23 - libc/nt/ntdllimport.S | 1 - libc/nt/process.h | 2 +- libc/nt/signals.h | 49 +- libc/nt/struct/teb.h | 16 +- libc/nt/thunk/accounting.inc | 11 + libc/nt/thunk/files.inc | 2 + libc/nt/thunk/memory.inc | 10 + libc/nt/thunk/process.inc | 17 +- libc/nt/thunk/runtime.inc | 32 +- libc/pe.h | 2 + libc/rand/g_rando.S | 11 +- libc/rand/lcg.h | 2 - libc/rand/{rand.ncabi.c => rand.c} | 4 +- libc/rand/rand.h | 6 - libc/rand/rngset.c | 14 +- libc/rand/xorshift.h | 21 +- libc/rand/xorshift32.c | 29 + libc/rand/xorshift64.c | 29 + libc/runtime/arememoryintervalsok.c | 36 + libc/runtime/asan.greg.c | 54 +- libc/runtime/assertfail.c | 2 +- libc/runtime/atexit.c | 34 + libc/runtime/carsort.h | 11 + libc/runtime/carsort100.c | 41 + libc/runtime/{heapsortcar.c => carsort1000.c} | 37 +- libc/runtime/close_s.c | 6 +- libc/runtime/closesymboltable.c | 14 +- libc/runtime/construct.S | 45 + libc/runtime/cxaatexit.S | 181 - libc/runtime/cxaatexit.c | 122 + libc/runtime/defer.greg.c | 4 +- libc/runtime/{startmain.S => executive.S} | 88 +- libc/runtime/exit.S | 39 + libc/runtime/ezmap.c | 12 +- libc/runtime/ezmap.h | 6 +- libc/runtime/findmemoryinterval.c | 36 + libc/runtime/free_s.c | 1 - libc/runtime/ftrace.greg.c | 108 +- libc/runtime/gc.h | 18 +- libc/runtime/getdosargv.c | 5 +- libc/runtime/getdosenviron.h | 2 + libc/runtime/grow.c | 2 +- libc/runtime/hook.greg.c | 125 + libc/runtime/internal.h | 7 +- libc/runtime/isheap.c | 26 +- libc/runtime/mapanon.c | 2 +- libc/runtime/mappings.h | 78 - libc/runtime/memtrack.c | 117 + libc/runtime/memtrack.h | 33 + libc/runtime/memtracknt.c | 41 + libc/runtime/missioncritical.h | 3 + libc/runtime/mmap.c | 150 +- libc/runtime/msync-nt.c | 21 +- libc/runtime/msync.c | 1 - libc/runtime/munmap.c | 106 - libc/runtime/munmap_s.c | 3 +- libc/runtime/opensymboltable.c | 14 +- libc/runtime/piro.c | 13 +- libc/runtime/printmemoryintervals.c | 45 + libc/runtime/quick_exit.c | 2 +- libc/runtime/relocate.c | 42 + libc/runtime/runtime.h | 54 +- libc/runtime/runtime.mk | 2 + libc/runtime/spawn.S | 51 + libc/runtime/sysconf.h | 3 +- libc/runtime/winmain.greg.c | 20 +- libc/sock/internal.h | 2 + libc/sock/ipclassify.h | 4 +- libc/sock/sock.h | 2 +- libc/sock/xinet_ntop.c | 1 + libc/stdio/fclose.c | 2 +- libc/stdio/fclose_s.c | 1 - libc/stdio/fdopen.c | 2 +- libc/stdio/fflush.c | 4 +- libc/stdio/fputc.c | 22 +- libc/stdio/fputc.h | 36 - libc/stdio/fputs.c | 3 +- libc/stdio/fputwc.c | 3 +- libc/stdio/fread.c | 16 +- libc/stdio/fseteof.c | 4 +- libc/stdio/fwrite.c | 19 +- libc/stdio/g_stderr.c | 7 +- libc/stdio/g_stdin.c | 3 +- libc/stdio/g_stdout.c | 6 +- libc/stdio/internal.h | 4 +- libc/stdio/puts.c | 1 - libc/stdio/serialstdio.c | 10 +- libc/stdio/stdio.h | 24 +- libc/stdio/stdio.mk | 4 + libc/stdio/system.c | 2 +- libc/stdio/temp.h | 2 +- libc/stdio/vfprintf.c | 15 +- libc/str/appendchar.h | 2 + libc/{nexgen32e => str}/getcachesize.c | 0 libc/str/getutf16.ncabi.c | 2 +- .../{nexgen32e => str}/getx86processormodel.c | 0 libc/{nexgen32e => str}/insertionsort.greg.c | 0 libc/str/internal.h | 6 +- libc/{nexgen32e => str}/knobs.c | 0 libc/str/knuthmultiplicativehash.h | 21 +- libc/{nexgen32e => str}/kx86processormodels.c | 0 libc/{nexgen32e => str}/lz4check.c | 0 .../lz4cpy.c => str/lz4cpy.initabi.c} | 0 libc/{nexgen32e => str}/lz4decode.c | 0 libc/{nexgen32e => str}/memset16.c | 0 libc/str/sha256.c | 2 +- libc/str/startswith.c | 7 +- libc/str/stpcpy.c | 5 +- libc/str/str.h | 172 +- libc/{nexgen32e => str}/strcspn.c | 0 libc/{nexgen32e => str}/strcspn16.c | 4 +- libc/{nexgen32e => str}/strpbrk.c | 0 libc/{nexgen32e => str}/strpbrk16.c | 5 +- libc/str/strsignal.c | 8 +- libc/{nexgen32e => str}/strspn.c | 3 + libc/{nexgen32e => str}/strspn16.c | 4 +- libc/str/strtok_r.c | 5 +- libc/str/tinymemccpy.c | 31 + libc/str/tinymemccpy.h | 10 + libc/str/tinymemmem.h | 6 +- libc/str/tpdecode.h | 2 +- libc/str/tpdecodecb.h | 16 +- libc/str/tpenc.h | 23 +- libc/str/varint.h | 87 - libc/{nexgen32e => str}/wcscspn.c | 4 +- libc/{nexgen32e => str}/wcspbrk.c | 5 +- libc/{nexgen32e => str}/wcsspn.c | 4 +- libc/{nexgen32e => str}/wmemset.c | 0 libc/stubs/abort.S | 2 +- libc/stubs/addvdi3.S | 4 +- libc/stubs/addvsi3.S | 4 +- libc/stubs/addvti3.S | 9 +- libc/stubs/assertfail.S | 1 + libc/stubs/errnolocation.S | 5 +- libc/stubs/exit.S | 3 +- libc/stubs/exit11.S | 36 - libc/stubs/ld.S | 9 +- libc/stubs/mulvdi3.S | 11 +- libc/stubs/mulvsi3.S | 11 +- libc/stubs/mulvti3.S | 114 +- libc/stubs/negvdi2.S | 4 +- libc/stubs/negvsi2.S | 4 +- libc/stubs/negvti2.S | 15 +- libc/stubs/onarithmeticoverflow.S | 4 +- libc/stubs/panic.S | 2 +- .../munmap-thunk.S => stubs/retpoline.S} | 15 +- libc/{sysv => stubs}/stackchkguard.S | 20 +- libc/stubs/stubs.mk | 10 +- libc/stubs/subvdi3.S | 4 +- libc/stubs/subvsi3.S | 4 +- libc/stubs/subvti3.S | 9 +- libc/{nexgen32e => stubs}/sysv2nt.S | 0 libc/stubs/zip.S | 30 +- libc/sysv/calls/__utimensat-sysv.s | 2 + libc/sysv/calls/futimens-sysv.s | 2 + libc/sysv/calls/futimens.s | 2 - libc/sysv/calls/futimes-sysv.s | 2 + libc/sysv/calls/futimes.s | 2 - libc/sysv/calls/futimesat-sysv.s | 2 + libc/sysv/calls/futimesat.s | 2 - libc/sysv/calls/utimensat.s | 2 - libc/sysv/consts.sh | 11 +- libc/sysv/consts/GLOB_ABORTED.s | 2 - libc/sysv/consts/GLOB_APPEND.s | 2 - libc/sysv/consts/GLOB_DOOFFS.s | 2 - libc/sysv/consts/GLOB_ERR.s | 2 - libc/sysv/consts/GLOB_MARK.s | 2 - libc/sysv/consts/GLOB_NOCHECK.s | 2 - libc/sysv/consts/GLOB_NOESCAPE.s | 2 - libc/sysv/consts/GLOB_NOMATCH.s | 2 - libc/sysv/consts/GLOB_NOSORT.s | 2 - libc/sysv/consts/GLOB_NOSPACE.s | 2 - libc/sysv/consts/GLOB_NOSYS.s | 2 - libc/sysv/consts/UTIME_NOW.s | 2 +- libc/sysv/consts/UTIME_OMIT.s | 2 +- libc/sysv/consts/at.h | 10 +- libc/sysv/consts/auxv.h | 48 +- libc/sysv/consts/utime.h | 16 + libc/sysv/syscall.S | 2 +- libc/sysv/syscalls.sh | 12 +- libc/sysv/systemfive.S | 77 +- libc/sysv/sysv.mk | 26 +- libc/testlib/almostequallongdouble.c | 2 +- libc/testlib/bench.h | 27 +- libc/testlib/clearxmmregisters.c | 31 + libc/testlib/ezbenchreport.c | 5 +- libc/testlib/fixturerunner.c | 4 +- libc/testlib/showerror.c | 2 +- libc/testlib/showerror_.c | 2 +- libc/testlib/testlib.h | 13 +- libc/testlib/testlib.mk | 1 + libc/testlib/testmain.c | 1 + libc/testlib/testmem.c | 2 +- libc/testlib/testrunner.c | 3 +- libc/testlib/ugly.h | 4 +- libc/time/clock_gettime.c | 23 +- libc/time/dsleep.c | 2 +- libc/time/futimens.c | 31 + libc/time/futimes.c | 40 + libc/time/futimesat.c | 40 + libc/time/gettimeofday.c | 2 +- libc/time/localtime.c | 10 +- libc/time/nanosleep.c | 14 +- libc/time/now.c | 56 +- libc/time/strftime.c | 3 +- libc/time/struct/utimbuf.h | 11 + libc/time/time.h | 16 +- libc/time/times.c | 44 +- libc/time/utime.c | 17 +- libc/time/utime.h | 15 - .../{calls/copyfile.c => time/utimensat-nt.c} | 94 +- libc/time/utimensat-sysv.c | 30 + .../time/utimensat-xnu.c | 68 +- libc/time/utimensat.c | 37 + libc/time/utimes.c | 54 +- libc/tinymath/c2rangr.S | 4 +- libc/tinymath/cbrt.S | 3 +- libc/tinymath/cbrtf.S | 3 +- libc/tinymath/cbrtl.S | 3 +- libc/tinymath/copysignl.S | 2 +- libc/tinymath/cprojl.S | 33 +- libc/tinymath/exp2.S | 6 +- libc/tinymath/exp2f.S | 10 +- libc/tinymath/exp2l.S | 11 +- libc/tinymath/expm1.S | 10 +- libc/tinymath/expm1f.S | 10 +- libc/tinymath/expm1l.S | 11 +- libc/tinymath/f2ld2.S | 2 +- libc/tinymath/fmodl.S | 5 +- libc/tinymath/ilogb.S | 21 +- libc/tinymath/ilogbf.S | 21 +- libc/tinymath/ilogbl.S | 40 +- libc/tinymath/log1p.S | 14 +- libc/tinymath/log1pf.S | 14 +- libc/tinymath/log1pl.S | 14 +- libc/tinymath/log2l.S | 1 + libc/tinymath/logb.S | 24 +- libc/tinymath/logbf.S | 24 +- libc/tinymath/logbl.S | 12 +- libc/tinymath/lroundl.S | 2 +- libc/tinymath/remainderl.S | 4 +- libc/tinymath/roundl.S | 12 +- libc/tinymath/scalbnf.S | 21 +- libc/tinymath/scalbnl.S | 23 +- libc/tinymath/sincosl.S | 15 +- libc/tinymath/truncl.S | 2 +- libc/unicode/kcombiningchars.S | 215 +- libc/x/xstrmul.c | 1 - libc/zipos/find.c | 19 +- libc/zipos/fstat.c | 2 +- libc/zipos/get.c | 65 + libc/zipos/open.c | 39 +- libc/zipos/stat-impl.c | 22 +- libc/zipos/stat.c | 11 +- libc/zipos/zipcentraldir.S | 58 - libc/zipos/zipos.S | 29 + libc/zipos/zipos.h | 10 +- libc/zipos/zipos.mk | 6 +- test/ape/lib/test.mk | 4 +- test/dsp/core/test.mk | 4 +- test/dsp/scale/magikarp_test.c | 2 +- test/dsp/scale/test.mk | 4 +- test/dsp/tty/rgb2ansi_test.c | 2 +- test/dsp/tty/test.mk | 4 +- test/dsp/tty/ttyraster_test.c | 12 +- test/libc/alg/tarjan_test.c | 42 +- test/libc/alg/test.mk | 13 +- .../mappings_test.c => bits/division_test.c} | 70 +- test/libc/bits/integralarithmetic_test.c | 1 + test/libc/bits/popcnt_test.c | 35 + test/libc/bits/test.mk | 7 +- test/libc/calls/mkntpath_test.c | 2 +- test/libc/calls/test.mk | 4 +- test/libc/conv/itoa64radix10_test.c | 63 + test/libc/conv/itoa64radix16_test.c | 33 + test/libc/conv/strtoimax_test.c | 23 +- test/libc/conv/test.mk | 8 +- test/libc/dns/parseresolvconf_test.c | 16 +- test/libc/dns/test.mk | 4 +- test/libc/fmt/palandprintf_test.c | 36 +- test/libc/fmt/sprintf_s.inc | 6 +- test/libc/fmt/strerror_test.c | 5 + test/libc/fmt/test.mk | 9 +- test/libc/intrin/intrin_test.c | 2108 ++++++++++++ test/libc/intrin/palignr_test.c | 209 +- test/libc/intrin/pshuf_test.c | 162 + test/libc/intrin/test.mk | 13 +- test/libc/math/ilogb_test.c | 29 + test/libc/math/test.mk | 6 +- test/libc/mem/malloc_test.c | 54 +- test/libc/mem/test.mk | 11 +- test/libc/nexgen32e/crc32_test.c | 33 +- test/libc/nexgen32e/kcp437_test.c | 30 + test/libc/nexgen32e/memeqmask_test.c | 1 - test/libc/{str => nexgen32e}/memmove_test.c | 23 +- .../memset_test.c} | 93 +- test/libc/nexgen32e/sidiv_test.c | 48 + .../{strtolower_test.c => strcaseconv_test.c} | 26 +- test/libc/nexgen32e/test.mk | 3 +- test/libc/rand/test.mk | 8 +- test/libc/runtime/arch_prctl_test.c | 3 +- test/libc/runtime/balloc_test.c | 105 - test/libc/runtime/carsort_test.c | 75 + test/libc/runtime/grow_test.c | 1 - test/libc/runtime/itsatrap_test.c | 108 +- test/libc/runtime/memtrack_test.c | 289 ++ test/libc/runtime/mmap_test.c | 87 +- test/libc/runtime/test.mk | 8 +- test/libc/sock/test.mk | 8 +- test/libc/stdio/test.mk | 4 +- test/libc/str/crc32c_test.c | 8 +- test/libc/str/memccpy_test.c | 23 + test/libc/str/regex_test.c | 33 + test/libc/str/sha256_test.c | 11 +- test/libc/str/strcmp_test.c | 6 +- test/libc/str/strlen_test.c | 2 +- test/libc/str/strrchr_test.c | 20 +- test/libc/str/test.mk | 5 +- test/libc/str/undeflate_test.c | 2 + test/libc/str/varint_test.c | 301 -- test/libc/time/dsleep_test.c | 2 +- test/libc/time/test.mk | 4 +- .../logb_test.c} | 53 +- test/libc/tinymath/round_test.c | 117 +- test/libc/tinymath/test.mk | 7 +- test/libc/unicode/test.mk | 4 +- test/libc/unicode/wcwidth_test.c | 15 - test/libc/x/test.mk | 7 +- test/libc/xed/test.mk | 4 +- test/libc/xed/x86ild_lib.c | 6 +- test/libc/xed/x86ild_popular_i86_test.c | 2 +- test/libc/xed/x86ild_test.c | 23 +- test/net/http/test.mk | 4 +- test/net/http/uriparse_test.c | 7 +- test/tool/build/lib/alu_test.c | 106 + test/tool/build/lib/bitscan_test.c | 112 + test/tool/build/lib/bsu_test.c | 225 ++ test/tool/build/lib/disinst_test.c | 88 + test/tool/build/lib/divmul_test.c | 606 ++++ test/tool/build/lib/machine_test.c | 298 ++ test/tool/build/lib/modrm_test.c | 105 + test/tool/build/lib/numbers.c | 57 + test/tool/build/lib/numbers.h | 10 + test/tool/build/lib/optest.c | 94 + test/tool/build/lib/optest.h | 21 + test/tool/build/lib/pml4t_test.c | 150 + test/tool/build/lib/test.mk | 32 +- test/tool/build/lib/x87_test.c | 39 + test/tool/build/lib/xlaterrno_test.c | 28 + test/tool/viz/lib/test.mk | 4 +- third_party/bzip2/bzip2.mk | 4 +- third_party/compiler_rt/compiler_rt.mk | 11 +- third_party/compiler_rt/divmodti4.c | 64 + third_party/compiler_rt/divti3.c | 52 +- third_party/compiler_rt/int_lib.h | 2 + third_party/compiler_rt/udivmodti4.c | 368 +- third_party/compiler_rt/udivti3.c | 38 +- third_party/ctags/COPYING | 340 ++ third_party/ctags/README.txt | 1195 +++++++ third_party/ctags/ant.c | 35 + third_party/ctags/args.c | 238 ++ third_party/ctags/args.h | 47 + third_party/ctags/asm.c | 298 ++ third_party/ctags/asp.c | 255 ++ third_party/ctags/awk.c | 63 + third_party/ctags/basic.c | 175 + third_party/ctags/beta.c | 273 ++ third_party/ctags/c.c | 2966 +++++++++++++++++ third_party/ctags/cobol.c | 47 + third_party/ctags/config.h | 283 ++ third_party/ctags/ctags.h | 26 + third_party/ctags/ctags.mk | 61 + third_party/ctags/debug.h | 53 + third_party/ctags/dosbatch.c | 35 + third_party/ctags/eiffel.c | 1304 ++++++++ third_party/ctags/entry.c | 736 ++++ third_party/ctags/entry.h | 93 + third_party/ctags/erlang.c | 164 + third_party/ctags/flex.c | 2105 ++++++++++++ third_party/ctags/fortran.c | 2026 +++++++++++ third_party/ctags/general.h | 90 + third_party/ctags/get.c | 614 ++++ third_party/ctags/get.h | 50 + third_party/ctags/go.c | 596 ++++ third_party/ctags/html.c | 41 + third_party/ctags/jscript.c | 1496 +++++++++ third_party/ctags/keyword.c | 222 ++ third_party/ctags/keyword.h | 18 + third_party/ctags/lisp.c | 110 + third_party/ctags/lregex.c | 616 ++++ third_party/ctags/lua.c | 108 + third_party/ctags/main.c | 478 +++ third_party/ctags/main.h | 15 + third_party/ctags/make.c | 175 + third_party/ctags/matlab.c | 42 + third_party/ctags/objc.c | 1027 ++++++ third_party/ctags/ocaml.c | 1713 ++++++++++ third_party/ctags/options.c | 1669 ++++++++++ third_party/ctags/options.h | 134 + third_party/ctags/parse.c | 574 ++++ third_party/ctags/parse.h | 125 + third_party/ctags/parsers.h | 15 + third_party/ctags/pascal.c | 222 ++ third_party/ctags/perl.c | 327 ++ third_party/ctags/php.c | 240 ++ third_party/ctags/python.c | 668 ++++ third_party/ctags/read.c | 473 +++ third_party/ctags/read.h | 98 + third_party/ctags/readtags.c | 805 +++++ third_party/ctags/readtags.h | 224 ++ third_party/ctags/rexx.c | 34 + third_party/ctags/routines.c | 756 +++++ third_party/ctags/routines.h | 125 + third_party/ctags/ruby.c | 344 ++ third_party/ctags/scheme.c | 90 + third_party/ctags/sh.c | 87 + third_party/ctags/slang.c | 41 + third_party/ctags/sml.c | 175 + third_party/ctags/sort.c | 196 ++ third_party/ctags/sort.h | 32 + third_party/ctags/sql.c | 2174 ++++++++++++ third_party/ctags/strlist.c | 237 ++ third_party/ctags/strlist.h | 54 + third_party/ctags/tcl.c | 91 + third_party/ctags/tex.c | 476 +++ third_party/ctags/verilog.c | 293 ++ third_party/ctags/vhdl.c | 737 ++++ third_party/ctags/vim.c | 547 +++ third_party/ctags/vstring.c | 197 ++ third_party/ctags/vstring.h | 68 + third_party/ctags/yacc.c | 33 + third_party/dlmalloc/dlmalloc-debug.c | 2 +- third_party/dlmalloc/dlmalloc.c | 103 +- third_party/dlmalloc/dlmalloc.h | 2 + third_party/dlmalloc/initdlmalloc.S | 30 + .../double-conversion/double-conversion.mk | 2 +- third_party/dtoa/dtoa.c | 13 +- third_party/dtoa/dtoa.mk | 1 - third_party/dtoa/strtod.c | 1 - third_party/duktape/duktape.mk | 2 +- third_party/editline/editline.mk | 2 +- third_party/f2c/f2c.mk | 2 +- third_party/getopt/getopt.c | 49 +- third_party/getopt/getopt.mk | 2 +- third_party/getopt/initgetopt.S | 30 + third_party/linenoise/linenoise.mk | 2 +- third_party/lz4cli/bench.c | 2 + third_party/lz4cli/lz4io.c | 1 + third_party/lz4cli/util.h | 2 +- third_party/m4/m4.mk | 4 +- third_party/musl/musl.mk | 2 +- third_party/regex/regcomp.c | 90 +- third_party/regex/regerror.c | 65 +- third_party/regex/regex.h | 53 +- third_party/regex/regex.mk | 6 +- third_party/regex/regexec.c | 23 +- third_party/regex/tre-mem.c | 7 - third_party/stb/stb_image_write.c | 5 +- third_party/third_party.mk | 1 + third_party/xed/private.h | 2 +- third_party/xed/x86.h | 237 +- third_party/xed/x86ild.greg.c | 651 ++-- third_party/xed/x86tab.S | 4 +- third_party/xed/xed.mk | 4 +- third_party/xed/xederror.c | 52 + third_party/zlib/zalloc.c | 6 +- tool/build/build.mk | 24 +- tool/build/calculator.c | 118 +- tool/build/calculator.inc | 4 +- tool/build/emubin/emubin.mk | 55 + tool/build/emubin/metalsha256.c | 156 + tool/build/emubin/metalsha256.h | 19 + tool/build/emubin/mips.c | 50 + tool/build/emubin/pi.c | 56 + tool/build/emubin/prime.c | 42 + tool/build/emubin/sha256.c | 53 + tool/build/emucrt/emucrt.S | 34 + tool/build/emucrt/emucrt.lds | 81 + tool/build/emucrt/emucrt.mk | 12 + tool/build/emulator.c | 1480 ++++++++ tool/build/helpop.c | 372 +++ tool/build/lib/abp.h | 9 + .../showmappings.c => tool/build/lib/alu.c | 91 +- tool/build/lib/alu.h | 43 + tool/build/lib/argv.c | 82 + tool/build/lib/bitscan.c | 80 + tool/build/lib/bitscan.h | 13 + tool/build/lib/breakpoint.c | 50 + tool/build/lib/breakpoint.h | 21 + tool/build/lib/bsu.c | 167 + tool/build/lib/buffer.c | 68 + tool/build/lib/buffer.h | 20 + tool/build/lib/buildlib.mk | 20 +- tool/build/lib/case.h | 11 + tool/build/lib/cond.h | 50 + tool/build/lib/cpuid.c | 72 + tool/build/lib/cpuid.h | 11 + tool/build/lib/cvt.c | 316 ++ tool/build/lib/cvt.h | 18 + tool/build/lib/debug.c | 45 + tool/build/lib/dis.c | 263 ++ tool/build/lib/dis.h | 86 + tool/build/lib/disarg.c | 564 ++++ tool/build/lib/diself.c | 151 + tool/build/lib/dishigh.c | 34 + tool/build/lib/disinst.c | 194 ++ tool/build/lib/disspec.c | 1117 +++++++ tool/build/lib/divmul.c | 271 ++ tool/build/lib/divmul.h | 20 + tool/build/lib/elfwriter.c | 2 +- tool/build/lib/endian.h | 115 + tool/build/lib/errnos.S | 166 + tool/build/lib/flags.h | 78 + tool/build/lib/fpu.c | 1080 ++++++ tool/build/lib/fpu.h | 47 + tool/build/lib/initmachine.c | 49 + tool/build/lib/instruction.c | 74 + tool/build/lib/ioports.c | 42 + tool/build/lib/ioports.h | 12 + tool/build/lib/loader.c | 147 + tool/build/lib/loader.h | 21 + tool/build/lib/machine.c | 2381 +++++++++++++ tool/build/lib/machine.h | 149 + tool/build/lib/memory.c | 241 ++ tool/build/lib/memory.h | 32 + tool/build/lib/memorymalloc.c | 50 + tool/build/lib/message.c | 47 + tool/build/lib/modrm.c | 232 ++ tool/build/lib/modrm.h | 82 + tool/build/lib/panel.c | 155 + tool/build/lib/panel.h | 19 + tool/build/lib/pml4t.c | 218 ++ tool/build/lib/pml4t.h | 21 + tool/build/lib/pml4tfmt.c | 112 + tool/build/lib/reset.c | 78 + tool/build/lib/sse.c | 255 ++ tool/build/lib/sse.h | 102 + tool/build/lib/stack.c | 102 + tool/build/lib/stack.h | 19 + libc/rand/rando.c => tool/build/lib/stats.c | 6 +- tool/build/lib/stats.h | 12 + tool/build/lib/string.c | 227 ++ tool/build/lib/string.h | 22 + tool/build/lib/syscall.c | 647 ++++ tool/build/lib/syscall.h | 13 + tool/build/lib/throw.c | 60 + tool/build/lib/throw.h | 17 + tool/build/lib/x87.c | 71 + tool/build/lib/x87.h | 15 + tool/build/lib/xlaterrno.c | 64 + tool/build/lib/xlaterrno.h | 10 + tool/build/mkdeps.c | 13 +- tool/build/package.c | 21 +- tool/build/tinyemu.c | 48 + tool/build/x86combos.c | 192 ++ tool/build/zipobj.c | 16 +- tool/decode/decode.mk | 5 +- tool/decode/elf.c | 4 +- tool/decode/lib/elfidnames.c | 1 + tool/decode/lib/flagger.c | 31 +- tool/decode/lib/flagger.h | 2 +- tool/decode/lib/pollnames.S | 2 +- tool/decode/lib/xederrors.c | 2 +- tool/decode/lib/xederrors.h | 2 +- tool/decode/macho.c | 12 +- tool/decode/mkcombos.c | 3 +- tool/decode/pe2.c | 16 +- tool/decode/x86opinfo.c | 122 +- tool/decode/x87.c | 135 + tool/decode/zip.c | 19 +- tool/emacs/cosmo-asm-mode.el | 22 +- tool/emacs/cosmo-c-builtins.el | 74 +- tool/emacs/cosmo-c-constants.el | 7 +- tool/emacs/cosmo-c-keywords.el | 1 + tool/emacs/cosmo-cpp-constants.el | 40 +- tool/emacs/cosmo-platform-constants.el | 84 + tool/emacs/cosmo-stuff.el | 43 +- tool/hash/crctab.c | 2 +- tool/hash/hash.mk | 8 +- tool/net/dig.c | 22 +- tool/net/net.mk | 4 +- tool/scripts/distribute.sh | 43 + tool/viz/ascii2utf8.c | 2 +- tool/viz/derasterize.c | 2 +- tool/viz/generatematrix.c | 7 +- tool/viz/lib/convolution.h | 2 +- tool/viz/lib/perlin3.c | 5 +- tool/viz/lib/vizlib.mk | 14 - tool/viz/lib/ycbcr2rgb3.c | 2 +- tool/viz/printimage.c | 243 +- tool/viz/printpeb.c | 26 +- .../phaddsw_test.c => tool/viz/printpixel.c | 49 +- tool/viz/printvideo.c | 10 +- tool/viz/viz.mk | 4 +- tool/viz/xterm256cubes.c | 92 +- tool/viz/xterm256effective2.c | 16 +- tool/viz/xterm256info.c | 263 +- usr/share/rom/mario.nes | Bin 0 -> 40976 bytes usr/share/rom/tetris.nes | Bin 0 -> 49168 bytes usr/share/rom/zelda.nes | Bin 0 -> 262160 bytes 1052 files changed, 65667 insertions(+), 7825 deletions(-) create mode 100755 build/rollup delete mode 100644 dsp/tty/rgb2xterm256.c delete mode 100644 dsp/tty/rgb2xterm256.h rename dsp/tty/{quantinit.c => ttyquant.c} (89%) create mode 100644 dsp/tty/xtermname.c create mode 100644 dsp/tty/xtermname.h create mode 100644 examples/cp.c create mode 100644 libc/bits/bswap.h create mode 100644 libc/bits/ezlea.h create mode 100644 libc/bits/initializer.h create mode 100644 libc/bits/segmentation.h create mode 100644 libc/bits/weaken.h rename libc/{runtime/mapanon-thunk.S => calls/g_fds_init.S} (92%) rename test/libc/intrin/phaddw_test.c => libc/calls/getrusage-nt.c (69%) create mode 100644 libc/calls/hefty/copyfile.c create mode 100644 libc/calls/hefty/copyfile.h create mode 100644 libc/calls/hefty/faccessat-nt.c rename libc/calls/{mprotect.c => mkdirat.c} (87%) create mode 100644 libc/calls/openat.c create mode 100644 libc/calls/renameat.c create mode 100644 libc/calls/struct/sigaction-freebsd.h create mode 100644 libc/calls/struct/sigaction-linux.h create mode 100644 libc/calls/struct/sigaction-openbsd.h create mode 100644 libc/calls/struct/sigaction-xnu.h create mode 100644 libc/calls/unlinkat.c create mode 100644 libc/conv/itoa64fixed16.greg.c create mode 100644 libc/conv/timespectofiletime.c create mode 100644 libc/conv/unsleb128.c delete mode 100644 libc/fmt/vcscanf.h delete mode 100644 libc/fmt/vsscanf.h create mode 100644 libc/intrin/pabsb.c create mode 100644 libc/intrin/pabsb.h create mode 100644 libc/intrin/pabsd.c create mode 100644 libc/intrin/pabsd.h create mode 100644 libc/intrin/pabsw.c create mode 100644 libc/intrin/pabsw.h rename libc/{nexgen32e/tinystrncmp.ncabi.c => intrin/packssdw.c} (84%) create mode 100644 libc/intrin/packssdw.h rename test/libc/runtime/heapsortcar_test.c => libc/intrin/packsswb.c (78%) create mode 100644 libc/intrin/packusdw.c create mode 100644 libc/intrin/packusdw.h create mode 100644 libc/intrin/packuswb.c create mode 100644 libc/intrin/paddb.c create mode 100644 libc/intrin/paddb.h create mode 100644 libc/intrin/paddd.c create mode 100644 libc/intrin/paddd.h create mode 100644 libc/intrin/paddq.c create mode 100644 libc/intrin/paddq.h create mode 100644 libc/intrin/paddsb.c create mode 100644 libc/intrin/paddsb.h create mode 100644 libc/intrin/paddsw.c create mode 100644 libc/intrin/paddusb.c create mode 100644 libc/intrin/paddusb.h create mode 100644 libc/intrin/paddusw.c create mode 100644 libc/intrin/paddusw.h create mode 100644 libc/intrin/paddw.c create mode 100644 libc/intrin/palignr.c create mode 100644 libc/intrin/palignrs.S create mode 100644 libc/intrin/pand.c create mode 100644 libc/intrin/pand.h create mode 100644 libc/intrin/pandn.c create mode 100644 libc/intrin/pandn.h create mode 100644 libc/intrin/pavgb.c create mode 100644 libc/intrin/pavgb.h create mode 100644 libc/intrin/pavgw.c create mode 100644 libc/intrin/pavgw.h create mode 100644 libc/intrin/pcmpeqb.c create mode 100644 libc/intrin/pcmpeqb.h create mode 100644 libc/intrin/pcmpeqd.c create mode 100644 libc/intrin/pcmpeqd.h create mode 100644 libc/intrin/pcmpeqw.c create mode 100644 libc/intrin/pcmpeqw.h create mode 100644 libc/intrin/pcmpgtb.c create mode 100644 libc/intrin/pcmpgtb.h create mode 100644 libc/intrin/pcmpgtd.c create mode 100644 libc/intrin/pcmpgtd.h create mode 100644 libc/intrin/pcmpgtw.c create mode 100644 libc/intrin/pcmpgtw.h create mode 100644 libc/intrin/phaddd.c create mode 100644 libc/intrin/phaddd.h create mode 100644 libc/intrin/phsubd.c create mode 100644 libc/intrin/phsubd.h rename test/libc/intrin/paddw_test.c => libc/intrin/phsubsw.c (73%) create mode 100644 libc/intrin/phsubsw.h create mode 100644 libc/intrin/phsubw.c create mode 100644 libc/intrin/phsubw.h create mode 100644 libc/intrin/pmaddwd.c create mode 100644 libc/intrin/pmaddwd.h create mode 100644 libc/intrin/pmaxsw.c create mode 100644 libc/intrin/pmaxsw.h create mode 100644 libc/intrin/pmaxub.c create mode 100644 libc/intrin/pmaxub.h create mode 100644 libc/intrin/pminsw.c create mode 100644 libc/intrin/pminsw.h create mode 100644 libc/intrin/pminub.c create mode 100644 libc/intrin/pminub.h rename libc/{runtime/exit.c => intrin/pmovmskb.c} (90%) create mode 100644 libc/intrin/pmovmskb.h create mode 100644 libc/intrin/pmulhuw.c create mode 100644 libc/intrin/pmulhuw.h create mode 100644 libc/intrin/pmulhw.c create mode 100644 libc/intrin/pmulhw.h create mode 100644 libc/intrin/pmulld.c create mode 100644 libc/intrin/pmulld.h create mode 100644 libc/intrin/pmullw.c create mode 100644 libc/intrin/pmullw.h create mode 100644 libc/intrin/pmuludq.c create mode 100644 libc/intrin/pmuludq.h create mode 100644 libc/intrin/por.c create mode 100644 libc/intrin/por.h create mode 100644 libc/intrin/psadbw.c create mode 100644 libc/intrin/psadbw.h create mode 100644 libc/intrin/pshufb.c create mode 100644 libc/intrin/pshufb.h create mode 100644 libc/intrin/pshufd.c create mode 100644 libc/intrin/pshufd.h create mode 100644 libc/intrin/pshufhw.c create mode 100644 libc/intrin/pshufhw.h create mode 100644 libc/intrin/pshuflw.c create mode 100644 libc/intrin/pshuflw.h create mode 100644 libc/intrin/pshufw.c create mode 100644 libc/intrin/pshufw.h create mode 100644 libc/intrin/psignb.c create mode 100644 libc/intrin/psignb.h create mode 100644 libc/intrin/psignd.c create mode 100644 libc/intrin/psignd.h create mode 100644 libc/intrin/psignw.c create mode 100644 libc/intrin/psignw.h create mode 100644 libc/intrin/pslld.c create mode 100644 libc/intrin/pslld.h create mode 100644 libc/intrin/pslldq.c create mode 100644 libc/intrin/pslldq.h create mode 100644 libc/intrin/pslldqs.S create mode 100644 libc/intrin/pslldv.c create mode 100644 libc/intrin/psllq.c create mode 100644 libc/intrin/psllq.h create mode 100644 libc/intrin/psllqv.c create mode 100644 libc/intrin/psllw.c create mode 100644 libc/intrin/psllw.h create mode 100644 libc/intrin/psllwv.c create mode 100644 libc/intrin/psrad.c create mode 100644 libc/intrin/psrad.h create mode 100644 libc/intrin/psradv.c create mode 100644 libc/intrin/psraw.c rename test/libc/intrin/psraw_test.c => libc/intrin/psrawv.c (83%) create mode 100644 libc/intrin/psrld.c create mode 100644 libc/intrin/psrld.h create mode 100644 libc/intrin/psrldq.c create mode 100644 libc/intrin/psrldq.h create mode 100644 libc/intrin/psrldqs.S create mode 100644 libc/intrin/psrldv.c create mode 100644 libc/intrin/psrlq.c create mode 100644 libc/intrin/psrlq.h create mode 100644 libc/intrin/psrlqv.c create mode 100644 libc/intrin/psrlw.c create mode 100644 libc/intrin/psrlw.h create mode 100644 libc/intrin/psrlwv.c create mode 100644 libc/intrin/psubb.c create mode 100644 libc/intrin/psubb.h create mode 100644 libc/intrin/psubd.c create mode 100644 libc/intrin/psubd.h create mode 100644 libc/intrin/psubq.c create mode 100644 libc/intrin/psubq.h create mode 100644 libc/intrin/psubsb.c create mode 100644 libc/intrin/psubsb.h create mode 100644 libc/intrin/psubsw.c create mode 100644 libc/intrin/psubsw.h rename libc/{runtime/findmapping.c => intrin/psubusb.c} (78%) create mode 100644 libc/intrin/psubusb.h create mode 100644 libc/intrin/psubusw.c create mode 100644 libc/intrin/psubusw.h create mode 100644 libc/intrin/psubw.c create mode 100644 libc/intrin/psubw.h create mode 100644 libc/intrin/punpckhbw.c create mode 100644 libc/intrin/punpckhbw.h create mode 100644 libc/intrin/punpckhdq.c create mode 100644 libc/intrin/punpckhdq.h create mode 100644 libc/intrin/punpckhqdq.c create mode 100644 libc/intrin/punpckhqdq.h create mode 100644 libc/intrin/punpckhwd.c create mode 100644 libc/intrin/punpckhwd.h create mode 100644 libc/intrin/punpcklbw.c create mode 100644 libc/intrin/punpcklbw.h create mode 100644 libc/intrin/punpckldq.c create mode 100644 libc/intrin/punpckldq.h create mode 100644 libc/intrin/punpcklqdq.c create mode 100644 libc/intrin/punpcklqdq.h create mode 100644 libc/intrin/punpcklwd.c create mode 100644 libc/intrin/punpcklwd.h create mode 100644 libc/intrin/pxor.c create mode 100644 libc/intrin/pxor.h create mode 100644 libc/intrin/repstosb.h create mode 100644 libc/intrin/shufpd.c create mode 100644 libc/intrin/shufpd.h create mode 100644 libc/intrin/shufps.c create mode 100644 libc/intrin/shufps.h delete mode 100644 libc/intrin/vpalignr.c delete mode 100644 libc/leb128.h create mode 100644 libc/linux/exit.h create mode 100644 libc/linux/linux.mk create mode 100644 libc/linux/read.h create mode 100644 libc/linux/write.h create mode 100644 libc/log/addr2linepath.c rename libc/{runtime/mappings.c => log/gdbpath.c} (95%) rename libc/log/{logfile.c => logfile.initabi.c} (97%) rename libc/{runtime => mem}/balloc.c (68%) rename libc/{runtime => mem}/bfree.c (84%) delete mode 100644 libc/ncabi.h delete mode 100644 libc/nexgen32e/bsf.S delete mode 100644 libc/nexgen32e/bsfl.S delete mode 100644 libc/nexgen32e/bsr.S delete mode 100644 libc/nexgen32e/bsrl.S create mode 100644 libc/nexgen32e/crc32.h delete mode 100644 libc/nexgen32e/div10.greg.S rename libc/nexgen32e/{div1000000000int64.greg.S => div1000000000int64.S} (96%) rename libc/nexgen32e/{div1000000int64.greg.S => div1000000int64.S} (96%) create mode 100644 libc/nexgen32e/div10000int64.S rename libc/nexgen32e/{div1000int64.greg.S => div1000int64.S} (97%) create mode 100644 libc/nexgen32e/div100int64.S rename libc/nexgen32e/{div10int64.greg.S => div10int64.S} (96%) delete mode 100644 libc/nexgen32e/nontemporal.h rename libc/nexgen32e/{mod1000000000int64.greg.S => rem1000000000int64.S} (96%) rename libc/nexgen32e/{mod1000000int64.greg.S => rem1000000int64.S} (94%) create mode 100644 libc/nexgen32e/rem10000int64.S rename libc/nexgen32e/{mod1000int64.greg.S => rem1000int64.S} (94%) create mode 100644 libc/nexgen32e/rem100int64.S rename libc/nexgen32e/{mod10int64.greg.S => rem10int64.S} (95%) create mode 100644 libc/nexgen32e/slowcall.h create mode 100644 libc/nexgen32e/tinystrncmp.ncabi.S delete mode 100644 libc/nt/nt/systemthreads.h create mode 100644 libc/nt/thunk/accounting.inc create mode 100644 libc/nt/thunk/memory.inc rename libc/rand/{rand.ncabi.c => rand.c} (96%) create mode 100644 libc/rand/xorshift32.c create mode 100644 libc/rand/xorshift64.c create mode 100644 libc/runtime/arememoryintervalsok.c create mode 100644 libc/runtime/atexit.c create mode 100644 libc/runtime/carsort.h create mode 100644 libc/runtime/carsort100.c rename libc/runtime/{heapsortcar.c => carsort1000.c} (73%) create mode 100644 libc/runtime/construct.S delete mode 100644 libc/runtime/cxaatexit.S create mode 100644 libc/runtime/cxaatexit.c rename libc/runtime/{startmain.S => executive.S} (65%) create mode 100644 libc/runtime/exit.S create mode 100644 libc/runtime/findmemoryinterval.c create mode 100644 libc/runtime/hook.greg.c delete mode 100644 libc/runtime/mappings.h create mode 100644 libc/runtime/memtrack.c create mode 100644 libc/runtime/memtrack.h create mode 100644 libc/runtime/memtracknt.c delete mode 100644 libc/runtime/munmap.c create mode 100644 libc/runtime/printmemoryintervals.c create mode 100644 libc/runtime/relocate.c create mode 100644 libc/runtime/spawn.S delete mode 100644 libc/stdio/fputc.h rename libc/{nexgen32e => str}/getcachesize.c (100%) rename libc/{nexgen32e => str}/getx86processormodel.c (100%) rename libc/{nexgen32e => str}/insertionsort.greg.c (100%) rename libc/{nexgen32e => str}/knobs.c (100%) rename libc/{nexgen32e => str}/kx86processormodels.c (100%) rename libc/{nexgen32e => str}/lz4check.c (100%) rename libc/{nexgen32e/lz4cpy.c => str/lz4cpy.initabi.c} (100%) rename libc/{nexgen32e => str}/lz4decode.c (100%) rename libc/{nexgen32e => str}/memset16.c (100%) rename libc/{nexgen32e => str}/strcspn.c (100%) rename libc/{nexgen32e => str}/strcspn16.c (97%) rename libc/{nexgen32e => str}/strpbrk.c (100%) rename libc/{nexgen32e => str}/strpbrk16.c (95%) rename libc/{nexgen32e => str}/strspn.c (96%) rename libc/{nexgen32e => str}/strspn16.c (97%) create mode 100644 libc/str/tinymemccpy.c create mode 100644 libc/str/tinymemccpy.h delete mode 100644 libc/str/varint.h rename libc/{nexgen32e => str}/wcscspn.c (97%) rename libc/{nexgen32e => str}/wcspbrk.c (95%) rename libc/{nexgen32e => str}/wcsspn.c (97%) rename libc/{nexgen32e => str}/wmemset.c (100%) delete mode 100644 libc/stubs/exit11.S rename libc/{runtime/munmap-thunk.S => stubs/retpoline.S} (92%) rename libc/{sysv => stubs}/stackchkguard.S (85%) rename libc/{nexgen32e => stubs}/sysv2nt.S (100%) create mode 100644 libc/sysv/calls/__utimensat-sysv.s create mode 100644 libc/sysv/calls/futimens-sysv.s delete mode 100644 libc/sysv/calls/futimens.s create mode 100644 libc/sysv/calls/futimes-sysv.s delete mode 100644 libc/sysv/calls/futimes.s create mode 100644 libc/sysv/calls/futimesat-sysv.s delete mode 100644 libc/sysv/calls/futimesat.s delete mode 100644 libc/sysv/calls/utimensat.s delete mode 100644 libc/sysv/consts/GLOB_ABORTED.s delete mode 100644 libc/sysv/consts/GLOB_APPEND.s delete mode 100644 libc/sysv/consts/GLOB_DOOFFS.s delete mode 100644 libc/sysv/consts/GLOB_ERR.s delete mode 100644 libc/sysv/consts/GLOB_MARK.s delete mode 100644 libc/sysv/consts/GLOB_NOCHECK.s delete mode 100644 libc/sysv/consts/GLOB_NOESCAPE.s delete mode 100644 libc/sysv/consts/GLOB_NOMATCH.s delete mode 100644 libc/sysv/consts/GLOB_NOSORT.s delete mode 100644 libc/sysv/consts/GLOB_NOSPACE.s delete mode 100644 libc/sysv/consts/GLOB_NOSYS.s create mode 100644 libc/sysv/consts/utime.h create mode 100644 libc/testlib/clearxmmregisters.c create mode 100644 libc/time/futimens.c create mode 100644 libc/time/futimes.c create mode 100644 libc/time/futimesat.c create mode 100644 libc/time/struct/utimbuf.h delete mode 100644 libc/time/utime.h rename libc/{calls/copyfile.c => time/utimensat-nt.c} (56%) create mode 100644 libc/time/utimensat-sysv.c rename test/dsp/tty/rgb2xterm256_test.c => libc/time/utimensat-xnu.c (65%) create mode 100644 libc/time/utimensat.c create mode 100644 libc/zipos/get.c delete mode 100644 libc/zipos/zipcentraldir.S create mode 100644 libc/zipos/zipos.S rename test/libc/{runtime/mappings_test.c => bits/division_test.c} (55%) create mode 100644 test/libc/bits/popcnt_test.c create mode 100644 test/libc/conv/itoa64radix10_test.c create mode 100644 test/libc/conv/itoa64radix16_test.c create mode 100644 test/libc/intrin/intrin_test.c create mode 100644 test/libc/intrin/pshuf_test.c create mode 100644 test/libc/math/ilogb_test.c create mode 100644 test/libc/nexgen32e/kcp437_test.c rename test/libc/{str => nexgen32e}/memmove_test.c (87%) rename test/libc/{intrin/packuswb_test.c => nexgen32e/memset_test.c} (60%) create mode 100644 test/libc/nexgen32e/sidiv_test.c rename test/libc/nexgen32e/{strtolower_test.c => strcaseconv_test.c} (74%) delete mode 100644 test/libc/runtime/balloc_test.c create mode 100644 test/libc/runtime/carsort_test.c create mode 100644 test/libc/runtime/memtrack_test.c create mode 100644 test/libc/str/regex_test.c delete mode 100644 test/libc/str/varint_test.c rename test/libc/{conv/sizemultiply_test.c => tinymath/logb_test.c} (71%) create mode 100644 test/tool/build/lib/alu_test.c create mode 100644 test/tool/build/lib/bitscan_test.c create mode 100644 test/tool/build/lib/bsu_test.c create mode 100644 test/tool/build/lib/disinst_test.c create mode 100644 test/tool/build/lib/divmul_test.c create mode 100644 test/tool/build/lib/machine_test.c create mode 100644 test/tool/build/lib/modrm_test.c create mode 100644 test/tool/build/lib/numbers.c create mode 100644 test/tool/build/lib/numbers.h create mode 100644 test/tool/build/lib/optest.c create mode 100644 test/tool/build/lib/optest.h create mode 100644 test/tool/build/lib/pml4t_test.c create mode 100644 test/tool/build/lib/x87_test.c create mode 100644 test/tool/build/lib/xlaterrno_test.c create mode 100644 third_party/compiler_rt/divmodti4.c create mode 100644 third_party/ctags/COPYING create mode 100644 third_party/ctags/README.txt create mode 100644 third_party/ctags/ant.c create mode 100644 third_party/ctags/args.c create mode 100644 third_party/ctags/args.h create mode 100644 third_party/ctags/asm.c create mode 100644 third_party/ctags/asp.c create mode 100644 third_party/ctags/awk.c create mode 100644 third_party/ctags/basic.c create mode 100644 third_party/ctags/beta.c create mode 100644 third_party/ctags/c.c create mode 100644 third_party/ctags/cobol.c create mode 100644 third_party/ctags/config.h create mode 100644 third_party/ctags/ctags.h create mode 100644 third_party/ctags/ctags.mk create mode 100644 third_party/ctags/debug.h create mode 100644 third_party/ctags/dosbatch.c create mode 100644 third_party/ctags/eiffel.c create mode 100644 third_party/ctags/entry.c create mode 100644 third_party/ctags/entry.h create mode 100644 third_party/ctags/erlang.c create mode 100644 third_party/ctags/flex.c create mode 100644 third_party/ctags/fortran.c create mode 100644 third_party/ctags/general.h create mode 100644 third_party/ctags/get.c create mode 100644 third_party/ctags/get.h create mode 100644 third_party/ctags/go.c create mode 100644 third_party/ctags/html.c create mode 100644 third_party/ctags/jscript.c create mode 100644 third_party/ctags/keyword.c create mode 100644 third_party/ctags/keyword.h create mode 100644 third_party/ctags/lisp.c create mode 100644 third_party/ctags/lregex.c create mode 100644 third_party/ctags/lua.c create mode 100644 third_party/ctags/main.c create mode 100644 third_party/ctags/main.h create mode 100644 third_party/ctags/make.c create mode 100644 third_party/ctags/matlab.c create mode 100644 third_party/ctags/objc.c create mode 100644 third_party/ctags/ocaml.c create mode 100644 third_party/ctags/options.c create mode 100644 third_party/ctags/options.h create mode 100644 third_party/ctags/parse.c create mode 100644 third_party/ctags/parse.h create mode 100644 third_party/ctags/parsers.h create mode 100644 third_party/ctags/pascal.c create mode 100644 third_party/ctags/perl.c create mode 100644 third_party/ctags/php.c create mode 100644 third_party/ctags/python.c create mode 100644 third_party/ctags/read.c create mode 100644 third_party/ctags/read.h create mode 100644 third_party/ctags/readtags.c create mode 100644 third_party/ctags/readtags.h create mode 100644 third_party/ctags/rexx.c create mode 100644 third_party/ctags/routines.c create mode 100644 third_party/ctags/routines.h create mode 100644 third_party/ctags/ruby.c create mode 100644 third_party/ctags/scheme.c create mode 100644 third_party/ctags/sh.c create mode 100644 third_party/ctags/slang.c create mode 100644 third_party/ctags/sml.c create mode 100644 third_party/ctags/sort.c create mode 100644 third_party/ctags/sort.h create mode 100644 third_party/ctags/sql.c create mode 100644 third_party/ctags/strlist.c create mode 100644 third_party/ctags/strlist.h create mode 100644 third_party/ctags/tcl.c create mode 100644 third_party/ctags/tex.c create mode 100644 third_party/ctags/verilog.c create mode 100644 third_party/ctags/vhdl.c create mode 100644 third_party/ctags/vim.c create mode 100644 third_party/ctags/vstring.c create mode 100644 third_party/ctags/vstring.h create mode 100644 third_party/ctags/yacc.c create mode 100644 third_party/dlmalloc/initdlmalloc.S create mode 100644 third_party/getopt/initgetopt.S create mode 100644 third_party/xed/xederror.c create mode 100644 tool/build/emubin/emubin.mk create mode 100644 tool/build/emubin/metalsha256.c create mode 100644 tool/build/emubin/metalsha256.h create mode 100644 tool/build/emubin/mips.c create mode 100644 tool/build/emubin/pi.c create mode 100644 tool/build/emubin/prime.c create mode 100644 tool/build/emubin/sha256.c create mode 100644 tool/build/emucrt/emucrt.S create mode 100644 tool/build/emucrt/emucrt.lds create mode 100644 tool/build/emucrt/emucrt.mk create mode 100644 tool/build/emulator.c create mode 100644 tool/build/helpop.c create mode 100644 tool/build/lib/abp.h rename libc/log/showmappings.c => tool/build/lib/alu.c (59%) create mode 100644 tool/build/lib/alu.h create mode 100644 tool/build/lib/argv.c create mode 100644 tool/build/lib/bitscan.c create mode 100644 tool/build/lib/bitscan.h create mode 100644 tool/build/lib/breakpoint.c create mode 100644 tool/build/lib/breakpoint.h create mode 100644 tool/build/lib/bsu.c create mode 100644 tool/build/lib/buffer.c create mode 100644 tool/build/lib/buffer.h create mode 100644 tool/build/lib/case.h create mode 100644 tool/build/lib/cond.h create mode 100644 tool/build/lib/cpuid.c create mode 100644 tool/build/lib/cpuid.h create mode 100644 tool/build/lib/cvt.c create mode 100644 tool/build/lib/cvt.h create mode 100644 tool/build/lib/debug.c create mode 100644 tool/build/lib/dis.c create mode 100644 tool/build/lib/dis.h create mode 100644 tool/build/lib/disarg.c create mode 100644 tool/build/lib/diself.c create mode 100644 tool/build/lib/dishigh.c create mode 100644 tool/build/lib/disinst.c create mode 100644 tool/build/lib/disspec.c create mode 100644 tool/build/lib/divmul.c create mode 100644 tool/build/lib/divmul.h create mode 100644 tool/build/lib/endian.h create mode 100644 tool/build/lib/errnos.S create mode 100644 tool/build/lib/flags.h create mode 100644 tool/build/lib/fpu.c create mode 100644 tool/build/lib/fpu.h create mode 100644 tool/build/lib/initmachine.c create mode 100644 tool/build/lib/instruction.c create mode 100644 tool/build/lib/ioports.c create mode 100644 tool/build/lib/ioports.h create mode 100644 tool/build/lib/loader.c create mode 100644 tool/build/lib/loader.h create mode 100644 tool/build/lib/machine.c create mode 100644 tool/build/lib/machine.h create mode 100644 tool/build/lib/memory.c create mode 100644 tool/build/lib/memory.h create mode 100644 tool/build/lib/memorymalloc.c create mode 100644 tool/build/lib/message.c create mode 100644 tool/build/lib/modrm.c create mode 100644 tool/build/lib/modrm.h create mode 100644 tool/build/lib/panel.c create mode 100644 tool/build/lib/panel.h create mode 100644 tool/build/lib/pml4t.c create mode 100644 tool/build/lib/pml4t.h create mode 100644 tool/build/lib/pml4tfmt.c create mode 100644 tool/build/lib/reset.c create mode 100644 tool/build/lib/sse.c create mode 100644 tool/build/lib/sse.h create mode 100644 tool/build/lib/stack.c create mode 100644 tool/build/lib/stack.h rename libc/rand/rando.c => tool/build/lib/stats.c (95%) create mode 100644 tool/build/lib/stats.h create mode 100644 tool/build/lib/string.c create mode 100644 tool/build/lib/string.h create mode 100644 tool/build/lib/syscall.c create mode 100644 tool/build/lib/syscall.h create mode 100644 tool/build/lib/throw.c create mode 100644 tool/build/lib/throw.h create mode 100644 tool/build/lib/x87.c create mode 100644 tool/build/lib/x87.h create mode 100644 tool/build/lib/xlaterrno.c create mode 100644 tool/build/lib/xlaterrno.h create mode 100644 tool/build/tinyemu.c create mode 100644 tool/build/x86combos.c create mode 100644 tool/decode/x87.c create mode 100644 tool/emacs/cosmo-platform-constants.el create mode 100755 tool/scripts/distribute.sh rename test/libc/intrin/phaddsw_test.c => tool/viz/printpixel.c (70%) create mode 100644 usr/share/rom/mario.nes create mode 100644 usr/share/rom/tetris.nes create mode 100644 usr/share/rom/zelda.nes diff --git a/.clang-format b/.clang-format index 4c76b9c8..26899f6f 100644 --- a/.clang-format +++ b/.clang-format @@ -7,9 +7,10 @@ AlignConsecutiveDeclarations: false AlwaysBreakBeforeMultilineStrings: false AllowShortFunctionsOnASingleLine: false KeepEmptyLinesAtTheStartOfBlocks: true +ConstructorInitializerAllOnOneLineOrOnePerLine: true --- Language: Cpp -AllowShortFunctionsOnASingleLine: true +AllowShortFunctionsOnASingleLine: false --- Language: Proto ... diff --git a/Makefile b/Makefile index c5b9f6f1..534291f3 100644 --- a/Makefile +++ b/Makefile @@ -60,7 +60,7 @@ # # build/config.mk -SHELL = dash +SHELL = /bin/sh HOSTS ?= freebsd openbsd alpine .SUFFIXES: @@ -69,8 +69,7 @@ HOSTS ?= freebsd openbsd alpine .PHONY: all o bins check test depend tags all: o -o: o/libc \ - o/$(MODE)/ape \ +o: o/$(MODE)/ape \ o/$(MODE)/dsp \ o/$(MODE)/net \ o/$(MODE)/libc \ @@ -81,6 +80,7 @@ o: o/libc \ PKGS = +-include ~/.cosmo.mk #──No.1 include build/functions.mk #─┐ include build/definitions.mk # ├──meta include build/config.mk # │ @@ -89,6 +89,7 @@ include build/online.mk # │ include libc/stubs/stubs.mk #─┘ include libc/nexgen32e/nexgen32e.mk #─┐ include libc/intrin/intrin.mk # │ +include libc/linux/linux.mk # │ include libc/math/math.mk # ├──metal include libc/tinymath/tinymath.mk # │ include third_party/compiler_rt/compiler_rt.mk # │ @@ -106,6 +107,7 @@ include libc/fmt/fmt.mk # │ include libc/rand/rand.mk #─┘ include libc/calls/calls.mk #─┐ include libc/runtime/runtime.mk # ├──systems +include libc/crt/crt.mk # │ include libc/unicode/unicode.mk # │ include third_party/dlmalloc/dlmalloc.mk # │ include libc/mem/mem.mk # │ @@ -140,9 +142,9 @@ include third_party/editline/editline.mk include third_party/duktape/duktape.mk include third_party/regex/regex.mk include third_party/avir/avir.mk +include third_party/ctags/ctags.mk include third_party/third_party.mk include libc/testlib/testlib.mk -include libc/crt/crt.mk include tool/viz/lib/vizlib.mk include examples/examples.mk include third_party/lex/lex.mk @@ -150,6 +152,8 @@ include third_party/m4/m4.mk include third_party/lz4cli/lz4cli.mk include third_party/bzip2/bzip2.mk include tool/build/lib/buildlib.mk +include tool/build/emucrt/emucrt.mk +include tool/build/emubin/emubin.mk include tool/build/build.mk include tool/debug/debug.mk include tool/decode/lib/decodelib.mk @@ -213,7 +217,7 @@ depend: o/$(MODE)/depend tags: TAGS HTAGS o/$(MODE)/.x: - @$(MKDIR) $(dir $@) && touch $@ + @$(MKDIR) $(@D) && touch $@ o/$(MODE)/srcs.txt: o/$(MODE)/.x $(MAKEFILES) $(call uniq,$(foreach x,$(SRCS),$(dir $(x)))) $(file >$@) $(foreach x,$(SRCS),$(file >>$@,$(x))) @@ -236,6 +240,71 @@ loc: o/$(MODE)/tool/build/summy.com find -name \*.h -or -name \*.c -or -name \*.S | \ $(XARGS) wc -l | grep total | awk '{print $$1}' | $< +COSMOPOLITAN_OBJECTS = \ + APE_LIB \ + LIBC \ + LIBC_ALG \ + LIBC_BITS \ + LIBC_CALLS \ + LIBC_CALLS_HEFTY \ + LIBC_CONV \ + LIBC_CRYPTO \ + LIBC_DNS \ + LIBC_FMT \ + LIBC_ELF \ + LIBC_LOG \ + LIBC_MEM \ + LIBC_NEXGEN32E \ + LIBC_NT \ + LIBC_OHMYPLUS \ + LIBC_RAND \ + LIBC_RUNTIME \ + LIBC_SOCK \ + LIBC_STDIO \ + LIBC_STR \ + LIBC_STUBS \ + LIBC_SYSV \ + LIBC_TIME \ + LIBC_TINYMATH \ + LIBC_UNICODE \ + LIBC_ZIPOS \ + THIRD_PARTY_DLMALLOC \ + THIRD_PARTY_DTOA \ + THIRD_PARTY_GETOPT \ + THIRD_PARTY_MUSL \ + THIRD_PARTY_REGEX + +COSMOPOLITAN_HEADERS = \ + LIBC \ + LIBC_CALLS \ + LIBC_CONV \ + LIBC_CRYPTO \ + LIBC_DNS \ + LIBC_FMT \ + LIBC_MEM \ + LIBC_RAND \ + LIBC_RUNTIME \ + LIBC_SOCK \ + LIBC_STDIO \ + LIBC_STR \ + LIBC_TIME \ + LIBC_UNICODE \ + LIBC_ZIPOS \ + LIBC_SYSV \ + LIBC_NT \ + THIRD_PARTY_DLMALLOC \ + THIRD_PARTY_DTOA \ + THIRD_PARTY_GETOPT \ + THIRD_PARTY_MUSL \ + THIRD_PARTY_REGEX + +o/$(MODE)/cosmopolitan.a: $(filter-out o/libc/stubs/exit11.o,$(foreach x,$(COSMOPOLITAN_OBJECTS),$($(x)_OBJS))) +o/$(MODE)/.cosmopolitan.h: $(foreach x,$(COSMOPOLITAN_HEADERS),$($(x)_HDRS)) + build/rollup $^ >$@ +o/$(MODE)/cosmopolitan.h: o/$(MODE)/.cosmopolitan.h + build/compile $(PREPROCESS) -P $(OUTPUT_OPTION) $< + clang-format-10 -i $@ + # UNSPECIFIED PREREQUISITES TUTORIAL # # A build rule must exist for all files that make needs to consider in @@ -255,11 +324,12 @@ loc: o/$(MODE)/tool/build/summy.com # never get executed since they're not members of the transitive closure # of `make all`. In that case the build config could be improved. %.mk: +~/.cosmo.mk: $(SRCS): $(HDRS): .DEFAULT: @echo >&2 - @echo NOTE: deleting o/$(MODE)/depend due to unspecified prerequisite: $@ >&2 + @echo NOTE: deleting o/$(MODE)/depend because of an unspecified prerequisite: $@ >&2 @echo >&2 rm -f o/$(MODE)/depend diff --git a/ape/ape.S b/ape/ape.S index b92ed5fe..3f4e49eb 100644 --- a/ape/ape.S +++ b/ape/ape.S @@ -44,6 +44,7 @@ #include "libc/nexgen32e/uart.h" #include "libc/nexgen32e/vidya.h" #include "libc/nt/pedef.h" +#include "libc/nexgen32e/vidya.h" #include "libc/sysv/consts/prot.h" .source "NOTICE" @@ -136,43 +137,24 @@ ape.mz: .ascii "MZ" # Mark 'Zibo' Joseph Zbikowski .short 0 # MZ: OEM information .org 0x40-4 # MZ: bytes reserved for you .long RVA(ape.pe) # PE: the new technology - .endobj ape.mz,globl,hidden + .endfn ape.mz,globl,hidden / Disk Operating System Stub / @noreturn .org 0x40 # mz/elf header length stub: mov $0x40,%dl # *literally* dos jmp 1f # good bios skips here -1: jmp pc +1: jmp pc # thus avoiding heroics nop # system five bootpoint - .org 0x48,0x90 # ⌂ELF → JNLE 47 - jmp 3f -2: push %rdx # don't move or shell script breaks - xor %edx,%edx # Z in MZ ate BIOS drive letter :( -3: .byte 0xbd,0,0 # mov $0x????0000,%[e]bp - jmp pc - jmp ape.hop # already in userspace + .org 0x48,0x90 # note ⌂ELF means JG 47 + jmp 3f # MZ also means pop r10 +2: sub $8,%rsp # a.k.a. dec %ax sub %sp + xor %edx,%edx # MZ ate BIOS drive code +3: .byte 0xbd,0,0 # a.k.a. mov imm,%bp + jmp pc # real mode, is real + jmp _start # surprise it's unix .endfn stub -/ Mitigate incidental quotation marks. - .real -ape.hop:pop %rdx - push %r10 # MZ → pop %r10 w/ NexGen32e - .weak __imp_GetStartupInfoW - ezlea __imp_GetStartupInfoW,ax - test %rax,%rax - jz 0f - .weak KernelBase.GetStartupInfo - test %rax,%rax -/ TODO(jart) -/ cmpq $RVA(KernelBase.GetStartupInfo),(%rax) - jz 0f - jmp WinMain -0: .weak _start - jmp _start - .endfn ape.hop - .previous - /*─────────────────────────────────────────────────────────────────────────────╗ │ αcτµαlly pδrταblε εxεcµταblε § ibm personal computer │ ╚──────────────────────────────────────────────────────────────────────────────┘ @@ -835,9 +817,9 @@ ape.pe: .ascin "PE",4 .short v_ntsubsystem # Subsystem: 0=Neutral,2=GUI,3=Console .short .LDLLEXE # DllCharacteristics .quad 0x0000000000100000 # StackReserve - .quad 0x0000000000030000 # StackCommit (64kb [goog] + arg + env) + .quad 0x0000000000100000 # StackCommit .quad 0x0000000000080000 # HeapReserve - .quad 0x0000000000001000 # HeapCommit (we make our own heap) + .quad 0x0000000000001000 # HeapCommit .long 0x00000000 # LoaderFlags .long 16 # NumberOfDirectoryEntries .long 0,0 # ExportsDirectory @@ -1933,5 +1915,10 @@ __data_start: .type __piro_start,@object .hidden __piro_start + .type __ubsan_data_start,@object + .type __ubsan_data_end,@object + .type __ubsan_types_start,@object + .type __ubsan_types_end,@object + .end  \ No newline at end of file diff --git a/ape/ape.lds b/ape/ape.lds index d864c7e1..d8857c56 100644 --- a/ape/ape.lds +++ b/ape/ape.lds @@ -435,36 +435,36 @@ SECTIONS { /*END: linux addressability guarantee */ /*END: xnu addressability guarantee */ - .stab 0 : { *(.stab) } - .stabstr 0 : { *(.stabstr) } - .stab.excl 0 : { *(.stab.excl) } - .stab.exclstr 0 : { *(.stab.exclstr) } - .stab.index 0 : { *(.stab.index) } - .stab.indexstr 0 : { *(.stab.indexstr) } - .comment 0 : { *(.comment) } - .debug 0 : { *(.debug) } - .line 0 : { *(.line) } - .debug_srcinfo 0 : { *(.debug_srcinfo) } - .debug_sfnames 0 : { *(.debug_sfnames) } - .debug_aranges 0 : { *(.debug_aranges) } - .debug_pubnames 0 : { *(.debug_pubnames) } - .debug_info 0 : { *(.debug_info .gnu.linkonce.wi.*) } - .debug_abbrev 0 : { *(.debug_abbrev) } - .debug_line 0 : { *(.debug_line .debug_line.* .debug_line_end ) } - .debug_frame 0 : { *(.debug_frame) } - .debug_str 0 : { *(.debug_str) } - .debug_loc 0 : { *(.debug_loc) } - .debug_macinfo 0 : { *(.debug_macinfo) } - .debug_weaknames 0 : { *(.debug_weaknames) } - .debug_funcnames 0 : { *(.debug_funcnames) } - .debug_typenames 0 : { *(.debug_typenames) } - .debug_varnames 0 : { *(.debug_varnames) } - .debug_pubtypes 0 : { *(.debug_pubtypes) } - .debug_ranges 0 : { *(.debug_ranges) } - .debug_macro 0 : { *(.debug_macro) } - .debug_addr 0 : { *(.debug_addr) } - .gnu.attributes 0 : { KEEP(*(.gnu.attributes)) } - .GCC.command.line 0 : { *(.GCC.command.line) } + .stab 0 : { *(.stab) } + .stabstr 0 : { *(.stabstr) } + .stab.excl 0 : { *(.stab.excl) } + .stab.exclstr 0 : { *(.stab.exclstr) } + .stab.index 0 : { *(.stab.index) } + .stab.indexstr 0 : { *(.stab.indexstr) } + .comment 0 : { *(.comment) } + .debug 0 : { *(.debug) } + .line 0 : { *(.line) } + .debug_srcinfo 0 : { *(.debug_srcinfo) } + .debug_sfnames 0 : { *(.debug_sfnames) } + .debug_aranges 0 : { *(.debug_aranges) } + .debug_pubnames 0 : { *(.debug_pubnames) } + .debug_info 0 : { *(.debug_info .gnu.linkonce.wi.*) } + .debug_abbrev 0 : { *(.debug_abbrev) } + .debug_line 0 : { *(.debug_line .debug_line.* .debug_line_end ) } + .debug_frame 0 : { *(.debug_frame) } + .debug_str 0 : { *(.debug_str) } + .debug_loc 0 : { *(.debug_loc) } + .debug_macinfo 0 : { *(.debug_macinfo) } + .debug_weaknames 0 : { *(.debug_weaknames) } + .debug_funcnames 0 : { *(.debug_funcnames) } + .debug_typenames 0 : { *(.debug_typenames) } + .debug_varnames 0 : { *(.debug_varnames) } + .debug_pubtypes 0 : { *(.debug_pubtypes) } + .debug_ranges 0 : { *(.debug_ranges) } + .debug_macro 0 : { *(.debug_macro) } + .debug_addr 0 : { *(.debug_addr) } + .gnu.attributes 0 : { KEEP(*(.gnu.attributes)) } + .GCC.command.line 0 : { *(.GCC.command.line) } /DISCARD/ : { *(.discard) diff --git a/ape/ape.mk b/ape/ape.mk index 06fc9d5c..fdf6e144 100644 --- a/ape/ape.mk +++ b/ape/ape.mk @@ -21,8 +21,7 @@ APE = $(APE_DEPS) \ APELINK = \ ACTION=LINK.ape \ - $(MKDIR) \ - $(dir $@) && \ + $(MKDIR) $(@D) && \ $(LINK) \ $(LINKARGS) \ $(OUTPUT_OPTION) && \ @@ -32,18 +31,6 @@ APELINK = \ $(ZFLAGS) \ -f $@.map -APECOPY = \ - ACTION=OBJCOPY.ape \ - TARGET=$@ \ - build/do \ - $(OBJCOPY) \ - -SO binary \ - $< \ - $@ - -DEFAULT_COPTS += -mno-red-zone -DEFAULT_LDFLAGS += -z max-page-size=0x1000 - APE_FILES := $(wildcard ape/*.*) APE_HDRS = $(filter %.h,$(APE_FILES)) APE_SRCS = $(filter %.S,$(APE_FILES)) @@ -51,9 +38,6 @@ APE_OBJS = $(APE_SRCS:%.S=o/$(MODE)/%.o) APE_DEPS = $(APE_LIB) APE_CHECKS = $(APE_HDRS:%=o/%.ok) -o/%.com: o/%.com.dbg - @$(APECOPY) - o/ape/idata.inc: \ ape/idata.h \ ape/relocations.h diff --git a/ape/idata.h b/ape/idata.h index 096f383f..89ba3192 100644 --- a/ape/idata.h +++ b/ape/idata.h @@ -32,39 +32,35 @@ / @see libc/nt/master.sh / @see ape/ape.lds / @see winimp -.macro .imp dll:req fn:req actual hint +.macro .imp dll:req fn:req actual:req hint .dll \dll - .section .piro.data.sort.iat.2.\dll\().2.\fn,"aw",@progbits + .section .piro.data.sort.iat.2.\dll\().2.\actual,"aw",@progbits .type \fn,@object .align __SIZEOF_POINTER__ -\fn: .quad RVA((.L\dll\().\fn)) +\fn: .quad RVA((\dll\().\actual)) .size \fn,.-\fn .globl \fn .hidden \fn .previous - .section .idata.ro.ilt.\dll\().2.\fn,"a",@progbits -.Lidata.ilt.\dll\().\fn: - .quad RVA((.L\dll\().\fn)) - .type .Lidata.ilt..L\dll\().\fn,@object - .size .Lidata.ilt..L\dll\().\fn,.-.Lidata.ilt.\dll\().\fn + .section .idata.ro.ilt.\dll\().2.\actual,"a",@progbits +.Lidata.ilt.\dll\().\actual: + .quad RVA((\dll\().\actual)) + .type .Lidata.ilt.\dll\().\actual,@object + .size .Lidata.ilt.\dll\().\actual,.-.Lidata.ilt.\dll\().\actual .previous - .section .idata.ro.hnt.\dll\().2.\fn,"a",@progbits -.L\dll\().\fn: + .section .idata.ro.hnt.\dll\().2.\actual,"a",@progbits +\dll\().\actual: .ifnb \hint # hint i.e. guess function ordinal .short \hint .else .short 0 .endif - .ifnb \actual # name .asciz "\actual" - .else - .asciz "\fn" - .endif .align 2 # documented requirement -/ .globl .L\dll\().\fn -/ .hidden .L\dll\().\fn - .type .L\dll\().\fn,@object - .size .L\dll\().\fn,.-.L\dll\().\fn + .globl \dll\().\actual + .hidden \dll\().\actual + .type \dll\().\actual,@object + .size \dll\().\actual,.-\dll\().\actual .previous .endm diff --git a/ape/lib/pc.h b/ape/lib/pc.h index cc4b38bd..b8a4fc63 100644 --- a/ape/lib/pc.h +++ b/ape/lib/pc.h @@ -52,11 +52,13 @@ SF: Stack Fault ────────────────┐││││││ ES: Exception Summary Status ──┐│││││││ C0-3: Condition Codes ──┬────┐ ││││││││ - Top of Stack Pointer ─────┐ │ ││││││││ + TOP of Stack Pointer ─────┐ │ ││││││││ B: FPU Busy ───────────┐│ │ │ ││││││││ ││┌┴┐┌┼┐││││││││ │↓│ │↓↓↓││││││││*/ +#define FPU_IE 0b0000000000100000000000001 #define FPU_ZE 0b0000000000100000000000100 +#define FPU_SF 0b0000000000000000001000000 #define FPU_C0 0b0000000000000000100000000 #define FPU_C1 0b0000000000000001000000000 #define FPU_C2 0b0000000000000010000000000 @@ -151,6 +153,7 @@ #define PAGE_V /* */ 0b000000001 #define PAGE_RW /* */ 0b000000010 #define PAGE_U /* */ 0b000000100 +#define PAGE_4KB /* */ 0b010000000 #define PAGE_2MB /* */ 0b110000000 #define PAGE_1GB /* */ 0b110000000 #define PAGE_TA 0b11111111111111111111111111111111111111000000000000 diff --git a/build/archive b/build/archive index 663b6bf7..1ba83e1d 100755 --- a/build/archive +++ b/build/archive @@ -48,6 +48,7 @@ for x; do done set -- "$AR" "$ARFLAGS" "$OUT" "$@" +printf %s\\n "$*" >"$OUT.cmd" OUTDIR="${OUT%/*}" if [ "$OUTDIR" != "$OUT" ] && [ ! -d "$OUTDIR" ]; then diff --git a/build/bootstrap/mkdeps.com b/build/bootstrap/mkdeps.com index 00b8bc39a36b47a5feadb288811c9597f773d68a..946514027cf6ec78f57d8cd5d55d409aac24f214 100755 GIT binary patch literal 49152 zcmc${3wTpi);E6ACMAWI1Zc4;&=d+%6>X3z5vmO&a85aaaw(KMTByiFN(h63~E6vX^Xu8LR%D2yc9$@A99|G($^ z!qap1b?vqHT6^ua*53QznemG;D_q%kbQJ`v&?ZEF2Em*Y4bm%j$_xbVWm{Gm{Yu&QbBs7M3UkauNj(g)GMe-(Y_^6@2 zp}w@rH?F|ZyU18);~S7e@6eVj~Cfy+J?-x-RoSjV#(u$k9Z$nzHFs! z_6VDI;eusd=g+r2UWAIv``eaB-;b`$9&TH?`0*9l{cZFcW*^qyR=C`^%o|Ox4S#?V zmaWQOw%l9nTUKZr`p~`e7p%H>nQzIGt_zl+#RaPu6xtpb`4Es;-ap#ZisHwYd5dg! z%(6f5fLk6jYv?RH{$<+n_mh7^DgMCycy{L9m-)b~%%P}}iJYJL8)`?o9rb=D?|+mQ z#el;{+Z?0JVG($gcQf2OY!3~3plh^~gNAf1dOQ-LAxD>U`gfg_o&Ipw$k?Y(vJF}A z3!8oD{kGY*L4!aI(8;&#k);c4LzZUrx20vvvgDVx72)DD&5tfQXFIq0T;XXROzbK|+Y}HiT;Y3s zzOa1B{FRHBFJB_ywXA5luweMmOkvsbr3;qgxn%jW$ApComMmG`RY(wo(VK@0$6S#q zRAfnwhA6JKW9W^LZ&^KH?{m%uDq$!rj|2T-rRuhjhmwxh44Q)h-p)`Zo-_offjG9c8d^cxMS}_ zy@zV$ogLtbliAg0zI@}EFUx}76jnPmuN>muTmYerx3NGQEX)aiHFI_~yU051P0_9e zy*>O@rX4xo+L|wK_Fmy#tInG^iCv;(8y63$&aN*K3O=&#Fl-mzvAr!IWZfS7p70NZ>_N1L8vbvszhzh0X?Esyc4(1dM!5Ub zH_C#(tL)O8j7tQOHL~{7lS1j~($cCerAI8NzQxh*%_!;l#>lSTo+A^zJzwA6<#j?& zV{HIx_$qvfwP8(by_#`!M))*Pp31)L>~i;Lm(2pJodc}HHLXqTO6duq^qbOW4rN@N zm~n-57!G29`gOVd7v8CI0gK5;$;G#=zv{{JC6ya^)PCGvU*2W%KMZfjew3^kx&c?M zub}!?T(o8*Iimd2a3E>Fi=?G^Ax>OR8Yh!i{2Lmn^0sw zR+LxtN1o=W^gdXW7K!fxs-;D&t!S*}&qa>5HGL39h>~qZY3)Uhc3-?bIJ>(153X?6 z@^@TeQ~3+7u%NyABN{4NnCJ9No?Lm}xT&_tt{2&l7nR$~g(=&v#Q zLq=bCdbkmUZ|`RhZVHNdPrN z2-@Ak_BT(@*9HpP8~E$gygV&M*xrap51}9-=JY&F>Fxi4Xp9hk_0;^WF~0U{ybRv< z>Z`=M{mO+Un~oLL#tE6R&2a(+Y_ScIfHgK42ershR33}|+gs4lA`=NDe~p<~^?WLd z+lC4#Ra7oqh!ZURkD&(^L%+DZ>`O`5$aEQtP77wnjWT6Pyf09OivHn%fWL2d2=0)o~6 z*|=vn1g|n|pa2NkfvdU>kt7HZN{F68G&IC42%?%i2rn%ck5C^xHgkCmqy#Q*@s9j* zqHnZFzwMFNwRbd`>c!oZpA#v#Jz6085;jMYsRQ(z_>FfyuNR5_x6e*6)EW9)0o|8= zE4t-G-;okDFX-3#MhE#1y)2~w#i>5wJz>RyWuqp$5aOdyO7A?kAk5zoBP{MYe-_2* zcR?5s(=Dbu)avWM0k+O?P9#9hQfd0 ze&_ffYX7bS>}#OjV`gxkdkKL~RBlC@h`+gW^PFSdGu)FVAac{sc2n#>ix8Zb@Hcnv z6nDW*_ES!z%1?0YIv_AFkCcGlmu07|@eRSo@Yq6h! z7tD#|mR>J(BfQS>>db`_$LHWjFAO@Xo(=rE^G!0Q9Dg({cjE8*FXwvj|6-`3=l`tT z%|-WEQp-4E|7Su`c{Ia0iH1WE13_MgO-cqW?*=U@0R&%4Dm^j*0&26n&pJo@e;N;aHJX%Z>rEW%K}P z{aziCOj>_RxS;EzGTJQ0#)<j)SekVi++4T8PB1xb00Y)*vnprbzxZ2D9KbIrXaWx6b#8)g^UeiraCx*ia23 zeFNg;JEu$*mD^-xky&C5F6B|HODRrrv16=Kuaia;Xxg6-mW^jSdY|Gc-pk0myFkC(a&X5Mo+P zy+L`S?*S$IO9VuAdeK*l^ykNzpO3RQEwVaiD5e&qTTF_H@+~G-TS2WWrlUN%X9FVI z8wxa>AXopNIX<&!^MUs7!=!@xzcuI2QJ-pCWS(vhUVa_Q&73w%w{R=6W>?pkUVabj zNZ2X{bkX8Iu2zv@3;&#q^J4X;Ru2hC%FN@pk zL4Umtj=3bhB{4nJ9avHS_6QI*722CQg2fK6&>}+tGv(ZA5SZzHJVn)Hp%*h*@tV9y zSk!wVq+{tq(4}-ymqjs)3=(UD*Q(y~=WS8H66RGm=Yztku;{O~hA(WMx$DgDa)ft! z*n@9utb7A}EN<(l_qBI?p?+S0}B8`;vu=1XhoV)XTp{0|G>G<4cf@z#Ij z2;n;y^7bo!guNr#*Lm0jfcsfC;$Q2151QkFFY?7Z$}lpOc^@?|D}c zSe_B{&;+|eWMWEfj*ZI$HSr&JBSlTF&J-{x?suRah!z*bUtT6C-5qcM?$B9qZSwSM zQ1*QGQz&gPh5;pqL(z9!hcXyY>QEg}SXTofMF`z<4V1V2r=a>r5Q5VK2<^B56d|-0 zFv=fUoC8W}v7f{^S1Mx=@7i-;hn6yH;kEmo#qy#nlSu|h| zY7eJVJGCNn*NJM@8YF5zKz?Fr0owgg6y_#;zS{4>aV~a7X6Ixzs~;8nOCLP8KtT}| zQx*!^kIU*jvsC$QtmNN|zf(WR?2PvN`w713RDU!&qHiL~S^Svsg1ERkL5wveAi0MYhWXAmBOFkc#{OagA>0jI^Z z{B2sMx;vFM=?Pj807>0())>h{_tl6Q~4qcx%HBb9)svyjmGZ!l&8Nct7;-j6s zqU-*|#e)ojp?o+kC3$#=aJ3}lP1LQ^3q*g=96rApkcg+*C2hkGy3T5~3z(kyi&~~n zn>txk`^x=jEJs6S`gX9W3|ad-2&mZSp)8EHF0wv9+Dwbh=)r7s4jWydf3oPG(~*;c z!1ol$)IY&f1nmaToC7U|caX3A>%VP7e_YCt=e7%i)Blapmno|6z^kb%ec#4B(3T+^ z{MAzlq#zOM7F?Nv4Kw&*)^{@sgdeV;ab_RbAt+}mF+JmXn>Dn*VR z_;O#+R~ebEwIo3gYKw7NUii>$dG|YU{a%g&(>Pr^k%Ocm8KX^kj7sa??wC=`{jEmk zv*P}i*w8t9b~RYuQg#P0$X87DZ$+4J+E#N^vGL9p3+JY|Ebo=X;j!E(7fxFt7d|X2 zk6C49?mSr;3-LM^<2iSstW2zsuo-|gjGm86I0*UX}$ZjCu21H$LYfQa%wEwFYyPkS5_4lA9u54=^HJ&1fT zgr%>ilD+9oL8vkHLXttNLow}--*<{FSsiG8TIe*fz~_m-e}fh-PqX%?z6hwMKO?&= z=&`u=%FL~43R0nZ{EDYa7T3oUWyRABA*v!pR@@rII!51#9k&nZ6hoI%{ zjR*8-dq*lcPWK`O!*~KsdeP+cIn*RAJ)I+k&fdPBI;-|QfH$=SY+o`!aE1FJu4F$4 zNZMCCY$`>#2GQR`VZgYo-$vtr9)He9$4{xycL3*x(OHUAj7}3zy>WE*Atj3KV8F%b zOhXEdPQC4>(NRpR(GW8|3JJbQFQ`^bD|md;PvWDl#TWAYq1WQ`c)b6$cma?1{7F3K zT0D>EUjh?H+Ka?bM&k#tV5@>5LL%=S7&Dua=tF{@JmFWQr!GZUR1z2QBo~snQtZ!O zNR>J(OyRlr@CtIIg2a=0MJqsi+(|w{njKN?kxb3?--^wE$O6GsC^0NhhnJpw4&szr z2?Tu-AtcsTf}xS|Vk9F)R@H`esGue<#cWsv0F};im|aA2sO}XgR=^kh8G7=G=Q~mU z)&|+cOxs_>8kT$($r9VEC&v>^z`K{=z0>%x4pZ(7weBn@*WNDuVJi5cb{O4tvc2el z5-Dt`e6~kflQ*%neU>k_#&EBw2g6@GE$_L7qHO(WNg@mEPStgI_pv-$0GeW?2!2ou z`Uj~=bU{}04~hoFc(&Ufl+-CEw71`W+*$GCI;?gdP(8RsfUh9wL1}%P4v-~1We3cP zW1?kLWiBiGkOt1Bs8rVJzp_p$tXu`V`;fL4jiKbq1W~Wt=+wnZ&FnjGc`xQ)QH01m zs-;D6144!>cc9+)hREvVjALRpp(0tv*3^`l2yG!WUkCC499IMO&@1JPPs=+{MpoZx z01o2%E0Nd-oUoR$aG7wt%L#eeGB#LKmdF7S#bg%Btu6bIDx#Bs@g`A2Pkv|h`X-V%eaf;?1vgh3G}uB>{tUo&yA@dGqPk*(b>vqSI+ zZGouV3h7lYGIvm$16=)U>jZBf#a)L*A=%$%E14%*Uaeg@1Ld`ckRLupYW*Q?Ho_ik zXhB-NP~yVh!Gna<{ybGHMv?>v(s!OflgyfZ zDUyCb^8cWFe^oIH5U7h!P%TaLw;5L5-kFGTdP#c(89??WtadTsubic)4Bp-q{iC&9 z9PmC%nHwYIEbTBjxo1oAeVaj`(T{p zgNZs|V3aQcV*x(aBFrQZ6Me6H6*C*dtz-|fLXlnkc?x+4*Y@hR6;%r%3E$ zxZf{hf)4itPI;}7{R(VbR9hh)!(q%LsI0)1a^qdJS)ud_n~2Iy6S30lmK2YWM?ytU zZhD5dmx&2yQrx@4N%r6ZC28P7^upi1)N9h7_tCIi7ZW18=s$=R@*B*I=2(oM8nD4- zAhRlM2%?xHCV!nC&VtTlC7H(#$!wq2lV^FqfhT_#jgZU^YF}afak3!EB~;P|p&@em zkt0W9EtrEOPtle$AM?;T1?{z=s#c5X2tkKYAD?Hh-v!nXoI6WlKRWq&g9-$pqG4WW zP7J|}$PD;!x)(zVCs3`{fqdvi0HIw3R$;UD26zxr>ZOY{N1RL0@vn9B0x`DwB6UZT z&^mbpZXxxh1@&*}!F<4p_JH=AqO|%Rv^q?*g8cKs6TK!KE+xp3y;gXb!e=?Ls85vL z&8@i7&61Lo{-2O*@`h{V%J8qLD*@|a3t6_EiW&?x` ztGl(Sve~@;WGPAx6!2VMm4mwR4QyZK$*z^{sq0S)TPR+7!n#Yy!BS!OcNn}q?D|f@ zhEeQ@vA7Shec>*9gn8tni*MgaUTuFzY=wZI#eFb-ABheC9G#*m`M^h6+rV8(Y;vCV zL?j`IgaVOGo~J!d32gL2#r^qCz(IKO3MId}0%ibQzoUl}aMHCan{8rzgOdL(3TGghv!$X7widcF_l}54dW+1CXl+HDDScgwYC$EMehZcS9oUYX zF1{ogD*f&E`nuG*caX+}9hg`#`wT)QnJ6QwBjZKGSL@sLqe$sm#BPazg-Z6N_Ui~( z?`z^qqMN=WzK!9NfkZ4#;}ykHB|C z*1zH9%|PH?3fu=BJYIVzQY=nXpSUxej-+1NpRXs~6Yhbee%kYqBy62Lku^hPZPPp5 zkCD@sBD+E}Mo%_9-8pfi8H%ZWJyh1Gz|Mr~wbXAZb>Ypaoj{HuFEabD75|8eJ8mwn zm<~}YoR>F(=4c#5#!V1-YTeJK?)qt}V%meG&?lhmO(_3~Quo}P+KKXuKLx@YeCDUA zoyAxEG(#~hCg_jc3|%oTrBvrnQ|D4@*3GG%pjRTVzFPN*x-Uof4cC2{x^J-Vv+2GR z-Dh=9bmn1ySYnttS44J+Q>~%C`T*SgEYToTD^tymX=cv4B{8=KtCil42qUjETV4rT z-VL@MtIo$lV-N0100JN>l#%D-NxjJ4jMvJ6;^GSlqM^h8kl;;{9YJq`w73olhqQ}W z3SkG6eBBSt>(`j`9EQwlWM&$T%Gx$~X=cN5*E<3*m0oJ%YAQ*xC!`=he^&}MtY-C~ z+{mUMw3%+a>{?BG7R_KX(vDL(39kx{r^VGKvh35askCtb5=Gwh1rji=Mz}9PBDSp@ z7e!v_4v(V)U3v%kvXcBRUNqs<30R(M$(Xmi)h0HcN4Mm(L*imRK{g!4o}}-D){UA+ z7q@Al+oMc)30QXSyTLetJ5t06S$*>R2I>gbGd=l_ripfP8uwe^+{WsLfGb)?0q+~WAmH^ixw z7_mErVu!YhG!n)1B4sPtzs1YmJ1yj2BsWEl52{{Wuwk9b}J8^ z(cMYh^qYB}n=5AJVnKXMsgw)~k`J zb?UbO;OKx_8i#aAflZn{_zeP@+;cI#vU=kMFKBf)!uMfqO>XDDV`>P1)_0H+unOCf z$)5sLx8&mlP+1$){xlZKf#L(=ty&>i3~!OuXx~G%g;DSdpfdL>nnXDYaiV)OG=e(W zZ~?)MX!i+}pt>g>0YKU{uC$chNh7FG$^4DnmjDJXmDOPeIpY$Q{h9~|+xrOaMjh;*5$D|j|G zE{|jr56cQqkrkfr+ZO7|i=EMa(S@jJDU)epVRLvtRCc1j=0!bl;Od(=5bT#Pt3y6` z9O_PFBcENKB(t+3`;xXAJ^+)EZoSCgww%0L#XcLbr}-r^6Rr{n;lqd*MB=}qc(tf> z1J9Wr0YtDu-`7C|HL(w3@TJoWjFE~L%4GG$8=Uc4d?!aFo+0v>L;xZx^eP=Lc)KHE zY$UR{d)PCEJ49VJhP5i^Ls~#4bqGMq?7}A(^P{K24p!ZYsfF0Z>35-R9@d ze0{{)+S^v<5Ti~nrD$+aSy@0eJ)T!e`9Mvg<0tD zA!J?K)WE(hHw@IiFTqa&YjH80K=+XWaK~&8jLb~4>K-JN`b*{5F5|BMx-Bk3ToRZ1AKO@G5j^5(p_}adgG8)vo==L>u|JU z4<;G|b(5R0n?eHGSmkoqe0g+80w}+NRF`Gj;NlTzdmSP)ooKO@3O_{3Ql#K?7@e&6 zJA%q;BgADLLfUUph8$ud_*btE|F0-}cJAn)>r*k7W6-+xM5MnsN{~fs4h_L$k)&SJ z$&665h9Q~u-}8_>gSMi$K7Z3e6>j-i{n>xVJQI9fYfXPcfC%i*{vA)UqBl<=1kSPq zUPXpjn7je;T9cRZDFKR)GUXG&~|rV4qaspMIby*F+GU49&|ePGSj^XK4pbPq_{pLi9+)y zJQyo*2l$k5xXYj29chC1c_730an!5>3()d1l7=k#2+HXu4bQM?=Ywc`2CJKX69>UZ z(SYKT@s}Ers{xX$5U;eTdTo{rcc~@Av)Pi7zr~U-4?Mnw2u&-9qNr|X?F7|0PuEs*7PX?5ssdc*J7g4H`bV{ zkZ7=!4aO*eaaQl$qx;)pErAro;j2v=I1iPTrzr~8faSelx2O9coUO;hp5Jf@nGjL! zUjxl8&4hs|_6N)?#YU5B)@68m;Q5NNw!C#>-QNj3VL!J6^eMvU%pE1%!3c;jCL6S(U2E@Ns~X4o=NG) zBk8{JHKxzMjv(+o-rC}u5jci4J^Alw5C|MXgd=bpM<5u9`uCaim9Ly^yLGU7}S=E&G_8ih`Eial1Q8wIDF+HJT2(?(aS~i*{AnjR`gr}@5 zh|f=5R7Xi34*A6L;4*Y;XhO*`(~{hknQTaR!ut(Q(zF)|D%_OZUltX7 zSUNbT1$5s?4DGR#e1}H{>r4J0b?=i%`xt4t%1DjQiuKZJfC7N~;s|Vbrqi%j(mx8r zA$`ifE)K8I5?famnc););!5U{`@XRI{j9Q0z9&&B*msH6mk!*_O(@Vucq!us%e z!xu^Cl(96xk1=_o29AM{J_f&W959pE7vg}vA3q1$IIjA7fu7lq5=)%XzMMrQ^=9L| z-;c=lEkRydG_Q*1`EUk&;E(e_@H?sUKop3H7Wh-N09H}45(NeT%6kOmE)?2_D%8T7 zNFnfX_J9*$qM=4X_=kb|+z0Ty&}Gl;dKXLqXEj{E$F+u$d_-u7eDDT;7fy#uloYS^ z7%Ffiww=AEjRl5qLfLU2T-;~0Y)ComDMt2)B4^A&#SR6kvuQBUQL;`V9ADGT)DtmQ zX%9YoOKE$t&)m%vLPq$3()JRcF}^N*Pigxj2wVscEN#DwvYRM7rL=uDMc$!Ex6<|n zzPN6tmk<&4+Oj6!KR69;kg5Owh3Sj`nJ|S}@H4`6XbcyoaB0Dx1xf0ipo~cpm8mH? z8%M?6E>gNyc&9Y#8GD z2^5OUvAkOwHY2J*52Nd=TzSGiYE)Mw->mc5e4(t#QuYE$D5KHvIP8CZxG~9~th6Dn zj@R{ITAeb^%0?$)hpt!7v-iPfK+8ED2YoY2+q?Rpn@vW`P59CP`$}&LCt&B!Np9VV z014&?v-s>3*GPE01ao#e!*ts~N&Arm2JQ!nzkozIMVaaLA3&)Dr(P4HbhL65Z_aU=aDt;f-Vk40VS0& zXnXb2d_4%_$x0j>V=u|`-#Q_Q9`2UfFRw7|63DY>u@&%MikdiY? zBeq1YExtZ*4=w1~bdI6c-A1FN?H&`6UT{B0r23@_GEa#Srr_2xjvI4H&JAqua8{6j zCD#L@Na!>)KhwJ~x+t9K#IAu+9ANTnMC}o#Wq5hPT4%H`KGyY_*h(Vh6xRmUnRUwx z!D_tuZ6d)UjWt&*V-skMOG1Z zZ`VL0^LDu`S;{{oUOunAOY4GO=Q~<496gMfMZbzi8t;UqY&}xxFxp^G;5*3u53tNa zl%3ZCon>lh-~WtOh&beScnYjT2q`2vYhCGl(b9T=ytIvy0u@2O6&KlKoEwelTH#e) zA7ILTh7Z9?yjT^s#zvS*5mb_A;AwGzU#6tPqZHlbp`&_y-jQ^lh(nS#oPSJF)?uLY zbZtDkaGm|`0p{U5(2oc=_JE-q2B#7?0y7dH0&!#&wUETE0VV4@&^dG-W;$<#uO*%1 zDX{~57nS87z|kAf&kC21a|G(H>>&Ya3c<0uEbopqgmY*_eh0$y30v#q+R6iDanpp| zime{+2ULW%HnAN=GYng5-pSZe!gYh{a4wGS#z-UFLUB4a%m4>Nj_vSP?F#gHIH+*C z^^68t0yPU;2YuX@idxqQ_W?=4Iq1;_*T#{ziLBZZn2HjV25Hh&a?BQ18EcHUVT5kn zzpRG8QJIi5VuICsk5ZDLPBneI)xht{huEcWaTFCdc7hv?U6m3W7IH{Vns{t%Qh1*W z+XB3@)iEK-dv|ALTck48WOdIPV{qRdWYQ}EMz2e&WD~6615_1!+e8!RhVu*9K=Jl( zF3&6j}B zr-1U-jBkU3a%Nz5gj|Kn)Fr1!m68=fk_kh9{WsEMu?U%4cJ2x6}xu-BwG6qN{$Pw`p9b1MJEm;Ny>u~ z;3>l{YPTR;GV~08E@m`@-@{300)ic!$dusRdL%lY9<~?j5#5@DH^6OWo&z|vxLsny zaifCxNrX>VY(0ioxqs~e!&5~lRSwS{RFF&XJeAYYYgru`D;ByBD6S1{Vn(HC*bldl zQ~x9o{N*4NlUWBA9mJii5}q~PU55ir)V&iaI&ykQKV)Qu1xetuEzpqsV+g>KQt+ zRdJ_W?;yioDxHtweca*>d}k?JNaQG_bl-LlJL6*ca8QOgQ~E zOBuDO4$!*Z5D4O=)k_$a&1koLT{m~2$r8u_u<#sb=}Fuj#J;X!-L2>wJEqsE#Rd-> z*%0v!g#Jont+=gi8 z;jYtOAXgpwg!8W;(-|ODhy7hnFz(>Qv4Xu~)VhVB44p=KbP(RmwY-zOKOLRmEgQ#S zki#!qb_c=SDL7UV_|9u|T6TY;jX(!6RdE z@6KpszB&*4;qV#Oh(ln9!_LFs#a|6QPqe&hummU&I!N4q6Jphtw=Q)OH*%p6dlLa{ z^?S;6P)F%b#kk%XYhoqRo`Q*ajl0Kmn7`cKCC+hcvAa}r2W?qXLkj8<~bgO#TeCprDK zG1@lr)Orq}ymKZEH#=B)Hl{KZQ+c`{rc(SR=WLc=7upL1-0E@=C4cK^CE+y}Te@A$ z_)K&(uE}ub5qweg)Ln0Y4mo(X&=!rBO0=ZC{#2}D5 z?Hv0@fEuvcNR=7lqqh%avUmf+b(QWwg@VN-WZ5fS+n^|Lg&EfN4H@5ab=zpOkJ5qTv zrt(Bg<+*;1XF-IH_>2Bav6ZJ&<1aOa2!Heo@TnJ0CFxgrPIasf(qR@uR(!Pxnuup> zo@}@zW*p^1uBp&Q&<}Je1tW(7)V~N>_)uiDvPNeOda{5Y@nN6p&SQHP|Ch`xwJq zFr%JU+3+!a;NZ2e`Ul?0pLg@;J^Y!8C!bHy{-UCij<8(aW5YO+BSt$wiU9Ns77KmB zg9T`gtS+tNcXPR}M*DS_|0=VK6;y@v;{qU!aG`M&N`lhJo^$T-4}Z+ch^E1f8gkxB8ncxTuZGm957Rq=iyVh?6oPVu9C$ z!MKP5`BevBsnaYfrss|TO0BE6Igk3l#%5kyEP&5@QH|bBeR& zL%6(_B8DqUW_(47##R}QLk^-k*Xln>h_oK}e~qR5Gg0kx3qZ;^e~F8BkPf20&l0es zhODeGOa3!$xoX0QTy@F=+cB>#PbVQqazLq&l^m<@773~(jhR3J^6v?u20U==1emSN z6dlKChk`Dvk4nKMPixqP18^_}#&MMb4xG_8QV8xriVe57`Gz@0CU>x?OhS<+T+~(a zY}oEZu_j-$R7=tkx=xFCi82{IYLwKZk448H3V;^uLUK|)O<_7eJQ)yIa9i?TG>yop z%3X!XNEXBrF;_);BnY`!?$N3r3k3>(9dstNZ%U25D5no{c-%|(U z_i@WWVz2NhoeT=+W3iHy7kGlVTchUuz=3+sj1M90WT<7h*2{3%bCg;1H@C@-HVixX z(NK>AK@a0v9ai6ARW!6FIAX&`Mb-!AAdGP~6QrAIrAw%zK*$2bX5c*y`vB#yma z%J>qe{-9rI9Rq;}9FDhe6xf%tp;dd_Myy~76wxPW)$4a;7J<1On}m{DHS+AldhfduOxKZdt95v zHY!{cqT$F^$kY`WJXz@?XVh+u^`&{#`BocLO05y>MtF^cZ%xj4l{hq%Nnt~k=y=j* zDZ|Dz*c5y)Hbun!e7bjlVJk_;bvdi#fN>R_B5AQ9oK(TU=YW_w))3d&9knQyNppGv z$nOLzhShcBJRn-s0s$&hb>S{pJ@=i@1j!Y1jaieA^${> z97nKO97`=di5@iYMz6J&bW3Or3Q3r!z!2kKN#u)AtBq4jRB%_rQF9};jv)hhgC;Ai zq5Twb)Bs^lt?@TeYYeD0zLQ#GI;k~2LM>Q1|0T7+P@G!c#V%$1bxOVcbCeqY-%x6> zz^y*0fM<^Ag1s_);N75}AJVPN{j+0 z?N0={tQc!Z-NCt|8WQLuVRhloE?b8luHyoI4D}f?Y%`Jqg4?n{jSF;0Z<0Oh^=33j zj*6+=RE`xWOXMBIw+0bV$)ZH@JgZ*9QN zkf9QT?@|*!LQM$vHB};xT0>so+GUPacGUX1S^ja5Wxa*!VSHa@H5h-a72lA-rZg)s z=lW%{*$_I@o#tWkuTe;Hd}i_cpBO-HO5eEC{;WIAP_^^xBw&GxXZCV?vZ2dH? z&a%4>2&f}@`yO>xrHEZeNg0jy=UF|DuYKJQpKNVPJHoQ=CKTd5@Sdc2SRn?c1IGb| ziy2>v{*Kg=WG6m6o?h zkXn}qp)!w`#S#E@3xKf%%(Gz{de~`^?UU6?GdVtfez4N0gR_bzlP^M;Re zl${`S>H7t_z99fLj!n5}CZ!Gw2b<9;`*Q(&B4oeI@{cntdrd8d3KC^Bmnf6D?B{x% zuZ~nY2$G0IfpUpj5~fv2Z#v7vKT?!O>@^sa+?ar2Qu~1Q@WG+|SQhq0XjlM-oNS)J zjIrJ^AS5>22ctdWa*z;ixGSQQpG$HcKH&-+^mTW|<2b<;mUXg*2pCx}NvcE$NK!^- zrjL**v%X*A6|$gVOt*E=0W8nFPLm8XOUeEl@@Re$RbQf1c1!}J;hv(@`e`7@YP@%3 zlT4~e#R!(%Zuua{WfD|-|Ao|yN;p~$pY@+KsKbh7H8~%^*adr&q?B=fpmo{{3Dc44 z?Zz%~$qY;(i%XZ^d-(ffg&{yoP`cHyApfl}T*^NkhALh112VU}^z12z3ASKcu z15LS#5dsGs;){hvL=9M>V&Vo!xEyj8j=nI`j}g@PO5b?CYO{-nn=wVJEM@P3=wR9g z&a_ZvIg+sc@R^PqIJBNP6envNkjfbd!%IZnhvt2sph*yC<%i@&c?|G?wFJw>Ka;{> zVo_NEChpViUzylVy^&SY7n*2uIl{#EA|=AaaJztsoj+ybQJqX|OeZcT4f-*cl0#g~ z@%O0nt}*e+C==s~<Ud&eBUo%7RH;rrjwldUNKlgGfC!UR>(`8E95TDjWBv5E2PZ!M^?y7;^8plB(0E1 z6LD6p8vNME8$Nu-$?}FvwK;=n5ZM)kY!nJ~u%o_ylKMh>WPyaa#X~IXIaEd;B5Pzj zET0`b18d~j$Qp?at^MNRuSqWByftw!7{O~NMj@l;ER3Ei^lIc0vf^R%bcEpxF~W(x zBGUt{7k}2!FU`a?Q@O;2a>1oA3Zwu%zD$M+sDOVzoM6d%6@re>is!?C?*`p^YT~1fP4v8%B6DsLH1l=|Mh**`tMj`(B&B^!%!}g%*=0z}RgHJxoZHHk&?AeN$&RmAK29dgtX>#)bxL}( z2>C}4_}Po(oU|F>iFQO=D$Tz8N>3QP_ehSa;tY?YaaFo( zfWPU8%y0rO`A6b5C3yn@Gn~}ecW^vEV?ty^;uA>Yz4!$J1i+x-HwB8#M?>y4a)dWz+;sBD}+Qf&q_|D&%ol8swSC1 zja#8A%NYl`mb5=sax_V7y4lxXjLrIP4}mgu@K@GX^zt7B_pdOADrj^@Bp*kbF9St) zMT+j{FC6L5dX1OfhtgnwHY_vr9BRnQB>1b)HvCoaEA|ErJnUn>E&N#Z{nkZ%(Ihi?@BsnjkP|L}!jTq0&a z>Ze4U0&q)Io;L?{)(-Ix^#yu(DjFc4mmXC{F8fxAG?0q!(`i)axILw!TL*rN(53y_p0KkJ>U+t;ml-)cTuCKGs@`&rJ zfs8HO8H}z1(;|9|;|N(wH?9J(l#WI%rCPF-jQCp(8boF-xxPquBc+?HByK6fQi?1A z621`_n!}mW?r~fyj+bM9VWvQ@PM6YY5zZ-M0!ueZ9rHG1b<_zXP1nlCbXt zf$;a;5%IbmQbBTj=&_7CtL?;9010O+rCPTcTfvxqS+PMXw${pTtX=*qCYOPyxQ9bT zod$P9nx4U{)wy<2s{A zY!q|_p8!TlP6BqO@f|)nTK%;U7@DIga5U26bIIR z#yP4a{|T^JQzS@I|JqG_onfVC38BlnJ>bz_2ay;IJ;1Kmc^dcRnt_M_76SPQqMumSDpsw)O@% zft-YuMzo*N_qUivp?46bYebIh2|&YAMluyPC>;!l2-Aq45W+u?~=2C9CmME?3E8II2^wMyW z>fu?PPQyGdSK;>uf*+Fbk2noS!tRD#g}FJDYm<@1hn$A}p;w+s*;{7L4lxZLXE+(Lrke28dV*fUUj#j$Lf3FKb_tcE zdc@l}6%@ppAB@tx4ega+X(MmrksD~<4G=;n2pKjNtXpgieM}L@ksCdZ*J(Z#G$)rH zr}@-Qnv=&7G>6CWzoa<^kk(I_sxD;?(R6WNVWF)MMU z=hV9>K%z8TM;}OnHX-FE2^u6nB$uG?QQ=Mr`Y*mny3~LhUjL1yg@aE&YiZ2|Y%H~% zE(-cC-!*q+{dITbK_{jTmfEs9E=3QHW4n0>HXuhX>XD?yb_dV6!P-L8o$GAG+9C`4 zU)R|qH+dwnxVV?2VH5miXENo#0iI0AT{)=lWjRIHMms}1dZXs;iNX>tVn1SSXWd_%#ir6 z)0E>%fjSI=vO&s#HG09aAr(bDiVwwR4Sxum!`O<(?vt2SJziZi)Fx-N0uvmR(Vl?F z=BK~^9SLy4W#UerG{TjhBC5H~67!g`gOtpTFA8wuYo&<}Tc${{khzLX{w$_khBe_JP^S{_G7wPK? zBs!bCOP$KZ;gGhpUoJbIrP+P+emRpbMQ$A`YL2-EM1d~>^HCKq9ts{DX&EMHE=Z87q8LR(%?{B z20woHT{a93rm#8{I~XS~Nb0H!kSzAMaeoW+7BnMm)?&Eh$Z8McjK_H`OgAPu1(d1a zxmp|y5E(A6kayI77JC?H@u{*;w51uOJGE*P(VD2ZwHebRAFRZ;1b?*ypnYrBXD_Ws z0Gc`E03FT5&-PG{BzouEkf3@d-X0W1$aHsWX7pXJQ6KaeHb>tZeui?tYMmC&4(! zDHfb_A{97MjLk#YC(+ej;o-QSV1_GFjK^m+>SOpmjbFx>l|g9&z_S`7Speskdz%f~ z(qS+3e=fa0@_j#m|NIx z2zS?dZ-dk`Y4_pejjO~l zcw)rC57;o(chO>G*D=$$)ZrXE&CP0~w>F&YBl=JqoHaPKt(fl22iN7(v28#$GIzNb zx9;d_jkvy=G%WllX>_bf9k~82@=RKNOX->hx!zYGL4wN)KO5S|XpVO)v-V;m644Zie>WD8@Jvzl6T&!NC{|vv zV&~2ZqHkBHn00oP<=1Ix7#G5FC|&2np-P;>bSmDxX2-=fU7RE2y$Mfsb6i@Jgu51d z6V@eaSGPi@7HXksXv^vU$m(BPjywLc;t4AG2Rwzf7?o)A0{}SmGP;E$rEoRkRvq*b z8=GB+_RF>7cc594AixgImb-AK4L4AFNQJH9;+7P+VsEKL3FRTWV7}x|I^tEoNY|cu zPuJ%HT?vFM@Qwgz`n23@)CYVNg4N^!5f8g4v15yH;t3yAZMSKg%dv>_Iv-B=B_UNC z3@F++zyPBr0SUTr8TrY%)nWgld(83 zo*=2-gf^|;0A8>J1toQN!aN8pN!^i9fOoX4cC|mk5zY~HlLsZ;3}Y--hC$KYdl#bU zD0RwxxQ>ER7I8%N=1YXyC;!gx8sQWzt$#Se+<9_41%l&8%QN&n7fex089rr#hHz69 zuS(sDuSaNaQ1F};M|Z(E>Y4|1?H&}u020+76LB1keDWxz4ExmKJAku*UkK|ah=n*x zc*fddO2xGYqc1t*h+ZQWwuqIdt=b=ft~0(SJWzWC14*}01JE2yU&1JITU_76S9MJL z@Ex4F4e}^E7JdQe{Z_8PXig$*)WmUk$&U6_W5N6%lC}$F_D6cl(E1~Ti(TzuX00j> z<(1^yQ9f7Q9ZzGMtL`w+J6crxH!2cqYTHIhSawb45Q@k;NFLWuP6D!T%0*8O=XjHL zH|~4QrUSk*2z(26oMP}X8^_3+b#d5rHH5**RpC<*yYyv+mjD&4_3Xuz*hOOXu0a0P+pf@q@V zK=13TF&Qjn&tM$#E4U5?V$+8|XnoOM1J$sm^pOhp(tqWHF8`DK&uVhN4SO(!hU2>8 zgDs{Nc|t5x9yGDtK)f}n{6$s{vjMkf#Q zARt%`$qX4tX5!2_3A9p%PG~ud=}lYhwO-qDd+ohmtM}4gifD^W2oM5R16m%UhG3P6 z#|NN>kScS(YtKvwYTG}4pWo;H!-32`d!PMWd+oLNUVH7eASoR}Zh@PNx-c`pfw)8y zr$PtATm;}8#f#E$GP7X9_wb@5s5gHT;ZS(;SP=JRC@DxZ)PN2j8S4M-Ek%)WVtl$*^rEHpsi^|zWn@!O0u!tEI=KQhShdg zk_OScuhS`s_Mb+j01XMy2u(*HL4XQ(tI*~ES2cix3d}Xog&zcRj0bgx+9-6@!h68E z&TCDh>x7Y6?n?l@&z}|k02a%K za4^6FgZW^ws(j`qSv60hM6ksFjWERJFMG4Lq9afTU%%)sP~3xL=0RT~9N z-~sZj*1FHBILejmqvy+`WrtSNqR28b9N)kt1FC8?DUXe$R835ENH_@zL1m&> z5K-ehP-6^PeW-@^WkArFj4n{^HjZlDL>2mo5uCU1AUR_3!XVu40_svMh8F$~zOF(2 zJvQ`4A7PkA@B%NK zw2q5r|2O;cUKDgyU#>wZ=*uM|sC5`3+E=00i#*`(qSj|%0#o%vyD)Nq7mI#*HC|8` zLc-zzqG7|`4Tml|>i>)Uc?Vh~?DmD<3bwU-Cl~r3P8@=oRU8U+;H!2s7(3djwjR0% zum3JWLfvR8Xw!BgZ_$y7EzqC&%tV*0F%gWIeHs4*wtiR4zHm&Ui>7>-8mlmvY3!WD z{RH%h#7)lNg00SApo+EYOE*S(fRN(;@aaMQlUBkbEXvN}A}$XdX!}_>JOaCa;yEc} zj4MO=zrcb77ALM`+6&Gxjq^ylY@#vk`bD^tL(89ULBITjot(G)Zsigv-hP7K0}KC! zoVA~1;}l7W%Rx_3j!|*8lK056>zZ492**sdT0ru z4}Xqz8|_OhyO{f9D~5dd1>a8}2SU@H2m7+MN9(&B9hO}>mLMgt1l%y%WkJ!q5`<&v zGvjWB|7q^mv&!^a;^xEuIQQSg?v?Ik_rkx0`;*yz=`CSD{Oh=X46{mBW`)0j`vu|E z^iKq%5H>91e%SexbWDfu7Vb|Hj--Dk6ybX=_a_P`(k}{T_-97avv?^Ute&u8GW@(p zBL*Y+R9vF&C;IR*UC~~4swwSM=sU^X<_=e_i)ph?22b{%nq|dq_hiqh^KYGOz5dj! zdq;dNllp>MTpy;L4u{Lh{(xc522>yv1l=Wh8d%*hx`0L20c&Q^KeVV7;uS;5E!RUE ze@8av8tolufY=4W8+LNT4;=yX4}CAEI#lx`=;QyGg23R(rbqfV_)a$c@SeKyK1*PO zudm4mMQC4*#C-r+M9ctQgU0)C-VC}yVz21#(9UpA~?)GDEj|(0U z{|Mqd0V^PtXZ-g2eFJ^L)>WXan3=IO$6sGqtLO{%1))f~!^jmb_%AO~KWS~S{H(oH zFv5Dk#bHbMfPZgx;g+g#gq6zMR^cGzwic;}TMHo(i)+Yo3M^P9qrsipdk0K6cg)6R zG1A=-iG{LFIkGV;MTeM{BY4n(HRU@s-)KTUeVlx$i8-FEWbzSD#I;-F4o zaDS?-g9>cObVA`%7Y^A7!bKyn9q|8kQEM*6?+YS@oUERpZ?m+UkTgE#y-gbnZ<2gn z+td><=IQ;E-%`7bRRK0eAqy7qASr#IC>nNezVIRcFiuL; zUi1&n9K@F%)^`}g#}MU=b_#}qeDB6#)`1|{jia)`ISJ1uvgWynDSlt%o_R$5N(575 z`z2iT1JH0m2Rfz7F%~WA?gzQUwNKg9%? ztjR+q>CjvG`({)YTe`MWHucx*=v~~|FSdOTEwn7yO#v`$uz<%h?iA!E7)FVvu%w^2 z1b$C3Ea{gm$*+Lp9^k6BEP=-;6w&GCw*M%zrtgQCl}&w)Qd-loo{H$X4Df7`BSp8R zAG4*Ov_vXoQF&NoH5|Afm&JchKvg%}@QY%2sbhF)%5_HnUvS*?q0!fmW0pZwd@EP{ z1zjl6k`fFhg4>4ATQ)C7+SlH@cuf7h1#f07@o^4Y@<^-^f!$O+llt=lI5^G;ExwKp z=egISMp~xwmT83n=q`og3l0kKQ6deFHeW4y!~|(g@j2XWc;DAwfD<}sI5i);IuGA& zst%KH1nWYlOfahQY@vt(*_3I$u|WnbO;f-Umc4skPC zQZdtY_Ht4D+(;)i6&Qv)8mumS|-P}PJYX#`{dyYWb0w8{| zsCCSNxNKv1KUA`6@A2=wlzgHw?=5dE0*gclYVQtqripFDnep$YzKItf-8X|4yU`E{nWM{B5 ze#8s11G&VJOe7>U&^IT&@h^y7zw2z3*D9&eW z)_j%7QS2-?%08YM(0`1h60CorIzdUacJCu=w$SW2e=_!SFD)o&17jcog+M`)0frH< z4hp|cnnXXugo!nYb_zc^JzI5__U{P!S@NA5r{>=x=WR^Om*m^?rz`XPJs?bf)!xp( zO_}NMiPtXVXUk*rACzy%pQTI!Q&j86brJ1NUyn~aj_>}SB<=mLRX8o}R#HR)oZ-O~%bOv$Y`=M0K3VWck; z+G}`C?p-^W0`dKJ`R-or-JC(+@U2SjKQ7as%Q!z)v}PmsI1RGaLpg(6(=6@l1mEx) z`M!-HMG0 z1f_9pwxYEUNNye@7JcrQ>)hp&4!(i_PA&>j(Q;I|1|S$>n)_B0kb8z0PH zg`t4(jdSyh#kO;J%U^~yY1u@qfOet3Aqw>j1scT7=aB#%zsCUo!29sz*UK9=j?J$V z+ep2_z!3^^h;6^2_)dA%0PVE$J!0D$xErVBH_N&C4~cCLP^dd%{Q+!zxpd%h3Q^>F z`RgJ@QfO|zS8TfzANc7raAV|C!Si#BFQlRjZM?syz~4^-1E6?Zn>bQJ2TvdP>fpJb zPQ)2X1voBPjLAdc@DdPLs3up>qY+q9`}au$x1a!E^dcB4A|&w#F^itYELslccTLi% znOdeXP!qpt_`gwHt4L^-7D20;_^a{iEv8;rN~PlVAZ-(RpiSZybY)<}AEA}oq?YU;H5u*t2WYtWf^7{Bp(#+Z13FE1O+!rR zbk2^9o_}1FC6LC`ltcJg?Lpm9;~J`b`^^tgUBj>4w_HmWUm8MqWUz1k1baeSEs@sl zuj(-%B~%xq#~C!M(y>-0wEBRM6UDf^N8;J;$C}ERyiW^}mcOr%CrUpUJ>4qdYHVQE z^#e>}X@L?bPsDjr$u70z$t~vR6{x70unXe&w1t8**cE@k{5;H%nUDJ4>4QEj-(jDY zhfuuAIr)o}X?ws1K)e17e%Uzjr43JlZNPoX4QSNB=h#i>i^_X@6vWa_V6D1~$S?BS z2eVtdm|Wr8@cbg&LZ65Zxb>kuv{VK&Uzr<9#Pv6A9=?kF+qf&=CWcOq#W(PR^h3?D z6mmT0qHG5a<6OeuFX2F98&r;~8kK6+$5XRWE-2RMwJwH(2MKls;=7|<@aW4u_ zgsM!8-pirql8DnyRy+3bMrk9Bx_eZkN)M2=j&A>k$HA=JCr|MYZ-hP*WDzS%ONa8R zBiI{)i$4QjM}KS8Z>c`0rRMZ-b0;AyVxd76#T2P4z}*LY>Fxbq5rk1Puy$bA*&6oYRB%-KKiIbJ{z zN9hj&ldFAxKS=qD4C4ZfoDjA-7;5@IA?#|Qus?&P(*EltWCbmFQ5HQ`<3p%es0<8< zCD>r}>|y9Y;|q1jq(nWvhG8)jSokpVGO7nck30!wH=BbG$QhtNp&L=4!@rv{qynFy z%cboT9>hR`;jFzH=1qMiJdW~Riq_coTMev<{JVFIMSt+6Q~dd>V^<>P7Ogk zqzknJs)x|oG-Qr7_6l7WPpEqBKVgh1 zyzEj}K%aXj#^i@MugwM|Iztk)1H-(c=zW)`^1Rk%RJ(APYoJ4PV<)!co$pLGC7;mV zhyd#^=R&lhiZ&G%u0s|Oic#MGMlbhtEvZmJvi#wVEz`u^ufACFs{$6V-E%=5?)*5x zcVf@O1gfGINVwh<~m}Y8ki(?tvK35Z<7LZ%-Au$7f+PWDKrn?`! zuq73(6TvaP9^y(uOS+>wk)3dCI>g@8tnJ@pQaHRYjKyV35|L_9^eeW57H)cxD*RF($f_I;S?y8O-(>0 z(zcFI#KtQXLn!x%tZBOK0*OZC1Ra6Y#SX{_e3J}$^g4K+RRuP> zu$e_EzI9=yfC1E1&1VC`Js7y*y({jVq#oJSA4H!1PM?yZ?nPhk96w1L7~JcV>(wK^ z^I>s!55M-lxyQ(79MN>xsGiL1!c}Qw=0TI%6YjyK`Se5WsX8NUkj`nh2wUS5!2<%< z2TZ3+J|)$EdQs<@gyg;ILG2{4f`nnj2##QE!V{-X95L$z+Pt|Zf_*?@9HTF(5Fdk( z)@4Eb=V04jd7?i&we>pxtG)xeAn(h9E#DC^KVb+ah3K3D>^rcB^NvA>Pa25f4bc5h zmR4}bNg0TuUwhdl-=*r-+jp%bVD9V*XZ&ehuz>BT?b@-bYlqx5mbq4@bnR&A+TrNh z;py5jt!u|ET{~8G?XY)|9$o*?#?~<}WOo;^!FVA}xzT?*NSc6hJNw7_&n9-Bot*q? zd;AadE3>n+yDBYz_|waghqOkau~Tf~?qf#(g+;~}AlSsrnMYvKwm`Xq>;;|DJm80t zv@^Kd?_ko@2}j%Gf2>~0>@=#h2cl4916AEQVXjFG@E<&B^bgIbz`|Ig@CcPEeG z@09jmcCQ>4+<$rFN)f!}L#-MGOgv2ilTPpdOU)_G5>4t|g=A@z+TWS>iHKyUVRe0_ zc$z1pfQjcQVA44esjjcI1fUgR)-x!mdr~qfn*VIwerwc zYUB{l#%d(~*U*2Ss7yjatr#^>`ccP^@4~9i{&$xnI zc@psdkb*5>yhx;>atW-}zdahfe<oa;Fw7uK~USuFpKMO#7d(VIS})Y&d{lD^7~F_zv(skjF7H z-&2BJbS7kw!Dy4Ta$7R$qBiy$SK-;w#*=@4>{xbqNT2?eXKz`?EjkTj^DlEIiH z&nNH&=?B3<33*DM!~4H*r|thxK7qd@`#;oP+yBM!L*gHM*D}tzx!z7ZN+e`C8r>150VkrXYzjNL(4uAe)hD}{iJ0A&2 z@#p^_5^$kQ9kR9Svu4oI=fz$KJJI>RE(>55kINp0H;oXE;x%syyUl_ssgqKK@f0nK8(IKiD58cOyk0) z%%B`1MgbHJ9hI|aZACLCOe99Yc@u#%HTQ&)MCU|#hhU#3^8}eDp$5W??I#4ZR%De}GU?KP(D28P@!wfPJccL8<6bhgCAGi_M)f z{b}LF0t6{IftddcQdsH^(g{S~D=+vEESisKm0b59X$GB%$e@}`DX;{_Zs3uRS<(-Y z^1sLSiaU?CkAI9lV{3QmMF}tde!c&aUcSCyusi-mqLoS_vpjI_hwF~GJk|XvtlU^-sVuhI%&ftEm*+0# zvfJw=MB+gak4JLP$(UJEW-@0oX=O?>Yjm!vo8yw_L|(kIvdSBKU+-<1!`yS&oO*l9 z9IssOR%BFRw%pXrT3L$0i+A^3l!?L1X5)V)n_Y+h^w-=xYW5PR7BHu)u2HGCOVa=$ zdZ;PR#zy<<+D6IUEIZvUY1S;sP?y5LL_<9GhZHBEBh|X3TF+{w$?lS4(QaIk-NbHO zk(aX|r-|7;9=8W6=^udze@*13u!Rd*`a)J^t|(#kO4BM<*I=((D>-VNjrMw8lwo?~ zI>eN?JJaCJOrc!oEtr=_+25YMASW9IM6-_M4J4As81dtGQFhMU+vm-{U@R;$ znTx3|Ha3%Lo5>b2v%xzDJ-5(MPmard7hV?IWm~ORHdBYf4}dp#nGlTpi#r|rr#lz> z7e5sHr~mxQzmBoIbz@V|Rv*=H8@p@9f*JN1ju~|`9-J{vI$=0$IB6)HQ7~i8jI}fJ zXS`_GXLu|_dPn-yX^*D=&hSTr#M0SK(sY&qL%K8AWN9YL zWVf>0SQe0BHk-q;Sq_`aZfEn@e0B%BljX8Jb{AW~^4UUmH(M+fFe58uMa;y^te7ok z7D!6m18E8yD`BOqjFqz`tb$cC40X1Y)v#r3IlGUoU@O`E>|5*s_8_Zez+Uv1ojKTQ z*1()>4O`0^Src_hem z8)oO(5%vr?M0;4;^k>;__DA-w>^JNW>_>(l8-8MlGh`TQ4XX@G4b_G^!xBS{;eNwP z!wN&1VZ33UATMTW6M+_YXpW(N`*i=VdtCRQx*fVH(+=o9R}4M*F&2SG}iJs&KEedt|RvrMO)7^(iS;4NkAr>~XL5)HX?Y1=Y4o zUbjPTt@YR!NbB8-R0lHUvDZ7jvd6hfk?oRG2IZ=s_)D&L_`-zEq0HWsOj8T&e3dUf3*o$YFaNjUA!6%?q*c1p;o54w}L=RtL&2EwL6r? zED3E&sj*gBKtm*B>2j&YSW#gtty;bSG35r(8p+;bkL2TQYHoC*x&V%+)+Mh;Wl~Da z6-5@rHx^oL)~e+az*20jDm7PDO2uUrl2Ix*R#aJws%^#!sl2+PysXkZTdG7mslk9x z%HM#GgTvQjm+I{@=(-mzU5?)HqDGDNQbTQv9lcU#cebEDwNf2AJ_ee<4fK@SMz?D< zZx@Qc((?->r$chN-Nl1 zn46QAduNuElb3fp|D7jQ0Z#LO6H|(D~gDs4pK{H#b3b;DmaQ7vn6Y(R9YsPmzqnf zq)H1FJ*rNHX31tX7TU~&LMeolmF4CltI?K)>QP@M!;9gD{N~}PuDywZ(RQuwJvJ%Nw24pQOM#q>@00iYJDd2|6R%S^x z7Bi|=K{Ym(rWlK=tYxJX8--R?z?&tNnit!wi_N7)W=dGbV^@^{>&vPERh}SAGFDhC zsYDuP01hG|L*!A4=1`6_1UU>)3tqmt0*x;*@?49r2LEjIb7hm;EjM`G9^g=kwJJte zMq|?4?3^5FX>B7g2N+sb*WgVtIlXmmpbn=wlDXcy8f44lT(cI`jj*S@wh^>VD)%^D zvg7Y%JZ~0q&yz}OJ#}s=k}9R#XhR>{z0TDx$x`cbQ9)Utf1ajV7pSDm;Vx+A`OmI% zH(|j(m3^NTHM+faM6PeN<8ZymW3QF%#aPCb)HXL`^<1hn)z&Z;9QCD!U1lbGqdgL@ zh9wIodkcuF)m`Lv$sTtjTckIybIRo&cb(nqWt)ZKM#bAenHDOvQu8wQL!sD>TFE7d zfF(Fe_>n-P-Rx>{dfYBrkzzojd~XfgEnxLhTQ@!YS_z>5SQ28XfG;f?D!~u ziM!s;*c+ol$`rX-ktyD%JXG;`kbSInH^d?LuPc^Kr!Q1%D*#881^q8v9|aZ5_&ZlnK&_PuI-vz?jAH?jhQFbVllRi!Zsdn052LSa-bV|_hV=YZ1d6jCYI$I5({``3GA zdlS`(eWXJ#Ep^&k%N$fs^E&D+c3xM4j*kS?u&MeAdu=`Wa0f$QNst_YThAze1a2>n z3b_Ke;}mMLJ8G3i+0@u*!sek)c6*$5)bW=I)vl(|wO~^dYlw*RB2PR^tHj8Yhz;nn z?J?igZaIlY;Q3b6T^Dt~A9Wv%y8j+^cSPNTQTL6g`)Sloz<&W*ZvN8Qq>`(V^_*MIY0bp9MI_!DHX{<6mczppVn(d=eAJ8(xpoCHZ_o=8N~G;jGh_B*bE8#HG#{e;ARI z;&gG6&li7IxaIC~Iv=7-h%Zp=_>tI=ypJP#LM-oLmQ8ubkIp*btnSfQ*l`w!>y3N$ z6&5#6&~5yX35bR|=$OPd;(^DP8Xql&JoMR@>hq-`D?IVv{TuDsgeZ^V@7MU-jz20M ziAS3Dbvwxae_^rx;<;ZIFnT_Tx`R>oQq=t->hehZr-1+Nn;da_qwbqgmw&|1wec5& r#5MOC1g=5g8U(IE;2H$3LEst$u0h}$1g=5g8U(IE;2H$}?;-HN5lEP4 literal 86016 zcmc${3wRVo)<4{nWMC2k9VCHxWx#-miY6>kMu=o^26}J?0?JLfDG)JoTbKcKK?0o! zZQIDI?7F_XvR-ykQBefLEldD22}Vr-A*(0>xzx}I5j6>j%=bIhJ-MJh|M&Tx|MPtV znXazusZ*y;opb6`jqia!51H%AxhX*ql7uF~_*ev6t{J4qu=`uRvVn>R z^=dtKRMx&7f}mT&Z;66%X{RtZtMcNiLrT>zoyIXin6&Kd4B>;gnzM4)61cEMh}kK` zt@?IVD61hW^5Yw))m%Z?B3%CQjjZoieS_?yl@nWp%o4$}Dwq{Sj<*Xh=K61Bw+SUE z7Tmc`$ZW`}RGQdC;hA!|+&;qJUJ5j>D%Z~_opSpsT?h9){U^5|B-}UZBim%c{$s`s{{w-Lo22m6r;AS2Vs!pEtTc|IT0EdG4KYZ$J0o?gt9b7iI-f zs!=byFYD{4Z%FLx*u5}DsU4ZOO-Onsn6Z1=cM)-mRjAQcG6!Te^@ej`H@{!=!y||-uK&OsoL=1ung728N56ej z!i_ieOqi3?GcA4UoLRFTnmx_lXMw$ET4v9To(c1(`|W-D;B8J%eC75`cr@SspuNvD z`|ZxTb7wv}^&$VGb7s%CPaI(P&zL?tVcInNqxq-_x#yVAhv(Z5;c>%GZ-ckeqB%ue?1laT+Y@xl>4Dkp7_ zf%UXe_CC}9Y|rX@hkc^GcW;aXhAc4qp;^=IeP(6!wD-udFY15C^?9f29-evDessV8}`V}f8-Bp^6-Zs5^r!kn4Y=07%P z&P)N1+4*yX>HYg=3bW_Tnm!BPGv~~HM3^ys=FB+>LZTqtlP`962||CN1mE;A-i@#z zL?Q^iBpT1>%;!sAnD6HEf8ICVpMPJ7+3@bQf?yFYj~IEpK8|Wh5`?_Yf>764NU{n- z4l>*#7JuGJ5DJ7>KFk&ELAma-*fW@adTg#Bti}htiG9n9}^o_%2bS$UTQSsriTS=d#A!4G!|nWb?8D-_sj3yP{A$7B{a1me^? zgv_j9-k6e^uDmg0$CONS?Jmbc(bYYTyMLS^Lbc1^u1n zUDl6s&)DmJTa2&;lh)4*7N zOy>7lkpf}dXGtGfHVB*S9|#B~ZHWC?*oaV85bdFad?x8{$_3@5a_Vj6ivr67dZ#h# zi-Lh)l=G7^&Qpy_jdHQ@m{54KuyEJf!h<4;uXSAXXDqyG{lEnORRcTvuX=k!`?m=^ zrZk~a%Mty&(u6gu@t2H459lWW@)+gYXqPeiyvsI02~7gjdU@kMpPJmjNuQcTk6<^DDG%dakVT35!p5NnQzT;vbJ}WqJcS*pd zCzPyq>DH1tE?r=+|B8n4XXH73_uW@^E^bvQKP!@-^>u!U9psk3B^BlK)iIaQqu%)g zhFl8Ri!1$||$O^6sN~>yj3f9sjYwxG6c~8gX^)9yx<#CbTg*lONE91uXUTN(f z$Stu5>{emJyCH71xY?$pM^QdlG+IZs8SHv>1e2w+T02N z1omKir2ZW&SnbzKKn<+wmq-LGNf5x4@Olcb;TPitLDEwGiU$kfE4%_YId(S_&9(Sz;;`Ga?LOxp-(R5X!SIsDHdS1N_Wy5-oeZ1W9Adq^0(&q zkth7qcsVor|MZAfL!G3*8IXDDGowSU`80CSe4$VDX)|7h)nfNT3er23C%8wlK1H8G9a=Jh zj}@jYYkm=k)aYYEXy&1qRzV@|R*R5Mk^h%J<1n6c2?>Ajxy?U)k)P5sP57hFR{5t0 z!ra@YJ>-9g;@0AiXoP^LXRA*TGa`!LFD`z686<3>-ebmantKs}Xo;&4C*g1MXG^l%!c3&h3my#X)}peObc`uLtjL z2ULxgloQVTCHaia;MN}HlB9KHJ%SOi`LZMBky4@!HU$$&lI@qBqGe#3B=4~~Ct#6B zS)&x;T~KyOS|Zv?{*2xvd2u(J;J3;_i%z0&b6!bpe@nZz5$z0IwVOM znq;Iq$2doG;OtT0E_;-pyxOb^NxAG*&asapk%(70=~WupdLBIMRZg*gAqW`l&I!(m z&PirfW1A~$?rDVGg`A4JHsn4`RHhH|Dh*U^A)=k~8H>C4xc?^4)II1^uB63%N&WI^ z{o4DqneCaG_eSI`4leyfMjfiJ*5Nxmznx2+epugSpou_mPW}xLNzFZylt8VdjIIXe zoe+L<1t*LwNkVun8uO^_JdRJi;$Y@k_DKZGglE|ap*|AP=X&HlsUFppW~1>;cFK#> zk}UqylCsMao1Er#oSyfsN9BQqsgkAI>tG9#yxO|fRrHZmU$vx;uH_@_RU+&!Xwj>j zmgK#$l9J?E79y5|hWP`-WLulpj3%&p4MIkAQgY)sW`Wo{T^T z>*SQr#@{P1J^|p$^bdZE0Zg|fx#RgyEJ93!b*jfA#0C1RIUR;ugn}alk!Rga&${a+ zB z)n=yIhHL)gsbC-5fdHV?2AKiu;m>{W$hc4zj{Q-3Co3pdViGosLD)a<5JD_oRo-FLGg1DU*-3lP|RMCpyKgb5pOQ?=e4FG>vJ%OLH;d=sqFu=DmU{pleV!2=?bwCk7Ni!6>K^Ru%mVW99RL8n}|)U&@%E^F_N z-&GV`q_4~M$9t6uX@8v*ze`f8B|wF7Z7;o}mrtGqx=`&ks6#n{NobFx^u8G{SSut= z778){EYAuBG0)~Esws(RO34HGDv7g_B4HkM>@|`j{m3Xn##?SoU?9ClQu5LzMb1P8 zRL@$Apr*V#thxB==Hd|#FD~=q1+9wPsW{3}@gN6-Ku}uh*w9?ZYk9nm3A~Qpt?J+) zpzzo+9(9Sp5-UMyllc&y`Ui@q9y=X?rO?rtT> ztR`mQY*~HHrQE4p@+d!}Y{!czqR2D$gYJ)JA*^oW;q|)&0cNV?=zHHXdKZgwND@&u zQXYW+^#y3vLDsK7RT7f0mV=HyLnQ0w=JigYeCR-t59IDu&blBUJOO(bdlNFESABgg z(mfijqh%~sl7slGyM%gzsE%b!<8{nM9(UIFUM;(}w?*(39~X;sOlVWD1LvOwh@RiT zA&Oh>Qm4duV_&bvt5?f;5+wlAr6y|q6kB(UdJ({i8M65tjbhO%ROC_bq_k&vn*TVm z|7>PY##F#${sgbocg$XNb94peJ^@_$z@n5gtlN^)1P;5AhnLi{YY@l^ zO4{_g7Lfs6E63_aeYPIw*vmrHb~EbycRcC_L~Syojvxx-u?RI2(muUROIONkNDc3# zaL8Q?=#hmJTKLmnVb_>tZL?~r+h{H`$1s76sjWoUZ8S+)kLLK+qBD@2iRtH2_B2>m zIcZT(aza8&1R<^2LbJ$kb(Y7aS*RyRh!OlojNZ!O(y~3UOG%;7^-FREwjA_6akpW^ z0Rz14cqGDJZi+Hw1(CJSs0P-QIv0{h*0RUoj|#W zrkD?m4|vydUPcP*iQ;;F3ngS#u79d*{dYFL!e7+SPu%Sk*94u_br3hWtU(C+F)N5g zqAwN+q`7Rtr{mi3H0j;lV)s@;60ObH2#tx2v$kt5ZMB&nlfQt&!lXuvej8HM{!W^1 zzBs!)Uz{5h3gX5gT#fKPP{EnFNQDxY7Wi3AN=(}Vi31tu{1<=U<%{zUSn%~@cYJ+^ zudi$K#dC)#iP|{)qqwv?30wK%<-L;Q(7auhyRm`0uchbCvEXVYn?UDkdyhH-`bVYz zI!O+vN=h;mIKK^Fi)~3BWrQs&NFqN-^VB3D9r^0>#*QiIF}WaVQlw_m1|;G|Igb~w)^QD<=-~_WP*bE_P?46c@yHy?16I7PuON&>kqBP% zX1r^Z_s|Q<-;GM$#PT{WzlhvelCy6?66mh`&%E>!l+HuxGG3ZgP`q?7OAp7B76Gl} zqF-o+A4BmoSS3)r4q_V`zZ=C#|6k6F?>CB9AqzDwAksr}CZu3q9vXc1dZYYWqx@wo zGtnh@LO)oD`7|sCAEhqc%4@w2ad=r^*18dqMwi~F)tS09&ZyIC)cJO+I&IB$&O@Co zBd=PI=RkmxS3(jmh>UAZ`l?mA`wx>E{y6DRf1K1b=npGA@yDee_+tpW{c(lY4gABl z-umODe?p7;18nGj3}H!I2&-D~E6F*1|A}!6BqG$Ghk8uht>$z^kiB;U^vi4!K}pR? zq?nhwga3W0=Cr4mzruK=W(!90udg8HFO(dl+M6Z4WoN2EY=*9{>==mIMDm`pCY|`T zqS^8AD#fYvrtO%5M=7!kTW8>ljmA13DyB$!4Y^+y04bY>spC{aT*ByyRnf^yIJZ+d z{V$T{u8@@CBoqpNfEcW}M3pJmbFpX>#Y^grOgvaeJC|vT)>DqD#r-L%m4!!&mFJR* zKw_1Wq+c(+_`9jXFjxmRzz$>a$d_UkK6a1ldXq11UkrmSU;|*G#>(B_Sjyes3gzx2 z!r;Qiy}l8|;*r##0jdc#2aDhtthnB+Ryau1&*D3f)e|bmcSL_78c;p*<%oZZT1;4# z>nwh2i7iI3H1PKrr}#pdTijeJX=`$kj>*4dQUh*(?@rey=J1D3bbJszF2Il%l0%xdWfj5RtUHoP>dl7l`uRK#jf7k$)4XowS z7QISevo%#C#3)j-3yOOiS@H@BrG0{^9qv3gGHDFbAn39V54Z9Ka958|DXNPwOJ}0L za!0*b^f$EZRSvQD@r1m`6^zw9?i|bDtJxmHEEY+?6YOvskjg0tHnH4i4eHcr}l=5Y(~k+F`qYI6crR1e7>QYF#758BSw#9(EwiI|0*qTGs!#ysguB@rMGi#4CV~=m@aWW^xf<+B*2j?%m zS4&O<64BX#m?BH6(eYaL=Wl=oM0zZHyvp-~c&Zw4N^jJPoM;~5xF#-U@6_24nzB0Pjxt&lRo`qKw0FIYbdzGVG zIj9F;;EHAOOeBUA@r9-W36ktCv4p!}J^n4GzkuRG*!epA&6VkBMPTn6>0(hSB`;f2 zA><0e0+D52Knrgpj-^6*K{u=U+ZEK9+f0qMR2%4xQzd(z2t;wO(_{Xu8hu!MJifR-P=8ukPqt{CI&3&Qfz-dNRO{1ie`&29(;%t z_jW8Q(E$n=15{-W5REj^L7M@}e%=@$KP9S5*0I(15s|eSqma1b8|88$8P{MV1P}!rOVpck+te)%yEUAhr!c^M8eYX&vvB(4>*`B(ox?SE+C59B+?ygqEFIi`t@1UQ#wq05m4CgxrwWsj}j^ zgfyji22VSXj>wR^Dw(G(*MAuYms}z)0$j!8gF%d9Nq#cB!)tkfr9;k!aTF3L)-eYSX2JvHCT)=HRXQUuM@J zfQegI+6n6$>AV}!OB7m;NP|UT!0T9XiXjCNAd*It$9)EhhPxV?fv*Y;Y*5!wmn9W6 zsT%M;)&*5#Lx&7%B(q?n>kKx@0;y^-&pN>RZ6?h8#mPtucf*%K44k#JCKj^2np0g_ zf}O9QoytMwbH!KXR>1pwwLUPqvU7Hi5%=qMIx5A1^gozOQK^R!kfYqDebf@+}3h5f_vkS>i{ zyx14>DGZx=Fq*%yhB+Ul+*l7cp`Sns_ZlJ&g?@a#E2fYn^mwCe6OUYlxe2X;$H zg4r4wWYF6XynwijRWc67Obb~DV|fX2bLXOc{)EDd69Om8Ew}4Oo#MxCAwMT_Xu-{> ztqPTrbdj)+stEcl_|MQ-%~G_&u^J{>NBlUb6jNS;@^U~MIryc zS9Ug>H#~y(s>DAt)k@R9Y4&QJl5-|)>z~k8}kkj)- zCD9oI0>Un?I!OFj%tt2mtE}|FElxA6F{FQVm3fTbI0`oR`$4Hc9<+lH_Qd z-(AYU4*r^;fnO)22ytb%=Ds;?u+Yt=)_*r|XODx=q7I~EI_y981p zh!#>C_ScAZwIxC31QLyPH%!>&aeWYnIfuD-jof61h9Wn`V^hIa6R4A$68vAwO&$FQ ziL0ze2^hz*Y%M97k4|%msd}E(Sn5>Y254Ounhdd6PnR$EL@n~=3E~Su+b*&ABSNq? zJxEjZCoF*U{18vDFc^TkyNenERq|!Dk1}Wn$wWSM!{DA8fkg>aWuin z%2>Z!R4-G^-6s&SO7$4d>QnnLOUh@MDp~F);qx? zvENtR>kb40X@qoolj!XR;?_Ozb{Az@=q!PaU(mZcjd{|^Sps$`=(P{age`|dVm z8wVle^r@Ej5qw@QI|&OmbS|#R@l`@#HzSHTGOa0qo3~qmDgrlB|C?8EC-?l+U*$|x zU$>Qn1O5F1ObaZx25W7lmh;w1_XzQvPkhq}(+BnI?|MR?fDj}48&}Ls(~Shvuv!Tm zCTU;!j5c|={h~h`fC}WM7_n#%T6L+t>alU}Y&_t)ar$AGae>$=9!As zB`y~_-6zNlvtpw+b{ka$7ZElXqX!eMp_4#V0|}#enEU`BjKW}?-8TPKJpWLeH+Cac zK@K8V!W9(c&HRDIfCeI}!}|_%1)fp=TO`2dzAZ{+lR6rmD<&5lZ20hos1u2~=*VZN z3&15vN@9$eD5=9>rS`_y!V6JiD+FGQLU!^=dgS!PgC(rN*Ubgeq#dyk=KE&Nx$(#>dmozb{{wee(cg?XP6^6v@7-9sd8V&H-ClO{XI zImbI6aMIxfS_`khd2wV>7YUZB0rCut>Acy7$Ym^2w2CbQ98Tp|%EDUU9TS~!kFGd>FB1L-z79(f)~}n4W^OeY*x#v8cj!v`#~yWY znvFd|74#_AtJsWM#9G-?=wIs*rJg`_st12HMGI|B5+-UXMnA%OBIKy|ca|V^XQL`0 zxIJ&MayRQn6*ltnEhZ!RLc~8fs-okGJeWx#H{(ymR|g%UTEZwqB1=o?3sMp|jqeh( z$Mfnts_sjbyA5K|53&!TChO;Zr;*|RuNrwAsKEkoHLE~CZkmM?8w6N$gL~1s{)1st zH#Iu_7`0AhZQw`w0S4sl$Kahb==Q)vl5&uZ_<~oOdSk~o(`j;bEXTut2Lj(v;7^i< z(;heTO#WN((uG~$@^S-9GKzPVv?sFRb)`LVo1VbKa5H)0W=Uxx(xWXiBp5;%?T$*( z33e&u-jItvU=`lLmbc?b>_@_eM{%Xu5eQNsU6pe|LHSOE^PC}9+6)YMv#mg)T`xA| zAaHH85GC#O=2pm1uUco`1cPl&k1|Jve>`4Cx4aI+>)m+mvCB~{7N0}qg*m^zj7G$w z_Xs-G%J7EO*YFeA%GX!UXd=}V`F<1}-CdY-fC~C0ubRCZA=;_`(Xs)qQ4&>QqXw1u|$wdn~Z9(EuOurj!Na!;KAkr1WB9R``Uct0s-?0zyE^)S6c1~F zD=pIlvh7dap{l#egPm;Eeb~rRXXda$o1mPSM5EVkK}Mkz)0}tpD~t!wI1Te*tz;$N z1H)?-9zO9@BsWSlc|0@tB5+$q3BeP-9&tFJ zgis1X2|ARGPN!8aA_uymHhRJfG5U6?-h_J1&e?m(KU5xNRoKOxK@}zT1A`0Rt#*o?+cq`|2I1>> zJNbHqq=4T6JmJa`s`V7|hQ+oGsMhh+>;=&0?^F`FvUb&akt%@|sdaYMnoO~8Um1Jy zGGei_-ZpPNQi3FrbAU6`g#&l-$?rj84eNcTo3f8Gg36;abMm1Ge?ye%Z z;W8+98)zNLKO?){hZVoo$EGYM% zHJE@`mDlYjje8q;Q~JP|eOZ<8w8;u?g$^0@N0qdGqD$@M2BW*)3`HxJQ*KP(oOR)W4DZNw=n=I8%aDhtU<<|11uc(E1HW>Ly9qMm;R0?ywK2 zT}7t8(uM`Lmum(86Yi{i?yO1{V^(!&hO9 zH7Ek~g-THu@BKbK9{pGLV%fufb?a)pIJx5KacG2=ymYwM4^|Ku#kJ&2^)8nq6!^>w zpJM=a09M~xbzYnlyRjO`BzH4)NidQ!FPXY#HX;`Ng2GUQd(Bup`0chLzKmtaR~D$Esq*9QlF=y<#92BD%T=}B;|Pum@eG#!0Btl zD4VCp$b-?Geb_VyPXyE(2?|Mj(#HN4Ft%qO!`yK0#8*Tw9IU!BudJYDcI02-55K3- zT;QSF5!RvVmhk6Pxj*=bq__*Esb5HnMm{`n?G@#9SdAds7J_Wcu_Z`5vXB`zUu_XO zSWED&xUuV;{Uq%8l4_>*XYsTrsJPd1rEJb26v6%;B0x=K#CN}_UmfLFWDlyDPclC& zc5dF-=(WHz#&XGvEbVGk#FJ<_LR1O+%O>ES8{N53dNf*gBhL`1ro#kpOW@6z<>HF( z(L1j?tAvEs6_E9?%R<4;(2o@NGf>!GhM9-WLJBtQyehU9pdEk|0OUGO&*UU^fT~@9DZ2f;W_U(y`s2(`dWEwQ?Nf6LmBC)MjZ`tG zuRDOtqM%phS-dJQ5LgwyfLYR9KlvZ2OV+Y&ShM)ReEub%mfeLGP9-v@0h=^{`_qZb z4QC^a3nAJGb4Jf+%zxXX^AAaf1UiR5jBJGNW}415M3epgsh|~rplCwXks)X{dvFw zi`ox7Y9h|Jp0P=q^=iOEUa}Xr_^u7}|^mY<+VC(mz*Q!d#2eu+o zC^c9{P8h}5Ctaf1id$u)#V(p{6J`5nbGAvy#@AD9ni!+wQGPAVv9Cm}{(LE9jinGi zG=&_SX*TSU9MaT7Io-|BMf2Z*q6Ki;(nVWWHR&Rl_oYp`KT-uF>Ib*0SYkJ*n>HZ5l!9&G+x zgGG|Rh+b^^y$##mzA|-&1@8 zvV~-xgvL~rFPl?wpe+tq3X5794ILK%QXD8%*uRfh{4S512caOl7u8gnYfxSO8X$1Q1BFJgmV>A-a@<>CArjXRD)BF+$9#hMiF#I{S+G; z&Atk8g^Mgm{SqN5#Mu}^U`WDX3)-Rvp75aNXbmx3pvu;Hckhl#GvwPv| zP@&4Z3MEA00hfa<5#hv$aWjkRFoau|7=@q2gM;LEnjl7DJH&Us@!hNP8cC}ifv0&382(JmdnBguIGkmMXjL~bw zjENh>jOjr!V^+185vUV0mehxe`y(eQOCj!*ok^ptxsxET(X5lS;ym6;F)3P-wuy2x zh+3v5js>WBV;4Hnd8kl$l(V{9jD@hBq`I51ErSBB)W^b{my0oUfqv!I=Mk{T9am#; zaSbCr?5V}vl4q$hM-6uyVY1{r0_!eos)|Ia*wiuuA0`)3&a!`Eo2Km~rQIWxGqQN# z*`#c;@s6w!@bDjws}f&M}cq1d4NvPYHXqVk!$Q_EL~WBIOtU$W8@wFcGprU=sQZMwUtL%d?5t z1pgyoy|k9^M3TTJcccMLMNKrIp<<$9b7<2sw07PEcl*a%I)CF{_GIugCJS0Q2C|6O zifuN5#iLfX2qOo(Astx!8zQl;xPln$oLR8uSj1JtJG@$-Y{X$%SK={h;S~rg#e{!% z(Q*G@uz+5TWLh&ezDylCV>9}$@;vbQr3FmO?#S!lQy0+OPted7`*r|;O9d{&Od?Ii zg%QUV_KlLtLrDPe1=yMa)m>1u!qd?vpuy=p!><@k_e5BiBNft%a0Xd`t*?#>2oL;V z6Dl00CfA|m*LX~{<*(XfA&l(NRpdRsgJ$S&BpnDNx7pCu2LT#r$qgy&Hq?ROJs87h zsXFTAXdn8fbzDL3TJ}?ITJfV5>K|l0)1?xX1gJT9kfqF|z_wl|C9hC6(guHsh~riO;0?-bTF0c~rUeIskw(slEE)$=1@A zxU~m@JHx-CSO2ChWcWxRf{NaB8dEO29p2>6trW2w-U)o`1Q$m|K{X>OM$2Bt zOGRn(ZYyP@TLGd}gNg@j1F^h``tG2>d#J4s1vYT}v|RfxuQoi_tJHv+P>bUW z_{3>(BIHu{fa}VZTH)ZShvB_r(Z8dk*rEqlBDQl6xb*@Fd*>|BgQclZ217PA?y{7z zOZ8rcoH@Kd-HTAxG{~crd9rFe@MSFCx2Q)6Ru{03a`=CuZ$$tT!(y!N-~1gN?NLHv zj{Xg@NJa8i0&vhp1?j*!pV$X4~CD$ zh;;7pu+^;7yLb&>bMz08G_p%^*g>9yr305BsY?#9YZvjsSz1dk>;A&4J-n4#qa(DJ zmp50J;1V}a|At(~JO5@=&p@Jn>I$;4M<+exi=W|pTETGV1(5~}OQ0DN)}w=m>j-ej z-m7NnkD4B$4|(LX7>;^A956ykvCbr~Y7V!@vB!VJ$t^F=66y_Mi(55;6D*@Q!u*3P zMzC_ytw>t-OQb?8hIGQ{%n}U@ZBQop)ak}Zpvu6z`qKtLZH$N|gmhj1eP%&-VR)3! zF|Fu){vNbTxbGaTrdh#7Dp%)EF_L=q<5U}Dvbhkx4J;Mr{NpbG%pX$;$A(o1g0!^N zKN9m@BcNKg7*Ld3{gg@nnu>sYQl$|0lKnMSi39{yZ3`vwBM*;tgvA>zCXQy($gQKNAJ(w{1Bb{}!B zbkPkLv<{>iU{2g!xa5cs(8-fcTZs0L8azt>ZwvWhQw%E*?zM%cs*JHo>fO}D2z6|- z6gxI`WsEu^8G8lGj*&BmGB#Nqkpz2f7jg5QxrLWw{0aEB1X2bj1UgIV!gR7X$3+h6 z*CCG;yJ&hBra%hR6WMZlSlNBMA`wHcX{~6%=nKWo-2@6Fu83FM>jQx~XEZv^7CdFB zUa?&BYCXs;b>R9dxKz}FM`3fYionR>I6dSQ-^VskJzQ6$w?I7{dG*HJX>5=jA-Mct zlyRWMIOv^)LO_wkN;g_kn>V@eH+kOusI#NRz zoJ=PleU6{!|K%QWbK>pd<}#;xe_FC97VbGK6P$1xOxDJlcqN~mWT)y+OLCm|Z-kmP zFfF$*660ToZ%g2V!V3w3_4@zdnuULJX{{Yl`7$Pk*$s3X_XTxo9ib;wu}&L7Kjhxi z#*4SrzzKOBg*8Hm8q13qkq z)HQK8f$BR4G=hxTx} zdA)v;k9vz_+H7p0s|FjD(FbVy;(kI5KsyQNn*&)01`>Vh?fkR^k;c}WX-u^2S3aOe z&ii<448VGcfGcB<d-i;*t!tZAQll;UFsa08?u+ziNm;oX(oI0 z!(^a72$K=iIS?4mvz&pK*Kz*@Sou#h5hSrGe?hvW-bMA}6eQwkwq-gkpXfC$Xz|}n z{kQ}Dpw!za^%$06wu-yleWG{3GKjMyycsy#83`x9M$ZWk8nLPyQ zd@J&uF?@KpdO~}EvWUL7W!`}icB5?IP%4)TuLXzt#OTXN96Y;=W)ZXK#|wmK!RcEH z^2y2uKrS`SjKmjBfds2D!w`u@MSz(-OGBqFVeCubA4JWd^QJN#BuBBKp=1S%6fPwf z?9k9edZA2eu8l@7$8hpC^b6Qh8T$|gjb1}Y!NMSi64{ywe5?cIyd-BfN}f&s*pya6 z=CgtmYIIy%fUS-0a2J=+h~R`5=&Qq9G>61ua*Kw-3n_nRCUvJeh2D*OBQ{X>5WeV< z3oV2{4W1v$O*P-`<};n1q1#69$amZvlB*OuBK6Eli#jwp%2bu1 z$*r?V>ey6>v`Pi8tcXFGv)ImAc9hGyBceAUU!hSzVLU{qe)#m)6TqvSkwbjVCLT_V zJ=xy{M{sfdoR()j<1G3l@HKv<<0x(pF{E-*9$F?M><>eLL%vDLoQ0Mwd~5ksyA zOYz(afr^&;3y|G`=t#=)b6hR7nb?2au$|v44?HE97asfw1J8+66k{hl(@f{L9nghY z@1hQmLi;!9#}FGA`}q|MIySZwabHW~K}pMn(msj~RnF5rp*ULp=`6 z_&q8bg$N%wUhY1C8<4)l4M+!tVPdJP*8wOMpZX){2iYfZ4`%}7!VBEsvH7n*L>`(hRBNhCNEQWU;G<&cOc|994RYrf4ihDg^2(+t)FH~kc7)YZuey% zb0Zr!kDxfsJyXcu;6TlWgBQ+HYbiTkr)0jXeiM2QI0J5o$bqSpwxv0(gPE4$Qv0+! zi>WQ2hzsc}e<_~J+h+c-}5Nzt8@%W-OQosl^ z4a7iZ!BH%@FC4;%)W3=ORe;Gz-M)o-a}qlhn7$kER||#|1XWS2&H8yE@-z+eNgT6O@E9%&`;)9VM-gsiNM>lk)x3iNTzo&e)-oD*o^n1FTa7Q!TN8=SaJSQFprZMuVo)P#mAtQm7`)Jt?Q(z z@L+6ik1iu1B!5KRS=C&uE?VqaqgbG_IjM53S)<=f!p2kfKR6AaVvoMayCfDr1meNO z;Tz3B^h8L>M7PVs5)x?mU?qmVC~Aw<_(nN<7tomI(WpS%tf-Jf9+(8&h2W6^mpJT2 z42YTEo4;6s_$h#Vmy*ukV3zSHxX-GUPHw0W7)JPi4daV*D0;-Q)`X_z6sl9HZ?H~0 zO^C!KZ*_cb^=>*o7r44|H|93hDRQM&bGBnfvpc~kbgNTgKdIxUH zh@@N$Hyd19NuvDTY0zB)xSvl=OQTy5uGfd~CU9+@vx7IS3k0yTunotHed@HTh_hjj z#eWsqDnz=d+fZFTr6I(6Vk2`}wFl5(d6 z^&P}ZDt$A@qMowJO^xZ|5B z3cnI&hHB0^8xOgAgq($q5nf|_lK~4@*UnV5!#c(jlbHfRd{OdN>cB0dYhl0fD*dWy zX6th}ByLy)j8nw=odg2kIdeH4@3P<_X(|dRi7*X<-oEoc$7m&Y)%oF!=s-l5 zF-(1K3XH%U1CHU<=3-rHWPPE8fnW2nJnQRssJRKXYEQxHU}`JvpE%P@rGmk2)XRnl-jA9kO?&S71#Jc$Fp@Lg)G#c!X) zd8U4e@eNA1xDM9iraGf4tVmeW)c)8&tB%wZ2N%6!HgRQqu?tQ^+>iyQ9ezbzn@utf zfDZulD+2&A1b|QZiC@=1sE&>wR@~g?6&FNgbQc{(XMG!7>XKBi+QX~<+2&FDT3}}pi*O$^bi#{+7IcJhB^T8joL;HHk9lm#Mb->#!Zk|!PkG`mQRG>tB@}Yo zksv5^^lw%zUAZC_#n8wBVdi}qfMOlGzXAKDwccB9g(-w?hoN&Gncxrz8F*er8i~Bb z4uNzRTm>z-F}r9Nj6ZNr==) z(j2)2=|)43&%qrDpl9M8Bt_gB^5B+xoNJ}*;`VCeXA3w+ib)Leh}+8%haas8=0XS0 z6^pA7%Skc33!$xJsGyde{t7*E`r^S3k0c9agIN4CGEkrVouoDn<+zA}HldB>KZ$*g z6%0dX!x<{vv9-ac;C#mg15X%15)d-Tx`Sn3|M-gGHU%u$5lp`U8f+gf=%k$$Y;4mI z<5|nQ2at+Z$07nU1n!2iG=Zl;E*lGS=(dFxB^}NYWl*w=Un+uqwp)`Y*43>ZN(l}d zcat3T^S(8dx-e;t1T(o(UGQQWxZrDqd%-YF7>9FgA@zwiIKV{_4`Qs5%8uSG#ba-u zVo%3lCf0%t$MAk}`TdXTx`pEhLJ!dwHxZ-eTDs&(9gPW%io=79iV=y`XLPl}|0UU1 zZSh%t_70d;BFcLSVn8I<;kOLvWOfem9{h+Aw)edmr#+7QLrvmxKO%9&72FpWrwRDM zuxS5UcW$Ktke%O)ML&py``5^tItnvj(1K(bS95va4SBYjBOeeGitzIr0GiJyR|Y)K z4odsaO7S3MGx?YSK8;&=AYj=FF@fl-;w5_I?8AdC9zo&%1B}P)159BW?SoR;S&-2WQrWQ>mp0|HB%g0jS1Lh+EISD@ zfG~t?HMj!MSL>O2z@rX41P0>GfSmwxwdEWiSPRUqc`ZY$;2v{y+$3N27A$pbDD03_ zb`Dk3d;t%-|0=A@XahUBpmfw+U}-}qzLMseCR0tuAcXgV8Nm6b8YIkrdcn9yD@d_z zn1i`?z6k4z$;)wLBW?+W$Cf6553mcy$R@QuooZYb!mw5NJ2u8kxeXzUzn52=gKjOw zZN|t&SEICNxLgn7@i{gM=%v*g+l@mJH0@1ol_;|UPv&gBBH-+Jl0T;%n_Eit=)r(M3>B9(`ef|ys)5y zr1Yqft({R-cdBYJs%lkOqrR2NM@4@;WD&Ve#G6-(0~V{L3_v*)KDa}Z@ViM+M{;da z$ko-BNATecAlgA%W^1b^pOb`&Zd%+`RaF4-> ze^5uNq_&eBO$*OqcN)LA(X`+sH|6QZl?iQ%;+J8ldChfKL~8s>>QQZ4WLhM*W~BgC z$#qS1We(sLpE`tZuQu3*Q@jhWAzwX6CPr-|0J9Ij)bSMbKtb_gzmjXfUiFUQ+Tu(* z?vEM7e;9+*U=rvu8jXpg`fvmjO3P&?g5Ppm`(m7!g@h9JX5j9Uk#!QeUs&K}bV~XY zA1q0^O9~CPbs$WJT%GFi#E}y0H!Q$U1>xb#_|+G?K5ZP(<5SG%fc0rp@V>xCF%+Ve zxQ#Y%DA&w*9BnnoCWQp%s5K^k#Eu;X|}1wyc&Ae3i@B-~H)IrSc>mMfre$V8MAcUQ3~m|1-O{bpc5BdDTTpYF=u?E{KFH5G^0GDWeaI#s5QDw6RH$SDf-mPP1Rh$3(K4 zSuT6v@IowJJ_%kMsFsgmslI5zZ=LM`q(azDWif>EIAQL=zVw*>N{!F)QPnsIOJWgSU*f*6XAE(30l^qMZE1EfTPFP$E!fc#rJ>9|{~$B%JcodOK5KBMe)Cp%76WV9 z{bBb5L-D~h#1?>46Z^FzXA2*O49u>%o0M*`n5MEXBT9qY(`cB&oB)P}=Q z!9fk+Qkvm*ya2vnphK9p4J8#K8r)d{gS;qR2%JOEj-Xu#9QQb|Ee>WG0$8Z?zgWbo z*j=bo+2>J;4iJ8b9*P@~;8chA=Zf(DV~zWqJ+P$~K0=G0GQ=J}$O~Ffu!{YHz04~1 zGygt^Z!G;-?%|=xY)Tohc^l+4n2URgJA!h3_pRO0aK^4Jz3EX#`Svqh`L8AB>DDcj zrc`*ekMf__tar;oMG|M&N+Qu^9No&&8Buzp)>L?IBk*uKTLkSE~pqdDsvE z@?mgU*b5V2q1~&@gB&s!6AC1)3`s>7Y59q6^(R-cf1#kc1#j-m^J|1?BaQqg+Att9 z-?!%TAfNWF1OYEr8z|3MW&H7;Ml7fllq~h&$A~ZIGn&U!5Db%Mb4EFqBOC6pmE=mG zMZy%nApJ=t?&48wUYf730$^N2Xrp_Wir%Id`EqyhsS*k~#-@u!@Y&OW5OvP zjrGVE%=?!l`C%Ju>bP@aFIKrcXf*vYNuTZcT15Ud4m4 z@+3hG6ZsX~>ahM!<0DsJEU)T4RQJ@VtkXA!g`NPL!~C(%P!LxRoPIfC{-4K@oq_%mNn z#|ZG^yOBWs-A3%#x&J$7<9=6<$#LP{h^Ow^`MJU>_9BW_vFG^rGyMB0d~baWr9i-6 z!`cVJ9!SG?wG%y6;iNKPY^o@qgT7nE9zb3q$gZk%g!<6Jx1hR@Q7ICC00i)#YuuK2ym zSTJL+Qe<>e$pb&k1)b(5g{uJ!Z%Tg~6G1g<#T@r{tqD7~5Ic5eRJxQ(4{57M^@J_d zf(|1QG7rN{JK7{Ue(=MZ@{{6 zp@V{J)qSuG%}JNE6;Zi57XnBSih=t7&~`5HQB~K%KNAv25IIp1(W*=oG*N-zlYvAs zFhgcwCJAqR0zv{PBq5n0=miN*0!+uL-fDZ>e!ac+ZC|(Tts+K?39kUA5JY*1XvEhU z$45{b0#)+;*WNQpKx*&r`~5yNbN1P1zxUc}uh(81dgZk$=xUIg1M7>f%KWJ}>bCo!#QTWz_$Z73@)v(}5de&|GCJ!|JYf<}Sii*@ zzQf&Wn)P4NXfNXTn|O+D=O#r6dSMiVp;{*`bD9?vX3E=lgs9-H*iMBzyn6S5Z~CB2|^O|l6244dVR&KQ>Ik1pi8$#F9eRlO^s1$DO1@Ax#ZfO}?r zITnnTH?&BwJi<7^@-xBm7_(yjnMCrU?^j5k@(zIHJBO}V=#1Q&+~;=$%QKKc_ho1H zh3^LeF9v0}nX8#YK)$_nf(`2^h!wFbtX9Om5mrP$h$=bDBk_OM@iQg z>iEeLe~@@j1ZWsHX-M_sE!O4hW%X?7UOm}~)l;rl&*q^Nk#nGX?QGF&Cpw^ty(n6D zKrbCXOUIp9I$HGVjI2m5o%#dNF`qfZvlJQg`#lOhgx##1WvrZKnaWw8UOB%5_#;oM zLS^M-?u%_!bj%*M%>8Z2HG>YI*P1yQC=iu;lTa{4teMo^N&=TyGk(XUAvKq30_KL# z7}B0<#W^_rUngsZ@rV?$X7M}3h+Y&P4>^*@%faPX6)-2BnYbJ2Xh-_!`T_mds`l&!Ng7l|=^X#tZ0LY15MN!{g zR@o`ab@1608QP7+_nURM6V<(iJR+w*y0JbV1d^GJlu?Mlk??X}>=%HGCvt)K9*eRshaS=l z3!@TxNH$W4o*MR18Ua48QLq)EM3RS}R9g(inDvkGK_*8ZeM(R%#i=R-lVOjQtDIBG z*#Q8=8`CONa`*;c>AcY{d;8-MgxK)w@~i>D`}LcggrW zb-YjFe?Yt|<8?)g# z7X&3pcu(FNzQ((SbPh zJKK*cZ({;P0xS<&g>!ErdJw@rK?M8o%q@tZBs>1PI+*$tTa;P=IiEpIo9o7^CBJA0 zq_nwyH6Q)a)Em8E91)d(Ax8Tghs^r10GA(x5V}iJMDB;Bp8!eTnp_Ffz!2oWo@749 zG%!S#3YCB%E+Lc@Hz*9@j7&q+;B#o$H0z2~Qo#^GLVm|gFoa+v&?c*)5#G{K$VZV9 zfKjP=nyd>!1A;gTiSCgzOj9@|@kuHD)GghJAlM4bbxK$PhVX`~zyO`yn?D8bOv(_8 z!vk2jyLzxgE;U3`Gei(I16Ap5-8-&XC+E{ftOP*dF_#%62E&TC5rBw@CeZ`1OSk zF)R$%gmk}c|5?l43sKW}qtF*?tR8gkNxU*Zfajjmj%g?o5W!#gp4T8~U)GBp` zzfML+X7`9R0ymI0u^?oN&+idcpvK*k5K@fR>HgjHS#^5*1>{(?}b;^#Y!#zSD9)Y6^e&Jzt+cu^ zW5o!}y=tvh>?~r7G!%OEDWT0!BROV1U2R5YS}0I?w|ow(>SEDN^vwY-*KU(K^n^AD z83w@qeGO-#fL%^zI}?;`TuV)o%8G8&A$5{4`tW$G!xF&S=8e=Z7F5FmzQP;%o`{Mk zM+YzvyUozcQaWz!9@#~}?|9z~iTFuc(w}~agxZvo=(K<#3?E2JQgu+I)S~bzZbO%w z>)`^(Fr1H#iT}iN(_duA_k>4#nw?4M_1JbwVzc&REWay|#&oPm1@MRX`}|TGa3hus@I;DMw1AiW-KD140bqTS)#K_2akuD0BHR~>*1hHOC6Q;?m`=M%UWxBV|Gs7gt=QwTF zts-)>On{hu4~jfI1a)0{@H{SpbeuHn%K7RS@!*Oyf0&0r%SBxFHOlLFQkB6ir#dt7 zO5iSekJ&_YQlV-`OQlRh*Bqy7Vg@f#@O8`Nn^4R6?;}n=a1v%fu2;k%LJC4OLQ?PP zgKRO~chk=WYJA}=q19sJ$g3=Lfd5~vQZp@nbgXxv-7V2kDx8X!JR^1=YgA#6gb-EM zvDBsdktTep?v_9NqUw!kMrs)uXZX=!nrlojpZF*5CAIv(n*1KsL_=+j2oX82jsc!) z7fM_7KT?PnJAA9jX$(q^oE;gjf)+joY2sIoH6890!fvbtFO*|izXXJK|Nrlq;#LHS>8h{c<1&nANJ{}{^YDEqlQ;DsbHBDCc6uKx-UG&XF~xZx<*ygrVr8j z^|zM60ja6#v6Ip(;8Xpu^h0TVdRg=&v|w`CcT!C_oijD;AYJDAxr{Lw6ktOVCDRSY zEx}w2X&H0eUwwT+X@r>SsB28(I;|5Ye0Xr;`0c0u=&uA2<5x9&TM|cadF>%JpBs5t z7mWh;A`4;7F>bg>88teZsy@ah1V$Z;zjx$Z*pR2w&3ZvjPa}(!r(bT}fN`%*s%_wk z0UZni@$&XgR~G=Bo)9lrC0To%)}xQr$~Xlsz@@0;YDY?!z|q$TT{O|3vy`H9{#&q; zsBpw?1IHjza{L&Be3fYRynXUG=JSbl(QtJfVMn5_gy>t+<^nrh3L3l)jYqE6F)4F} zU8LrWXczY&{Z*Dz{9xj_S3jwq3&OC}JEyo@&sjH9tmBi68JD@=JVDQw~C4 zQE~0i_pEpXw>&OGAGw!>{7$DUVb|jhzv6f7*M>C2!O6W+9G&s12OlZ0K$5}HoVZpH za<={#5)&?jTw9qLyI#;_bm;|lk>+y~62tuatF^Z2*J(_BLfaJk5ibp=HAID0(z`BK*v8J@xSR6KV8SS@UZ@!c(3DRO@EK0W(YJdGsONM?CMe$d;32)anPBS zH!SufHKF$vNiyoYI`QGwV*Qq6IGcefei#bl1Xr)p6HY-xhwn1i|4v>U%b~-6szNo; z;cC#s55k}p1M;BnJjz9VWS_vp4vhv^ zo9o{t9u}826hW`<64f8GX|8W1o?7gl@PqPAuirgVi2nX7{XJX0$2RiPVjaOW3GE9U z@WWD#aKNbRA`eq8hgV`g`YZ;KLL!0>LTfY!OckLungebU4w$0A%0txvg(*?wq%SIk zrZl}ij}OveMZi1vw>eL=y0cJ>#jfT99kjVxy$1zPidk=0fXs$t)@>)1s7V>a&j~8=tZg?pf?#q(L0zbB>KINR z0u~>;o8qTFCZUf_yY5Q@aIw&IqUt!}b-ZlW8>Eg77*D^Vz7~&Qp=EEM&E!gs%U#6!JA?ZLC_N*+`INsvpvx)tp!fNg6Ka2i)uk z$|HYq4J)Gtf;1VQrQ`3E__@8}Gj#lKd02(S`yD%Fi6EY;lZ~&%T93{}O?CL>{lArR~{=S$VkA+hn2b@gTV#z_pyIByCe@R;ss&6M6d!Im|@R6a(h%}=SsWzd$_k_6=y+?KJe9=zc*f>^1gYd+ltaX+0 z$S+0ey0Fpg|W_9CbB; z55#;zc2rkHO_(~uy#M|1acBK;v+grKAhty)7g4kSh>C!~gWgcoj}Uy^3?Nne!atWv z6ytXhklnCP&*1ONX+<>uKP#B253gdTUeWPS>iC09r1eU#_*FXo7ZTqf@$}-%nQc|; z=JN*-#U;Sb%ZaLxN1#ptd$+nGGsg6HH5}13UZqR!D<#j9k`wX$b^MpoLJ#q-j9pS18RI&lSe{&rcMrBF%eTZ%6URXgd-Y;Fu>UZ=^}#N0sXz9 zWU1#^(XkvUwK*jDJ_=jc@;}2yWgdL9eCg`bGy$y^$HzhJ;j#td}dufMn+~fkYUdTsnDbi%F zzc5`WwcSq42ECXKnsqiJ#Go8USNEoO%4w`j8 z<*VYo#KZ}{0H*4p%-9I(#r|!v&?HL-!dmS_*Db8o)WMpy`b%%t>Q};Axn$?y)X4+# z$oDP-!pqo;lku54evHHy^@{JjU!`BpXX{qt6RZ`IdFAb<#acll&Sb4b_6G5sBnJzb zh8C+>+8UYDLk>2`*eE3^tktjOMJYl5Q-y>ad_qFqtkn^-PLO1`B6PRtaJ7+_&v7R- z+i{Y69TS<9HzgGQHzCs!$^^$lBsfO2FFcnPXkrk3#$=*jQq?5+Dy0lH>turT=X0=C zb&F)!Q7ZP3f+!WcNx>5OtJtdD|BMveC!`?FXX6tlsl~dH5=F82qUx1mtH$%;EXATb z%LiK}7CQ9KuCC8(u}+jp0ms#ET>ZYsFPQpFwn{jvb;E!I`JznbF5yM7RyhEMZ`=ep zs~2byA%;-cB+XdCv-e`G9+RnM()=)1Q2d$$gGbP3xXxy*gsWAIl_%=ktr@HHG;El4 zLkReUv63yWhp{@YStTK2F~^fNR{sxy}pRt(fkY+i8bW%p7<>~{vS(-H+seYR>x0SOuY3i8G*?dv-!sffAyZ) z%dRbDFH;nG_+*lshQ&I@N6`UFV&SpmXh@57Y9aDPcq}hGmRIo1i?m+t(h9Vn=FKN% z*Z)L?WL04Y+-p@Ku-HIJPQ9#(#?>X3RkNfQmE(FL2&fX6Xd~1uu$V?Jk6KpmN>bz% zRIB6_iDlJ9bW&WwCl({(#>-VMS#{DhJ~K~9E7DYFMo4Giu;`5Duz0calHjn4fqS*A z{t1VrhO%2>FMhDFe_imkb#*%*RCjx6?DfJLrWO|Rif^gBKOrynZ|e8E>i1E8g~!5L z3&7}suQLrtYnDXwGH_oQU3E&)R4YciA}PISsrJAa_7&);$wR8fsACowfw46Lwkwlt zR=I|*Riz5LMgTNc!=*xvG(kOwPsVq)SX~IV5T_yYNDrUn7{x7XX*H&G+2&unLMMhg zH-W1$YwOgE7R(OkFh3=4=xwpCb0$rm_xhrbo)U;gIf&1sY%;MN3heT9MJA*sFuSw9 zGcX)P5P4B07WLp%Z09x!FXhbPMk|#4?OyDF+^&q~@IxpDJz)(?umZWs!yqVi$B6qX zR@E3NWg0BY@U^`x!#=zyDQ&dF(PqbKXaFkySbp!(v$LWO4oktBf;_k$d)7xC>ZR}n(?Y|}y_RsvD z&4bCo1qS+7F-1222wv=zQ+U5oUMR1Gdz1~$@SHdS<&U9r!nYU5sU?o8w?#z$*L+5U zkYx|hu zo<{g}8WQG7-Z5S=LlFr??nrlbY$bX%Z^8mx1Vd7_Oipu#I)~O=imEA&;^BSke_Ci< zFTI>Mdh;MB%%hikWJ5bs{HT0@`lS7jCp=i_(9mwEIz(0>FBEt8M=x|H&Pv9|z*!Xk z8!TkmU0N9)LST{$LHRTav0e&|5<#=ic1ZZ7N4^B`Icej~dCeC-+(TFy0H`;zoNdR+ zw$o(Ri7HA2+)5MXiry^yHS)X;zvE4_zLt+Y1l3y*RG*vmAtIHaTWw*Et!DlAB}S~@ z#S%XB6=K6Qsa;$Nq73t7NbgdVl+9;r^C+God5&ic-HPoMWYz~jqT%a?%Zklu<&60^ z4w{Thl1A6)uMp)=TP&o@Vy|Kl-jOW{BgLG3Vu-$?P-cQA{ENxht;?0uFsvOVcVrQT z#oE6ub~WYe{BTE!$?Wb`_q=(+nc_o1#*(L9o|5@}7@CId8YRW%@hzG^WC1AY+Kbun z67l$X@GEyX5=ea_)w zpSz3K7VAP10mnskqBb56Fo%m_3j3qc@DFNB^Y&iWTm4vF=DPEg{zl?PKSAoO#1 zIqcskdJ`jJ#Gc??hr->VHQk{gA%%Uvh6I%>6_D_i33Vno9h7z*h;QJE(U>L=#bC2N zeuKDDdLHTyR1%+WU5orDp_-dv{T7vW;-#>`PFuXu740bXlpb zrLd65uW^PvuDBq0l_DFgJFsIHOnxa!Q5LY(8*y^~%U#lId|v7%NWvrN9;pIr($I%M z?W;Jd;QSz{zr)(*6Uf3J7A=eaMf$C^eTexoIdu*7i5@p0WEen$wfEiE{NC+c;l*$UtTfGPF*^hNxLNQ)9VF z72w#mA`SE1I6j%S#TyV$4P4RR7rh_(<9qGc1pZYLO^L1=o@tGzY0~HCebLHxKbMtL zac7_y@d9}BZ4ZWrS%B1s?7#<=6aq@>5_aeFYsm z`UK};$lmS=Ujp7_SLNW}r1YozVB7(wlp`+DnRc7jk18v0ExCUBwI8?YSdP)l*1d}eq}jhkf`yh1HuiDsOzgUD6yN#fF}sBCv6b(q3r zN~DT8%S$qa)_-I$CEEpciOyCZI^S^?j_G!egKe64uJdq%ryP`5#!J7G$<(L*wY%qw zlrZD>M0-Qy4YU4dQq5zC@bJQRp0mSkaCk&SEZ)d!6qG97towtCaD#`ru;W^9XthXs zBqv*mSlB_1^`SyJl_F=i{-nMf_nLo%%5L{%v}vLoj+zfi!rXh}3onHE!+1xuC5iKg z8ww;v^p-DxI;a}kT{LbgIJUv5d59ny`V(hYHVCtwo4AX)4mSnaKh%T6gexNOtis|%p{8v}RikoeJwRH%sHKD_GOwJp(^4nDyb=@sKT!!BkMO5c|~5bpBrG`YOg0cpv%Y>i!i@-_=9;h2z z`GA5b8yExn)ry4w{%)^|2|OlHyYNdkcYUcs^4KcoRzu{|7|;!?A&4ud?Q!L=wYQr| z?fu2K{#r@ePGbPi{+sS&_=sSj&c&G}92VE+Z%d`@z+kTQdVvriNb>F*8$8_G^<%~o zVD&`uFZ)l%_90Ln?}rj(NU9TzCC;I0s_Bz264>4<n~yQXj*S4o&cXmmCkwc4!NJ1muA# zAyI*#n}&Q{P>n9=zoddWQo-#yey)!HuEdKY>4u3qzEH<+mH2FlX9Y+@N9p+ANqnZn z`*i#zI{pcX?*y=|S5}KP8{Xtzk^#aR(b$CoC-P!@dMaN(T_a!3Ix*kD7*NT~o|SNP zc~4qbJ0uFHs!rQ6*n8|oig7x&n~yqG;0yDyIx;vN+aAtvI{t1xIzdGz-l9JJwnl%O zm3X_6l&eN~7Ib==KFG}3!F>gu1s(Xt$?QnW{1I*-%z76ott+x9IHNhnI`2vzRlaqW znxOy>>k>Ms`xP5c5-u)3goL+xxhR?FC5AXFrumRN;L)5WGHg0Jn##HRmFY#%0kF@@ z)2(L!e0F4&CFd2K_Vn>ZF%4pTB-!LBf&{{zXl1$;mZaC?lE@a3S%o*k|wSd)>+?=72&$D8{hQWg*|{0^q>`?Pl7>joTACI4F~NMvLX==rRt(&F7|$aU86k%q2~}KFjUqbIWf<0Kul` zJ&ZNZm4kt*h*!`G(@vrW#|99WG&P0rao{3nj$#D6Dt zo~i!ubdF2ED}gDwipEhJS=^y5MBNO;^>XCThv_Q{i>fPq;|g>LA@Ql5wEC9I%mh*tn`{QQYs~u<VyuiPp(fYdpou(Rb zuQU*Sj=s&Mmx*t0D(nlc*>HyU;C2$9eIPn+l`ZTNif|~NZ~iFLscJN834xp&1hO=8 zZq*VBC+wBJGS0hThv(|FH(2y^r;C$V_=gmkl%labCxA*=3hgFDVf9^<-jNpno_HL^ zLniy{0PosuQh1qcsrX0_wIRiMg=At5TlZ%IWugh#L!hCQACTvdrpY6J28 zbo?VaJ~oy37bN};9e=-$AIQV{xy0Y8|#SY9@>R^*nbFbp^=%dCO-Wg!H`Hj#f^E#hSPKwzc5H`?|5RFi2Hlse z4L+<8yA_2gXY5PGCzQAvZ92xPe$AuSs?iRL!H8264#1eO^6TY@rkA1RpPjYyy)$3M z*xs5lo5c}Mb|6}Z5{Jx9R;C<}kGgWZpu0fw+n5J%p;`A3na)Dxa+=+7VAc7F7CHh= z16E&-YdW;=k4}-~dKkv7+A_d=Y^+rJ6dPyk0@hH&ztc1z=1{Rb@*gb$GAJmCxF&@7;_CXImV0$7^`1Sx?6#7=U0X4dE-sj#$lxtDfcFJdq^@9%(fX&pm)<}!;rA1PX zs|F@c4t-!9#e7G+tXbNSCj`rn`N)l;>{o%g=5tN7(|m6B?OgowVblpNrrIl=8B2TN z+8Yz?-GK=jgw*XKzG~poOWNBf_#6T^Zy!!4ik6&CAyaGu$5o%#V%?H#LjqE!T6HK; zw5!I^9oDZ0%ND!gJBE=SP&d5w3-`m-$>=k{ma~k4nl!|r{sDWbCz3jR9`R5!^8oWU&MZn1k~ zMn^HKNu@#ZLLpG+ZQKLQf`*`1lPxt}uS0nF6!spP`m#g>H>ig{WZ`wS#)nzInaVP} zq9ZMXHIW*$yTUP-+#C9SB)=BaZ@r90t2ZsIbIKC<&r&if=+|??RyYqQe4cSnEap*t zy(c<>-7_R{fT}p&9xx4{`5W_q9m^Ay#0!lXe@BoED)^(a;nG z?f^!{#Xn!<1-a^1cIJy=6n0vvxlA3tloPuv%8}KFfKKo-E`$Bi8=6JNDdTjVOL=le z#}VAwb@8vit&<9wX;lbkN>WzWBPLf0iFVMhn8IV+zBH;&PC|y=qjVfuov+I?Z2vKx98))7| zy_$EEu1KGp{l>B5>YsBqDtIMD7WOJ~kxu5&6~1}y#(J8YXcOv0X`+?05X$q%(4Z>o zH#Q?Rkag~6TwrZN3>2Fsr3g_NkNEHk#;3T#Do1?6za9l!V8ppc;R?M>^?oM}mi-PYMvcf@|71j%ft!{J5tYJu zRQOv4Z?pDp9^a@?b2q)kqBQI56qW>|#7g|%gHc+b=o~FayveMOQ(!A&`SsNcm~`!9 zcbDtJ{p{o2a&q0Z%^hiZ>%M9ISqILS9d~5D zCUkM2XnU-ZiPY7>F+9b& zwm^jeN1cTMJJM#VBC?o(Yh|Po6HurJ^-}9El&mfl6@+t@6kXNb;d76y>hLn1|LhLm z^Z$l$6so+CO-y@#5_ML6Hc)dW&it<=5G7FM{~n0`f8wGs%%VED^RI{P8*tGLuz*T| z{mr;YM3>!|(E97WWD-B<4Y0$4jNWr9WOST|^*FKw*?dCcp+z}cCBZhG;B`q*L4w|} zc0)*r7MO*!-+`;JItXt;%Ma!Hdl+Nrmbs$eQr9W7^RE5KqRJ$8`+0B7^(geNV6GeF z2TvWn&{r)AqT3Z!q=mt2w@E3=cjp3iKPH#;&H`TX#y9Tb*9Zh(aD`Rj@&8fT;>}uc ztXyB{+M|P%Xe4wjmCL@{QZlwgQ@^56Bh+Tk*b;8vN2CoeEuS@=N?Ci1czZ@`)B9;_ z-zQj`(XN?|GmtfOyZDQ=@)P~QMR$D(5xce~Zyfr2&#;{Zk}kb0y0^z0&y7Kw3p61X zQIs!?kjE_S^n_m!Iyi}Z^`+|HIMP=keUm}HfFI7-!?ApQIQ!KlhI1W(;DO|D{!QWO zuTypkVSm8bKvGB2E?X-&a0l+DQGPs2tS>U+_L_vq?^WLMV4&`DhMqI)en*7Ik)KZ{ z%qzL|0D7?1`eL{MlY3|U0#tMrNZNR5jlU~bOCn~)E@nG2_whmyj>Zon9}(SdfhKJ2 zy7-A&MH6xrK|i^1p*Al)KZuhg|4y!Mhs%tgr$;;08*!(DJc75yFD0n>ympQX1wOzC zA~UX)3_i#3Ur=1|BH;Ua0&y&eJL54`0%)ceZK(_8>IbfeNitDqc%tz|e=ZXRf5RZ% z-{-M@_YZok5c#4U{3^_p3o8q=!-WOm!a4VM%(dQwVi3k*$o+qxYaL3wYIHSb8PRtW~E&Cq%bp*cH+ z!9Q!pJd$c=i5;2y8@fGWh?fQ3b+%DWAaTn>TY?AF*TtjsXhY5g130uJ%~W;R6Lg|n z3l_<*17TRok48evB-e!_7yH0+gA?PiudkB-hu!}+JsoFlG{I{-(pEhzdt(7BaAGfV zYJpCFi^ij`B`Ab{LZ@?^yT@9}P{p;v;u|SRTj0ubw`HJJ4%eMrx1XGQ$qU)~ZJD}* z3b48fLdL4r1oEbm}$qb3Lt)WPkS0Iu$ zd9^IrK)|=)HY7F`efe3G>Wa+eBHh`8d<%-X<*0C;XF*LR_Tq~_H!J4a`6ZtTAc&?VFQq4+VWG<)H7L7q8Re&+KhssK*Y>q2T(q1KVW{CzYDxtH^7*O7XO<`QX4k4gqOVwkUmLyJF5R~(LkmJ{<0nMJye8gWHqsQ&bo?uOyaz9M z8hYAy;9s||;cqSaUv~#E37nm1XHqMu0p1zjZ*J^oZbX28{*yuEL8%wm-B~2c_Xyr63x2EH+TJ!pjkUU*Q`StVK`rqPHSTjtvWY1nm+7IDlxL>4m)m(Z-g7$ z?#7J%yG_pYznS$vCxaB^Ul8X83{TU^@km7Xa2X|!*OVWzcJGPzQ1Q5YuK>`-sn* zAP}+zNwE4T_#ie`O5ifS7t-bJ2HwQTilMt5iF>fW_r#mt&_N8f-V(F1@Ts09YYwd? z2YS;CW4ryRZ9BKqWIibGHY3%ZYNYaRsQ1f_DfTJG6yDoc%J=gO9B~_7-rLlBmT{;3 zPUBAATh;qGW1O8o-W%2X0)s}$zu|vU?|p3tva(aI58u5x$50!+ne>d%}IK@dN+(d_C7`*zGtfUG-Gm#56aKj?QYWJ(W>2I6A17 zt5`fj;e;QUY?81`kQ%b;W3#XvMuJUDlyNSWR7JKa)w%L z@vHbviC@ldD&DC=E$MM?LTky4_jlH|)W$JcXh9P!#(*M(c1tt!t`F=pzmp3j=8Xt6 ztvZ;O8+bNvo>_k@F{+rCp^A1|qW%oKw<$Rp_d}1WxzZWw=j9NLGt@6Vg4$^1xA^P{ zxp&&s`Bbl5Lm8DLnzPg6Z&6Uni9;laA0=q#1Jz4^3v7CrR%GT~7q}uZ6tQ+HQ?+hG zKaOBkT>_$N9d=f&ibXQLt++$ibw5kiGwME^=L-atOAs0Lo|+AFb7$TXvrecc^E*#6 zih0Y;x)A8Y{EnZvylS)VL3w-h0eX5DuscBNUrfwU0zx8BT{Zvsq=*stYFATTiR zp+NtsQ?M%r<#=I%g^`% z^eal3*C*uleCH>Mpt4t;9Ja;@(!`iuh?ews{U6J@s?sy3{6|||L=VD2teQtWbcEwzh(CE!ip$83e*5rUQ+!6_9 zM%;F}RxE{kw1V?OtA`i?J6ht)5!8)G7cdO9_5VIuZ= z9V?0skzqJ`(%bcyYl(@>9OBAB;Sf9|HYX2Zxuh2IWqgX?8e09T5xCyvST>}lpXyTl zYL))OWctZEJ=Ov;APH*$!3O_yC$3B|YvI)!fTvoo+zIuvD_p;d3f)}28(E!=9ls}| zHJBPIwIee)L*V?Ubftkg0zWms za$|G&{AQZRXvg-yf8u~E>p(d8W54-_*S-7W7(^8W`ufAKyGI}CaNBDGPdh`W`viaH z6mt@H_}vLRT*IHbLEGfSpU}hv`1dKnMy#Qdhuu35~thT>obRT-()W zLS`rh%QTqOdFk%aAG=3)bTkzRk&jpQ9p3%#dG}+*W?_E`3B+FYiWsAUw&j}ECSOL( zmw`bh9_3qom$p@>S--|CS!&cyA(62AMx!=AEza%@T>8=I7rUf7L8eb(K&Fjcc#b2GZ~?=qiQZm zb_ZL}Aqhh5I=)FJ5~oYzxOPh)|B)1(bxfD5z_lxAcTS7U%$(+&>c(U|+$hIpan#Sr zHER-dNMct=y3mc5S{kY~-)F>MlBqzf+DaFbP>thMVMOXH)g!u+2j>gyW{$ho%<(^N z=FEJ`%(>@JW=?6NnX|0b%n7!cIjh^7>&K9C2n97OFN?W^JW+S1CtP2^2TxQ|M+m@;blKQa?cMAFZ)Sk^$)_UYmc~V55*7wG_`Rw;^Nlp4CBZRDR1mL zoK|{0UD|!*v!kz@p+*Wjyi4VMYxU#g zK9rrE<3k7rRYa_f(BY;-!~dP`(SO~C!@HyxUbCTdd`w9ELuo7c9J@sYACMqsE?q|t zA5OcE7iqS+9z%gHIvnWl>}bS$z<6GzgKGc5BJfr{pfVC8$#dlRih+lBshsP6MC;zDT_k4<#)wX=?-b75 zFk|>rTuG84cVS|%vMv4$KdQJP(Meq}_7||l?QmWERBm{OhPh+vEBm>E#gQsfMdddv zDl01scb@nV=~Jg6PfDf@I|L7 z>JGUGov~3Rlnc)AX+1pHb=ejJJlG~~<8M25qG{<%*_<)f8H&!tJ1%ut5DoGt?B)Hk z!DrW;2R>tJ_Vy4N${I1LN$93nd)p+)38C<_>GaAI{+`HRLZYkR$gEiR@`fQcMQT%Z z>);fjx|&^)zbHkYzTx}%1*G*wl>-ySumE>TLimz#A%)B*k5D`4Wmc6nt5j?Sp}%HQ zxDj}WJ}tkJW>c0Y`aOx9R^puP4j+es646=o;=fez`AEI^ij8{MlM7^UKsY{ zbHP?2$>D8{@O9B5*G$D!*vW0><&>#fe>{*>Vi|NAfbBsYKEY!_BNnm>z;gh_oV zc=mS}-{WsKyIOE$ zXaq_L4+$<+Ma1tpyQKItDn@4K!moY%fv(cJ93vg+@`mv4>t`|o$}oD`M+##Ee-v8` zk)ZNIJ8f|9iT|6T=;FsQM(kIX-*wPjx0Nme9d8JK(FTBWhfg%GFtqv&hQ2?!BQxKi zm0+rM$PGfL)|l(XvF4_0`Z$g7K`|=X25tT&6{~9WWCqUT=t6q%eOfaLw=;#@Jfbo=B4osr9%)q@A+7DLm$tG-{_k=#P)#0G*-^kF=kmW>N&}crF zIw9pTXI^<5SH)9(?{OPo1>);`Rb{*uR`nDlDcz|H04!%Y(T?b#~5tD1Kk`*x< zHFs{9`Rf0NYOlAQ+=OWFUgx@Th;a69M1gaU4w8;)X^8uyjMU1H3wbp-sbem`dD}7eO1v^ zXUXhYQ>ST^EO)JazY)OyUQ=C@D1uJgY6?J;KJ%~Y7Vz_bI!qwuxdBdB7zr1EvXiSR ztFV}El{;d_d$52H?Wrx;aI?vA)WD@pANGyEv!h>QXm{;~*|tqARb*@Evcv0;MALi%-#B6=;iu0zEzE#=fwE z%Q&7vV{0F_8G+e2J28V99ch1@%5Chyo27)`uq$9HHoj&AmT?jt7$zgZQuz}G3DzUv z3M`gHaehCDIi6)LkOwT3I(cT3b#%VDx!oE}IdapX9FHwHvq`!+G(!AOFxt0Dl>&HD zMAyZ(B<%I%>VYwJ6i{U6=K{7FoFzSo0Qvh((MJq>S64pA66MG@+T5bGAIhcla@f=~ zI42Hlu5g80o2>q2BaG~_aKEy($GZBIk!o03cy@{cHi52twwB?$5N}1(spy>fWgQWuLY5%W+LGWq`Ag41>Td9<*-Bm zI~nbBA$~xuE)AL8WpK_i-p|81QO-R1U_eB*c!|030;H9h(W;jUYENHU(-zud-*7?v zu+G{uV5+?E7W275v%~w!V5+|?TYJ)YBbWx!75_lOhXMo5=Uxd9CPMd^gIp}q;_%+G zwI4|GF5`{B`OSU50&i_%d+CZFB0VE@$S|6Y^*5i}6@9Y5!6K6)QsOV1xj@jCyQ1a7 zGi52lRbXroPy*HoWcZ@<5P^Myjz&q*&RiXcMS5cwhOVC)bIw$)J=O>atUXoBZ*Tz6 z&#+#C8GHO4SjgJIa*~{Qh4ZA`UMiOX2h*OXMkMi~<#6WgR`zZCkA*(8;Z(|*)vE2? zGM!CreM4>Iof(bH7%0l=cxA;kE=D8F9-V56xVf?RWNqM}jDJ?|0l_i~Wv?sTH;Is( zcOs-wW2G!h5)dES)fFGuOEvEvy$hi_`u#m!{?Uzb4?eDXSqpmBf41QO@&3`T#7FeN z)aXX{XwD*Z1qVSYle6cGm+YevXLhPXxk6OK`&M22^w7Xn-%cO1JJ5H`oh5Z!2}P<)@K4+pDEw^%!;kglx=;cYU?vs zZGGn2tSeMWc1 zenJu4OxcU^dG_OwsGT@2PVkt2I6+0?lL{M(%e>)#c=x{}HX`EijRc>1 zGMYX6KN*b>b4zISF6Z-X3pk_sMZ1lk(>9;^i?@_))AqVZKr{B^Qt<^+7@kYFdYb+* zK71JGR-t$Ldvf0Ngbu;2Yzc0W{`7H%b0hpFf#j%^i zLwG^H;^#AE{$CaoZ8mW;Bzpx@qY{Oh?AyQ0ynA5d_Af8E+vLR2?wYtn3~rN%OWL-- zJNrm{LLzJRJ!H$2+{~y+t~QfQZIbC@UI(}HdP%!Uj!f=F@XDirnTl}Sx*{m5R z{I_%G#Q*0EP@bwF0b0H4JUsfEa3_I*U0;3vi!bk;n;>u#*asO18AtEJZ-d4-TQbdT ze8Jf;Hpt67(F{vq1P7IwV;A%rP!p@}yZpd|M+B?v+Umv8bLef;8SZqhZBwkn+Rk>8 z*9_39oo3;#cuSeNLUs{3>g_bM)6pBCBbeDAfXAJ z)JufC8Iw9V@TqXI8wU-}X}wbjsUEQ0L<1oc)wWyrN)VrljW+m#)IvF`8~BzIbip1y zH)vKFkae`o$wAmxAb;W?enTnHSyIZz)P6!=U79R}`XQ6va*k;Yye36_)Vm1I?+r#Y zi~YcGjmLPUpbK;Z-+YW7dWpc;?rey79@`xD^j2Hc+m~SQ_UT$~R;v)odg@%V7}5?T3pNA?y9}YxhO{A0c3c7P*Zbjv@F$=KbPh6b>(h+uKA$)6FUZF--o$ zQc3}(;9RFOA(4c4b53`56Tu}OF5f|*`_AX5ksELGu7AT5%70csLOZeRNSpIF!@$q+ zQ$%?%c8{F@1HSqrOR)N#neM)RYPLJvif3Cp-mMGJGAe2ukmVjDE{w+4ooifb(;9pr zY>6Wou7s8rs>8&iaiFhj+@RyDQ-z$wTNBqR9(Qp;q0cd567A-AGolpbKPX+upvd_6 z=SY)ig<*QOcz%GE#Q@WO2?f650bh8;g?y6MM@yfc(?c#h@6z0!C;G66;=iKlJ5qD) zEUlCssbj!*t?)`z(il97E3eZORx{jbZk&;3u)4n`OP^tECf9~Rx2e^8c(alhKoZW9 z0)MXy?B9|0n_@BbzswU!dz_6!xsZVAew6QKTI_D=33~dA>5`IitNHG+@b5P>-JJN3A!4%J={kMEN1!-gczUfT%zwHuK zuzIHvyk0q^qd755{N$bogh!s7(^d<8R5n3BC7>oNew$)o;`?``EZQ-_W_hE9r*H(l zK}@)su!BwCFd2qhLYW&Tr+S(?hisUT#t1SeX^*PHJ+x#hUWPw2*WE^`9-QSzX0#Vz zD~s#WRj>N^5WSKQ5m&oMHs`3bleN0y1)LA{LrjO1@1edp?#lIKbP`M>n5~1<_JLgo z`w$$WgDC_vbv@TB<{wSSYUcVHS(9cRQZCH7R}G_6hzdWzi0Pzv?XE?L z_`Rc>k`A7P%;mDYlOn!byZ2%#Vsx{wp6g5Am#CMdSRyI7(~~czs@S?Td-4~VL$Szd zEJ$w_3TKNghIpW>_91Gnd!2kcQe-s$p+*xjGclTpab+~=l#FI-><>~&_ateDcUMVu zobFdJAIU!nLb3i&v&`={yFj(Jo9IXz1#6NT9L;R>ohH!6QmeWYD{oNbiXlk}U}7(Y z-`8H5!mZlkLW^=3lfQ#_6gU8;i>R5MAc=KwgN*F}{`yAKASwq>nrbIuv9;^}=u zprT^wsQZFTmM$E%?16=)mDM8`7^PKJs|~zvja;yRzsgEPZi%+q^X+97m8Ip`^Q#uExNFHhH`uSLx+*7!*;N0A zSC6I7_Pa(@@wkU)g?_4WxTinmP-Ax)RBIn^m*25=;wh^}$})!Tpt{mN++N}=bWiu*>9*(R+jCVt z*-Od;m4T|cft<=^OK%Q*EzP&?%3a3b8{fL?rdyU76Fu&UQ;Y?ROBXy~FPqP_FEkiX z8C3mWe8yO;tspR3^20$wQ^~5%$qWVDXZwg>x(CR|Os%wQ%XO`Aeyh zzzP*0$1*<6UsgSG+5AOI7TB{iIJlsq&Ye#eK)wZwgXIrME!h=I7nUulst)9+dL_Z! zCFKiCSKg?;dJ`+BW`0#UC}XU>Dp)Q7Sq}D!GJECX2dkGb26nnqQT1T|Pu_j`-xPV~$ynle`wrjbk`Q!?Rbdim zE?HDwx-h#M+*)2%TD}0ZKp{68mH(u?o_?)R<5{KBU2iYHPl~ISY0Gxanx13uBPE^H zoL*I`l5S+WvMWJlmAZlIF$w&=GN2=pp-DQ$eXNyQncIH`SEaOoE{#~Sta2&fWn^bd z13^brWWUCqt-flEXCIxTx{!P3WYK6kbI$UmcT394Dkylwh_XP{`~{_Y{Q!)CCCf@j zEvl-h;p37(=`yL7^J>}GRjNh3V1DKN1xo@CCfBx#&AP5TTKXjavX)dYE}dUVHl4Bu zpGraY(YGX;JaXZEi_WZjnc6$4Ju5rco@ZATR1(Ntc_UKZ+}s>v*~pcGzL$-B&{#0? zL75$5jI66_f`O}&<2oXDMd}M+c(MZxV_WeMK1S*nT5uv z$|?|Lb?MSFsEGN2V71Y6no;)U83IIAB}*2L&7h+N#t;PsJwHPJ$v=gr!EZ~eM+sFi zqFgZeqPb-Yt4CJeRkrXRwguo(cdU2fs?zxjB_&hBOe&!4Ra8wCpvq@8@M;g&m_M16 zy=FmB5don7z+$7MsiJYkfqEu?cBY;Aek%Qp_kte}IODx!t9i!zurDs`{mvQM!M)$@ zFK)iH_j~pOpIqMaJ{Mam{*3VgFNVzc?Gn~sKU~K zZ+>;aEo2by&@hXu$*BHi%M|1oHv-XybK*qzbVb*Ai)K!Dt58w#3>+DFeZ0&voRf-* zr_U@YDV{dNa27hfMTXNi&FysEVN7tk-10L?{kY`UKfO>M)6~z@iSn54gLI!%3=1$} z=JY#^iO!;lZlBu)83;Mg@3f*q=GyO<2`_R^pc?r|c$#|>4N8WmyJr|Ks`X9bF@1&* zV$BRNCjq6s&k^p@XRhL_D(M@ zGQ2K`L9cH*MR|)#rWF@Xb5Ea6aKjI`S8{dCmpAsx+f&nPbT6_c?@ zPhXK+_nar)VgLywN1#ZRRa6X`G>VE@8q;}9obKh)A~JvsBr zSjAt7^!sed%Y07SvY!w0v!5>pWItaQ^nLEd?2s2)+PO6Hd= zDOp-Fx8zB6n|ilnuGoa%W1af2W3}Th#~Q~f$5`iD$D`_IRlor!iur>I6(tytQcS=^ zOhOsMqHl?m zI1)%Ag=Gx#m-F%TTks?N7`Ng!ozcA}M29S5#ZhmRy|#^cF3iOo$L; zH?2pa>+UgQxOTzyc0$O+k01=ElAU^-3+OhX$D%zV(k@CXCx{+B#e{`+7I6kmnO%>I z(kYqCUE!sg2)88kXez0(XG|9isbU=rYm&M|5Erc^S`NECRO=N%e|4ytYewM=dpBkC2UAO`%$UKzKO!*SUQ4LE);a6YRxFAmsA~?ajVWtq6E)TmiQC zvkQ^+A#QNjHFH*_f3>~dOaCV_SCy~M7g}TzuJ(l*c>5_Az6R0Y z7v2S&dS zgvV7c^EZMQWLK+bZ?1K5MkU1nM-`O!6mnl3@Yjnve~_)K1?L$O;H^P+rQFDDu#E51 z_L8-LZ?+EcF7M;gTx2?w@zF6~P*UWK zCxyd?L@4hE9FD|jid)KnT$uDZrTYV%7$w%~Z;M~*IlZBE@5-$k(;K3+hwk)a@x3e4 z8(O02jUv5qd3s~(){WVU|Ji^;(r6QUdy-iR({K~1mL*ziQrcRwQxl1BmnM>0yo)BC z5dED;GR6cDGjgJ|D(IO!bwWSlm9u8fC=->lW=%DZ(?p240$K;7sf5gRNYqWS$TE$= zcd|#;xEa>A>&%RViPz0V{p6}hQiS3f?Y^`z)e>b+#-n$IBQes6Yi*I_Y~d-tx?EIo z>7Gbxkr?IP7VZ*t^p&XALoC5AU1!RbsUlHY?+ek%TeVn1>(3{>yizO(b9GHW7yj1H zgxwQKwCZGxp4-wRylsgNEiOEfB})^GPKY&|Iy#3d;dWQ_<4jNSEjn6Q%x#O#Z)Ku2 zakJK;muqdQc|_4@uIxplnXCFP-lk19r=j|Ayj2&OUiL;;9oFhwA0!?Bp;wM^7d zMd4VFUB>qFGm%4itKP+5)~?EftCdNSgj?n)aVOj|Ao*lm>>Bi$OXKU-p8NW^UY4Ln*@%iw9oV8x>?qp*FtTW97~kK!`L zlj^1lNh+i-)nZ)w5AZIJBpWjraEDSIO(i-d8&xS8d~tREbER76ppx|@!9*^ptX8UN z58l|aNL-J}$UK;&2s_BY(+VR5x6Z&k)>g+EyBT{jH8IYknwwu?9O_rD*V{OXqy1|9 zsbqI5DcgN&)~4cdqR(=7aUQXsk1U3pS2z$iQD&Q?Sk}t1_^1sz1ZJ|UdMuG++;?2{ zE(gYEuLl{q(h+(%@=4jcP!AR0m8G-s5OAF=b5N^!vMlCiw+TA9cK0U`6^QmgJy;PYRmD zktD4zNb8Dfese~|*n!P5*I<&bD9dIw=nbP3ES%q0w!%^q$oO`>e3d7+XDqnKf~PI` zsRi>UWZDh0pwoiWELd&9kOey}c#8$sTJRwYZnEGD7Cdgjk1bd@F+oT$7qK#D$_&kcV$laTX(q) zhO#1G846|fMMd_}1B(i-9Tq4kDwsWNN`BD_UcdF8yMM8QTbH$Y&fVq=G4ZtD|Jz^R z$!&?X%{h0Ql4-}V=FGr>LnoXQW|l!%#!{jWHn4MnU!pWth5~ zQ%1gRP;c)R^yc>#@hR}$K@{`YfkWQFUl>B1!UZ2=+TfRXzqn%NtT{#hguQGB1C0+D zW)vTRV;6%5j^sl|5O*k(_A{8^P~O2MUog~Iq4X+KCzoHo7yIxkUSWt)QTjE8k3EVP zFz$24ETTsq@zOu3R{`v&Usj8O_FN~s*f=&R&*6ScTgNimI(%Mj9pSC=yxKa*ft4EPYRfo)!lUPC>vfd82C1$? z$I@7v#v!~u=!G<2AJJ!x@+{3J`||Ri zAlS=&YZ}|qcs`96^NgLc_xizk&yk8@T*&>&G{zf-GICcMwpZlUO=&!x#_>VM;X#Ck zQTR-nN-~~Gy?H0be3=oo*VaVYZ z=35$-1=*svu(!8}GUAqhj((3zLPpV%^Xm5~P4B$=mEL%w&l^u7>VL$@ -o/$(MODE)/%-gcc.asm: %.f; @ACTION=COMPILE.f build/compile $(COMPILE.f) $(OUTPUT_OPTION) $< +o/$(MODE)/%-clang.asm: %.c; @ACTION=OBJECTIFY.c build/compile $(OBJECTIFY.c) -S -g0 $(OUTPUT_OPTION) $< || echo / need $(CLANG) >$@ +o/$(MODE)/%-gcc.asm: %.f; @ACTION=OBJECTIFY.f build/compile $(OBJECTIFY.f) -S -g0 $(OUTPUT_OPTION) $< o/$(MODE)/%-clang.asm: CC = $(CLANG) -o/$(MODE)/%-clang.asm: %.f; @ACTION=COMPILE.f build/compile $(COMPILE.f) $(OUTPUT_OPTION) $< || echo / need $(CLANG) >$@ -o/$(MODE)/%-gcc.asm: %.F; @ACTION=COMPILE.F build/compile $(COMPILE.F) $(OUTPUT_OPTION) $< +o/$(MODE)/%-clang.asm: %.f; @ACTION=OBJECTIFY.f build/compile $(OBJECTIFY.f) -S -g0 $(OUTPUT_OPTION) $< || echo / need $(CLANG) >$@ +o/$(MODE)/%-gcc.asm: %.F; @ACTION=OBJECTIFY.F build/compile $(OBJECTIFY.F) -S -g0 $(OUTPUT_OPTION) $< o/$(MODE)/%-clang.asm: CC = $(CLANG) -o/$(MODE)/%-clang.asm: %.F; @ACTION=COMPILE.F build/compile $(COMPILE.F) $(OUTPUT_OPTION) $< || echo / need $(CLANG) >$@ +o/$(MODE)/%-clang.asm: %.F; @ACTION=OBJECTIFY.F build/compile $(OBJECTIFY.F) -S -g0 $(OUTPUT_OPTION) $< || echo / need $(CLANG) >$@ # ragel state machine compiler .PRECIOUS: build/bootstrap/%.c.gz diff --git a/dsp/mpeg/mpeg1.c b/dsp/mpeg/mpeg1.c index fa708c19..968ccb38 100644 --- a/dsp/mpeg/mpeg1.c +++ b/dsp/mpeg/mpeg1.c @@ -32,6 +32,7 @@ #include "dsp/mpeg/idct.h" #include "dsp/mpeg/mpeg.h" #include "dsp/mpeg/video.h" +#include "libc/bits/initializer.h" #include "libc/conv/conv.h" #include "libc/log/log.h" #include "libc/macros.h" @@ -455,18 +456,6 @@ long plmpegdecode_latency_; static plm_vlc_t *PLM_VIDEO_MACROBLOCK_TYPE[4]; static plm_vlc_t *PLM_VIDEO_DCT_SIZE[3]; -static textstartup void __init_mpeg1(void) { - PLM_VIDEO_MACROBLOCK_TYPE[0] = NULL; - PLM_VIDEO_MACROBLOCK_TYPE[1] = PLM_VIDEO_MACROBLOCK_TYPE_INTRA; - PLM_VIDEO_MACROBLOCK_TYPE[2] = PLM_VIDEO_MACROBLOCK_TYPE_PREDICTIVE, - PLM_VIDEO_MACROBLOCK_TYPE[3] = PLM_VIDEO_MACROBLOCK_TYPE_B; - PLM_VIDEO_DCT_SIZE[0] = PLM_VIDEO_DCT_SIZE_LUMINANCE; - PLM_VIDEO_DCT_SIZE[1] = PLM_VIDEO_DCT_SIZE_CHROMINANCE; - PLM_VIDEO_DCT_SIZE[2] = PLM_VIDEO_DCT_SIZE_CHROMINANCE; -} - -INITIALIZER(300, _init_mpeg1, __init_mpeg1()); - #define plm_clamp(n) MIN(255, MAX(0, n)) void plm_video_destroy(plm_video_t *self) { @@ -1113,3 +1102,15 @@ plm_video_t *plm_video_create_with_buffer(plm_buffer_t *buffer, } return self; } + +static textstartup void plm_video_init(void) { + PLM_VIDEO_MACROBLOCK_TYPE[0] = NULL; + PLM_VIDEO_MACROBLOCK_TYPE[1] = PLM_VIDEO_MACROBLOCK_TYPE_INTRA; + PLM_VIDEO_MACROBLOCK_TYPE[2] = PLM_VIDEO_MACROBLOCK_TYPE_PREDICTIVE, + PLM_VIDEO_MACROBLOCK_TYPE[3] = PLM_VIDEO_MACROBLOCK_TYPE_B; + PLM_VIDEO_DCT_SIZE[0] = PLM_VIDEO_DCT_SIZE_LUMINANCE; + PLM_VIDEO_DCT_SIZE[1] = PLM_VIDEO_DCT_SIZE_CHROMINANCE; + PLM_VIDEO_DCT_SIZE[2] = PLM_VIDEO_DCT_SIZE_CHROMINANCE; +} + +const void *const plm_video_init_ctor[] initarray = {plm_video_init}; diff --git a/dsp/mpeg/ycbcrio.h b/dsp/mpeg/ycbcrio.h index aff70985..c2a787bb 100644 --- a/dsp/mpeg/ycbcrio.h +++ b/dsp/mpeg/ycbcrio.h @@ -1,7 +1,7 @@ #ifndef COSMOPOLITAN_DSP_MPEG_YCBCRIO_H_ #define COSMOPOLITAN_DSP_MPEG_YCBCRIO_H_ #include "dsp/mpeg/mpeg.h" -#include "libc/bits/bits.h" +#include "libc/bits/bswap.h" #if !(__ASSEMBLER__ + __LINKER__ + 0) COSMOPOLITAN_C_START_ diff --git a/dsp/scale/gyarados.c b/dsp/scale/gyarados.c index dbc4f6d0..a2a01854 100644 --- a/dsp/scale/gyarados.c +++ b/dsp/scale/gyarados.c @@ -30,7 +30,6 @@ #include "libc/mem/mem.h" #include "libc/nexgen32e/bsr.h" #include "libc/runtime/gc.h" -#include "libc/stdio/stdio.h" #include "libc/str/str.h" #include "libc/testlib/testlib.h" #include "libc/x/x.h" @@ -44,7 +43,7 @@ * @see Magikarp */ -#define M 14 +#define M 15 #define SQR(X) ((X) * (X)) struct SamplingSolution { @@ -148,9 +147,9 @@ static int Sharpen(int ax, int bx, int cx) { static void GyaradosImpl(long dyw, long dxw, int dst[dyw][dxw], long syw, long sxw, const int src[syw][sxw], long dyn, long dxn, - long syn, long sxn, int tmp0[restrict dyn][sxn], - int tmp1[restrict dyn][sxn], - int tmp2[restrict dyn][dxn], long yfn, long xfn, + long syn, long sxn, short tmp0[restrict dyn][sxn], + short tmp1[restrict dyn][sxn], + short tmp2[restrict dyn][dxn], long yfn, long xfn, const short fyi[dyn][yfn], const short fyw[dyn][yfn], const short fxi[dxn][xfn], const short fxw[dxn][xfn], bool sharpen) { @@ -165,7 +164,6 @@ static void GyaradosImpl(long dyw, long dxw, int dst[dyw][dxw], long syw, } } for (dy = 0; dy < dyn; ++dy) { - /* TODO: pmulhrsw() would probably make this much faster */ for (sx = 0; sx < sxn; ++sx) { tmp1[dy][sx] = sharpen ? Sharpen(tmp0[MIN(dyn - 1, MAX(0, dy - 1))][sx], tmp0[dy][sx], @@ -218,9 +216,9 @@ void *Gyarados(long dyw, long dxw, int dst[dyw][dxw], long syw, long sxw, CHECK_LE(syn, 0x7fff); CHECK_LE(sxn, 0x7fff); GyaradosImpl(dyw, dxw, dst, syw, sxw, src, dyn, dxn, syn, sxn, - gc(xmemalign(64, sizeof(int) * dyn * sxn)), - gc(xmemalign(64, sizeof(int) * dyn * sxn)), - gc(xmemalign(64, sizeof(int) * dyn * dxn)), cy->s, cx->s, + gc(xmemalign(64, sizeof(short) * dyn * sxn)), + gc(xmemalign(64, sizeof(short) * dyn * sxn)), + gc(xmemalign(64, sizeof(short) * dyn * dxn)), cy->s, cx->s, cy->indices, cy->weights, cx->indices, cx->weights, sharpen); } else { ZeroMatrix(dyw, dxw, dst, dyn, dxn); diff --git a/dsp/scale/scale.mk b/dsp/scale/scale.mk index 6c9035eb..a342e423 100644 --- a/dsp/scale/scale.mk +++ b/dsp/scale/scale.mk @@ -47,6 +47,13 @@ $(DSP_SCALE_A).pkg: \ $(DSP_SCALE_A_OBJS) \ $(foreach x,$(DSP_SCALE_A_DIRECTDEPS),$($(x)_A).pkg) +o/$(MODE)/dsp/scale/cdecimate2xuint8x8.o \ +o/$(MODE)/dsp/scale/gyarados.o \ +o/$(MODE)/dsp/scale/magikarp.o \ +o/$(MODE)/dsp/scale/scale.o: \ + OVERRIDE_CFLAGS += \ + $(MATHEMATICAL) + DSP_SCALE_LIBS = $(foreach x,$(DSP_SCALE_ARTIFACTS),$($(x))) DSP_SCALE_SRCS = $(foreach x,$(DSP_SCALE_ARTIFACTS),$($(x)_SRCS)) DSP_SCALE_HDRS = $(foreach x,$(DSP_SCALE_ARTIFACTS),$($(x)_HDRS)) diff --git a/dsp/tty/ident.c b/dsp/tty/ident.c index 3560c270..ef840b40 100644 --- a/dsp/tty/ident.c +++ b/dsp/tty/ident.c @@ -18,8 +18,8 @@ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "dsp/tty/tty.h" -#include "libc/bits/bits.h" #include "libc/bits/safemacros.h" +#include "libc/bits/weaken.h" #include "libc/calls/calls.h" #include "libc/calls/termios.h" #include "libc/fmt/fmt.h" diff --git a/dsp/tty/itoa8.c b/dsp/tty/itoa8.c index c7ecffb1..1cfc4069 100644 --- a/dsp/tty/itoa8.c +++ b/dsp/tty/itoa8.c @@ -19,15 +19,15 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "dsp/tty/itoa8.h" #include "libc/bits/bits.h" +#include "libc/bits/initializer.h" #include "libc/str/str.h" struct Itoa8 kItoa8; -static nooptimize textstartup void itoa8init(void) { - size_t i; +static textstartup void itoa8_init(void) { + int i; uint8_t z; char p[4]; - /*102*/ for (i = 0; i < 256; ++i) { memset(p, 0, sizeof(p)); if (i < 10) { @@ -48,4 +48,4 @@ static nooptimize textstartup void itoa8init(void) { } } -INITIALIZER(301, _init_itoa8, itoa8init()); +const void *const itoa8_init_ctor[] initarray = {itoa8_init}; diff --git a/dsp/tty/quant.h b/dsp/tty/quant.h index 9d08bf0e..ea6ccf4c 100644 --- a/dsp/tty/quant.h +++ b/dsp/tty/quant.h @@ -64,9 +64,10 @@ extern double g_xterm256_gamma; extern struct TtyRgb g_ansi2rgb_[256]; extern struct TtyQuant g_ttyquant_; extern const uint8_t kXtermXlat[2][256]; +extern const uint8_t kXtermCube[6]; -void ttyquantinit(enum TtyQuantizationAlgorithm, enum TtyQuantizationChannels, - enum TtyBlocksSelection); +void ttyquantsetup(enum TtyQuantizationAlgorithm, enum TtyQuantizationChannels, + enum TtyBlocksSelection); extern char *ttyraster(char *, const struct TtyRgb *, size_t, size_t, struct TtyRgb, struct TtyRgb); diff --git a/dsp/tty/rgb2ansi.c b/dsp/tty/rgb2ansi.c index f418d0ec..e0b3de01 100644 --- a/dsp/tty/rgb2ansi.c +++ b/dsp/tty/rgb2ansi.c @@ -20,6 +20,7 @@ #include "dsp/core/core.h" #include "dsp/tty/quant.h" #include "libc/assert.h" +#include "libc/bits/initializer.h" #include "libc/limits.h" #include "libc/log/log.h" #include "libc/macros.h" @@ -32,12 +33,8 @@ #define SQR(X) ((X) * (X)) #define SUM(X, Y, Z) ((X) + (Y) + (Z)) -static const uint8_t kXtermCube[] = {0, 0137, 0207, 0257, 0327, 0377}; +const uint8_t kXtermCube[6] = {0, 0137, 0207, 0257, 0327, 0377}; struct TtyRgb g_ansi2rgb_[256]; -static uint8_t g_quant[256]; -static uint8_t g_rindex[256]; -static uint8_t g_gindex[256]; -static uint8_t g_bindex[256]; double g_xterm256_gamma; struct TtyRgb tty2rgb_(struct TtyRgb rgbxt) { @@ -101,17 +98,10 @@ static int uncube(int x) { return x < 48 ? 0 : x < 115 ? 1 : (x - 35) / 40; } -static optimizesize textstartup void xterm2rgbsetup_(void) { +static textstartup void rgb2ansi_init(void) { uint8_t c, y; uint32_t i, j; memcpy(g_ansi2rgb_, &kCgaPalette, sizeof(kCgaPalette)); - for (i = 0; i < 256; ++i) { - j = uncube(i); - g_quant[i] = kXtermCube[j]; - g_rindex[i] = j * 36; - g_gindex[i] = j * 6; - g_bindex[i] = j + 16; - } for (i = 16; i < 232; ++i) { g_ansi2rgb_[i].r = kXtermCube[((i - 020) / 044) % 06]; g_ansi2rgb_[i].g = kXtermCube[((i - 020) / 06) % 06]; @@ -126,4 +116,4 @@ static optimizesize textstartup void xterm2rgbsetup_(void) { } } -INITIALIZER(301, _init_ansi2rgb, xterm2rgbsetup_()); +const void *const rgb2ansi_init_ctor[] initarray = {rgb2ansi_init}; diff --git a/dsp/tty/rgb2xterm256.c b/dsp/tty/rgb2xterm256.c deleted file mode 100644 index 3f37a1f6..00000000 --- a/dsp/tty/rgb2xterm256.c +++ /dev/null @@ -1,71 +0,0 @@ -/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ -│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ -╞══════════════════════════════════════════════════════════════════════════════╡ -│ Copyright 2020 Justine Alexandra Roberts Tunney │ -│ │ -│ This program is free software; you can redistribute it and/or modify │ -│ it under the terms of the GNU General Public License as published by │ -│ the Free Software Foundation; version 2 of the License. │ -│ │ -│ This program is distributed in the hope that it will be useful, but │ -│ WITHOUT ANY WARRANTY; without even the implied warranty of │ -│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ -│ General Public License for more details. │ -│ │ -│ You should have received a copy of the GNU General Public License │ -│ along with this program; if not, write to the Free Software │ -│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ -│ 02110-1301 USA │ -╚─────────────────────────────────────────────────────────────────────────────*/ -#include "dsp/tty/rgb2xterm256.h" - -/* 1bc */ -forceinline int sqr(int x) { return x * x; } -/* forceinline int dst6(int x) { return x * x; } */ -int rgb2xterm256v2(int r, int g, int b) { - static const int i2cv[] = {0, 0137, 0207, 0257, 0327, 0377, 0377}; -#define v2ci(v) (v < 060 ? 0 : v < 0163 ? 01 : (v - 043) / 050) -#define dst6(A, B, C, a, b, c) (sqr(A - a) + sqr(B - b) + sqr(C - c)) - int ir = v2ci(r); - int ig = v2ci(g); - int ib = v2ci(b); - int avg = (r + g + b) / 3; - int cr = i2cv[ir]; - int cg = i2cv[ig]; - int cb = i2cv[ib]; - int gidx = avg > 238 ? 23 : (avg - 3) / 10; - int gv = 8 + 10 * gidx; - int cerr = dst6(cr, cg, cb, r, g, b); - int gerr = dst6(gv, gv, gv, r, g, b); - return cerr <= gerr ? 16 + (36 * ir + 6 * ig + ib) : 232 + gidx; -#undef dst6 -#undef cidx -#undef v2ci -} - -/* 1e3 */ -// Convert RGB24 to xterm-256 8-bit value -// For simplicity, assume RGB space is perceptually uniform. -// There are 5 places where one of two outputs needs to be chosen when -// input is the exact middle: -// - The r/g/b channels and the gray value: choose higher value output -// - If gray and color have same distance from input - choose color -int rgb2xterm256(uint8_t r, uint8_t g, uint8_t b) { - // Calculate the nearest 0-based color index at 16 .. 231 -#define v2ci(v) (v < 48 ? 0 : v < 115 ? 1 : (v - 35) / 40) - int ir = v2ci(r), ig = v2ci(g), ib = v2ci(b); // 0..5 each -#define color_index() (36 * ir + 6 * ig + ib) /* 0..215, lazy eval */ - // Calculate the nearest 0-based gray index at 232 .. 255 - int average = (r + g + b) / 3; - int gray_index = average > 238 ? 23 : (average - 3) / 10; // 0..23 - // Calculate the represented colors back from the index - static const int i2cv[6] = {0, 0x5f, 0x87, 0xaf, 0xd7, 0xff}; - int cr = i2cv[ir], cg = i2cv[ig], cb = i2cv[ib]; // r/g/b, 0..255 each - int gv = 8 + 10 * gray_index; // same value for r/g/b, 0..255 -// Return the one which is nearer to the original input rgb value -#define dist_square(A, B, C, a, b, c) \ - ((A - a) * (A - a) + (B - b) * (B - b) + (C - c) * (C - c)) - int color_err = dist_square(cr, cg, cb, r, g, b); - int gray_err = dist_square(gv, gv, gv, r, g, b); - return color_err <= gray_err ? 16 + color_index() : 232 + gray_index; -} diff --git a/dsp/tty/rgb2xterm256.h b/dsp/tty/rgb2xterm256.h deleted file mode 100644 index 071de016..00000000 --- a/dsp/tty/rgb2xterm256.h +++ /dev/null @@ -1,11 +0,0 @@ -#ifndef COSMOPOLITAN_DSP_TTY_RGB2XTERM256_H_ -#define COSMOPOLITAN_DSP_TTY_RGB2XTERM256_H_ -#if !(__ASSEMBLER__ + __LINKER__ + 0) -COSMOPOLITAN_C_START_ - -int rgb2xterm256(uint8_t, uint8_t, uint8_t); -int rgb2xterm256v2(int, int, int); - -COSMOPOLITAN_C_END_ -#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ -#endif /* COSMOPOLITAN_DSP_TTY_RGB2XTERM256_H_ */ diff --git a/dsp/tty/quantinit.c b/dsp/tty/ttyquant.c similarity index 89% rename from dsp/tty/quantinit.c rename to dsp/tty/ttyquant.c index 07caa3e1..f1b02ae4 100644 --- a/dsp/tty/quantinit.c +++ b/dsp/tty/ttyquant.c @@ -19,6 +19,7 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "dsp/tty/internal.h" #include "dsp/tty/quant.h" +#include "libc/bits/initializer.h" #include "libc/dce.h" #include "libc/runtime/runtime.h" #include "libc/stdio/stdio.h" @@ -29,9 +30,9 @@ struct TtyQuant g_ttyquant_; /** * Chooses xterm quantization mode. */ -optimizesize textstartup void ttyquantinit(enum TtyQuantizationAlgorithm alg, - enum TtyQuantizationChannels chans, - enum TtyBlocksSelection blocks) { +textstartup void ttyquantsetup(enum TtyQuantizationAlgorithm alg, + enum TtyQuantizationChannels chans, + enum TtyBlocksSelection blocks) { switch (alg) { case kTtyQuantAnsi: TTYQUANT()->rgb2tty = rgb2ansi_; @@ -74,5 +75,8 @@ optimizesize textstartup void ttyquantinit(enum TtyQuantizationAlgorithm alg, TTYQUANT()->blocks = blocks; } -INITIALIZER(400, _init_ttyquant, - ttyquantinit(kTtyQuantXterm256, kTtyQuantRgb, kTtyBlocksUnicode)); +textstartup void ttyquant_init(void) { + ttyquantsetup(kTtyQuantXterm256, kTtyQuantRgb, kTtyBlocksUnicode); +} + +const void *const ttyquant_init_ctor[] initarray = {ttyquant_init}; diff --git a/dsp/tty/ttyraw.c b/dsp/tty/ttyraw.c index a285cbe9..e6cbfa09 100644 --- a/dsp/tty/ttyraw.c +++ b/dsp/tty/ttyraw.c @@ -42,6 +42,7 @@ static struct TtyRaw { bool setup; + bool hasold; bool noreentry; bool initialized; enum TtyRawFlags flags; @@ -51,12 +52,15 @@ static struct TtyRaw { } g_ttyraw; static textstartup int ttyraw_setup(void) { - if (isatty(FD) && - ttyconfig(FD, ttysetrawmode, g_ttyraw.flags, &g_ttyraw.old) != -1) { - return 0; - } else { - return -1; + struct termios *old; + if (isatty(FD)) { + old = !g_ttyraw.hasold ? &g_ttyraw.old : NULL; + if (ttyconfig(FD, ttysetrawmode, g_ttyraw.flags, old) != -1) { + g_ttyraw.hasold = true; + return 0; + } } + return -1; } static textstartup int ttyraw_enable(void) { diff --git a/dsp/tty/xtermname.c b/dsp/tty/xtermname.c new file mode 100644 index 00000000..b238c7e9 --- /dev/null +++ b/dsp/tty/xtermname.c @@ -0,0 +1,282 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "dsp/tty/xtermname.h" + +/** + * 256-entry double-nul-terminated list of XTERM256 names. + */ +const char kXtermName[] = "\ +Black\0\ +Maroon\0\ +Green\0\ +Olive\0\ +Navy\0\ +Purple\0\ +Teal\0\ +Silver\0\ +Grey\0\ +Red\0\ +Lime\0\ +Yellow\0\ +Blue\0\ +Fuchsia\0\ +Aqua\0\ +White\0\ +Grey0\0\ +NavyBlue\0\ +DarkBlue\0\ +Blue3\0\ +Blue3\0\ +Blue1\0\ +DarkGreen\0\ +DeepSkyBlue4\0\ +DeepSkyBlue4\0\ +DeepSkyBlue4\0\ +DodgerBlue3\0\ +DodgerBlue2\0\ +Green4\0\ +SpringGreen4\0\ +Turquoise4\0\ +DeepSkyBlue3\0\ +DeepSkyBlue3\0\ +DodgerBlue1\0\ +Green3\0\ +SpringGreen3\0\ +DarkCyan\0\ +LightSeaGreen\0\ +DeepSkyBlue2\0\ +DeepSkyBlue1\0\ +Green3\0\ +SpringGreen3\0\ +SpringGreen2\0\ +Cyan3\0\ +DarkTurquoise\0\ +Turquoise2\0\ +Green1\0\ +SpringGreen2\0\ +SpringGreen1\0\ +MediumSpringGreen\0\ +Cyan2\0\ +Cyan1\0\ +DarkRed\0\ +DeepPink4\0\ +Purple4\0\ +Purple4\0\ +Purple3\0\ +BlueViolet\0\ +Orange4\0\ +Grey37\0\ +MediumPurple4\0\ +SlateBlue3\0\ +SlateBlue3\0\ +RoyalBlue1\0\ +Chartreuse4\0\ +DarkSeaGreen4\0\ +PaleTurquoise4\0\ +SteelBlue\0\ +SteelBlue3\0\ +CornflowerBlue\0\ +Chartreuse3\0\ +DarkSeaGreen4\0\ +CadetBlue\0\ +CadetBlue\0\ +SkyBlue3\0\ +SteelBlue1\0\ +Chartreuse3\0\ +PaleGreen3\0\ +SeaGreen3\0\ +Aquamarine3\0\ +MediumTurquoise\0\ +SteelBlue1\0\ +Chartreuse2\0\ +SeaGreen2\0\ +SeaGreen1\0\ +SeaGreen1\0\ +Aquamarine1\0\ +DarkSlateGray2\0\ +DarkRed\0\ +DeepPink4\0\ +DarkMagenta\0\ +DarkMagenta\0\ +DarkViolet\0\ +Purple\0\ +Orange4\0\ +LightPink4\0\ +Plum4\0\ +MediumPurple3\0\ +MediumPurple3\0\ +SlateBlue1\0\ +Yellow4\0\ +Wheat4\0\ +Grey53\0\ +LightSlateGrey\0\ +MediumPurple\0\ +LightSlateBlue\0\ +Yellow4\0\ +DarkOliveGreen3\0\ +DarkSeaGreen\0\ +LightSkyBlue3\0\ +LightSkyBlue3\0\ +SkyBlue2\0\ +Chartreuse2\0\ +DarkOliveGreen3\0\ +PaleGreen3\0\ +DarkSeaGreen3\0\ +DarkSlateGray3\0\ +SkyBlue1\0\ +Chartreuse1\0\ +LightGreen\0\ +LightGreen\0\ +PaleGreen1\0\ +Aquamarine1\0\ +DarkSlateGray1\0\ +Red3\0\ +DeepPink4\0\ +MediumVioletRed\0\ +Magenta3\0\ +DarkViolet\0\ +Purple\0\ +DarkOrange3\0\ +IndianRed\0\ +HotPink3\0\ +MediumOrchid3\0\ +MediumOrchid\0\ +MediumPurple2\0\ +DarkGoldenrod\0\ +LightSalmon3\0\ +RosyBrown\0\ +Grey63\0\ +MediumPurple2\0\ +MediumPurple1\0\ +Gold3\0\ +DarkKhaki\0\ +NavajoWhite3\0\ +Grey69\0\ +LightSteelBlue3\0\ +LightSteelBlue\0\ +Yellow3\0\ +DarkOliveGreen3\0\ +DarkSeaGreen3\0\ +DarkSeaGreen2\0\ +LightCyan3\0\ +LightSkyBlue1\0\ +GreenYellow\0\ +DarkOliveGreen2\0\ +PaleGreen1\0\ +DarkSeaGreen2\0\ +DarkSeaGreen1\0\ +PaleTurquoise1\0\ +Red3\0\ +DeepPink3\0\ +DeepPink3\0\ +Magenta3\0\ +Magenta3\0\ +Magenta2\0\ +DarkOrange3\0\ +IndianRed\0\ +HotPink3\0\ +HotPink2\0\ +Orchid\0\ +MediumOrchid1\0\ +Orange3\0\ +LightSalmon3\0\ +LightPink3\0\ +Pink3\0\ +Plum3\0\ +Violet\0\ +Gold3\0\ +LightGoldenrod3\0\ +Tan\0\ +MistyRose3\0\ +Thistle3\0\ +Plum2\0\ +Yellow3\0\ +Khaki3\0\ +LightGoldenrod2\0\ +LightYellow3\0\ +Grey84\0\ +LightSteelBlue1\0\ +Yellow2\0\ +DarkOliveGreen1\0\ +DarkOliveGreen1\0\ +DarkSeaGreen1\0\ +Honeydew2\0\ +LightCyan1\0\ +Red1\0\ +DeepPink2\0\ +DeepPink1\0\ +DeepPink1\0\ +Magenta2\0\ +Magenta1\0\ +OrangeRed1\0\ +IndianRed1\0\ +IndianRed1\0\ +HotPink\0\ +HotPink\0\ +MediumOrchid1\0\ +DarkOrange\0\ +Salmon1\0\ +LightCoral\0\ +PaleVioletRed1\0\ +Orchid2\0\ +Orchid1\0\ +Orange1\0\ +SandyBrown\0\ +LightSalmon1\0\ +LightPink1\0\ +Pink1\0\ +Plum1\0\ +Gold1\0\ +LightGoldenrod2\0\ +LightGoldenrod2\0\ +NavajoWhite1\0\ +MistyRose1\0\ +Thistle1\0\ +Yellow1\0\ +LightGoldenrod1\0\ +Khaki1\0\ +Wheat1\0\ +Cornsilk1\0\ +Grey100\0\ +Grey3\0\ +Grey7\0\ +Grey11\0\ +Grey15\0\ +Grey19\0\ +Grey23\0\ +Grey27\0\ +Grey30\0\ +Grey35\0\ +Grey39\0\ +Grey42\0\ +Grey46\0\ +Grey50\0\ +Grey54\0\ +Grey58\0\ +Grey62\0\ +Grey66\0\ +Grey70\0\ +Grey74\0\ +Grey78\0\ +Grey82\0\ +Grey85\0\ +Grey89\0\ +Grey93\0\ +\0"; diff --git a/dsp/tty/xtermname.h b/dsp/tty/xtermname.h new file mode 100644 index 00000000..ed3551b8 --- /dev/null +++ b/dsp/tty/xtermname.h @@ -0,0 +1,10 @@ +#ifndef COSMOPOLITAN_DSP_TTY_XTERMNAME_H_ +#define COSMOPOLITAN_DSP_TTY_XTERMNAME_H_ +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +extern const char kXtermName[]; + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_DSP_TTY_XTERMNAME_H_ */ diff --git a/examples/bigmem.c b/examples/bigmem.c index fd58835c..c311ab3b 100644 --- a/examples/bigmem.c +++ b/examples/bigmem.c @@ -8,6 +8,7 @@ ╚─────────────────────────────────────────────────────────────────*/ #endif #include "libc/calls/calls.h" +#include "libc/calls/hefty/copyfile.h" #include "libc/calls/hefty/spawn.h" #include "libc/calls/struct/stat.h" #include "libc/log/check.h" @@ -87,7 +88,7 @@ int main(int argc, char *argv[]) { CHECK_NE(-1, close(fd)); t1 = dtime(CLOCK_REALTIME); - CHECK_NE(-1, copyfile(core, core2, false)); + CHECK_NE(-1, copyfile(core, core2, 0)); t2 = dtime(CLOCK_REALTIME); printf("%.6Lf\n", t2 - t1); diff --git a/examples/cp.c b/examples/cp.c new file mode 100644 index 00000000..99bcdb67 --- /dev/null +++ b/examples/cp.c @@ -0,0 +1,96 @@ +#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 "libc/calls/calls.h" +#include "libc/calls/hefty/copyfile.h" +#include "libc/conv/conv.h" +#include "libc/errno.h" +#include "libc/fmt/fmt.h" +#include "libc/runtime/gc.h" +#include "libc/runtime/runtime.h" +#include "libc/stdio/stdio.h" +#include "libc/str/str.h" +#include "libc/sysv/consts/ex.h" +#include "libc/sysv/consts/exit.h" +#include "libc/sysv/consts/ok.h" +#include "libc/x/x.h" +#include "third_party/getopt/getopt.h" + +#define USAGE \ + " SRC... DST\n\ +\n\ +SYNOPSIS\n\ +\n\ + Copies Files\n\ +\n\ +FLAGS\n\ +\n\ + -?\n\ + -h help\n\ + -f force\n\ + -n no clobber\n\ + -a preserve all\n\ + -p preserve owner and timestamps\n\ +\n" + +int flags; +bool force; + +noreturn void PrintUsage(int rc, FILE *f) { + fprintf(f, "%s%s%s", "Usage: ", program_invocation_name, USAGE); + exit(rc); +} + +void GetOpts(int argc, char *argv[]) { + int opt; + while ((opt = getopt(argc, argv, "?hfnap")) != -1) { + switch (opt) { + case 'f': + force = true; + break; + case 'n': + flags |= COPYFILE_NOCLOBBER; + break; + case 'a': + case 'p': + flags |= COPYFILE_PRESERVE_OWNER; + flags |= COPYFILE_PRESERVE_TIMESTAMPS; + break; + case 'h': + case '?': + PrintUsage(EXIT_SUCCESS, stdout); + default: + PrintUsage(EX_USAGE, stderr); + } + } +} + +int cp(const char *src, const char *dst) { + if (endswith(dst, "/") || isdirectory(dst)) { + dst = gc(xasprintf("%s/%s", dst, basename)); + } + if (!force && access(dst, W_OK) == -1 && errno != ENOENT) goto OnFail; + if (copyfile(src, dst, flags) == -1) goto OnFail; + return 0; +OnFail: + fprintf(stderr, "%s %s %s: %s\n", "error: cp", src, dst, strerror(errno)); + return 1; +} + +int main(int argc, char *argv[]) { + int i; + GetOpts(argc, argv); + if (argc - optind < 2) PrintUsage(EX_USAGE, stderr); + for (i = optind; i < argc - 1; ++i) { + if (cp(argv[i], argv[argc - 1]) == -1) { + return -1; + } + } + return 0; +} diff --git a/examples/examples.mk b/examples/examples.mk index 92d2868f..304cd0a2 100644 --- a/examples/examples.mk +++ b/examples/examples.mk @@ -8,120 +8,135 @@ EXAMPLES_MAINS_S = $(filter %.S,$(EXAMPLES_FILES)) EXAMPLES_MAINS_C = $(filter %.c,$(EXAMPLES_FILES)) EXAMPLES_MAINS_CC = $(filter %.cc,$(EXAMPLES_FILES)) -EXAMPLES_SRCS = \ - $(EXAMPLES_MAINS_S) \ - $(EXAMPLES_MAINS_C) \ +EXAMPLES_SRCS = \ + $(EXAMPLES_MAINS_S) \ + $(EXAMPLES_MAINS_C) \ $(EXAMPLES_MAINS_CC) -EXAMPLES_MAINS = \ - $(EXAMPLES_MAINS_S) \ - $(EXAMPLES_MAINS_C) \ +EXAMPLES_MAINS = \ + $(EXAMPLES_MAINS_S) \ + $(EXAMPLES_MAINS_C) \ $(EXAMPLES_MAINS_CC) -EXAMPLES_OBJS = \ - $(EXAMPLES_SRCS:%=o/$(MODE)/%.zip.o) \ - $(EXAMPLES_MAINS_S:%.S=o/$(MODE)/%.o) \ - $(EXAMPLES_MAINS_C:%.c=o/$(MODE)/%.o) \ +EXAMPLES_OBJS = \ + $(EXAMPLES_SRCS:%=o/$(MODE)/%.zip.o) \ + $(EXAMPLES_MAINS_S:%.S=o/$(MODE)/%.o) \ + $(EXAMPLES_MAINS_C:%.c=o/$(MODE)/%.o) \ $(EXAMPLES_MAINS_CC:%.cc=o/$(MODE)/%.o) -EXAMPLES_COMS = \ - $(EXAMPLES_OBJS:%.o=%.com) +EXAMPLES_COMS = \ + $(EXAMPLES_MAINS_S:%.S=o/$(MODE)/%.com) \ + $(EXAMPLES_MAINS_C:%.c=o/$(MODE)/%.com) \ + $(EXAMPLES_MAINS_CC:%.cc=o/$(MODE)/%.com) -EXAMPLES_ELFS = \ +EXAMPLES_ELFS = \ $(EXAMPLES_OBJS:%.o=%.elf) -EXAMPLES_BINS = \ - $(EXAMPLES_ELFS) \ - $(EXAMPLES_COMS) \ +EXAMPLES_BINS = \ + $(EXAMPLES_ELFS) \ + $(EXAMPLES_COMS) \ $(EXAMPLES_COMS:%=%.dbg) -EXAMPLES_DIRECTDEPS = \ - APE_LIB \ - DSP_CORE \ - DSP_SCALE \ - DSP_TTY \ - LIBC_ALG \ - LIBC_BITS \ - LIBC_CALLS \ - LIBC_CALLS_HEFTY \ - LIBC_CONV \ - LIBC_FMT \ - LIBC_LOG \ - LIBC_MEM \ - LIBC_NEXGEN32E \ - LIBC_NT_KERNELBASE \ - LIBC_NT_NTDLL \ - LIBC_NT_USER32 \ - LIBC_OHMYPLUS \ - LIBC_RAND \ - LIBC_RUNTIME \ - LIBC_SOCK \ - LIBC_STDIO \ - LIBC_STR \ - LIBC_STUBS \ - LIBC_SYSV \ - LIBC_SYSV_CALLS \ - LIBC_TESTLIB \ - LIBC_TIME \ - LIBC_TINYMATH \ - LIBC_UNICODE \ - LIBC_X \ - THIRD_PARTY_COMPILER_RT \ - THIRD_PARTY_DLMALLOC \ - THIRD_PARTY_DTOA \ - THIRD_PARTY_GETOPT \ - THIRD_PARTY_MUSL \ - THIRD_PARTY_STB \ - THIRD_PARTY_XED \ - THIRD_PARTY_ZLIB \ +EXAMPLES_DIRECTDEPS = \ + APE_LIB \ + DSP_CORE \ + DSP_SCALE \ + DSP_TTY \ + LIBC_ALG \ + LIBC_BITS \ + LIBC_CALLS \ + LIBC_CALLS_HEFTY \ + LIBC_CONV \ + LIBC_FMT \ + LIBC_LOG \ + LIBC_MEM \ + LIBC_NEXGEN32E \ + LIBC_NT_KERNELBASE \ + LIBC_NT_NTDLL \ + LIBC_NT_USER32 \ + LIBC_OHMYPLUS \ + LIBC_RAND \ + LIBC_RUNTIME \ + LIBC_SOCK \ + LIBC_STDIO \ + LIBC_STR \ + LIBC_STUBS \ + LIBC_SYSV \ + LIBC_SYSV_CALLS \ + LIBC_TESTLIB \ + LIBC_TIME \ + LIBC_TINYMATH \ + LIBC_UNICODE \ + LIBC_X \ + LIBC_ZIPOS \ + THIRD_PARTY_COMPILER_RT \ + THIRD_PARTY_DLMALLOC \ + THIRD_PARTY_DTOA \ + THIRD_PARTY_GETOPT \ + THIRD_PARTY_MUSL \ + THIRD_PARTY_STB \ + THIRD_PARTY_XED \ + THIRD_PARTY_ZLIB \ TOOL_VIZ_LIB -EXAMPLES_DEPS := \ +EXAMPLES_DEPS := \ $(call uniq,$(foreach x,$(EXAMPLES_DIRECTDEPS),$($(x)))) -o/$(MODE)/examples/examples.pkg: \ - $(EXAMPLES_OBJS) \ - $(THIRD_PARTY_DUKTAPE_A).pkg \ +o/$(MODE)/examples/examples.pkg: \ + $(EXAMPLES_OBJS) \ + $(THIRD_PARTY_DUKTAPE_A).pkg \ $(foreach x,$(EXAMPLES_DIRECTDEPS),$($(x)_A).pkg) -o/$(MODE)/examples/unbourne.o: \ - OVERRIDE_CPPFLAGS += \ +o/$(MODE)/examples/unbourne.o: \ + OVERRIDE_CPPFLAGS += \ -DSTACK_FRAME_UNLIMITED -o/$(MODE)/examples/%.com.dbg: \ - $(EXAMPLES_DEPS) \ - o/$(MODE)/examples/%.o \ - o/$(MODE)/examples/examples.pkg \ - $(CRT) \ +o/$(MODE)/examples/%.com.dbg: \ + $(EXAMPLES_DEPS) \ + o/$(MODE)/examples/%.o \ + o/$(MODE)/examples/examples.pkg \ + $(CRT) \ $(APE) @$(APELINK) -o/$(MODE)/examples/%.elf: \ - $(EXAMPLES_DEPS) \ - $(THIRD_PARTY_DUKTAPE) \ - o/$(MODE)/examples/%.o \ - $(CRT) \ +o/$(MODE)/examples/%.elf: \ + $(EXAMPLES_DEPS) \ + $(THIRD_PARTY_DUKTAPE) \ + o/$(MODE)/examples/%.o \ + $(CRT) \ $(ELF) @$(ELFLINK) $(EXAMPLES_OBJS): examples/examples.mk -o/$(MODE)/examples/hellojs.com.dbg: \ - $(EXAMPLES_DEPS) \ - $(THIRD_PARTY_DUKTAPE) \ - o/$(MODE)/examples/hellojs.o \ - o/$(MODE)/examples/hello.js.zip.o \ - o/$(MODE)/examples/examples.pkg \ - $(CRT) \ +o/$(MODE)/examples/hellojs.com.dbg: \ + $(EXAMPLES_DEPS) \ + $(THIRD_PARTY_DUKTAPE) \ + o/$(MODE)/examples/hellojs.o \ + o/$(MODE)/examples/hello.js.zip.o \ + o/$(MODE)/examples/examples.pkg \ + $(CRT) \ $(APE) @$(APELINK) -o/$(MODE)/examples/ispell.com.dbg: \ - $(EXAMPLES_DEPS) \ - o/$(MODE)/examples/ispell.o \ - o/$(MODE)/usr/share/dict/words.zip.o \ - o/$(MODE)/examples/examples.pkg \ - $(CRT) \ +o/$(MODE)/examples/ispell.com.dbg: \ + $(EXAMPLES_DEPS) \ + o/$(MODE)/examples/ispell.o \ + o/$(MODE)/usr/share/dict/words.zip.o \ + o/$(MODE)/examples/examples.pkg \ + $(CRT) \ + $(APE) + @$(APELINK) + +o/$(MODE)/examples/nesemu1.com.dbg: \ + $(EXAMPLES_DEPS) \ + $(THIRD_PARTY_DUKTAPE) \ + o/$(MODE)/examples/nesemu1.o \ + o/$(MODE)/usr/share/rom/mario.nes.zip.o \ + o/$(MODE)/usr/share/rom/zelda.nes.zip.o \ + o/$(MODE)/usr/share/rom/tetris.nes.zip.o \ + o/$(MODE)/examples/examples.pkg \ + $(CRT) \ $(APE) @$(APELINK) @@ -129,7 +144,11 @@ o/$(MODE)/usr/share/dict/words: usr/share/dict/words.gz @$(MKDIR) $(dir $@) @$(GZ) $(ZFLAGS) <$< >$@ +o/$(MODE)/examples/ugh.ok: o/$(MODE)/examples/wut.com + $< + touch $@ + .PHONY: o/$(MODE)/examples -o/$(MODE)/examples: \ - o/$(MODE)/examples/package \ +o/$(MODE)/examples: \ + o/$(MODE)/examples/package \ $(EXAMPLES_BINS) diff --git a/examples/findprime.c b/examples/findprime.c index 7a7524f5..fcc75417 100644 --- a/examples/findprime.c +++ b/examples/findprime.c @@ -28,6 +28,7 @@ static const char *magic_; void OnInterrupt(int sig) { showprogress_ = true; } + void ShowProgress(uintmax_t i, uintmax_t j, uintmax_t n) { fprintf(stderr, "%s%,40jd%,40jd%,40jd\r\n", magic_, i, j, n); showprogress_ = false; diff --git a/examples/hello.c b/examples/hello.c index 3b2e16d0..0a367ca1 100644 --- a/examples/hello.c +++ b/examples/hello.c @@ -8,6 +8,7 @@ ╚─────────────────────────────────────────────────────────────────*/ #endif #include "libc/errno.h" +#include "libc/log/log.h" #include "libc/stdio/stdio.h" int main() { @@ -19,5 +20,5 @@ int main() { * have that string consist solely of directives. */ printf("%s\n", "hello world"); - return errno; + return 0; } diff --git a/examples/kilo.c b/examples/kilo.c index a9607fe9..f74a094d 100644 --- a/examples/kilo.c +++ b/examples/kilo.c @@ -58,7 +58,7 @@ Contact: antirez@gmail.com\"\n\ #define _GNU_SOURCE #include "libc/alg/alg.h" -#include "libc/alg/arraylist.h" +#include "libc/alg/arraylist2.h" #include "libc/calls/calls.h" #include "libc/calls/termios.h" #include "libc/calls/weirdtypes.h" @@ -879,7 +879,7 @@ struct abuf { }; static void abAppend(struct abuf *ab, const char *s, int len) { - concat(ab, s, len); + CONCAT(&ab->p, &ab->i, &ab->n, s, len); } /* This function writes the whole screen using VT100 escape characters diff --git a/examples/nesemu1.cc b/examples/nesemu1.cc index 04d53af2..20403b74 100644 --- a/examples/nesemu1.cc +++ b/examples/nesemu1.cc @@ -4,11 +4,13 @@ /* TRADEMARKS ARE OWNED BY THEIR RESPECTIVE OWNERS LAWYERCATS LUV TAUTOLOGIES */ /* https://bisqwit.iki.fi/jutut/kuvat/programming_examples/nesemu1/nesemu1.cc */ #include "dsp/core/core.h" +#include "dsp/core/half.h" #include "dsp/core/illumination.h" #include "dsp/scale/scale.h" #include "dsp/tty/itoa8.h" #include "dsp/tty/quant.h" #include "dsp/tty/tty.h" +#include "libc/alg/arraylist2.h" #include "libc/assert.h" #include "libc/bits/bits.h" #include "libc/bits/safemacros.h" @@ -16,6 +18,7 @@ #include "libc/calls/hefty/spawn.h" #include "libc/calls/struct/itimerval.h" #include "libc/calls/struct/winsize.h" +#include "libc/conv/conv.h" #include "libc/errno.h" #include "libc/fmt/fmt.h" #include "libc/inttypes.h" @@ -30,6 +33,8 @@ #include "libc/sock/sock.h" #include "libc/stdio/stdio.h" #include "libc/str/str.h" +#include "libc/sysv/consts/ex.h" +#include "libc/sysv/consts/exit.h" #include "libc/sysv/consts/fileno.h" #include "libc/sysv/consts/itimer.h" #include "libc/sysv/consts/o.h" @@ -37,24 +42,77 @@ #include "libc/sysv/consts/sig.h" #include "libc/time/time.h" #include "libc/x/x.h" +#include "libc/zip.h" +#include "libc/zipos/zipos.h" +#include "third_party/getopt/getopt.h" #include "tool/viz/lib/knobs.h" -#define DYN 240 -#define DXN 256 -#define FPS 60.0988 -#define HZ 1789773 -#define KEYHZ 20 -#define GAMMA 2.2 +#define USAGE \ + " [ROM] [FMV]\n\ +\n\ +SYNOPSIS\n\ +\n\ + Emulates NES Video Games in Terminal\n\ +\n\ +FLAGS\n\ +\n\ + -A ansi color mode\n\ + -t normal color mode\n\ + -x xterm256 color mode\n\ + -4 unicode character set\n\ + -3 ibm cp437 character set\n\ + -1 ntsc crt artifact emulation\n\ + -h\n\ + -? shows this information\n\ +\n\ +KEYBOARD\n\ +\n\ + We support Emacs / Mac OS X control key bindings. We also support\n\ + Vim. We support arrow keys. We also support WASD QWERTY & Dvorak.\n\ + The 'A' button is mapped to SPACE. The 'B' button is mapped to b.\n\ + Lastly TAB is SELECT and ENTER is START.\n\ +\n\ + Teletypewriters are naturally limited in terms of keyboard input.\n\ + They don't exactly have n-key rollover. More like 1-key rollover.\n\ +\n\ + Try tapping rather than holding keys. You can tune the activation\n\ + duration by pressing '8' and '9'. You can also adjust the keyboard\n\ + repeat delay in your operating system settings to make it shorter.\n\ +\n\ + Ideally we should send patches to all the terms that introduces a\n\ + new ANSI escape sequences for key down / key up events. It'd also\n\ + be great to have inband audio with terminals too.\n\ +\n\ +GRAPHICS\n\ +\n\ + The '1' key toggles CRT monitor artifact emulation, which can make\n\ + some games like Zelda II look better. The '3' and '4' keys toggle\n\ + the selection of UNICODE block characters.\n\ +\n\ +ZIP\n\ +\n\ + This executable is also a ZIP archive. If you change the extension\n\ + then you can modify its inner structure, to place roms inside it.\n\ +\n\ +AUTHORS\n\ +\n\ + Joel Yliluoma \n\ + Justine Tunney \n\ +\n\ +\n" +#define DYN 240 +#define DXN 256 +#define FPS 60.0988 +#define HZ 1789773 +#define GAMMA 2.2 #define CTRL(C) ((C) ^ 0100) #define ALT(C) ((033 << 010) | (C)) -static const char* inputfn; - -typedef uint32_t u32; -typedef uint16_t u16; -typedef uint8_t u8; typedef int8_t s8; +typedef uint8_t u8; +typedef uint16_t u16; +typedef uint32_t u32; static const struct itimerval kNesFps = { {0, 1. / FPS * 1e6}, @@ -75,9 +133,19 @@ struct Audio { int16_t p[FRAMESIZE]; }; +struct Status { + int wait; + char text[80]; +}; + +struct ZipGames { + size_t i, n; + char** p; +}; + static int frame_; +static int drain_; static int playfd_; -static bool piped_; static int devnull_; static int playpid_; static bool exited_; @@ -87,22 +155,32 @@ static size_t vtsize_; static bool artifacts_; static long tyn_, txn_; static const char* ffplay_; -static struct Audio audio_; -static struct TtyRgb* ttyrgb_; static struct Frame vf_[2]; +static struct Audio audio_; +static const char* inputfn_; +static struct Status status_; +static struct TtyRgb* ttyrgb_; static unsigned char *R, *G, *B; +static struct ZipGames zipgames_; static struct Action arrow_, button_; static struct SamplingSolution* asx_; static struct SamplingSolution* ssy_; static struct SamplingSolution* ssx_; static unsigned char pixels_[3][DYN][DXN]; static unsigned char palette_[3][64][512][3]; -static int joy_current_[2] = {0, 0}; -static int joy_next_[2] = {0, 0}; -static int joypos_[2] = {0, 0}; +static int joy_current_[2], joy_next_[2], joypos_[2]; -static int Clamp(int v) { return MAX(0, MIN(255, v)); } -static double FixGamma(double x) { return tv2pcgamma(x, GAMMA); } +static int keyframes_ = 20; +static enum TtyBlocksSelection blocks_ = kTtyBlocksUnicode; +static enum TtyQuantizationAlgorithm quant_ = kTtyQuantTrue; + +static int Clamp(int v) { + return MAX(0, MIN(255, v)); +} + +static double FixGamma(double x) { + return tv2pcgamma(x, GAMMA); +} void InitPalette(void) { // The input value is a NES color index (with de-emphasis bits). @@ -110,7 +188,7 @@ void InitPalette(void) { // We need RGB values. To produce a RGB value, we emulate the NTSC circuitry. double A[3] = {-1.109, -.275, .947}; double B[3] = {1.709, -.636, .624}; - double rgbc[3], lightbulb[3][3], rgbd65[3]; + double rgbc[3], lightbulb[3][3], rgbd65[3], sc[2]; int o, u, r, c, b, p, y, i, l, q, e, p0, p1, pixel; signed char volt[] = "\372\273\32\305\35\311I\330D\357\175\13D!}N"; GetChromaticAdaptationMatrix(lightbulb, kIlluminantC, kIlluminantD65); @@ -119,9 +197,7 @@ void InitPalette(void) { for (p1 = 0; p1 < 64; ++p1) { for (u = 0; u < 3; ++u) { // Calculate the luma and chroma by emulating the relevant circuits: - y = 0; - i = 0; - q = 0; + y = i = q = 0; // 12 samples of NTSC signal constitute a color. for (p = 0; p < 12; ++p) { // Sample either the previous or the current pixel. @@ -140,9 +216,10 @@ void InitPalette(void) { b = 40 + volt[(c > 12 * ((c + 8 + p) % 12 < 6)) + 2 * !(0451326 >> p / 2 * 3 & e) + l]; // Ideal TV NTSC demodulator? + sincos(M_PI * p / 6, &sc[0], &sc[1]); y += b; - i += b * round(cos(M_PI * p / 6) * 5909); - q += b * round(sin(M_PI * p / 6) * 5909); + i += b * sc[1] * 5909; + q += b * sc[0] * 5909; } // Converts YIQ to RGB // Store color at subpixel precision @@ -161,31 +238,62 @@ static void WriteStringNow(const char* s) { ttywrite(STDOUT_FILENO, s, strlen(s)); } -void CleanupTerminal(void) { - ttyraw((enum TtyRawFlags)(-1u)); - ttyshowcursor(STDOUT_FILENO); +void Exit(int rc) { + WriteStringNow("\r\n\e[0m\e[J"); + if (rc && errno) { + fprintf(stderr, "%s%s\r\n", "error: ", strerror(errno)); + } + exit(rc); } -void OnTimer(void) { timeout_ = true; } -void OnResize(void) { resized_ = true; } -void OnCtrlC(void) { exited_ = true; } -void OnSigChld(void) { piped_ = true, playpid_ = 0; } +void Cleanup(void) { + ttyraw((enum TtyRawFlags)(-1u)); + ttyshowcursor(STDOUT_FILENO); + if (playpid_) kill(playpid_, SIGTERM), sched_yield(); +} + +void OnTimer(void) { + timeout_ = true; // also sends EINTR to poll() +} + +void OnResize(void) { + resized_ = true; +} + +void OnPiped(void) { + exited_ = true; +} + +void OnCtrlC(void) { + drain_ = exited_ = true; +} + +void OnSigChld(void) { + exited_ = true, playpid_ = 0; +} void InitFrame(struct Frame* f) { f->p = f->w = f->mem = (char*)realloc(f->mem, vtsize_); } +long ChopAxis(long dn, long sn) { + while (HALF(sn) > dn) { + sn = HALF(sn); + } + return sn; +} + void GetTermSize(void) { struct winsize wsize_; wsize_.ws_row = 25; wsize_.ws_col = 80; - getttysize(STDOUT_FILENO, &wsize_); - tyn_ = wsize_.ws_row * 2; - txn_ = wsize_.ws_col * 2; + getttysize(STDIN_FILENO, &wsize_); FreeSamplingSolution(ssy_); FreeSamplingSolution(ssx_); - ssy_ = ComputeSamplingSolution(tyn_, DYN, 0, 0, 2); - ssx_ = ComputeSamplingSolution(txn_, DXN, 0, 0, 0); + tyn_ = wsize_.ws_row * 2; + txn_ = wsize_.ws_col * 2; + ssy_ = ComputeSamplingSolution(tyn_, ChopAxis(tyn_, DYN), 0, 0, 2); + ssx_ = ComputeSamplingSolution(txn_, ChopAxis(txn_, DXN), 0, 0, 0); R = (unsigned char*)realloc(R, tyn_ * txn_); G = (unsigned char*)realloc(G, tyn_ * txn_); B = (unsigned char*)realloc(B, tyn_ * txn_); @@ -216,20 +324,23 @@ bool TrySpeaker(const char* prog, char* const* args) { void IoInit(void) { GetTermSize(); xsigaction(SIGINT, (void*)OnCtrlC, 0, 0, NULL); + xsigaction(SIGPIPE, (void*)OnPiped, 0, 0, NULL); xsigaction(SIGWINCH, (void*)OnResize, 0, 0, NULL); xsigaction(SIGALRM, (void*)OnTimer, 0, 0, NULL); + xsigaction(SIGCHLD, (void*)OnSigChld, 0, 0, NULL); setitimer(ITIMER_REAL, &kNesFps, NULL); ttyhidecursor(STDOUT_FILENO); ttyraw(kTtySigs); - ttyquantinit(kTtyQuantTrue, kTtyQuantRgb, kTtyBlocksUnicode); - atexit(CleanupTerminal); + ttyquantsetup(quant_, kTtyQuantRgb, blocks_); + atexit(Cleanup); } -void SystemFailure(void) { - fputs("error: ", stderr); - fputs(strerror(errno), stderr); - fputc('\n', stderr); - exit(7); +void SetStatus(const char* fmt, ...) { + va_list va; + va_start(va, fmt); + vsnprintf(status_.text, sizeof(status_.text), fmt, va); + va_end(va); + status_.wait = FPS / 2; } void ReadKeyboard(void) { @@ -238,6 +349,7 @@ void ReadKeyboard(void) { ssize_t i, rc; memset(b, -1, sizeof(b)); if ((rc = read(STDIN_FILENO, b, 16)) != -1) { + if (!rc) exited_ = true; for (i = 0; i < rc; ++i) { ch = b[i]; if (b[i] == '\e') { @@ -263,71 +375,105 @@ void ReadKeyboard(void) { } } switch (ch) { - case '1': - artifacts_ = !artifacts_; - InitPalette(); - break; case ' ': button_.code = 0b00100000; // A - button_.wait = KEYHZ; + button_.wait = keyframes_; break; case 'b': button_.code = 0b00010000; // B - button_.wait = KEYHZ; + button_.wait = keyframes_; break; case '\r': // enter button_.code = 0b10000000; // START - button_.wait = KEYHZ; + button_.wait = keyframes_; break; case '\t': // tab button_.code = 0b01000000; // SELECT - button_.wait = KEYHZ; + button_.wait = keyframes_; break; case 'k': // vim case 'w': // wasd qwerty case ',': // wasd dvorak case CTRL('P'): // emacs arrow_.code = 0b00000100; // UP - arrow_.wait = KEYHZ; + arrow_.wait = keyframes_; break; case 'j': // vim case 's': // wasd qwerty case 'o': // wasd dvorak case CTRL('N'): // emacs arrow_.code = 0b00001000; // DOWN - arrow_.wait = KEYHZ; + arrow_.wait = keyframes_; break; case 'h': // vim case 'a': // wasd qwerty & dvorak case CTRL('B'): // emacs arrow_.code = 0b00000010; // LEFT - arrow_.wait = KEYHZ; + arrow_.wait = keyframes_; break; case 'l': // vim case 'd': // wasd qwerty case 'e': // wasd dvorak case CTRL('F'): // emacs arrow_.code = 0b00000001; // RIGHT - arrow_.wait = KEYHZ; + arrow_.wait = keyframes_; + break; + case 'A': // ansi 4-bit color mode + quant_ = kTtyQuantAnsi; + ttyquantsetup(quant_, kTtyQuantRgb, blocks_); + SetStatus("ansi color"); break; case 'x': // xterm256 color mode - ttyquantinit(kTtyQuantXterm256, kTtyQuantRgb, kTtyBlocksUnicode); + quant_ = kTtyQuantXterm256; + ttyquantsetup(quant_, kTtyQuantRgb, blocks_); + SetStatus("xterm256 color"); break; case 't': // ansi 24bit color mode - ttyquantinit(kTtyQuantTrue, kTtyQuantRgb, kTtyBlocksUnicode); + quant_ = kTtyQuantTrue; + ttyquantsetup(quant_, kTtyQuantRgb, blocks_); + SetStatus("24-bit color"); + break; + case '1': + artifacts_ = !artifacts_; + InitPalette(); + SetStatus("artifacts %s", artifacts_ ? "on" : "off"); + break; + case '3': // oldskool ibm unicode rasterization + blocks_ = kTtyBlocksCp437; + ttyquantsetup(quant_, kTtyQuantRgb, blocks_); + SetStatus("IBM CP437"); + break; + case '4': // newskool unicode rasterization + blocks_ = kTtyBlocksUnicode; + ttyquantsetup(quant_, kTtyQuantRgb, blocks_); + SetStatus("UNICODE"); + break; + case '8': + keyframes_ = MAX(1, keyframes_ - 1); + SetStatus("%d key frames", keyframes_); + break; + case '9': + keyframes_ = keyframes_ + 1; + SetStatus("%d key frames", keyframes_); break; default: break; } } - } else { - SystemFailure(); } } -bool HasVideo(struct Frame* f) { return f->w < f->p; } -bool HasPendingVideo(void) { return HasVideo(&vf_[0]) || HasVideo(&vf_[1]); } -bool HasPendingAudio(void) { return playpid_ && audio_.i; } +bool HasVideo(struct Frame* f) { + return f->w < f->p; +} + +bool HasPendingVideo(void) { + return HasVideo(&vf_[0]) || HasVideo(&vf_[1]); +} + +bool HasPendingAudio(void) { + return playpid_ && audio_.i; +} struct Frame* FlipFrameBuffer(void) { frame_ = !frame_; @@ -341,8 +487,10 @@ void TransmitVideo(void) { if (!HasVideo(f)) f = FlipFrameBuffer(); if ((rc = write(STDOUT_FILENO, f->w, f->p - f->w)) != -1) { f->w += rc; - } else { - SystemFailure(); + } else if (errno == EPIPE) { + Exit(0); + } else if (errno != EINTR) { + Exit(1); } } @@ -353,19 +501,36 @@ void TransmitAudio(void) { rc /= sizeof(short); memmove(audio_.p, audio_.p + rc, (audio_.i - rc) * sizeof(short)); audio_.i -= rc; - } else { - SystemFailure(); + } else if (errno == EPIPE) { + Exit(0); + } else if (errno != EINTR) { + Exit(1); } } void ScaleVideoFrameToTeletypewriter(void) { - long y, x; - GyaradosUint8(tyn_, txn_, R, DYN, DXN, pixels_[0], tyn_, txn_, DYN, DXN, 0, - 255, ssy_, ssx_, true); - GyaradosUint8(tyn_, txn_, G, DYN, DXN, pixels_[1], tyn_, txn_, DYN, DXN, 0, - 255, ssy_, ssx_, true); - GyaradosUint8(tyn_, txn_, B, DYN, DXN, pixels_[2], tyn_, txn_, DYN, DXN, 0, - 255, ssy_, ssx_, true); + long y, x, yn, xn; + yn = DYN, xn = DXN; + while (HALF(yn) > tyn_ || HALF(xn) > txn_) { + if (HALF(xn) > txn_) { + Magikarp2xX(DYN, DXN, pixels_[0], yn, xn); + Magikarp2xX(DYN, DXN, pixels_[1], yn, xn); + Magikarp2xX(DYN, DXN, pixels_[2], yn, xn); + xn = HALF(xn); + } + if (HALF(yn) > tyn_) { + Magikarp2xY(DYN, DXN, pixels_[0], yn, xn); + Magikarp2xY(DYN, DXN, pixels_[1], yn, xn); + Magikarp2xY(DYN, DXN, pixels_[2], yn, xn); + yn = HALF(yn); + } + } + GyaradosUint8(tyn_, txn_, R, DYN, DXN, pixels_[0], tyn_, txn_, yn, xn, 0, 255, + ssy_, ssx_, true); + GyaradosUint8(tyn_, txn_, G, DYN, DXN, pixels_[1], tyn_, txn_, yn, xn, 0, 255, + ssy_, ssx_, true); + GyaradosUint8(tyn_, txn_, B, DYN, DXN, pixels_[2], tyn_, txn_, yn, xn, 0, 255, + ssy_, ssx_, true); for (y = 0; y < tyn_; ++y) { for (x = 0; x < txn_; ++x) { ttyrgb_[y * txn_ + x] = @@ -382,15 +547,10 @@ void KeyCountdown(struct Action* a) { } } -void DrainAndExit(void) { - while (HasPendingVideo()) TransmitVideo(); - WriteStringNow("\r\n\e[0m\e[J"); - exit(0); -} - void PollAndSynchronize(void) { struct pollfd fds[3]; do { + errno = 0; fds[0].fd = STDIN_FILENO; fds[0].events = POLLIN; fds[1].fd = HasPendingVideo() ? STDOUT_FILENO : -1; @@ -402,10 +562,15 @@ void PollAndSynchronize(void) { if (fds[1].revents & (POLLOUT | POLLERR)) TransmitVideo(); if (fds[2].revents & (POLLOUT | POLLERR)) TransmitAudio(); } else if (errno != EINTR) { - SystemFailure(); + Exit(1); } if (exited_) { - DrainAndExit(); + if (drain_) { + while (HasPendingVideo()) { + TransmitVideo(); + } + } + Exit(0); } if (resized_) { resized_ = false; @@ -429,6 +594,11 @@ void Raster(void) { f->p = f->w = f->mem; f->p = stpcpy(f->p, "\e[0m\e[H"); f->p = ttyraster(f->p, ttyrgb_, tyn_, txn_, bg, fg); + if (status_.wait) { + status_.wait--; + f->p = stpcpy(f->p, "\e[0m\e[H"); + f->p = stpcpy(f->p, status_.text); + } CHECK_LT(f->p - f->mem, vtsize_); PollAndSynchronize(); } @@ -443,13 +613,8 @@ void FlushScanline(unsigned py) { } static void PutPixel(unsigned px, unsigned py, unsigned pixel, int offset) { - static bool once; - static unsigned prev; unsigned rgb; - if (!once) { - InitPalette(); - once = true; - } + static unsigned prev; pixels_[0][py][px] = palette_[offset][prev % 64][pixel][2]; pixels_[1][py][px] = palette_[offset][prev % 64][pixel][1]; pixels_[2][py][px] = palette_[offset][prev % 64][pixel][0]; @@ -491,8 +656,12 @@ struct RegBit { data = (data & ~(mask << bitno)) | ((nbits > 1 ? v & mask : !!v) << bitno); return *this; } - operator unsigned() const { return (data >> bitno) & mask; } - RegBit& operator++() { return *this = *this + 1; } + operator unsigned() const { + return (data >> bitno) & mask; + } + RegBit& operator++() { + return *this = *this + 1; + } unsigned operator++(int) { unsigned r = *this; ++*this; @@ -602,8 +771,12 @@ bool intr = false; template u8 MemAccess(u16 addr, u8 v = 0); -u8 RB(u16 addr) { return MemAccess<0>(addr); } -u8 WB(u16 addr, u8 v) { return MemAccess<1>(addr, v); } +u8 RB(u16 addr) { + return MemAccess<0>(addr); +} +u8 WB(u16 addr, u8 v) { + return MemAccess<1>(addr, v); +} void Tick(); } // namespace CPU @@ -922,8 +1095,8 @@ void RenderPixel() { void ReadToolAssistedSpeedrunRobotKeys() { static FILE* fp; - if (!fp && !isempty(inputfn)) { - fp = fopen(inputfn, "rb"); + if (!fp && !isempty(inputfn_)) { + fp = fopen(inputfn_, "rb"); } if (fp) { static unsigned ctrlmask = 0; @@ -1039,7 +1212,9 @@ bool ChannelsEnabled[5]; bool PeriodicIRQ; bool DMC_IRQ; -bool count(int& v, int reset) { return --v < 0 ? (v = reset), true : false; } +bool count(int& v, int reset) { + return --v < 0 ? (v = reset), true : false; +} struct channel { int length_counter, linear_counter, address, envelope; @@ -1119,9 +1294,11 @@ struct channel { // Note: Re-entrant! But not recursive, because even // the shortest wave length is greater than the read time. // TODO: proper clock - if (ch.reg.WaveLength > 20) - for (unsigned t = 0; t < 3; ++t) - CPU::RB(u16(ch.address) | 0x8000); // timing + if (ch.reg.WaveLength > 20) { + for (unsigned t = 0; t < 3; ++t) { + CPU::RB(u16(ch.address) | 0x8000); // timing + } + } ch.hold = CPU::RB(u16(ch.address++) | 0x8000); // Fetch byte ch.phase = 8; --ch.length_counter; @@ -1357,13 +1534,19 @@ union { /* Status flags: */ RegBit<7> N; // negative } P; -u16 wrap(u16 oldaddr, u16 newaddr) { return (oldaddr & 0xFF00) + u8(newaddr); } +u16 wrap(u16 oldaddr, u16 newaddr) { + return (oldaddr & 0xFF00) + u8(newaddr); +} void Misfire(u16 old, u16 addr) { u16 q = wrap(old, addr); if (q != addr) RB(q); } -u8 Pop() { return RB(0x100 | u8(++S)); } -void Push(u8 v) { WB(0x100 | u8(S--), v); } +u8 Pop() { + return RB(0x100 | u8(++S)); +} +void Push(u8 v) { + WB(0x100 | u8(S--), v); +} template // Execute a single CPU instruction, defined by opcode "op". void Ins() { // With template magic, the compiler will literally synthesize @@ -1503,47 +1686,59 @@ void Op() { } // namespace CPU -int main(int argc, char** argv) { +char* GetLine(void) { + static char* line; + static size_t linesize; + if (getline(&line, &linesize, stdin) > 0) { + return chomp(line); + } else { + return NULL; + } +} + +int PlayGame(const char* romfile, const char* opt_tasfile) { FILE* fp; + inputfn_ = opt_tasfile; - if (argc <= 1 || (strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "-?") == 0 || - strcmp(argv[1], "--help") == 0)) { - fprintf(stderr, "%s%s%s\n", "Usage: ", argv[0], " ROM [FMV]"); - exit(1); + if (!(fp = fopen(romfile, "rb"))) { + fprintf(stderr, "%s%s\n", "failed to open: ", romfile); + return 2; } - - // Open the ROM file specified on commandline - fp = fopen(argv[1], "rb"); /* your .nes file */ - inputfn = argc >= 3 ? argv[2] : NULL; /* some tas thing */ - - if (!fp) { - fprintf(stderr, "%s%s\n", "not a nes rom file: ", argv[1]); - exit(2); - } - if (!(fgetc(fp) == 'N' && fgetc(fp) == 'E' && fgetc(fp) == 'S' && fgetc(fp) == CTRL('Z'))) { - fprintf(stderr, "%s%s\n", "not a nes rom file: ", argv[1]); - exit(3); + fprintf(stderr, "%s%s\n", "not a nes rom file: ", romfile); + return 3; } + InitPalette(); + // open speaker // todo: this needs plenty of work devnull_ = open("/dev/null", O_WRONLY); - ffplay_ = commandvenv("FFPLAY", "ffplay"); - if (devnull_ != -1 && ffplay_) { + if ((ffplay_ = commandvenv("FFPLAY", "ffplay"))) { const char* args[] = { "ffplay", "-nodisp", "-loglevel", "quiet", "-fflags", "nobuffer", "-ac", "1", "-ar", "1789773", "-f", "s16le", "pipe:", NULL, }; TrySpeaker(ffplay_, (char* const*)args); + } else { + fputs("\nWARNING\n\ +\n\ + Need `ffplay` command to play audio\n\ + Try `sudo apt install ffmpeg` on Linux\n\ + You can specify it on `PATH` or in `FFPLAY`\n\ +\n\ +Press enter to continue without sound: ", + stdout); + fflush(stdout); + GetLine(); } // Read the ROM file header u8 rom16count = fgetc(fp); u8 vrom8count = fgetc(fp); u8 ctrlbyte = fgetc(fp); - u8 mappernum = fgetc(fp) | (ctrlbyte >> 4); + u8 mappernum = fgetc(fp) | ctrlbyte >> 4; fgetc(fp); fgetc(fp); @@ -1578,3 +1773,96 @@ int main(int argc, char** argv) { // Run the CPU until the program is killed. for (;;) CPU::Op(); } + +noreturn void PrintUsage(int rc, FILE* f) { + fprintf(f, "%s%s%s", "Usage: ", program_invocation_name, USAGE); + exit(rc); +} + +void GetOpts(int argc, char* argv[]) { + int opt; + while ((opt = getopt(argc, argv, "?hAxt134")) != -1) { + switch (opt) { + case 'A': + quant_ = kTtyQuantAnsi; + break; + case 'x': + quant_ = kTtyQuantXterm256; + break; + case 't': + quant_ = kTtyQuantTrue; + break; + case '1': + artifacts_ = !artifacts_; + break; + case '3': + blocks_ = kTtyBlocksCp437; + break; + case '4': + blocks_ = kTtyBlocksUnicode; + break; + case 'h': + case '?': + PrintUsage(EXIT_SUCCESS, stdout); + default: + PrintUsage(EX_USAGE, stderr); + } + } +} + +size_t FindZipGames(void) { + char* name; + struct Zipos* zipos; + size_t i, cf, namesize; + if ((zipos = __zipos_get())) { + for (i = 0, cf = ZIP_CDIR_OFFSET(zipos->cdir); + i < ZIP_CDIR_RECORDS(zipos->cdir); + ++i, cf += ZIP_CFILE_HDRSIZE(zipos->map + cf)) { + if ((name = strndup(ZIP_CFILE_NAME(zipos->map + cf), + ZIP_CFILE_NAMESIZE(zipos->map + cf))) && + endswith(name, ".nes")) { + APPEND(&zipgames_.p, &zipgames_.i, &zipgames_.n, &name); + } else { + free(name); + } + } + } + return zipgames_.i; +} + +int SelectGameFromZip(void) { + int i, rc; + char *line, *uri; + fputs("\nCOSMOPOLITAN NESEMU1\n\n", stdout); + for (i = 0; i < zipgames_.i; ++i) { + printf(" [%d] %s\n", i, zipgames_.p[i]); + } + fputs("\nPlease choose a game (or CTRL-C to quit) [default 0]: ", stdout); + fflush(stdout); + rc = 0; + if ((line = GetLine())) { + i = MAX(0, MIN(zipgames_.i - 1, atoi(line))); + uri = xasprintf("zip:%s", zipgames_.p[i]); + rc = PlayGame(uri, NULL); + free(uri); + } else { + fputs("\n", stdout); + } + return rc; +} + +int main(int argc, char** argv) { + int rc; + GetOpts(argc, argv); + if (optind + 1 < argc) { + rc = PlayGame(argv[optind], argv[optind + 1]); + } else if (optind < argc) { + rc = PlayGame(argv[optind], NULL); + } else { + if (!FindZipGames()) { + PrintUsage(0, stderr); + } + rc = SelectGameFromZip(); + } + return rc; +} diff --git a/examples/printargs.c b/examples/printargs.c index c3648a01..0953aed3 100644 --- a/examples/printargs.c +++ b/examples/printargs.c @@ -50,19 +50,22 @@ const struct AuxiliaryValue { }; int main(int argc, char *argv[], char **envp) { + long key; + unsigned i; + unsigned long val; + char fmt[64], **env; printf("\nArguments:\n"); - for (unsigned i = 0; i < argc; ++i) { + for (i = 0; i < argc; ++i) { printf(" ☼ %s\n", argv[i]); } printf("\nEnvironment:\n"); - for (char **env = envp; *env; ++env) { + for (env = envp; *env; ++env) { printf(" ☼ %s\n", *env); } printf("\nAuxiliary Values:\n"); - for (unsigned i = 0; i < ARRAYLEN(kAuxiliaryValues); ++i) { - long key = *kAuxiliaryValues[i].id; - unsigned long val = getauxval(key); - char fmt[64]; + for (i = 0; i < ARRAYLEN(kAuxiliaryValues); ++i) { + key = *kAuxiliaryValues[i].id; + val = getauxval(key); printf(PROGN(stpcpy(stpcpy(stpcpy(fmt, "%16s[%p] = "), kAuxiliaryValues[i].fmt), " # %s\n"), diff --git a/examples/unbourne.c b/examples/unbourne.c index fc61eefb..03adad57 100644 --- a/examples/unbourne.c +++ b/examples/unbourne.c @@ -431,40 +431,40 @@ /* * Evaluate a command. */ -#define ALIASCMD (builtincmd + 3) -#define BGCMD (builtincmd + 4) -#define BREAKCMD (builtincmd + 5) -#define CDCMD (builtincmd + 6) -#define COMMANDCMD (builtincmd + 8) -#define DOTCMD (builtincmd + 0) -#define ECHOCMD (builtincmd + 10) -#define EVALCMD (builtincmd + 11) -#define EXECCMD (builtincmd + 12) -#define EXITCMD (builtincmd + 13) -#define EXPORTCMD (builtincmd + 14) -#define FALSECMD (builtincmd + 15) -#define FGCMD (builtincmd + 16) -#define GETOPTSCMD (builtincmd + 17) -#define HASHCMD (builtincmd + 18) -#define JOBSCMD (builtincmd + 19) -#define KILLCMD (builtincmd + 20) -#define LOCALCMD (builtincmd + 21) -#define PRINTFCMD (builtincmd + 22) -#define PWDCMD (builtincmd + 23) -#define READCMD (builtincmd + 24) -#define RETURNCMD (builtincmd + 26) -#define SETCMD (builtincmd + 27) -#define SHIFTCMD (builtincmd + 28) -#define TESTCMD (builtincmd + 2) -#define TIMESCMD (builtincmd + 30) -#define TRAPCMD (builtincmd + 31) -#define TRUECMD (builtincmd + 1) -#define TYPECMD (builtincmd + 33) -#define ULIMITCMD (builtincmd + 34) -#define UMASKCMD (builtincmd + 35) -#define UNALIASCMD (builtincmd + 36) -#define UNSETCMD (builtincmd + 37) -#define WAITCMD (builtincmd + 38) +#define ALIASCMD (kBuiltinCmds + 3) +#define BGCMD (kBuiltinCmds + 4) +#define BREAKCMD (kBuiltinCmds + 5) +#define CDCMD (kBuiltinCmds + 6) +#define COMMANDCMD (kBuiltinCmds + 8) +#define DOTCMD (kBuiltinCmds + 0) +#define ECHOCMD (kBuiltinCmds + 10) +#define EVALCMD (kBuiltinCmds + 11) +#define EXECCMD (kBuiltinCmds + 12) +#define EXITCMD (kBuiltinCmds + 13) +#define EXPORTCMD (kBuiltinCmds + 14) +#define FALSECMD (kBuiltinCmds + 15) +#define FGCMD (kBuiltinCmds + 16) +#define GETOPTSCMD (kBuiltinCmds + 17) +#define HASHCMD (kBuiltinCmds + 18) +#define JOBSCMD (kBuiltinCmds + 19) +#define KILLCMD (kBuiltinCmds + 20) +#define LOCALCMD (kBuiltinCmds + 21) +#define PRINTFCMD (kBuiltinCmds + 22) +#define PWDCMD (kBuiltinCmds + 23) +#define READCMD (kBuiltinCmds + 24) +#define RETURNCMD (kBuiltinCmds + 26) +#define SETCMD (kBuiltinCmds + 27) +#define SHIFTCMD (kBuiltinCmds + 28) +#define TESTCMD (kBuiltinCmds + 2) +#define TIMESCMD (kBuiltinCmds + 30) +#define TRAPCMD (kBuiltinCmds + 31) +#define TRUECMD (kBuiltinCmds + 1) +#define TYPECMD (kBuiltinCmds + 33) +#define ULIMITCMD (kBuiltinCmds + 34) +#define UMASKCMD (kBuiltinCmds + 35) +#define UNALIASCMD (kBuiltinCmds + 36) +#define UNSETCMD (kBuiltinCmds + 37) +#define WAITCMD (kBuiltinCmds + 38) #define BUILTIN_SPECIAL 0x1 #define BUILTIN_REGULAR 0x2 @@ -620,12 +620,12 @@ #define octtobin(c) ((c) - '0') #define scopy(s1, s2) ((void)strcpy(s2, s1)) -#define TRACE(param) -/* #define TRACE(param) \ */ -/* do { \ */ -/* printf("TRACE: "); \ */ -/* printf param; \ */ -/* } while (0) */ +/* #define TRACE(param) */ +#define TRACE(param) \ + do { \ + printf("TRACE: "); \ + printf param; \ + } while (0) #define TRACEV(param) #define digit_val(c) ((c) - '0') @@ -1184,13 +1184,16 @@ static const char dolatstr[] = {CTLQUOTEMARK, CTLVAR, VSNORMAL, '@', '=', CTLQUO /* Some macros depend on the order, add new variables to the end. */ static void changepath(const char *); static void getoptsreset(const char *); -static struct Var varinit[] = {{0, VSTRFIXED | VTEXTFIXED, defifsvar, 0}, - {0, VSTRFIXED | VTEXTFIXED, defpathvar, changepath}, - {0, VSTRFIXED | VTEXTFIXED, "PS1=$ ", 0}, - {0, VSTRFIXED | VTEXTFIXED, "PS2=> ", 0}, - {0, VSTRFIXED | VTEXTFIXED, "PS4=+ ", 0}, - {0, VSTRFIXED | VTEXTFIXED, defoptindvar, getoptsreset}, - {0, VSTRFIXED | VTEXTFIXED, linenovar, 0}}; + +static struct Var varinit[] = { + {0, VSTRFIXED | VTEXTFIXED, defifsvar, 0}, + {0, VSTRFIXED | VTEXTFIXED, defpathvar, changepath}, + {0, VSTRFIXED | VTEXTFIXED, "PS1=$ ", 0}, + {0, VSTRFIXED | VTEXTFIXED, "PS2=> ", 0}, + {0, VSTRFIXED | VTEXTFIXED, "PS4=+ ", 0}, + {0, VSTRFIXED | VTEXTFIXED, defoptindvar, getoptsreset}, + {0, VSTRFIXED | VTEXTFIXED, linenovar, 0}, +}; static const char kPrec[ARITH_BINOP_MAX - ARITH_BINOP_MIN] = { #define ARITH_PRECEDENCE(OP, PREC) [OP - ARITH_BINOP_MIN] = PREC @@ -1308,7 +1311,8 @@ static const char sqsyntax[] = { CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CCTL, CCTL, CCTL, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, - CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CCTL, CWORD}; + CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CCTL, CWORD, +}; /* syntax table used when in arithmetic */ static const char arisyntax[] = { @@ -1331,7 +1335,8 @@ static const char arisyntax[] = { CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CBACK, CWORD, CWORD, CWORD, CBQUOTE, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, - CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CENDVAR, CWORD, CWORD}; + CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CENDVAR, CWORD, CWORD, +}; /* character classification table */ static const char is_type[] /* clang-format off */ = { @@ -1405,20 +1410,47 @@ static int unaliascmd(); static int unsetcmd(); static int waitcmd(); -static const struct builtincmd builtincmd[] = { - {".", dotcmd, 3}, {":", truecmd, 3}, {"[", testcmd, 0}, - {"alias", aliascmd, 6}, {"bg", bgcmd, 2}, {"break", breakcmd, 3}, - {"cd", cdcmd, 2}, {"chdir", cdcmd, 0}, {"command", commandcmd, 2}, - {"continue", breakcmd, 3}, {"echo", echocmd, 0}, {"eval", NULL, 3}, - {"exec", execcmd, 3}, {"exit", exitcmd, 3}, {"export", exportcmd, 7}, - {"false", falsecmd, 2}, {"fg", fgcmd, 2}, {"getopts", getoptscmd, 2}, - {"hash", hashcmd, 2}, {"jobs", jobscmd, 2}, {"kill", killcmd, 2}, - {"local", localcmd, 7}, {"printf", printfcmd, 0}, {"pwd", pwdcmd, 2}, - {"read", readcmd, 2}, {"readonly", exportcmd, 7}, {"return", returncmd, 3}, - {"set", setcmd, 3}, {"shift", shiftcmd, 3}, {"test", testcmd, 0}, - {"times", timescmd, 3}, {"trap", trapcmd, 3}, {"true", truecmd, 2}, - {"type", typecmd, 2}, {"ulimit", ulimitcmd, 2}, {"umask", umaskcmd, 2}, - {"unalias", unaliascmd, 2}, {"unset", unsetcmd, 3}, {"wait", waitcmd, 2}}; +static const struct builtincmd kBuiltinCmds[] = { + {".", dotcmd, 3}, // + {":", truecmd, 3}, // + {"[", testcmd, 0}, // + {"alias", aliascmd, 6}, // + {"bg", bgcmd, 2}, // + {"break", breakcmd, 3}, // + {"cd", cdcmd, 2}, // + {"chdir", cdcmd, 0}, // + {"command", commandcmd, 2}, // + {"continue", breakcmd, 3}, // + {"echo", echocmd, 0}, // + {"eval", NULL, 3}, // + {"exec", execcmd, 3}, // + {"exit", exitcmd, 3}, // + {"export", exportcmd, 7}, // + {"false", falsecmd, 2}, // + {"fg", fgcmd, 2}, // + {"getopts", getoptscmd, 2}, // + {"hash", hashcmd, 2}, // + {"jobs", jobscmd, 2}, // + {"kill", killcmd, 2}, // + {"local", localcmd, 7}, // + {"printf", printfcmd, 0}, // + {"pwd", pwdcmd, 2}, // + {"read", readcmd, 2}, // + {"readonly", exportcmd, 7}, // + {"return", returncmd, 3}, // + {"set", setcmd, 3}, // + {"shift", shiftcmd, 3}, // + {"test", testcmd, 0}, // + {"times", timescmd, 3}, // + {"trap", trapcmd, 3}, // + {"true", truecmd, 2}, // + {"type", typecmd, 2}, // + {"ulimit", ulimitcmd, 2}, // + {"umask", umaskcmd, 2}, // + {"unalias", unaliascmd, 2}, // + {"unset", unsetcmd, 3}, // + {"wait", waitcmd, 2}, // +}; enum token { EOI, @@ -2115,7 +2147,7 @@ static int olderf(const char *, const char *); static int64_t openhere(union node *); static int openredirect(union node *); static int options(int); -static int padvance_magic(const char **path, const char *name, int magic); +static int padvance_magic(const char **, const char *, int); static int patmatch(char *, const char *); static int peektoken(void); static int pgetc(void); @@ -2882,7 +2914,7 @@ step6: goto docd; err: sh_error("can't cd to %s", dest); - /* NOTREACHED */ + unreachable; out: if (flags & CD_PRINT) out1fmt(snlfmt, curdir); return 0; @@ -3446,7 +3478,7 @@ static void evalbackcmd(union node *n, struct backcmd *result) { } ifsfree(); evaltreenr(n, EV_EXIT); - /* NOTREACHED */ + unreachable; } close(pip[1]); result->fd = pip[0]; @@ -3629,7 +3661,7 @@ static int evalcommand(union node *cmd, int flags) { break; } shellexec(argv, path, cmdentry.u.index); - /* NOTREACHED */ + unreachable; case CMDBUILTIN: if (evalbltin(cmdentry.u.cmd, argc, argv, flags) && !(exception == EXERROR && spclbltin <= 0)) { @@ -3742,9 +3774,11 @@ funcdone: */ static void prehash(union node *n) { struct cmdentry entry; - if (n->type == NCMD && n->ncmd.args) - if (goodname(n->ncmd.args->narg.text)) + if (n->type == NCMD && n->ncmd.args) { + if (goodname(n->ncmd.args->narg.text)) { find_command(n->ncmd.args->narg.text, &entry, 0, pathval()); + } + } } /*───────────────────────────────────────────────────────────────────────────│─╗ @@ -4014,10 +4048,8 @@ static void readcmdfile(char *name) { * Search the table of builtin commands. */ static struct builtincmd *find_builtin(const char *name) { - struct builtincmd *bp; - bp = bsearch(&name, builtincmd, sizeof(builtincmd) / sizeof(builtincmd[0]), sizeof(builtincmd[0]), - pstrcmp); - return bp; + return bsearch(&name, kBuiltinCmds, sizeof(kBuiltinCmds) / sizeof(kBuiltinCmds[0]), + sizeof(kBuiltinCmds[0]), pstrcmp); } /* @@ -4025,15 +4057,11 @@ static struct builtincmd *find_builtin(const char *name) { * change the shellexec routine as well. */ static void find_command(char *name, struct cmdentry *entry, int act, const char *path) { - struct tblentry *cmdp; - int idx; - int prev; char *fullname; struct stat statb; - int e; - int updatetbl; + struct tblentry *cmdp; struct builtincmd *bcmd; - int len; + int e, bit, idx, prev, updatetbl, len; /* If name contains a slash, don't use PATH or hash table */ if (strchr(name, '/') != NULL) { entry->u.index = -1; @@ -4050,7 +4078,6 @@ static void find_command(char *name, struct cmdentry *entry, int act, const char if (!updatetbl) act |= DO_ALTPATH; /* If name is in the table, check answer will be ok */ if ((cmdp = cmdlookup(name, 0)) != NULL) { - int bit; switch (cmdp->cmdtype) { default: case CMDNORMAL: @@ -4067,9 +4094,10 @@ static void find_command(char *name, struct cmdentry *entry, int act, const char if (act & bit & DO_REGBLTIN) goto fail; updatetbl = 0; cmdp = NULL; - } else if (cmdp->rehash == 0) + } else if (cmdp->rehash == 0) { /* if not invalidated by cd, we're done */ goto success; + } } /* If %builtin not in path, check for builtin next */ bcmd = find_builtin(name); @@ -4744,7 +4772,7 @@ static void expbackq(union node *cmd, int flag) { recordregion(startloc, dest - (char *)stackblock(), 0); } TRACE(("evalbackq: size=%d: \"%.*s\"\n", (dest - (char *)stackblock()) - startloc, - (dest - (char *)stackblock()) - startloc, stackblock() + startloc)); + (dest - (char *)stackblock()) - startloc, (char *)stackblock() + startloc)); out: argbackq = argbackq->next; } @@ -4822,7 +4850,7 @@ static char *subevalvar(char *start, char *str, int strloc, int startloc, int va goto out; case VSQUESTION: varunset(start, str, startp, varflags); - /* NOTREACHED */ + unreachable; } subtype -= VSTRIMRIGHT; rmesc = startp; @@ -6450,7 +6478,7 @@ static void forkparent(struct job *jp, union node *n, int mode, int pid) { TRACE(("Fork failed, errno=%d", errno)); if (jp) freejob(jp); sh_error("Cannot fork"); - /* NOTREACHED */ + unreachable; } TRACE(("In parent shell: child = %d\n", pid)); if (!jp) return; @@ -6480,10 +6508,11 @@ static int forkshell(struct job *jp, union node *n, int mode) { int pid; TRACE(("forkshell(%%%d, %p, %d) called\n", jobno(jp), n, mode)); pid = fork(); - if (pid == 0) + if (pid == 0) { forkchild(jp, n, mode); - else + } else { forkparent(jp, n, mode, pid); + } return pid; } @@ -6498,7 +6527,7 @@ static struct job *vforkexec(union node *n, char **argv, const char *path, int i forkchild(jp, n, FORK_FG); sigclearmask(); shellexec(argv, path, idx); - /* NOTREACHED */ + unreachable; } vforked = 0; sigclearmask(); @@ -7355,7 +7384,7 @@ static void setoption(int flag, int val) { return; } sh_error("Illegal option -%c", flag); - /* NOTREACHED */ + unreachable; } /* @@ -7799,7 +7828,7 @@ static union node *command(void) { switch (readtoken()) { default: synexpect(-1); - /* NOTREACHED */ + unreachable; case TIF: n1 = (union node *)stalloc(sizeof(struct nif)); n1->type = NIF; @@ -9998,10 +10027,10 @@ static void initvar(void) { vp->next = *vpp; *vpp = vp; } while (++vp < end); - /* - * PS1 depends on uid - */ - if (!geteuid()) vps1.text = "PS1=# "; + /* PS1 depends on uid */ + if (!geteuid()) { + vps1.text = "PS1=# "; + } } /* @@ -10749,7 +10778,7 @@ static int exitcmd(int argc, char **argv) { exraise(EXEXIT); } -/* +/** * Main routine. We initialize things, parse the arguments, execute * profiles if we're a login shell, and then call cmdloop to execute * commands. The setjmp call sets up the location to jump to when an diff --git a/examples/x86split.c b/examples/x86split.c index 22122abd..a5b423d5 100644 --- a/examples/x86split.c +++ b/examples/x86split.c @@ -39,7 +39,7 @@ int main(int argc, char *argv[argc]) { errno = err; break; } - l = xedd.decoded_length; + l = xedd.length; if (l <= 0 || l > i) abort(); for (j = 0; j < l; ++j) { if (fputhex(buf[j], stdout) == -1) { diff --git a/libc/README.md b/libc/README.md index 6fc8dd36..184c406e 100644 --- a/libc/README.md +++ b/libc/README.md @@ -48,7 +48,3 @@ OVERVIEW be compared to something more along the lines of, "the Russians just used a pencil to write in space", versus spending millions researching a pen like NASA. - - One cost worth noting, concerning stateless API translation, is - file descriptors needed to change from `int` to `int64_t`, thus - requiring a certain degree of added care when porting software. diff --git a/libc/alg/alg.h b/libc/alg/alg.h index 467eb3a6..207ce403 100644 --- a/libc/alg/alg.h +++ b/libc/alg/alg.h @@ -18,9 +18,8 @@ void qsort(void *, size_t, size_t, int (*)(const void *, const void *)) void qsort_r(void *, size_t, size_t, int cmp(const void *, const void *, void *), void *arg) paramsnonnull((1, 4)); -int tarjan(uint32_t, const uint32_t (*)[2], uint32_t, uint32_t[], uint32_t[], - uint32_t *) paramsnonnull((2, 4)) nocallback nothrow; -void heapsortcar(int32_t (*)[2], unsigned) paramsnonnull() nocallback nothrow; +int tarjan(int, const int (*)[2], int, int[], int[], int *) + paramsnonnull((2, 4)) nocallback nothrow; void *memmem(const void *, size_t, const void *, size_t) paramsnonnull() nothrow nocallback nosideeffect; diff --git a/libc/alg/arraylist2.h b/libc/alg/arraylist2.h index 92195f2a..9ad045bc 100644 --- a/libc/alg/arraylist2.h +++ b/libc/alg/arraylist2.h @@ -19,6 +19,11 @@ COSMOPOLITAN_C_START_ size_t SizE = sizeof(*Item); \ size_t Count = (COUNT); \ ssize_t Entry = -1; \ + /* NOTE: We use `<` to guarantee one additional slot */ \ + /* grow() will memset(0) extended memory, thus */ \ + /* you get a nul-terminator for free sometimes */ \ + /* the exception is if you list.i=0 and re-use */ \ + /* so you need concat(...); list.p[list.i++]=0 */ \ if (*ListI + Count < *ListN || grow(ListP, ListN, SizE, Count)) { \ memcpy(&(*ListP)[*ListI], Item, (SizE) * (Count)); \ Entry = *ListI; \ diff --git a/libc/alg/qsort.c b/libc/alg/qsort.c index 5d1dda34..1083e6fc 100644 --- a/libc/alg/qsort.c +++ b/libc/alg/qsort.c @@ -71,25 +71,25 @@ static void cycle(size_t width, unsigned char *ar[], size_t n) { forceinline void shl(unsigned p[2], size_t n) { assert(n > 0); - if (n >= 8 * sizeof(unsigned)) { - n -= 8 * sizeof(unsigned); + if (n >= CHAR_BIT * sizeof(unsigned)) { + n -= CHAR_BIT * sizeof(unsigned); p[1] = p[0]; p[0] = 0; } p[1] <<= n; - p[1] |= p[0] >> (sizeof(unsigned) * 8 - n); + p[1] |= p[0] >> (sizeof(unsigned) * CHAR_BIT - n); p[0] <<= n; } forceinline void shr(unsigned p[2], size_t n) { assert(n > 0); - if (n >= 8 * sizeof(unsigned)) { - n -= 8 * sizeof(unsigned); + if (n >= CHAR_BIT * sizeof(unsigned)) { + n -= CHAR_BIT * sizeof(unsigned); p[0] = p[1]; p[1] = 0; } p[0] >>= n; - p[0] |= p[1] << (sizeof(unsigned) * 8 - n); + p[0] |= p[1] << (sizeof(unsigned) * CHAR_BIT - n); p[1] >>= n; } diff --git a/libc/alg/replacestr16.c b/libc/alg/replacestr16.c index 7c5513bf..751b36f2 100644 --- a/libc/alg/replacestr16.c +++ b/libc/alg/replacestr16.c @@ -23,7 +23,9 @@ #include "libc/str/str.h" #include "libc/sysv/errfuns.h" +#undef strlen #undef replacestr #define replacestr replacestr16 #define char char16_t +#define strlen strlen16 #include "libc/alg/replacestr.c" diff --git a/libc/alg/tarjan.c b/libc/alg/tarjan.c index 2c9128db..bdd51e2d 100644 --- a/libc/alg/tarjan.c +++ b/libc/alg/tarjan.c @@ -19,8 +19,8 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/alg/alg.h" #include "libc/assert.h" -#include "libc/bits/safemacros.h" #include "libc/limits.h" +#include "libc/macros.h" #include "libc/mem/mem.h" /** @@ -33,79 +33,77 @@ * topological sorting as a byproduct.” ──D.E. Knuth */ -struct Vertex { - uint32_t Vi; - uint32_t Ei; - uint32_t index; - uint32_t lowlink; - bool onstack; - bool selfreferential; -}; - -struct TarjanStack { - size_t i; - size_t n; - uint32_t *p; -}; - struct Tarjan { - uint32_t Vn; - uint32_t En; - struct Vertex *V; - const uint32_t (*E)[2]; - uint32_t *R; - uint32_t *C; - uint32_t Ci; - uint32_t Ri; - uint32_t index; - struct TarjanStack S; + int Vn, En, Ci, Ri, *R, *C, index; + const int (*E)[2]; + struct Vertex { + int Vi; + int Ei; + int index; + int lowlink; + bool onstack; + bool selfreferential; + } * V; + struct TarjanStack { + int i; + int n; + int *p; + } S; }; -static uint32_t TarjanPush(struct TarjanStack *st, uint32_t Vi) { - if (st->i < st->n || grow(&st->p, &st->n, sizeof(uint32_t), 0)) { - return (st->p[st->i++] = Vi); - } else { - return -1; +static bool TarjanPush(struct Tarjan *t, int v) { + int *q; + assert(t->S.i >= 0); + assert(t->S.n >= 0); + assert(0 <= v && v < t->Vn); + if (t->S.i == t->S.n) { + if ((q = realloc(t->S.p, (t->S.n + (t->S.n >> 1) + 8) * sizeof(*t->S.p)))) { + t->S.p = q; + } else { + return false; + } } + t->S.p[t->S.i++] = v; + return true; } -static uint32_t TarjanPop(struct TarjanStack *st) { - assert(st->i != 0); - return st->p[--st->i]; +static int TarjanPop(struct Tarjan *t) { + assert(t->S.i > 0); + return t->S.p[--t->S.i]; } -static int TarjanConnect(struct Tarjan **tj, uint32_t Vi) { - struct Vertex *v = &(*tj)->V[Vi]; - v->index = (*tj)->index; - v->lowlink = (*tj)->index; - v->onstack = true; - (*tj)->index++; - if (TarjanPush(&(*tj)->S, Vi) == -1) return -1; - uint32_t fs = (*tj)->V[Vi].Ei; +static bool TarjanConnect(struct Tarjan *t, int v) { + int fs, w, e; + assert(0 <= v && v < t->Vn); + t->V[v].index = t->index; + t->V[v].lowlink = t->index; + t->V[v].onstack = true; + t->index++; + if (!TarjanPush(t, v)) return false; + fs = t->V[v].Ei; if (fs != -1) { - for (uint32_t Ei = fs; Ei < (*tj)->En && Vi == (*tj)->E[Ei][0]; ++Ei) { - struct Vertex *w = &(*tj)->V[(*tj)->E[Ei][1]]; - if (!w->index) { - if (TarjanConnect(tj, w->Vi) == -1) return -1; - v->lowlink = min(v->lowlink, w->lowlink); - } else if (w->onstack) { - v->lowlink = min(v->lowlink, w->index); + for (e = fs; e < t->En && v == t->E[e][0]; ++e) { + w = t->E[e][1]; + if (!t->V[w].index) { + if (!TarjanConnect(t, t->V[w].Vi)) return false; + t->V[v].lowlink = MIN(t->V[v].lowlink, t->V[w].lowlink); + } else if (t->V[w].onstack) { + t->V[v].lowlink = MIN(t->V[v].lowlink, t->V[w].index); } if (w == v) { - w->selfreferential = true; + t->V[w].selfreferential = true; } } } - if (v->lowlink == v->index) { - struct Vertex *w; + if (t->V[v].lowlink == t->V[v].index) { do { - w = &(*tj)->V[TarjanPop(&(*tj)->S)]; - w->onstack = false; - (*tj)->R[(*tj)->Ri++] = w->Vi; + w = TarjanPop(t); + t->V[w].onstack = false; + t->R[t->Ri++] = t->V[w].Vi; } while (w != v); - if ((*tj)->C) (*tj)->C[(*tj)->Ci++] = (*tj)->Ri; + if (t->C) t->C[t->Ci++] = t->Ri; } - return 0; + return true; } /** @@ -133,51 +131,53 @@ static int TarjanConnect(struct Tarjan **tj, uint32_t Vi) { * @error ENOMEM * @note Tarjan's Algorithm is O(|V|+|E|) */ -int tarjan(uint32_t vertex_count, const uint32_t (*edges)[2], - uint32_t edge_count, uint32_t out_sorted[], - uint32_t out_opt_components[], uint32_t *out_opt_componentcount) { - assert(edge_count <= INT_MAX); - assert(vertex_count <= INT_MAX); - for (unsigned i = 0; i < edge_count; ++i) { +int tarjan(int vertex_count, const int (*edges)[2], int edge_count, + int out_sorted[], int out_opt_components[], + int *out_opt_componentcount) { + int i, rc, v, e; + struct Tarjan *t; + assert(0 <= edge_count && edge_count <= INT_MAX); + assert(0 <= vertex_count && vertex_count <= INT_MAX); + for (i = 0; i < edge_count; ++i) { if (i) assert(edges[i - 1][0] <= edges[i][0]); assert(edges[i][0] < vertex_count); assert(edges[i][1] < vertex_count); } - int rc; - struct Tarjan *tj; - if ((tj = calloc(1, (sizeof(struct Tarjan) + + if (!(t = calloc(1, (sizeof(struct Tarjan) + sizeof(struct Vertex) * vertex_count)))) { - tj->V = (struct Vertex *)((char *)tj + sizeof(struct Tarjan)); - tj->Vn = vertex_count; - tj->E = edges; - tj->En = edge_count; - tj->R = out_sorted; - tj->C = out_opt_components; - tj->index = 1; - uint32_t Vi, Ei; - for (Vi = 0; Vi < tj->Vn; ++Vi) { - tj->V[Vi].Vi = Vi; - tj->V[Vi].Ei = -1u; - } - for (Ei = 0, Vi = -1u; Ei < tj->En; ++Ei) { - if (tj->E[Ei][0] == Vi) continue; - Vi = tj->E[Ei][0]; - tj->V[Vi].Ei = Ei; - } - rc = 0; - for (Vi = 0; Vi < tj->Vn; ++Vi) { - if (!tj->V[Vi].index) { - if ((rc = TarjanConnect(&tj, Vi)) == -1) { - break; - } + return -1; + } + t->V = (struct Vertex *)((char *)t + sizeof(struct Tarjan)); + t->Vn = vertex_count; + t->E = edges; + t->En = edge_count; + t->R = out_sorted; + t->C = out_opt_components; + t->index = 1; + for (v = 0; v < t->Vn; ++v) { + t->V[v].Vi = v; + t->V[v].Ei = -1; + } + for (e = 0, v = -1; e < t->En; ++e) { + if (t->E[e][0] == v) continue; + v = t->E[e][0]; + t->V[v].Ei = e; + } + rc = 0; + for (v = 0; v < t->Vn; ++v) { + if (!t->V[v].index) { + if (!TarjanConnect(t, v)) { + free(t->S.p); + free(t); + return -1; } } - free(tj->S.p); - assert(tj->Ri == vertex_count); - if (out_opt_components) *out_opt_componentcount = tj->Ci; - } else { - rc = -1; } - free(tj); + if (out_opt_components) { + *out_opt_componentcount = t->Ci; + } + assert(t->Ri == vertex_count); + free(t->S.p); + free(t); return rc; } diff --git a/libc/bits/bitreverse32.c b/libc/bits/bitreverse32.c index 05b1804d..9ccc86d1 100644 --- a/libc/bits/bitreverse32.c +++ b/libc/bits/bitreverse32.c @@ -18,6 +18,7 @@ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/bits/bits.h" +#include "libc/bits/bswap.h" uint32_t(bitreverse32)(uint32_t x) { x = bswap_32(x); diff --git a/libc/bits/bitreverse64.c b/libc/bits/bitreverse64.c index b3864703..8e9310eb 100644 --- a/libc/bits/bitreverse64.c +++ b/libc/bits/bitreverse64.c @@ -18,6 +18,7 @@ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/bits/bits.h" +#include "libc/bits/bswap.h" uint64_t bitreverse64(uint64_t x) { x = bswap_64(x); diff --git a/libc/bits/bits.h b/libc/bits/bits.h index 4fc78f3f..e26f53c8 100644 --- a/libc/bits/bits.h +++ b/libc/bits/bits.h @@ -13,9 +13,6 @@ extern const bool kTrue; extern const bool kFalse; extern const uint8_t kReverseBits[256]; -uint16_t bswap_16(uint16_t) pureconst; -uint32_t bswap_32(uint32_t) pureconst; -uint32_t bswap_64(uint32_t) pureconst; uint32_t gray(uint32_t) pureconst; uint32_t ungray(uint32_t) pureconst; unsigned bcdadd(unsigned, unsigned) pureconst; @@ -157,16 +154,6 @@ unsigned long hamming(unsigned long, unsigned long) pureconst; #define ABOVEFLAG_ASM(OP) OP "\n\tseta\t%b0" #endif -/** - * Reads scalar from memory, offset by segment. - * - * @return *(MEM) relative to segment - * @see arch_prctl() - * @see pushpop() - */ -#define fs(MEM) __peek("fs", MEM) -#define gs(MEM) __peek("gs", MEM) - /** * Reads scalar from memory w/ one operation. * @@ -374,115 +361,16 @@ unsigned long hamming(unsigned long, unsigned long) pureconst; #define invlpg(MEM) \ asm volatile("invlpg\t(%0)" : /* no outputs */ : "r"(MEM) : "memory") -/** - * Teleports code fragment inside _init(). - */ -#define INITIALIZER(PRI, NAME, CODE) \ - asm(".pushsection .init." #PRI "." #NAME ",\"ax\",@progbits\n\t" \ - "call\t" #NAME "\n\t" \ - ".popsection"); \ - textstartup optimizesize void NAME(char *rdi, const char *rsi) { \ - CODE; \ - asm volatile("" : /* no outputs */ : "D"(rdi), "S"(rsi)); \ - } - -#ifndef __STRICT_ANSI__ -#if __PIC__ + __code_model_medium__ + __code_model_large__ + 0 > 1 -#define __EZLEA(SYMBOL) "lea\t" SYMBOL "(%%rip),%" -#else -#define __EZLEA(SYMBOL) "mov\t$" SYMBOL ",%k" -#endif -#define weaken(symbol) ((const typeof(&(symbol)))weakaddr(#symbol)) -#define strongaddr(symbolstr) \ - ({ \ - intptr_t waddr; \ - asm(__EZLEA(symbolstr) "0" : "=r"(waddr)); \ - waddr; \ - }) -#define weakaddr(symbolstr) \ - ({ \ - intptr_t waddr; \ - asm(".weak\t" symbolstr "\n\t" __EZLEA(symbolstr) "0" : "=r"(waddr)); \ - waddr; \ - }) -#else -#define weaken(symbol) symbol -#define weakaddr(symbolstr) &(symbolstr) -#endif - -#define slowcall(fn, arg1, arg2, arg3, arg4, arg5, arg6) \ - ({ \ - void *ax; \ - asm volatile("push\t%7\n\t" \ - "push\t%6\n\t" \ - "push\t%5\n\t" \ - "push\t%4\n\t" \ - "push\t%3\n\t" \ - "push\t%2\n\t" \ - "push\t%1\n\t" \ - "call\tslowcall" \ - : "=a"(ax) \ - : "g"(fn), "g"(arg1), "g"(arg2), "g"(arg3), "g"(arg4), \ - "g"(arg5), "g"(arg6) \ - : "memory"); \ - ax; \ - }) - #define IsAddressCanonicalForm(P) \ ({ \ intptr_t p2 = (intptr_t)(P); \ (0xffff800000000000l <= p2 && p2 <= 0x00007fffffffffffl); \ }) -/*───────────────────────────────────────────────────────────────────────────│─╗ -│ cosmopolitan § bits » optimizations ─╬─│┼ -╚────────────────────────────────────────────────────────────────────────────│*/ -#if defined(__GNUC__) && !defined(__STRICT_ANSI__) - -#define bswap_16(U16) \ - (isconstant(U16) ? ((((U16)&0xff00) >> 010) | (((U16)&0x00ff) << 010)) : ({ \ - uint16_t Swapped16, Werd16 = (U16); \ - asm("xchg\t%b0,%h0" : "=Q"(Swapped16) : "0"(Werd16)); \ - Swapped16; \ - })) - -#define bswap_32(U32) \ - (isconstant(U32) \ - ? ((((U32)&0xff000000) >> 030) | (((U32)&0x000000ff) << 030) | \ - (((U32)&0x00ff0000) >> 010) | (((U32)&0x0000ff00) << 010)) \ - : ({ \ - uint32_t Swapped32, Werd32 = (U32); \ - asm("bswap\t%0" : "=r"(Swapped32) : "0"(Werd32)); \ - Swapped32; \ - })) - -#define bswap_64(U64) \ - (isconstant(U64) ? ((((U64)&0xff00000000000000ul) >> 070) | \ - (((U64)&0x00000000000000fful) << 070) | \ - (((U64)&0x00ff000000000000ul) >> 050) | \ - (((U64)&0x000000000000ff00ul) << 050) | \ - (((U64)&0x0000ff0000000000ul) >> 030) | \ - (((U64)&0x0000000000ff0000ul) << 030) | \ - (((U64)&0x000000ff00000000ul) >> 010) | \ - (((U64)&0x00000000ff000000ul) << 010)) \ - : ({ \ - uint64_t Swapped64, Werd64 = (U64); \ - asm("bswap\t%0" : "=r"(Swapped64) : "0"(Werd64)); \ - Swapped64; \ - })) - -#endif /* defined(__GNUC__) && !defined(__STRICT_ANSI__) */ /*───────────────────────────────────────────────────────────────────────────│─╗ │ cosmopolitan § bits » implementation details ─╬─│┼ ╚────────────────────────────────────────────────────────────────────────────│*/ -#define __peek(SEGMENT, ADDRESS) \ - ({ \ - typeof(*(ADDRESS)) Pk; \ - asm("mov\t%%" SEGMENT ":%1,%0" : "=r"(Pk) : "m"(*(ADDRESS))); \ - Pk; \ - }) - #define __ArithmeticOp1(OP, MEM) \ ({ \ asm(OP "%z0\t%0" : "+m"(*(MEM)) : /* no inputs */ : "cc"); \ diff --git a/libc/bits/bswap.h b/libc/bits/bswap.h new file mode 100644 index 00000000..6bde2bc9 --- /dev/null +++ b/libc/bits/bswap.h @@ -0,0 +1,48 @@ +#ifndef COSMOPOLITAN_LIBC_BITS_BSWAP_H_ +#define COSMOPOLITAN_LIBC_BITS_BSWAP_H_ +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +uint16_t bswap_16(uint16_t) pureconst; +uint32_t bswap_32(uint32_t) pureconst; +uint32_t bswap_64(uint32_t) pureconst; + +#if defined(__GNUC__) && !defined(__STRICT_ANSI__) + +#define bswap_16(U16) \ + (isconstant(U16) ? ((((U16)&0xff00) >> 010) | (((U16)&0x00ff) << 010)) : ({ \ + uint16_t Swapped16, Werd16 = (U16); \ + asm("xchg\t%b0,%h0" : "=Q"(Swapped16) : "0"(Werd16)); \ + Swapped16; \ + })) + +#define bswap_32(U32) \ + (isconstant(U32) \ + ? ((((U32)&0xff000000) >> 030) | (((U32)&0x000000ff) << 030) | \ + (((U32)&0x00ff0000) >> 010) | (((U32)&0x0000ff00) << 010)) \ + : ({ \ + uint32_t Swapped32, Werd32 = (U32); \ + asm("bswap\t%0" : "=r"(Swapped32) : "0"(Werd32)); \ + Swapped32; \ + })) + +#define bswap_64(U64) \ + (isconstant(U64) ? ((((U64)&0xff00000000000000ul) >> 070) | \ + (((U64)&0x00000000000000fful) << 070) | \ + (((U64)&0x00ff000000000000ul) >> 050) | \ + (((U64)&0x000000000000ff00ul) << 050) | \ + (((U64)&0x0000ff0000000000ul) >> 030) | \ + (((U64)&0x0000000000ff0000ul) << 030) | \ + (((U64)&0x000000ff00000000ul) >> 010) | \ + (((U64)&0x00000000ff000000ul) << 010)) \ + : ({ \ + uint64_t Swapped64, Werd64 = (U64); \ + asm("bswap\t%0" : "=r"(Swapped64) : "0"(Werd64)); \ + Swapped64; \ + })) + +#endif /* defined(__GNUC__) && !defined(__STRICT_ANSI__) */ + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_BITS_BSWAP_H_ */ diff --git a/libc/bits/ezlea.h b/libc/bits/ezlea.h new file mode 100644 index 00000000..08507b47 --- /dev/null +++ b/libc/bits/ezlea.h @@ -0,0 +1,12 @@ +#ifndef COSMOPOLITAN_LIBC_BITS_EZLEA_H_ +#define COSMOPOLITAN_LIBC_BITS_EZLEA_H_ +#if !(__ASSEMBLER__ + __LINKER__ + 0) + +#if __pic__ + __pie__ + __code_model_medium__ + __code_model_large__ + 0 > 1 +#define ezlea(symbol) "lea\t" symbol "(%%rip),%" +#else +#define ezlea(symbol) "mov\t$" symbol ",%k" +#endif + +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_BITS_EZLEA_H_ */ diff --git a/libc/bits/initializer.h b/libc/bits/initializer.h new file mode 100644 index 00000000..901f7148 --- /dev/null +++ b/libc/bits/initializer.h @@ -0,0 +1,18 @@ +#ifndef COSMOPOLITAN_LIBC_BITS_INITIALIZER_H_ +#define COSMOPOLITAN_LIBC_BITS_INITIALIZER_H_ +#if !(__ASSEMBLER__ + __LINKER__ + 0) + +/** + * Teleports code fragment inside _init(). + */ +#define INITIALIZER(PRI, NAME, CODE) \ + asm(".pushsection .init." #PRI "." #NAME ",\"ax\",@progbits\n\t" \ + "call\t" #NAME "\n\t" \ + ".popsection"); \ + textstartup optimizesize void NAME(char *rdi, const char *rsi) { \ + CODE; \ + asm volatile("" : /* no outputs */ : "D"(rdi), "S"(rsi)); \ + } + +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_BITS_INITIALIZER_H_ */ diff --git a/libc/bits/popcnt.c b/libc/bits/popcnt.c index 532ada5c..3e4a8524 100644 --- a/libc/bits/popcnt.c +++ b/libc/bits/popcnt.c @@ -19,18 +19,13 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/bits/popcnt.h" -static uint32_t popcnt32(uint32_t x) { - x -= (x >> 1) & 0x55555555; - x = (x & 0x33333333) + ((x >> 2) & 0x33333333); - return (((x + (x >> 4)) & 0xF0F0F0F) * 0x1010101) >> 24; -} - -unsigned long(popcnt)(unsigned long x) { - unsigned long r; - r = 0; - while (x) { - r += popcnt32(x); - x >>= 32; - } - return r; +uint64_t(popcnt)(uint64_t x) { + uint32_t r; + x = x - ((x >> 1) & 0x5555555555555555); + x = ((x >> 2) & 0x3333333333333333) + (x & 0x3333333333333333); + x = (x + (x >> 4)) & 0x0f0f0f0f0f0f0f0f; + x = (x + (x >> 32)) & 0xffffffff; + x = x + (x >> 16); + x = (x + (x >> 8)) & 0x0000007f; + return x; } diff --git a/libc/bits/popcnt.h b/libc/bits/popcnt.h index 0af8af80..e3f95540 100644 --- a/libc/bits/popcnt.h +++ b/libc/bits/popcnt.h @@ -8,7 +8,7 @@ unsigned long popcnt(unsigned long) pureconst; #if defined(__GNUC__) && !defined(__STRICT_ANSI__) #define popcnt(X) \ - (isconstant(X) ? __builtin_popcount(X) : ({ \ + (isconstant(X) ? __builtin_popcountll(X) : ({ \ unsigned long Res, Pop = (X); \ if (X86_HAVE(POPCNT)) { \ asm("popcnt\t%1,%0" : "=r"(Res) : "r"(Pop) : "cc"); \ diff --git a/libc/bits/pushpop.h b/libc/bits/pushpop.h index caf26b4e..16656c7d 100644 --- a/libc/bits/pushpop.h +++ b/libc/bits/pushpop.h @@ -31,7 +31,7 @@ #endif #if !defined(__GNUC__) || defined(__STRICT_ANSI__) -#define pushmov(d, x) ((d) = (x)) +#define pushmov(d, x) (*(d) = (x)) #else #define pushmov(d, x) \ ({ \ diff --git a/libc/bits/safemacros.h b/libc/bits/safemacros.h index df8c5783..00e638b1 100644 --- a/libc/bits/safemacros.h +++ b/libc/bits/safemacros.h @@ -1,20 +1,21 @@ #ifndef COSMOPOLITAN_LIBC_BITS_SAFEMACROS_H_ #define COSMOPOLITAN_LIBC_BITS_SAFEMACROS_H_ +#ifndef __STRICT_ANSI__ #include "libc/limits.h" #include "libc/macros.h" #include "libc/runtime/runtime.h" #if !(__ASSEMBLER__ + __LINKER__ + 0) COSMOPOLITAN_C_START_ -long min(long x, long y); -long max(long x, long y); -long roundup(long w, long k); -long rounddown(long w, long k); -bool isempty(const char *s); -const char *nulltoempty(const char *s); -const char *emptytonull(const char *s); -const char *firstnonnull(const char *a, const char *b); -uint64_t(unsignedsubtract)(uint64_t x, uint64_t y) pureconst; +long min(long, long); +long max(long, long); +long roundup(long, long); +long rounddown(long, long); +bool isempty(const char *); +const char *nulltoempty(const char *); +const char *emptytonull(const char *); +const char *firstnonnull(const char *, const char *); +uint64_t(unsignedsubtract)(uint64_t, uint64_t) pureconst; #if !defined(__STRICT_ANSI__) && defined(__GNUC__) @@ -83,4 +84,5 @@ uint64_t(unsignedsubtract)(uint64_t x, uint64_t y) pureconst; COSMOPOLITAN_C_END_ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* !ANSI */ #endif /* COSMOPOLITAN_LIBC_BITS_SAFEMACROS_H_ */ diff --git a/libc/bits/segmentation.h b/libc/bits/segmentation.h new file mode 100644 index 00000000..ad36da68 --- /dev/null +++ b/libc/bits/segmentation.h @@ -0,0 +1,23 @@ +#ifndef COSMOPOLITAN_LIBC_BITS_SEGMENTATION_H_ +#define COSMOPOLITAN_LIBC_BITS_SEGMENTATION_H_ +#if !(__ASSEMBLER__ + __LINKER__ + 0) + +/** + * Reads scalar from memory, offset by segment. + * + * @return *(MEM) relative to segment + * @see arch_prctl() + * @see pushpop() + */ +#define fs(MEM) __peek("fs", MEM) +#define gs(MEM) __peek("gs", MEM) + +#define __peek(SEGMENT, ADDRESS) \ + ({ \ + typeof(*(ADDRESS)) Pk; \ + asm("mov\t%%" SEGMENT ":%1,%0" : "=r"(Pk) : "m"(*(ADDRESS))); \ + Pk; \ + }) + +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_BITS_SEGMENTATION_H_ */ diff --git a/libc/bits/weaken.h b/libc/bits/weaken.h new file mode 100644 index 00000000..c190d44f --- /dev/null +++ b/libc/bits/weaken.h @@ -0,0 +1,28 @@ +#ifndef COSMOPOLITAN_LIBC_BITS_WEAKEN_H_ +#define COSMOPOLITAN_LIBC_BITS_WEAKEN_H_ +#include "libc/bits/ezlea.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +#ifndef __STRICT_ANSI__ + +#define weaken(symbol) ((const typeof(&(symbol)))weakaddr(#symbol)) + +#define strongaddr(symbolstr) \ + ({ \ + intptr_t waddr; \ + asm(ezlea(symbolstr) "0" : "=r"(waddr)); \ + waddr; \ + }) + +#define weakaddr(symbolstr) \ + ({ \ + intptr_t waddr; \ + asm(".weak\t" symbolstr "\n\t" ezlea(symbolstr) "0" : "=r"(waddr)); \ + waddr; \ + }) + +#else +#define weaken(symbol) symbol +#define weakaddr(symbolstr) &(symbolstr) +#endif /* __STRICT_ANSI__ */ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_BITS_WEAKEN_H_ */ diff --git a/libc/calls/calls.h b/libc/calls/calls.h index 626f323d..638af166 100644 --- a/libc/calls/calls.h +++ b/libc/calls/calls.h @@ -81,20 +81,19 @@ bool isregularfile(const char *); bool32 isatty(int) nosideeffect; bool32 ischardev(int) nosideeffect; char *get_current_dir_name(void) nodiscard; -char *getcwd(char *, size_t) paramsnonnull(); +char *getcwd(char *, size_t); char *realpath(const char *, char *); char *replaceuser(const char *) nodiscard; char *slurp(const char *, size_t *) nodiscard; char *ttyname(int); const char *commandv(const char *); -int access(const char *, int) nothrow paramsnonnull(); +int access(const char *, int) nothrow; int arch_prctl(); -int chdir(const char *) paramsnonnull(); +int chdir(const char *); int chmod(const char *, uint32_t); -int chown(const char *, uint32_t, uint32_t) paramsnonnull(); +int chown(const char *, uint32_t, uint32_t); int close(int); int closedir(DIR *); -int copyfile(const char *, const char *, bool); int creat(const char *, uint32_t) nodiscard; int dirfd(DIR *); int dup(int) nodiscard; @@ -119,7 +118,7 @@ int fdatasync(int); int filecmp(const char *, const char *); int flock(int, int); int fork(void); -int fstat(int, struct stat *) paramsnonnull(); +int fstat(int, struct stat *); int fstatat(int, const char *, struct stat *, uint32_t); int fsync(int); int ftruncate(int, int64_t); @@ -131,7 +130,7 @@ int kill(int, int); int killpg(int, int); int link(const char *, const char *) nothrow; int linkat(int, const char *, int, const char *, uint32_t); -int lstat(const char *, struct stat *) paramsnonnull(); +int lstat(const char *, struct stat *); int madvise(void *, uint64_t, int); int mkdir(const char *, uint32_t); int mkdirat(int, const char *, uint32_t); @@ -141,7 +140,7 @@ int mknodat(int, const char *, int32_t, uint64_t); int mlock(const void *, size_t); int mlock2(const void *, size_t, int); int mlockall(int); -int mprotect(void *, uint64_t, int) paramsnonnull() privileged; +int mprotect(void *, uint64_t, int) privileged; int msync(void *, size_t, int); int munlock(const void *, size_t); int munlockall(void); @@ -150,11 +149,11 @@ int munmap_s(void *, uint64_t); int nice(int); int open(const char *, int, ...) nodiscard; int openanon(char *, unsigned) nodiscard; -int openat(); +int openat(int, const char *, int, ...); int pause(void); int personality(uint64_t); -int pipe(int[hasatleast 2]) paramsnonnull() nodiscard; -int pipe2(int[hasatleast 2], int) paramsnonnull() nodiscard; +int pipe(int[hasatleast 2]) nodiscard; +int pipe2(int[hasatleast 2], int) nodiscard; int posix_fadvise(int, uint64_t, uint64_t, int); int posix_fallocate(int, int64_t, int64_t); int posix_madvise(void *, uint64_t, int); @@ -184,7 +183,7 @@ int sigaction(int, const struct sigaction *, struct sigaction *); int sigignore(int); int sigprocmask(int, const struct sigset *, struct sigset *); int sigsuspend(const struct sigset *); -int stat(const char *, struct stat *) paramsnonnull(); +int stat(const char *, struct stat *); int symlink(const char *, const char *); int symlinkat(const char *, int, const char *); int sync_file_range(int, int64_t, int64_t, unsigned); @@ -230,7 +229,7 @@ uint32_t gettid(void) nosideeffect; uint32_t getuid(void) nosideeffect; uint32_t umask(int32_t); void *getprocaddressmodule(const char *, const char *); -void *mmap(void *, uint64_t, int32_t, int32_t, int32_t, int64_t) vallocesque; +void *mmap(void *, uint64_t, int32_t, int32_t, int32_t, int64_t); void *mremap(void *, uint64_t, uint64_t, int32_t, void *); #define getcwd(BUF, SIZE) \ diff --git a/libc/calls/chdir.c b/libc/calls/chdir.c index 859f71d1..741acd40 100644 --- a/libc/calls/chdir.c +++ b/libc/calls/chdir.c @@ -20,12 +20,14 @@ #include "libc/calls/calls.h" #include "libc/calls/internal.h" #include "libc/dce.h" +#include "libc/sysv/errfuns.h" /** * Sets current directory. * @asyncsignalsafe */ int chdir(const char *path) { + if (!path) return efault(); if (!IsWindows()) { return chdir$sysv(path); } else { diff --git a/libc/calls/chmod.c b/libc/calls/chmod.c index f8378b86..2b6a4a18 100644 --- a/libc/calls/chmod.c +++ b/libc/calls/chmod.c @@ -45,5 +45,6 @@ * @see fchmod() */ int chmod(const char *pathname, uint32_t mode) { + if (!pathname) return efault(); return fchmodat$sysv(AT_FDCWD, pathname, mode, 0); } diff --git a/libc/calls/chown.c b/libc/calls/chown.c index 023df63d..2f222106 100644 --- a/libc/calls/chown.c +++ b/libc/calls/chown.c @@ -20,6 +20,7 @@ #include "libc/calls/calls.h" #include "libc/calls/internal.h" #include "libc/sysv/consts/at.h" +#include "libc/sysv/errfuns.h" /** * Changes owner and/or group of pathname. @@ -34,5 +35,6 @@ * @asyncsignalsafe */ int chown(const char *pathname, uint32_t uid, uint32_t gid) { + if (!pathname) return efault(); return fchownat$sysv(AT_FDCWD, pathname, uid, gid, 0); } diff --git a/libc/calls/close.c b/libc/calls/close.c index e0d700ab..14bcd0d5 100644 --- a/libc/calls/close.c +++ b/libc/calls/close.c @@ -17,10 +17,9 @@ │ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/bits/bits.h" +#include "libc/bits/weaken.h" #include "libc/calls/calls.h" #include "libc/calls/internal.h" -#include "libc/dce.h" #include "libc/sock/internal.h" #include "libc/sysv/errfuns.h" #include "libc/zipos/zipos.h" @@ -33,7 +32,7 @@ */ int close(int fd) { int rc; - if (fd == -1) return 0; + if (fd == -1) return einval(); if (isfdkind(fd, kFdZip)) { rc = weaken(__zipos_close)( (struct ZiposHandle *)(intptr_t)g_fds.p[fd].handle); diff --git a/libc/calls/fstat-nt.c b/libc/calls/fstat-nt.c index be4a64d3..95d16dd0 100644 --- a/libc/calls/fstat-nt.c +++ b/libc/calls/fstat-nt.c @@ -49,9 +49,9 @@ textwindows int fstat$nt(int64_t handle, struct stat *st) { : (((filetype == kNtFileTypeDisk) ? S_IFBLK : 0) | ((filetype == kNtFileTypeChar) ? S_IFCHR : 0) | ((filetype == kNtFileTypePipe) ? S_IFIFO : 0)))); - filetimetotimespec(&st->st_atim, wst.ftLastAccessFileTime); - filetimetotimespec(&st->st_mtim, wst.ftLastWriteFileTime); - filetimetotimespec(&st->st_ctim, wst.ftCreationFileTime); + st->st_atim = filetimetotimespec(wst.ftLastAccessFileTime); + st->st_mtim = filetimetotimespec(wst.ftLastWriteFileTime); + st->st_ctim = filetimetotimespec(wst.ftCreationFileTime); st->st_size = (uint64_t)wst.nFileSizeHigh << 32 | wst.nFileSizeLow; st->st_blksize = PAGESIZE; st->st_dev = wst.dwVolumeSerialNumber; diff --git a/libc/calls/fstat-sysv.c b/libc/calls/fstat-sysv.c index 0931d4f4..de793d0b 100644 --- a/libc/calls/fstat-sysv.c +++ b/libc/calls/fstat-sysv.c @@ -23,7 +23,7 @@ * Supports fstat(), etc. implementations. * @asyncsignalsafe */ -int32_t fstat$sysv(int32_t fd, struct stat *st) { +textstartup int32_t fstat$sysv(int32_t fd, struct stat *st) { int res; if ((res = __fstat$sysv(fd, st)) != -1) { stat2linux(st); diff --git a/libc/calls/fstat.c b/libc/calls/fstat.c index aec7e456..df181231 100644 --- a/libc/calls/fstat.c +++ b/libc/calls/fstat.c @@ -17,7 +17,7 @@ │ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/bits/bits.h" +#include "libc/bits/weaken.h" #include "libc/calls/calls.h" #include "libc/calls/internal.h" #include "libc/dce.h" diff --git a/libc/calls/g_fds.c b/libc/calls/g_fds.c index 17e42925..896004fe 100644 --- a/libc/calls/g_fds.c +++ b/libc/calls/g_fds.c @@ -17,18 +17,19 @@ │ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/bits/bits.h" +#include "libc/bits/initializer.h" #include "libc/bits/pushpop.h" #include "libc/calls/internal.h" -#include "libc/macros.h" #include "libc/nt/runtime.h" #include "libc/sysv/consts/fileno.h" +STATIC_YOINK("_init_g_fds"); + struct Fds g_fds; -INITIALIZER(300, _init_g_fds, { +void InitializeFileDescriptors(void) { struct Fds *fds; - fds = VEIL("D", &g_fds); + fds = VEIL("r", &g_fds); pushmov(&fds->f, 3ul); pushmov(&fds->n, ARRAYLEN(fds->__init_p)); fds->p = fds->__init_p; @@ -40,4 +41,4 @@ INITIALIZER(300, _init_g_fds, { GetStdHandle(pushpop(kNtStdOutputHandle)); fds->__init_p[STDERR_FILENO].handle = GetStdHandle(pushpop(kNtStdErrorHandle)); -}) +} diff --git a/libc/runtime/mapanon-thunk.S b/libc/calls/g_fds_init.S similarity index 92% rename from libc/runtime/mapanon-thunk.S rename to libc/calls/g_fds_init.S index 88cc453c..69b9d40f 100644 --- a/libc/runtime/mapanon-thunk.S +++ b/libc/calls/g_fds_init.S @@ -18,16 +18,12 @@ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/macros.h" -.source __FILE__ -mapanon:push %rbp - mov %rsp,%rbp - push %rbx - push %rbx - ezlea _base,bx - call __mapanon - pop %rbx - pop %rbx - pop %rbp - ret - .endfn mapanon,globl + .init.start 300,_init_g_fds + push %rdi + push %rsi + call InitializeFileDescriptors + pop %rsi + pop %rdi + .init.end 300,_init_g_fds + .source __FILE__ diff --git a/libc/calls/getcwd.c b/libc/calls/getcwd.c index bf3c21c4..70c5225f 100644 --- a/libc/calls/getcwd.c +++ b/libc/calls/getcwd.c @@ -35,23 +35,28 @@ * @error ERANGE, EINVAL */ char *(getcwd)(char *buf, size_t size) { - buf[0] = '\0'; - if (!IsWindows()) { - int olderr = errno; - if (getcwd$sysv(buf, size) != NULL) { - return buf; - } else if (IsXnu() && errno == ENOSYS) { - if (size >= 2) { - buf[0] = '.'; /* XXX: could put forth more effort */ - buf[1] = '\0'; - errno = olderr; + if (buf) { + buf[0] = '\0'; + if (!IsWindows()) { + int olderr = errno; + if (getcwd$sysv(buf, size) != NULL) { return buf; - } else { - erange(); + } else if (IsXnu() && errno == ENOSYS) { + if (size >= 2) { + buf[0] = '.'; /* XXX: could put forth more effort */ + buf[1] = '\0'; + errno = olderr; + return buf; + } else { + erange(); + } } + return NULL; + } else { + return getcwd$nt(buf, size); } - return NULL; } else { - return getcwd$nt(buf, size); + efault(); + return NULL; } } diff --git a/libc/calls/getenv.c b/libc/calls/getenv.c index df77208b..19932159 100644 --- a/libc/calls/getenv.c +++ b/libc/calls/getenv.c @@ -26,10 +26,13 @@ * Returns value of environment variable, or NULL if not found. */ char *getenv(const char *name) { - char *empty[1] = {NULL}; - char **ep = firstnonnull(environ, empty); - unsigned namelen = strlen(name); - for (int i = 0; ep[i]; ++i) { + char **ep; + size_t i, namelen; + char *empty[1] = {0}; + ep = environ; + if (!ep) ep = empty; + namelen = strlen(name); + for (i = 0; ep[i]; ++i) { if (strncmp(ep[i], name, namelen) == 0 && ep[i][namelen] == '=') { return &ep[i][namelen + 1]; } diff --git a/test/libc/intrin/phaddw_test.c b/libc/calls/getrusage-nt.c similarity index 69% rename from test/libc/intrin/phaddw_test.c rename to libc/calls/getrusage-nt.c index 99c8319d..2df0f13f 100644 --- a/test/libc/intrin/phaddw_test.c +++ b/libc/calls/getrusage-nt.c @@ -17,32 +17,29 @@ │ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/intrin/phaddw.h" +#include "libc/calls/calls.h" +#include "libc/calls/internal.h" +#include "libc/calls/struct/rusage.h" +#include "libc/conv/conv.h" +#include "libc/nt/accounting.h" +#include "libc/nt/runtime.h" +#include "libc/nt/thread.h" #include "libc/str/str.h" -#include "libc/testlib/testlib.h" -#include "tool/viz/lib/formatstringtable-testlib.h" -/* clang-format off */ +#include "libc/sysv/consts/rusage.h" -FIXTURE(phaddw, disableHardwareExtensions) { - memset((/*unconst*/ void *)kCpuids, 0, sizeof(kCpuids)); -} - -TEST(phaddw, testOverflow_wrapsAround) { - short M[2][8] = { - {0x7fff, 0, 0x7fff, 1, 13004, -30425, 20777, -16389}, - {-28040, 13318, -1336, -24798, -13876, 3599, -7346, -23575}, - }; - phaddw(M[0], M[0], M[1]); - EXPECT_SHRTMATRIXEQ(2, 8, M, "\n\ - 32767 -32768 -17421 4388 -14722 -26134 -10277 -30921\n\ --28040 13318 -1336 -24798 -13876 3599 -7346 -23575"); -} - -TEST(phaddw, testAliasing_isOk) { - short M[1][8] = { - {0,1, 2,3, 4,5, 6,7}, - }; - phaddw(M[0],M[0],M[0]); - EXPECT_SHRTMATRIXEQ(1, 8, M, "\n\ - 1 5 9 13 1 5 9 13"); +textwindows int getrusage$nt(int who, struct rusage *usage) { + struct NtFileTime CreationFileTime; + struct NtFileTime ExitFileTime; + struct NtFileTime KernelFileTime; + struct NtFileTime UserFileTime; + memset(usage, 0, sizeof(*usage)); + if ((who == RUSAGE_SELF ? GetProcessTimes : GetThreadTimes)( + (who == RUSAGE_SELF ? GetCurrentProcess : GetCurrentThread)(), + &CreationFileTime, &ExitFileTime, &KernelFileTime, &UserFileTime)) { + filetimetotimeval(&usage->ru_utime, UserFileTime); + filetimetotimeval(&usage->ru_stime, KernelFileTime); + return 0; + } else { + return winerr(); + } } diff --git a/libc/calls/getrusage.c b/libc/calls/getrusage.c index db4332f6..c8979fa7 100644 --- a/libc/calls/getrusage.c +++ b/libc/calls/getrusage.c @@ -19,35 +19,8 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/calls.h" #include "libc/calls/internal.h" -#include "libc/calls/struct/rusage.h" -#include "libc/conv/conv.h" -#include "libc/dce.h" -#include "libc/nt/accounting.h" -#include "libc/nt/runtime.h" -#include "libc/nt/struct/filetime.h" -#include "libc/nt/thread.h" -#include "libc/runtime/runtime.h" -#include "libc/str/str.h" -#include "libc/sysv/consts/rusage.h" #include "libc/sysv/errfuns.h" -static textwindows noinline int getrusage$nt(int who, struct rusage *usage) { - struct NtFileTime CreationFileTime; - struct NtFileTime ExitFileTime; - struct NtFileTime KernelFileTime; - struct NtFileTime UserFileTime; - memset(usage, 0, sizeof(*usage)); - if ((who == RUSAGE_SELF ? GetProcessTimes : GetThreadTimes)( - (who == RUSAGE_SELF ? GetCurrentProcess : GetCurrentThread)(), - &CreationFileTime, &ExitFileTime, &KernelFileTime, &UserFileTime)) { - filetimetotimeval(&usage->ru_utime, UserFileTime); - filetimetotimeval(&usage->ru_stime, KernelFileTime); - return 0; - } else { - return winerr(); - } -} - /** * Returns resource usage statistics. * diff --git a/libc/calls/growfds.c b/libc/calls/growfds.c index 8759fd7f..5329907a 100644 --- a/libc/calls/growfds.c +++ b/libc/calls/growfds.c @@ -18,7 +18,7 @@ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/assert.h" -#include "libc/bits/bits.h" +#include "libc/bits/weaken.h" #include "libc/calls/internal.h" #include "libc/mem/mem.h" #include "libc/sysv/errfuns.h" diff --git a/libc/calls/hefty/access.c b/libc/calls/hefty/access.c index cfa2647e..94e3c43e 100644 --- a/libc/calls/hefty/access.c +++ b/libc/calls/hefty/access.c @@ -17,10 +17,11 @@ │ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/dce.h" -#include "libc/calls/internal.h" #include "libc/calls/calls.h" +#include "libc/calls/internal.h" +#include "libc/dce.h" #include "libc/sysv/consts/at.h" +#include "libc/sysv/errfuns.h" /** * Checks if effective user can access path in particular ways. @@ -31,6 +32,7 @@ * @asyncsignalsafe */ int access(const char *path, int mode) { + if (!path) return efault(); if (!IsWindows()) { return faccessat$sysv(AT_FDCWD, path, mode, 0); } else { diff --git a/libc/calls/hefty/copyfile.c b/libc/calls/hefty/copyfile.c new file mode 100644 index 00000000..061cf21c --- /dev/null +++ b/libc/calls/hefty/copyfile.c @@ -0,0 +1,108 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/hefty/copyfile.h" +#include "libc/calls/internal.h" +#include "libc/calls/struct/stat.h" +#include "libc/dce.h" +#include "libc/nt/createfile.h" +#include "libc/nt/enum/accessmask.h" +#include "libc/nt/enum/creationdisposition.h" +#include "libc/nt/files.h" +#include "libc/nt/runtime.h" +#include "libc/sysv/consts/at.h" +#include "libc/sysv/consts/madv.h" +#include "libc/sysv/consts/o.h" +#include "libc/time/time.h" + +static textwindows int copyfile$nt(const char *src, const char *dst, + int flags) { + int64_t fhsrc, fhdst; + struct NtFileTime accessed, modified; + char16_t src16[PATH_MAX], dst16[PATH_MAX]; + if (mkntpath(src, src16) == -1) return -1; + if (mkntpath(dst, dst16) == -1) return -1; + if (CopyFile(src16, dst16, !!(flags & COPYFILE_NOCLOBBER))) { + if (flags & COPYFILE_PRESERVE_TIMESTAMPS) { + fhsrc = CreateFile(src16, kNtFileReadAttributes, kNtFileShareRead, NULL, + kNtOpenExisting, kNtFileAttributeNormal, 0); + fhdst = CreateFile(dst16, kNtFileWriteAttributes, kNtFileShareRead, NULL, + kNtOpenExisting, kNtFileAttributeNormal, 0); + if (fhsrc != -1 && fhdst != -1) { + GetFileTime(fhsrc, NULL, &accessed, &modified); + SetFileTime(fhdst, NULL, &accessed, &modified); + } + CloseHandle(fhsrc); + CloseHandle(fhdst); + } + return 0; + } else { + return winerr(); + } +} + +static int copyfile$sysv(const char *src, const char *dst, int flags) { + struct stat st; + size_t remaining; + ssize_t transferred; + struct timespec amtime[2]; + int64_t inoffset, outoffset; + int rc, srcfd, dstfd, oflags, omode; + rc = -1; + if ((srcfd = openat$sysv(AT_FDCWD, src, O_RDONLY, 0)) != -1) { + if (fstat$sysv(srcfd, &st) != -1) { + omode = st.st_mode & 0777; + oflags = O_WRONLY | O_CREAT; + if (flags & COPYFILE_NOCLOBBER) oflags |= O_EXCL; + if ((dstfd = openat$sysv(AT_FDCWD, dst, oflags, omode)) != -1) { + remaining = st.st_size; + ftruncate(dstfd, remaining); + inoffset = 0; + outoffset = 0; + while (remaining && + (transferred = copy_file_range( + srcfd, &inoffset, dstfd, &outoffset, remaining, 0)) != -1) { + remaining -= transferred; + } + if (!remaining) { + rc = 0; + if (flags & COPYFILE_PRESERVE_TIMESTAMPS) { + amtime[0] = st.st_atim; + amtime[1] = st.st_mtim; + utimensat$sysv(dstfd, NULL, amtime, 0); + } + } + rc |= close$sysv(dstfd); + } + } + rc |= close$sysv(srcfd); + } + return rc; +} + +/** + * Copies file. + */ +int copyfile(const char *src, const char *dst, int flags) { + if (!IsWindows()) { + return copyfile$sysv(src, dst, flags); + } else { + return copyfile$nt(src, dst, flags); + } +} diff --git a/libc/calls/hefty/copyfile.h b/libc/calls/hefty/copyfile.h new file mode 100644 index 00000000..0f0c78ed --- /dev/null +++ b/libc/calls/hefty/copyfile.h @@ -0,0 +1,15 @@ +#ifndef COSMOPOLITAN_LIBC_CALLS_HEFTY_COPYFILE_H_ +#define COSMOPOLITAN_LIBC_CALLS_HEFTY_COPYFILE_H_ + +#define COPYFILE_NOCLOBBER 1 +#define COPYFILE_PRESERVE_OWNER 2 +#define COPYFILE_PRESERVE_TIMESTAMPS 4 + +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +int copyfile(const char *, const char *, int) paramsnonnull(); + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_CALLS_HEFTY_COPYFILE_H_ */ diff --git a/libc/calls/hefty/faccessat-nt.c b/libc/calls/hefty/faccessat-nt.c new file mode 100644 index 00000000..e5290a73 --- /dev/null +++ b/libc/calls/hefty/faccessat-nt.c @@ -0,0 +1,30 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/hefty/internal.h" +#include "libc/calls/internal.h" +#include "libc/sysv/consts/at.h" +#include "libc/sysv/errfuns.h" + +int faccessat$nt(int dirfd, const char *path, int mode, uint32_t flags) { + char16_t path16[PATH_MAX]; + if (dirfd != AT_FDCWD || flags) return einval(); + if (mkntpath(path, path16) == -1) return -1; + return ntaccesscheck(path16, mode); +} diff --git a/libc/calls/hefty/faccessat.c b/libc/calls/hefty/faccessat.c index bd8302ac..2a36c592 100644 --- a/libc/calls/hefty/faccessat.c +++ b/libc/calls/hefty/faccessat.c @@ -18,6 +18,7 @@ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/calls.h" +#include "libc/calls/hefty/internal.h" #include "libc/calls/internal.h" #include "libc/dce.h" #include "libc/sysv/consts/at.h" @@ -33,12 +34,10 @@ * @asyncsignalsafe */ int faccessat(int dirfd, const char *path, int mode, uint32_t flags) { + if (!path) return efault(); if (!IsWindows()) { return faccessat$sysv(dirfd, path, mode, flags); } else { - char16_t path16[PATH_MAX]; - if (dirfd != AT_FDCWD || flags) return einval(); - if (mkntpath(path, path16) == -1) return -1; - return ntaccesscheck(path16, mode); + return faccessat$nt(dirfd, path, mode, flags); } } diff --git a/libc/calls/hefty/hefty.mk b/libc/calls/hefty/hefty.mk index 5d9ef1b7..cd294d02 100644 --- a/libc/calls/hefty/hefty.mk +++ b/libc/calls/hefty/hefty.mk @@ -36,17 +36,18 @@ LIBC_CALLS_HEFTY_A_CHECKS = \ LIBC_CALLS_HEFTY_A_DIRECTDEPS = \ LIBC_ALG \ + LIBC_CALLS \ LIBC_CONV \ LIBC_FMT \ LIBC_MEM \ - LIBC_STR \ LIBC_NEXGEN32E \ - LIBC_RUNTIME \ - LIBC_CALLS \ - LIBC_STUBS \ LIBC_NT_KERNELBASE \ + LIBC_RUNTIME \ + LIBC_STR \ + LIBC_STUBS \ + LIBC_SYSV \ LIBC_SYSV_CALLS \ - LIBC_SYSV + LIBC_TIME LIBC_CALLS_HEFTY_A_DEPS := \ $(call uniq,$(foreach x,$(LIBC_CALLS_HEFTY_A_DIRECTDEPS),$($(x)))) diff --git a/libc/calls/hefty/internal.h b/libc/calls/hefty/internal.h index b56dd330..e3e86cd0 100644 --- a/libc/calls/hefty/internal.h +++ b/libc/calls/hefty/internal.h @@ -3,6 +3,7 @@ #if !(__ASSEMBLER__ + __LINKER__ + 0) COSMOPOLITAN_C_START_ +int faccessat$nt(int, const char *, int, uint32_t) hidden; int execve$nt(const char *, char *const[], char *const[]) hidden; int spawnve$nt(unsigned, int[3], const char *, char *const[], char *const[]) hidden; diff --git a/libc/calls/hefty/mkntenvblock.c b/libc/calls/hefty/mkntenvblock.c index 8777c25d..493f42c8 100644 --- a/libc/calls/hefty/mkntenvblock.c +++ b/libc/calls/hefty/mkntenvblock.c @@ -35,36 +35,32 @@ * @return freshly allocated lpEnvironment or NULL w/ errno */ textwindows char16_t *mkntenvblock(char *const envp[]) { - size_t block_i = 0; - size_t block_n = 0; - char16_t *block_p = NULL; - size_t i, j; - if (!(envp = sortenvp(envp))) goto error; - const char16_t kNul = u'\0'; - for (i = 0; envp[i]; ++i) { - unsigned consumed; - for (j = 0;; j += consumed) { - wint_t wc; - char16_t cbuf[2]; - consumed = abs(tpdecode(&envp[i][j], &wc)); - if (CONCAT(&block_p, &block_i, &block_n, cbuf, - abs(pututf16(cbuf, ARRAYLEN(cbuf), wc, false))) == -1) { - goto error; + wint_t wc; + size_t i, j, bi, bn; + char16_t *bp, cbuf[2]; + unsigned consumed, produced; + bi = 0; + bn = 8; + bp = NULL; + if ((envp = sortenvp(envp)) && (bp = calloc(bn, sizeof(char16_t)))) { + for (i = 0; envp[i]; ++i) { + for (j = 0;; j += consumed) { + consumed = abs(tpdecode(&envp[i][j], &wc)); + produced = abs(pututf16(cbuf, ARRAYLEN(cbuf), wc, false)); + if (CONCAT(&bp, &bi, &bn, cbuf, produced) == -1) goto error; + if (!wc) break; } - if (!wc) break; } + ++bi; + if (bi > ENV_MAX) { + e2big(); + goto error; + } + free(envp); + return bp; } - if (APPEND(&block_p, &block_i, &block_n, &kNul) == -1) { - goto error; - } - if (block_i > ENV_MAX) { - e2big(); - goto error; - } - free(envp); - return block_p; error: free(envp); - free(block_p); + free(bp); return NULL; } diff --git a/libc/calls/internal.h b/libc/calls/internal.h index 3e7ed2bb..665c2a1a 100644 --- a/libc/calls/internal.h +++ b/libc/calls/internal.h @@ -19,12 +19,14 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #ifndef COSMOPOLITAN_LIBC_CALLS_INTERNAL_H_ #define COSMOPOLITAN_LIBC_CALLS_INTERNAL_H_ +#ifndef __STRICT_ANSI__ #include "libc/calls/calls.h" #include "libc/dce.h" #include "libc/limits.h" #include "libc/macros.h" #include "libc/nt/struct/securityattributes.h" #include "libc/nt/struct/startupinfo.h" +#include "libc/nt/struct/systeminfo.h" #include "libc/runtime/runtime.h" #define kSigactionMinRva 8 /* >SIG_{ERR,DFL,IGN,...} */ @@ -36,6 +38,7 @@ COSMOPOLITAN_C_START_ struct NtContext; struct NtWin32FileAttributeData; +struct ZiposHandle; struct __darwin_siginfo; struct __darwin_ucontext; struct itimerval; @@ -45,7 +48,7 @@ struct sigset; struct sysinfo; struct timeval; struct timezone; -struct ZiposHandle; +struct utimbuf; struct IoctlPtmGet { int theduxfd; @@ -62,7 +65,8 @@ struct IoctlPtmGet { * and helps us abstract peculiarities like close() vs. closesocket(). */ struct Fds { - size_t f, n; + size_t f; // length + size_t n; // capacity struct Fd { int64_t handle; int64_t extra; @@ -132,6 +136,7 @@ i32 __dup3$sysv(i32, i32, i32) hidden; i32 __fstat$sysv(i32, struct stat *) hidden; i32 __fstatat$sysv(i32, const char *, struct stat *, i32) hidden; i32 __pipe2$sysv(i32[hasatleast 2], u32) hidden; +i32 __utimensat$sysv(i32, const char *, const struct timespec *, i32) hidden; i32 chdir$sysv(const char *) hidden; i32 clock_gettime$sysv(i32, struct timespec *) hidden; i32 close$sysv(i32) hidden; @@ -154,6 +159,8 @@ i32 fstat$sysv(i32, struct stat *) hidden; i32 fstatat$sysv(i32, const char *, struct stat *, i32) hidden; i32 fsync$sysv(i32) hidden; i32 ftruncate$sysv(i32, i64) hidden; +i32 futimes$sysv(i32, const struct timeval *) hidden; +i32 futimesat$sysv(i32, const char *, const struct timeval *) hidden; i32 getdents(i32, char *, u32) hidden; i32 getppid$sysv(void) hidden; i32 getpriority$sysv(i32, u32) hidden; @@ -164,6 +171,7 @@ i32 ioctl$sysv(i32, u64, void *) hidden; i32 kill$sysv(i32, i32, i32) hidden; i32 linkat$sysv(i32, const char *, i32, const char *, i32) hidden; i32 lseek$sysv(i32, i64, i32) hidden; +i32 lutimes$sysv(const char *, const struct timeval *) hidden; i32 madvise$sysv(void *, size_t, i32) hidden; i32 memfd_create$sysv(const char *, u32) hidden; i32 mkdirat$sysv(i32, const char *, u32) hidden; @@ -194,6 +202,8 @@ i32 sysinfo$sysv(struct sysinfo *) hidden; i32 truncate$sysv(const char *, u64) hidden; i32 uname$sysv(char *) hidden; i32 unlinkat$sysv(i32, const char *, i32) hidden; +i32 utime$sysv(const char *, const struct utimbuf *) hidden; +i32 utimensat$sysv(i32, const char *, const struct timespec *, i32) hidden; i32 utimes$sysv(const char *, const struct timeval *) hidden; i32 wait4$sysv(i32, i32 *, i32, struct rusage *) hidden; i64 copy_file_range$sysv(i32, long *, i32, long *, u64, u32) hidden; @@ -207,12 +217,12 @@ i64 sendfile$sysv(i32, i32, i64 *, u64) hidden; i64 splice$sysv(i32, i64 *, i32, i64 *, u64, u32) hidden; i64 vmsplice$sysv(i32, const struct iovec *, i64, u32) hidden; i64 write$sysv(i32, const void *, u64) hidden; +int setresgid$sysv(uint32_t, uint32_t, uint32_t) hidden; +int setresuid$sysv(uint32_t, uint32_t, uint32_t) hidden; u32 getgid$sysv(void) hidden; u32 getpid$sysv(void) hidden; u32 gettid$sysv(void) hidden; u32 getuid$sysv(void) hidden; -int setresuid$sysv(uint32_t, uint32_t, uint32_t) hidden; -int setresgid$sysv(uint32_t, uint32_t, uint32_t) hidden; void *mmap$sysv(void *, u64, u32, u32, i64, i64) hidden; void *mremap$sysv(void *, u64, u64, i32, void *) hidden; @@ -223,13 +233,13 @@ void *mremap$sysv(void *, u64, u64, i32, void *) hidden; int __getpid(void) hidden; void __onfork(void) hidden; bool32 __sigenter(i32, struct siginfo *, struct ucontext *) hidden; -i32 __mprotect(void *, u64, i32) privileged; i32 fixupnewfd$sysv(i32, i32) hidden; i32 tunefd$sysv(i32, i32, i32, i32) hidden; u32 fprot2nt(i32, i32) hidden; u32 prot2nt(i32, i32) privileged; void __restore_rt() hidden; void __sigenter$xnu(void *, i32, i32, void *, void *) hidden noreturn; +int utimensat$xnu(int, const char *, const struct timespec *, int) hidden; void stat2linux(void *) hidden; void xnutrampoline(void *, i32, i32, const struct __darwin_siginfo *, const struct __darwin_ucontext *) hidden noreturn; @@ -271,6 +281,8 @@ int wait4$nt(int, int *, int, struct rusage *) hidden; i64 lseek$nt(int, i64, int) hidden; ssize_t read$nt(struct Fd *, const struct iovec *, size_t, ssize_t) hidden; ssize_t write$nt(struct Fd *, const struct iovec *, size_t, ssize_t) hidden; +int utimensat$nt(int, const char *, const struct timespec *, int) hidden; +int getrusage$nt(int, struct rusage *) hidden; /*───────────────────────────────────────────────────────────────────────────│─╗ │ cosmopolitan § syscalls » windows nt » support ─╬─│┼ @@ -317,4 +329,5 @@ int __mkntpath(const char *, unsigned, char16_t[hasatleast PATH_MAX - 16]) #undef u64 COSMOPOLITAN_C_END_ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* !ANSI */ #endif /* COSMOPOLITAN_LIBC_CALLS_INTERNAL_H_ */ diff --git a/libc/calls/ioctl-default.c b/libc/calls/ioctl-default.c index 2ec69662..ecccd46c 100644 --- a/libc/calls/ioctl-default.c +++ b/libc/calls/ioctl-default.c @@ -17,7 +17,7 @@ │ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/bits/bits.h" +#include "libc/bits/weaken.h" #include "libc/calls/internal.h" #include "libc/calls/ioctl.h" #include "libc/nt/winsock.h" diff --git a/libc/calls/ioctl-tiocgwinsz-nt.c b/libc/calls/ioctl-tiocgwinsz-nt.c index a787a6a1..5aac3c3f 100644 --- a/libc/calls/ioctl-tiocgwinsz-nt.c +++ b/libc/calls/ioctl-tiocgwinsz-nt.c @@ -32,18 +32,17 @@ textwindows int ioctl$tiocgwinsz$nt(int fd, struct winsize *ws) { struct NtConsoleScreenBufferInfoEx sbinfo; if (!isfdkind(fd, kFdFile)) return ebadf(); if (!GetConsoleMode(g_fds.p[fd].handle, &mode)) return enotty(); - if (g_ntstartupinfo.dwFlags & kNtStartfUsecountchars) { - ws->ws_col = g_ntstartupinfo.dwXCountChars; - ws->ws_row = g_ntstartupinfo.dwYCountChars; - ws->ws_xpixel = 0; - ws->ws_ypixel = 0; - return 0; - } memset(&sbinfo, 0, sizeof(sbinfo)); sbinfo.cbSize = sizeof(sbinfo); if (GetConsoleScreenBufferInfoEx(g_fds.p[fd].handle, &sbinfo)) { - ws->ws_col = sbinfo.srWindow.Right; - ws->ws_row = sbinfo.srWindow.Bottom; + ws->ws_col = sbinfo.srWindow.Right - sbinfo.srWindow.Left; + ws->ws_row = sbinfo.srWindow.Bottom - sbinfo.srWindow.Top; + ws->ws_xpixel = 0; + ws->ws_ypixel = 0; + return 0; + } else if (g_ntstartupinfo.dwFlags & kNtStartfUsecountchars) { + ws->ws_col = g_ntstartupinfo.dwXCountChars; + ws->ws_row = g_ntstartupinfo.dwYCountChars; ws->ws_xpixel = 0; ws->ws_ypixel = 0; return 0; diff --git a/libc/calls/ioctl.c b/libc/calls/ioctl.c index 759117b3..90e4bdb4 100644 --- a/libc/calls/ioctl.c +++ b/libc/calls/ioctl.c @@ -17,6 +17,9 @@ │ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#ifdef __STRICT_ANSI__ +#undef __STRICT_ANSI__ +#endif #include "libc/calls/ioctl.h" #define EQUAL(X, Y) ((X) == (Y)) diff --git a/libc/calls/ischardev.c b/libc/calls/ischardev.c index 54585a62..a65b8d52 100644 --- a/libc/calls/ischardev.c +++ b/libc/calls/ischardev.c @@ -28,7 +28,7 @@ /** * Returns true if file descriptor is backed by character i/o. */ -bool32 ischardev(int fd) { +textstartup bool32 ischardev(int fd) { int olderr; struct stat st; if (!IsWindows()) { diff --git a/libc/calls/isdebuggerpresent.c b/libc/calls/isdebuggerpresent.c index 802e6a6d..4522428f 100644 --- a/libc/calls/isdebuggerpresent.c +++ b/libc/calls/isdebuggerpresent.c @@ -24,13 +24,14 @@ #include "libc/conv/conv.h" #include "libc/dce.h" #include "libc/log/log.h" +#include "libc/nexgen32e/vendor.h" #include "libc/nt/struct/teb.h" #include "libc/runtime/runtime.h" #include "libc/str/str.h" #include "libc/sysv/consts/at.h" #include "libc/sysv/consts/o.h" -#define kBufSize 1024 +#define kBufSize 1024 #define kProcStatus "/proc/self/status" alignas(16) static const char kGdbPid[] = "TracerPid:\t"; @@ -43,18 +44,20 @@ int IsDebuggerPresent(bool force) { ssize_t got; char buf[1024]; res = 0; - if (force || isempty(getenv("HEISENDEBUG"))) { - if (IsWindows()) { - res = NtGetPeb()->BeingDebugged; - } else if (IsLinux()) { - if ((fd = openat$sysv(AT_FDCWD, kProcStatus, O_RDONLY, 0)) != -1) { - if ((got = read$sysv(fd, buf, sizeof(buf) - sizeof(kGdbPid))) != -1) { - buf[got] = '\0'; - res = atoi(firstnonnull(strstr(buf, kGdbPid), kGdbPid) + - strlen(kGdbPid)); - } - close$sysv(fd); + if (!force) { + if (getenv("HEISENDEBUG")) return false; + if (IsGenuineCosmo()) return false; + } + if (IsWindows()) { + res = NtGetPeb()->BeingDebugged; + } else if (IsLinux()) { + if ((fd = openat$sysv(AT_FDCWD, kProcStatus, O_RDONLY, 0)) != -1) { + if ((got = read$sysv(fd, buf, sizeof(buf) - sizeof(kGdbPid))) != -1) { + buf[got] = '\0'; + res = + atoi(firstnonnull(strstr(buf, kGdbPid), kGdbPid) + strlen(kGdbPid)); } + close$sysv(fd); } } return res; diff --git a/libc/calls/isdirectory.c b/libc/calls/isdirectory.c index 05b3ad7e..6e79fa38 100644 --- a/libc/calls/isdirectory.c +++ b/libc/calls/isdirectory.c @@ -17,12 +17,12 @@ │ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/errno.h" -#include "libc/calls/struct/stat.h" #include "libc/calls/calls.h" +#include "libc/calls/struct/stat.h" +#include "libc/errno.h" /** - * Returns true if file exists and is a directory + * Returns true if file exists and is a directory. */ bool isdirectory(const char *path) { struct stat st; diff --git a/libc/calls/kntprioritycombos.c b/libc/calls/kntprioritycombos.c index 00d28d64..91cf4767 100644 --- a/libc/calls/kntprioritycombos.c +++ b/libc/calls/kntprioritycombos.c @@ -24,33 +24,35 @@ #include "libc/nt/enum/processcreationflags.h" #include "libc/nt/enum/threadpriority.h" +#define FFS(x) __builtin_ffs(x) + const struct NtPriorityCombo kNtPriorityCombos[] = { - {-20, ffs(kNtHighPriorityClass), kNtThreadPriorityHighest, 15}, - {-18, ffs(kNtHighPriorityClass), kNtThreadPriorityTimeCritical, 15}, - {-17, ffs(kNtNormalPriorityClass), kNtThreadPriorityTimeCritical, 15}, - {-15, ffs(kNtIdlePriorityClass), kNtThreadPriorityTimeCritical, 15}, - {-13, ffs(kNtHighPriorityClass), kNtThreadPriorityAboveNormal, 14}, - {-11, ffs(kNtHighPriorityClass), kNtThreadPriorityNormal, 13}, - {-9, ffs(kNtHighPriorityClass), kNtThreadPriorityBelowNormal, 12}, - {-7, ffs(kNtNormalPriorityClass), kNtThreadPriorityHighest, 11}, - {-5, ffs(kNtHighPriorityClass), kNtThreadPriorityLowest, 11}, - {-3, ffs(kNtNormalPriorityClass), kNtThreadPriorityAboveNormal, 10}, - {-1, ffs(kNtNormalPriorityClass), kNtThreadPriorityHighest, 9}, - {0, ffs(kNtNormalPriorityClass), kNtThreadPriorityNormal, 9}, - {1, ffs(kNtNormalPriorityClass), kNtThreadPriorityAboveNormal, 8}, - {2, ffs(kNtNormalPriorityClass), kNtThreadPriorityBelowNormal, 8}, - {3, ffs(kNtNormalPriorityClass), kNtThreadPriorityNormal, 7}, - {4, ffs(kNtNormalPriorityClass), kNtThreadPriorityLowest, 7}, - {5, ffs(kNtIdlePriorityClass), kNtThreadPriorityHighest, 6}, - {6, ffs(kNtNormalPriorityClass), kNtThreadPriorityBelowNormal, 6}, - {7, ffs(kNtIdlePriorityClass), kNtThreadPriorityAboveNormal, 5}, - {9, ffs(kNtNormalPriorityClass), kNtThreadPriorityLowest, 5}, - {11, ffs(kNtIdlePriorityClass), kNtThreadPriorityNormal, 4}, - {13, ffs(kNtIdlePriorityClass), kNtThreadPriorityBelowNormal, 3}, - {15, ffs(kNtIdlePriorityClass), kNtThreadPriorityLowest, 2}, - {17, ffs(kNtHighPriorityClass), kNtThreadPriorityIdle, 1}, - {18, ffs(kNtNormalPriorityClass), kNtThreadPriorityIdle, 1}, - {19, ffs(kNtIdlePriorityClass), kNtThreadPriorityIdle, 1}, + {-20, FFS(kNtHighPriorityClass), kNtThreadPriorityHighest, 15}, + {-18, FFS(kNtHighPriorityClass), kNtThreadPriorityTimeCritical, 15}, + {-17, FFS(kNtNormalPriorityClass), kNtThreadPriorityTimeCritical, 15}, + {-15, FFS(kNtIdlePriorityClass), kNtThreadPriorityTimeCritical, 15}, + {-13, FFS(kNtHighPriorityClass), kNtThreadPriorityAboveNormal, 14}, + {-11, FFS(kNtHighPriorityClass), kNtThreadPriorityNormal, 13}, + {-9, FFS(kNtHighPriorityClass), kNtThreadPriorityBelowNormal, 12}, + {-7, FFS(kNtNormalPriorityClass), kNtThreadPriorityHighest, 11}, + {-5, FFS(kNtHighPriorityClass), kNtThreadPriorityLowest, 11}, + {-3, FFS(kNtNormalPriorityClass), kNtThreadPriorityAboveNormal, 10}, + {-1, FFS(kNtNormalPriorityClass), kNtThreadPriorityHighest, 9}, + {0, FFS(kNtNormalPriorityClass), kNtThreadPriorityNormal, 9}, + {1, FFS(kNtNormalPriorityClass), kNtThreadPriorityAboveNormal, 8}, + {2, FFS(kNtNormalPriorityClass), kNtThreadPriorityBelowNormal, 8}, + {3, FFS(kNtNormalPriorityClass), kNtThreadPriorityNormal, 7}, + {4, FFS(kNtNormalPriorityClass), kNtThreadPriorityLowest, 7}, + {5, FFS(kNtIdlePriorityClass), kNtThreadPriorityHighest, 6}, + {6, FFS(kNtNormalPriorityClass), kNtThreadPriorityBelowNormal, 6}, + {7, FFS(kNtIdlePriorityClass), kNtThreadPriorityAboveNormal, 5}, + {9, FFS(kNtNormalPriorityClass), kNtThreadPriorityLowest, 5}, + {11, FFS(kNtIdlePriorityClass), kNtThreadPriorityNormal, 4}, + {13, FFS(kNtIdlePriorityClass), kNtThreadPriorityBelowNormal, 3}, + {15, FFS(kNtIdlePriorityClass), kNtThreadPriorityLowest, 2}, + {17, FFS(kNtHighPriorityClass), kNtThreadPriorityIdle, 1}, + {18, FFS(kNtNormalPriorityClass), kNtThreadPriorityIdle, 1}, + {19, FFS(kNtIdlePriorityClass), kNtThreadPriorityIdle, 1}, }; const unsigned kNtPriorityCombosLen = ARRAYLEN(kNtPriorityCombos); diff --git a/libc/calls/mprotect.c b/libc/calls/mkdirat.c similarity index 87% rename from libc/calls/mprotect.c rename to libc/calls/mkdirat.c index 2e23d020..b1d5b90c 100644 --- a/libc/calls/mprotect.c +++ b/libc/calls/mkdirat.c @@ -17,16 +17,14 @@ │ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/calls/internal.h" #include "libc/calls/calls.h" +#include "libc/calls/internal.h" +#include "libc/sysv/consts/at.h" -/** - * Modifies restrictions on virtual memory address range. - * - * @param prot can have PROT_{NONE,READ,WRITE,EXEC,GROWSDOWN} - * @return 0 on success, or -1 w/ errno - * @see mmap() - */ -int mprotect(void *addr, uint64_t len, int prot) { - return __mprotect(addr, len, prot); +int mkdirat(int dirfd, const char *pathname, unsigned mode) { + if (dirfd == AT_FDCWD) { + return mkdir(pathname, mode); + } else { + return mkdirat$sysv(dirfd, pathname, mode); + } } diff --git a/libc/calls/mkntpath.ncabi.c b/libc/calls/mkntpath.ncabi.c index 5958354f..9ea70ef9 100644 --- a/libc/calls/mkntpath.ncabi.c +++ b/libc/calls/mkntpath.ncabi.c @@ -39,8 +39,9 @@ * @return short count excluding NUL on success, or -1 w/ errno * @error ENAMETOOLONG */ -textwindows int(mkntpath)(const char *path, unsigned flags, - char16_t path16[hasatleast PATH_MAX - 16]) { +forcealignargpointer textwindows int(mkntpath)( + const char *path, unsigned flags, + char16_t path16[hasatleast PATH_MAX - 16]) { /* * 1. Reserve +1 for NUL-terminator * 2. Reserve +1 for UTF-16 overflow diff --git a/libc/calls/mprotect.greg.c b/libc/calls/mprotect.greg.c index 3f516435..d28b3e43 100644 --- a/libc/calls/mprotect.greg.c +++ b/libc/calls/mprotect.greg.c @@ -26,7 +26,14 @@ #include "libc/nt/thunk/msabi.h" #include "libc/sysv/consts/nr.h" -privileged int __mprotect(void *addr, uint64_t len, int prot) { +/** + * Modifies restrictions on virtual memory address range. + * + * @param prot can have PROT_{NONE,READ,WRITE,EXEC,GROWSDOWN} + * @return 0 on success, or -1 w/ errno + * @see mmap() + */ +int mprotect(void *addr, uint64_t len, int prot) { extern __msabi typeof(VirtualProtect) *const __imp_VirtualProtect; bool cf; int64_t rc; diff --git a/libc/calls/open.c b/libc/calls/open.c index 040c154a..d707ac83 100644 --- a/libc/calls/open.c +++ b/libc/calls/open.c @@ -17,7 +17,7 @@ │ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/bits/bits.h" +#include "libc/bits/weaken.h" #include "libc/calls/calls.h" #include "libc/calls/internal.h" #include "libc/dce.h" diff --git a/libc/calls/openat.c b/libc/calls/openat.c new file mode 100644 index 00000000..e25f21f1 --- /dev/null +++ b/libc/calls/openat.c @@ -0,0 +1,49 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/calls.h" +#include "libc/calls/internal.h" +#include "libc/sysv/consts/at.h" +#include "libc/sysv/errfuns.h" + +/** + * Opens file, the modern way. + * + * @param dirfd is normally AT_FDCWD or an open relative directory thing + * @param file is a UTF-8 string, preferably relative w/ forward slashes + * @param flags should be O_RDONLY, O_WRONLY, or O_RDWR, and can be or'd + * with O_CREAT, O_TRUNC, O_APPEND, O_EXCL, O_CLOEXEC, O_TMPFILE + * @param mode is an octal user/group/other permission signifier, that's + * ignored if O_CREAT or O_TMPFILE weren't passed + * @return number needing close(), or -1 w/ errno + * @asyncsignalsafe + */ +int openat(int dirfd, const char *pathname, int flags, ...) { + va_list va; + unsigned mode; + va_start(va, flags); + mode = va_arg(va, unsigned); + va_end(va); + if (!pathname) return efault(); + if (dirfd == AT_FDCWD) { + return open(pathname, flags, mode); + } else { + return openat$sysv(dirfd, pathname, flags, mode); + } +} diff --git a/libc/calls/pipe.c b/libc/calls/pipe.c index d7e76ab8..f82d2272 100644 --- a/libc/calls/pipe.c +++ b/libc/calls/pipe.c @@ -20,6 +20,7 @@ #include "libc/calls/calls.h" #include "libc/calls/internal.h" #include "libc/dce.h" +#include "libc/sysv/errfuns.h" /** * Creates file-less file descriptors for inter-process communication. @@ -30,6 +31,7 @@ * @see pipe2() */ int pipe(int pipefd[hasatleast 2]) { + if (!pipefd) return efault(); if (!IsWindows()) { return pipe$sysv(pipefd); } else { diff --git a/libc/calls/pipe2.c b/libc/calls/pipe2.c index f53f147a..5b399781 100644 --- a/libc/calls/pipe2.c +++ b/libc/calls/pipe2.c @@ -20,6 +20,7 @@ #include "libc/calls/calls.h" #include "libc/calls/internal.h" #include "libc/dce.h" +#include "libc/sysv/errfuns.h" /** * Creates file-less file descriptors for interprocess communication. @@ -29,6 +30,7 @@ * @return 0 on success, or -1 w/ errno and pipefd isn't modified */ int pipe2(int pipefd[hasatleast 2], int flags) { + if (!pipefd) return efault(); if (!IsWindows()) { return pipe2$sysv(pipefd, flags); } else { diff --git a/libc/calls/pread.c b/libc/calls/pread.c index a7b21961..b4b10f7f 100644 --- a/libc/calls/pread.c +++ b/libc/calls/pread.c @@ -18,7 +18,7 @@ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/assert.h" -#include "libc/bits/bits.h" +#include "libc/bits/weaken.h" #include "libc/calls/calls.h" #include "libc/calls/internal.h" #include "libc/dce.h" diff --git a/libc/calls/read.c b/libc/calls/read.c index 8f7c34e4..eb3e0c4b 100644 --- a/libc/calls/read.c +++ b/libc/calls/read.c @@ -18,7 +18,7 @@ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/assert.h" -#include "libc/bits/bits.h" +#include "libc/bits/weaken.h" #include "libc/calls/calls.h" #include "libc/calls/internal.h" #include "libc/calls/struct/iovec.h" diff --git a/libc/calls/readv.c b/libc/calls/readv.c index da64fd5f..b299c3f5 100644 --- a/libc/calls/readv.c +++ b/libc/calls/readv.c @@ -17,6 +17,7 @@ │ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/bits/weaken.h" #include "libc/calls/calls.h" #include "libc/calls/internal.h" #include "libc/calls/struct/iovec.h" diff --git a/libc/calls/rename.c b/libc/calls/rename.c index 003c6917..8b82c02b 100644 --- a/libc/calls/rename.c +++ b/libc/calls/rename.c @@ -17,17 +17,20 @@ │ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/dce.h" -#include "libc/calls/internal.h" #include "libc/calls/calls.h" +#include "libc/calls/internal.h" +#include "libc/dce.h" #include "libc/sysv/consts/at.h" +#include "libc/sysv/errfuns.h" /** * Moves file the Unix way. + * * @return 0 on success or -1 w/ errno * @asyncsignalsafe */ int rename(const char *oldpathname, const char *newpathname) { + if (!oldpathname || !newpathname) return efault(); if (!IsWindows()) { return renameat$sysv(AT_FDCWD, oldpathname, AT_FDCWD, newpathname); } else { diff --git a/libc/calls/renameat.c b/libc/calls/renameat.c new file mode 100644 index 00000000..21503785 --- /dev/null +++ b/libc/calls/renameat.c @@ -0,0 +1,32 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/calls.h" +#include "libc/calls/internal.h" +#include "libc/sysv/consts/at.h" + +int renameat(int olddirfd, const char *oldpath, int newdirfd, + const char *newpath) { + unsigned mode; + if (olddirfd == AT_FDCWD && newdirfd == AT_FDCWD) { + return rename(oldpath, newpath); + } else { + return renameat$sysv(olddirfd, oldpath, newdirfd, newpath); + } +} diff --git a/libc/calls/sched_setaffinity.c b/libc/calls/sched_setaffinity.c index e0648127..ee0ebf88 100644 --- a/libc/calls/sched_setaffinity.c +++ b/libc/calls/sched_setaffinity.c @@ -30,10 +30,14 @@ static textwindows noinline int sched_setaffinity$nt(int pid, uint64_t bitsetsize, const void *bitset) { + int rc; + uintptr_t mask; + int64_t handle; typeof(SetThreadAffinityMask) *SetAffinityMask = SetThreadAffinityMask; - uintptr_t mask = 0; + mask = 0; memcpy(&mask, bitset, min(bitsetsize, sizeof(uintptr_t))); - int64_t handle = 0; + handle = 0; + if (!pid) pid = GetCurrentThreadId(); if (0 < pid && pid <= UINT32_MAX) { if (pid == GetCurrentProcessId()) { pid = GetCurrentProcess(); @@ -50,7 +54,7 @@ static textwindows noinline int sched_setaffinity$nt(int pid, } } } - int rc = SetAffinityMask(handle ? handle : pid, mask) ? 0 : winerr(); + rc = SetAffinityMask(handle ? handle : pid, mask) ? 0 : winerr(); if (handle) CloseHandle(handle); return rc; } @@ -58,7 +62,7 @@ static textwindows noinline int sched_setaffinity$nt(int pid, /** * Asks kernel to only schedule process on particular CPUs. * - * @param pid is the process or thread id + * @param pid is the process or thread id (or 0 for caller) * @param bitsetsize is byte length of bitset * @param bitset can be manipulated using bt(), bts(), etc. * @return 0 on success, or -1 w/ errno diff --git a/libc/calls/sigaction.c b/libc/calls/sigaction.c index effc4c0c..0aa2bc50 100644 --- a/libc/calls/sigaction.c +++ b/libc/calls/sigaction.c @@ -20,7 +20,12 @@ #include "libc/bits/bits.h" #include "libc/calls/calls.h" #include "libc/calls/internal.h" +#include "libc/calls/struct/sigaction-freebsd.h" +#include "libc/calls/struct/sigaction-linux.h" +#include "libc/calls/struct/sigaction-openbsd.h" +#include "libc/calls/struct/sigaction-xnu.h" #include "libc/calls/struct/sigaction.h" +#include "libc/calls/typedef/sigaction_f.h" #include "libc/calls/ucontext.h" #include "libc/dce.h" #include "libc/limits.h" @@ -32,51 +37,13 @@ #include "libc/sysv/consts/sig.h" #include "libc/sysv/errfuns.h" -struct siginfo; - union metasigaction { struct sigaction cosmo; - - struct sigaction$linux { - intptr_t sa_handler; - uint64_t sa_flags; - void (*sa_restorer)(void); - struct sigset$linux { - uint32_t sig[2]; - } sa_mask; - } linux; - - struct sigaction$freebsd { - intptr_t sa_handler; - uint32_t sa_flags; - struct sigset$freebsd { - uint32_t sig[4]; - } sa_mask; - } freebsd; - - struct sigaction$openbsd { - intptr_t sa_handler; - struct sigset$openbsd { - uint32_t sig[1]; - } sa_mask; - int32_t sa_flags; - } openbsd; - - struct sigaction$xnu_in { - intptr_t sa_handler; - void (*sa_restorer)(void *, int, int, const struct __darwin_siginfo *, - const struct __darwin_ucontext *); - struct sigset$xnu { - uint32_t sig[1]; - } sa_mask; - int32_t sa_flags; - } xnu_in; - - struct sigaction$xnu_out { - intptr_t sa_handler; - struct sigset$xnu sa_mask; - int32_t sa_flags; - } xnu_out; + struct sigaction$linux linux; + struct sigaction$freebsd freebsd; + struct sigaction$openbsd openbsd; + struct sigaction$xnu_in xnu_in; + struct sigaction$xnu_out xnu_out; }; #define SWITCHEROO(S1, S2, A, B, C, D) \ @@ -210,9 +177,8 @@ int(sigaction)(int sig, const struct sigaction *act, struct sigaction *oldact) { if (rc != -1) { if (oldact) { oldrva = g_sighandrvas[sig]; - oldact->sa_sigaction = oldrva < kSigactionMinRva - ? (sigaction_f)(intptr_t)oldrva - : (sigaction_f)((uintptr_t)&_base + oldrva); + oldact->sa_sigaction = (sigaction_f)( + oldrva < kSigactionMinRva ? oldrva : (intptr_t)&_base + oldrva); } if (act) { g_sighandrvas[sig] = rva; diff --git a/libc/calls/sigsuspend.c b/libc/calls/sigsuspend.c index 65b55a84..2021068a 100644 --- a/libc/calls/sigsuspend.c +++ b/libc/calls/sigsuspend.c @@ -17,10 +17,10 @@ │ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/dce.h" +#include "libc/calls/calls.h" #include "libc/calls/internal.h" #include "libc/calls/struct/sigset.h" -#include "libc/calls/calls.h" +#include "libc/dce.h" #include "libc/sysv/errfuns.h" /** @@ -31,6 +31,7 @@ * @asyncsignalsafe */ int sigsuspend(const sigset_t *mask) { + if (!mask) return efault(); if (!IsWindows()) { return sigsuspend$sysv(mask, 8); } else { diff --git a/libc/calls/stat.c b/libc/calls/stat.c index 5b9ef522..be4019b5 100644 --- a/libc/calls/stat.c +++ b/libc/calls/stat.c @@ -17,7 +17,7 @@ │ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/bits/bits.h" +#include "libc/bits/weaken.h" #include "libc/calls/calls.h" #include "libc/calls/internal.h" #include "libc/dce.h" diff --git a/libc/calls/stat2linux.c b/libc/calls/stat2linux.c index 36ee41c6..d1a31c7a 100644 --- a/libc/calls/stat2linux.c +++ b/libc/calls/stat2linux.c @@ -73,7 +73,7 @@ forceinline void stat2linux_openbsd(union metastat *ms) { * Transcodes “The Dismal Data Structure” from BSD→Linux ABI. * @asyncsignalsafe */ -void stat2linux(void *ms) { +textstartup void stat2linux(void *ms) { if (ms) { if (SupportsXnu() && IsXnu()) { stat2linux_xnu((union metastat *)ms); diff --git a/libc/calls/struct/metastat.h b/libc/calls/struct/metastat.h index 1c18a4ee..9bcc4607 100644 --- a/libc/calls/struct/metastat.h +++ b/libc/calls/struct/metastat.h @@ -1,5 +1,6 @@ #ifndef COSMOPOLITAN_LIBC_CALLS_STRUCT_METASTAT_H_ #define COSMOPOLITAN_LIBC_CALLS_STRUCT_METASTAT_H_ +#ifndef __STRICT_ANSI__ #include "libc/calls/struct/stat.h" #if !(__ASSEMBLER__ + __LINKER__ + 0) COSMOPOLITAN_C_START_ @@ -52,4 +53,5 @@ union metastat { COSMOPOLITAN_C_END_ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* !ANSI */ #endif /* COSMOPOLITAN_LIBC_CALLS_STRUCT_METASTAT_H_ */ diff --git a/libc/calls/struct/metatermios.h b/libc/calls/struct/metatermios.h index dcde41a4..f78b52b5 100644 --- a/libc/calls/struct/metatermios.h +++ b/libc/calls/struct/metatermios.h @@ -1,5 +1,6 @@ #ifndef COSMOPOLITAN_LIBC_CALLS_STRUCT_METATERMIOS_H_ #define COSMOPOLITAN_LIBC_CALLS_STRUCT_METATERMIOS_H_ +#ifndef __STRICT_ANSI__ #include "libc/calls/struct/termios.h" #if !(__ASSEMBLER__ + __LINKER__ + 0) COSMOPOLITAN_C_START_ @@ -32,4 +33,5 @@ union metatermios { COSMOPOLITAN_C_END_ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* !ANSI */ #endif /* COSMOPOLITAN_LIBC_CALLS_STRUCT_METATERMIOS_H_ */ diff --git a/libc/calls/struct/sigaction-freebsd.h b/libc/calls/struct/sigaction-freebsd.h new file mode 100644 index 00000000..aac23d13 --- /dev/null +++ b/libc/calls/struct/sigaction-freebsd.h @@ -0,0 +1,14 @@ +#ifndef COSMOPOLITAN_LIBC_CALLS_STRUCT_SIGACTION_FREEBSD_H_ +#define COSMOPOLITAN_LIBC_CALLS_STRUCT_SIGACTION_FREEBSD_H_ +#if !(__ASSEMBLER__ + __LINKER__ + 0) + +struct sigaction$freebsd { + intptr_t sa_handler; + uint32_t sa_flags; + struct sigset$freebsd { + uint32_t sig[4]; + } sa_mask; +}; + +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_CALLS_STRUCT_SIGACTION_FREEBSD_H_ */ diff --git a/libc/calls/struct/sigaction-linux.h b/libc/calls/struct/sigaction-linux.h new file mode 100644 index 00000000..5a4e3f93 --- /dev/null +++ b/libc/calls/struct/sigaction-linux.h @@ -0,0 +1,15 @@ +#ifndef COSMOPOLITAN_LIBC_CALLS_STRUCT_SIGACTION_LINUX_H_ +#define COSMOPOLITAN_LIBC_CALLS_STRUCT_SIGACTION_LINUX_H_ +#if !(__ASSEMBLER__ + __LINKER__ + 0) + +struct sigaction$linux { + intptr_t sa_handler; + uint64_t sa_flags; + void (*sa_restorer)(void); + struct sigset$linux { + uint32_t sig[2]; + } sa_mask; +}; + +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_CALLS_STRUCT_SIGACTION_LINUX_H_ */ diff --git a/libc/calls/struct/sigaction-openbsd.h b/libc/calls/struct/sigaction-openbsd.h new file mode 100644 index 00000000..47c629ea --- /dev/null +++ b/libc/calls/struct/sigaction-openbsd.h @@ -0,0 +1,14 @@ +#ifndef COSMOPOLITAN_LIBC_CALLS_STRUCT_SIGACTION_OPENBSD_H_ +#define COSMOPOLITAN_LIBC_CALLS_STRUCT_SIGACTION_OPENBSD_H_ +#if !(__ASSEMBLER__ + __LINKER__ + 0) + +struct sigaction$openbsd { + intptr_t sa_handler; + struct sigset$openbsd { + uint32_t sig[1]; + } sa_mask; + int32_t sa_flags; +}; + +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_CALLS_STRUCT_SIGACTION_OPENBSD_H_ */ diff --git a/libc/calls/struct/sigaction-xnu.h b/libc/calls/struct/sigaction-xnu.h new file mode 100644 index 00000000..e83bac80 --- /dev/null +++ b/libc/calls/struct/sigaction-xnu.h @@ -0,0 +1,28 @@ +#ifndef COSMOPOLITAN_LIBC_CALLS_STRUCT_SIGACTION_XNU_H_ +#define COSMOPOLITAN_LIBC_CALLS_STRUCT_SIGACTION_XNU_H_ +#include "libc/calls/internal.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) + +struct __darwin_ucontext; +struct __darwin_siginfo; + +struct sigset$xnu { + uint32_t sig[1]; +}; + +struct sigaction$xnu_in { + intptr_t sa_handler; + void (*sa_restorer)(void *, int, int, const struct __darwin_siginfo *, + const struct __darwin_ucontext *); + struct sigset$xnu sa_mask; + int32_t sa_flags; +}; + +struct sigaction$xnu_out { + intptr_t sa_handler; + struct sigset$xnu sa_mask; + int32_t sa_flags; +}; + +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_CALLS_STRUCT_SIGACTION_XNU_H_ */ diff --git a/libc/calls/struct/sigaltstack.h b/libc/calls/struct/sigaltstack.h index fa11f460..766cdfca 100644 --- a/libc/calls/struct/sigaltstack.h +++ b/libc/calls/struct/sigaltstack.h @@ -10,7 +10,5 @@ struct sigaltstack { typedef struct sigaltstack stack_t; -static_assert(sizeof(stack_t) == 24); - #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ #endif /* COSMOPOLITAN_LIBC_CALLS_STRUCT_SIGALTSTACK_H_ */ diff --git a/libc/calls/struct/sigset.h b/libc/calls/struct/sigset.h index bd076e48..b46b6015 100644 --- a/libc/calls/struct/sigset.h +++ b/libc/calls/struct/sigset.h @@ -2,7 +2,7 @@ #define COSMOPOLITAN_LIBC_CALLS_STRUCT_SIGSET_H_ #if !(__ASSEMBLER__ + __LINKER__ + 0) -struct sigset { +struct sigset { /* cosmo abi (linux is uint64_t) */ uint32_t sig[4]; /* ignore sig[2] and sig[3] (for freebsd) */ } aligned(8); diff --git a/libc/calls/termios-internal.h b/libc/calls/termios-internal.h index 6bbb2af5..2b468576 100644 --- a/libc/calls/termios-internal.h +++ b/libc/calls/termios-internal.h @@ -1,5 +1,6 @@ #ifndef COSMOPOLITAN_LIBC_CALLS_TERMIOS_INTERNAL_H_ #define COSMOPOLITAN_LIBC_CALLS_TERMIOS_INTERNAL_H_ +#ifndef __STRICT_ANSI__ #include "libc/bits/safemacros.h" #include "libc/str/str.h" #if !(__ASSEMBLER__ + __LINKER__ + 0) @@ -26,4 +27,5 @@ void termios2linux(struct termios *, const union metatermios *); COSMOPOLITAN_C_END_ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* !ANSI */ #endif /* COSMOPOLITAN_LIBC_CALLS_TERMIOS_INTERNAL_H_ */ diff --git a/libc/calls/termios.h b/libc/calls/termios.h index b02ff646..029a232d 100644 --- a/libc/calls/termios.h +++ b/libc/calls/termios.h @@ -12,10 +12,10 @@ COSMOPOLITAN_C_START_ │ cosmopolitan § teletypewriter control ─╬─│┼ ╚────────────────────────────────────────────────────────────────────────────│*/ -int tcgetattr(int fd, struct termios *tio); -int tcsetattr(int fd, int opt, const struct termios *tio); -int tcsetpgrp(int fd, int32_t pgrp); -int32_t tcgetpgrp(int fd); +int tcgetattr(int, struct termios *); +int tcsetattr(int, int, const struct termios *); +int tcsetpgrp(int, int32_t); +int32_t tcgetpgrp(int); int openpty(int *, int *, char *, const struct termios *, const struct winsize *) paramsnonnull((1, 2)) nodiscard; diff --git a/libc/calls/truncate.c b/libc/calls/truncate.c index 9f444441..087bde3c 100644 --- a/libc/calls/truncate.c +++ b/libc/calls/truncate.c @@ -20,6 +20,7 @@ #include "libc/calls/calls.h" #include "libc/calls/internal.h" #include "libc/dce.h" +#include "libc/sysv/errfuns.h" /** * Reduces or extends underlying physical medium of file. @@ -32,6 +33,7 @@ * @error ENOENT */ int truncate(const char *path, uint64_t length) { + if (!path) return efault(); if (!IsWindows()) { return truncate$sysv(path, length); } else { diff --git a/libc/calls/ucontext.h b/libc/calls/ucontext.h index 8345b09d..2af25a1e 100644 --- a/libc/calls/ucontext.h +++ b/libc/calls/ucontext.h @@ -6,29 +6,29 @@ #if !(__ASSEMBLER__ + __LINKER__ + 0) COSMOPOLITAN_C_START_ -#define REG_R8 REG_R8 -#define REG_R9 REG_R9 -#define REG_R10 REG_R10 -#define REG_R11 REG_R11 -#define REG_R12 REG_R12 -#define REG_R13 REG_R13 -#define REG_R14 REG_R14 -#define REG_R15 REG_R15 -#define REG_RDI REG_RDI -#define REG_RSI REG_RSI -#define REG_RBP REG_RBP -#define REG_RBX REG_RBX -#define REG_RDX REG_RDX -#define REG_RAX REG_RAX -#define REG_RCX REG_RCX -#define REG_RSP REG_RSP -#define REG_RIP REG_RIP -#define REG_EFL REG_EFL -#define REG_CSGSFS REG_CSGSFS -#define REG_ERR REG_ERR -#define REG_TRAPNO REG_TRAPNO +#define REG_R8 REG_R8 +#define REG_R9 REG_R9 +#define REG_R10 REG_R10 +#define REG_R11 REG_R11 +#define REG_R12 REG_R12 +#define REG_R13 REG_R13 +#define REG_R14 REG_R14 +#define REG_R15 REG_R15 +#define REG_RDI REG_RDI +#define REG_RSI REG_RSI +#define REG_RBP REG_RBP +#define REG_RBX REG_RBX +#define REG_RDX REG_RDX +#define REG_RAX REG_RAX +#define REG_RCX REG_RCX +#define REG_RSP REG_RSP +#define REG_RIP REG_RIP +#define REG_EFL REG_EFL +#define REG_CSGSFS REG_CSGSFS +#define REG_ERR REG_ERR +#define REG_TRAPNO REG_TRAPNO #define REG_OLDMASK REG_OLDMASK -#define REG_CR2 REG_CR2 +#define REG_CR2 REG_CR2 enum GeneralRegister { REG_R8, @@ -60,16 +60,12 @@ struct XmmRegister { uint64_t u64[2]; }; -static_assert(sizeof(struct XmmRegister) == 16); - struct FpuStackEntry { uint16_t significand[4]; uint16_t exponent; uint16_t padding[3]; }; -static_assert(sizeof(struct FpuStackEntry) == 16); - struct FpuState { uint16_t cwd; uint16_t swd; diff --git a/libc/calls/unlink.c b/libc/calls/unlink.c index 1a540998..8ae88b6a 100644 --- a/libc/calls/unlink.c +++ b/libc/calls/unlink.c @@ -17,12 +17,12 @@ │ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/internal.h" #include "libc/dce.h" #include "libc/nt/files.h" #include "libc/str/str.h" -#include "libc/calls/internal.h" -#include "libc/sysv/errfuns.h" #include "libc/sysv/consts/at.h" +#include "libc/sysv/errfuns.h" /** * Deletes file. @@ -36,7 +36,7 @@ * @asyncsignalsafe */ int unlink(const char *name) { - if (!name) return 0; + if (!name) return efault(); if (!IsWindows()) { return unlinkat$sysv(AT_FDCWD, name, 0); } else { diff --git a/libc/calls/unlink_s.c b/libc/calls/unlink_s.c index 406e1a3e..ea9f5464 100644 --- a/libc/calls/unlink_s.c +++ b/libc/calls/unlink_s.c @@ -19,7 +19,6 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/bits/bits.h" #include "libc/calls/calls.h" -#include "libc/runtime/mappings.h" /** * Deletes file, the Cosmopolitan way. diff --git a/libc/calls/unlinkat.c b/libc/calls/unlinkat.c new file mode 100644 index 00000000..a3fd6f25 --- /dev/null +++ b/libc/calls/unlinkat.c @@ -0,0 +1,30 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/calls.h" +#include "libc/calls/internal.h" +#include "libc/sysv/consts/at.h" + +int unlinkat(int dirfd, const char *pathname, int flags) { + if (dirfd == AT_FDCWD) { + return unlink(pathname); + } else { + return unlinkat$sysv(dirfd, pathname, flags); + } +} diff --git a/libc/calls/vdprintf.c b/libc/calls/vdprintf.c index 962034e2..ff6dfe71 100644 --- a/libc/calls/vdprintf.c +++ b/libc/calls/vdprintf.c @@ -21,45 +21,46 @@ #include "libc/dce.h" #include "libc/fmt/fmt.h" #include "libc/limits.h" +#include "libc/macros.h" #include "libc/nt/files.h" #include "libc/sysv/errfuns.h" -#define DBUFSIZ 1460 /* tcp ethernet frame < -Wframe-larger-than=4096 */ - -struct dfile { +struct VdprintfState { + int n; int fd; - unsigned idx; - unsigned toto; - unsigned char buf[DBUFSIZ]; + unsigned char buf[1024]; }; -static int vdprintf_flush(struct dfile *df) { - ssize_t wrote; - do { - wrote = write(df->fd, &df->buf[0], df->idx); - if (wrote == -1) return -1; - df->toto += (unsigned)wrote; - df->idx -= (unsigned)wrote; - if (df->toto > INT_MAX) return eoverflow(); - } while (df->idx); +static int vdprintf_flush(struct VdprintfState *df, int n) { + int i, rc; + for (i = 0; i < n; i += rc) { + if ((rc = write(df->fd, df->buf + i, n - i)) == -1) { + return -1; + } + } return 0; } -static int vdprintfputchar(unsigned char c, struct dfile *df) { - df->buf[df->idx++] = c; - if (df->idx == DBUFSIZ && vdprintf_flush(df) == -1) return -1; - return 0; +static int vdprintfputchar(int c, struct VdprintfState *df) { + df->buf[df->n++ & (ARRAYLEN(df->buf) - 1)] = c & 0xff; + if ((df->n & (ARRAYLEN(df->buf) - 1))) { + return 0; + } else { + return vdprintf_flush(df, ARRAYLEN(df->buf)); + } } /** * Formats string directly to system i/o device. */ int(vdprintf)(int fd, const char *fmt, va_list va) { - struct dfile df; + struct VdprintfState df; + df.n = 0; df.fd = fd; - df.idx = 0; - df.toto = 0; - if (palandprintf(vdprintfputchar, &df, fmt, va) == -1) return -1; - if (df.idx && vdprintf_flush(&df) == -1) return -1; - return df.toto; + if (palandprintf(vdprintfputchar, &df, fmt, va) != -1 || + vdprintf_flush(&df, df.n & (ARRAYLEN(df.buf) - 1)) != -1) { + return df.n; + } else { + return -1; + } } diff --git a/libc/calls/write.c b/libc/calls/write.c index 5628f1f0..a52fc22f 100644 --- a/libc/calls/write.c +++ b/libc/calls/write.c @@ -18,7 +18,7 @@ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/assert.h" -#include "libc/bits/bits.h" +#include "libc/bits/weaken.h" #include "libc/calls/calls.h" #include "libc/calls/internal.h" #include "libc/calls/struct/iovec.h" diff --git a/libc/calls/writev.c b/libc/calls/writev.c index 4aceaf31..1a61dd84 100644 --- a/libc/calls/writev.c +++ b/libc/calls/writev.c @@ -17,6 +17,7 @@ │ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/bits/weaken.h" #include "libc/calls/calls.h" #include "libc/calls/internal.h" #include "libc/sock/internal.h" diff --git a/libc/complex.h b/libc/complex.h index a804cdd2..cbb98cdb 100644 --- a/libc/complex.h +++ b/libc/complex.h @@ -2,10 +2,26 @@ #define COSMOPOLITAN_LIBC_COMPLEX_H_ #if !(__ASSEMBLER__ + __LINKER__ + 0) COSMOPOLITAN_C_START_ +#if __STDC_VERSION__ + 0 >= 201112 #define complex _Complex #define imaginary _Imaginary +double cabs(complex double); +double carg(complex double); +double cimag(complex double); +double creal(complex double); + +float cabsf(complex float); +float cargf(complex float); +float cimagf(complex float); +float crealf(complex float); + +long double cabsl(complex long double); +long double cargl(complex long double); +long double cimagl(complex long double); +long double creall(complex long double); + complex double cacos(complex double); complex double cacosh(complex double); complex double casin(complex double); @@ -46,21 +62,6 @@ complex float csqrtf(complex float); complex float ctanf(complex float); complex float ctanhf(complex float); -double cabs(complex double); -double carg(complex double); -double cimag(complex double); -double creal(complex double); - -float cabsf(complex float); -float cargf(complex float); -float cimagf(complex float); -float crealf(complex float); - -long double cabsl(complex long double); -long double cargl(complex long double); -long double cimagl(complex long double); -long double creall(complex long double); - complex long double cprojl(complex long double); complex long double csinhl(complex long double); complex long double csinl(complex long double); @@ -81,6 +82,7 @@ complex long double clogl(complex long double); complex long double conjl(complex long double); complex long double cpowl(complex long double, complex long double); +#endif /* C11 */ COSMOPOLITAN_C_END_ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ #endif /* COSMOPOLITAN_LIBC_COMPLEX_H_ */ diff --git a/libc/conv/basename.c b/libc/conv/basename.c index da11e0ca..df16bb81 100644 --- a/libc/conv/basename.c +++ b/libc/conv/basename.c @@ -31,4 +31,6 @@ * @param path is NUL-terminated UTF-8 path * @return pointer inside path or path itself */ -char *basename(const char *path) { return basename_n(path, strlen(path)); } +textstartup char *basename(const char *path) { + return basename_n(path, strlen(path)); +} diff --git a/libc/conv/basename_n.c b/libc/conv/basename_n.c index 07266275..544507d3 100644 --- a/libc/conv/basename_n.c +++ b/libc/conv/basename_n.c @@ -31,7 +31,7 @@ * @param size is byte length of path * @return pointer inside path or path itself */ -char *basename_n(const char *path, size_t size) { +textstartup char *basename_n(const char *path, size_t size) { size_t i, l; if (size) { if (isslash(path[size - 1])) { diff --git a/libc/conv/conv.h b/libc/conv/conv.h index 6b8c5ae3..713ae0b2 100644 --- a/libc/conv/conv.h +++ b/libc/conv/conv.h @@ -1,5 +1,8 @@ #ifndef COSMOPOLITAN_LIBC_CONV_CONV_H_ #define COSMOPOLITAN_LIBC_CONV_CONV_H_ +#include "libc/calls/struct/timespec.h" +#include "libc/calls/struct/timeval.h" +#include "libc/nt/struct/filetime.h" /*───────────────────────────────────────────────────────────────────────────│─╗ │ cosmopolitan § conversion ─╬─│┼ @@ -23,6 +26,7 @@ long labs(long) libcesque pureconst; long long llabs(long long) libcesque pureconst; char *ltpcpy(char *, long) paramsnonnull() libcesque nocallback; int llog10(unsigned long) libcesque pureconst; +int unsleb128(const void *, size_t, int64_t *); int atoi(const char *) paramsnonnull() libcesque nosideeffect; long atol(const char *) paramsnonnull() libcesque nosideeffect; long long atoll(const char *) paramsnonnull() libcesque nosideeffect; @@ -38,18 +42,12 @@ long wcstol(const wchar_t *, wchar_t **, int); long strtol(const char *, char **, int) paramsnonnull((1)) libcesque nosideeffect; -intmax_t __imaxabs(intmax_t) asm("imaxabs") libcesque pureconst; -#define imaxabs(x) __imaxabs(x) - /*───────────────────────────────────────────────────────────────────────────│─╗ │ cosmopolitan § conversion » time ─╬─│┼ ╚────────────────────────────────────────────────────────────────────────────│*/ -struct NtFileTime; -struct timespec; -struct timeval; - -void filetimetotimespec(struct timespec *, struct NtFileTime) paramsnonnull(); +struct timespec filetimetotimespec(struct NtFileTime); +struct NtFileTime timespectofiletime(struct timespec); struct NtFileTime timetofiletime(int64_t) nothrow pureconst; int64_t filetimetotime(struct NtFileTime) nothrow pureconst; void filetimetotimeval(struct timeval *, struct NtFileTime) nothrow; @@ -104,6 +102,11 @@ double RoundDecimalPlaces(double, double, double(double)); #define lldiv(num, den) ((lldiv_t){(num) / (den), (num) % (den)}) #endif +#ifndef __STRICT_ANSI__ +intmax_t __imaxabs(intmax_t) asm("imaxabs") libcesque pureconst; +#define imaxabs(x) __imaxabs(x) +#endif /* !ANSI */ + COSMOPOLITAN_C_END_ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ #endif /* COSMOPOLITAN_LIBC_CONV_CONV_H_ */ diff --git a/libc/conv/conv.mk b/libc/conv/conv.mk index 9d115526..5365f94e 100644 --- a/libc/conv/conv.mk +++ b/libc/conv/conv.mk @@ -38,7 +38,8 @@ LIBC_CONV_A_DIRECTDEPS = \ LIBC_STUBS \ LIBC_NEXGEN32E \ LIBC_TINYMATH \ - LIBC_SYSV + LIBC_SYSV \ + THIRD_PARTY_COMPILER_RT LIBC_CONV_A_DEPS := \ $(call uniq,$(foreach x,$(LIBC_CONV_A_DIRECTDEPS),$($(x)))) @@ -51,12 +52,10 @@ $(LIBC_CONV_A).pkg: \ $(LIBC_CONV_A_OBJS) \ $(foreach x,$(LIBC_CONV_A_DIRECTDEPS),$($(x)_A).pkg) -#o/$(MODE)/libc/conv/strtoimax.o: CC = clang-10 -#o/$(MODE)/libc/conv/strtoumax.o: CC = clang-10 - -o/$(MODE)/libc/conv/itoa64radix10.o \ +o/$(MODE)/libc/conv/itoa64radix10.greg.o \ o/$(MODE)/libc/conv/timetofiletime.o \ o/$(MODE)/libc/conv/filetimetotime.o \ +o/$(MODE)/libc/conv/timespectofiletime.o \ o/$(MODE)/libc/conv/filetimetotimespec.o \ o/$(MODE)/libc/conv/filetimetotimeval.o: \ OVERRIDE_COPTS += \ diff --git a/libc/conv/filetimetotimespec.c b/libc/conv/filetimetotimespec.c index c6a7d8a4..f1f79689 100644 --- a/libc/conv/filetimetotimespec.c +++ b/libc/conv/filetimetotimespec.c @@ -24,9 +24,11 @@ /** * Converts Windows COBOL timestamp to UNIX epoch in nanoseconds. */ -void filetimetotimespec(struct timespec *ts, struct NtFileTime ft) { - uint64_t t = (uint64_t)ft.dwHighDateTime << 32 | ft.dwLowDateTime; - uint64_t x = t - MODERNITYSECONDS * HECTONANOSECONDS; - ts->tv_sec = x / HECTONANOSECONDS; - ts->tv_nsec = x % HECTONANOSECONDS * 100; +struct timespec filetimetotimespec(struct NtFileTime ft) { + uint64_t x; + x = ft.dwHighDateTime; + x <<= 32; + x |= ft.dwLowDateTime; + x -= MODERNITYSECONDS; + return (struct timespec){x / HECTONANOSECONDS, x % HECTONANOSECONDS * 100}; } diff --git a/libc/conv/itoa.h b/libc/conv/itoa.h index 437c1b57..66de9912 100644 --- a/libc/conv/itoa.h +++ b/libc/conv/itoa.h @@ -23,13 +23,17 @@ COSMOPOLITAN_C_START_ */ -size_t int128toarray_radix10(int128_t, char *); -size_t uint128toarray_radix10(uint128_t, char *); size_t int64toarray_radix10(int64_t, char *); size_t uint64toarray_radix10(uint64_t, char *); size_t int64toarray(int64_t, char *, int); size_t uint64toarray(uint64_t, char *, int); size_t uint64toarray_radix16(uint64_t, char[hasatleast 17]); +size_t uint64toarray_fixed16(uint64_t, char[hasatleast 17], uint8_t); + +#ifndef __STRICT_ANSI__ +size_t int128toarray_radix10(int128_t, char *); +size_t uint128toarray_radix10(uint128_t, char *); +#endif COSMOPOLITAN_C_END_ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ diff --git a/libc/conv/itoa128radix10.greg.c b/libc/conv/itoa128radix10.greg.c index 46033b80..65b22262 100644 --- a/libc/conv/itoa128radix10.greg.c +++ b/libc/conv/itoa128radix10.greg.c @@ -20,13 +20,21 @@ #include "libc/alg/reverse.h" #include "libc/conv/conv.h" #include "libc/conv/itoa.h" +#include "libc/limits.h" +uint128_t __udivmodti4(uint128_t, uint128_t, uint128_t *); + +/** + * Converts unsigned 128-bit integer to string. + * @param a needs at least 40 bytes + * @return bytes written w/o nul + */ noinline size_t uint128toarray_radix10(uint128_t i, char *a) { size_t j; - unsigned rem; + uint128_t rem; j = 0; do { - i = div10(i, &rem); + i = __udivmodti4(i, 10, &rem); a[j++] = rem + '0'; } while (i > 0); a[j] = '\0'; @@ -34,10 +42,21 @@ noinline size_t uint128toarray_radix10(uint128_t i, char *a) { return j; } +/** + * Converts signed 128-bit integer to string. + * @param a needs at least 41 bytes + * @return bytes written w/o nul + */ size_t int128toarray_radix10(int128_t i, char *a) { if (i < 0) { - *a++ = '-'; - i = -i; + if (i != INT128_MIN) { + *a++ = '-'; + return 1 + uint128toarray_radix10(-i, a); + } else { + memcpy(a, "-170141183460469231731687303715884105728", 41); + return 40; + } + } else { + return uint128toarray_radix10(i, a); } - return uint128toarray_radix10(i, a); } diff --git a/libc/conv/itoa64fixed16.greg.c b/libc/conv/itoa64fixed16.greg.c new file mode 100644 index 00000000..f88b25ac --- /dev/null +++ b/libc/conv/itoa64fixed16.greg.c @@ -0,0 +1,38 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/alg/reverse.h" +#include "libc/assert.h" +#include "libc/conv/itoa.h" + +size_t uint64toarray_fixed16(uint64_t i, char a[hasatleast 17], uint8_t b) { + size_t j; + assert(b <= 64); + assert(b % 4 == 0); + j = 0; + if (b) { + do { + a[j++] = "0123456789abcdef"[i & 15]; + i >>= 4; + } while (b -= 4); + } + a[j] = '\0'; + reverse(a, j); + return j; +} diff --git a/libc/conv/itoa64radix10.greg.c b/libc/conv/itoa64radix10.greg.c index 37e7b6ff..d7d39bb7 100644 --- a/libc/conv/itoa64radix10.greg.c +++ b/libc/conv/itoa64radix10.greg.c @@ -20,7 +20,13 @@ #include "libc/alg/reverse.h" #include "libc/conv/conv.h" #include "libc/conv/itoa.h" +#include "libc/limits.h" +/** + * Converts unsigned 64-bit integer to string. + * @param a needs at least 21 bytes + * @return bytes written w/o nul + */ noinline size_t uint64toarray_radix10(uint64_t i, char *a) { size_t j; j = 0; @@ -33,10 +39,21 @@ noinline size_t uint64toarray_radix10(uint64_t i, char *a) { return j; } +/** + * Converts signed 64-bit integer to string. + * @param a needs at least 21 bytes + * @return bytes written w/o nul + */ size_t int64toarray_radix10(int64_t i, char *a) { if (i < 0) { - *a++ = '-'; - i = -i; + if (i != INT64_MIN) { + *a++ = '-'; + return 1 + uint64toarray_radix10(-i, a); + } else { + memcpy(a, "-9223372036854775808", 21); + return 20; + } + } else { + return uint64toarray_radix10(i, a); } - return uint64toarray_radix10(i, a); } diff --git a/libc/conv/itoa64radix16.greg.c b/libc/conv/itoa64radix16.greg.c index 56e5ed1b..a01d5919 100644 --- a/libc/conv/itoa64radix16.greg.c +++ b/libc/conv/itoa64radix16.greg.c @@ -22,11 +22,9 @@ size_t uint64toarray_radix16(uint64_t i, char a[hasatleast 17]) { size_t j; - unsigned char d; j = 0; do { - d = i % 16; - a[j++] = d < 10 ? d + '0' : d + 'a'; + a[j++] = "0123456789abcdef"[i % 16]; i /= 16; } while (i > 0); a[j] = '\0'; diff --git a/libc/conv/strtoimax.c b/libc/conv/strtoimax.c index a4efc27e..877a8e76 100644 --- a/libc/conv/strtoimax.c +++ b/libc/conv/strtoimax.c @@ -76,6 +76,10 @@ intmax_t strtoimax(const char *s, char **endptr, int base) { } else { base = 10; } + } else if (*s == '0') { + ++s; + if (base == 2 && *s == 'b' && *s == 'B') ++s; + if (base == 16 && *s == 'x' && *s == 'X') ++s; } for (;;) { diff --git a/libc/conv/strtoumax.c b/libc/conv/strtoumax.c index da06a947..2040aeaa 100644 --- a/libc/conv/strtoumax.c +++ b/libc/conv/strtoumax.c @@ -28,7 +28,10 @@ */ uintmax_t strtoumax(const char *s, char **endptr, int base) { const unsigned char *p = (const unsigned char *)s; - uintmax_t res = 0; + unsigned diglet; + uintmax_t res; + + res = 0; while (isspace(*p)) { p++; @@ -49,10 +52,14 @@ uintmax_t strtoumax(const char *s, char **endptr, int base) { } else { base = 10; } + } else if (*s == '0') { + ++s; + if (base == 2 && *s == 'b' && *s == 'B') ++s; + if (base == 16 && *s == 'x' && *s == 'X') ++s; } for (;;) { - unsigned diglet = kBase36[*p]; + diglet = kBase36[*p]; if (!diglet || diglet > base) break; p++; res *= base; diff --git a/libc/conv/timespectofiletime.c b/libc/conv/timespectofiletime.c new file mode 100644 index 00000000..4d3dc8f5 --- /dev/null +++ b/libc/conv/timespectofiletime.c @@ -0,0 +1,34 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/calls.h" +#include "libc/conv/conv.h" +#include "libc/nexgen32e/nexgen32e.h" +#include "libc/nt/struct/filetime.h" + +/** + * Converts UNIX nanosecond timestamp to Windows COBOL timestamp. + */ +struct NtFileTime timespectofiletime(struct timespec ts) { + uint64_t x; + x = MODERNITYSECONDS; + x += ts.tv_sec * HECTONANOSECONDS; + x += div100int64(ts.tv_nsec); + return (struct NtFileTime){x, x >> 32}; +} diff --git a/libc/conv/unsleb128.c b/libc/conv/unsleb128.c new file mode 100644 index 00000000..e52fd41f --- /dev/null +++ b/libc/conv/unsleb128.c @@ -0,0 +1,45 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/conv/conv.h" + +/** + * Decodes a GNU-style varint from a buffer. + * + * The GNU Assembler is able to encode numbers this way, since it's used + * by the DWARF debug format. + */ +int unsleb128(const void *buf, size_t size, int64_t *out) { + int b; + int64_t r, w; + unsigned char c; + const unsigned char *p, *pe; + pe = (p = buf) + size; + r = b = 0; + do { + if (size && p == pe) return -1; + c = *p++; + w = c & 0x7f; + r |= w << b; + b += 7; + } while (c & 0x80); + if (c & 0x40) r |= -1ull << b; + if (out) *out = r; + return p - (const unsigned char *)buf; +} diff --git a/libc/crt/crt.S b/libc/crt/crt.S index b349d146..6f490515 100644 --- a/libc/crt/crt.S +++ b/libc/crt/crt.S @@ -24,21 +24,22 @@ .section .start,"ax",@progbits .source __FILE__ + nop + / System Five userspace program entrypoint. / / @param rsp is [n,argv₀..argvₙ₋₁,0,envp₀..,0,auxv₀..,0,..] / @note FreeBSD is special (see freebsd/lib/csu/amd64/...) / @noreturn -_start_xnu: - movb $XNU,hostos(%rip) - jmp 0f _start: test %rdi,%rdi cmovnz %rdi,%rsp jz 0f movb $FREEBSD,hostos(%rip) -0: movslq (%rsp),%r12 # argc - lea 8(%rsp),%r13 # argv - lea 24(%rsp,%r12,8),%r14 # envp +0: mov (%rsp),%ebx # argc + lea 8(%rsp),%rsi # argv + lea 24(%rsp,%rbx,8),%rdx # envp + .frame0 + bofram 9f .weak idata.iat,idata.iatend ezlea missingno,ax # make win32 imps noop ezlea idata.iat,di @@ -48,9 +49,21 @@ _start: test %rdi,%rdi rep stosq xor %eax,%eax # find end of environ or $-1,%ecx - mov %r14,%rdi + mov %rdx,%rdi repnz scasq - mov %rdi,%r15 # auxv - jmp __executive - .endfn _start,weak,hidden + mov %rdi,%rcx # auxv + mov %ebx,%edi + call _executive +9: .endfn _start,weak,hidden + + ud2 + +/ Macintosh userspace program entrypoint. +/ +/ @param rsp is [n,argv₀..argvₙ₋₁,0,envp₀..,0,auxv₀..,0,..] +/ @note FreeBSD is special (see freebsd/lib/csu/amd64/...) +/ @noreturn +_start_xnu: + movb $XNU,hostos(%rip) + jmp 0b .endfn _start_xnu,weak,hidden diff --git a/libc/crypto/rijndael.h b/libc/crypto/rijndael.h index 09c63091..87a6236f 100644 --- a/libc/crypto/rijndael.h +++ b/libc/crypto/rijndael.h @@ -1,5 +1,6 @@ #ifndef COSMOPOLITAN_LIBC_CRYPTO_RIJNDAEL_H_ #define COSMOPOLITAN_LIBC_CRYPTO_RIJNDAEL_H_ +#ifndef __STRICT_ANSI__ #include "libc/str/str.h" #if !(__ASSEMBLER__ + __LINKER__ + 0) COSMOPOLITAN_C_START_ @@ -46,11 +47,12 @@ aes_block_t unrijndael(uint32_t, aes_block_t, const struct Rijndael *); │ cosmopolitan § cryptography » implementation details ─╬─│┼ ╚────────────────────────────────────────────────────────────────────────────│*/ -aligned(64) extern const uint8_t kAesSbox[256]; -aligned(64) extern const uint8_t kAesSboxInverse[256]; +extern const uint8_t kAesSbox[256] aligned(64); +extern const uint8_t kAesSboxInverse[256] aligned(64); aes_block_t InvMixColumns(aes_block_t) hidden; COSMOPOLITAN_C_END_ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* !ANSI */ #endif /* COSMOPOLITAN_LIBC_CRYPTO_RIJNDAEL_H_ */ diff --git a/libc/dce.h b/libc/dce.h index 55747393..606d5c11 100644 --- a/libc/dce.h +++ b/libc/dce.h @@ -17,6 +17,7 @@ * offer marginal improvements in terms of code size and performance, at * the cost of portability. */ +#define HOSTOS (hostos & SUPPORT_VECTOR) #ifndef SUPPORT_VECTOR #define SUPPORT_VECTOR 0b11111111 #endif @@ -93,13 +94,13 @@ ((SUPPORT_VECTOR & (LINUX | METAL | XNU | OPENBSD | FREEBSD)) != 0) #ifndef __ASSEMBLER__ -#define IsLinux() ((hostos & LINUX) == LINUX) -#define IsMetal() ((hostos & METAL) == METAL) -#define IsWindows() ((hostos & WINDOWS) == WINDOWS) -#define IsBsd() ((hostos & (XNU | FREEBSD | OPENBSD)) != 0) -#define IsXnu() ((hostos & XNU) == XNU) -#define IsFreebsd() ((hostos & FREEBSD) == FREEBSD) -#define IsOpenbsd() ((hostos & OPENBSD) == OPENBSD) +#define IsLinux() ((HOSTOS & LINUX) == LINUX) +#define IsMetal() ((HOSTOS & METAL) == METAL) +#define IsWindows() ((HOSTOS & WINDOWS) == WINDOWS) +#define IsBsd() ((HOSTOS & (XNU | FREEBSD | OPENBSD)) != 0) +#define IsXnu() ((HOSTOS & XNU) == XNU) +#define IsFreebsd() ((HOSTOS & FREEBSD) == FREEBSD) +#define IsOpenbsd() ((HOSTOS & OPENBSD) == OPENBSD) #else /* clang-format off */ #define IsLinux() $LINUX,hostos(%rip) @@ -115,8 +116,7 @@ #if !(__ASSEMBLER__ + __LINKER__ + 0) COSMOPOLITAN_C_START_ -#define hostos (__hostos & SUPPORT_VECTOR) -extern const int __hostos asm("hostos"); +extern const int hostos; COSMOPOLITAN_C_END_ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ diff --git a/libc/dns/dnsquestion.h b/libc/dns/dnsquestion.h index d6dd7e4f..bb916139 100644 --- a/libc/dns/dnsquestion.h +++ b/libc/dns/dnsquestion.h @@ -1,5 +1,6 @@ #ifndef COSMOPOLITAN_LIBC_DNS_DNSQUESTION_H_ #define COSMOPOLITAN_LIBC_DNS_DNSQUESTION_H_ +#ifndef __STRICT_ANSI__ #include "libc/dns/dns.h" #include "libc/sysv/errfuns.h" #if !(__ASSEMBLER__ + __LINKER__ + 0) @@ -29,4 +30,5 @@ forceinline int serializednsquestion(uint8_t *buf, size_t size, COSMOPOLITAN_C_END_ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* !ANSI */ #endif /* COSMOPOLITAN_LIBC_DNS_DNSQUESTION_H_ */ diff --git a/libc/dns/getaddrinfo.c b/libc/dns/getaddrinfo.c index 77ca0cbe..94ae3541 100644 --- a/libc/dns/getaddrinfo.c +++ b/libc/dns/getaddrinfo.c @@ -18,6 +18,7 @@ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/bits/safemacros.h" +#include "libc/calls/calls.h" #include "libc/conv/conv.h" #include "libc/dns/dns.h" #include "libc/dns/hoststxt.h" @@ -25,12 +26,11 @@ #include "libc/mem/mem.h" #include "libc/sock/sock.h" #include "libc/str/str.h" -#include "libc/calls/calls.h" -#include "libc/sysv/errfuns.h" #include "libc/sysv/consts/af.h" #include "libc/sysv/consts/ai.h" #include "libc/sysv/consts/eai.h" #include "libc/sysv/consts/inaddr.h" +#include "libc/sysv/errfuns.h" /** * Resolves address for internet name. @@ -44,20 +44,15 @@ */ int getaddrinfo(const char *name, const char *service, const struct addrinfo *hints, struct addrinfo **res) { - int port = 0; + int rc, port; + const char *canon; struct addrinfo *ai; - if ((!name && !service) || (service && (port = parseport(service)) == -1)) { - return EAI_NONAME; - } - if (!name && (hints->ai_flags & AI_CANONNAME) == AI_CANONNAME) { - return EAI_BADFLAGS; - } - if (!(ai = newaddrinfo(port))) { - return EAI_MEMORY; - } - if (service) { - ai->ai_addr4->sin_port = htons(port); - } + port = 0; + if (!name && !service) return EAI_NONAME; + if (service && (port = parseport(service)) == -1) return EAI_NONAME; + if (!name && (hints->ai_flags & AI_CANONNAME)) return EAI_BADFLAGS; + if (!(ai = newaddrinfo(port))) return EAI_MEMORY; + if (service) ai->ai_addr4->sin_port = htons(port); if (hints) { ai->ai_socktype = hints->ai_socktype; ai->ai_protocol = hints->ai_protocol; @@ -69,7 +64,6 @@ int getaddrinfo(const char *name, const char *service, : INADDR_LOOPBACK; return 0; } - const char *canon; if (inet_pton(AF_INET, name, &ai->ai_addr4->sin_addr.s_addr) == 1) { *res = ai; return 0; @@ -82,8 +76,8 @@ int getaddrinfo(const char *name, const char *service, *res = ai; return 0; } else { - int rc = resolvedns(getresolvconf(), AF_INET, name, ai->ai_addr, - sizeof(ai->ai_addr4)); + rc = resolvedns(getresolvconf(), AF_INET, name, ai->ai_addr, + sizeof(ai->ai_addr4)); if (rc > 0) { *res = ai; return 0; diff --git a/libc/dns/getresolvconf.c b/libc/dns/getresolvconf.c index 38b8b988..c6dafb9b 100644 --- a/libc/dns/getresolvconf.c +++ b/libc/dns/getresolvconf.c @@ -36,15 +36,15 @@ static struct ResolvConfInitialStaticMemory { * Returns singleton with DNS server address. */ const struct ResolvConf *getresolvconf(void) { + int rc; + FILE *f; struct ResolvConfInitialStaticMemory *init = &g_resolvconf_init; if (!g_resolvconf) { g_resolvconf = &init->rv; pushmov(&init->rv.nameservers.n, ARRAYLEN(init->nameservers)); init->rv.nameservers.p = init->nameservers; __cxa_atexit(freeresolvconf, &g_resolvconf, NULL); - int rc; if (!IsWindows()) { - FILE *f; if ((f = fopen("/etc/resolv.conf", "r"))) { rc = parseresolvconf(g_resolvconf, f); } else { diff --git a/libc/dns/parseresolvconf.c b/libc/dns/parseresolvconf.c index 5dda632b..6620b581 100644 --- a/libc/dns/parseresolvconf.c +++ b/libc/dns/parseresolvconf.c @@ -56,9 +56,7 @@ int parseresolvconf(struct ResolvConf *resolv, struct FILE *f) { if ((directive = strtok_r(line, " \t\r\n\v", &tok)) && (value = strtok_r(NULL, " \t\r\n\v", &tok))) { if ((strcmp(directive, "nameserver") == 0 && - inet_pton(AF_INET, value, &nameserver.sin_addr.s_addr) == 1) || - (strcmp(directive, "search") == 0 && strcmp(value, "local") == 0 && - (nameserver.sin_addr.s_addr = htonl(INADDR_LOOPBACK)))) { + inet_pton(AF_INET, value, &nameserver.sin_addr.s_addr) == 1)) { if (append(&resolv->nameservers, &nameserver) != -1) ++rc; } } diff --git a/libc/elf/elf.h b/libc/elf/elf.h index 97f5f040..f897a43d 100644 --- a/libc/elf/elf.h +++ b/libc/elf/elf.h @@ -1,5 +1,6 @@ #ifndef COSMOPOLITAN_LIBC_ELF_H_ #define COSMOPOLITAN_LIBC_ELF_H_ +#ifndef __STRICT_ANSI__ #include "libc/bits/safemacros.h" #include "libc/elf/def.h" #include "libc/elf/struct/ehdr.h" @@ -25,7 +26,7 @@ Elf64_Shdr *getelfsectionbyaddress(const Elf64_Ehdr *, size_t, void *); forceinline void checkelfaddress(const Elf64_Ehdr *elf, size_t mapsize, intptr_t addr, size_t addrsize) { -#if !(TRUSTWORTHY + ELF_TRUSTWORTHY + 0) +#if !(TRUSTWORTHY + ELF_TRUSTWORTHY + 0) || ELF_UNTRUSTWORTHY + 0 if (addr < (intptr_t)elf || addr + addrsize > (intptr_t)elf + mapsize) { abort(); } @@ -119,4 +120,5 @@ static inline const char *getelfsectionname(const Elf64_Ehdr *elf, COSMOPOLITAN_C_END_ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* !ANSI */ #endif /* COSMOPOLITAN_LIBC_ELF_H_ */ diff --git a/libc/elf/struct/sym.h b/libc/elf/struct/sym.h index 8b1baa0b..c33f625b 100644 --- a/libc/elf/struct/sym.h +++ b/libc/elf/struct/sym.h @@ -7,8 +7,9 @@ typedef struct Elf64_Sym { Elf64_Word st_name; /* ELF64_ST_TYPE(st_info) → STT_{NOTYPE,OBJECT,FUNC,SECTION,FILE,COMMON,...} * ELF64_ST_BIND(st_info) → STB_{LOCAL,GLOBAL,WEAK,...} */ - unsigned char st_info; - unsigned char st_other; + uint8_t st_info; + /* STV_{DEFAULT,INTERNAL,HIDDEN,PROTECTED} */ + uint8_t st_other; Elf64_Section st_shndx; Elf64_Addr st_value; Elf64_Xword st_size; diff --git a/libc/errno.h b/libc/errno.h index 16c36e0b..d37c319b 100644 --- a/libc/errno.h +++ b/libc/errno.h @@ -6,137 +6,138 @@ * @see libc/sysv/consts.sh for numbers */ -#define EPERM EPERM /* operation not permitted */ -#define ENOENT ENOENT /* no such file or directory */ -#define ESRCH ESRCH /* no such process */ -#define EINTR EINTR /* interrupted system call */ -#define EIO EIO /* input/output error */ -#define ENXIO ENXIO /* no such device or address */ -#define E2BIG E2BIG /* argument list too long */ -#define ENOEXEC ENOEXEC /* exec format error */ -#define EBADF EBADF /* bad file descriptor */ -#define ECHILD ECHILD /* no child processes */ -#define EAGAIN EAGAIN /* resource temporarily unavailable */ -#define ENOMEM ENOMEM /* not enough space */ -#define EACCES EACCES /* permission denied */ -#define EFAULT EFAULT /* bad address */ -#define ENOTBLK ENOTBLK /* block device required */ -#define EBUSY EBUSY /* device or resource busy */ -#define EEXIST EEXIST /* file exists */ -#define EXDEV EXDEV /* improper link */ -#define ENODEV ENODEV /* no such device */ -#define ENOTDIR ENOTDIR /* not a directory */ -#define EISDIR EISDIR /* is a directory */ -#define EINVAL EINVAL /* invalid argument */ -#define ENFILE ENFILE /* too many open files in system */ -#define EMFILE EMFILE /* too many open files */ -#define ENOTTY ENOTTY /* inappropriate I/O control op */ -#define ETXTBSY ETXTBSY /* text file busy */ -#define EFBIG EFBIG /* file too large */ -#define ENOSPC ENOSPC /* no space left on device */ -#define ESPIPE ESPIPE /* invalid seek */ -#define EROFS EROFS /* read-only filesystem */ -#define EMLINK EMLINK /* too many links */ -#define EPIPE EPIPE /* broken pipe */ -#define EDOM EDOM /* argument out of function domain */ -#define ERANGE ERANGE /* result too large */ -#define EDEADLK EDEADLK /* resource deadlock avoided */ -#define ENAMETOOLONG ENAMETOOLONG /* filename too long */ -#define ENOLCK ENOLCK /* no locks available */ -#define ENOSYS ENOSYS /* system call not implemented */ -#define ENOTEMPTY ENOTEMPTY /* directory not empty */ -#define ELOOP ELOOP /* too many levels of symbolic links */ -#define ENOMSG ENOMSG /* no message of the desired type */ -#define EIDRM EIDRM /* identifier removed */ -#define ECHRNG ECHRNG /* channel number out of range */ -#define EL2NSYNC EL2NSYNC /* level 2 not synchronized */ -#define EL3HLT EL3HLT /* level 3 halted */ -#define EL3RST EL3RST /* level 3 halted */ -#define ELNRNG ELNRNG /* link number out of range */ -#define EUNATCH EUNATCH /* protocol driver not attached */ -#define ENOCSI ENOCSI /* no csi structure available */ -#define EL2HLT EL2HLT /* level 2 halted */ -#define EBADE EBADE /* invalid exchange */ -#define EBADR EBADR /* invalid request descriptor */ -#define EXFULL EXFULL /* exchange full */ -#define ENOANO ENOANO /* no anode */ -#define EBADRQC EBADRQC /* invalid request code */ -#define EBADSLT EBADSLT /* invalid slot */ -#define ENOSTR ENOSTR /* no string */ -#define ENODATA ENODATA /* no data */ -#define ETIME ETIME /* timer expired */ -#define ENOSR ENOSR /* out of streams resources */ -#define ENONET ENONET /* no network */ -#define ENOPKG ENOPKG /* package not installed */ -#define EREMOTE EREMOTE /* object is remote */ -#define ENOLINK ENOLINK /* link severed */ -#define EADV EADV /* todo */ -#define ESRMNT ESRMNT /* todo */ -#define ECOMM ECOMM /* communication error on send */ -#define EPROTO EPROTO /* protocol error */ -#define EMULTIHOP EMULTIHOP /* multihop attempted */ -#define EDOTDOT EDOTDOT /* todo */ -#define EBADMSG EBADMSG /* bad message */ -#define EOVERFLOW EOVERFLOW /* value too large for type */ -#define ENOTUNIQ ENOTUNIQ /* name not unique on network */ -#define EBADFD EBADFD /* fd in bad *state* (cf. EBADF) */ -#define EREMCHG EREMCHG /* remote address changed */ -#define ELIBACC ELIBACC /* cannot access dso */ -#define ELIBBAD ELIBBAD /* corrupted shared library */ -#define ELIBSCN ELIBSCN /* a.out section corrupted */ -#define ELIBMAX ELIBMAX /* too many shared libraries */ -#define ELIBEXEC ELIBEXEC /* cannot exec a dso directly */ -#define EILSEQ EILSEQ /* invalid wide character */ -#define ERESTART ERESTART /* please restart syscall */ -#define ESTRPIPE ESTRPIPE /* streams pipe error */ -#define EUSERS EUSERS /* too many users */ -#define ENOTSOCK ENOTSOCK /* not a socket */ -#define EDESTADDRREQ EDESTADDRREQ /* dest address needed */ -#define EMSGSIZE EMSGSIZE /* message too long */ -#define EPROTOTYPE EPROTOTYPE /* protocol wrong for socket */ -#define ENOPROTOOPT ENOPROTOOPT /* protocol not available */ +#define EPERM EPERM /* operation not permitted */ +#define ENOENT ENOENT /* no such file or directory */ +#define ESRCH ESRCH /* no such process */ +#define EINTR EINTR /* interrupted system call */ +#define EIO EIO /* input/output error */ +#define ENXIO ENXIO /* no such device or address */ +#define E2BIG E2BIG /* argument list too long */ +#define ENOEXEC ENOEXEC /* exec format error */ +#define EBADF EBADF /* bad file descriptor */ +#define ECHILD ECHILD /* no child processes */ +#define EAGAIN EAGAIN /* resource temporarily unavailable */ +#define ENOMEM ENOMEM /* not enough space */ +#define EACCES EACCES /* permission denied */ +#define EFAULT EFAULT /* bad address */ +#define ENOTBLK ENOTBLK /* block device required */ +#define EBUSY EBUSY /* device or resource busy */ +#define EEXIST EEXIST /* file exists */ +#define EXDEV EXDEV /* improper link */ +#define ENODEV ENODEV /* no such device */ +#define ENOTDIR ENOTDIR /* not a directory */ +#define EISDIR EISDIR /* is a directory */ +#define EINVAL EINVAL /* invalid argument */ +#define ENFILE ENFILE /* too many open files in system */ +#define EMFILE EMFILE /* too many open files */ +#define ENOTTY ENOTTY /* inappropriate I/O control op */ +#define ETXTBSY ETXTBSY /* text file busy */ +#define EFBIG EFBIG /* file too large */ +#define ENOSPC ENOSPC /* no space left on device */ +#define ESPIPE ESPIPE /* invalid seek */ +#define EROFS EROFS /* read-only filesystem */ +#define EMLINK EMLINK /* too many links */ +#define EPIPE EPIPE /* broken pipe */ +#define EDOM EDOM /* argument out of function domain */ +#define ERANGE ERANGE /* result too large */ +#define EDEADLK EDEADLK /* resource deadlock avoided */ +#define ENAMETOOLONG ENAMETOOLONG /* filename too long */ +#define ENOLCK ENOLCK /* no locks available */ +#define ENOSYS ENOSYS /* system call not implemented */ +#define ENOTEMPTY ENOTEMPTY /* directory not empty */ +#define ELOOP ELOOP /* too many levels of symbolic links */ +#define ENOMSG ENOMSG /* no message of the desired type */ +#define EIDRM EIDRM /* identifier removed */ +#define ECHRNG ECHRNG /* channel number out of range */ +#define EL2NSYNC EL2NSYNC /* level 2 not synchronized */ +#define EL3HLT EL3HLT /* level 3 halted */ +#define EL3RST EL3RST /* level 3 halted */ +#define ELNRNG ELNRNG /* link number out of range */ +#define EUNATCH EUNATCH /* protocol driver not attached */ +#define ENOCSI ENOCSI /* no csi structure available */ +#define EL2HLT EL2HLT /* level 2 halted */ +#define EBADE EBADE /* invalid exchange */ +#define EBADR EBADR /* invalid request descriptor */ +#define EXFULL EXFULL /* exchange full */ +#define ENOANO ENOANO /* no anode */ +#define EBADRQC EBADRQC /* invalid request code */ +#define EBADSLT EBADSLT /* invalid slot */ +#define ENOSTR ENOSTR /* no string */ +#define ENODATA ENODATA /* no data */ +#define ETIME ETIME /* timer expired */ +#define ENOSR ENOSR /* out of streams resources */ +#define ENONET ENONET /* no network */ +#define ENOPKG ENOPKG /* package not installed */ +#define EREMOTE EREMOTE /* object is remote */ +#define ENOLINK ENOLINK /* link severed */ +#define EADV EADV /* todo */ +#define ESRMNT ESRMNT /* todo */ +#define ECOMM ECOMM /* communication error on send */ +#define EPROTO EPROTO /* protocol error */ +#define EMULTIHOP EMULTIHOP /* multihop attempted */ +#define EDOTDOT EDOTDOT /* todo */ +#define EBADMSG EBADMSG /* bad message */ +#define EOVERFLOW EOVERFLOW /* value too large for type */ +#define ENOTUNIQ ENOTUNIQ /* name not unique on network */ +#define EBADFD EBADFD /* fd in bad *state* (cf. EBADF) */ +#define EREMCHG EREMCHG /* remote address changed */ +#define ELIBACC ELIBACC /* cannot access dso */ +#define ELIBBAD ELIBBAD /* corrupted shared library */ +#define ELIBSCN ELIBSCN /* a.out section corrupted */ +#define ELIBMAX ELIBMAX /* too many shared libraries */ +#define ELIBEXEC ELIBEXEC /* cannot exec a dso directly */ +#define EILSEQ EILSEQ /* invalid wide character */ +#define ERESTART ERESTART /* please restart syscall */ +#define ESTRPIPE ESTRPIPE /* streams pipe error */ +#define EUSERS EUSERS /* too many users */ +#define ENOTSOCK ENOTSOCK /* not a socket */ +#define EDESTADDRREQ EDESTADDRREQ /* dest address needed */ +#define EMSGSIZE EMSGSIZE /* message too long */ +#define EPROTOTYPE EPROTOTYPE /* protocol wrong for socket */ +#define ENOPROTOOPT ENOPROTOOPT /* protocol not available */ #define EPROTONOSUPPORT EPROTONOSUPPORT /* protocol not supported */ #define ESOCKTNOSUPPORT ESOCKTNOSUPPORT /* socket type not supported */ -#define EOPNOTSUPP EOPNOTSUPP /* operation not supported on socket */ -#define EPFNOSUPPORT EPFNOSUPPORT /* protocol family not supported */ -#define EAFNOSUPPORT EAFNOSUPPORT /* address family not supported */ -#define EADDRINUSE EADDRINUSE /* address already in use */ -#define EADDRNOTAVAIL EADDRNOTAVAIL /* address not available */ -#define ENETDOWN ENETDOWN /* network is down */ -#define ENETUNREACH ENETUNREACH /* network unreachable */ -#define ENETRESET ENETRESET /* connection aborted by network */ -#define ECONNABORTED ECONNABORTED /* connection aborted */ -#define ECONNRESET ECONNRESET /* connection reset */ -#define ENOBUFS ENOBUFS /* no buffer space available */ -#define EISCONN EISCONN /* socket is connected */ -#define ENOTCONN ENOTCONN /* the socket is not connected */ -#define ESHUTDOWN ESHUTDOWN /* no send after endpoint shutdown */ -#define ETOOMANYREFS ETOOMANYREFS /* too many refs */ -#define ETIMEDOUT ETIMEDOUT /* connection timed out */ -#define ECONNREFUSED ECONNREFUSED /* connection refused */ -#define EHOSTDOWN EHOSTDOWN /* host is down */ -#define EHOSTUNREACH EHOSTUNREACH /* host is unreachable */ -#define EALREADY EALREADY /* connection already in progress */ -#define EINPROGRESS EINPROGRESS /* operation in progress */ -#define ESTALE ESTALE /* stale file handle */ -#define EUCLEAN EUCLEAN /* structure needs cleaning */ -#define ENOTNAM ENOTNAM /* todo */ -#define ENAVAIL ENAVAIL /* todo */ -#define EISNAM EISNAM /* is a named type file */ -#define EREMOTEIO EREMOTEIO /* remote i/o error */ -#define EDQUOT EDQUOT /* disk quota exceeded */ -#define ENOMEDIUM ENOMEDIUM /* no medium found */ -#define EMEDIUMTYPE EMEDIUMTYPE /* wrong medium type */ -#define ECANCELED ECANCELED /* operation canceled */ -#define ENOKEY ENOKEY /* required key not available */ -#define EKEYEXPIRED EKEYEXPIRED /* key has expired */ -#define EKEYREVOKED EKEYREVOKED /* key has been revoked */ -#define EKEYREJECTED EKEYREJECTED /* key was rejected by service */ -#define EOWNERDEAD EOWNERDEAD /* owner died */ +#define EOPNOTSUPP EOPNOTSUPP /* operation not supported on socket */ +#define EPFNOSUPPORT EPFNOSUPPORT /* protocol family not supported */ +#define EAFNOSUPPORT EAFNOSUPPORT /* address family not supported */ +#define EADDRINUSE EADDRINUSE /* address already in use */ +#define EADDRNOTAVAIL EADDRNOTAVAIL /* address not available */ +#define ENETDOWN ENETDOWN /* network is down */ +#define ENETUNREACH ENETUNREACH /* network unreachable */ +#define ENETRESET ENETRESET /* connection aborted by network */ +#define ECONNABORTED ECONNABORTED /* connection aborted */ +#define ECONNRESET ECONNRESET /* connection reset */ +#define ENOBUFS ENOBUFS /* no buffer space available */ +#define EISCONN EISCONN /* socket is connected */ +#define ENOTCONN ENOTCONN /* the socket is not connected */ +#define ESHUTDOWN ESHUTDOWN /* no send after endpoint shutdown */ +#define ETOOMANYREFS ETOOMANYREFS /* too many refs */ +#define ETIMEDOUT ETIMEDOUT /* connection timed out */ +#define ECONNREFUSED ECONNREFUSED /* connection refused */ +#define EHOSTDOWN EHOSTDOWN /* host is down */ +#define EHOSTUNREACH EHOSTUNREACH /* host is unreachable */ +#define EALREADY EALREADY /* connection already in progress */ +#define EINPROGRESS EINPROGRESS /* operation in progress */ +#define ESTALE ESTALE /* stale file handle */ +#define EUCLEAN EUCLEAN /* structure needs cleaning */ +#define ENOTNAM ENOTNAM /* todo */ +#define ENAVAIL ENAVAIL /* todo */ +#define EISNAM EISNAM /* is a named type file */ +#define EREMOTEIO EREMOTEIO /* remote i/o error */ +#define EDQUOT EDQUOT /* disk quota exceeded */ +#define ENOMEDIUM ENOMEDIUM /* no medium found */ +#define EMEDIUMTYPE EMEDIUMTYPE /* wrong medium type */ +#define ECANCELED ECANCELED /* operation canceled */ +#define ENOKEY ENOKEY /* required key not available */ +#define EKEYEXPIRED EKEYEXPIRED /* key has expired */ +#define EKEYREVOKED EKEYREVOKED /* key has been revoked */ +#define EKEYREJECTED EKEYREJECTED /* key was rejected by service */ +#define EOWNERDEAD EOWNERDEAD /* owner died */ #define ENOTRECOVERABLE ENOTRECOVERABLE /* state not recoverable */ -#define ERFKILL ERFKILL /* can't op b/c RF-kill */ -#define EHWPOISON EHWPOISON /* mempage has h/w error */ -#define EWOULDBLOCK EAGAIN /* poll fd and try again */ +#define ERFKILL ERFKILL /* can't op b/c RF-kill */ +#define EHWPOISON EHWPOISON /* mempage has h/w error */ +#define EWOULDBLOCK EAGAIN /* poll fd and try again */ +#define ENOTSUP ENOTSUP #if !(__ASSEMBLER__ + __LINKER__ + 0) COSMOPOLITAN_C_START_ @@ -273,6 +274,7 @@ hidden extern const long EOWNERDEAD; hidden extern const long ENOTRECOVERABLE; hidden extern const long ERFKILL; hidden extern const long EHWPOISON; +hidden extern const long ENOTSUP; COSMOPOLITAN_C_END_ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ diff --git a/libc/fmt/bing.h b/libc/fmt/bing.h index 227d6ec7..a3cf5ccd 100644 --- a/libc/fmt/bing.h +++ b/libc/fmt/bing.h @@ -1,7 +1,7 @@ #ifndef COSMOPOLITAN_LIBC_FMT_BING_H_ #define COSMOPOLITAN_LIBC_FMT_BING_H_ #if !(__ASSEMBLER__ + __LINKER__ + 0) -COSMOPOLITAN_C_START_ +#ifndef __cplusplus int bing(int, int) nosideeffect; int unbing(int) nosideeffect; @@ -11,6 +11,6 @@ void *unhexbuf(void *, size_t, const char *); void *unhexstr(const char *) mallocesque; short *bingblit(int ys, int xs, unsigned char[ys][xs], int, int); -COSMOPOLITAN_C_END_ +#endif /* __cplusplus */ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ #endif /* COSMOPOLITAN_LIBC_FMT_BING_H_ */ diff --git a/libc/fmt/fmt.mk b/libc/fmt/fmt.mk index 47407d56..53f5d164 100644 --- a/libc/fmt/fmt.mk +++ b/libc/fmt/fmt.mk @@ -39,7 +39,8 @@ LIBC_FMT_A_DIRECTDEPS = \ LIBC_TINYMATH \ LIBC_NEXGEN32E \ LIBC_NT_KERNELBASE \ - LIBC_SYSV + LIBC_SYSV \ + THIRD_PARTY_COMPILER_RT LIBC_FMT_A_DEPS := \ $(call uniq,$(foreach x,$(LIBC_FMT_A_DIRECTDEPS),$($(x)))) diff --git a/libc/fmt/paland.inc b/libc/fmt/paland.inc index e80b474b..0f78f28b 100644 --- a/libc/fmt/paland.inc +++ b/libc/fmt/paland.inc @@ -35,20 +35,6 @@ info@paland.com\""); #include "libc/str/internal.h" #include "libc/sysv/errfuns.h" -/* 'ntoa' conversion buffer size, this must be big enough to hold one - converted numeric number including padded zeros (dynamically created - on stack) default: 64 byte */ -#ifndef PRINTF_NTOA_BUFFER_SIZE -#define PRINTF_NTOA_BUFFER_SIZE 64 -#endif - -/* 'ftoa' conversion buffer size, this must be big enough to hold one - converted float number including padded zeros (dynamically created on - stack) default: 32 byte */ -#ifndef PRINTF_FTOA_BUFFER_SIZE -#define PRINTF_FTOA_BUFFER_SIZE 64 -#endif - #define FLAGS_ZEROPAD (1U << 0U) #define FLAGS_LEFT (1U << 1U) #define FLAGS_PLUS (1U << 2U) diff --git a/libc/fmt/palandftoa.c b/libc/fmt/palandftoa.c index 04b46a06..238f7e5c 100644 --- a/libc/fmt/palandftoa.c +++ b/libc/fmt/palandftoa.c @@ -59,7 +59,7 @@ int ftoa(int out(int, void *), void *arg, long double value, unsigned long prec, buf[2] = 'n'; buf[3] = '\0'; len += 3; - } else if (isinf(value) || fabsl(value) > 0x7ffffffffffffffful) { + } else if (isinf(value) || (value && ilogbl(fabsl(value)) > 63)) { buf[0] = 'f'; buf[1] = 'n'; buf[2] = 'i'; @@ -90,11 +90,11 @@ int ftoa(int out(int, void *), void *arg, long double value, unsigned long prec, ++whole; } } else if (diff < 0.5) { - } else if ((frac == 0U) || (frac & 1U)) { + } else if (!frac || (frac & 1)) { ++frac; /* if halfway, round up if odd OR if last digit is 0 */ } - if (prec == 0U) { + if (!prec) { diff = fabsl(value) - whole; if ((!(diff < 0.5) || (diff > 0.5)) && (whole & 1)) { /* exactly 0.5 and ODD, then round up */ @@ -112,7 +112,7 @@ int ftoa(int out(int, void *), void *arg, long double value, unsigned long prec, } } /* add extra 0s */ - while ((len < PRINTF_FTOA_BUFFER_SIZE) && (count-- > 0U)) { + while ((len < PRINTF_FTOA_BUFFER_SIZE) && (count-- > 0)) { buf[len++] = '0'; } if (len < PRINTF_FTOA_BUFFER_SIZE) { diff --git a/libc/fmt/palandntoa.c b/libc/fmt/palandntoa.c index 9d636e59..093a6ca4 100644 --- a/libc/fmt/palandntoa.c +++ b/libc/fmt/palandntoa.c @@ -29,9 +29,11 @@ #include "libc/fmt/paland.inc" #include "libc/fmt/palandprintf.h" +uintmax_t __udivmodti4(uintmax_t, uintmax_t, uintmax_t *); + static int ntoaformat(int out(int, void *), void *arg, char *buf, unsigned len, bool negative, unsigned log2base, unsigned prec, - unsigned width, unsigned flags) { + unsigned width, unsigned char flags) { unsigned i, idx; idx = 0; @@ -101,9 +103,10 @@ static int ntoaformat(int out(int, void *), void *arg, char *buf, unsigned len, return 0; } -static int ntoa2(int out(int, void *), void *arg, uintmax_t value, bool neg, - unsigned log2base, unsigned prec, unsigned width, - unsigned flags, const char *alphabet) { +int ntoa2(int out(int, void *), void *arg, uintmax_t value, bool neg, + unsigned log2base, unsigned prec, unsigned width, unsigned flags, + const char *alphabet) { + uintmax_t remainder; unsigned len, count, digit; char buf[PRINTF_NTOA_BUFFER_SIZE]; len = 0; @@ -112,12 +115,13 @@ static int ntoa2(int out(int, void *), void *arg, uintmax_t value, bool neg, count = 0; do { assert(len < PRINTF_NTOA_BUFFER_SIZE); - if (log2base) { + if (!log2base) { + value = __udivmodti4(value, 10, &remainder); + digit = remainder; + } else { digit = value; digit &= (1u << log2base) - 1; value >>= log2base; - } else { - value = div10(value, &digit); } if ((flags & FLAGS_GROUPING) && count == 3) { buf[len++] = ','; @@ -132,12 +136,12 @@ static int ntoa2(int out(int, void *), void *arg, uintmax_t value, bool neg, } int ntoa(int out(int, void *), void *arg, va_list va, unsigned char signbit, - unsigned long log2base, unsigned long precision, unsigned long width, - unsigned long flags, const char *alphabet) { - bool negative; + unsigned long log2base, unsigned long prec, unsigned long width, + unsigned char flags, const char *lang) { + bool neg; uintmax_t value, sign; - /* ignore '0' flag when precision is given */ + /* ignore '0' flag when prec is given */ if (flags & FLAGS_PRECISION) { flags &= ~FLAGS_ZEROPAD; } @@ -153,24 +157,22 @@ int ntoa(int out(int, void *), void *arg, va_list va, unsigned char signbit, value = va_arg(va, uint64_t); } - negative = false; - sign = (uintmax_t)1 << signbit; - if (value > (sign | (sign - 1))) erange(); + neg = 0; + sign = 1; + sign <<= signbit; + value &= sign | (sign - 1); if (flags & FLAGS_ISSIGNED) { if (value != sign) { if (value & sign) { value = ~value + 1; - negative = true; + value &= sign | (sign - 1); + neg = 1; } value &= sign - 1; + } else { + neg = 1; } - } else { - value &= sign | (sign - 1); } - if (ntoa2(out, arg, value, negative, log2base, precision, width, flags, - alphabet) == -1) { - return -1; - } - return 0; + return ntoa2(out, arg, value, neg, log2base, prec, width, flags, lang); } diff --git a/libc/fmt/palandprintf.c b/libc/fmt/palandprintf.c index 407658c1..ab1d5736 100644 --- a/libc/fmt/palandprintf.c +++ b/libc/fmt/palandprintf.c @@ -37,6 +37,7 @@ └─────────────────────────────────────────────────────────────────────────────*/ #include "libc/assert.h" #include "libc/bits/bits.h" +#include "libc/bits/weaken.h" #include "libc/conv/conv.h" #include "libc/escape/escape.h" #include "libc/fmt/fmt.h" @@ -47,10 +48,12 @@ #include "libc/str/str.h" #include "libc/sysv/errfuns.h" -static unsigned ppatoi(const char **str) { - unsigned i; - i = 0; - while (isdigit(**str)) i = i * 10u + (unsigned)(*((*str)++) - '0'); +static int ppatoi(const char **str) { + int i; + for (i = 0; '0' <= **str && **str <= '9'; ++*str) { + i *= 10; + i += **str - '0'; + } return i; } @@ -104,9 +107,14 @@ static unsigned ppatoi(const char **str) { * @see printf() for wordier documentation */ hidden int palandprintf(void *fn, void *arg, const char *format, va_list va) { + void *p; + char qchar; + long double ldbl; + wchar_t charbuf[3]; + const char *alphabet; int (*out)(int, void *); - unsigned flags, width, precision; - int lasterr; + unsigned char signbit, log2base; + int w, rc, flags, width, lasterr, precision; lasterr = errno; out = fn ? fn : (int (*)(int, void *))missingno; @@ -161,7 +169,7 @@ hidden int palandprintf(void *fn, void *arg, const char *format, va_list va) { if (isdigit(*format)) { width = ppatoi(&format); } else if (*format == '*') { - const int w = va_arg(va, int); + w = va_arg(va, int); if (w < 0) { flags |= FLAGS_LEFT; /* reverse padding */ width = -w; @@ -179,14 +187,16 @@ hidden int palandprintf(void *fn, void *arg, const char *format, va_list va) { if (isdigit(*format)) { precision = ppatoi(&format); } else if (*format == '*') { - const int prec = (int)va_arg(va, int); - precision = prec > 0 ? prec : 0; + precision = va_arg(va, int); format++; } } + if (precision < 0) { + precision = 0; + } /* evaluate length field */ - unsigned char signbit = 31; + signbit = 31; switch (*format) { case 'j': /* intmax_t */ format++; @@ -196,8 +206,8 @@ hidden int palandprintf(void *fn, void *arg, const char *format, va_list va) { if (format[1] == 'l') format++; /* fallthrough */ case 't': /* ptrdiff_t */ - case 'Z': /* size_t */ case 'z': /* size_t */ + case 'Z': /* size_t */ case 'L': /* long double */ format++; signbit = 63; @@ -216,12 +226,9 @@ hidden int palandprintf(void *fn, void *arg, const char *format, va_list va) { } /* evaluate specifier */ - void *p; - const char *alphabet = "0123456789abcdef"; - unsigned log2base = 0; - wchar_t charbuf[3]; - int rc; - char qchar = '"'; + alphabet = "0123456789abcdef"; + log2base = 0; + qchar = '"'; switch (*format++) { case 'p': flags |= FLAGS_ZEROPAD; @@ -256,23 +263,21 @@ hidden int palandprintf(void *fn, void *arg, const char *format, va_list va) { } case 'f': - case 'F': { - long double value; + case 'F': if (signbit == 63) { - value = va_arg(va, long double); + ldbl = va_arg(va, long double); } else { - value = va_arg(va, double); + ldbl = va_arg(va, double); } - if (weaken(ftoa)(out, arg, value, precision, width, flags) == -1) { + if (weaken(ftoa)(out, arg, ldbl, precision, width, flags) == -1) { return -1; } break; - } case 'c': qchar = '\''; p = charbuf; - charbuf[0] = (wchar_t)va_arg(va, int); /* @assume little endian */ + charbuf[0] = va_arg(va, int); charbuf[1] = L'\0'; goto showstr; diff --git a/libc/fmt/palandprintf.h b/libc/fmt/palandprintf.h index ac202e01..7c65f304 100644 --- a/libc/fmt/palandprintf.h +++ b/libc/fmt/palandprintf.h @@ -1,5 +1,9 @@ #ifndef COSMOPOLITAN_LIBC_FMT_PALANDPRINTF_H_ #define COSMOPOLITAN_LIBC_FMT_PALANDPRINTF_H_ + +#define PRINTF_NTOA_BUFFER_SIZE 144 +#define PRINTF_FTOA_BUFFER_SIZE 64 + #if !(__ASSEMBLER__ + __LINKER__ + 0) COSMOPOLITAN_C_START_ @@ -9,7 +13,7 @@ int ftoa(int(int, void *), void *, long double, unsigned long, unsigned long, int stoa(int(int, void *), void *, void *, unsigned long, unsigned long, unsigned long, unsigned char, unsigned char) hidden; int ntoa(int(int, void *), void *, va_list, unsigned char, unsigned long, - unsigned long, unsigned long, unsigned long, const char *) hidden; + unsigned long, unsigned long, unsigned char, const char *) hidden; COSMOPOLITAN_C_END_ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ diff --git a/libc/fmt/sscanf.c b/libc/fmt/sscanf.c index eac689bc..06323409 100644 --- a/libc/fmt/sscanf.c +++ b/libc/fmt/sscanf.c @@ -18,7 +18,7 @@ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/dce.h" -#include "libc/fmt/vsscanf.h" +#include "libc/fmt/fmt.h" /** * String decoder. @@ -28,11 +28,7 @@ int(sscanf)(const char *str, const char *fmt, ...) { int rc; va_list va; va_start(va, fmt); - if (IsTiny()) { - rc = (vsscanf)(str, fmt, va); - } else { - rc = __vsscanf(str, fmt, va, __vcscanf); - } + rc = (vsscanf)(str, fmt, va); va_end(va); return rc; } diff --git a/libc/fmt/stoa.c b/libc/fmt/stoa.c index 2ce5b5c2..7b99132c 100644 --- a/libc/fmt/stoa.c +++ b/libc/fmt/stoa.c @@ -19,6 +19,7 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/bits/bits.h" #include "libc/bits/safemacros.h" +#include "libc/bits/weaken.h" #include "libc/escape/escape.h" #include "libc/fmt/fmt.h" #include "libc/fmt/paland.inc" @@ -176,6 +177,7 @@ int stoa(int out(int, void *), void *arg, void *data, unsigned long flags, if (w <= width && (flags & FLAGS_LEFT)) { if (spacepad(out, arg, width - w) == -1) return -1; } + if (!(flags & FLAGS_NOQUOTE) && (flags & FLAGS_REPR)) { if (out(qchar, arg) == -1) return -1; } diff --git a/libc/fmt/strerror.c b/libc/fmt/strerror.c index cce88772..7077c5d9 100644 --- a/libc/fmt/strerror.c +++ b/libc/fmt/strerror.c @@ -26,5 +26,5 @@ char *strerror(int err) { alignas(1) static char buf[512]; strerror_r(err, buf, sizeof(buf)); - return &buf[0]; + return buf; } diff --git a/libc/fmt/strerror_r.c b/libc/fmt/strerror_r.c index b2e6f998..b5013f2c 100644 --- a/libc/fmt/strerror_r.c +++ b/libc/fmt/strerror_r.c @@ -27,14 +27,15 @@ #include "libc/nt/runtime.h" #include "libc/str/str.h" -static const char *geterrname(int code) { +const char *geterrname(int code) { extern const char kErrnoNames[]; const long *e; - const char *s; - size_t i; - for (i = 0, e = &E2BIG; e <= &EXFULL; ++e, ++i) { - if (code == *e && (s = indexdoublenulstring(&kErrnoNames[0], i))) { - return s; + size_t i, n; + e = &E2BIG; + n = &EXFULL + 1 - e; + for (i = 0; i < n; ++i) { + if (code == e[i]) { + return indexdoublenulstring(kErrnoNames, i); } } return NULL; @@ -46,8 +47,13 @@ static const char *geterrname(int code) { */ int strerror_r(int err, char *buf, size_t size) { const char *s; - s = (err == -1 || IsTiny()) ? "?" : firstnonnull(geterrname(err), "?"); + if (err == -1 || IsTiny()) { + s = "?"; + } else { + s = firstnonnull(geterrname(err), "?"); + } if (!SupportsWindows()) { + DebugBreak(); snprintf(buf, size, "E%s[%d]", s, err); } else { char16_t buf16[100]; diff --git a/libc/fmt/vcscanf.c b/libc/fmt/vcscanf.c index f918a217..52760ad7 100644 --- a/libc/fmt/vcscanf.c +++ b/libc/fmt/vcscanf.c @@ -17,13 +17,276 @@ │ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/bits/weaken.h" +#include "libc/conv/conv.h" #include "libc/fmt/fmt.h" -#include "libc/fmt/vcscanf.h" +#include "libc/mem/mem.h" +#include "libc/runtime/runtime.h" +#include "libc/str/str.h" +#include "libc/str/tpdecodecb.h" +#include "libc/sysv/errfuns.h" /** - * Linkable callback-driven string / file / stream decoder. - * @see libc/fmt/vcscanf.h (for docs and implementation) + * String / file / stream decoder. + * + * This scanf implementation is able to tokenize strings containing + * 8-bit through 128-bit integers (with validation), floating point + * numbers, etc. It can also be used to convert UTF-8 to UTF-16/32. + * + * - `%d` parses integer + * - `%ms` parses string allocating buffer assigning pointer + * + * @param callback supplies UTF-8 characters using -1 sentinel + * @param fmt is a computer program embedded inside a c string, written + * in a domain-specific programming language that, by design, lacks + * Turing-completeness + * @param va points to the variadic argument state + * @see libc/fmt/pflink.h (dynamic memory is not a requirement) */ -int(vcscanf)(int callback(void *), void *arg, const char *fmt, va_list ap) { - return __vcscanf(callback, arg, fmt, ap); +int vcscanf(int callback(void *), void *arg, const char *fmt, va_list va) { + struct FreeMe { + struct FreeMe *next; + void *ptr; + } *freeme = NULL; + const unsigned char *p = (const unsigned char *)fmt; + unsigned i = 0; + int items = 0; + int c = callback(arg); + while (c != -1) { + switch (p[i++]) { + case '\0': + return items; + case ' ': + case '\t': + case '\n': + case '\r': + case '\v': + while (isspace(c)) c = callback(arg); + break; + case '%': { + uintmax_t number; + void *buf; + size_t bufsize; + unsigned width = 0; + unsigned char bits = 32; + unsigned char charbytes = sizeof(char); + unsigned char diglet; + unsigned char base; + unsigned char prefix; + bool rawmode = false; + bool issigned = false; + bool ismalloc = false; + bool isneg = false; + bool thousands = false; + bool discard = false; + for (;;) { + switch (p[i++]) { + case '%': /* %% → % */ + goto NonDirectiveCharacter; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + width *= 10; + width += p[i - 1] - '0'; + break; + case '*': + discard = true; + break; + case 'm': + ismalloc = true; + break; + case 'c': + rawmode = true; + if (!width) width = 1; + /* εpsilon transition */ + case 's': + goto DecodeString; + case '\'': + thousands = true; + break; + case 'j': /* 128-bit */ + bits = sizeof(intmax_t) * 8; + break; + case 'l': /* long */ + case 'L': /* loooong */ + charbytes = sizeof(wchar_t); + /* fallthrough */ + case 't': /* ptrdiff_t */ + case 'Z': /* size_t */ + case 'z': /* size_t */ + bits = 64; + break; + case 'h': /* short and char */ + charbytes = sizeof(char16_t); + bits >>= 1; + break; + case 'b': /* binary */ + base = 2; + prefix = 'b'; + goto ConsumeBasePrefix; + case 'p': /* pointer (NexGen32e) */ + bits = 48; + /* fallthrough */ + case 'x': + case 'X': /* hexadecimal */ + base = 16; + prefix = 'x'; + goto ConsumeBasePrefix; + case 'o': /* octal */ + base = 8; + goto DecodeNumber; + case 'd': /* decimal */ + case 'n': /* TODO(jart): flexidecimal */ + issigned = true; + if (c == '+' || (isneg = c == '-')) { + c = callback(arg); + } + /* εpsilon transition */ + case 'u': + base = 10; + goto DecodeNumber; + default: + items = einval(); + goto Done; + } + } + ConsumeBasePrefix: + if (c == '0') { + c = callback(arg); + if (c == prefix || c == prefix + ('a' - 'A')) { + c = callback(arg); + } + } + DecodeNumber: + if (c != -1) { + number = 0; + do { + diglet = kBase36[(unsigned char)c]; + if (1 <= diglet && diglet <= base) { + number *= base; + number += diglet - 1; + } else if (thousands && diglet == ',') { + /* ignore */ + } else { + break; + } + } while ((c = callback(arg)) != -1); + if (!discard) { + uintmax_t bane = (uintmax_t)1 << (bits - 1); + if (!(number & ~((bane - 1) | (issigned ? 0 : bane))) || + (issigned && number == bane /* two's complement bane */)) { + ++items; + } else { + items = erange(); + goto Done; + } + if (issigned && isneg) { + number = ~number + 1; + } + void *out = va_arg(va, void *); + switch (bits) { + case sizeof(uintmax_t) * CHAR_BIT: + *(uintmax_t *)out = number; + break; + case 48: + case 64: + *(uint64_t *)out = (uint64_t)number; + break; + case 32: + *(uint32_t *)out = (uint32_t)number; + break; + case 16: + *(uint16_t *)out = (uint16_t)number; + break; + case 8: + default: + *(uint8_t *)out = (uint8_t)number; + break; + } + } + } + continue; + DecodeString: + bufsize = !width ? 32 : rawmode ? width : width + 1; + if (discard) { + buf = NULL; + } else if (ismalloc) { + buf = weaken(malloc)(bufsize * charbytes); + struct FreeMe *entry; + if (buf && (entry = weaken(calloc)(1, sizeof(struct FreeMe)))) { + entry->ptr = buf; + entry->next = freeme; + freeme = entry; + } + } else { + buf = va_arg(va, void *); + } + if (buf) { + size_t j = 0; + for (;;) { + if (ismalloc && !width && j + 2 + 1 >= bufsize && + !weaken(grow)(&buf, &bufsize, charbytes, 0)) { + width = bufsize - 1; + } + if (c != -1 && j + !rawmode < bufsize && (rawmode || !isspace(c))) { + if (charbytes == 1) { + ((unsigned char *)buf)[j++] = (unsigned char)c; + c = callback(arg); + } else if (tpdecodecb((wint_t *)&c, c, (void *)callback, arg) != + -1) { + if (charbytes == sizeof(char16_t)) { + j += abs(pututf16(&((char16_t *)buf)[j], bufsize - j - 1, c, + false)); + } else { + ((wchar_t *)buf)[j++] = (wchar_t)c; + } + c = callback(arg); + } + } else { + if (!rawmode && j < bufsize) { + if (charbytes == sizeof(char)) { + ((unsigned char *)buf)[j] = '\0'; + } else if (charbytes == sizeof(char16_t)) { + ((char16_t *)buf)[j] = u'\0'; + } else if (charbytes == sizeof(wchar_t)) { + ((wchar_t *)buf)[j] = L'\0'; + } + } + break; + } + } + ++items; + if (ismalloc) { + *va_arg(va, char **) = buf; + } + } else { + do { + if (isspace(c)) break; + } while ((c = callback(arg)) != -1); + } + break; + } + default: + NonDirectiveCharacter: + c = (c == p[i - 1]) ? callback(arg) : -1; + break; + } + } +Done: + while (freeme) { + struct FreeMe *entry = freeme; + freeme = entry->next; + if (items == -1) { + weaken(free_s)((void **)&entry->ptr); + } + weaken(free_s)((void **)&entry); + } + return items; } diff --git a/libc/fmt/vcscanf.h b/libc/fmt/vcscanf.h deleted file mode 100644 index 23209e05..00000000 --- a/libc/fmt/vcscanf.h +++ /dev/null @@ -1,297 +0,0 @@ -/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ -│vi: set net ft=c ts=8 sts=2 sw=2 fenc=utf-8 :vi│ -╞══════════════════════════════════════════════════════════════════════════════╡ -│ Copyright 2020 Justine Alexandra Roberts Tunney │ -│ │ -│ This program is free software; you can redistribute it and/or modify │ -│ it under the terms of the GNU General Public License as published by │ -│ the Free Software Foundation; version 2 of the License. │ -│ │ -│ This program is distributed in the hope that it will be useful, but │ -│ WITHOUT ANY WARRANTY; without even the implied warranty of │ -│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ -│ General Public License for more details. │ -│ │ -│ You should have received a copy of the GNU General Public License │ -│ along with this program; if not, write to the Free Software │ -│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ -│ 02110-1301 USA │ -╚─────────────────────────────────────────────────────────────────────────────*/ -#ifndef COSMOPOLITAN_LIBC_FMT_VCSSCANF_H_ -#define COSMOPOLITAN_LIBC_FMT_VCSSCANF_H_ -#include "libc/conv/conv.h" -#include "libc/mem/mem.h" -#include "libc/runtime/runtime.h" -#include "libc/str/str.h" -#include "libc/str/tpdecodecb.h" -#include "libc/sysv/errfuns.h" -#if !(__ASSEMBLER__ + __LINKER__ + 0) - -/** - * String / file / stream decoder. - * - * This scanf implementation is able to tokenize strings containing - * 8-bit through 128-bit integers (with validation), floating point - * numbers, etc. It can also be used to convert UTF-8 to UTF-16/32. - * - * - `%d` parses integer - * - `%ms` parses string allocating buffer assigning pointer - * - * @param callback supplies UTF-8 characters using -1 sentinel - * @param fmt is a computer program embedded inside a c string, written - * in a domain-specific programming language that, by design, lacks - * Turing-completeness - * @param va points to the variadic argument state - * @see libc/fmt/pflink.h (dynamic memory is not a requirement) - */ -forceinline int __vcscanf(int callback(void *), void *arg, const char *fmt, - va_list va) { - struct FreeMe { - struct FreeMe *next; - void *ptr; - } *freeme = NULL; - const unsigned char *p = (const unsigned char *)fmt; - unsigned i = 0; - int items = 0; - int c = callback(arg); - while (c != -1) { - switch (p[i++]) { - case '\0': - return items; - case ' ': - case '\t': - case '\n': - case '\r': - case '\v': - while (isspace(c)) c = callback(arg); - break; - case '%': { - uintmax_t number; - void *buf; - size_t bufsize; - unsigned width = 0; - unsigned char bits = 32; - unsigned char charbytes = sizeof(char); - unsigned char diglet; - unsigned char base; - unsigned char prefix; - bool rawmode = false; - bool issigned = false; - bool ismalloc = false; - bool isneg = false; - bool thousands = false; - bool discard = false; - for (;;) { - switch (p[i++]) { - case '%': /* %% → % */ - goto NonDirectiveCharacter; - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - width *= 10; - width += p[i - 1] - '0'; - break; - case '*': - discard = true; - break; - case 'm': - ismalloc = true; - break; - case 'c': - rawmode = true; - if (!width) width = 1; - /* εpsilon transition */ - case 's': - goto DecodeString; - case '\'': - thousands = true; - break; - case 'j': /* 128-bit */ - bits = sizeof(intmax_t) * 8; - break; - case 'l': /* long */ - case 'L': /* loooong */ - charbytes = sizeof(wchar_t); - /* fallthrough */ - case 't': /* ptrdiff_t */ - case 'Z': /* size_t */ - case 'z': /* size_t */ - bits = 64; - break; - case 'h': /* short and char */ - charbytes = sizeof(char16_t); - bits >>= 1; - break; - case 'b': /* binary */ - base = 2; - prefix = 'b'; - goto ConsumeBasePrefix; - case 'p': /* pointer (NexGen32e) */ - bits = 48; - /* fallthrough */ - case 'x': - case 'X': /* hexadecimal */ - base = 16; - prefix = 'x'; - goto ConsumeBasePrefix; - case 'o': /* octal */ - base = 8; - goto DecodeNumber; - case 'd': /* decimal */ - case 'n': /* TODO(jart): flexidecimal */ - issigned = true; - if (c == '+' || (isneg = c == '-')) { - c = callback(arg); - } - /* εpsilon transition */ - case 'u': - base = 10; - goto DecodeNumber; - default: - items = einval(); - goto Done; - } - } - ConsumeBasePrefix: - if (c == '0') { - c = callback(arg); - if (c == prefix || c == prefix + ('a' - 'A')) { - c = callback(arg); - } - } - DecodeNumber: - if (c != -1) { - number = 0; - do { - diglet = kBase36[(unsigned char)c]; - if (1 <= diglet && diglet <= base) { - number *= base; - number += diglet - 1; - } else if (thousands && diglet == ',') { - /* ignore */ - } else { - break; - } - } while ((c = callback(arg)) != -1); - if (!discard) { - uintmax_t bane = (uintmax_t)1 << (bits - 1); - if (!(number & ~((bane - 1) | (issigned ? 0 : bane))) || - (issigned && number == bane /* two's complement bane */)) { - ++items; - } else { - items = erange(); - goto Done; - } - if (issigned && isneg) { - number = ~number + 1; - } - void *out = va_arg(va, void *); - switch (bits) { - case sizeof(uintmax_t) * CHAR_BIT: - *(uintmax_t *)out = number; - break; - case 48: - case 64: - *(uint64_t *)out = (uint64_t)number; - break; - case 32: - *(uint32_t *)out = (uint32_t)number; - break; - case 16: - *(uint16_t *)out = (uint16_t)number; - break; - case 8: - default: - *(uint8_t *)out = (uint8_t)number; - break; - } - } - } - continue; - DecodeString: - bufsize = !width ? 32 : rawmode ? width : width + 1; - if (discard) { - buf = NULL; - } else if (ismalloc) { - buf = weaken(malloc)(bufsize * charbytes); - struct FreeMe *entry; - if (buf && (entry = weaken(calloc)(1, sizeof(struct FreeMe)))) { - entry->ptr = buf; - entry->next = freeme; - freeme = entry; - } - } else { - buf = va_arg(va, void *); - } - if (buf) { - size_t j = 0; - for (;;) { - if (ismalloc && !width && j + 2 + 1 >= bufsize && - !weaken(grow)(&buf, &bufsize, charbytes, 0)) { - width = bufsize - 1; - } - if (c != -1 && j + !rawmode < bufsize && (rawmode || !isspace(c))) { - if (charbytes == 1) { - ((unsigned char *)buf)[j++] = (unsigned char)c; - c = callback(arg); - } else if (tpdecodecb((wint_t *)&c, c, (void *)callback, arg) != - -1) { - if (charbytes == sizeof(char16_t)) { - j += abs(pututf16(&((char16_t *)buf)[j], bufsize - j - 1, c, - false)); - } else { - ((wchar_t *)buf)[j++] = (wchar_t)c; - } - c = callback(arg); - } - } else { - if (!rawmode && j < bufsize) { - if (charbytes == sizeof(char)) { - ((unsigned char *)buf)[j] = '\0'; - } else if (charbytes == sizeof(char16_t)) { - ((char16_t *)buf)[j] = u'\0'; - } else if (charbytes == sizeof(wchar_t)) { - ((wchar_t *)buf)[j] = L'\0'; - } - } - break; - } - } - ++items; - if (ismalloc) { - *va_arg(va, char **) = buf; - } - } else { - do { - if (isspace(c)) break; - } while ((c = callback(arg)) != -1); - } - break; - } - default: - NonDirectiveCharacter: - c = (c == p[i - 1]) ? callback(arg) : -1; - break; - } - } -Done: - while (freeme) { - struct FreeMe *entry = freeme; - freeme = entry->next; - if (items == -1) { - weaken(free_s)((void **)&entry->ptr); - } - weaken(free_s)((void **)&entry); - } - return items; -} - -#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ -#endif /* COSMOPOLITAN_LIBC_FMT_VCSSCANF_H_ */ diff --git a/libc/fmt/vsscanf.c b/libc/fmt/vsscanf.c index 67e286b7..1f273c55 100644 --- a/libc/fmt/vsscanf.c +++ b/libc/fmt/vsscanf.c @@ -18,7 +18,24 @@ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/dce.h" -#include "libc/fmt/vsscanf.h" +#include "libc/fmt/fmt.h" + +struct StringScannerState { + const unsigned char *s; + size_t i; +}; + +static int vsscanfcb(void *arg) { + int res; + struct StringScannerState *state; + state = arg; + if ((res = state->s[state->i])) { + state->i++; + } else { + res = -1; + } + return res; +} /** * Decodes string. @@ -29,5 +46,6 @@ * a small code size penalty to using both */ int(vsscanf)(const char *str, const char *fmt, va_list va) { - return __vsscanf(str, fmt, va, IsTiny() ? vcscanf : __vcscanf); + struct StringScannerState state = {(const unsigned char *)str, 0}; + return vcscanf(vsscanfcb, &state, fmt, va); } diff --git a/libc/fmt/vsscanf.h b/libc/fmt/vsscanf.h deleted file mode 100644 index dd811c1a..00000000 --- a/libc/fmt/vsscanf.h +++ /dev/null @@ -1,39 +0,0 @@ -#ifndef COSMOPOLITAN_LIBC_INTERNAL_VSSCANF_H_ -#define COSMOPOLITAN_LIBC_INTERNAL_VSSCANF_H_ -#include "libc/fmt/fmt.h" -#include "libc/fmt/vcscanf.h" -#if !(__ASSEMBLER__ + __LINKER__ + 0) - -struct StringScannerState { - const unsigned char *s; - size_t i; -}; - -static inline int vsscanfcb(void *arg) { - struct StringScannerState *state = arg; - int res; - if ((res = state->s[state->i])) { - state->i++; - } else { - res = -1; - } - return res; -} - -/** - * String decoder builder. - * - * This macro grants sscanf() and vsscanf() the choice to either link or - * inline the full vcscanf() implementation. - * - * @see libc/fmt/vcscanf.h (for docs and implementation) - */ -static inline int __vsscanf(const char *str, const char *fmt, va_list ap, - int impl(int callback(void *), void *arg, - const char *fmt, va_list ap)) { - struct StringScannerState state = {(const unsigned char *)str, 0}; - return impl(vsscanfcb, &state, fmt, ap); -} - -#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ -#endif /* COSMOPOLITAN_LIBC_INTERNAL_VSSCANF_H_ */ diff --git a/libc/integral/c.inc b/libc/integral/c.inc index 5e3ea3e4..eebeda14 100644 --- a/libc/integral/c.inc +++ b/libc/integral/c.inc @@ -123,7 +123,7 @@ typedef _Bool bool; #endif #endif -#ifndef __cplusplus +#if !defined(__cplusplus) && !defined(__STRICT_ANSI__) typedef __WCHAR_TYPE__ wchar_t; typedef __CHAR16_TYPE__ char16_t; typedef __CHAR32_TYPE__ char32_t; @@ -205,7 +205,7 @@ typedef uint64_t uintmax_t; #define strlenesque libcesque nosideeffect paramsnonnull() #define vallocesque \ libcesque nodiscard returnsaligned((PAGESIZE)) returnspointerwithnoaliases -#define reallocesque libcesque nodiscard returnsaligned((__BIGGEST_ALIGNMENT__)) +#define reallocesque libcesque returnsaligned((__BIGGEST_ALIGNMENT__)) #define mallocesque reallocesque returnspointerwithnoaliases #define interruptfn nocallersavedregisters forcealignargpointer @@ -530,6 +530,8 @@ typedef uint64_t uintmax_t; /** * Asks compiler to not optimize function definition. + * + * @todo this is dangerous delete? */ #ifndef nooptimize #ifndef __STRICT_ANSI__ @@ -547,6 +549,8 @@ typedef uint64_t uintmax_t; * Asks compiler to generate as little code as possible for function. * * This does the same thing as relegated, but without relocation. + * + * @todo this is dangerous delete? */ #ifndef optimizesize #ifndef __STRICT_ANSI__ @@ -566,6 +570,8 @@ typedef uint64_t uintmax_t; * This keyword provides an alternative to build flag tuning, in cases * where the compiler is reluctant to vectorize mathematical code that's * written in standards-compliant C rather than GCC extensions. + * + * @todo this is dangerous delete? */ #ifndef optimizespeed #if !defined(__STRICT_ANSI__) && \ @@ -602,7 +608,7 @@ typedef uint64_t uintmax_t; #endif /** - * Associates debug info with caller of inline function. + * Associates debug information with call site. * @see nodebuginfo */ #ifndef artificial @@ -772,11 +778,11 @@ typedef uint64_t uintmax_t; #define /* TODO(jart): delete */ assume(x) __builtin_assume(x) #endif -#ifndef /* TODO(jart): delete */ likely +#ifndef likely #define likely(expr) __builtin_expect(!!(expr), 1) #endif -#ifndef /* TODO(jart): delete */ unlikely +#ifndef unlikely #define unlikely(expr) __builtin_expect(!!(expr), 0) #endif @@ -804,8 +810,8 @@ typedef uint64_t uintmax_t; #ifndef __STRICT_ANSI__ #define testonly noinline _Section(".test") -#define textstartup _Section(".text.startup") -#define textexit _Section(".text.exit") +#define textstartup _Section(".text.startup") noinstrument +#define textexit _Section(".text.exit") noinstrument #define textreal _Section(".text.real") #define textwindows _Section(".text.windows") #define antiquity _Section(".text.antiquity") @@ -860,6 +866,7 @@ typedef uint64_t uintmax_t; /** * Systemic suppressions. */ +#ifndef __STRICT_ANSI__ #if defined(__GNUC__) || defined(__llvm__) #pragma GCC diagnostic ignored "-Wsign-compare" /* lint needs to change */ #pragma GCC diagnostic ignored "-Wtype-limits" /* makes macros unsafe */ @@ -885,6 +892,9 @@ typedef uint64_t uintmax_t; #pragma GCC diagnostic ignored /* tidy */ "-Wunused-but-set-variable" #pragma GCC diagnostic ignored /* tidy */ "-Wunused-but-set-parameter" #endif /* GCC6+ */ +#if __GNUC__ + 0 >= 9 +#pragma GCC diagnostic ignored /* "always true" breaks dce */ "-Waddress" +#endif /* GCC9+ */ #endif /* !C++ */ #endif /* GCC && !LLVM */ #ifdef __llvm__ @@ -899,6 +909,7 @@ typedef uint64_t uintmax_t; #pragma clang diagnostic ignored \ "-Wincompatible-pointer-types-discards-qualifiers" #endif /* !GCC && LLVM */ +#endif /* ANSI */ /** * Elevate warnings of material consequence. @@ -912,6 +923,7 @@ typedef uint64_t uintmax_t; * time, e.g. 1's complement, big endian, under 32bit word size, etc. */ #ifndef __W__ +#ifndef __STRICT_ANSI__ #if defined(__GNUC__) || defined(__llvm__) #pragma GCC diagnostic error "-Wpointer-arith" #pragma GCC diagnostic error "-Wnonnull" @@ -939,7 +951,7 @@ typedef uint64_t uintmax_t; #pragma GCC diagnostic error "-Wredundant-decls" #if __GNUC__ >= 6 #pragma GCC diagnostic error "-Wnonnull-compare" -#ifndef /* we guarantee no stack overflow unless -D*/ STACK_FRAME_UNLIMITED +#if !defined(MODE_DBG) && !defined(STACK_FRAME_UNLIMITED) #pragma GCC diagnostic error "-Wframe-larger-than=4096" #if __GNUC__ >= 9 #pragma GCC diagnostic error "-Walloca-larger-than=1024" @@ -953,6 +965,7 @@ typedef uint64_t uintmax_t; #ifdef __llvm__ #pragma clang diagnostic error "-Wassume" #endif /* !GCC && LLVM */ +#endif /* ANSI */ #endif /* -w */ /** diff --git a/libc/integral/normalize.inc b/libc/integral/normalize.inc index adb7c344..587d6008 100644 --- a/libc/integral/normalize.inc +++ b/libc/integral/normalize.inc @@ -62,7 +62,7 @@ #endif #ifndef __BIGGEST_ALIGNMENT__ -#define __BIGGEST_ALIGNMENT__ 16 /* intrinsics practices need to change */ +#define __BIGGEST_ALIGNMENT__ 16 #endif #define BIGPAGESIZE 0x200000 diff --git a/libc/internal/notice.h b/libc/internal/notice.h index 25ffa1f9..4bb9ab30 100644 --- a/libc/internal/notice.h +++ b/libc/internal/notice.h @@ -1,5 +1,6 @@ #ifndef COSMOPOLITAN_LIBC_INTERNAL_NOTICE_H_ #define COSMOPOLITAN_LIBC_INTERNAL_NOTICE_H_ +#ifndef __STRICT_ANSI__ #ifdef __ASSEMBLER__ .include "libc/notice.inc" @@ -7,4 +8,5 @@ asm(".include \"libc/notice.inc\""); #endif +#endif /* !ANSI */ #endif /* COSMOPOLITAN_LIBC_INTERNAL_NOTICE_H_ */ diff --git a/libc/intrin/intrin.mk b/libc/intrin/intrin.mk index a9e9dcd8..06594bb6 100644 --- a/libc/intrin/intrin.mk +++ b/libc/intrin/intrin.mk @@ -41,6 +41,10 @@ $(LIBC_INTRIN_A).pkg: \ $(LIBC_INTRIN_A_OBJS) \ $(foreach x,$(LIBC_INTRIN_A_DIRECTDEPS),$($(x)_A).pkg) +$(LIBC_INTRIN_A_OBJS): \ + OVERRIDE_CFLAGS += \ + -fwrapv -O3 + LIBC_INTRIN_LIBS = $(foreach x,$(LIBC_INTRIN_ARTIFACTS),$($(x))) LIBC_INTRIN_HDRS = $(foreach x,$(LIBC_INTRIN_ARTIFACTS),$($(x)_HDRS)) LIBC_INTRIN_SRCS = $(foreach x,$(LIBC_INTRIN_ARTIFACTS),$($(x)_SRCS)) diff --git a/libc/intrin/macros.h b/libc/intrin/macros.h index 01b167cb..35257446 100644 --- a/libc/intrin/macros.h +++ b/libc/intrin/macros.h @@ -7,13 +7,13 @@ #define INTRIN_COMMUTATIVE "%" #define INTRIN_NONCOMMUTATIVE -#ifndef __STRICT_ANSI__ +#if defined(__x86_64__) && !defined(__STRICT_ANSI__) -typedef char __intrin_xmm_t _Vector_size(16) mayalias; +typedef char __intrin_xmm_t _Vector_size(16) aligned(16) mayalias; #define INTRIN_SSEVEX_X_X_X_(PURE, ISA, OP, FLAGS, A, B, C) \ do { \ - if (!IsModeDbg() && X86_NEED(SSE) && X86_HAVE(ISA)) { \ + if (!IsModeDbg() && X86_HAVE(ISA)) { \ __intrin_xmm_t *Xmm0 = (void *)(A); \ const __intrin_xmm_t *Xmm1 = (const __intrin_xmm_t *)(B); \ const __intrin_xmm_t *Xmm2 = (const __intrin_xmm_t *)(C); \ @@ -29,7 +29,37 @@ typedef char __intrin_xmm_t _Vector_size(16) mayalias; #define INTRIN_SSEVEX_X_X_I_(PURE, ISA, OP, A, B, I) \ do { \ - if (!IsModeDbg() && X86_NEED(SSE) && X86_HAVE(ISA)) { \ + if (!IsModeDbg() && X86_HAVE(ISA)) { \ + __intrin_xmm_t *Xmm0 = (void *)(A); \ + const __intrin_xmm_t *Xmm1 = (const __intrin_xmm_t *)(B); \ + if (!X86_NEED(AVX)) { \ + asm(OP "\t%2,%1,%0" : "=x"(*Xmm0) : "x"(*Xmm1), "i"(I)); \ + } else { \ + asm("v" OP "\t%2,%1,%0" : "=x"(*Xmm0) : "x"(*Xmm1), "i"(I)); \ + } \ + } else { \ + PURE(A, B, I); \ + } \ + } while (0) + +#define INTRIN_SSEVEX_X_X_(PURE, ISA, OP, A, B) \ + do { \ + if (!IsModeDbg() && X86_HAVE(ISA)) { \ + __intrin_xmm_t *Xmm0 = (void *)(A); \ + const __intrin_xmm_t *Xmm1 = (const __intrin_xmm_t *)(B); \ + if (!X86_NEED(AVX)) { \ + asm(OP "\t%1,%0" : "=x"(*Xmm0) : "0"(*Xmm1)); \ + } else { \ + asm("v" OP "\t%1,%0" : "=x"(*Xmm0) : "x"(*Xmm1)); \ + } \ + } else { \ + PURE(A, B); \ + } \ + } while (0) + +#define INTRIN_SSEVEX_X_I_(PURE, ISA, OP, A, B, I) \ + do { \ + if (!IsModeDbg() && X86_HAVE(ISA)) { \ __intrin_xmm_t *Xmm0 = (void *)(A); \ const __intrin_xmm_t *Xmm1 = (const __intrin_xmm_t *)(B); \ if (!X86_NEED(AVX)) { \ @@ -45,7 +75,8 @@ typedef char __intrin_xmm_t _Vector_size(16) mayalias; #else #define INTRIN_SSEVEX_X_X_X_(PURE, ISA, OP, FLAGS, A, B, C) PURE(A, B, C) #define INTRIN_SSEVEX_X_X_I_(PURE, ISA, OP, A, B, I) PURE(A, B, I) -#endif /* ANSI */ +#define INTRIN_SSEVEX_X_I_(PURE, ISA, OP, A, B, I) PURE(A, B, I) +#endif /* X86 && !ANSI */ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ #endif /* COSMOPOLITAN_LIBC_INTRIN_MACROS_H_ */ diff --git a/libc/intrin/pabsb.c b/libc/intrin/pabsb.c new file mode 100644 index 00000000..ec4ab902 --- /dev/null +++ b/libc/intrin/pabsb.c @@ -0,0 +1,35 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/pabsb.h" +#include "libc/macros.h" +#include "libc/str/str.h" + +/** + * Converts signed bytes to absolute values, 𝑎ᵢ ← |𝑏ᵢ|. + * @note goes fast w/ ssse3 (intel c. 2004, amd c. 2011) + */ +void(pabsb)(uint8_t a[16], const int8_t b[16]) { + unsigned i; + uint8_t r[16]; + for (i = 0; i < 16; ++i) { + r[i] = ABS(b[i]); + } + memcpy(a, r, 16); +} diff --git a/libc/intrin/pabsb.h b/libc/intrin/pabsb.h new file mode 100644 index 00000000..81e3cb82 --- /dev/null +++ b/libc/intrin/pabsb.h @@ -0,0 +1,13 @@ +#ifndef COSMOPOLITAN_LIBC_INTRIN_PABSB_H_ +#define COSMOPOLITAN_LIBC_INTRIN_PABSB_H_ +#include "libc/intrin/macros.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +void pabsb(uint8_t[16], const int8_t[16]); + +#define pabsb(A, B) INTRIN_SSEVEX_X_X_(pabsb, SSSE3, "pabsb", A, B) + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_INTRIN_PABSB_H_ */ diff --git a/libc/intrin/pabsd.c b/libc/intrin/pabsd.c new file mode 100644 index 00000000..0b9cf2e9 --- /dev/null +++ b/libc/intrin/pabsd.c @@ -0,0 +1,35 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/pabsd.h" +#include "libc/macros.h" +#include "libc/str/str.h" + +/** + * Converts shorts to absolute values, 𝑎ᵢ ← |𝑏ᵢ|. + * @note goes fast w/ ssse3 (intel c. 2004, amd c. 2011) + */ +void(pabsd)(uint32_t a[4], const int32_t b[4]) { + unsigned i; + uint32_t r[4]; + for (i = 0; i < 4; ++i) { + r[i] = ABS(b[i]); + } + memcpy(a, r, 16); +} diff --git a/libc/intrin/pabsd.h b/libc/intrin/pabsd.h new file mode 100644 index 00000000..5988396d --- /dev/null +++ b/libc/intrin/pabsd.h @@ -0,0 +1,13 @@ +#ifndef COSMOPOLITAN_LIBC_INTRIN_PABSD_H_ +#define COSMOPOLITAN_LIBC_INTRIN_PABSD_H_ +#include "libc/intrin/macros.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +void pabsd(uint32_t[4], const int32_t[4]); + +#define pabsd(A, B) INTRIN_SSEVEX_X_X_(pabsd, SSSE3, "pabsd", A, B) + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_INTRIN_PABSD_H_ */ diff --git a/libc/intrin/pabsw.c b/libc/intrin/pabsw.c new file mode 100644 index 00000000..59f948ca --- /dev/null +++ b/libc/intrin/pabsw.c @@ -0,0 +1,35 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/pabsw.h" +#include "libc/macros.h" +#include "libc/str/str.h" + +/** + * Converts shorts to absolute values, 𝑎ᵢ ← |𝑏ᵢ|. + * @note goes fast w/ ssse3 (intel c. 2004, amd c. 2011) + */ +void(pabsw)(uint16_t a[8], const int16_t b[8]) { + unsigned i; + uint16_t r[8]; + for (i = 0; i < 8; ++i) { + r[i] = ABS(b[i]); + } + memcpy(a, r, 16); +} diff --git a/libc/intrin/pabsw.h b/libc/intrin/pabsw.h new file mode 100644 index 00000000..f4fd3846 --- /dev/null +++ b/libc/intrin/pabsw.h @@ -0,0 +1,13 @@ +#ifndef COSMOPOLITAN_LIBC_INTRIN_PABSW_H_ +#define COSMOPOLITAN_LIBC_INTRIN_PABSW_H_ +#include "libc/intrin/macros.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +void pabsw(uint16_t[8], const int16_t[8]); + +#define pabsw(A, B) INTRIN_SSEVEX_X_X_(pabsw, SSSE3, "pabsw", A, B) + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_INTRIN_PABSW_H_ */ diff --git a/libc/nexgen32e/tinystrncmp.ncabi.c b/libc/intrin/packssdw.c similarity index 84% rename from libc/nexgen32e/tinystrncmp.ncabi.c rename to libc/intrin/packssdw.c index 31c28312..3aa41288 100644 --- a/libc/nexgen32e/tinystrncmp.ncabi.c +++ b/libc/intrin/packssdw.c @@ -17,19 +17,16 @@ │ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/packssdw.h" #include "libc/limits.h" -#include "libc/nexgen32e/tinystrcmp.h" +#include "libc/macros.h" /** - * Compares strings w/ limit & no-clobber abi guarantee. + * Casts ints to shorts w/ saturation. + * @mayalias */ -int(tinystrncmp)(const char *s1, const char *s2, size_t n) { - size_t i; - if (!n) return 0; - i = SIZE_MAX; - while (s1[++i]) { - if (s1[i] != s2[i]) break; - if (!--n) break; - } - return (int)(unsigned char)s1[i] - (int)(unsigned char)s2[i]; +void(packssdw)(int16_t a[8], const int32_t b[4], const int32_t c[4]) { + unsigned i; + for (i = 0; i < 4; ++i) a[i + 0] = MIN(INT16_MAX, MAX(INT16_MIN, b[i])); + for (i = 0; i < 4; ++i) a[i + 4] = MIN(INT16_MAX, MAX(INT16_MIN, c[i])); } diff --git a/libc/intrin/packssdw.h b/libc/intrin/packssdw.h new file mode 100644 index 00000000..4d014984 --- /dev/null +++ b/libc/intrin/packssdw.h @@ -0,0 +1,15 @@ +#ifndef COSMOPOLITAN_LIBC_INTRIN_PACKSSDW_H_ +#define COSMOPOLITAN_LIBC_INTRIN_PACKSSDW_H_ +#include "libc/intrin/macros.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +void packssdw(int16_t[8], const int32_t[4], const int32_t[4]); + +#define packssdw(A, B, C) \ + INTRIN_SSEVEX_X_X_X_(packssdw, SSE2, "packssdw", INTRIN_NONCOMMUTATIVE, A, \ + B, C) + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_INTRIN_PACKSSDW_H_ */ diff --git a/test/libc/runtime/heapsortcar_test.c b/libc/intrin/packsswb.c similarity index 78% rename from test/libc/runtime/heapsortcar_test.c rename to libc/intrin/packsswb.c index fab29e3b..53647567 100644 --- a/test/libc/runtime/heapsortcar_test.c +++ b/libc/intrin/packsswb.c @@ -17,19 +17,23 @@ │ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/alg/alg.h" -#include "libc/bits/bits.h" +#include "libc/intrin/packsswb.h" +#include "libc/limits.h" #include "libc/macros.h" #include "libc/str/str.h" -#include "libc/testlib/testlib.h" -TEST(heapsortcar, test) { - int32_t A[][2] = {{4, 'a'}, {65, 'b'}, {2, 'c'}, {-31, 'd'}, {0, 'e'}, - {99, 'f'}, {2, 'g'}, {83, 'h'}, {782, 'i'}, {1, 'j'}}; - const int32_t B[][2] = {{-31, 'd'}, {0, 'e'}, {1, 'j'}, {2, 'c'}, - {2, 'g'}, {4, 'a'}, {65, 'b'}, {83, 'h'}, - {99, 'f'}, {782, 'i'}}; - unsigned n = ARRAYLEN(A); - heapsortcar(A, n); - ASSERT_EQ(0, memcmp(&A[0], &B[0], sizeof(A))); +/** + * Casts shorts to signed chars w/ saturation. + * + * 𝑎 ← {CLAMP[𝑏ᵢ]|𝑖∈[0,4)} ║ {CLAMP[𝑐ᵢ]|𝑖∈[4,8)} + * + * @see packuswb() + * @mayalias + */ +void(packsswb)(int8_t a[16], const int16_t b[8], const int16_t c[8]) { + unsigned i; + int8_t r[16]; + for (i = 0; i < 8; ++i) r[i + 0] = MIN(INT8_MAX, MAX(INT8_MIN, b[i])); + for (i = 0; i < 8; ++i) r[i + 8] = MIN(INT8_MAX, MAX(INT8_MIN, c[i])); + memcpy(a, r, 16); } diff --git a/libc/intrin/packsswb.h b/libc/intrin/packsswb.h index fa72939a..c39b7f74 100644 --- a/libc/intrin/packsswb.h +++ b/libc/intrin/packsswb.h @@ -1,30 +1,15 @@ #ifndef COSMOPOLITAN_LIBC_INTRIN_PACKSSWB_H_ #define COSMOPOLITAN_LIBC_INTRIN_PACKSSWB_H_ #include "libc/intrin/macros.h" -#include "libc/macros.h" #if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ -/** - * Casts shorts to signed chars w/ saturation. - * - * 𝑎 ← {CLAMP[𝑏ᵢ]|𝑖∈[0,4)} ║ {CLAMP[𝑐ᵢ]|𝑖∈[4,8)} - * - * @see packuswb() - * @mayalias - */ -static void packsswb(signed char a[16], const short b[8], const short c[8]) { - int i; - for (i = 0; i < 8; ++i) { - a[i] = MIN(127, MAX(-128, b[i])); - } - for (i = 0; i < 8; ++i) { - a[i + 8] = MIN(127, MAX(-128, c[i])); - } -} +void packsswb(int8_t[16], const int16_t[8], const int16_t[8]); #define packsswb(A, B, C) \ INTRIN_SSEVEX_X_X_X_(packsswb, SSE2, "packsswb", INTRIN_NONCOMMUTATIVE, A, \ B, C) +COSMOPOLITAN_C_END_ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ #endif /* COSMOPOLITAN_LIBC_INTRIN_PACKSSWB_H_ */ diff --git a/libc/intrin/packusdw.c b/libc/intrin/packusdw.c new file mode 100644 index 00000000..2be957d2 --- /dev/null +++ b/libc/intrin/packusdw.c @@ -0,0 +1,35 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/packusdw.h" +#include "libc/limits.h" +#include "libc/macros.h" +#include "libc/str/str.h" + +/** + * Casts ints to shorts w/ saturation. + * @mayalias + */ +void(packusdw)(uint16_t a[8], const int32_t b[4], const int32_t c[4]) { + unsigned i; + uint16_t r[8]; + for (i = 0; i < 4; ++i) r[i + 0] = MIN(UINT16_MAX, MAX(UINT16_MIN, b[i])); + for (i = 0; i < 4; ++i) r[i + 4] = MIN(UINT16_MAX, MAX(UINT16_MIN, c[i])); + memcpy(a, r, 16); +} diff --git a/libc/intrin/packusdw.h b/libc/intrin/packusdw.h new file mode 100644 index 00000000..c9847ae8 --- /dev/null +++ b/libc/intrin/packusdw.h @@ -0,0 +1,15 @@ +#ifndef COSMOPOLITAN_LIBC_INTRIN_PACKUSDW_H_ +#define COSMOPOLITAN_LIBC_INTRIN_PACKUSDW_H_ +#include "libc/intrin/macros.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +void packusdw(uint16_t[8], const int32_t[4], const int32_t[4]); + +#define packusdw(A, B, C) \ + INTRIN_SSEVEX_X_X_X_(packusdw, SSE4_1, "packusdw", INTRIN_NONCOMMUTATIVE, A, \ + B, C) + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_INTRIN_PACKUSDW_H_ */ diff --git a/libc/intrin/packuswb.c b/libc/intrin/packuswb.c new file mode 100644 index 00000000..359c6727 --- /dev/null +++ b/libc/intrin/packuswb.c @@ -0,0 +1,39 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/packuswb.h" +#include "libc/limits.h" +#include "libc/macros.h" +#include "libc/str/str.h" + +/** + * Casts shorts to unsigned chars w/ saturation. + * + * 𝑎 ← {CLAMP[𝑏ᵢ]|𝑖∈[0,4)} ║ {CLAMP[𝑐ᵢ]|𝑖∈[4,8)} + * + * @see packsswb() + * @mayalias + */ +void(packuswb)(uint8_t a[16], const int16_t b[8], const int16_t c[8]) { + unsigned i; + uint8_t r[16]; + for (i = 0; i < 8; ++i) r[i + 0] = MIN(UINT8_MAX, MAX(UINT8_MIN, b[i])); + for (i = 0; i < 8; ++i) r[i + 8] = MIN(UINT8_MAX, MAX(UINT8_MIN, c[i])); + memcpy(a, r, 16); +} diff --git a/libc/intrin/packuswb.h b/libc/intrin/packuswb.h index cb3553b2..405de694 100644 --- a/libc/intrin/packuswb.h +++ b/libc/intrin/packuswb.h @@ -1,30 +1,15 @@ #ifndef COSMOPOLITAN_LIBC_INTRIN_PACKUSWB_H_ #define COSMOPOLITAN_LIBC_INTRIN_PACKUSWB_H_ #include "libc/intrin/macros.h" -#include "libc/macros.h" #if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ -/** - * Casts shorts to unsigned chars w/ saturation. - * - * 𝑎 ← {CLAMP[𝑏ᵢ]|𝑖∈[0,4)} ║ {CLAMP[𝑐ᵢ]|𝑖∈[4,8)} - * - * @see packsswb() - * @mayalias - */ -static void packuswb(unsigned char a[16], const short b[8], const short c[8]) { - int i; - for (i = 0; i < 8; ++i) { - a[i] = MIN(255, MAX(0, b[i])); - } - for (i = 0; i < 8; ++i) { - a[i + 8] = MIN(255, MAX(0, c[i])); - } -} +void packuswb(uint8_t[16], const int16_t[8], const int16_t[8]); #define packuswb(A, B, C) \ INTRIN_SSEVEX_X_X_X_(packuswb, SSE2, "packuswb", INTRIN_NONCOMMUTATIVE, A, \ B, C) +COSMOPOLITAN_C_END_ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ #endif /* COSMOPOLITAN_LIBC_INTRIN_PACKUSWB_H_ */ diff --git a/libc/intrin/paddb.c b/libc/intrin/paddb.c new file mode 100644 index 00000000..2d58a734 --- /dev/null +++ b/libc/intrin/paddb.c @@ -0,0 +1,36 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/paddb.h" +#include "libc/str/str.h" + +/** + * Adds 8-bit integers. + * + * @param 𝑎 [w/o] receives result + * @param 𝑏 [r/o] supplies first input vector + * @param 𝑐 [r/o] supplies second input vector + * @mayalias + */ +void(paddb)(int8_t a[16], const int8_t b[16], const int8_t c[16]) { + unsigned i; + int8_t r[16]; + for (i = 0; i < 16; ++i) r[i] = b[i] + c[i]; + memcpy(a, r, 16); +} diff --git a/libc/intrin/paddb.h b/libc/intrin/paddb.h new file mode 100644 index 00000000..68698e3a --- /dev/null +++ b/libc/intrin/paddb.h @@ -0,0 +1,14 @@ +#ifndef COSMOPOLITAN_LIBC_INTRIN_PADDB_H_ +#define COSMOPOLITAN_LIBC_INTRIN_PADDB_H_ +#include "libc/intrin/macros.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +void paddb(int8_t[16], const int8_t[16], const int8_t[16]); + +#define paddb(A, B, C) \ + INTRIN_SSEVEX_X_X_X_(paddb, SSE2, "paddb", INTRIN_COMMUTATIVE, A, B, C) + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_INTRIN_PADDB_H_ */ diff --git a/libc/intrin/paddd.c b/libc/intrin/paddd.c new file mode 100644 index 00000000..79ab1637 --- /dev/null +++ b/libc/intrin/paddd.c @@ -0,0 +1,36 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/paddd.h" +#include "libc/str/str.h" + +/** + * Adds 32-bit integers. + * + * @param 𝑎 [w/o] receives result + * @param 𝑏 [r/o] supplies first input vector + * @param 𝑐 [r/o] supplies second input vector + * @mayalias + */ +void(paddd)(int32_t a[4], const int32_t b[4], const int32_t c[4]) { + unsigned i; + int32_t r[4]; + for (i = 0; i < 4; ++i) r[i] = b[i] + c[i]; + memcpy(a, r, 16); +} diff --git a/libc/intrin/paddd.h b/libc/intrin/paddd.h new file mode 100644 index 00000000..110d5807 --- /dev/null +++ b/libc/intrin/paddd.h @@ -0,0 +1,14 @@ +#ifndef COSMOPOLITAN_LIBC_INTRIN_PADDD_H_ +#define COSMOPOLITAN_LIBC_INTRIN_PADDD_H_ +#include "libc/intrin/macros.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +void paddd(int32_t[4], const int32_t[4], const int32_t[4]); + +#define paddd(A, B, C) \ + INTRIN_SSEVEX_X_X_X_(paddd, SSE2, "paddd", INTRIN_COMMUTATIVE, A, B, C) + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_INTRIN_PADDD_H_ */ diff --git a/libc/intrin/paddq.c b/libc/intrin/paddq.c new file mode 100644 index 00000000..f3e839f1 --- /dev/null +++ b/libc/intrin/paddq.c @@ -0,0 +1,36 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/paddq.h" +#include "libc/str/str.h" + +/** + * Adds 64-bit integers. + * + * @param 𝑎 [w/o] receives result + * @param 𝑏 [r/o] supplies first input vector + * @param 𝑐 [r/o] supplies second input vector + * @mayalias + */ +void(paddq)(int64_t a[2], const int64_t b[2], const int64_t c[2]) { + unsigned i; + int64_t r[2]; + for (i = 0; i < 2; ++i) r[i] = b[i] + c[i]; + memcpy(a, r, 16); +} diff --git a/libc/intrin/paddq.h b/libc/intrin/paddq.h new file mode 100644 index 00000000..906f460b --- /dev/null +++ b/libc/intrin/paddq.h @@ -0,0 +1,14 @@ +#ifndef COSMOPOLITAN_LIBC_INTRIN_PADDQ_H_ +#define COSMOPOLITAN_LIBC_INTRIN_PADDQ_H_ +#include "libc/intrin/macros.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +void paddq(int64_t[2], const int64_t[2], const int64_t[2]); + +#define paddq(A, B, C) \ + INTRIN_SSEVEX_X_X_X_(paddq, SSE2, "paddq", INTRIN_COMMUTATIVE, A, B, C) + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_INTRIN_PADDQ_H_ */ diff --git a/libc/intrin/paddsb.c b/libc/intrin/paddsb.c new file mode 100644 index 00000000..710c1c3e --- /dev/null +++ b/libc/intrin/paddsb.c @@ -0,0 +1,40 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/paddsb.h" +#include "libc/limits.h" +#include "libc/macros.h" +#include "libc/str/str.h" + +/** + * Adds signed 8-bit integers w/ saturation. + * + * @param 𝑎 [w/o] receives result + * @param 𝑏 [r/o] supplies first input vector + * @param 𝑐 [r/o] supplies second input vector + * @mayalias + */ +void(paddsb)(int8_t a[16], const int8_t b[16], const int8_t c[16]) { + unsigned i; + int8_t r[16]; + for (i = 0; i < 16; ++i) { + r[i] = MIN(INT8_MAX, MAX(INT8_MIN, b[i] + c[i])); + } + memcpy(a, r, 16); +} diff --git a/libc/intrin/paddsb.h b/libc/intrin/paddsb.h new file mode 100644 index 00000000..10cabb1a --- /dev/null +++ b/libc/intrin/paddsb.h @@ -0,0 +1,14 @@ +#ifndef COSMOPOLITAN_LIBC_INTRIN_PADDSB_H_ +#define COSMOPOLITAN_LIBC_INTRIN_PADDSB_H_ +#include "libc/intrin/macros.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +void paddsb(int8_t[16], const int8_t[16], const int8_t[16]); + +#define paddsb(A, B, C) \ + INTRIN_SSEVEX_X_X_X_(paddsb, SSE2, "paddsb", INTRIN_COMMUTATIVE, A, B, C) + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_INTRIN_PADDSB_H_ */ diff --git a/libc/intrin/paddsw.c b/libc/intrin/paddsw.c new file mode 100644 index 00000000..50fb9149 --- /dev/null +++ b/libc/intrin/paddsw.c @@ -0,0 +1,40 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/paddsw.h" +#include "libc/limits.h" +#include "libc/macros.h" +#include "libc/str/str.h" + +/** + * Adds signed 16-bit integers w/ saturation. + * + * @param 𝑎 [w/o] receives result + * @param 𝑏 [r/o] supplies first input vector + * @param 𝑐 [r/o] supplies second input vector + * @mayalias + */ +void(paddsw)(int16_t a[8], const int16_t b[8], const int16_t c[8]) { + unsigned i; + int16_t r[8]; + for (i = 0; i < 8; ++i) { + r[i] = MIN(SHRT_MAX, MAX(SHRT_MIN, b[i] + c[i])); + } + memcpy(a, r, 16); +} diff --git a/libc/intrin/paddsw.h b/libc/intrin/paddsw.h index 71d0a49d..a500e436 100644 --- a/libc/intrin/paddsw.h +++ b/libc/intrin/paddsw.h @@ -1,28 +1,14 @@ #ifndef COSMOPOLITAN_LIBC_INTRIN_PADDSW_H_ #define COSMOPOLITAN_LIBC_INTRIN_PADDSW_H_ #include "libc/intrin/macros.h" -#include "libc/limits.h" -#include "libc/macros.h" #if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ -/** - * Adds signed 16-bit integers w/ saturation. - * - * @param 𝑎 [w/o] receives result - * @param 𝑏 [r/o] supplies first input vector - * @param 𝑐 [r/o] supplies second input vector - * @see paddw() - * @mayalias - */ -static void paddsw(short a[8], const short b[8], const short c[8]) { - int i; - for (i = 0; i < 8; ++i) { - a[i] = MIN(SHRT_MAX, MAX(SHRT_MIN, b[i] + c[i])); - } -} +void paddsw(int16_t[8], const int16_t[8], const int16_t[8]); #define paddsw(A, B, C) \ INTRIN_SSEVEX_X_X_X_(paddsw, SSE2, "paddsw", INTRIN_COMMUTATIVE, A, B, C) +COSMOPOLITAN_C_END_ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ #endif /* COSMOPOLITAN_LIBC_INTRIN_PADDSW_H_ */ diff --git a/libc/intrin/paddusb.c b/libc/intrin/paddusb.c new file mode 100644 index 00000000..4904e0a7 --- /dev/null +++ b/libc/intrin/paddusb.c @@ -0,0 +1,40 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/paddusb.h" +#include "libc/limits.h" +#include "libc/macros.h" +#include "libc/str/str.h" + +/** + * Adds unsigned 8-bit integers w/ saturation. + * + * @param 𝑎 [w/o] receives result + * @param 𝑏 [r/o] supplies first input vector + * @param 𝑐 [r/o] supplies second input vector + * @mayalias + */ +void(paddusb)(uint8_t a[16], const uint8_t b[16], const uint8_t c[16]) { + unsigned i; + uint8_t r[16]; + for (i = 0; i < 16; ++i) { + r[i] = MIN(UINT8_MAX, b[i] + c[i]); + } + memcpy(a, r, 16); +} diff --git a/libc/intrin/paddusb.h b/libc/intrin/paddusb.h new file mode 100644 index 00000000..8c6b300a --- /dev/null +++ b/libc/intrin/paddusb.h @@ -0,0 +1,14 @@ +#ifndef COSMOPOLITAN_LIBC_INTRIN_PADDUSB_H_ +#define COSMOPOLITAN_LIBC_INTRIN_PADDUSB_H_ +#include "libc/intrin/macros.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +void paddusb(uint8_t[16], const uint8_t[16], const uint8_t[16]); + +#define paddusb(A, B, C) \ + INTRIN_SSEVEX_X_X_X_(paddusb, SSE2, "paddusb", INTRIN_COMMUTATIVE, A, B, C) + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_INTRIN_PADDUSB_H_ */ diff --git a/libc/intrin/paddusw.c b/libc/intrin/paddusw.c new file mode 100644 index 00000000..ae46f00c --- /dev/null +++ b/libc/intrin/paddusw.c @@ -0,0 +1,40 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/paddusw.h" +#include "libc/limits.h" +#include "libc/macros.h" +#include "libc/str/str.h" + +/** + * Adds unsigned 16-bit integers w/ saturation. + * + * @param 𝑎 [w/o] receives result + * @param 𝑏 [r/o] supplies first input vector + * @param 𝑐 [r/o] supplies second input vector + * @mayalias + */ +void(paddusw)(uint16_t a[8], const uint16_t b[8], const uint16_t c[8]) { + unsigned i; + uint16_t r[8]; + for (i = 0; i < 8; ++i) { + r[i] = MIN(UINT16_MAX, b[i] + c[i]); + } + memcpy(a, r, 16); +} diff --git a/libc/intrin/paddusw.h b/libc/intrin/paddusw.h new file mode 100644 index 00000000..1b47caf7 --- /dev/null +++ b/libc/intrin/paddusw.h @@ -0,0 +1,14 @@ +#ifndef COSMOPOLITAN_LIBC_INTRIN_PADDUSW_H_ +#define COSMOPOLITAN_LIBC_INTRIN_PADDUSW_H_ +#include "libc/intrin/macros.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +void paddusw(uint16_t[8], const uint16_t[8], const uint16_t[8]); + +#define paddusw(A, B, C) \ + INTRIN_SSEVEX_X_X_X_(paddusw, SSE2, "paddusw", INTRIN_COMMUTATIVE, A, B, C) + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_INTRIN_PADDUSW_H_ */ diff --git a/libc/intrin/paddw.c b/libc/intrin/paddw.c new file mode 100644 index 00000000..48008121 --- /dev/null +++ b/libc/intrin/paddw.c @@ -0,0 +1,40 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/paddw.h" +#include "libc/str/str.h" + +/** + * Adds 16-bit integers. + * + * @param 𝑎 [w/o] receives result + * @param 𝑏 [r/o] supplies first input vector + * @param 𝑐 [r/o] supplies second input vector + * @note shorts can't overflow so ubsan won't report it when it happens + * @see paddsw() + * @mayalias + */ +void(paddw)(int16_t a[8], const int16_t b[8], const int16_t c[8]) { + unsigned i; + int16_t r[8]; + for (i = 0; i < 8; ++i) { + r[i] = b[i] + c[i]; + } + memcpy(a, r, 16); +} diff --git a/libc/intrin/paddw.h b/libc/intrin/paddw.h index d67c72a5..755d4c17 100644 --- a/libc/intrin/paddw.h +++ b/libc/intrin/paddw.h @@ -1,28 +1,14 @@ #ifndef COSMOPOLITAN_LIBC_INTRIN_PADDW_H_ #define COSMOPOLITAN_LIBC_INTRIN_PADDW_H_ #include "libc/intrin/macros.h" -#include "libc/str/str.h" #if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ -/** - * Adds signed 16-bit integers. - * - * @param 𝑎 [w/o] receives result - * @param 𝑏 [r/o] supplies first input vector - * @param 𝑐 [r/o] supplies second input vector - * @note shorts can't overflow so ubsan won't report it when it happens - * @see paddsw() - * @mayalias - */ -static void paddw(short a[8], const short b[8], const short c[8]) { - int i; - for (i = 0; i < 8; ++i) { - a[i] = b[i] + c[i]; - } -} +void paddw(int16_t[8], const int16_t[8], const int16_t[8]); #define paddw(A, B, C) \ INTRIN_SSEVEX_X_X_X_(paddw, SSE2, "paddw", INTRIN_COMMUTATIVE, A, B, C) +COSMOPOLITAN_C_END_ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ #endif /* COSMOPOLITAN_LIBC_INTRIN_PADDW_H_ */ diff --git a/libc/intrin/palignr.c b/libc/intrin/palignr.c new file mode 100644 index 00000000..c10b5c28 --- /dev/null +++ b/libc/intrin/palignr.c @@ -0,0 +1,44 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/assert.h" +#include "libc/intrin/palignr.h" +#include "libc/macros.h" + +/** + * Overlaps vectors. + * + * 𝑖= 0 means 𝑐←𝑎 + * 0<𝑖<16 means 𝑐←𝑎║𝑏 + * 𝑖=16 means 𝑐←𝑏 + * 16<𝑖<32 means 𝑐←𝑏║0 + * 𝑖≥32 means 𝑐←0 + * + * @param 𝑖 goes faster as constexpr + * @note not compatible with mmx + * @see pvalignr() + * @mayalias + */ +void(palignr)(void *c, const void *b, const void *a, unsigned long i) { + char t[48]; + memcpy(t, a, 16); + memcpy(t + 16, b, 16); + memset(t + 32, 0, 16); + memcpy(c, t + MIN(i, 32), 16); +} diff --git a/libc/intrin/palignr.h b/libc/intrin/palignr.h index bba2a4d0..9ece960a 100644 --- a/libc/intrin/palignr.h +++ b/libc/intrin/palignr.h @@ -1,55 +1,46 @@ #ifndef COSMOPOLITAN_LIBC_INTRIN_PALIGNR_H_ #define COSMOPOLITAN_LIBC_INTRIN_PALIGNR_H_ -#include "libc/assert.h" #include "libc/intrin/macros.h" -#include "libc/macros.h" #include "libc/str/str.h" #if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ -void pvalignr(void *, const void *, const void *, size_t); - -/** - * Overlaps vectors. - * - * 𝑖= 0 means 𝑐←𝑎 - * 0<𝑖<16 means 𝑐←𝑎║𝑏 - * 𝑖=16 means 𝑐←𝑏 - * 16<𝑖<32 means 𝑐←𝑏║0 - * 𝑖≥32 means 𝑐←0 - * - * @param 𝑖 needs to be a literal, constexpr, or embedding - * @see pvalignr() - * @mayalias - */ -static void palignr(void *c, const void *b, const void *a, size_t i) { - char t[48]; - memcpy(t, a, 16); - memcpy(t + 16, b, 16); - memset(t + 32, 0, 16); - memcpy(c, t + MIN(32, i), 16); -} +void palignr(void *, const void *, const void *, unsigned long); #ifndef __STRICT_ANSI__ -#define palignr(C, B, A, I) \ - do { \ - if (!IsModeDbg() && X86_NEED(SSE) && X86_HAVE(SSSE3)) { \ - __intrin_xmm_t *Xmm0 = (void *)(C); \ - const __intrin_xmm_t *Xmm1 = (const __intrin_xmm_t *)(B); \ - const __intrin_xmm_t *Xmm2 = (const __intrin_xmm_t *)(A); \ - if (!X86_NEED(AVX)) { \ - asm("palignr\t%2,%1,%0" \ - : "=x"(*Xmm0) \ - : "x"(*Xmm2), "i"(I), "0"(*Xmm1)); \ - } else { \ - asm("vpalignr\t%3,%2,%1,%0" \ - : "=x"(*Xmm0) \ - : "x"(*Xmm1), "x"(*Xmm2), "i"(I)); \ - } \ - } else { \ - palignr(C, B, A, I); \ - } \ +__intrin_xmm_t __palignrs(__intrin_xmm_t, __intrin_xmm_t); +#define palignr(C, B, A, I) \ + do { \ + if (likely(!IsModeDbg() && X86_NEED(SSE) && X86_HAVE(SSSE3))) { \ + __intrin_xmm_t *Xmm0 = (void *)(C); \ + const __intrin_xmm_t *Xmm1 = (const __intrin_xmm_t *)(B); \ + const __intrin_xmm_t *Xmm2 = (const __intrin_xmm_t *)(A); \ + if (isconstant(I)) { \ + if (!X86_NEED(AVX)) { \ + asm("palignr\t%2,%1,%0" \ + : "=x"(*Xmm0) \ + : "x"(*Xmm2), "i"(I), "0"(*Xmm1)); \ + } else { \ + asm("vpalignr\t%3,%2,%1,%0" \ + : "=x"(*Xmm0) \ + : "x"(*Xmm1), "x"(*Xmm2), "i"(I)); \ + } \ + } else { \ + unsigned long Vimm = (I); \ + typeof(__palignrs) *Fn; \ + if (likely(Vimm < 32)) { \ + Fn = (typeof(__palignrs) *)((uintptr_t)&__palignrs + Vimm * 8); \ + *Xmm0 = Fn(*Xmm1, *Xmm2); \ + } else { \ + memset(Xmm0, 0, 16); \ + } \ + } \ + } else { \ + palignr(C, B, A, I); \ + } \ } while (0) #endif +COSMOPOLITAN_C_END_ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ #endif /* COSMOPOLITAN_LIBC_INTRIN_PALIGNR_H_ */ diff --git a/libc/intrin/palignrs.S b/libc/intrin/palignrs.S new file mode 100644 index 00000000..9119bce5 --- /dev/null +++ b/libc/intrin/palignrs.S @@ -0,0 +1,126 @@ +/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│ +│vi: set et ft=asm ts=8 tw=8 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/macros.h" + +/ Jump table for palignr() with non-constexpr immediate parameter. +/ +/ @note needs ssse3 cf. prescott c. 2004 cf. bulldozer c. 2011 +/ @see palignr() + .align 8 +__palignrs: + palignr $0,%xmm1,%xmm0 + ret + nop + palignr $1,%xmm1,%xmm0 + ret + nop + palignr $2,%xmm1,%xmm0 + ret + nop + palignr $3,%xmm1,%xmm0 + ret + nop + palignr $4,%xmm1,%xmm0 + ret + nop + palignr $5,%xmm1,%xmm0 + ret + nop + palignr $6,%xmm1,%xmm0 + ret + nop + palignr $7,%xmm1,%xmm0 + ret + nop + palignr $8,%xmm1,%xmm0 + ret + nop + palignr $9,%xmm1,%xmm0 + ret + nop + palignr $10,%xmm1,%xmm0 + ret + nop + palignr $11,%xmm1,%xmm0 + ret + nop + palignr $12,%xmm1,%xmm0 + ret + nop + palignr $13,%xmm1,%xmm0 + ret + nop + palignr $14,%xmm1,%xmm0 + ret + nop + palignr $15,%xmm1,%xmm0 + ret + nop + palignr $16,%xmm1,%xmm0 + ret + nop + palignr $17,%xmm1,%xmm0 + ret + nop + palignr $18,%xmm1,%xmm0 + ret + nop + palignr $19,%xmm1,%xmm0 + ret + nop + palignr $20,%xmm1,%xmm0 + ret + nop + palignr $21,%xmm1,%xmm0 + ret + nop + palignr $22,%xmm1,%xmm0 + ret + nop + palignr $23,%xmm1,%xmm0 + ret + nop + palignr $24,%xmm1,%xmm0 + ret + nop + palignr $25,%xmm1,%xmm0 + ret + nop + palignr $26,%xmm1,%xmm0 + ret + nop + palignr $27,%xmm1,%xmm0 + ret + nop + palignr $28,%xmm1,%xmm0 + ret + nop + palignr $29,%xmm1,%xmm0 + ret + nop + palignr $30,%xmm1,%xmm0 + ret + nop + palignr $31,%xmm1,%xmm0 + ret + .if . - __palignrs != 8 * 32 - 1 + .error "bad assemblage" + .endif + .endfn __palignrs,globl diff --git a/libc/intrin/pand.c b/libc/intrin/pand.c new file mode 100644 index 00000000..3017343b --- /dev/null +++ b/libc/intrin/pand.c @@ -0,0 +1,35 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/pand.h" + +/** + * Nands 128-bit integers. + * + * @param 𝑎 [w/o] receives result + * @param 𝑏 [r/o] supplies first input vector + * @param 𝑐 [r/o] supplies second input vector + * @mayalias + */ +void(pand)(uint64_t a[2], const uint64_t b[2], const uint64_t c[2]) { + unsigned i; + for (i = 0; i < 2; ++i) { + a[i] = b[i] & c[i]; + } +} diff --git a/libc/intrin/pand.h b/libc/intrin/pand.h new file mode 100644 index 00000000..0edfee7c --- /dev/null +++ b/libc/intrin/pand.h @@ -0,0 +1,14 @@ +#ifndef COSMOPOLITAN_LIBC_INTRIN_PAND_H_ +#define COSMOPOLITAN_LIBC_INTRIN_PAND_H_ +#include "libc/intrin/macros.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +void pand(uint64_t[2], const uint64_t[2], const uint64_t[2]); + +#define pand(A, B, C) \ + INTRIN_SSEVEX_X_X_X_(pand, SSE2, "pand", INTRIN_COMMUTATIVE, A, B, C) + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_INTRIN_PAND_H_ */ diff --git a/libc/intrin/pandn.c b/libc/intrin/pandn.c new file mode 100644 index 00000000..3ac1ed73 --- /dev/null +++ b/libc/intrin/pandn.c @@ -0,0 +1,35 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/pandn.h" + +/** + * Nands 128-bit integers. + * + * @param 𝑎 [w/o] receives result + * @param 𝑏 [r/o] supplies first input vector + * @param 𝑐 [r/o] supplies second input vector + * @mayalias + */ +void(pandn)(uint64_t a[2], const uint64_t b[2], const uint64_t c[2]) { + unsigned i; + for (i = 0; i < 2; ++i) { + a[i] = ~b[i] & c[i]; + } +} diff --git a/libc/intrin/pandn.h b/libc/intrin/pandn.h new file mode 100644 index 00000000..474d77a9 --- /dev/null +++ b/libc/intrin/pandn.h @@ -0,0 +1,14 @@ +#ifndef COSMOPOLITAN_LIBC_INTRIN_PANDN_H_ +#define COSMOPOLITAN_LIBC_INTRIN_PANDN_H_ +#include "libc/intrin/macros.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +void pandn(uint64_t[2], const uint64_t[2], const uint64_t[2]); + +#define pandn(A, B, C) \ + INTRIN_SSEVEX_X_X_X_(pandn, SSE2, "pandn", INTRIN_NONCOMMUTATIVE, A, B, C) + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_INTRIN_PANDN_H_ */ diff --git a/libc/intrin/pavgb.c b/libc/intrin/pavgb.c new file mode 100644 index 00000000..0c3ab7db --- /dev/null +++ b/libc/intrin/pavgb.c @@ -0,0 +1,38 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/pavgb.h" +#include "libc/str/str.h" + +/** + * Averages packed 8-bit unsigned integers w/ rounding. + * + * @param 𝑎 [w/o] receives result + * @param 𝑏 [r/o] supplies first input vector + * @param 𝑐 [r/o] supplies second input vector + * @mayalias + */ +void(pavgb)(uint8_t a[16], const uint8_t b[16], const uint8_t c[16]) { + unsigned i; + uint8_t r[16]; + for (i = 0; i < 16; ++i) { + r[i] = (b[i] + c[i] + 1) >> 1; + } + memcpy(a, r, 16); +} diff --git a/libc/intrin/pavgb.h b/libc/intrin/pavgb.h new file mode 100644 index 00000000..8f68e655 --- /dev/null +++ b/libc/intrin/pavgb.h @@ -0,0 +1,14 @@ +#ifndef COSMOPOLITAN_LIBC_INTRIN_PAVGB_H_ +#define COSMOPOLITAN_LIBC_INTRIN_PAVGB_H_ +#include "libc/intrin/macros.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +void pavgb(uint8_t[16], const uint8_t[16], const uint8_t[16]); + +#define pavgb(A, B, C) \ + INTRIN_SSEVEX_X_X_X_(pavgb, SSE2, "pavgb", INTRIN_COMMUTATIVE, A, B, C) + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_INTRIN_PAVGB_H_ */ diff --git a/libc/intrin/pavgw.c b/libc/intrin/pavgw.c new file mode 100644 index 00000000..3e2fed02 --- /dev/null +++ b/libc/intrin/pavgw.c @@ -0,0 +1,38 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/pavgw.h" +#include "libc/str/str.h" + +/** + * Averages packed 16-bit unsigned integers w/ rounding. + * + * @param 𝑎 [w/o] receives result + * @param 𝑏 [r/o] supplies first input vector + * @param 𝑐 [r/o] supplies second input vector + * @mayalias + */ +void(pavgw)(uint16_t a[8], const uint16_t b[8], const uint16_t c[8]) { + unsigned i; + int16_t r[8]; + for (i = 0; i < 8; ++i) { + r[i] = (b[i] + c[i] + 1) >> 1; + } + memcpy(a, r, 16); +} diff --git a/libc/intrin/pavgw.h b/libc/intrin/pavgw.h new file mode 100644 index 00000000..d13662e6 --- /dev/null +++ b/libc/intrin/pavgw.h @@ -0,0 +1,14 @@ +#ifndef COSMOPOLITAN_LIBC_INTRIN_PAVGW_H_ +#define COSMOPOLITAN_LIBC_INTRIN_PAVGW_H_ +#include "libc/intrin/macros.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +void pavgw(uint16_t[8], const uint16_t[8], const uint16_t[8]); + +#define pavgw(A, B, C) \ + INTRIN_SSEVEX_X_X_X_(pavgw, SSE2, "pavgw", INTRIN_COMMUTATIVE, A, B, C) + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_INTRIN_PAVGW_H_ */ diff --git a/libc/intrin/pcmpeqb.c b/libc/intrin/pcmpeqb.c new file mode 100644 index 00000000..5e81ea41 --- /dev/null +++ b/libc/intrin/pcmpeqb.c @@ -0,0 +1,36 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/pcmpeqb.h" +#include "libc/str/str.h" + +/** + * Compares signed 8-bit integers w/ equal to predicate. + * + * @param 𝑎 [w/o] receives result + * @param 𝑏 [r/o] supplies first input vector + * @param 𝑐 [r/o] supplies second input vector + * @mayalias + */ +void(pcmpeqb)(uint8_t a[16], const uint8_t b[16], const uint8_t c[16]) { + unsigned i; + uint8_t r[16]; + for (i = 0; i < 16; ++i) r[i] = -(b[i] == c[i]); + memcpy(a, r, 16); +} diff --git a/libc/intrin/pcmpeqb.h b/libc/intrin/pcmpeqb.h new file mode 100644 index 00000000..1c42f60e --- /dev/null +++ b/libc/intrin/pcmpeqb.h @@ -0,0 +1,14 @@ +#ifndef COSMOPOLITAN_LIBC_INTRIN_PCMPEQB_H_ +#define COSMOPOLITAN_LIBC_INTRIN_PCMPEQB_H_ +#include "libc/intrin/macros.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +void pcmpeqb(uint8_t[16], const uint8_t[16], const uint8_t[16]); + +#define pcmpeqb(A, B, C) \ + INTRIN_SSEVEX_X_X_X_(pcmpeqb, SSE2, "pcmpeqb", INTRIN_NONCOMMUTATIVE, A, B, C) + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_INTRIN_PCMPEQB_H_ */ diff --git a/libc/intrin/pcmpeqd.c b/libc/intrin/pcmpeqd.c new file mode 100644 index 00000000..ff5e6bb7 --- /dev/null +++ b/libc/intrin/pcmpeqd.c @@ -0,0 +1,36 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/pcmpeqd.h" +#include "libc/str/str.h" + +/** + * Compares signed 32-bit integers w/ equal to predicate. + * + * @param 𝑎 [w/o] receives result + * @param 𝑏 [r/o] supplies first input vector + * @param 𝑐 [r/o] supplies second input vector + * @mayalias + */ +void(pcmpeqd)(int32_t a[4], const int32_t b[4], const int32_t c[4]) { + unsigned i; + int32_t r[4]; + for (i = 0; i < 4; ++i) r[i] = -(b[i] == c[i]); + memcpy(a, r, 16); +} diff --git a/libc/intrin/pcmpeqd.h b/libc/intrin/pcmpeqd.h new file mode 100644 index 00000000..87f2bba0 --- /dev/null +++ b/libc/intrin/pcmpeqd.h @@ -0,0 +1,14 @@ +#ifndef COSMOPOLITAN_LIBC_INTRIN_PCMPEQD_H_ +#define COSMOPOLITAN_LIBC_INTRIN_PCMPEQD_H_ +#include "libc/intrin/macros.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +void pcmpeqd(int32_t[4], const int32_t[4], const int32_t[4]); + +#define pcmpeqd(A, B, C) \ + INTRIN_SSEVEX_X_X_X_(pcmpeqd, SSE2, "pcmpeqd", INTRIN_NONCOMMUTATIVE, A, B, C) + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_INTRIN_PCMPEQD_H_ */ diff --git a/libc/intrin/pcmpeqw.c b/libc/intrin/pcmpeqw.c new file mode 100644 index 00000000..a5666c4a --- /dev/null +++ b/libc/intrin/pcmpeqw.c @@ -0,0 +1,36 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/pcmpeqw.h" +#include "libc/str/str.h" + +/** + * Compares signed 16-bit integers w/ equal to predicate. + * + * @param 𝑎 [w/o] receives result + * @param 𝑏 [r/o] supplies first input vector + * @param 𝑐 [r/o] supplies second input vector + * @mayalias + */ +void(pcmpeqw)(int16_t a[8], const int16_t b[8], const int16_t c[8]) { + unsigned i; + int16_t r[8]; + for (i = 0; i < 8; ++i) r[i] = -(b[i] == c[i]); + memcpy(a, r, 16); +} diff --git a/libc/intrin/pcmpeqw.h b/libc/intrin/pcmpeqw.h new file mode 100644 index 00000000..acd33799 --- /dev/null +++ b/libc/intrin/pcmpeqw.h @@ -0,0 +1,14 @@ +#ifndef COSMOPOLITAN_LIBC_INTRIN_PCMPEQW_H_ +#define COSMOPOLITAN_LIBC_INTRIN_PCMPEQW_H_ +#include "libc/intrin/macros.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +void pcmpeqw(int16_t[8], const int16_t[8], const int16_t[8]); + +#define pcmpeqw(A, B, C) \ + INTRIN_SSEVEX_X_X_X_(pcmpeqw, SSE2, "pcmpeqw", INTRIN_NONCOMMUTATIVE, A, B, C) + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_INTRIN_PCMPEQW_H_ */ diff --git a/libc/intrin/pcmpgtb.c b/libc/intrin/pcmpgtb.c new file mode 100644 index 00000000..3dd7c3ae --- /dev/null +++ b/libc/intrin/pcmpgtb.c @@ -0,0 +1,36 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/pcmpgtb.h" +#include "libc/str/str.h" + +/** + * Compares signed 8-bit integers w/ greater than predicate. + * + * @param 𝑎 [w/o] receives result + * @param 𝑏 [r/o] supplies first input vector + * @param 𝑐 [r/o] supplies second input vector + * @mayalias + */ +void(pcmpgtb)(int8_t a[16], const int8_t b[16], const int8_t c[16]) { + unsigned i; + int8_t r[16]; + for (i = 0; i < 16; ++i) r[i] = -(b[i] > c[i]); + memcpy(a, r, 16); +} diff --git a/libc/intrin/pcmpgtb.h b/libc/intrin/pcmpgtb.h new file mode 100644 index 00000000..c9f4c481 --- /dev/null +++ b/libc/intrin/pcmpgtb.h @@ -0,0 +1,14 @@ +#ifndef COSMOPOLITAN_LIBC_INTRIN_PCMPGTB_H_ +#define COSMOPOLITAN_LIBC_INTRIN_PCMPGTB_H_ +#include "libc/intrin/macros.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +void pcmpgtb(int8_t[16], const int8_t[16], const int8_t[16]); + +#define pcmpgtb(A, B, C) \ + INTRIN_SSEVEX_X_X_X_(pcmpgtb, SSE2, "pcmpgtb", INTRIN_NONCOMMUTATIVE, A, B, C) + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_INTRIN_PCMPGTB_H_ */ diff --git a/libc/intrin/pcmpgtd.c b/libc/intrin/pcmpgtd.c new file mode 100644 index 00000000..c434acdb --- /dev/null +++ b/libc/intrin/pcmpgtd.c @@ -0,0 +1,36 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/pcmpgtd.h" +#include "libc/str/str.h" + +/** + * Compares signed 32-bit integers w/ greater than predicate. + * + * @param 𝑎 [w/o] receives result + * @param 𝑏 [r/o] supplies first input vector + * @param 𝑐 [r/o] supplies second input vector + * @mayalias + */ +void(pcmpgtd)(int32_t a[4], const int32_t b[4], const int32_t c[4]) { + unsigned i; + int32_t r[4]; + for (i = 0; i < 4; ++i) r[i] = -(b[i] > c[i]); + memcpy(a, r, 16); +} diff --git a/libc/intrin/pcmpgtd.h b/libc/intrin/pcmpgtd.h new file mode 100644 index 00000000..728824e4 --- /dev/null +++ b/libc/intrin/pcmpgtd.h @@ -0,0 +1,14 @@ +#ifndef COSMOPOLITAN_LIBC_INTRIN_PCMPGTD_H_ +#define COSMOPOLITAN_LIBC_INTRIN_PCMPGTD_H_ +#include "libc/intrin/macros.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +void pcmpgtd(int32_t[4], const int32_t[4], const int32_t[4]); + +#define pcmpgtd(A, B, C) \ + INTRIN_SSEVEX_X_X_X_(pcmpgtd, SSE2, "pcmpgtd", INTRIN_NONCOMMUTATIVE, A, B, C) + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_INTRIN_PCMPGTD_H_ */ diff --git a/libc/intrin/pcmpgtw.c b/libc/intrin/pcmpgtw.c new file mode 100644 index 00000000..669f5313 --- /dev/null +++ b/libc/intrin/pcmpgtw.c @@ -0,0 +1,36 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/pcmpgtw.h" +#include "libc/str/str.h" + +/** + * Compares signed 16-bit integers w/ greater than predicate. + * + * @param 𝑎 [w/o] receives result + * @param 𝑏 [r/o] supplies first input vector + * @param 𝑐 [r/o] supplies second input vector + * @mayalias + */ +void(pcmpgtw)(int16_t a[8], const int16_t b[8], const int16_t c[8]) { + unsigned i; + int16_t r[8]; + for (i = 0; i < 8; ++i) r[i] = -(b[i] > c[i]); + memcpy(a, r, 16); +} diff --git a/libc/intrin/pcmpgtw.h b/libc/intrin/pcmpgtw.h new file mode 100644 index 00000000..a518acac --- /dev/null +++ b/libc/intrin/pcmpgtw.h @@ -0,0 +1,14 @@ +#ifndef COSMOPOLITAN_LIBC_INTRIN_PCMPGTW_H_ +#define COSMOPOLITAN_LIBC_INTRIN_PCMPGTW_H_ +#include "libc/intrin/macros.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +void pcmpgtw(int16_t[8], const int16_t[8], const int16_t[8]); + +#define pcmpgtw(A, B, C) \ + INTRIN_SSEVEX_X_X_X_(pcmpgtw, SSE2, "pcmpgtw", INTRIN_NONCOMMUTATIVE, A, B, C) + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_INTRIN_PCMPGTW_H_ */ diff --git a/libc/intrin/pdep.h b/libc/intrin/pdep.h index b780837b..1ae35672 100644 --- a/libc/intrin/pdep.h +++ b/libc/intrin/pdep.h @@ -1,6 +1,7 @@ #ifndef COSMOPOLITAN_LIBC_INTRIN_PDEP_H_ #define COSMOPOLITAN_LIBC_INTRIN_PDEP_H_ #if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ /* TODO(jart): Implement polyfill. */ #define pdep(NUMBER, BITMASK) \ @@ -10,5 +11,6 @@ ShuffledBits; \ }) +COSMOPOLITAN_C_END_ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ #endif /* COSMOPOLITAN_LIBC_INTRIN_PDEP_H_ */ diff --git a/libc/intrin/pext.h b/libc/intrin/pext.h index cab29eaa..00dcf15a 100644 --- a/libc/intrin/pext.h +++ b/libc/intrin/pext.h @@ -1,6 +1,7 @@ #ifndef COSMOPOLITAN_LIBC_INTRIN_PEXT_H_ #define COSMOPOLITAN_LIBC_INTRIN_PEXT_H_ #if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ /* TODO(jart): Implement polyfill. */ #define pext(NUMBER, BITMASK) \ @@ -10,5 +11,6 @@ ShuffledBits; \ }) +COSMOPOLITAN_C_END_ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ #endif /* COSMOPOLITAN_LIBC_INTRIN_PEXT_H_ */ diff --git a/libc/intrin/phaddd.c b/libc/intrin/phaddd.c new file mode 100644 index 00000000..c2d8957f --- /dev/null +++ b/libc/intrin/phaddd.c @@ -0,0 +1,39 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/phaddd.h" +#include "libc/str/str.h" + +/** + * Adds adjacent 32-bit integers. + * + * @param 𝑎 [w/o] receives reduced 𝑏 and 𝑐 concatenated + * @param 𝑏 [r/o] supplies two pairs of ints + * @param 𝑐 [r/o] supplies two pairs of ints + * @note goes fast w/ ssse3 (intel c. 2004, amd c. 2011) + * @mayalias + */ +void(phaddd)(int32_t a[4], const int32_t b[4], const int32_t c[4]) { + int32_t t[4]; + t[0] = b[0] + b[1]; + t[1] = b[2] + b[3]; + t[2] = c[0] + c[1]; + t[3] = c[2] + c[3]; + memcpy(a, t, sizeof(t)); +} diff --git a/libc/intrin/phaddd.h b/libc/intrin/phaddd.h new file mode 100644 index 00000000..96e68bd8 --- /dev/null +++ b/libc/intrin/phaddd.h @@ -0,0 +1,14 @@ +#ifndef COSMOPOLITAN_LIBC_INTRIN_PHADDD_H_ +#define COSMOPOLITAN_LIBC_INTRIN_PHADDD_H_ +#include "libc/intrin/macros.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +void phaddd(int32_t[4], const int32_t[4], const int32_t[4]); + +#define phaddd(A, B, C) \ + INTRIN_SSEVEX_X_X_X_(phaddd, SSSE3, "phaddd", INTRIN_NONCOMMUTATIVE, A, B, C) + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_INTRIN_PHADDD_H_ */ diff --git a/libc/intrin/phaddsw.c b/libc/intrin/phaddsw.c index f1d47626..0728ac79 100644 --- a/libc/intrin/phaddsw.c +++ b/libc/intrin/phaddsw.c @@ -30,7 +30,7 @@ * @note goes fast w/ ssse3 (intel c. 2004, amd c. 2011) * @mayalias */ -void(phaddsw)(short a[8], const short b[8], const short c[8]) { +void(phaddsw)(int16_t a[8], const int16_t b[8], const int16_t c[8]) { int i, t[8]; t[0] = b[0] + b[1]; t[1] = b[2] + b[3]; diff --git a/libc/intrin/phaddsw.h b/libc/intrin/phaddsw.h index 5d0ec573..e85db81c 100644 --- a/libc/intrin/phaddsw.h +++ b/libc/intrin/phaddsw.h @@ -2,12 +2,14 @@ #define COSMOPOLITAN_LIBC_INTRIN_PHADDSW_H_ #include "libc/intrin/macros.h" #if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ -void phaddsw(short[8], const short[8], const short[8]); +void phaddsw(int16_t[8], const int16_t[8], const int16_t[8]); #define phaddsw(A, B, C) \ INTRIN_SSEVEX_X_X_X_(phaddsw, SSSE3, "phaddsw", INTRIN_NONCOMMUTATIVE, A, B, \ C) +COSMOPOLITAN_C_END_ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ #endif /* COSMOPOLITAN_LIBC_INTRIN_PHADDSW_H_ */ diff --git a/libc/intrin/phaddw.c b/libc/intrin/phaddw.c index 529a750c..5f70262e 100644 --- a/libc/intrin/phaddw.c +++ b/libc/intrin/phaddw.c @@ -18,9 +18,10 @@ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/intrin/phaddw.h" +#include "libc/str/str.h" /** - * Adds adjacent signed 16-bit integers. + * Adds adjacent 16-bit integers. * * @param 𝑎 [w/o] receives reduced 𝑏 and 𝑐 concatenated * @param 𝑏 [r/o] supplies four pairs of shorts @@ -28,8 +29,8 @@ * @note goes fast w/ ssse3 (intel c. 2004, amd c. 2011) * @mayalias */ -void(phaddw)(short a[8], const short b[8], const short c[8]) { - short t[8]; +void(phaddw)(int16_t a[8], const int16_t b[8], const int16_t c[8]) { + int16_t t[8]; t[0] = b[0] + b[1]; t[1] = b[2] + b[3]; t[2] = b[4] + b[5]; diff --git a/libc/intrin/phaddw.h b/libc/intrin/phaddw.h index b491f913..efebaef5 100644 --- a/libc/intrin/phaddw.h +++ b/libc/intrin/phaddw.h @@ -1,13 +1,14 @@ #ifndef COSMOPOLITAN_LIBC_INTRIN_PHADDW_H_ #define COSMOPOLITAN_LIBC_INTRIN_PHADDW_H_ #include "libc/intrin/macros.h" -#include "libc/str/str.h" #if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ -void phaddw(short[8], const short[8], const short[8]); +void phaddw(int16_t[8], const int16_t[8], const int16_t[8]); #define phaddw(A, B, C) \ INTRIN_SSEVEX_X_X_X_(phaddw, SSSE3, "phaddw", INTRIN_NONCOMMUTATIVE, A, B, C) +COSMOPOLITAN_C_END_ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ #endif /* COSMOPOLITAN_LIBC_INTRIN_PHADDW_H_ */ diff --git a/libc/intrin/phsubd.c b/libc/intrin/phsubd.c new file mode 100644 index 00000000..81775ba2 --- /dev/null +++ b/libc/intrin/phsubd.c @@ -0,0 +1,39 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/phsubd.h" +#include "libc/str/str.h" + +/** + * Subtracts adjacent 32-bit integers. + * + * @param 𝑎 [w/o] receives reduced 𝑏 and 𝑐 concatenated + * @param 𝑏 [r/o] supplies two pairs of ints + * @param 𝑐 [r/o] supplies two pairs of ints + * @note goes fast w/ ssse3 + * @mayalias + */ +void(phsubd)(int32_t a[4], const int32_t b[4], const int32_t c[4]) { + int32_t t[4]; + t[0] = b[0] - b[1]; + t[1] = b[2] - b[3]; + t[2] = c[0] - c[1]; + t[3] = c[2] - c[3]; + memcpy(a, t, sizeof(t)); +} diff --git a/libc/intrin/phsubd.h b/libc/intrin/phsubd.h new file mode 100644 index 00000000..641272fb --- /dev/null +++ b/libc/intrin/phsubd.h @@ -0,0 +1,14 @@ +#ifndef COSMOPOLITAN_LIBC_INTRIN_PHSUBD_H_ +#define COSMOPOLITAN_LIBC_INTRIN_PHSUBD_H_ +#include "libc/intrin/macros.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +void phsubd(int32_t[4], const int32_t[4], const int32_t[4]); + +#define phsubd(A, B, C) \ + INTRIN_SSEVEX_X_X_X_(phsubd, SSSE3, "phsubd", INTRIN_NONCOMMUTATIVE, A, B, C) + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_INTRIN_PHSUBD_H_ */ diff --git a/test/libc/intrin/paddw_test.c b/libc/intrin/phsubsw.c similarity index 73% rename from test/libc/intrin/paddw_test.c rename to libc/intrin/phsubsw.c index 8c0c0b82..1f415451 100644 --- a/test/libc/intrin/paddw_test.c +++ b/libc/intrin/phsubsw.c @@ -17,39 +17,30 @@ │ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/intrin/paddsw.h" -#include "libc/intrin/paddw.h" +#include "libc/intrin/phsubsw.h" #include "libc/limits.h" -#include "libc/testlib/testlib.h" +#include "libc/macros.h" -TEST(paddw, test) { - short A[8] = {7}; - short B[8] = {11}; - short C[8]; - paddw(C, A, B); - EXPECT_EQ(18, C[0]); -} - -TEST(paddsw, test) { - short A[8] = {7}; - short B[8] = {11}; - short C[8]; - paddsw(C, A, B); - EXPECT_EQ(18, C[0]); -} - -TEST(paddw, testOverflow_wrapsAround) { - short A[8] = {SHRT_MAX, SHRT_MIN}; - short B[8] = {1, -1}; - paddw(A, A, B); - EXPECT_EQ(SHRT_MIN, A[0]); - EXPECT_EQ(SHRT_MAX, A[1]); -} - -TEST(paddsw, testOverflow_saturates) { - short A[8] = {SHRT_MAX, SHRT_MIN}; - short B[8] = {1, -1}; - paddsw(A, A, B); - EXPECT_EQ(SHRT_MAX, A[0]); - EXPECT_EQ(SHRT_MIN, A[1]); +/** + * Subtracts adjacent shorts w/ saturation. + * + * @param 𝑎 [w/o] receives reduced 𝑏 and 𝑐 concatenated + * @param 𝑏 [r/o] supplies four pairs of shorts + * @param 𝑐 [r/o] supplies four pairs of shorts + * @note goes fast w/ ssse3 (intel c. 2004, amd c. 2011) + * @mayalias + */ +void(phsubsw)(int16_t a[8], const int16_t b[8], const int16_t c[8]) { + int i, t[8]; + t[0] = b[0] - b[1]; + t[1] = b[2] - b[3]; + t[2] = b[4] - b[5]; + t[3] = b[6] - b[7]; + t[4] = c[0] - c[1]; + t[5] = c[2] - c[3]; + t[6] = c[4] - c[5]; + t[7] = c[6] - c[7]; + for (i = 0; i < 8; ++i) { + a[i] = MIN(SHRT_MAX, MAX(SHRT_MIN, t[i])); + } } diff --git a/libc/intrin/phsubsw.h b/libc/intrin/phsubsw.h new file mode 100644 index 00000000..8c81d749 --- /dev/null +++ b/libc/intrin/phsubsw.h @@ -0,0 +1,15 @@ +#ifndef COSMOPOLITAN_LIBC_INTRIN_PHSUBSW_H_ +#define COSMOPOLITAN_LIBC_INTRIN_PHSUBSW_H_ +#include "libc/intrin/macros.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +void phsubsw(int16_t[8], const int16_t[8], const int16_t[8]); + +#define phsubsw(A, B, C) \ + INTRIN_SSEVEX_X_X_X_(phsubsw, SSSE3, "phsubsw", INTRIN_NONCOMMUTATIVE, A, B, \ + C) + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_INTRIN_PHSUBSW_H_ */ diff --git a/libc/intrin/phsubw.c b/libc/intrin/phsubw.c new file mode 100644 index 00000000..4eb9afd5 --- /dev/null +++ b/libc/intrin/phsubw.c @@ -0,0 +1,43 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/phsubw.h" +#include "libc/str/str.h" + +/** + * Subtracts adjacent 16-bit integers. + * + * @param 𝑎 [w/o] receives reduced 𝑏 and 𝑐 concatenated + * @param 𝑏 [r/o] supplies four pairs of shorts + * @param 𝑐 [r/o] supplies four pairs of shorts + * @note goes fast w/ ssse3 (intel c. 2004, amd c. 2011) + * @mayalias + */ +void(phsubw)(int16_t a[8], const int16_t b[8], const int16_t c[8]) { + int16_t t[8]; + t[0] = b[0] - b[1]; + t[1] = b[2] - b[3]; + t[2] = b[4] - b[5]; + t[3] = b[6] - b[7]; + t[4] = c[0] - c[1]; + t[5] = c[2] - c[3]; + t[6] = c[4] - c[5]; + t[7] = c[6] - c[7]; + memcpy(a, t, sizeof(t)); +} diff --git a/libc/intrin/phsubw.h b/libc/intrin/phsubw.h new file mode 100644 index 00000000..0c21fa05 --- /dev/null +++ b/libc/intrin/phsubw.h @@ -0,0 +1,14 @@ +#ifndef COSMOPOLITAN_LIBC_INTRIN_PHSUBW_H_ +#define COSMOPOLITAN_LIBC_INTRIN_PHSUBW_H_ +#include "libc/intrin/macros.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +void phsubw(int16_t[8], const int16_t[8], const int16_t[8]); + +#define phsubw(A, B, C) \ + INTRIN_SSEVEX_X_X_X_(phsubw, SSSE3, "phsubw", INTRIN_NONCOMMUTATIVE, A, B, C) + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_INTRIN_PHSUBW_H_ */ diff --git a/libc/intrin/pmaddubsw.c b/libc/intrin/pmaddubsw.c index 1eed8d7f..1931b26a 100644 --- a/libc/intrin/pmaddubsw.c +++ b/libc/intrin/pmaddubsw.c @@ -20,6 +20,7 @@ #include "libc/intrin/pmaddubsw.h" #include "libc/limits.h" #include "libc/macros.h" +#include "libc/str/str.h" /** * Multiplies bytes and adds adjacent results w/ short saturation. @@ -33,9 +34,8 @@ * @note greatest simd op, like, ever * @mayalias */ -void(pmaddubsw)(short w[8], const unsigned char b[16], - const signed char c[16]) { - int i; +void(pmaddubsw)(int16_t w[8], const uint8_t b[16], const int8_t c[16]) { + unsigned i; for (i = 0; i < 8; ++i) { w[i] = MIN(SHRT_MAX, MAX(SHRT_MIN, (c[i * 2 + 0] * b[i * 2 + 0] + c[i * 2 + 1] * b[i * 2 + 1]))); diff --git a/libc/intrin/pmaddubsw.h b/libc/intrin/pmaddubsw.h index 498a7ad4..c5224e1c 100644 --- a/libc/intrin/pmaddubsw.h +++ b/libc/intrin/pmaddubsw.h @@ -2,12 +2,14 @@ #define COSMOPOLITAN_LIBC_INTRIN_PMADDUBSW_H_ #include "libc/intrin/macros.h" #if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ -void pmaddubsw(short[8], const unsigned char[16], const signed char[16]); +void pmaddubsw(int16_t[8], const uint8_t[16], const int8_t[16]); #define pmaddubsw(W, B, C) \ INTRIN_SSEVEX_X_X_X_(pmaddubsw, SSSE3, "pmaddubsw", INTRIN_NONCOMMUTATIVE, \ W, B, C) +COSMOPOLITAN_C_END_ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ #endif /* COSMOPOLITAN_LIBC_INTRIN_PMADDUBSW_H_ */ diff --git a/libc/intrin/pmaddwd.c b/libc/intrin/pmaddwd.c new file mode 100644 index 00000000..05b60f36 --- /dev/null +++ b/libc/intrin/pmaddwd.c @@ -0,0 +1,35 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/pmaddwd.h" + +/** + * Multiplies 16-bit signed integers and adds adjacent results. + * + * @param 𝑎 [w/o] receives result + * @param 𝑏 [r/o] supplies first input vector + * @param 𝑐 [r/o] supplies second input vector + * @mayalias + */ +void(pmaddwd)(int32_t a[4], const int16_t b[8], const int16_t c[8]) { + unsigned i; + for (i = 0; i < 4; ++i) { + a[i] = b[i * 2] * c[i * 2] + b[i * 2 + 1] * c[i * 2 + 1]; + } +} diff --git a/libc/intrin/pmaddwd.h b/libc/intrin/pmaddwd.h new file mode 100644 index 00000000..45ae5dfb --- /dev/null +++ b/libc/intrin/pmaddwd.h @@ -0,0 +1,14 @@ +#ifndef COSMOPOLITAN_LIBC_INTRIN_PMADDWD_H_ +#define COSMOPOLITAN_LIBC_INTRIN_PMADDWD_H_ +#include "libc/intrin/macros.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +void pmaddwd(int32_t[4], const int16_t[8], const int16_t[8]); + +#define pmaddwd(A, B, C) \ + INTRIN_SSEVEX_X_X_X_(pmaddwd, SSE2, "pmaddwd", INTRIN_COMMUTATIVE, A, B, C) + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_INTRIN_PMADDWD_H_ */ diff --git a/libc/intrin/pmaxsw.c b/libc/intrin/pmaxsw.c new file mode 100644 index 00000000..54a42571 --- /dev/null +++ b/libc/intrin/pmaxsw.c @@ -0,0 +1,39 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/pmaxsw.h" +#include "libc/macros.h" +#include "libc/str/str.h" + +/** + * Gets maximum of signed 16-bit integers. + * + * @param 𝑎 [w/o] receives result + * @param 𝑏 [r/o] supplies first input vector + * @param 𝑐 [r/o] supplies second input vector + * @mayalias + */ +void(pmaxsw)(int16_t a[8], const int16_t b[8], const int16_t c[8]) { + unsigned i; + int16_t r[8]; + for (i = 0; i < 8; ++i) { + r[i] = MAX(b[i], c[i]); + } + memcpy(a, r, 16); +} diff --git a/libc/intrin/pmaxsw.h b/libc/intrin/pmaxsw.h new file mode 100644 index 00000000..406a607b --- /dev/null +++ b/libc/intrin/pmaxsw.h @@ -0,0 +1,14 @@ +#ifndef COSMOPOLITAN_LIBC_INTRIN_PMAXSW_H_ +#define COSMOPOLITAN_LIBC_INTRIN_PMAXSW_H_ +#include "libc/intrin/macros.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +void pmaxsw(int16_t[8], const int16_t[8], const int16_t[8]); + +#define pmaxsw(A, B, C) \ + INTRIN_SSEVEX_X_X_X_(pmaxsw, SSE2, "pmaxsw", INTRIN_COMMUTATIVE, A, B, C) + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_INTRIN_PMAXSW_H_ */ diff --git a/libc/intrin/pmaxub.c b/libc/intrin/pmaxub.c new file mode 100644 index 00000000..69e1e9f8 --- /dev/null +++ b/libc/intrin/pmaxub.c @@ -0,0 +1,37 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/pmaxub.h" +#include "libc/macros.h" + +/** + * Returns minimum of 8-bit unsigned integers. + * + * @param 𝑎 [w/o] receives result + * @param 𝑏 [r/o] supplies first input vector + * @param 𝑐 [r/o] supplies second input vector + * @mayalias + */ +void(pmaxub)(unsigned char a[16], const unsigned char b[16], + const unsigned char c[16]) { + unsigned i; + for (i = 0; i < 16; ++i) { + a[i] = MAX(b[i], c[i]); + } +} diff --git a/libc/intrin/pmaxub.h b/libc/intrin/pmaxub.h new file mode 100644 index 00000000..939bbea5 --- /dev/null +++ b/libc/intrin/pmaxub.h @@ -0,0 +1,15 @@ +#ifndef COSMOPOLITAN_LIBC_INTRIN_PMAXUB_H_ +#define COSMOPOLITAN_LIBC_INTRIN_PMAXUB_H_ +#include "libc/intrin/macros.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +void pmaxub(unsigned char[16], const unsigned char[16], + const unsigned char[16]); + +#define pmaxub(A, B, C) \ + INTRIN_SSEVEX_X_X_X_(pmaxub, SSE2, "pmaxub", INTRIN_COMMUTATIVE, A, B, C) + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_INTRIN_PMAXUB_H_ */ diff --git a/libc/intrin/pminsw.c b/libc/intrin/pminsw.c new file mode 100644 index 00000000..e650ca8b --- /dev/null +++ b/libc/intrin/pminsw.c @@ -0,0 +1,39 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/pminsw.h" +#include "libc/macros.h" +#include "libc/str/str.h" + +/** + * Gets minimum of signed 16-bit integers. + * + * @param 𝑎 [w/o] receives result + * @param 𝑏 [r/o] supplies first input vector + * @param 𝑐 [r/o] supplies second input vector + * @mayalias + */ +void(pminsw)(int16_t a[8], const int16_t b[8], const int16_t c[8]) { + unsigned i; + int16_t r[8]; + for (i = 0; i < 8; ++i) { + r[i] = MIN(b[i], c[i]); + } + memcpy(a, r, 16); +} diff --git a/libc/intrin/pminsw.h b/libc/intrin/pminsw.h new file mode 100644 index 00000000..6675076f --- /dev/null +++ b/libc/intrin/pminsw.h @@ -0,0 +1,14 @@ +#ifndef COSMOPOLITAN_LIBC_INTRIN_PMINSW_H_ +#define COSMOPOLITAN_LIBC_INTRIN_PMINSW_H_ +#include "libc/intrin/macros.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +void pminsw(int16_t[8], const int16_t[8], const int16_t[8]); + +#define pminsw(A, B, C) \ + INTRIN_SSEVEX_X_X_X_(pminsw, SSE2, "pminsw", INTRIN_COMMUTATIVE, A, B, C) + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_INTRIN_PMINSW_H_ */ diff --git a/libc/intrin/pminub.c b/libc/intrin/pminub.c new file mode 100644 index 00000000..db9474af --- /dev/null +++ b/libc/intrin/pminub.c @@ -0,0 +1,37 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/pminub.h" +#include "libc/macros.h" + +/** + * Returns minimum of 8-bit unsigned integers. + * + * @param 𝑎 [w/o] receives result + * @param 𝑏 [r/o] supplies first input vector + * @param 𝑐 [r/o] supplies second input vector + * @mayalias + */ +void(pminub)(unsigned char a[16], const unsigned char b[16], + const unsigned char c[16]) { + unsigned i; + for (i = 0; i < 16; ++i) { + a[i] = MIN(b[i], c[i]); + } +} diff --git a/libc/intrin/pminub.h b/libc/intrin/pminub.h new file mode 100644 index 00000000..8f7e29d3 --- /dev/null +++ b/libc/intrin/pminub.h @@ -0,0 +1,15 @@ +#ifndef COSMOPOLITAN_LIBC_INTRIN_PMINUB_H_ +#define COSMOPOLITAN_LIBC_INTRIN_PMINUB_H_ +#include "libc/intrin/macros.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +void pminub(unsigned char[16], const unsigned char[16], + const unsigned char[16]); + +#define pminub(A, B, C) \ + INTRIN_SSEVEX_X_X_X_(pminub, SSE2, "pminub", INTRIN_COMMUTATIVE, A, B, C) + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_INTRIN_PMINUB_H_ */ diff --git a/libc/runtime/exit.c b/libc/intrin/pmovmskb.c similarity index 90% rename from libc/runtime/exit.c rename to libc/intrin/pmovmskb.c index 083cb574..5dcee3f0 100644 --- a/libc/runtime/exit.c +++ b/libc/intrin/pmovmskb.c @@ -17,14 +17,12 @@ │ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/runtime/internal.h" -#include "libc/runtime/runtime.h" +#include "libc/intrin/pmovmskb.h" -/** - * Exits program with grace. - */ -noreturn textexit void exit(int rc) { - __cxa_finalize(NULL); - _exit(rc); - unreachable; +uint32_t(pmovmskb)(const uint8_t p[16]) { + uint32_t i, m; + for (m = i = 0; i < 16; ++i) { + if (p[i] & 0x80) m |= 1 << i; + } + return m; } diff --git a/libc/intrin/pmovmskb.h b/libc/intrin/pmovmskb.h new file mode 100644 index 00000000..a90039ee --- /dev/null +++ b/libc/intrin/pmovmskb.h @@ -0,0 +1,27 @@ +#ifndef COSMOPOLITAN_LIBC_INTRIN_PMOVMSKB_H_ +#define COSMOPOLITAN_LIBC_INTRIN_PMOVMSKB_H_ +#include "libc/intrin/macros.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +uint32_t pmovmskb(const uint8_t[16]); + +#define pmovmskb(A) \ + ({ \ + uint32_t Mask; \ + if (!IsModeDbg() && X86_HAVE(SSE2)) { \ + const __intrin_xmm_t *Xmm = (const __intrin_xmm_t *)(A); \ + if (!X86_NEED(AVX)) { \ + asm("pmovmskb\t%1,%0" : "=r"(Mask) : "x"(*Xmm)); \ + } else { \ + asm("vpmovmskb\t%1,%0" : "=r"(Mask) : "x"(*Xmm)); \ + } \ + } else { \ + Mask = pmovmskb(A); \ + } \ + Mask; \ + }) + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_INTRIN_PMOVMSKB_H_ */ diff --git a/libc/intrin/pmulhrsw.c b/libc/intrin/pmulhrsw.c index 8afe6832..ceea7a75 100644 --- a/libc/intrin/pmulhrsw.c +++ b/libc/intrin/pmulhrsw.c @@ -18,17 +18,19 @@ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/intrin/pmulhrsw.h" +#include "libc/str/str.h" /** * Multiplies Q15 numbers. * + * @note goes fast w/ ssse3 (intel c. 2004, amd c. 2011) * @note a.k.a. packed multiply high w/ round & scale * @see Q2F(15,𝑥), F2Q(15,𝑥) * @mayalias */ -void(pmulhrsw)(short a[8], const short b[8], const short c[8]) { - int i; - for (i = 0; i < 8; ++i) { - a[i] = (((b[i] * c[i]) >> 14) + 1) >> 1; - } +void(pmulhrsw)(int16_t a[8], const int16_t b[8], const int16_t c[8]) { + unsigned i; + int16_t r[8]; + for (i = 0; i < 8; ++i) r[i] = (((b[i] * c[i]) >> 14) + 1) >> 1; + memcpy(a, r, 16); } diff --git a/libc/intrin/pmulhrsw.h b/libc/intrin/pmulhrsw.h index f642a007..7c133cae 100644 --- a/libc/intrin/pmulhrsw.h +++ b/libc/intrin/pmulhrsw.h @@ -2,11 +2,13 @@ #define COSMOPOLITAN_LIBC_INTRIN_PMULHRSW_H_ #include "libc/intrin/macros.h" #if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ -void pmulhrsw(short a[8], const short b[8], const short c[8]); +void pmulhrsw(int16_t a[8], const int16_t b[8], const int16_t c[8]); #define pmulhrsw(A, B, C) \ INTRIN_SSEVEX_X_X_X_(pmulhrsw, SSSE3, "pmulhrsw", INTRIN_COMMUTATIVE, A, B, C) +COSMOPOLITAN_C_END_ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ #endif /* COSMOPOLITAN_LIBC_INTRIN_PMULHRSW_H_ */ diff --git a/libc/intrin/pmulhuw.c b/libc/intrin/pmulhuw.c new file mode 100644 index 00000000..1f68fdc1 --- /dev/null +++ b/libc/intrin/pmulhuw.c @@ -0,0 +1,38 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/pmulhuw.h" +#include "libc/str/str.h" + +/** + * Multiplies 16-bit unsigned integers and stores high word. + * + * @param 𝑎 [w/o] receives result + * @param 𝑏 [r/o] supplies first input vector + * @param 𝑐 [r/o] supplies second input vector + * @mayalias + */ +void(pmulhuw)(uint16_t a[8], const uint16_t b[8], const uint16_t c[8]) { + unsigned i; + uint16_t r[8]; + for (i = 0; i < 8; ++i) { + r[i] = ((b[i] * c[i]) & 0xffff0000) >> 16; + } + memcpy(a, r, 16); +} diff --git a/libc/intrin/pmulhuw.h b/libc/intrin/pmulhuw.h new file mode 100644 index 00000000..341dd20a --- /dev/null +++ b/libc/intrin/pmulhuw.h @@ -0,0 +1,14 @@ +#ifndef COSMOPOLITAN_LIBC_INTRIN_PMULHUW_H_ +#define COSMOPOLITAN_LIBC_INTRIN_PMULHUW_H_ +#include "libc/intrin/macros.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +void pmulhuw(uint16_t[8], const uint16_t[8], const uint16_t[8]); + +#define pmulhuw(A, B, C) \ + INTRIN_SSEVEX_X_X_X_(pmulhuw, SSE2, "pmulhuw", INTRIN_COMMUTATIVE, A, B, C) + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_INTRIN_PMULHUW_H_ */ diff --git a/libc/intrin/pmulhw.c b/libc/intrin/pmulhw.c new file mode 100644 index 00000000..dcd8676f --- /dev/null +++ b/libc/intrin/pmulhw.c @@ -0,0 +1,38 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/pmulhw.h" +#include "libc/str/str.h" + +/** + * Multiplies 16-bit signed integers and stores high word. + * + * @param 𝑎 [w/o] receives result + * @param 𝑏 [r/o] supplies first input vector + * @param 𝑐 [r/o] supplies second input vector + * @mayalias + */ +void(pmulhw)(int16_t a[8], const int16_t b[8], const int16_t c[8]) { + unsigned i; + int16_t r[8]; + for (i = 0; i < 8; ++i) { + r[i] = ((b[i] * c[i]) & 0xffff0000) >> 16; + } + memcpy(a, r, 16); +} diff --git a/libc/intrin/pmulhw.h b/libc/intrin/pmulhw.h new file mode 100644 index 00000000..ee405a7d --- /dev/null +++ b/libc/intrin/pmulhw.h @@ -0,0 +1,14 @@ +#ifndef COSMOPOLITAN_LIBC_INTRIN_PMULHW_H_ +#define COSMOPOLITAN_LIBC_INTRIN_PMULHW_H_ +#include "libc/intrin/macros.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +void pmulhw(int16_t[8], const int16_t[8], const int16_t[8]); + +#define pmulhw(A, B, C) \ + INTRIN_SSEVEX_X_X_X_(pmulhw, SSE2, "pmulhw", INTRIN_COMMUTATIVE, A, B, C) + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_INTRIN_PMULHW_H_ */ diff --git a/libc/intrin/pmulld.c b/libc/intrin/pmulld.c new file mode 100644 index 00000000..34566a10 --- /dev/null +++ b/libc/intrin/pmulld.c @@ -0,0 +1,39 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/pmulld.h" +#include "libc/str/str.h" + +/** + * Multiplies 32-bit signed integers. + * + * @param 𝑎 [w/o] receives result + * @param 𝑏 [r/o] supplies first input vector + * @param 𝑐 [r/o] supplies second input vector + * @see pmuludq() + * @mayalias + */ +void(pmulld)(int32_t a[4], const int32_t b[4], const int32_t c[4]) { + unsigned i; + int32_t r[4]; + for (i = 0; i < 4; ++i) { + r[i] = b[i] * c[i]; + } + memcpy(a, r, 16); +} diff --git a/libc/intrin/pmulld.h b/libc/intrin/pmulld.h new file mode 100644 index 00000000..8365f5b1 --- /dev/null +++ b/libc/intrin/pmulld.h @@ -0,0 +1,14 @@ +#ifndef COSMOPOLITAN_LIBC_INTRIN_PMULLD_H_ +#define COSMOPOLITAN_LIBC_INTRIN_PMULLD_H_ +#include "libc/intrin/macros.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +void pmulld(int32_t[4], const int32_t[4], const int32_t[4]); + +#define pmulld(A, B, C) \ + INTRIN_SSEVEX_X_X_X_(pmulld, SSE4_1, "pmulld", INTRIN_COMMUTATIVE, A, B, C) + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_INTRIN_PMULLD_H_ */ diff --git a/libc/intrin/pmullw.c b/libc/intrin/pmullw.c new file mode 100644 index 00000000..e5ae3f21 --- /dev/null +++ b/libc/intrin/pmullw.c @@ -0,0 +1,38 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/pmullw.h" +#include "libc/str/str.h" + +/** + * Multiplies 16-bit signed integers. + * + * @param 𝑎 [w/o] receives result + * @param 𝑏 [r/o] supplies first input vector + * @param 𝑐 [r/o] supplies second input vector + * @mayalias + */ +void(pmullw)(int16_t a[8], const int16_t b[8], const int16_t c[8]) { + unsigned i; + int16_t r[8]; + for (i = 0; i < 8; ++i) { + r[i] = b[i] * c[i]; + } + memcpy(a, r, 16); +} diff --git a/libc/intrin/pmullw.h b/libc/intrin/pmullw.h new file mode 100644 index 00000000..991653a8 --- /dev/null +++ b/libc/intrin/pmullw.h @@ -0,0 +1,14 @@ +#ifndef COSMOPOLITAN_LIBC_INTRIN_PMULLW_H_ +#define COSMOPOLITAN_LIBC_INTRIN_PMULLW_H_ +#include "libc/intrin/macros.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +void pmullw(int16_t[8], const int16_t[8], const int16_t[8]); + +#define pmullw(A, B, C) \ + INTRIN_SSEVEX_X_X_X_(pmullw, SSE2, "pmullw", INTRIN_COMMUTATIVE, A, B, C) + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_INTRIN_PMULLW_H_ */ diff --git a/libc/intrin/pmuludq.c b/libc/intrin/pmuludq.c new file mode 100644 index 00000000..589c652a --- /dev/null +++ b/libc/intrin/pmuludq.c @@ -0,0 +1,37 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/pmuludq.h" +#include "libc/str/str.h" + +/** + * Multiplies 32-bit unsigned integers w/ promotion. + * + * @param 𝑎 [w/o] receives result + * @param 𝑏 [r/o] supplies first input vector + * @param 𝑐 [r/o] supplies second input vector + * @see pmulld() + * @mayalias + */ +void(pmuludq)(uint64_t a[2], const uint32_t b[4], const uint32_t c[4]) { + unsigned i; + for (i = 0; i < 2; ++i) { + a[i] = (uint64_t)b[i * 2] * c[i * 2]; + } +} diff --git a/libc/intrin/pmuludq.h b/libc/intrin/pmuludq.h new file mode 100644 index 00000000..849eeb40 --- /dev/null +++ b/libc/intrin/pmuludq.h @@ -0,0 +1,14 @@ +#ifndef COSMOPOLITAN_LIBC_INTRIN_PMULUDQ_H_ +#define COSMOPOLITAN_LIBC_INTRIN_PMULUDQ_H_ +#include "libc/intrin/macros.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +void pmuludq(uint64_t[2], const uint32_t[4], const uint32_t[4]); + +#define pmuludq(A, B, C) \ + INTRIN_SSEVEX_X_X_X_(pmuludq, SSE2, "pmuludq", INTRIN_COMMUTATIVE, A, B, C) + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_INTRIN_PMULUDQ_H_ */ diff --git a/libc/intrin/por.c b/libc/intrin/por.c new file mode 100644 index 00000000..58fc1ba1 --- /dev/null +++ b/libc/intrin/por.c @@ -0,0 +1,35 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/por.h" + +/** + * Ors 128-bit integers. + * + * @param 𝑎 [w/o] receives result + * @param 𝑏 [r/o] supplies first input vector + * @param 𝑐 [r/o] supplies second input vector + * @mayalias + */ +void(por)(uint64_t a[2], const uint64_t b[2], const uint64_t c[2]) { + unsigned i; + for (i = 0; i < 2; ++i) { + a[i] = b[i] | c[i]; + } +} diff --git a/libc/intrin/por.h b/libc/intrin/por.h new file mode 100644 index 00000000..62fede4b --- /dev/null +++ b/libc/intrin/por.h @@ -0,0 +1,14 @@ +#ifndef COSMOPOLITAN_LIBC_INTRIN_POR_H_ +#define COSMOPOLITAN_LIBC_INTRIN_POR_H_ +#include "libc/intrin/macros.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +void por(uint64_t[2], const uint64_t[2], const uint64_t[2]); + +#define por(A, B, C) \ + INTRIN_SSEVEX_X_X_X_(por, SSE2, "por", INTRIN_COMMUTATIVE, A, B, C) + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_INTRIN_POR_H_ */ diff --git a/libc/intrin/psadbw.c b/libc/intrin/psadbw.c new file mode 100644 index 00000000..5df661dc --- /dev/null +++ b/libc/intrin/psadbw.c @@ -0,0 +1,37 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/psadbw.h" +#include "libc/macros.h" + +/** + * Computes sum of absolute differences. + * + * @param 𝑎 [w/o] receives sum at first index and rest is zero'd + * @param 𝑏 [r/o] is your first unsigned byte array + * @param 𝑐 [r/o] is your second unsigned byte array + * @mayalias + */ +void(psadbw)(uint64_t a[2], const uint8_t b[16], const uint8_t c[16]) { + unsigned i, x, y; + for (x = i = 0; i < 8; ++i) x += ABS(b[i] - c[i]); + for (y = 0; i < 16; ++i) y += ABS(b[i] - c[i]); + a[0] = x; + a[1] = y; +} diff --git a/libc/intrin/psadbw.h b/libc/intrin/psadbw.h new file mode 100644 index 00000000..2eb739af --- /dev/null +++ b/libc/intrin/psadbw.h @@ -0,0 +1,14 @@ +#ifndef COSMOPOLITAN_LIBC_INTRIN_PSADBW_H_ +#define COSMOPOLITAN_LIBC_INTRIN_PSADBW_H_ +#include "libc/intrin/macros.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +void psadbw(uint64_t[2], const uint8_t[16], const uint8_t[16]); + +#define psadbw(A, B, C) \ + INTRIN_SSEVEX_X_X_X_(psadbw, SSE2, "psadbw", INTRIN_COMMUTATIVE, A, B, C) + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_INTRIN_PSADBW_H_ */ diff --git a/libc/intrin/pshufb.c b/libc/intrin/pshufb.c new file mode 100644 index 00000000..40cad591 --- /dev/null +++ b/libc/intrin/pshufb.c @@ -0,0 +1,38 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/pshufb.h" +#include "libc/str/str.h" + +/** + * Shuffles and/or clears 8-bit integers. + * + * @param 𝑎 [w/o] receives result + * @param 𝑏 [r/o] supplies byte vector + * @param 𝑐 [r/o] supplies mask vector + * @note goes fast w/ ssse3 (intel c. 2004, amd c. 2011) + * @note doesn't perfectly emulate mmx + * @mayalias + */ +void(pshufb)(uint8_t a[16], const uint8_t b[16], const uint8_t c[16]) { + unsigned i; + uint8_t r[16]; + for (i = 0; i < 16; ++i) r[i] = (c[i] & 0x80) ? 0 : b[c[i] & 0x0F]; + memcpy(a, r, 16); +} diff --git a/libc/intrin/pshufb.h b/libc/intrin/pshufb.h new file mode 100644 index 00000000..8adefc6a --- /dev/null +++ b/libc/intrin/pshufb.h @@ -0,0 +1,13 @@ +#ifndef COSMOPOLITAN_LIBC_INTRIN_PSHUFB_H_ +#define COSMOPOLITAN_LIBC_INTRIN_PSHUFB_H_ +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +void pshufb(uint8_t[16], const uint8_t[16], const uint8_t[16]); + +#define pshufb(A, B, C) \ + INTRIN_SSEVEX_X_X_X_(pshufb, SSSE3, "pshufb", INTRIN_NONCOMMUTATIVE, A, B, C) + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_INTRIN_PSHUFB_H_ */ diff --git a/libc/intrin/pshufd.c b/libc/intrin/pshufd.c new file mode 100644 index 00000000..f2178195 --- /dev/null +++ b/libc/intrin/pshufd.c @@ -0,0 +1,37 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/pshufd.h" + +/** + * Shuffles int vector. + * @param 𝑚 needs to be a literal, constexpr, or embedding + * @mayalias + */ +void(pshufd)(int32_t b[4], const int32_t a[4], uint8_t m) { + int32_t t[4]; + t[0] = a[(m & 0b00000011) >> 0]; + t[1] = a[(m & 0b00001100) >> 2]; + t[2] = a[(m & 0b00110000) >> 4]; + t[3] = a[(m & 0b11000000) >> 6]; + b[0] = t[0]; + b[1] = t[1]; + b[2] = t[2]; + b[3] = t[3]; +} diff --git a/libc/intrin/pshufd.h b/libc/intrin/pshufd.h new file mode 100644 index 00000000..e7dd60dd --- /dev/null +++ b/libc/intrin/pshufd.h @@ -0,0 +1,13 @@ +#ifndef COSMOPOLITAN_LIBC_INTRIN_PSHUFD_H_ +#define COSMOPOLITAN_LIBC_INTRIN_PSHUFD_H_ +#include "libc/intrin/macros.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +void pshufd(int32_t[4], const int32_t[4], uint8_t); + +#define pshufd(A, B, I) INTRIN_SSEVEX_X_X_I_(pshufd, SSE2, "pshufd", A, B, I) + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_INTRIN_PSHUFD_H_ */ diff --git a/libc/intrin/pshufhw.c b/libc/intrin/pshufhw.c new file mode 100644 index 00000000..d41b9e36 --- /dev/null +++ b/libc/intrin/pshufhw.c @@ -0,0 +1,41 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/pshufhw.h" + +/** + * Shuffles lower half of word vector. + * @param 𝑚 needs to be a literal, constexpr, or embedding + * @mayalias + */ +void(pshufhw)(int16_t b[8], const int16_t a[8], uint8_t m) { + int16_t t[4]; + t[0] = a[4 + ((m & 0b00000011) >> 0)]; + t[1] = a[4 + ((m & 0b00001100) >> 2)]; + t[2] = a[4 + ((m & 0b00110000) >> 4)]; + t[3] = a[4 + ((m & 0b11000000) >> 6)]; + b[0] = a[0]; + b[1] = a[1]; + b[2] = a[2]; + b[3] = a[3]; + b[4] = t[0]; + b[5] = t[1]; + b[6] = t[2]; + b[7] = t[3]; +} diff --git a/libc/intrin/pshufhw.h b/libc/intrin/pshufhw.h new file mode 100644 index 00000000..051c1b6f --- /dev/null +++ b/libc/intrin/pshufhw.h @@ -0,0 +1,13 @@ +#ifndef COSMOPOLITAN_LIBC_INTRIN_PSHUFHW_H_ +#define COSMOPOLITAN_LIBC_INTRIN_PSHUFHW_H_ +#include "libc/intrin/macros.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +void pshufhw(int16_t[8], const int16_t[8], uint8_t); + +#define pshufhw(A, B, I) INTRIN_SSEVEX_X_X_I_(pshufhw, SSE2, "pshufhw", A, B, I) + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_INTRIN_PSHUFHW_H_ */ diff --git a/libc/intrin/pshuflw.c b/libc/intrin/pshuflw.c new file mode 100644 index 00000000..8de1a2f9 --- /dev/null +++ b/libc/intrin/pshuflw.c @@ -0,0 +1,41 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/pshuflw.h" + +/** + * Shuffles lower half of word vector. + * @param 𝑚 needs to be a literal, constexpr, or embedding + * @mayalias + */ +void(pshuflw)(int16_t b[8], const int16_t a[8], uint8_t m) { + int16_t t[4]; + t[0] = a[(m & 0b00000011) >> 0]; + t[1] = a[(m & 0b00001100) >> 2]; + t[2] = a[(m & 0b00110000) >> 4]; + t[3] = a[(m & 0b11000000) >> 6]; + b[0] = t[0]; + b[1] = t[1]; + b[2] = t[2]; + b[3] = t[3]; + b[4] = a[4]; + b[5] = a[5]; + b[6] = a[6]; + b[7] = a[7]; +} diff --git a/libc/intrin/pshuflw.h b/libc/intrin/pshuflw.h new file mode 100644 index 00000000..1c2457b6 --- /dev/null +++ b/libc/intrin/pshuflw.h @@ -0,0 +1,13 @@ +#ifndef COSMOPOLITAN_LIBC_INTRIN_PSHUFLW_H_ +#define COSMOPOLITAN_LIBC_INTRIN_PSHUFLW_H_ +#include "libc/intrin/macros.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +void pshuflw(int16_t[8], const int16_t[8], uint8_t); + +#define pshuflw(A, B, I) INTRIN_SSEVEX_X_X_I_(pshuflw, SSE2, "pshuflw", A, B, I) + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_INTRIN_PSHUFLW_H_ */ diff --git a/libc/intrin/pshufw.c b/libc/intrin/pshufw.c new file mode 100644 index 00000000..25198e6f --- /dev/null +++ b/libc/intrin/pshufw.c @@ -0,0 +1,37 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/pshufw.h" + +/** + * Shuffles mmx vector. + * @param 𝑚 needs to be a literal, constexpr, or embedding + * @mayalias + */ +void(pshufw)(int16_t b[4], const int16_t a[4], uint8_t m) { + int16_t t[4]; + t[0] = a[(m & 0b00000011) >> 0]; + t[1] = a[(m & 0b00001100) >> 2]; + t[2] = a[(m & 0b00110000) >> 4]; + t[3] = a[(m & 0b11000000) >> 6]; + b[0] = t[0]; + b[1] = t[1]; + b[2] = t[2]; + b[3] = t[3]; +} diff --git a/libc/intrin/pshufw.h b/libc/intrin/pshufw.h new file mode 100644 index 00000000..d5c6eb19 --- /dev/null +++ b/libc/intrin/pshufw.h @@ -0,0 +1,10 @@ +#ifndef COSMOPOLITAN_LIBC_INTRIN_PSHUFW_H_ +#define COSMOPOLITAN_LIBC_INTRIN_PSHUFW_H_ +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +void pshufw(int16_t[4], const int16_t[4], uint8_t); + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_INTRIN_PSHUFW_H_ */ diff --git a/libc/intrin/psignb.c b/libc/intrin/psignb.c new file mode 100644 index 00000000..b60ccd68 --- /dev/null +++ b/libc/intrin/psignb.c @@ -0,0 +1,38 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/psignb.h" +#include "libc/str/str.h" + +/** + * Conditionally negates or zeroes signed bytes. + * @note goes fast w/ ssse3 (intel c. 2004, amd c. 2011) + */ +void(psignb)(int8_t a[16], const int8_t b[16], const int8_t c[16]) { + unsigned i; + for (i = 0; i < 16; ++i) { + if (!c[i]) { + a[i] = 0; + } else if (c[i] < 0) { + a[i] = -b[i]; + } else { + a[i] = b[i]; + } + } +} diff --git a/libc/intrin/psignb.h b/libc/intrin/psignb.h new file mode 100644 index 00000000..92f01321 --- /dev/null +++ b/libc/intrin/psignb.h @@ -0,0 +1,14 @@ +#ifndef COSMOPOLITAN_LIBC_INTRIN_PSIGNB_H_ +#define COSMOPOLITAN_LIBC_INTRIN_PSIGNB_H_ +#include "libc/intrin/macros.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +void psignb(int8_t[16], const int8_t[16], const int8_t[16]); + +#define psignb(A, B, C) \ + INTRIN_SSEVEX_X_X_X_(psignb, SSSE3, "psignb", INTRIN_NONCOMMUTATIVE, A, B, C) + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_INTRIN_PSIGNB_H_ */ diff --git a/libc/intrin/psignd.c b/libc/intrin/psignd.c new file mode 100644 index 00000000..33809e9b --- /dev/null +++ b/libc/intrin/psignd.c @@ -0,0 +1,38 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/psignd.h" +#include "libc/str/str.h" + +/** + * Conditionally negates or zeroes ints. + * @note goes fast w/ ssse3 (intel c. 2004, amd c. 2011) + */ +void(psignd)(int32_t a[4], const int32_t b[4], const int32_t c[4]) { + unsigned i; + for (i = 0; i < 4; ++i) { + if (!c[i]) { + a[i] = 0; + } else if (c[i] < 0) { + a[i] = -b[i]; + } else { + a[i] = b[i]; + } + } +} diff --git a/libc/intrin/psignd.h b/libc/intrin/psignd.h new file mode 100644 index 00000000..e22cb12a --- /dev/null +++ b/libc/intrin/psignd.h @@ -0,0 +1,14 @@ +#ifndef COSMOPOLITAN_LIBC_INTRIN_PSIGND_H_ +#define COSMOPOLITAN_LIBC_INTRIN_PSIGND_H_ +#include "libc/intrin/macros.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +void psignd(int32_t[4], const int32_t[4], const int32_t[4]); + +#define psignd(A, B, C) \ + INTRIN_SSEVEX_X_X_X_(psignd, SSSE3, "psignd", INTRIN_NONCOMMUTATIVE, A, B, C) + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_INTRIN_PSIGND_H_ */ diff --git a/libc/intrin/psignw.c b/libc/intrin/psignw.c new file mode 100644 index 00000000..a0020aaa --- /dev/null +++ b/libc/intrin/psignw.c @@ -0,0 +1,38 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/psignw.h" +#include "libc/str/str.h" + +/** + * Conditionally negates or zeroes shorts. + * @note goes fast w/ ssse3 (intel c. 2004, amd c. 2011) + */ +void(psignw)(int16_t a[8], const int16_t b[8], const int16_t c[8]) { + unsigned i; + for (i = 0; i < 8; ++i) { + if (!c[i]) { + a[i] = 0; + } else if (c[i] < 0) { + a[i] = -b[i]; + } else { + a[i] = b[i]; + } + } +} diff --git a/libc/intrin/psignw.h b/libc/intrin/psignw.h new file mode 100644 index 00000000..ad0dc50b --- /dev/null +++ b/libc/intrin/psignw.h @@ -0,0 +1,14 @@ +#ifndef COSMOPOLITAN_LIBC_INTRIN_PSIGNW_H_ +#define COSMOPOLITAN_LIBC_INTRIN_PSIGNW_H_ +#include "libc/intrin/macros.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +void psignw(int16_t[8], const int16_t[8], const int16_t[8]); + +#define psignw(A, B, C) \ + INTRIN_SSEVEX_X_X_X_(psignw, SSSE3, "psignw", INTRIN_NONCOMMUTATIVE, A, B, C) + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_INTRIN_PSIGNW_H_ */ diff --git a/libc/intrin/pslld.c b/libc/intrin/pslld.c new file mode 100644 index 00000000..be0c7224 --- /dev/null +++ b/libc/intrin/pslld.c @@ -0,0 +1,38 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/pslld.h" +#include "libc/str/str.h" + +/** + * Multiplies ints by two power. + * + * @note c needs to be a literal, asmconstexpr, or linkconstsym + * @mayalias + */ +void(pslld)(uint32_t a[4], const uint32_t b[4], unsigned char c) { + if (c <= 31) { + unsigned i; + for (i = 0; i < 4; ++i) { + a[i] = b[i] << c; + } + } else { + memset(a, 0, 16); + } +} diff --git a/libc/intrin/pslld.h b/libc/intrin/pslld.h new file mode 100644 index 00000000..845f8326 --- /dev/null +++ b/libc/intrin/pslld.h @@ -0,0 +1,16 @@ +#ifndef COSMOPOLITAN_LIBC_INTRIN_PSLLD_H_ +#define COSMOPOLITAN_LIBC_INTRIN_PSLLD_H_ +#include "libc/intrin/macros.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +void pslld(uint32_t[4], const uint32_t[4], unsigned char); +void pslldv(uint32_t[4], const uint32_t[4], const uint64_t[2]); + +#define pslld(A, B, I) INTRIN_SSEVEX_X_I_(pslld, SSE2, "pslld", A, B, I) +#define pslldv(A, B, C) \ + INTRIN_SSEVEX_X_X_X_(pslldv, SSE2, "pslld", INTRIN_NONCOMMUTATIVE, A, B, C) + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_INTRIN_PSLLD_H_ */ diff --git a/libc/intrin/pslldq.c b/libc/intrin/pslldq.c new file mode 100644 index 00000000..02807864 --- /dev/null +++ b/libc/intrin/pslldq.c @@ -0,0 +1,35 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/pslldq.h" +#include "libc/str/str.h" + +/** + * Shifts vector right by n bytes w/ zero-fill. + * + * @param a is input vector + * @param b receives output + * @mayalias + */ +void(pslldq)(uint8_t b[16], const uint8_t a[16], unsigned long n) { + unsigned i; + if (n > 16) n = 16; + memmove(b + n, a, 16 - n); + memset(b, 0, n); +} diff --git a/libc/intrin/pslldq.h b/libc/intrin/pslldq.h new file mode 100644 index 00000000..d7c19b3a --- /dev/null +++ b/libc/intrin/pslldq.h @@ -0,0 +1,37 @@ +#ifndef COSMOPOLITAN_LIBC_INTRIN_PSLLDQ_H_ +#define COSMOPOLITAN_LIBC_INTRIN_PSLLDQ_H_ +#include "libc/intrin/macros.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +void pslldq(uint8_t[16], const uint8_t[16], unsigned long); + +#ifndef __STRICT_ANSI__ +__intrin_xmm_t __pslldqs(__intrin_xmm_t); +#define pslldq(B, A, I) \ + do { \ + if (likely(!IsModeDbg() && X86_NEED(SSE) && X86_HAVE(SSE2))) { \ + __intrin_xmm_t *Xmm0 = (void *)(B); \ + const __intrin_xmm_t *Xmm1 = (const __intrin_xmm_t *)(A); \ + if (isconstant(I)) { \ + if (!X86_NEED(AVX)) { \ + asm("pslldq\t%1,%0" : "=x"(*Xmm0) : "i"(I), "0"(*Xmm1)); \ + } else { \ + asm("vpslldq\t%2,%1,%0" : "=x"(*Xmm0) : "x"(*Xmm1), "i"(I)); \ + } \ + } else { \ + unsigned long Vimm = (I); \ + typeof(__pslldqs) *Fn; \ + if (Vimm > 16) Vimm = 16; \ + Fn = (typeof(__pslldqs) *)((uintptr_t)&__pslldqs + Vimm * 8); \ + *Xmm0 = Fn(*Xmm1); \ + } \ + } else { \ + pslldq(B, A, I); \ + } \ + } while (0) +#endif + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_INTRIN_PSLLDQ_H_ */ diff --git a/libc/intrin/pslldqs.S b/libc/intrin/pslldqs.S new file mode 100644 index 00000000..1b7c3567 --- /dev/null +++ b/libc/intrin/pslldqs.S @@ -0,0 +1,94 @@ +/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│ +│vi: set et ft=asm ts=8 tw=8 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/macros.h" + +/ Jump table for pslldq() with non-constexpr immediate parameter. + .align 8 +__pslldqs: + pslldq $0,%xmm0 + ret + nop + nop + pslldq $1,%xmm0 + ret + nop + nop + pslldq $2,%xmm0 + ret + nop + nop + pslldq $3,%xmm0 + ret + nop + nop + pslldq $4,%xmm0 + ret + nop + nop + pslldq $5,%xmm0 + ret + nop + nop + pslldq $6,%xmm0 + ret + nop + nop + pslldq $7,%xmm0 + ret + nop + nop + pslldq $8,%xmm0 + ret + nop + nop + pslldq $9,%xmm0 + ret + nop + nop + pslldq $10,%xmm0 + ret + nop + nop + pslldq $11,%xmm0 + ret + nop + nop + pslldq $12,%xmm0 + ret + nop + nop + pslldq $13,%xmm0 + ret + nop + nop + pslldq $14,%xmm0 + ret + nop + nop + pslldq $15,%xmm0 + ret + nop + nop + pslldq $16,%xmm0 + ret + .if . - __pslldqs != 8 * 17 - 2 + .error "bad assemblage" + .endif + .endfn __pslldqs,globl diff --git a/libc/intrin/pslldv.c b/libc/intrin/pslldv.c new file mode 100644 index 00000000..1d5f5299 --- /dev/null +++ b/libc/intrin/pslldv.c @@ -0,0 +1,36 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/pslld.h" +#include "libc/str/str.h" + +/** + * Multiplies ints by two power. + * @mayalias + */ +void(pslldv)(uint32_t a[4], const uint32_t b[4], const uint64_t c[2]) { + unsigned i; + if (c[0] <= 31) { + for (i = 0; i < 4; ++i) { + a[i] = b[i] << c[0]; + } + } else { + memset(a, 0, 16); + } +} diff --git a/libc/intrin/psllq.c b/libc/intrin/psllq.c new file mode 100644 index 00000000..903b5e18 --- /dev/null +++ b/libc/intrin/psllq.c @@ -0,0 +1,38 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/psllq.h" +#include "libc/str/str.h" + +/** + * Multiplies longs by two power. + * + * @note c needs to be a literal, asmconstexpr, or linkconstsym + * @mayalias + */ +void(psllq)(uint64_t a[2], const uint64_t b[2], unsigned char c) { + unsigned i; + if (c <= 63) { + for (i = 0; i < 2; ++i) { + a[i] = b[i] << c; + } + } else { + memset(a, 0, 16); + } +} diff --git a/libc/intrin/psllq.h b/libc/intrin/psllq.h new file mode 100644 index 00000000..7c04acb0 --- /dev/null +++ b/libc/intrin/psllq.h @@ -0,0 +1,16 @@ +#ifndef COSMOPOLITAN_LIBC_INTRIN_PSLLQ_H_ +#define COSMOPOLITAN_LIBC_INTRIN_PSLLQ_H_ +#include "libc/intrin/macros.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +void psllq(uint64_t[2], const uint64_t[2], unsigned char); +void psllqv(uint64_t[2], const uint64_t[2], const uint64_t[2]); + +#define psllq(A, B, I) INTRIN_SSEVEX_X_I_(psllq, SSE2, "psllq", A, B, I) +#define psllqv(A, B, C) \ + INTRIN_SSEVEX_X_X_X_(psllqv, SSE2, "psllq", INTRIN_NONCOMMUTATIVE, A, B, C) + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_INTRIN_PSLLQ_H_ */ diff --git a/libc/intrin/psllqv.c b/libc/intrin/psllqv.c new file mode 100644 index 00000000..481475b5 --- /dev/null +++ b/libc/intrin/psllqv.c @@ -0,0 +1,36 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/psllq.h" +#include "libc/str/str.h" + +/** + * Multiplies longs by two power. + * @mayalias + */ +void(psllqv)(uint64_t a[2], const uint64_t b[2], const uint64_t c[2]) { + unsigned i; + if (c[0] <= 63) { + for (i = 0; i < 2; ++i) { + a[i] = b[i] << c[0]; + } + } else { + memset(a, 0, 16); + } +} diff --git a/libc/intrin/psllw.c b/libc/intrin/psllw.c new file mode 100644 index 00000000..e6c692fa --- /dev/null +++ b/libc/intrin/psllw.c @@ -0,0 +1,38 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/psllw.h" +#include "libc/str/str.h" + +/** + * Multiplies shorts by two power. + * + * @note c needs to be a literal, asmconstexpr, or linkconstsym + * @mayalias + */ +void(psllw)(uint16_t a[8], const uint16_t b[8], unsigned char c) { + unsigned i; + if (c <= 15) { + for (i = 0; i < 8; ++i) { + a[i] = b[i] << c; + } + } else { + memset(a, 0, 16); + } +} diff --git a/libc/intrin/psllw.h b/libc/intrin/psllw.h new file mode 100644 index 00000000..0a38ceef --- /dev/null +++ b/libc/intrin/psllw.h @@ -0,0 +1,16 @@ +#ifndef COSMOPOLITAN_LIBC_INTRIN_PSLLW_H_ +#define COSMOPOLITAN_LIBC_INTRIN_PSLLW_H_ +#include "libc/intrin/macros.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +void psllw(uint16_t[8], const uint16_t[8], unsigned char); +void psllwv(uint16_t[8], const uint16_t[8], const uint64_t[2]); + +#define psllw(A, B, I) INTRIN_SSEVEX_X_I_(psllw, SSE2, "psllw", A, B, I) +#define psllwv(A, B, C) \ + INTRIN_SSEVEX_X_X_X_(psllwv, SSE2, "psllw", INTRIN_NONCOMMUTATIVE, A, B, C) + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_INTRIN_PSLLW_H_ */ diff --git a/libc/intrin/psllwv.c b/libc/intrin/psllwv.c new file mode 100644 index 00000000..9089dad1 --- /dev/null +++ b/libc/intrin/psllwv.c @@ -0,0 +1,38 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/psllw.h" + +/** + * Multiplies shorts by two power. + * + * @mayalias + */ +void(psllwv)(uint16_t a[8], const uint16_t b[8], const uint64_t c[2]) { + unsigned i; + if (c[0] > 15) { + for (i = 0; i < 8; ++i) { + a[i] = 0; + } + } else { + for (i = 0; i < 8; ++i) { + a[i] = b[i] << c[0]; + } + } +} diff --git a/libc/intrin/psrad.c b/libc/intrin/psrad.c new file mode 100644 index 00000000..6c6e0a8c --- /dev/null +++ b/libc/intrin/psrad.c @@ -0,0 +1,40 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/psrad.h" + +/** + * Divides ints by two power. + * + * @note c needs to be a literal, asmconstexpr, or linkconstsym + * @note arithmetic shift right will sign extend negatives + * @mayalias + */ +void(psrad)(int32_t a[4], const int32_t b[4], unsigned char k) { + unsigned i, x, m; + if (k > 31) k = 31; + for (i = 0; i < 4; ++i) { + m = 0; + x = b[i]; + if (x & 0x80000000) m = ~(0xffffffffu >> k); + x >>= k; + x |= m; + a[i] = x; + } +} diff --git a/libc/intrin/psrad.h b/libc/intrin/psrad.h new file mode 100644 index 00000000..1a91b00c --- /dev/null +++ b/libc/intrin/psrad.h @@ -0,0 +1,16 @@ +#ifndef COSMOPOLITAN_LIBC_INTRIN_PSRAD_H_ +#define COSMOPOLITAN_LIBC_INTRIN_PSRAD_H_ +#include "libc/intrin/macros.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +void psrad(int32_t[4], const int32_t[4], unsigned char); +void psradv(int32_t[4], const int32_t[4], const uint64_t[2]); + +#define psrad(A, B, I) INTRIN_SSEVEX_X_I_(psrad, SSE2, "psrad", A, B, I) +#define psradv(A, B, C) \ + INTRIN_SSEVEX_X_X_X_(psradv, SSE2, "psrad", INTRIN_NONCOMMUTATIVE, A, B, C) + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_INTRIN_PSRAD_H_ */ diff --git a/libc/intrin/psradv.c b/libc/intrin/psradv.c new file mode 100644 index 00000000..08f2bac2 --- /dev/null +++ b/libc/intrin/psradv.c @@ -0,0 +1,40 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/psrad.h" + +/** + * Divides shorts by two powers. + * + * @note arithmetic shift right will sign extend negatives + * @mayalias + */ +void(psradv)(int32_t a[4], const int32_t b[4], const uint64_t c[2]) { + unsigned char k; + unsigned i, x, m; + k = c[0] > 31 ? 31 : c[0]; + for (i = 0; i < 4; ++i) { + m = 0; + x = b[i]; + if (x & 0x80000000u) m = ~(0xffffffffu >> k); + x >>= k; + x |= m; + a[i] = x & 0xffffffffu; + } +} diff --git a/libc/intrin/psraw.c b/libc/intrin/psraw.c new file mode 100644 index 00000000..cdcefc7e --- /dev/null +++ b/libc/intrin/psraw.c @@ -0,0 +1,40 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/psraw.h" + +/** + * Divides shorts by two power. + * + * @note c needs to be a literal, asmconstexpr, or linkconstsym + * @note arithmetic shift right will sign extend negatives + * @mayalias + */ +void(psraw)(int16_t a[8], const int16_t b[8], unsigned char k) { + unsigned i, x, m; + if (k > 15) k = 15; + for (i = 0; i < 8; ++i) { + m = 0; + x = b[i]; + if (x & 0x8000) m = ~(0xffff >> k); + x >>= k; + x |= m; + a[i] = x; + } +} diff --git a/libc/intrin/psraw.h b/libc/intrin/psraw.h index 4b03c5db..59e6e281 100644 --- a/libc/intrin/psraw.h +++ b/libc/intrin/psraw.h @@ -1,23 +1,16 @@ #ifndef COSMOPOLITAN_LIBC_INTRIN_PSRAW_H_ #define COSMOPOLITAN_LIBC_INTRIN_PSRAW_H_ -#include "libc/bits/bits.h" #include "libc/intrin/macros.h" #if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ -/** - * Divides shorts by two power. - * - * @note c needs to be a literal, asmconstexpr, or linkconstsym - * @mayalias - */ -static void psraw(short a[8], const short b[8], char c) { - int i; - for (i = 0; i < 8; ++i) { - a[i] = SAR(b[i], c); - } -} +void psraw(int16_t[8], const int16_t[8], unsigned char); +void psrawv(int16_t[8], const int16_t[8], const uint64_t[2]); -#define psraw(A, B, I) INTRIN_SSEVEX_X_X_I_(psraw, SSE2, "psraw", A, B, I) +#define psraw(A, B, I) INTRIN_SSEVEX_X_I_(psraw, SSE2, "psraw", A, B, I) +#define psrawv(A, B, C) \ + INTRIN_SSEVEX_X_X_X_(psrawv, SSE2, "psraw", INTRIN_NONCOMMUTATIVE, A, B, C) +COSMOPOLITAN_C_END_ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ #endif /* COSMOPOLITAN_LIBC_INTRIN_PSRAW_H_ */ diff --git a/test/libc/intrin/psraw_test.c b/libc/intrin/psrawv.c similarity index 83% rename from test/libc/intrin/psraw_test.c rename to libc/intrin/psrawv.c index 505efb43..516913f7 100644 --- a/test/libc/intrin/psraw_test.c +++ b/libc/intrin/psrawv.c @@ -18,21 +18,23 @@ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/intrin/psraw.h" -#include "libc/limits.h" -#include "libc/testlib/testlib.h" -TEST(psraw, testPositive) { - short A[8] = {1, 2, SHRT_MAX}; - psraw(A, A, 1); - EXPECT_EQ(0, A[0]); - EXPECT_EQ(1, A[1]); - EXPECT_EQ(SHRT_MAX / 2, A[2]); -} - -TEST(psraw, testNegative) { - short A[8] = {-1, -2, SHRT_MIN}; - psraw(A, A, 1); - EXPECT_EQ(-1, A[0]); - EXPECT_EQ(-1, A[1]); - EXPECT_EQ(SHRT_MIN / 2, A[2]); +/** + * Divides shorts by two power. + * + * @note arithmetic shift right will sign extend negatives + * @mayalias + */ +void(psrawv)(int16_t a[8], const int16_t b[8], const uint64_t c[2]) { + unsigned char k; + unsigned i, x, m; + k = c[0] > 15 ? 15 : c[0]; + for (i = 0; i < 8; ++i) { + m = 0; + x = b[i]; + if (x & 0x8000) m = ~(0xffffu >> k); + x >>= k; + x |= m; + a[i] = x & 0xffffu; + } } diff --git a/libc/intrin/psrld.c b/libc/intrin/psrld.c new file mode 100644 index 00000000..c9dba069 --- /dev/null +++ b/libc/intrin/psrld.c @@ -0,0 +1,39 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/psrld.h" +#include "libc/str/str.h" + +/** + * Divides unsigned ints by two power. + * + * @note c needs to be a literal, asmconstexpr, or linkconstsym + * @note logical shift does not sign extend negatives + * @mayalias + */ +void(psrld)(uint32_t a[4], const uint32_t b[4], unsigned char c) { + unsigned i; + if (c <= 31) { + for (i = 0; i < 4; ++i) { + a[i] = b[i] >> c; + } + } else { + memset(a, 0, 16); + } +} diff --git a/libc/intrin/psrld.h b/libc/intrin/psrld.h new file mode 100644 index 00000000..9667b8f9 --- /dev/null +++ b/libc/intrin/psrld.h @@ -0,0 +1,16 @@ +#ifndef COSMOPOLITAN_LIBC_INTRIN_PSRLD_H_ +#define COSMOPOLITAN_LIBC_INTRIN_PSRLD_H_ +#include "libc/intrin/macros.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +void psrld(uint32_t[4], const uint32_t[4], unsigned char); +void psrldv(uint32_t[4], const uint32_t[4], const uint64_t[2]); + +#define psrld(A, B, I) INTRIN_SSEVEX_X_I_(psrld, SSE2, "psrld", A, B, I) +#define psrldv(A, B, C) \ + INTRIN_SSEVEX_X_X_X_(psrldv, SSE2, "psrld", INTRIN_NONCOMMUTATIVE, A, B, C) + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_INTRIN_PSRLD_H_ */ diff --git a/libc/intrin/psrldq.c b/libc/intrin/psrldq.c new file mode 100644 index 00000000..cfd6638d --- /dev/null +++ b/libc/intrin/psrldq.c @@ -0,0 +1,35 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/psrldq.h" +#include "libc/str/str.h" + +/** + * Shifts vector left by n bytes w/ zero-fill. + * + * @param a is input vector + * @param b receives output + * @mayalias + */ +void(psrldq)(uint8_t b[16], const uint8_t a[16], unsigned long n) { + unsigned i; + if (n > 16) n = 16; + memcpy(b, a + n, 16 - n); + memset(b + (16 - n), 0, n); +} diff --git a/libc/intrin/psrldq.h b/libc/intrin/psrldq.h new file mode 100644 index 00000000..534ce5fd --- /dev/null +++ b/libc/intrin/psrldq.h @@ -0,0 +1,37 @@ +#ifndef COSMOPOLITAN_LIBC_INTRIN_PSRLDQ_H_ +#define COSMOPOLITAN_LIBC_INTRIN_PSRLDQ_H_ +#include "libc/intrin/macros.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +void psrldq(uint8_t[16], const uint8_t[16], unsigned long); + +#ifndef __STRICT_ANSI__ +__intrin_xmm_t __psrldqs(__intrin_xmm_t); +#define psrldq(B, A, I) \ + do { \ + if (likely(!IsModeDbg() && X86_NEED(SSE) && X86_HAVE(SSE2))) { \ + __intrin_xmm_t *Xmm0 = (void *)(B); \ + const __intrin_xmm_t *Xmm1 = (const __intrin_xmm_t *)(A); \ + if (isconstant(I)) { \ + if (!X86_NEED(AVX)) { \ + asm("psrldq\t%1,%0" : "=x"(*Xmm0) : "i"(I), "0"(*Xmm1)); \ + } else { \ + asm("vpsrldq\t%2,%1,%0" : "=x"(*Xmm0) : "x"(*Xmm1), "i"(I)); \ + } \ + } else { \ + unsigned long Vimm = (I); \ + typeof(__psrldqs) *Fn; \ + if (Vimm > 16) Vimm = 16; \ + Fn = (typeof(__psrldqs) *)((uintptr_t)&__psrldqs + Vimm * 8); \ + *Xmm0 = Fn(*Xmm1); \ + } \ + } else { \ + psrldq(B, A, I); \ + } \ + } while (0) +#endif + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_INTRIN_PSRLDQ_H_ */ diff --git a/libc/intrin/psrldqs.S b/libc/intrin/psrldqs.S new file mode 100644 index 00000000..fbe4b583 --- /dev/null +++ b/libc/intrin/psrldqs.S @@ -0,0 +1,94 @@ +/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│ +│vi: set et ft=asm ts=8 tw=8 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/macros.h" + +/ Jump table for psrldq() with non-constexpr immediate parameter. + .align 8 +__psrldqs: + psrldq $0,%xmm0 + ret + nop + nop + psrldq $1,%xmm0 + ret + nop + nop + psrldq $2,%xmm0 + ret + nop + nop + psrldq $3,%xmm0 + ret + nop + nop + psrldq $4,%xmm0 + ret + nop + nop + psrldq $5,%xmm0 + ret + nop + nop + psrldq $6,%xmm0 + ret + nop + nop + psrldq $7,%xmm0 + ret + nop + nop + psrldq $8,%xmm0 + ret + nop + nop + psrldq $9,%xmm0 + ret + nop + nop + psrldq $10,%xmm0 + ret + nop + nop + psrldq $11,%xmm0 + ret + nop + nop + psrldq $12,%xmm0 + ret + nop + nop + psrldq $13,%xmm0 + ret + nop + nop + psrldq $14,%xmm0 + ret + nop + nop + psrldq $15,%xmm0 + ret + nop + nop + psrldq $16,%xmm0 + ret + .if . - __psrldqs != 8 * 17 - 2 + .error "bad assemblage" + .endif + .endfn __psrldqs,globl diff --git a/libc/intrin/psrldv.c b/libc/intrin/psrldv.c new file mode 100644 index 00000000..7b7ffd69 --- /dev/null +++ b/libc/intrin/psrldv.c @@ -0,0 +1,38 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/psrld.h" +#include "libc/str/str.h" + +/** + * Divides ints by two power. + * + * @note logical shift does not sign extend negatives + * @mayalias + */ +void(psrldv)(uint32_t a[4], const uint32_t b[4], const uint64_t c[2]) { + unsigned i; + if (c[0] <= 31) { + for (i = 0; i < 4; ++i) { + a[i] = b[i] >> c[0]; + } + } else { + memset(a, 0, 16); + } +} diff --git a/libc/intrin/psrlq.c b/libc/intrin/psrlq.c new file mode 100644 index 00000000..6a918ae9 --- /dev/null +++ b/libc/intrin/psrlq.c @@ -0,0 +1,39 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/psrlq.h" +#include "libc/str/str.h" + +/** + * Divides unsigned longs by two power. + * + * @note c needs to be a literal, asmconstexpr, or linkconstsym + * @note logical shift does not sign extend negatives + * @mayalias + */ +void(psrlq)(uint64_t a[2], const uint64_t b[2], unsigned char c) { + unsigned i; + if (c <= 63) { + for (i = 0; i < 2; ++i) { + a[i] = b[i] >> c; + } + } else { + memset(a, 0, 16); + } +} diff --git a/libc/intrin/psrlq.h b/libc/intrin/psrlq.h new file mode 100644 index 00000000..96abe9c4 --- /dev/null +++ b/libc/intrin/psrlq.h @@ -0,0 +1,16 @@ +#ifndef COSMOPOLITAN_LIBC_INTRIN_PSRLQ_H_ +#define COSMOPOLITAN_LIBC_INTRIN_PSRLQ_H_ +#include "libc/intrin/macros.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +void psrlq(uint64_t[2], const uint64_t[2], unsigned char); +void psrlqv(uint64_t[2], const uint64_t[2], const uint64_t[2]); + +#define psrlq(A, B, I) INTRIN_SSEVEX_X_I_(psrlq, SSE2, "psrlq", A, B, I) +#define psrlqv(A, B, C) \ + INTRIN_SSEVEX_X_X_X_(psrlqv, SSE2, "psrlq", INTRIN_NONCOMMUTATIVE, A, B, C) + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_INTRIN_PSRLQ_H_ */ diff --git a/libc/intrin/psrlqv.c b/libc/intrin/psrlqv.c new file mode 100644 index 00000000..6a47308b --- /dev/null +++ b/libc/intrin/psrlqv.c @@ -0,0 +1,38 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/psrlq.h" +#include "libc/str/str.h" + +/** + * Divides unsigned longs by two power. + * + * @note logical shift does not sign extend negatives + * @mayalias + */ +void(psrlqv)(uint64_t a[2], const uint64_t b[2], const uint64_t c[2]) { + unsigned i; + if (c[0] <= 63) { + for (i = 0; i < 2; ++i) { + a[i] = b[i] >> c[0]; + } + } else { + memset(a, 0, 16); + } +} diff --git a/libc/intrin/psrlw.c b/libc/intrin/psrlw.c new file mode 100644 index 00000000..faf20466 --- /dev/null +++ b/libc/intrin/psrlw.c @@ -0,0 +1,39 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/psrlw.h" +#include "libc/str/str.h" + +/** + * Divides unsigned shorts by two power. + * + * @note c needs to be a literal, asmconstexpr, or linkconstsym + * @note logical shift does not sign extend negatives + * @mayalias + */ +void(psrlw)(uint16_t a[8], const uint16_t b[8], unsigned char c) { + unsigned i; + if (c < 16) { + for (i = 0; i < 8; ++i) { + a[i] = b[i] >> c; + } + } else { + memset(a, 0, 16); + } +} diff --git a/libc/intrin/psrlw.h b/libc/intrin/psrlw.h new file mode 100644 index 00000000..25dee7f9 --- /dev/null +++ b/libc/intrin/psrlw.h @@ -0,0 +1,16 @@ +#ifndef COSMOPOLITAN_LIBC_INTRIN_PSRLW_H_ +#define COSMOPOLITAN_LIBC_INTRIN_PSRLW_H_ +#include "libc/intrin/macros.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +void psrlw(uint16_t[8], const uint16_t[8], unsigned char); +void psrlwv(uint16_t[8], const uint16_t[8], const uint64_t[2]); + +#define psrlw(A, B, I) INTRIN_SSEVEX_X_I_(psrlw, SSE2, "psrlw", A, B, I) +#define psrlwv(A, B, C) \ + INTRIN_SSEVEX_X_X_X_(psrlwv, SSE2, "psrlw", INTRIN_NONCOMMUTATIVE, A, B, C) + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_INTRIN_PSRLW_H_ */ diff --git a/libc/intrin/psrlwv.c b/libc/intrin/psrlwv.c new file mode 100644 index 00000000..dd3c2f05 --- /dev/null +++ b/libc/intrin/psrlwv.c @@ -0,0 +1,38 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/psrlw.h" +#include "libc/str/str.h" + +/** + * Divides unsigned shorts by two power. + * + * @note logical shift does not sign extend negatives + * @mayalias + */ +void(psrlwv)(uint16_t a[8], const uint16_t b[8], const uint64_t c[2]) { + unsigned i; + if (c[0] < 16) { + for (i = 0; i < 8; ++i) { + a[i] = b[i] >> c[0]; + } + } else { + memset(a, 0, 16); + } +} diff --git a/libc/intrin/psubb.c b/libc/intrin/psubb.c new file mode 100644 index 00000000..1f02b6f5 --- /dev/null +++ b/libc/intrin/psubb.c @@ -0,0 +1,38 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/psubb.h" +#include "libc/str/str.h" + +/** + * Subtracts 8-bit integers. + * + * @param 𝑎 [w/o] receives result + * @param 𝑏 [r/o] supplies first input vector + * @param 𝑐 [r/o] supplies second input vector + * @mayalias + */ +void(psubb)(int8_t a[16], const int8_t b[16], const int8_t c[16]) { + unsigned i; + int8_t r[16]; + for (i = 0; i < 16; ++i) { + r[i] = b[i] - c[i]; + } + memcpy(a, r, 16); +} diff --git a/libc/intrin/psubb.h b/libc/intrin/psubb.h new file mode 100644 index 00000000..fd6e0f46 --- /dev/null +++ b/libc/intrin/psubb.h @@ -0,0 +1,14 @@ +#ifndef COSMOPOLITAN_LIBC_INTRIN_PSUBB_H_ +#define COSMOPOLITAN_LIBC_INTRIN_PSUBB_H_ +#include "libc/intrin/macros.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +void psubb(int8_t[16], const int8_t[16], const int8_t[16]); + +#define psubb(A, B, C) \ + INTRIN_SSEVEX_X_X_X_(psubb, SSE2, "psubb", INTRIN_NONCOMMUTATIVE, A, B, C) + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_INTRIN_PSUBB_H_ */ diff --git a/libc/intrin/psubd.c b/libc/intrin/psubd.c new file mode 100644 index 00000000..5da83b9e --- /dev/null +++ b/libc/intrin/psubd.c @@ -0,0 +1,38 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/psubd.h" +#include "libc/str/str.h" + +/** + * Subtracts 32-bit integers. + * + * @param 𝑎 [w/o] receives result + * @param 𝑏 [r/o] supplies first input vector + * @param 𝑐 [r/o] supplies second input vector + * @mayalias + */ +void(psubd)(int32_t a[4], const int32_t b[4], const int32_t c[4]) { + unsigned i; + int32_t r[4]; + for (i = 0; i < 4; ++i) { + r[i] = b[i] - c[i]; + } + memcpy(a, r, 16); +} diff --git a/libc/intrin/psubd.h b/libc/intrin/psubd.h new file mode 100644 index 00000000..c8f396da --- /dev/null +++ b/libc/intrin/psubd.h @@ -0,0 +1,14 @@ +#ifndef COSMOPOLITAN_LIBC_INTRIN_PSUBD_H_ +#define COSMOPOLITAN_LIBC_INTRIN_PSUBD_H_ +#include "libc/intrin/macros.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +void psubd(int32_t[4], const int32_t[4], const int32_t[4]); + +#define psubd(A, B, C) \ + INTRIN_SSEVEX_X_X_X_(psubd, SSE2, "psubd", INTRIN_NONCOMMUTATIVE, A, B, C) + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_INTRIN_PSUBD_H_ */ diff --git a/libc/intrin/psubq.c b/libc/intrin/psubq.c new file mode 100644 index 00000000..ddfc3401 --- /dev/null +++ b/libc/intrin/psubq.c @@ -0,0 +1,38 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/psubq.h" +#include "libc/str/str.h" + +/** + * Subtracts 64-bit integers. + * + * @param 𝑎 [w/o] receives result + * @param 𝑏 [r/o] supplies first input vector + * @param 𝑐 [r/o] supplies second input vector + * @mayalias + */ +void(psubq)(int64_t a[2], const int64_t b[2], const int64_t c[2]) { + unsigned i; + int64_t r[2]; + for (i = 0; i < 2; ++i) { + r[i] = b[i] - c[i]; + } + memcpy(a, r, 16); +} diff --git a/libc/intrin/psubq.h b/libc/intrin/psubq.h new file mode 100644 index 00000000..d08bb6f7 --- /dev/null +++ b/libc/intrin/psubq.h @@ -0,0 +1,14 @@ +#ifndef COSMOPOLITAN_LIBC_INTRIN_PSUBQ_H_ +#define COSMOPOLITAN_LIBC_INTRIN_PSUBQ_H_ +#include "libc/intrin/macros.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +void psubq(int64_t[2], const int64_t[2], const int64_t[2]); + +#define psubq(A, B, C) \ + INTRIN_SSEVEX_X_X_X_(psubq, SSE2, "psubq", INTRIN_NONCOMMUTATIVE, A, B, C) + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_INTRIN_PSUBQ_H_ */ diff --git a/libc/intrin/psubsb.c b/libc/intrin/psubsb.c new file mode 100644 index 00000000..b48fb0e6 --- /dev/null +++ b/libc/intrin/psubsb.c @@ -0,0 +1,38 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/psubsb.h" +#include "libc/limits.h" +#include "libc/macros.h" +#include "libc/str/str.h" + +/** + * Subtracts signed 8-bit integers w/ saturation. + * + * @param 𝑎 [w/o] receives result + * @param 𝑏 [r/o] supplies first input vector + * @param 𝑐 [r/o] supplies second input vector + * @mayalias + */ +void(psubsb)(int8_t a[16], const int8_t b[16], const int8_t c[16]) { + unsigned i; + int8_t r[16]; + for (i = 0; i < 16; ++i) r[i] = MIN(INT8_MAX, MAX(INT8_MIN, b[i] - c[i])); + memcpy(a, r, 16); +} diff --git a/libc/intrin/psubsb.h b/libc/intrin/psubsb.h new file mode 100644 index 00000000..4e263c0d --- /dev/null +++ b/libc/intrin/psubsb.h @@ -0,0 +1,14 @@ +#ifndef COSMOPOLITAN_LIBC_INTRIN_PSUBSB_H_ +#define COSMOPOLITAN_LIBC_INTRIN_PSUBSB_H_ +#include "libc/intrin/macros.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +void psubsb(int8_t[16], const int8_t[16], const int8_t[16]); + +#define psubsb(A, B, C) \ + INTRIN_SSEVEX_X_X_X_(psubsb, SSE2, "psubsb", INTRIN_NONCOMMUTATIVE, A, B, C) + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_INTRIN_PSUBSB_H_ */ diff --git a/libc/intrin/psubsw.c b/libc/intrin/psubsw.c new file mode 100644 index 00000000..feb10439 --- /dev/null +++ b/libc/intrin/psubsw.c @@ -0,0 +1,38 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/psubsw.h" +#include "libc/limits.h" +#include "libc/macros.h" +#include "libc/str/str.h" + +/** + * Subtracts signed 16-bit integers w/ saturation. + * + * @param 𝑎 [w/o] receives result + * @param 𝑏 [r/o] supplies first input vector + * @param 𝑐 [r/o] supplies second input vector + * @mayalias + */ +void(psubsw)(int16_t a[8], const int16_t b[8], const int16_t c[8]) { + unsigned i; + int16_t r[8]; + for (i = 0; i < 8; ++i) r[i] = MIN(INT16_MAX, MAX(INT16_MIN, b[i] - c[i])); + memcpy(a, r, 16); +} diff --git a/libc/intrin/psubsw.h b/libc/intrin/psubsw.h new file mode 100644 index 00000000..a6994ac3 --- /dev/null +++ b/libc/intrin/psubsw.h @@ -0,0 +1,14 @@ +#ifndef COSMOPOLITAN_LIBC_INTRIN_PSUBSW_H_ +#define COSMOPOLITAN_LIBC_INTRIN_PSUBSW_H_ +#include "libc/intrin/macros.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +void psubsw(int16_t[8], const int16_t[8], const int16_t[8]); + +#define psubsw(A, B, C) \ + INTRIN_SSEVEX_X_X_X_(psubsw, SSE2, "psubsw", INTRIN_NONCOMMUTATIVE, A, B, C) + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_INTRIN_PSUBSW_H_ */ diff --git a/libc/runtime/findmapping.c b/libc/intrin/psubusb.c similarity index 78% rename from libc/runtime/findmapping.c rename to libc/intrin/psubusb.c index cd4ba45b..9470e3c5 100644 --- a/libc/runtime/findmapping.c +++ b/libc/intrin/psubusb.c @@ -17,33 +17,24 @@ │ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/runtime/mappings.h" +#include "libc/intrin/psubusb.h" +#include "libc/limits.h" +#include "libc/macros.h" +#include "libc/str/str.h" -size_t findmapping_(int32_t k, const struct MemoryCoord *coords, size_t count) { - size_t l, r, m; - l = 0; - r = count; - while (l < r) { - m = (l + r) >> 1; - if (coords[m].x > k) { - r = m; - } else { - l = m + 1; - } +/** + * Subtracts unsigned 8-bit integers w/ saturation. + * + * @param 𝑎 [w/o] receives result + * @param 𝑏 [r/o] supplies first input vector + * @param 𝑐 [r/o] supplies second input vector + * @mayalias + */ +void(psubusb)(uint8_t a[16], const uint8_t b[16], const uint8_t c[16]) { + unsigned i; + uint8_t r[16]; + for (i = 0; i < 16; ++i) { + r[i] = MIN(UINT8_MAX, MAX(UINT8_MIN, b[i] - c[i])); } - return l; -} - -/** - * Returns appropriate rightmost index. - */ -size_t findmapping(int32_t k) { - return findmapping_(k, _mm.p, _mm.i); -} - -/** - * Returns appropriate rightmost index. - */ -size_t pickmapping(int32_t k) { - return findmapping_(k, _mm.p, _mm.i); + memcpy(a, r, 16); } diff --git a/libc/intrin/psubusb.h b/libc/intrin/psubusb.h new file mode 100644 index 00000000..b93050cd --- /dev/null +++ b/libc/intrin/psubusb.h @@ -0,0 +1,14 @@ +#ifndef COSMOPOLITAN_LIBC_INTRIN_PSUBUSB_H_ +#define COSMOPOLITAN_LIBC_INTRIN_PSUBUSB_H_ +#include "libc/intrin/macros.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +void psubusb(uint8_t[16], const uint8_t[16], const uint8_t[16]); + +#define psubusb(A, B, C) \ + INTRIN_SSEVEX_X_X_X_(psubusb, SSE2, "psubusb", INTRIN_NONCOMMUTATIVE, A, B, C) + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_INTRIN_PSUBUSB_H_ */ diff --git a/libc/intrin/psubusw.c b/libc/intrin/psubusw.c new file mode 100644 index 00000000..250295aa --- /dev/null +++ b/libc/intrin/psubusw.c @@ -0,0 +1,40 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/psubusw.h" +#include "libc/limits.h" +#include "libc/macros.h" +#include "libc/str/str.h" + +/** + * Subtracts unsigned 16-bit integers w/ saturation. + * + * @param 𝑎 [w/o] receives result + * @param 𝑏 [r/o] supplies first input vector + * @param 𝑐 [r/o] supplies second input vector + * @mayalias + */ +void(psubusw)(uint16_t a[8], const uint16_t b[8], const uint16_t c[8]) { + unsigned i; + uint16_t r[8]; + for (i = 0; i < 8; ++i) { + r[i] = MIN(UINT16_MAX, MAX(UINT16_MIN, b[i] - c[i])); + } + memcpy(a, r, 16); +} diff --git a/libc/intrin/psubusw.h b/libc/intrin/psubusw.h new file mode 100644 index 00000000..2a96ba9e --- /dev/null +++ b/libc/intrin/psubusw.h @@ -0,0 +1,14 @@ +#ifndef COSMOPOLITAN_LIBC_INTRIN_PSUBUSW_H_ +#define COSMOPOLITAN_LIBC_INTRIN_PSUBUSW_H_ +#include "libc/intrin/macros.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +void psubusw(uint16_t[8], const uint16_t[8], const uint16_t[8]); + +#define psubusw(A, B, C) \ + INTRIN_SSEVEX_X_X_X_(psubusw, SSE2, "psubusw", INTRIN_NONCOMMUTATIVE, A, B, C) + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_INTRIN_PSUBUSW_H_ */ diff --git a/libc/intrin/psubw.c b/libc/intrin/psubw.c new file mode 100644 index 00000000..e553f49c --- /dev/null +++ b/libc/intrin/psubw.c @@ -0,0 +1,38 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/psubw.h" +#include "libc/str/str.h" + +/** + * Subtracts 16-bit integers. + * + * @param 𝑎 [w/o] receives result + * @param 𝑏 [r/o] supplies first input vector + * @param 𝑐 [r/o] supplies second input vector + * @mayalias + */ +void(psubw)(int16_t a[8], const int16_t b[8], const int16_t c[8]) { + unsigned i; + int16_t r[8]; + for (i = 0; i < 8; ++i) { + r[i] = b[i] - c[i]; + } + memcpy(a, r, 16); +} diff --git a/libc/intrin/psubw.h b/libc/intrin/psubw.h new file mode 100644 index 00000000..52b21870 --- /dev/null +++ b/libc/intrin/psubw.h @@ -0,0 +1,14 @@ +#ifndef COSMOPOLITAN_LIBC_INTRIN_PSUBW_H_ +#define COSMOPOLITAN_LIBC_INTRIN_PSUBW_H_ +#include "libc/intrin/macros.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +void(psubw)(int16_t[8], const int16_t[8], const int16_t[8]); + +#define psubw(A, B, C) \ + INTRIN_SSEVEX_X_X_X_(psubw, SSE2, "psubw", INTRIN_NONCOMMUTATIVE, A, B, C) + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_INTRIN_PSUBW_H_ */ diff --git a/libc/intrin/punpckhbw.c b/libc/intrin/punpckhbw.c new file mode 100644 index 00000000..a0132388 --- /dev/null +++ b/libc/intrin/punpckhbw.c @@ -0,0 +1,47 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/punpckhbw.h" + +/** + * Interleaves high bytes. + * + * @param 𝑎 [w/o] receives reduced 𝑏 and 𝑐 interleaved + * @param 𝑏 [r/o] supplies eight words + * @param 𝑐 [r/o] supplies eight words + * @mayalias + */ +void(punpckhbw)(uint8_t a[16], const uint8_t b[16], const uint8_t c[16]) { + a[0x0] = b[0x8]; + a[0x1] = c[0x8]; + a[0x2] = b[0x9]; + a[0x3] = c[0x9]; + a[0x4] = b[0xa]; + a[0x5] = c[0xa]; + a[0x6] = b[0xb]; + a[0x7] = c[0xb]; + a[0x8] = b[0xc]; + a[0x9] = c[0xc]; + a[0xa] = b[0xd]; + a[0xb] = c[0xd]; + a[0xc] = b[0xe]; + a[0xd] = c[0xe]; + a[0xe] = b[0xf]; + a[0xf] = c[0xf]; +} diff --git a/libc/intrin/punpckhbw.h b/libc/intrin/punpckhbw.h new file mode 100644 index 00000000..69ca1fc4 --- /dev/null +++ b/libc/intrin/punpckhbw.h @@ -0,0 +1,15 @@ +#ifndef COSMOPOLITAN_LIBC_INTRIN_PUNPCKHBW_H_ +#define COSMOPOLITAN_LIBC_INTRIN_PUNPCKHBW_H_ +#include "libc/intrin/macros.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +void punpckhbw(uint8_t[16], const uint8_t[16], const uint8_t[16]); + +#define punpckhbw(A, B, C) \ + INTRIN_SSEVEX_X_X_X_(punpckhbw, SSE2, "punpckhbw", INTRIN_NONCOMMUTATIVE, A, \ + B, C) + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_INTRIN_PUNPCKHBW_H_ */ diff --git a/libc/intrin/punpckhdq.c b/libc/intrin/punpckhdq.c new file mode 100644 index 00000000..5b751b90 --- /dev/null +++ b/libc/intrin/punpckhdq.c @@ -0,0 +1,43 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/punpckhdq.h" + +/** + * Interleaves high doublewords. + * + * 0 1 2 3 + * B aaaa bbbb CCCC DDDD + * C eeee ffff GGGG HHHH + * └┬─┘ └─┬┘ + * ┌────┘ │ + * ┌─────┴─┐ ┌──────┴┐ + * → A CCCC GGGG DDDD HHHH + * + * @param 𝑎 [w/o] receives reduced 𝑏 and 𝑐 interleaved + * @param 𝑏 [r/o] supplies four doublewords + * @param 𝑐 [r/o] supplies four doublewords + * @mayalias + */ +void(punpckhdq)(uint32_t a[4], const uint32_t b[4], const uint32_t c[4]) { + a[0] = b[2]; + a[1] = c[2]; + a[2] = b[3]; + a[3] = c[3]; +} diff --git a/libc/intrin/punpckhdq.h b/libc/intrin/punpckhdq.h new file mode 100644 index 00000000..b2516585 --- /dev/null +++ b/libc/intrin/punpckhdq.h @@ -0,0 +1,15 @@ +#ifndef COSMOPOLITAN_LIBC_INTRIN_PUNPCKHDQ_H_ +#define COSMOPOLITAN_LIBC_INTRIN_PUNPCKHDQ_H_ +#include "libc/intrin/macros.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +void punpckhdq(uint32_t[4], const uint32_t[4], const uint32_t[4]); + +#define punpckhdq(A, B, C) \ + INTRIN_SSEVEX_X_X_X_(punpckhdq, SSE2, "punpckhdq", INTRIN_NONCOMMUTATIVE, A, \ + B, C) + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_INTRIN_PUNPCKHDQ_H_ */ diff --git a/libc/intrin/punpckhqdq.c b/libc/intrin/punpckhqdq.c new file mode 100644 index 00000000..51a8e6ac --- /dev/null +++ b/libc/intrin/punpckhqdq.c @@ -0,0 +1,33 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/punpckhqdq.h" + +/** + * Interleaves high quadwords. + * + * @param 𝑎 [w/o] receives reduced 𝑏 and 𝑐 interleaved + * @param 𝑏 [r/o] supplies two quadwords + * @param 𝑐 [r/o] supplies two quadwords + * @mayalias + */ +void(punpckhqdq)(uint64_t a[2], const uint64_t b[2], const uint64_t c[2]) { + a[0] = b[1]; + a[1] = c[1]; +} diff --git a/libc/intrin/punpckhqdq.h b/libc/intrin/punpckhqdq.h new file mode 100644 index 00000000..ced06c5f --- /dev/null +++ b/libc/intrin/punpckhqdq.h @@ -0,0 +1,15 @@ +#ifndef COSMOPOLITAN_LIBC_INTRIN_PUNPCKHQDQ_H_ +#define COSMOPOLITAN_LIBC_INTRIN_PUNPCKHQDQ_H_ +#include "libc/intrin/macros.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +void punpckhqdq(uint64_t[2], const uint64_t[2], const uint64_t[2]); + +#define punpckhqdq(A, B, C) \ + INTRIN_SSEVEX_X_X_X_(punpckhqdq, SSE2, "punpckhqdq", INTRIN_NONCOMMUTATIVE, \ + A, B, C) + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_INTRIN_PUNPCKHQDQ_H_ */ diff --git a/libc/intrin/punpckhwd.c b/libc/intrin/punpckhwd.c new file mode 100644 index 00000000..8b7b5191 --- /dev/null +++ b/libc/intrin/punpckhwd.c @@ -0,0 +1,50 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/punpckhwd.h" +#include "libc/str/str.h" + +/** + * Interleaves high words. + * + * 0 1 2 3 4 5 6 7 + * B aa bb cc dd EE FF GG HH + * C ii jj kk ll MM NN OO PP + * └┤ └┤ └┤ └┤ + * ┌────────┘ │ │ │ + * │ ┌─────┘ │ │ + * │ │ ┌──┘ │ + * ┌───┤ ┌───┤ ┌───┤ ┌───┤ + * → A EE MM FF NN GG OO HH PP + * + * @param 𝑎 [w/o] receives reduced 𝑏 and 𝑐 interleaved + * @param 𝑏 [r/o] supplies eight words + * @param 𝑐 [r/o] supplies eight words + * @mayalias + */ +void(punpckhwd)(uint16_t a[8], const uint16_t b[8], const uint16_t c[8]) { + a[0] = b[4]; + a[1] = c[4]; + a[2] = b[5]; + a[3] = c[5]; + a[4] = b[6]; + a[5] = c[6]; + a[6] = b[7]; + a[7] = c[7]; +} diff --git a/libc/intrin/punpckhwd.h b/libc/intrin/punpckhwd.h new file mode 100644 index 00000000..4d850e9a --- /dev/null +++ b/libc/intrin/punpckhwd.h @@ -0,0 +1,15 @@ +#ifndef COSMOPOLITAN_LIBC_INTRIN_PUNPCKHWD_H_ +#define COSMOPOLITAN_LIBC_INTRIN_PUNPCKHWD_H_ +#include "libc/intrin/macros.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +void punpckhwd(uint16_t[8], const uint16_t[8], const uint16_t[8]); + +#define punpckhwd(A, B, C) \ + INTRIN_SSEVEX_X_X_X_(punpckhwd, SSE2, "punpckhwd", INTRIN_NONCOMMUTATIVE, A, \ + B, C) + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_INTRIN_PUNPCKHWD_H_ */ diff --git a/libc/intrin/punpcklbw.c b/libc/intrin/punpcklbw.c new file mode 100644 index 00000000..980d0442 --- /dev/null +++ b/libc/intrin/punpcklbw.c @@ -0,0 +1,57 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/punpcklbw.h" + +/** + * Interleaves low bytes. + * + * 0 1 2 3 4 5 6 7 8 9 A B C D E F + * B A B C D E F G H i j k l m n o p + * C Q R S T U V W X y z α σ π μ τ ε + * │ │ │ │ │ │ │ │ + * │ │ │ └─────┐ + * │ │ └───┐ │ etc... + * │ └─┐ │ │ + * ├─┐ ├─┐ ├─┐ ├─┐ + * → A A Q B R C S D T E U F V G W H X + * + * @param 𝑎 [w/o] receives reduced 𝑏 and 𝑐 interleaved + * @param 𝑏 [r/o] supplies eight words + * @param 𝑐 [r/o] supplies eight words + * @mayalias + */ +void(punpcklbw)(uint8_t a[16], const uint8_t b[16], const uint8_t c[16]) { + a[0xf] = c[7]; + a[0xe] = b[7]; + a[0xd] = c[6]; + a[0xc] = b[6]; + a[0xb] = c[5]; + a[0xa] = b[5]; + a[0x9] = c[4]; + a[0x8] = b[4]; + a[0x7] = c[3]; + a[0x6] = b[3]; + a[0x5] = c[2]; + a[0x4] = b[2]; + a[0x3] = c[1]; + a[0x2] = b[1]; + a[0x1] = c[0]; + a[0x0] = b[0]; +} diff --git a/libc/intrin/punpcklbw.h b/libc/intrin/punpcklbw.h new file mode 100644 index 00000000..259ec044 --- /dev/null +++ b/libc/intrin/punpcklbw.h @@ -0,0 +1,15 @@ +#ifndef COSMOPOLITAN_LIBC_INTRIN_PUNPCKLBW_H_ +#define COSMOPOLITAN_LIBC_INTRIN_PUNPCKLBW_H_ +#include "libc/intrin/macros.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +void punpcklbw(uint8_t[16], const uint8_t[16], const uint8_t[16]); + +#define punpcklbw(A, B, C) \ + INTRIN_SSEVEX_X_X_X_(punpcklbw, SSE2, "punpcklbw", INTRIN_NONCOMMUTATIVE, A, \ + B, C) + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_INTRIN_PUNPCKLBW_H_ */ diff --git a/libc/intrin/punpckldq.c b/libc/intrin/punpckldq.c new file mode 100644 index 00000000..220aedc9 --- /dev/null +++ b/libc/intrin/punpckldq.c @@ -0,0 +1,43 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/punpckldq.h" + +/** + * Interleaves low doublewords. + * + * 0 1 2 3 + * B AAAA BBBB cccc dddd + * C EEEE FFFF gggg hhhh + * └┬─┘ └─┬┘ + * │ └───┐ + * ┌┴──────┐ ┌┴──────┐ + * → A AAAA EEEE BBBB FFFF + * + * @param 𝑎 [w/o] receives reduced 𝑏 and 𝑐 interleaved + * @param 𝑏 [r/o] supplies four doublewords + * @param 𝑐 [r/o] supplies four doublewords + * @mayalias + */ +void(punpckldq)(uint32_t a[4], const uint32_t b[4], const uint32_t c[4]) { + a[3] = c[1]; + a[2] = b[1]; + a[1] = c[0]; + a[0] = b[0]; +} diff --git a/libc/intrin/punpckldq.h b/libc/intrin/punpckldq.h new file mode 100644 index 00000000..b52d6132 --- /dev/null +++ b/libc/intrin/punpckldq.h @@ -0,0 +1,15 @@ +#ifndef COSMOPOLITAN_LIBC_INTRIN_PUNPCKLDQ_H_ +#define COSMOPOLITAN_LIBC_INTRIN_PUNPCKLDQ_H_ +#include "libc/intrin/macros.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +void punpckldq(uint32_t[4], const uint32_t[4], const uint32_t[4]); + +#define punpckldq(A, B, C) \ + INTRIN_SSEVEX_X_X_X_(punpckldq, SSE2, "punpckldq", INTRIN_NONCOMMUTATIVE, A, \ + B, C) + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_INTRIN_PUNPCKLDQ_H_ */ diff --git a/libc/intrin/punpcklqdq.c b/libc/intrin/punpcklqdq.c new file mode 100644 index 00000000..b621affd --- /dev/null +++ b/libc/intrin/punpcklqdq.c @@ -0,0 +1,33 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/punpcklqdq.h" + +/** + * Interleaves low quadwords. + * + * @param 𝑎 [w/o] receives reduced 𝑏 and 𝑐 interleaved + * @param 𝑏 [r/o] supplies two quadwords + * @param 𝑐 [r/o] supplies two quadwords + * @mayalias + */ +void(punpcklqdq)(uint64_t a[2], const uint64_t b[2], const uint64_t c[2]) { + a[1] = c[0]; + a[0] = b[0]; +} diff --git a/libc/intrin/punpcklqdq.h b/libc/intrin/punpcklqdq.h new file mode 100644 index 00000000..fc0f88f9 --- /dev/null +++ b/libc/intrin/punpcklqdq.h @@ -0,0 +1,15 @@ +#ifndef COSMOPOLITAN_LIBC_INTRIN_PUNPCKLQDQ_H_ +#define COSMOPOLITAN_LIBC_INTRIN_PUNPCKLQDQ_H_ +#include "libc/intrin/macros.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +void punpcklqdq(uint64_t[2], const uint64_t[2], const uint64_t[2]); + +#define punpcklqdq(A, B, C) \ + INTRIN_SSEVEX_X_X_X_(punpcklqdq, SSE2, "punpcklqdq", INTRIN_NONCOMMUTATIVE, \ + A, B, C) + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_INTRIN_PUNPCKLQDQ_H_ */ diff --git a/libc/intrin/punpcklwd.c b/libc/intrin/punpcklwd.c new file mode 100644 index 00000000..7eca6719 --- /dev/null +++ b/libc/intrin/punpcklwd.c @@ -0,0 +1,49 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/punpcklwd.h" + +/** + * Interleaves low words. + * + * 0 1 2 3 4 5 6 7 + * B AA BB CC DD ee ff gg hh + * C II JJ KK LL mm nn oo pp + * ├┘ ├┘ ├┘ ├┘ + * │ │ │ └────────┐ + * │ │ └─────┐ │ + * │ └──┐ │ │ + * ├───┐ ├───┐ ├───┐ ├───┐ + * → A AA II BB JJ CC KK DD LL + * + * @param 𝑎 [w/o] receives reduced 𝑏 and 𝑐 interleaved + * @param 𝑏 [r/o] supplies eight words + * @param 𝑐 [r/o] supplies eight words + * @mayalias + */ +void(punpcklwd)(uint16_t a[8], const uint16_t b[8], const uint16_t c[8]) { + a[7] = c[3]; + a[6] = b[3]; + a[5] = c[2]; + a[4] = b[2]; + a[3] = c[1]; + a[2] = b[1]; + a[1] = c[0]; + a[0] = b[0]; +} diff --git a/libc/intrin/punpcklwd.h b/libc/intrin/punpcklwd.h new file mode 100644 index 00000000..f7e99e99 --- /dev/null +++ b/libc/intrin/punpcklwd.h @@ -0,0 +1,15 @@ +#ifndef COSMOPOLITAN_LIBC_INTRIN_PUNPCKLWD_H_ +#define COSMOPOLITAN_LIBC_INTRIN_PUNPCKLWD_H_ +#include "libc/intrin/macros.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +void punpcklwd(uint16_t[8], const uint16_t[8], const uint16_t[8]); + +#define punpcklwd(A, B, C) \ + INTRIN_SSEVEX_X_X_X_(punpcklwd, SSE2, "punpcklwd", INTRIN_NONCOMMUTATIVE, A, \ + B, C) + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_INTRIN_PUNPCKLWD_H_ */ diff --git a/libc/intrin/pxor.c b/libc/intrin/pxor.c new file mode 100644 index 00000000..5ea88f23 --- /dev/null +++ b/libc/intrin/pxor.c @@ -0,0 +1,35 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/pxor.h" + +/** + * Xors 128-bit integers. + * + * @param 𝑎 [w/o] receives result + * @param 𝑏 [r/o] supplies first input vector + * @param 𝑐 [r/o] supplies second input vector + * @mayalias + */ +void(pxor)(uint64_t a[2], const uint64_t b[2], const uint64_t c[2]) { + unsigned i; + for (i = 0; i < 2; ++i) { + a[i] = b[i] ^ c[i]; + } +} diff --git a/libc/intrin/pxor.h b/libc/intrin/pxor.h new file mode 100644 index 00000000..aee0120c --- /dev/null +++ b/libc/intrin/pxor.h @@ -0,0 +1,14 @@ +#ifndef COSMOPOLITAN_LIBC_INTRIN_PXOR_H_ +#define COSMOPOLITAN_LIBC_INTRIN_PXOR_H_ +#include "libc/intrin/macros.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +void pxor(uint64_t[2], const uint64_t[2], const uint64_t[2]); + +#define pxor(A, B, C) \ + INTRIN_SSEVEX_X_X_X_(pxor, SSE2, "pxor", INTRIN_COMMUTATIVE, A, B, C) + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_INTRIN_PXOR_H_ */ diff --git a/libc/intrin/repstosb.h b/libc/intrin/repstosb.h new file mode 100644 index 00000000..4536f7f8 --- /dev/null +++ b/libc/intrin/repstosb.h @@ -0,0 +1,25 @@ +#ifndef COSMOPOLITAN_LIBC_INTRIN_REPSTOSB_H_ +#define COSMOPOLITAN_LIBC_INTRIN_REPSTOSB_H_ +#if !(__ASSEMBLER__ + __LINKER__ + 0) + +static void *repstosb(void *dest, unsigned char al, size_t cx) { + unsigned char *di = (unsigned char *)dest; + while (cx) *di++ = al, cx--; + return di; +} + +#if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__)) +#define repstosb(DI, AL, CX) \ + ({ \ + void *Di = (DI); \ + size_t Cx = (CX); \ + unsigned char Al = (AL); \ + asm("rep stosb" \ + : "=D"(Di), "=c"(Cx), "=m"(*(char(*)[Cx])Di) \ + : "0"(Di), "1"(Cx), "a"(Al)); \ + Di; \ + }) +#endif + +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_INTRIN_REPSTOSB_H_ */ diff --git a/libc/intrin/shufpd.c b/libc/intrin/shufpd.c new file mode 100644 index 00000000..fc0aab03 --- /dev/null +++ b/libc/intrin/shufpd.c @@ -0,0 +1,33 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/shufpd.h" + +/** + * Shuffles double vector. + * @param 𝑚 needs to be a literal, constexpr, or embedding + * @mayalias + */ +void(shufpd)(double b[2], const double a[2], uint8_t m) { + double t[2]; + t[0] = a[(m & 0b0000001) >> 0]; + t[1] = a[(m & 0b0000010) >> 1]; + b[0] = t[0]; + b[1] = t[1]; +} diff --git a/libc/intrin/shufpd.h b/libc/intrin/shufpd.h new file mode 100644 index 00000000..62cf5a0b --- /dev/null +++ b/libc/intrin/shufpd.h @@ -0,0 +1,10 @@ +#ifndef COSMOPOLITAN_LIBC_INTRIN_SHUFPD_H_ +#define COSMOPOLITAN_LIBC_INTRIN_SHUFPD_H_ +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +void shufpd(double[2], const double[2], uint8_t); + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_INTRIN_SHUFPD_H_ */ diff --git a/libc/intrin/shufps.c b/libc/intrin/shufps.c new file mode 100644 index 00000000..350584f2 --- /dev/null +++ b/libc/intrin/shufps.c @@ -0,0 +1,37 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/shufps.h" + +/** + * Shuffles float vector. + * @param 𝑚 needs to be a literal, constexpr, or embedding + * @mayalias + */ +void(shufps)(float b[4], const float a[4], uint8_t m) { + float t[4]; + t[0] = a[(m & 0b00000011) >> 0]; + t[1] = a[(m & 0b00001100) >> 2]; + t[2] = a[(m & 0b00110000) >> 4]; + t[3] = a[(m & 0b11000000) >> 6]; + b[0] = t[0]; + b[1] = t[1]; + b[2] = t[2]; + b[3] = t[3]; +} diff --git a/libc/intrin/shufps.h b/libc/intrin/shufps.h new file mode 100644 index 00000000..bb5e824f --- /dev/null +++ b/libc/intrin/shufps.h @@ -0,0 +1,10 @@ +#ifndef COSMOPOLITAN_LIBC_INTRIN_SHUFPS_H_ +#define COSMOPOLITAN_LIBC_INTRIN_SHUFPS_H_ +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +void shufps(float[4], const float[4], uint8_t); + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_INTRIN_SHUFPS_H_ */ diff --git a/libc/intrin/vpalignr.c b/libc/intrin/vpalignr.c deleted file mode 100644 index 10fe6f18..00000000 --- a/libc/intrin/vpalignr.c +++ /dev/null @@ -1,131 +0,0 @@ -/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ -│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ -╞══════════════════════════════════════════════════════════════════════════════╡ -│ Copyright 2020 Justine Alexandra Roberts Tunney │ -│ │ -│ This program is free software; you can redistribute it and/or modify │ -│ it under the terms of the GNU General Public License as published by │ -│ the Free Software Foundation; version 2 of the License. │ -│ │ -│ This program is distributed in the hope that it will be useful, but │ -│ WITHOUT ANY WARRANTY; without even the implied warranty of │ -│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ -│ General Public License for more details. │ -│ │ -│ You should have received a copy of the GNU General Public License │ -│ along with this program; if not, write to the Free Software │ -│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ -│ 02110-1301 USA │ -╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/intrin/palignr.h" - -/** - * Shifts and concatenates xmm registers. - * - * @param i may be a non-literal - * @see palignr() - * @mayalias - */ -void pvalignr(void *p, const void *prev, const void *next, size_t i) { - switch (i) { - case 0: - palignr(p, prev, next, 0); - break; - case 1: - palignr(p, prev, next, 1); - break; - case 2: - palignr(p, prev, next, 2); - break; - case 3: - palignr(p, prev, next, 3); - break; - case 4: - palignr(p, prev, next, 4); - break; - case 5: - palignr(p, prev, next, 5); - break; - case 6: - palignr(p, prev, next, 6); - break; - case 7: - palignr(p, prev, next, 7); - break; - case 8: - palignr(p, prev, next, 8); - break; - case 9: - palignr(p, prev, next, 9); - break; - case 10: - palignr(p, prev, next, 10); - break; - case 11: - palignr(p, prev, next, 11); - break; - case 12: - palignr(p, prev, next, 12); - break; - case 13: - palignr(p, prev, next, 13); - break; - case 14: - palignr(p, prev, next, 14); - break; - case 15: - palignr(p, prev, next, 15); - break; - case 16: - palignr(p, prev, next, 16); - break; - case 17: - palignr(p, prev, next, 17); - break; - case 18: - palignr(p, prev, next, 18); - break; - case 19: - palignr(p, prev, next, 19); - break; - case 20: - palignr(p, prev, next, 20); - break; - case 21: - palignr(p, prev, next, 21); - break; - case 22: - palignr(p, prev, next, 22); - break; - case 23: - palignr(p, prev, next, 23); - break; - case 24: - palignr(p, prev, next, 24); - break; - case 25: - palignr(p, prev, next, 25); - break; - case 26: - palignr(p, prev, next, 26); - break; - case 27: - palignr(p, prev, next, 27); - break; - case 28: - palignr(p, prev, next, 28); - break; - case 29: - palignr(p, prev, next, 29); - break; - case 30: - palignr(p, prev, next, 30); - break; - case 31: - palignr(p, prev, next, 31); - break; - default: - palignr(p, prev, next, 32); - break; - } -} diff --git a/libc/leb128.h b/libc/leb128.h deleted file mode 100644 index 6370e3b2..00000000 --- a/libc/leb128.h +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef COSMOPOLITAN_LIBC_LEB128_H_ -#define COSMOPOLITAN_LIBC_LEB128_H_ -#if !(__ASSEMBLER__ + __LINKER__ + 0) - -/** - * Decodes a GNU-style varint from a buffer. - * - * The GNU Assembler is able to encode numbers this way, since it's used - * by the DWARF debug format. - */ -forceinline int unsleb128(const void *buf, size_t size, int64_t *out) { - const unsigned char *p = (const unsigned char *)buf; - const unsigned char *pe = (const unsigned char *)buf + size; - int64_t res = 0; - int bits = 0; - unsigned char c; - do { - if (size && p == pe) return -1; - c = *p++; - res |= (int64_t)(c & 0x7f) << bits; - bits += 7; - } while (c & 0x80); - if ((c & 0x40) != 0) res |= -1ULL << bits; - if (out) *out = res; - return p - (const unsigned char *)buf; -} - -#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ -#endif /* COSMOPOLITAN_LIBC_LEB128_H_ */ diff --git a/libc/libc.mk b/libc/libc.mk index 1c1faeae..f02d30dc 100644 --- a/libc/libc.mk +++ b/libc/libc.mk @@ -2,14 +2,10 @@ #───vi: set et ft=make ts=8 tw=8 fenc=utf-8 :vi───────────────────────┘ PKGS += LIBC -LIBC_FILES := $(wildcard libc/*) $(wildcard libc/internal/*) -LIBC_HDRS = $(filter %.h,$(LIBC_FILES)) -LIBC_CHECKS = $(LIBC_HDRS:%=o/$(MODE)/%.ok) -.PHONY: o/libc -o/libc: o/libc/stubs \ - o/libc/sysv \ - o/libc/nt +LIBC_HDRS = $(filter %.h,$(LIBC_FILES)) +LIBC_FILES := $(wildcard libc/*) $(wildcard libc/internal/*) +LIBC_CHECKS = $(LIBC_HDRS:%=o/$(MODE)/%.ok) .PHONY: o/$(MODE)/libc o/$(MODE)/libc: o/$(MODE)/libc/alg \ @@ -22,16 +18,20 @@ o/$(MODE)/libc: o/$(MODE)/libc/alg \ o/$(MODE)/libc/escape \ o/$(MODE)/libc/fmt \ o/$(MODE)/libc/intrin \ + o/$(MODE)/libc/linux \ o/$(MODE)/libc/log \ o/$(MODE)/libc/math \ o/$(MODE)/libc/mem \ o/$(MODE)/libc/nexgen32e \ + o/$(MODE)/libc/nt \ o/$(MODE)/libc/ohmyplus \ o/$(MODE)/libc/rand \ o/$(MODE)/libc/runtime \ o/$(MODE)/libc/sock \ o/$(MODE)/libc/stdio \ o/$(MODE)/libc/str \ + o/$(MODE)/libc/stubs \ + o/$(MODE)/libc/sysv \ o/$(MODE)/libc/testlib \ o/$(MODE)/libc/time \ o/$(MODE)/libc/tinymath \ diff --git a/libc/limits.h b/libc/limits.h index 7e012e3f..0f093bb2 100644 --- a/libc/limits.h +++ b/libc/limits.h @@ -86,6 +86,10 @@ (((intmax_t)0x7fffffffffffffff) << 64 | (intmax_t)0xffffffffffffffff) #define UINTMAX_MAX \ (((uintmax_t)0xffffffffffffffff) << 64 | (uintmax_t)0xffffffffffffffff) +#define INT128_MIN INTMAX_MIN +#define INT128_MAX INTMAX_MAX +#define UINT128_MIN ((uintmax_t)0) +#define UINT128_MAX UINTMAX_MAX #else #define INTMAX_MAX __INT64_MAX__ #define UINTMAX_MAX __UINT64_MAX__ diff --git a/libc/linux/exit.h b/libc/linux/exit.h new file mode 100644 index 00000000..655af633 --- /dev/null +++ b/libc/linux/exit.h @@ -0,0 +1,16 @@ +#ifndef COSMOPOLITAN_LIBC_LINUX_EXIT_H_ +#define COSMOPOLITAN_LIBC_LINUX_EXIT_H_ +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +forceinline noreturn long LinuxExit(int rc) { + asm volatile("syscall" + : /* no outputs */ + : "a"(0xE7), "D"(rc) + : "memory"); + unreachable; +} + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_LINUX_EXIT_H_ */ diff --git a/libc/linux/linux.mk b/libc/linux/linux.mk new file mode 100644 index 00000000..c4872cee --- /dev/null +++ b/libc/linux/linux.mk @@ -0,0 +1,11 @@ +#-*-mode:makefile-gmake;indent-tabs-mode:t;tab-width:8;coding:utf-8-*-┐ +#───vi: set et ft=make ts=8 tw=8 fenc=utf-8 :vi───────────────────────┘ + +PKGS += LIBC_LINUX + +LIBC_LINUX_HDRS = $(filter %.h,$(LIBC_FILES)) +LIBC_LINUX_FILES := $(wildcard libc/linux/*) +LIBC_LINUX_CHECKS = $(LIBC_LINUX_HDRS:%=o/$(MODE)/%.ok) + +.PHONY: o/$(MODE)/libc/linux +o/$(MODE)/linux: $(LIBC_LINUX_CHECKS) diff --git a/libc/linux/read.h b/libc/linux/read.h new file mode 100644 index 00000000..b3fae18a --- /dev/null +++ b/libc/linux/read.h @@ -0,0 +1,15 @@ +#ifndef COSMOPOLITAN_LIBC_LINUX_READ_H_ +#define COSMOPOLITAN_LIBC_LINUX_READ_H_ +#if !(__ASSEMBLER__ + __LINKER__ + 0) + +forceinline long LinuxRead(int fd, void *data, unsigned long size) { + long rc; + asm volatile("syscall" + : "=a"(rc), "=m"(*(char(*)[size])data) + : "0"(0), "D"(fd), "S"(data), "d"(size) + : "rcx", "r11", "memory"); + return rc; +} + +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_LINUX_READ_H_ */ diff --git a/libc/linux/write.h b/libc/linux/write.h new file mode 100644 index 00000000..413f1a29 --- /dev/null +++ b/libc/linux/write.h @@ -0,0 +1,18 @@ +#ifndef COSMOPOLITAN_LIBC_LINUX_WRITE_H_ +#define COSMOPOLITAN_LIBC_LINUX_WRITE_H_ +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +forceinline long LinuxWrite(int fd, const void *data, unsigned long size) { + long rc; + asm volatile("syscall" + : "=a"(rc) + : "0"(1), "D"(fd), "S"(data), "d"(size), + "m"(*(char(*)[size])data) + : "rcx", "r11", "memory"); + return rc; +} + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_LINUX_WRITE_H_ */ diff --git a/libc/log/addr2linepath.c b/libc/log/addr2linepath.c new file mode 100644 index 00000000..b5cb0a89 --- /dev/null +++ b/libc/log/addr2linepath.c @@ -0,0 +1,24 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/log/log.h" + +const char *GetAddr2linePath(void) { + return commandvenv("ADDR2LINE", "addr2line"); +} diff --git a/libc/log/attachdebugger.c b/libc/log/attachdebugger.c index 00e059c4..16b9962c 100644 --- a/libc/log/attachdebugger.c +++ b/libc/log/attachdebugger.c @@ -23,6 +23,7 @@ #include "libc/fmt/fmt.h" #include "libc/log/gdb.h" #include "libc/log/log.h" +#include "libc/nexgen32e/vendor.h" #include "libc/paths.h" #include "libc/runtime/runtime.h" #include "libc/runtime/symbols.h" @@ -52,8 +53,8 @@ relegated int(attachdebugger)(intptr_t continuetoaddr) { struct StackFrame *bp; char pidstr[11], breakcmd[40]; const char *se, *elf, *gdb, *rewind, *layout; - if (!((gdb = commandv(firstnonnull(getenv("GDB"), "gdb"))) && - (ttyin = ttyout = open(_PATH_TTY, O_RDWR, 0)) != -1)) { + if (IsGenuineCosmo() || !(gdb = GetGdbPath()) || + (ttyin = ttyout = open(_PATH_TTY, O_RDWR, 0)) == -1) { return -1; } snprintf(pidstr, sizeof(pidstr), "%u", getpid()); diff --git a/libc/log/backtrace.c b/libc/log/backtrace.c index 30ff54e4..f5e3f702 100644 --- a/libc/log/backtrace.c +++ b/libc/log/backtrace.c @@ -20,6 +20,6 @@ #include "libc/dce.h" #include "libc/log/log.h" -relegated void backtrace(FILE *f) { +void backtrace(FILE *f) { showbacktrace(f, __builtin_frame_address(0)); } diff --git a/libc/log/backtrace2.c b/libc/log/backtrace2.c index 888fd7cc..a08a6df3 100644 --- a/libc/log/backtrace2.c +++ b/libc/log/backtrace2.c @@ -19,13 +19,14 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/alg/alg.h" #include "libc/alg/bisectcarleft.h" -#include "libc/bits/bits.h" #include "libc/bits/safemacros.h" +#include "libc/bits/weaken.h" #include "libc/calls/calls.h" #include "libc/calls/hefty/spawn.h" #include "libc/conv/conv.h" #include "libc/dce.h" #include "libc/fmt/fmt.h" +#include "libc/log/log.h" #include "libc/nexgen32e/gc.h" #include "libc/runtime/runtime.h" #include "libc/runtime/symbols.h" @@ -36,7 +37,7 @@ #define kBacktraceMaxFrames 128 #define kBacktraceBufSize ((kBacktraceMaxFrames - 1) * (16 + 1)) -static char *formataddress(FILE *f, const struct SymbolTable *st, intptr_t addr, +static char *FormatAddress(FILE *f, const struct SymbolTable *st, intptr_t addr, char *out, unsigned size, bool symbolic) { int64_t addend; const char *name; @@ -54,7 +55,7 @@ static char *formataddress(FILE *f, const struct SymbolTable *st, intptr_t addr, return out; } -static int printbacktraceusingsymbols(FILE *f, const struct StackFrame *bp, +static int PrintBacktraceUsingSymbols(FILE *f, const struct StackFrame *bp, char buf[hasatleast kBacktraceBufSize]) { size_t gi; intptr_t addr; @@ -62,17 +63,17 @@ static int printbacktraceusingsymbols(FILE *f, const struct StackFrame *bp, struct SymbolTable *symbols; const struct StackFrame *frame; if ((symbols = getsymboltable())) { - garbage = weaken(__garbage); + garbage = weaken(g_garbage); gi = garbage ? garbage->i : 0; for (frame = bp; frame; frame = frame->next) { addr = frame->addr; - if (addr == weakaddr("__gc")) { + if (addr == weakaddr("CollectGarbage")) { do { --gi; - } while ((addr = garbage->p[gi].ret) == weakaddr("__gc")); + } while ((addr = garbage->p[gi].ret) == weakaddr("CollectGarbage")); } fprintf(f, "%p %p %s\n", frame, addr, - formataddress(f, symbols, addr, buf, kBacktraceBufSize, true)); + FormatAddress(f, symbols, addr, buf, kBacktraceBufSize, true)); } return 0; } else { @@ -80,7 +81,7 @@ static int printbacktraceusingsymbols(FILE *f, const struct StackFrame *bp, } } -static int printbacktraceusingaddr2line( +static int PrintBacktraceUsingAddr2line( FILE *f, const struct StackFrame *bp, char buf[hasatleast kBacktraceBufSize], char *argv[hasatleast kBacktraceMaxFrames]) { @@ -90,10 +91,8 @@ static int printbacktraceusingaddr2line( int rc, pid, tubes[3]; struct Garbages *garbage; const struct StackFrame *frame; - const char *addr2line, *debugbin, *p1, *p2, *p3; - if (!(debugbin = finddebugbinary()) || - !(addr2line = emptytonull(firstnonnull( - getenv("ADDR2LINE"), firstnonnull(commandv("addr2line"), ""))))) { + const char *debugbin, *p1, *p2, *p3, *addr2line; + if (!(debugbin = finddebugbinary()) || !(addr2line = GetAddr2linePath())) { return -1; } i = 0; @@ -102,14 +101,14 @@ static int printbacktraceusingaddr2line( argv[i++] = "-a"; /* filter out w/ shell script wrapper for old versions */ argv[i++] = "-pCife"; argv[i++] = debugbin; - garbage = weaken(__garbage); + garbage = weaken(g_garbage); gi = garbage ? garbage->i : 0; for (frame = bp; frame && i < kBacktraceMaxFrames - 1; frame = frame->next) { addr = frame->addr; - if (addr == weakaddr("__gc")) { + if (addr == weakaddr("CollectGarbage")) { do { --gi; - } while ((addr = garbage->p[gi].ret) == weakaddr("__gc")); + } while ((addr = garbage->p[gi].ret) == weakaddr("CollectGarbage")); } argv[i++] = &buf[j]; j += snprintf(&buf[j], 17, "%#x", addr - 1) + 1; @@ -118,7 +117,9 @@ static int printbacktraceusingaddr2line( tubes[0] = STDIN_FILENO; tubes[1] = -1; tubes[2] = STDERR_FILENO; - if ((pid = spawnve(0, tubes, addr2line, argv, environ)) == -1) return -1; + if ((pid = spawnve(0, tubes, addr2line, argv, environ)) == -1) { + return -1; + } while ((got = read(tubes[1], buf, kBacktraceBufSize)) > 0) { for (p1 = buf; got;) { /* @@ -147,15 +148,15 @@ static int printbacktraceusingaddr2line( return 0; } -static noinline int printbacktrace(FILE *f, const struct StackFrame *bp, +static noinline int PrintBacktrace(FILE *f, const struct StackFrame *bp, char *argv[hasatleast kBacktraceMaxFrames], char buf[hasatleast kBacktraceBufSize]) { if (!IsTiny()) { - if (printbacktraceusingaddr2line(f, bp, buf, argv) != -1) { + if (PrintBacktraceUsingAddr2line(f, bp, buf, argv) != -1) { return 0; } } - return printbacktraceusingsymbols(f, bp, buf); + return PrintBacktraceUsingSymbols(f, bp, buf); } void showbacktrace(FILE *f, const struct StackFrame *bp) { @@ -164,7 +165,7 @@ void showbacktrace(FILE *f, const struct StackFrame *bp) { char buf[kBacktraceBufSize]; if (!noreentry) { noreentry = true; - printbacktrace(f, bp, argv, buf); + PrintBacktrace(f, bp, argv, buf); noreentry = 0; } } diff --git a/libc/log/cancolor.c b/libc/log/cancolor.c index a97ae02c..f6bf6106 100644 --- a/libc/log/cancolor.c +++ b/libc/log/cancolor.c @@ -17,6 +17,7 @@ │ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/bits/weaken.h" #include "libc/calls/internal.h" #include "libc/dce.h" #include "libc/log/log.h" diff --git a/libc/log/check.h b/libc/log/check.h index 910b2509..77129d50 100644 --- a/libc/log/check.h +++ b/libc/log/check.h @@ -1,6 +1,7 @@ #ifndef COSMOPOLITAN_LIBC_LOG_CHECK_H_ #define COSMOPOLITAN_LIBC_LOG_CHECK_H_ #include "libc/dce.h" +#include "libc/macros.h" /** * @fileoverview Modern assertions, courtesy of Elgoog. @@ -19,12 +20,12 @@ COSMOPOLITAN_C_START_ #define CHECK_NOTNULL(X, ...) __CHK(ne, !=, NULL, "NULL", X, #X, "" __VA_ARGS__) #define DCHECK(X, ...) __DCHK(eq, ==, true, "true", !!(X), #X, "" __VA_ARGS__) -#define DCHECK_EQ(Y, X, ...) __DCHK(eq, ==, Y, #X, X, #Y, "" __VA_ARGS__) -#define DCHECK_NE(Y, X, ...) __DCHK(ne, !=, Y, #X, X, #Y, "" __VA_ARGS__) -#define DCHECK_LE(Y, X, ...) __DCHK(le, <=, Y, #X, X, #Y, "" __VA_ARGS__) -#define DCHECK_LT(Y, X, ...) __DCHK(lt, <, Y, #X, X, #Y, "" __VA_ARGS__) -#define DCHECK_GE(Y, X, ...) __DCHK(ge, >=, Y, #X, X, #Y, "" __VA_ARGS__) -#define DCHECK_GT(Y, X, ...) __DCHK(gt, >, Y, #X, X, #Y, "" __VA_ARGS__) +#define DCHECK_EQ(Y, X, ...) __DCHK(eq, ==, Y, #Y, X, #X, "" __VA_ARGS__) +#define DCHECK_NE(Y, X, ...) __DCHK(ne, !=, Y, #Y, X, #X, "" __VA_ARGS__) +#define DCHECK_LE(Y, X, ...) __DCHK(le, <=, Y, #Y, X, #X, "" __VA_ARGS__) +#define DCHECK_LT(Y, X, ...) __DCHK(lt, <, Y, #Y, X, #X, "" __VA_ARGS__) +#define DCHECK_GE(Y, X, ...) __DCHK(ge, >=, Y, #Y, X, #X, "" __VA_ARGS__) +#define DCHECK_GT(Y, X, ...) __DCHK(gt, >, Y, #Y, X, #X, "" __VA_ARGS__) #define DCHECK_NOTNULL(X, ...) \ __DCHK(ne, !=, NULL, "NULL", X, #X, "" __VA_ARGS__) @@ -48,8 +49,8 @@ COSMOPOLITAN_C_START_ #define __CHK(SUFFIX, OP, WANT, WANTSTR, GOT, GOTSTR, ...) \ do { \ - autotype(WANT) Want = (WANT); \ autotype(GOT) Got = (GOT); \ + autotype(WANT) Want = (WANT); \ if (!(Want OP Got)) { \ if (!NoDebug()) { \ __check_fail(#SUFFIX, #OP, (uint64_t)Want, (WANTSTR), (uint64_t)Got, \ @@ -64,9 +65,11 @@ COSMOPOLITAN_C_START_ #ifdef NDEBUG #define __DCHK(SUFFIX, OP, WANT, WANTSTR, GOT, ...) \ do { \ - autotype(WANT) Want = (WANT); \ autotype(GOT) Got = (GOT); \ - if (!(Want OP Got)) unreachable; \ + autotype(WANT) Want = (WANT); \ + if (!(Want OP Got)) { \ + unreachable; \ + } \ } while (0) #else #define __DCHK(SUFFIX, OP, WANT, WANTSTR, GOT, GOTSTR, ...) \ diff --git a/libc/log/checkaligned.c b/libc/log/checkaligned.c index 2222f734..6f2fdb77 100644 --- a/libc/log/checkaligned.c +++ b/libc/log/checkaligned.c @@ -17,14 +17,19 @@ │ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/calls.h" #include "libc/dce.h" #include "libc/log/check.h" #include "libc/log/log.h" #include "libc/stdio/stdio.h" +STATIC_YOINK("ntoa"); +STATIC_YOINK("stoa"); + void __check_fail_aligned(unsigned bytes, uint64_t ptr) { - if (!IsTiny()) memsummary(stderr); - fprintf(stderr, "%s%d%s%#p\n", "error: pointer not ", bytes, - "-byte aligned: ", ptr); + fflush(stderr); + if (!IsTiny()) memsummary(fileno(stderr)); + (dprintf)(fileno(stderr), "%s%d%s%#p\n", "error: pointer not ", bytes, + "-byte aligned: ", ptr); die(); } diff --git a/libc/log/checkfail.c b/libc/log/checkfail.c index ccc880af..c0fcbdb1 100644 --- a/libc/log/checkfail.c +++ b/libc/log/checkfail.c @@ -25,6 +25,7 @@ #include "libc/log/check.h" #include "libc/log/internal.h" #include "libc/log/log.h" +#include "libc/runtime/memtrack.h" #include "libc/runtime/runtime.h" #include "libc/stdio/stdio.h" #include "libc/str/str.h" @@ -51,7 +52,7 @@ relegated void __check_fail(const char *suffix, const char *opstr, "\tCHECK_%s(%s, %s);\n" "\t\t → %#lx (%s)\n" "\t\t%s %#lx (%s)\n", - sufbuf, wantstr, gotstr, got, gotstr, opstr, want, wantstr); + sufbuf, wantstr, gotstr, want, wantstr, opstr, got, gotstr); if (!isempty(fmt)) { fputc('\t', stderr); @@ -70,7 +71,8 @@ relegated void __check_fail(const char *suffix, const char *opstr, if (!IsTiny() && lasterr == ENOMEM) { fprintf(stderr, "\n"); - showmappings(stderr); + fflush(stderr); + PrintMemoryIntervals(fileno(stderr), &_mmi); } fflush(stderr); diff --git a/libc/log/gdbexec.c b/libc/log/gdbexec.c index deb9f326..c238779c 100644 --- a/libc/log/gdbexec.c +++ b/libc/log/gdbexec.c @@ -22,6 +22,7 @@ #include "libc/calls/hefty/spawn.h" #include "libc/fmt/fmt.h" #include "libc/log/gdb.h" +#include "libc/log/log.h" #include "libc/runtime/runtime.h" #include "libc/runtime/symbols.h" @@ -34,9 +35,7 @@ int(gdbexec)(const char *cmd) { intptr_t continuetoaddr; const char *se, *elf, *gdb; char pidstr[11], breakcmd[40]; - if (!(gdb = commandv(firstnonnull(getenv("GDB"), "gdb")))) { - return -1; - } + if (!(gdb = GetGdbPath())) return -1; snprintf(pidstr, sizeof(pidstr), "%u", getpid()); if ((elf = finddebugbinary())) { se = "-se"; diff --git a/libc/runtime/mappings.c b/libc/log/gdbpath.c similarity index 95% rename from libc/runtime/mappings.c rename to libc/log/gdbpath.c index 92eeaf55..ab3b6afd 100644 --- a/libc/runtime/mappings.c +++ b/libc/log/gdbpath.c @@ -17,6 +17,8 @@ │ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/runtime/mappings.h" +#include "libc/log/log.h" -struct Mappings _mm; +const char *GetGdbPath(void) { + return commandvenv("GDB", "gdb"); +} diff --git a/libc/log/internal.h b/libc/log/internal.h index cc9ee6a7..3b26f180 100644 --- a/libc/log/internal.h +++ b/libc/log/internal.h @@ -1,17 +1,17 @@ #ifndef COSMOPOLITAN_LIBC_LOG_INTERNAL_H_ #define COSMOPOLITAN_LIBC_LOG_INTERNAL_H_ -#include "libc/log/log.h" -#include "libc/calls/struct/sigaction.h" #include "libc/calls/calls.h" +#include "libc/calls/struct/sigaction.h" +#include "libc/log/log.h" #if !(__ASSEMBLER__ + __LINKER__ + 0) COSMOPOLITAN_C_START_ -#define RED (cancolor() ? "\x1b[30;101m" : "") +#define RED (cancolor() ? "\x1b[30;101m" : "") #define UNBOLD (cancolor() ? "\x1b[22m" : "") -#define RED2 (cancolor() ? "\x1b[91;1m" : "") -#define BLUE1 (cancolor() ? "\x1b[94;49m" : "") -#define BLUE2 (cancolor() ? "\x1b[34m" : "") -#define RESET (cancolor() ? "\x1b[0m" : "") +#define RED2 (cancolor() ? "\x1b[91;1m" : "") +#define BLUE1 (cancolor() ? "\x1b[94;49m" : "") +#define BLUE2 (cancolor() ? "\x1b[34m" : "") +#define RESET (cancolor() ? "\x1b[0m" : "") #define SUBTLE (cancolor() ? "\x1b[35m" : "") enum NtExceptionHandlerActions; @@ -20,7 +20,6 @@ struct NtExceptionPointers; extern int kCrashSigs[8]; extern struct sigaction g_oldcrashacts[8]; extern const char kCrashSigNames[8][5] aligned(1); -extern const char kGodHatesFlags[12][2] aligned(1); extern const char kGregNames[17][4] aligned(1); extern const char kGregOrder[17] aligned(1); diff --git a/libc/log/log.h b/libc/log/log.h index b32aa6a2..1ea9e1bc 100644 --- a/libc/log/log.h +++ b/libc/log/log.h @@ -17,8 +17,8 @@ #ifndef LOGGABLELEVEL #ifndef NDEBUG #define LOGGABLELEVEL kLogDebug -#elif IsTiny() -#define LOGGABLELEVEL kLogFatal +/* #elif IsTiny() */ +/* #define LOGGABLELEVEL kLogInfo */ #else #define LOGGABLELEVEL kLogInfo #endif @@ -37,15 +37,16 @@ extern FILE *g_logfile; void backtrace(FILE *) relegated; /* shows fn backtrace and args */ void perror(const char *) relegated; /* print the last system error */ void die(void) relegated noreturn; /* print backtrace and abort() */ -void meminfo(FILE *); /* shows malloc statistics &c. */ -void memsummary(FILE *); /* light version of same thing */ +void meminfo(int); /* shows malloc statistics &c. */ +void memsummary(int); /* light version of same thing */ uint16_t getttycols(uint16_t); int getttysize(int, struct winsize *) paramsnonnull(); bool cancolor(void) nothrow nocallback; bool isterminalinarticulate(void) nosideeffect; char *commandvenv(const char *, const char *) nodiscard; +const char *GetAddr2linePath(void); +const char *GetGdbPath(void); -void showmappings(FILE *); void showcrashreports(void); void callexitontermination(struct sigset *); bool32 IsDebuggerPresent(bool); diff --git a/libc/log/logfile.c b/libc/log/logfile.initabi.c similarity index 97% rename from libc/log/logfile.c rename to libc/log/logfile.initabi.c index 09ee4b1a..0107b73d 100644 --- a/libc/log/logfile.c +++ b/libc/log/logfile.initabi.c @@ -17,8 +17,7 @@ │ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/bits/bits.h" -#include "libc/log/log.h" +#include "libc/bits/initializer.h" #include "libc/stdio/stdio.h" FILE *g_logfile; diff --git a/libc/log/meminfo.c b/libc/log/meminfo.c index 5edd3d0f..d3e0a0c9 100644 --- a/libc/log/meminfo.c +++ b/libc/log/meminfo.c @@ -17,26 +17,25 @@ │ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/calls.h" #include "libc/fmt/fmt.h" #include "libc/log/log.h" #include "libc/mem/mem.h" -#include "libc/stdio/stdio.h" STATIC_YOINK("ntoa"); STATIC_YOINK("stoa"); static void onmemchunk(void *start, void *end, size_t used_bytes, void *arg) { - FILE *f = arg; - (fprintf)(f, "%p - %p : %08zx / %08lx\n", start, end, used_bytes, + (dprintf)(*(int *)arg, "%p - %p : %08zx / %08lx\n", start, end, used_bytes, (intptr_t)end - (intptr_t)start); } /** * Prints memory mappings. */ -void meminfo(FILE *f) { - memsummary(f); - (fprintf)(f, "%*s %*s %*s %*s\n", POINTER_XDIGITS, "start", +void meminfo(int fd) { + memsummary(fd); + (dprintf)(fd, "%*s %*s %*s %*s\n", POINTER_XDIGITS, "start", POINTER_XDIGITS, "end", 8, "used", 8, "size"); - malloc_inspect_all(onmemchunk, f); + malloc_inspect_all(onmemchunk, &fd); } diff --git a/libc/log/memsummary.c b/libc/log/memsummary.c index e2dc61e8..410d219b 100644 --- a/libc/log/memsummary.c +++ b/libc/log/memsummary.c @@ -17,15 +17,16 @@ │ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/calls.h" #include "libc/log/log.h" #include "libc/mem/mem.h" -#include "libc/stdio/stdio.h" STATIC_YOINK("ntoa"); -void memsummary(FILE *f) { - struct mallinfo mi = mallinfo(); - (fprintf)(f, +void memsummary(int fd) { + struct mallinfo mi; + mi = mallinfo(); + (dprintf)(fd, "arena\t\t%,-12zu\t# space allocated from system\n" "ordblks\t\t%,-12zu\t# number of free chunks\n" "hblkhd\t\t%,-12zu\t# space in mmapped regions\n" diff --git a/libc/log/oncrash.c b/libc/log/oncrash.c index 1d09c898..81729585 100644 --- a/libc/log/oncrash.c +++ b/libc/log/oncrash.c @@ -17,6 +17,7 @@ │ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/calls.h" #include "libc/calls/sigbits.h" #include "libc/calls/struct/utsname.h" #include "libc/calls/ucontext.h" @@ -27,10 +28,12 @@ #include "libc/log/log.h" #include "libc/macros.h" #include "libc/runtime/internal.h" +#include "libc/runtime/memtrack.h" #include "libc/runtime/runtime.h" #include "libc/stdio/stdio.h" #include "libc/str/str.h" #include "libc/sysv/consts/auxv.h" +#include "libc/sysv/consts/o.h" #include "libc/sysv/consts/sig.h" STATIC_YOINK("ftoa"); @@ -44,20 +47,23 @@ STATIC_YOINK("stoa"); struct siginfo; -aligned(1) const char kGregOrder[17] = {13, 11, 8, 14, 12, 9, 10, 15, 16, - 0, 1, 2, 3, 4, 5, 6, 7}; +aligned(1) const char kGregOrder[17] = { + 13, 11, 8, 14, 12, 9, 10, 15, 16, 0, 1, 2, 3, 4, 5, 6, 7, +}; + aligned(1) const char kGregNames[17][4] = { "R8", "R9", "R10", "R11", "R12", "R13", "R14", "R15", "RDI", - "RSI", "RBP", "RBX", "RDX", "RAX", "RCX", "RSP", "RIP"}; -aligned(1) const char kGodHatesFlags[12][2] = { - "CF", "VF", "PF", "RF", "AF", "KF", "ZF", "SF", "TF", "IF", "DF", "OF"}; + "RSI", "RBP", "RBX", "RDX", "RAX", "RCX", "RSP", "RIP", +}; + +aligned(1) const char kGodHatesFlags[12] = "CVPRAKZSTIDO"; aligned(1) const char kCrashSigNames[8][5] = {"QUIT", "FPE", "ILL", "SEGV", "TRAP", "ABRT", "BUS"}; int kCrashSigs[8]; struct sigaction g_oldcrashacts[8]; -relegated static const char *tinystrsignal(int sig) { +relegated static const char *TinyStrSignal(int sig) { size_t i; for (i = 0; i < ARRAYLEN(kCrashSigs); ++i) { if (kCrashSigs[i] && sig == kCrashSigs[i]) { @@ -67,10 +73,10 @@ relegated static const char *tinystrsignal(int sig) { return "???"; } -relegated static void showfunctioncalls(FILE *f, ucontext_t *ctx) { - fputc('\n', f); +relegated static void ShowFunctionCalls(FILE *f, ucontext_t *ctx) { struct StackFrame *bp; struct StackFrame goodframe; + fputc('\n', f); if (ctx && ctx->uc_mcontext.rip && ctx->uc_mcontext.rbp) { goodframe.next = (struct StackFrame *)ctx->uc_mcontext.rbp; goodframe.addr = ctx->uc_mcontext.rip; @@ -79,20 +85,20 @@ relegated static void showfunctioncalls(FILE *f, ucontext_t *ctx) { } } -relegated static void describecpuflags(FILE *f, unsigned efl) { +relegated static void DescribeCpuFlags(FILE *f, unsigned efl) { size_t i; for (i = 0; i < ARRAYLEN(kGodHatesFlags); ++i) { if (efl & 1) { fputc(' ', f); - fputc(kGodHatesFlags[i][0], f); - fputc(kGodHatesFlags[i][1], f); + fputc(kGodHatesFlags[i], f); + fputc('F', f); } efl >>= 1; } (fprintf)(f, " %s%d\n", "IOPL", efl & 3); } -relegated static void showgeneralregisters(FILE *f, ucontext_t *ctx) { +relegated static void ShowGeneralRegisters(FILE *f, ucontext_t *ctx) { size_t i, j, k; long double st; fputc('\n', f); @@ -112,10 +118,10 @@ relegated static void showgeneralregisters(FILE *f, ucontext_t *ctx) { } } fflush(stderr); - describecpuflags(f, ctx->uc_mcontext.gregs[REG_EFL]); + DescribeCpuFlags(f, ctx->uc_mcontext.gregs[REG_EFL]); } -relegated static void showsseregisters(FILE *f, ucontext_t *ctx) { +relegated static void ShowSseRegisters(FILE *f, ucontext_t *ctx) { size_t i; fputc('\n', f); for (i = 0; i < 8; ++i) { @@ -126,42 +132,42 @@ relegated static void showsseregisters(FILE *f, ucontext_t *ctx) { } } -relegated static void showmemorymappings(FILE *f) { - int c; - FILE *f2; +relegated static void ShowMemoryMappings(int outfd) { + ssize_t rc; + int c, infd; + char buf[64]; if (!IsTiny()) { - showmappings(f); - if (IsLinux()) { - if ((f2 = fopen("/proc/self/maps", "r"))) { - while ((c = fgetc(f2)) != -1) { - if (fputc(c, f) == -1) break; - } + PrintMemoryIntervals(outfd, &_mmi); + if ((infd = open("/proc/self/maps", O_RDONLY)) != -1) { + while ((rc = read(infd, buf, sizeof(buf))) > 0) { + write(outfd, buf, rc); } - fclose(f2); } + close(infd); } } -relegated static void showcrashreport(int err, FILE *f, int sig, +relegated static void ShowCrashReport(int err, FILE *f, int sig, ucontext_t *ctx) { struct utsname names; (fprintf)(f, VEIL("r", "\n%serror%s: Uncaught SIG%s\n %s\n %s\n"), RED2, - RESET, tinystrsignal(sig), getauxval(AT_EXECFN), strerror(err)); + RESET, TinyStrSignal(sig), getauxval(AT_EXECFN), strerror(err)); if (uname(&names) != -1) { (fprintf)(f, VEIL("r", " %s %s %s %s\n"), names.sysname, names.nodename, names.release, names.version); } - showfunctioncalls(f, ctx); + ShowFunctionCalls(f, ctx); if (ctx) { - showgeneralregisters(f, ctx); - showsseregisters(f, ctx); + ShowGeneralRegisters(f, ctx); + ShowSseRegisters(f, ctx); } fputc('\n', f); - memsummary(f); - showmemorymappings(f); + fflush(f); + memsummary(fileno(f)); + ShowMemoryMappings(fileno(f)); } -relegated static void restoredefaultcrashsignalhandlers(void) { +relegated static void RestoreDefaultCrashSignalHandlers(void) { size_t i; sigset_t ss; sigemptyset(&ss); @@ -200,7 +206,7 @@ relegated void oncrash(int sig, struct siginfo *si, ucontext_t *ctx) { } else if (isterminalinarticulate() || isrunningundermake()) { gdbpid = -1; } else { - restoredefaultcrashsignalhandlers(); + RestoreDefaultCrashSignalHandlers(); gdbpid = attachdebugger(((sig == SIGTRAP || sig == SIGQUIT) && (rip >= (intptr_t)&_base && rip < (intptr_t)&_etext)) @@ -208,7 +214,7 @@ relegated void oncrash(int sig, struct siginfo *si, ucontext_t *ctx) { : 0); } if (gdbpid > 0 && (sig == SIGTRAP || sig == SIGQUIT)) return; - showcrashreport(err, stderr, sig, ctx); + ShowCrashReport(err, stderr, sig, ctx); quick_exit(128 + sig); unreachable; } diff --git a/libc/log/showcrashreports.c b/libc/log/showcrashreports.c index 0a409548..11d7f12a 100644 --- a/libc/log/showcrashreports.c +++ b/libc/log/showcrashreports.c @@ -24,12 +24,16 @@ #include "libc/dce.h" #include "libc/log/internal.h" #include "libc/log/log.h" +#include "libc/log/ubsan.h" #include "libc/macros.h" #include "libc/nt/signals.h" #include "libc/str/str.h" #include "libc/sysv/consts/sa.h" #include "libc/sysv/consts/sig.h" +STATIC_YOINK("die"); +STATIC_YOINK("__ubsan_abort"); + extern const unsigned char kOncrashThunks[7][11]; /** @@ -49,11 +53,9 @@ extern const unsigned char kOncrashThunks[7][11]; * * @see callexitontermination() */ -optimizesize void showcrashreports(void) { +void showcrashreports(void) { size_t i; struct sigaction sa; - if (IsModeDbg()) STATIC_YOINK("__ubsan_abort"); - if (!NoDebug()) STATIC_YOINK("die"); /* : oncrashthunks.S */ kCrashSigs[0] = SIGQUIT; /* ctrl+\ aka ctrl+break */ kCrashSigs[1] = SIGFPE; /* 1 / 0 */ diff --git a/libc/log/vflogf.c b/libc/log/vflogf.c index 2cf49a74..2136f064 100644 --- a/libc/log/vflogf.c +++ b/libc/log/vflogf.c @@ -25,6 +25,7 @@ #include "libc/dce.h" #include "libc/errno.h" #include "libc/fmt/fmt.h" +#include "libc/log/internal.h" #include "libc/log/log.h" #include "libc/math.h" #include "libc/nexgen32e/nexgen32e.h" @@ -75,6 +76,7 @@ void vflogf_onfail(FILE *f) { void(vflogf)(unsigned level, const char *file, int line, FILE *f, const char *fmt, va_list va) { static struct timespec ts; + bool flush; struct tm tm; long double t2; const char *prog; @@ -85,7 +87,7 @@ void(vflogf)(unsigned level, const char *file, int line, FILE *f, if (fileno(f) == -1) return; t2 = nowl(); secs = t2; - nsec = mod1000000000int64(t2 * 1e9L); + nsec = rem1000000000int64(t2 * 1e9L); if (secs > ts.tv_sec) { localtime_r(&secs, &tm); strftime(timebuf, sizeof(timebuf), "%Y%m%dT%H%M%S", &tm); @@ -93,23 +95,28 @@ void(vflogf)(unsigned level, const char *file, int line, FILE *f, timebufp = timebuf; zonebufp = zonebuf; dots = nsec; + flush = true; } else { timebufp = "---------------"; zonebufp = "---"; dots = nsec - ts.tv_nsec; + flush = true; } ts.tv_sec = secs; ts.tv_nsec = nsec; prog = basename(program_invocation_name); if (fprintf(f, "%c%s%s%06ld:%s:%d:%.*s:%d] ", loglevel2char(level), timebufp, - zonebufp, mod1000000int64(div1000int64(dots)), file, line, + zonebufp, rem1000000int64(div1000int64(dots)), file, line, strchrnul(prog, '.') - prog, prog, getpid()) <= 0) { vflogf_onfail(f); } (vfprintf)(f, fmt, va); va_end(va); fputc('\n', f); + if (flush) fflush(f); if (level == kLogFatal) { + startfatal(file, line); + fprintf(stderr, "fatal error see logfile\n"); die(); unreachable; } diff --git a/libc/macho.h b/libc/macho.h index 3ce17af3..8c2cc892 100644 --- a/libc/macho.h +++ b/libc/macho.h @@ -1,124 +1,125 @@ #ifndef COSMOPOLITAN_LIBC_MACHO_H_ #define COSMOPOLITAN_LIBC_MACHO_H_ +#ifndef __STRICT_ANSI__ -#define MAC_OBJECT 0x1 -#define MAC_EXECUTE 0x2 -#define MAC_FVMLIB 0x3 -#define MAC_CORE 0x4 -#define MAC_PRELOAD 0x5 -#define MAC_DYLIB 0x6 +#define MAC_OBJECT 0x1 +#define MAC_EXECUTE 0x2 +#define MAC_FVMLIB 0x3 +#define MAC_CORE 0x4 +#define MAC_PRELOAD 0x5 +#define MAC_DYLIB 0x6 #define MAC_DYLINKER 0x7 -#define MAC_BUNDLE 0x8 +#define MAC_BUNDLE 0x8 -#define MAC_CPU_NEXGEN32E 0x1000007 +#define MAC_CPU_NEXGEN32E 0x1000007 #define MAC_CPU_NEXGEN32E_ALL 3 -#define MAC_THREAD_NEXGEN32E 4 +#define MAC_THREAD_NEXGEN32E 4 #define MAC_THREAD_NEXGEN32E_256BIT 17 -#define MAC_NOUNDEFS 0x1 -#define MAC_INCRLINK 0x2 -#define MAC_DYLDLINK 0x4 -#define MAC_BINDATLOAD 0x8 -#define MAC_PREBOUND 0x10 -#define MAC_SPLIT_SEGS 0x20 -#define MAC_LAZY_INIT 0x40 -#define MAC_TWOLEVEL 0x80 -#define MAC_FORCE_FLAT 0x100 -#define MAC_NOMULTIDEFS 0x200 -#define MAC_NOFIXPREBINDING 0x400 -#define MAC_PREBINDABLE 0x800 -#define MAC_ALLMODSBOUND 0x1000 +#define MAC_NOUNDEFS 0x1 +#define MAC_INCRLINK 0x2 +#define MAC_DYLDLINK 0x4 +#define MAC_BINDATLOAD 0x8 +#define MAC_PREBOUND 0x10 +#define MAC_SPLIT_SEGS 0x20 +#define MAC_LAZY_INIT 0x40 +#define MAC_TWOLEVEL 0x80 +#define MAC_FORCE_FLAT 0x100 +#define MAC_NOMULTIDEFS 0x200 +#define MAC_NOFIXPREBINDING 0x400 +#define MAC_PREBINDABLE 0x800 +#define MAC_ALLMODSBOUND 0x1000 #define MAC_SUBSECTIONS_VIA_SYMBOLS 0x2000 -#define MAC_CANONICAL 0x4000 -#define MAC_ROOT_SAFE 0x40000 -#define MAC_SETUID_SAFE 0x80000 -#define MAC_PIE 0x200000 -#define MAC_HAS_TLV_DESCRIPTORS 0x800000 -#define MAC_NO_HEAP_EXECUTION 0x1000000 +#define MAC_CANONICAL 0x4000 +#define MAC_ROOT_SAFE 0x40000 +#define MAC_SETUID_SAFE 0x80000 +#define MAC_PIE 0x200000 +#define MAC_HAS_TLV_DESCRIPTORS 0x800000 +#define MAC_NO_HEAP_EXECUTION 0x1000000 -#define MAC_SG_HIGHVM 0x1 -#define MAC_SG_FVMLIB 0x2 +#define MAC_SG_HIGHVM 0x1 +#define MAC_SG_FVMLIB 0x2 #define MAC_SG_NORELOC 0x4 -#define MAC_S_REGULAR 0x0 -#define MAC_S_ZEROFILL 0x1 -#define MAC_S_CSTRING_LITERALS 0x2 -#define MAC_S_4BYTE_LITERALS 0x3 -#define MAC_S_8BYTE_LITERALS 0x4 -#define MAC_S_LITERAL_POINTERS 0x5 +#define MAC_S_REGULAR 0x0 +#define MAC_S_ZEROFILL 0x1 +#define MAC_S_CSTRING_LITERALS 0x2 +#define MAC_S_4BYTE_LITERALS 0x3 +#define MAC_S_8BYTE_LITERALS 0x4 +#define MAC_S_LITERAL_POINTERS 0x5 #define MAC_S_NON_LAZY_SYMBOL_POINTERS 0x6 -#define MAC_S_LAZY_SYMBOL_POINTERS 0x7 -#define MAC_S_SYMBOL_STUBS 0x8 -#define MAC_S_MOD_INIT_FUNC_POINTERS 0x9 -#define MAC_S_MOD_TERM_FUNC_POINTERS 0xa -#define MAC_S_COALESCED 0xb -#define MAC_S_GB_ZEROFILL 0xc -#define MAC_S_INTERPOSING 0xd -#define MAC_S_16BYTE_LITERALS 0xe +#define MAC_S_LAZY_SYMBOL_POINTERS 0x7 +#define MAC_S_SYMBOL_STUBS 0x8 +#define MAC_S_MOD_INIT_FUNC_POINTERS 0x9 +#define MAC_S_MOD_TERM_FUNC_POINTERS 0xa +#define MAC_S_COALESCED 0xb +#define MAC_S_GB_ZEROFILL 0xc +#define MAC_S_INTERPOSING 0xd +#define MAC_S_16BYTE_LITERALS 0xe -#define MAC_SECTION_ATTRIBUTES_USR 0xff000000 -#define MAC_S_ATTR_PURE_INSTRUCTIONS 0x80000000 -#define MAC_S_ATTR_NO_TOC 0x40000000 -#define MAC_S_ATTR_STRIP_STATIC_SYMS 0x20000000 -#define MAC_S_ATTR_NO_DEAD_STRIP 0x10000000 -#define MAC_S_ATTR_LIVE_SUPPORT 0x08000000 +#define MAC_SECTION_ATTRIBUTES_USR 0xff000000 +#define MAC_S_ATTR_PURE_INSTRUCTIONS 0x80000000 +#define MAC_S_ATTR_NO_TOC 0x40000000 +#define MAC_S_ATTR_STRIP_STATIC_SYMS 0x20000000 +#define MAC_S_ATTR_NO_DEAD_STRIP 0x10000000 +#define MAC_S_ATTR_LIVE_SUPPORT 0x08000000 #define MAC_S_ATTR_SELF_MODIFYING_CODE 0x04000000 -#define MAC_S_ATTR_DEBUG 0x02000000 -#define MAC_SECTION_ATTRIBUTES_SYS 0x00ffff00 -#define MAC_S_ATTR_SOME_INSTRUCTIONS 0x00000400 -#define MAC_S_ATTR_EXT_RELOC 0x00000200 -#define MAC_S_ATTR_LOC_RELOC 0x00000100 +#define MAC_S_ATTR_DEBUG 0x02000000 +#define MAC_SECTION_ATTRIBUTES_SYS 0x00ffff00 +#define MAC_S_ATTR_SOME_INSTRUCTIONS 0x00000400 +#define MAC_S_ATTR_EXT_RELOC 0x00000200 +#define MAC_S_ATTR_LOC_RELOC 0x00000100 -#define MAC_LC_REQ_DYLD 0x80000000 -#define MAC_LC_SEGMENT 0x1 -#define MAC_LC_SYMTAB 0x2 -#define MAC_LC_SYMSEG 0x3 -#define MAC_LC_THREAD 0x4 -#define MAC_LC_UNIXTHREAD 0x5 -#define MAC_LC_LOADFVMLIB 0x6 -#define MAC_LC_IDFVMLIB 0x7 -#define MAC_LC_IDENT 0x8 -#define MAC_LC_FVMFILE 0x9 -#define MAC_LC_PREPAGE 0xa -#define MAC_LC_DYSYMTAB 0xb -#define MAC_LC_LOAD_DYLIB 0xc -#define MAC_LC_ID_DYLIB 0xd -#define MAC_LC_LOAD_DYLINKER 0xe -#define MAC_LC_ID_DYLINKER 0xf -#define MAC_LC_PREBOUND_DYLIB 0x10 -#define MAC_LC_ROUTINES 0x11 -#define MAC_LC_SUB_FRAMEWORK 0x12 -#define MAC_LC_SUB_UMBRELLA 0x13 -#define MAC_LC_SUB_CLIENT 0x14 -#define MAC_LC_SUB_LIBRARY 0x15 -#define MAC_LC_TWOLEVEL_HINTS 0x16 -#define MAC_LC_PREBIND_CKSUM 0x17 -#define MAC_LC_LOAD_WEAK_DYLIB (0x18 | MAC_LC_REQ_DYLD) -#define MAC_LC_SEGMENT_64 0x19 -#define MAC_LC_ROUTINES_64 0x1a -#define MAC_LC_UUID 0x1b -#define MAC_LC_CODE_SIGNATURE 0x1d -#define MAC_LC_SEGMENT_SPLIT_INFO 0x1e -#define MAC_LC_LAZY_LOAD_DYLIB 0x20 -#define MAC_LC_ENCRYPTION_INFO 0x21 -#define MAC_LC_DYLD_INFO 0x22 -#define MAC_LC_VERSION_MIN_MACOSX 0x24 +#define MAC_LC_REQ_DYLD 0x80000000 +#define MAC_LC_SEGMENT 0x1 +#define MAC_LC_SYMTAB 0x2 +#define MAC_LC_SYMSEG 0x3 +#define MAC_LC_THREAD 0x4 +#define MAC_LC_UNIXTHREAD 0x5 +#define MAC_LC_LOADFVMLIB 0x6 +#define MAC_LC_IDFVMLIB 0x7 +#define MAC_LC_IDENT 0x8 +#define MAC_LC_FVMFILE 0x9 +#define MAC_LC_PREPAGE 0xa +#define MAC_LC_DYSYMTAB 0xb +#define MAC_LC_LOAD_DYLIB 0xc +#define MAC_LC_ID_DYLIB 0xd +#define MAC_LC_LOAD_DYLINKER 0xe +#define MAC_LC_ID_DYLINKER 0xf +#define MAC_LC_PREBOUND_DYLIB 0x10 +#define MAC_LC_ROUTINES 0x11 +#define MAC_LC_SUB_FRAMEWORK 0x12 +#define MAC_LC_SUB_UMBRELLA 0x13 +#define MAC_LC_SUB_CLIENT 0x14 +#define MAC_LC_SUB_LIBRARY 0x15 +#define MAC_LC_TWOLEVEL_HINTS 0x16 +#define MAC_LC_PREBIND_CKSUM 0x17 +#define MAC_LC_LOAD_WEAK_DYLIB (0x18 | MAC_LC_REQ_DYLD) +#define MAC_LC_SEGMENT_64 0x19 +#define MAC_LC_ROUTINES_64 0x1a +#define MAC_LC_UUID 0x1b +#define MAC_LC_CODE_SIGNATURE 0x1d +#define MAC_LC_SEGMENT_SPLIT_INFO 0x1e +#define MAC_LC_LAZY_LOAD_DYLIB 0x20 +#define MAC_LC_ENCRYPTION_INFO 0x21 +#define MAC_LC_DYLD_INFO 0x22 +#define MAC_LC_VERSION_MIN_MACOSX 0x24 #define MAC_LC_VERSION_MIN_IPHONEOS 0x25 -#define MAC_LC_FUNCTION_STARTS 0x26 -#define MAC_LC_DYLD_ENVIRONMENT 0x27 -#define MAC_LC_DATA_IN_CODE 0x29 -#define MAC_LC_SOURCE_VERSION 0x2a -#define MAC_LC_RPATH (0x1c | MAC_LC_REQ_DYLD) -#define MAC_LC_MAIN (0x28 | MAC_LC_REQ_DYLD) +#define MAC_LC_FUNCTION_STARTS 0x26 +#define MAC_LC_DYLD_ENVIRONMENT 0x27 +#define MAC_LC_DATA_IN_CODE 0x29 +#define MAC_LC_SOURCE_VERSION 0x2a +#define MAC_LC_RPATH (0x1c | MAC_LC_REQ_DYLD) +#define MAC_LC_MAIN (0x28 | MAC_LC_REQ_DYLD) -#define VM_PROT_NONE 0 -#define VM_PROT_READ 1 -#define VM_PROT_WRITE 2 -#define VM_PROT_EXECUTE 4 -#define VM_PROT_NO_CHANGE 8 -#define VM_PROT_COPY 16 -#define VM_PROT_TRUSTED 32 +#define VM_PROT_NONE 0 +#define VM_PROT_READ 1 +#define VM_PROT_WRITE 2 +#define VM_PROT_EXECUTE 4 +#define VM_PROT_NO_CHANGE 8 +#define VM_PROT_COPY 16 +#define VM_PROT_TRUSTED 32 #define VM_PROT_STRIP_READ 64 #if !(__ASSEMBLER__ + __LINKER__ + 0) @@ -210,4 +211,5 @@ struct MachoLoadUuid { }; #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* !ANSI */ #endif /* COSMOPOLITAN_LIBC_MACHO_H_ */ diff --git a/libc/macros-cpp.inc b/libc/macros-cpp.inc index 308414c2..5ca2a1fb 100644 --- a/libc/macros-cpp.inc +++ b/libc/macros-cpp.inc @@ -24,6 +24,39 @@ #define vzeroupper #endif +/ Begins definition of frameless function that calls no functions. +.macro .leafprologue +#if !(defined(TINY) && !defined(__PG__)) + push %rbp + mov %rsp,%rbp +#endif +.endm + +/ Ends definition of frameless function that calls no functions. +.macro .leafepilogue +#if !(defined(TINY) && !defined(__PG__)) + pop %rbp +#endif + ret +.endm + +/ Good alignment for functions where alignment actually helps. +/ @note 16-byte +.macro .alignfunc +#ifndef __OPTIMIZE_SIZE__ + .p2align 4 +#endif +.endm + +/ Good alignment for loops where alignment actually helps. +/ @note 16-byte if <10 padding otherwise 8-byte +.macro .alignloop +#ifndef __OPTIMIZE_SIZE__ + .p2align 4,,10 + .p2align 4 +#endif +.endm + / Loads Effective Address / Supporting security blankets .macro plea symbol:req reg64:req reg32:req @@ -48,7 +81,7 @@ / Loads Effective Address / Supporting security blankets .macro ezlea symbol:req reg:req -#if __PIC__ + __PIE__ + __code_model_medium__ + __code_model_large__ + 0 > 1 +#if __pic__ + __pie__ + __code_model_medium__ + __code_model_large__ + 0 > 1 / lea \symbol(%rip),%r\reg mov $\symbol,%e\reg #else @@ -71,22 +104,6 @@ xor %ebp,%ebp .endm -/ Begins definition of frameless function that calls no functions. -.macro .leafprologue -#if !(defined(TINY) && !defined(__PG__)) - push %rbp - mov %rsp,%rbp -#endif -.endm - -/ Ends definition of frameless function that calls no functions. -.macro .leafepilogue -#if !(defined(TINY) && !defined(__PG__)) - pop %rbp -#endif - ret -.endm - / Pulls source code file into ZIP portion of binary. / / @param symbol is quoted path relative to root e.g. __FILE__ diff --git a/libc/macros.inc b/libc/macros.inc index d6f860ea..513e9baa 100644 --- a/libc/macros.inc +++ b/libc/macros.inc @@ -31,6 +31,9 @@ .macro .text.startup .section .text.startup,"ax",@progbits .endm +.macro .text.exit + .section .text.exit,"ax",@progbits +.endm .macro .firstclass .section .text.hot,"ax",@progbits .endm @@ -85,6 +88,14 @@ .section .rodata.cst64,"aM",@progbits,64 .align 64 .endm +.macro .tdata + .section .tdata,"awT",@progbits + .align 4 +.endm +.macro .tbss + .section .tdata,"awT",@nobits + .align 4 +.endm / Mergeable NUL-terminated UTF-8 string constant section. / @@ -175,11 +186,16 @@ .endif .endm +/ Custom emulator instruction for bottom stack frame. +.macro bofram endfunc:req + .byte 0x0f,0x1f,0x45,\endfunc-. # nopl disp8(%rbp) +.endm + / Overrides LOOP instruction. / With its mop-Fusion Mexican equivalent. / Thus avoiding 3x legacy pipeline slowdown. .macro loop label:req - .byte 0x83,0xe9,0x01 # sub $1,%ecx + .byte 0x83,0xe9,0x01 # sub $1,%ecx jnz \label .endm diff --git a/libc/math.h b/libc/math.h index cbacf38c..b70b21f2 100644 --- a/libc/math.h +++ b/libc/math.h @@ -252,6 +252,10 @@ long lround(double); long lroundf(float); long lroundl(long double); +int ilogbf(float); +int ilogb(double); +int ilogbl(long double); + long long llrint(double); long long llrintf(float); long long llrintl(long double); @@ -295,13 +299,16 @@ int __fpclassifyl(long double); #define fldlg2() __X87_CONST(fldlg2, M_LOG10_2) #define fldln2() __X87_CONST(fldln2, M_LN2) #define fldl2e() __X87_CONST(fldl2e, M_LOG2E) -#define __X87_CONST(OP, VALUE) \ - ({ \ - long double St0##OP; \ - asm(#OP : "=t"(St0##OP)); \ - /* assume(st0##OP == VALUE); */ \ - St0##OP; \ +#ifdef __x86__ +#define __X87_CONST(OP, VALUE) \ + ({ \ + long double St0##OP; \ + asm(#OP : "=t"(St0##OP)); \ + St0##OP; \ }) +#else +#define __X87_CONST(OP, VALUE) VALUE +#endif #define fnstsw() __X87_FPU_STATUS("fnstsw") #define fstsw() __X87_FPU_STATUS("fstsw") diff --git a/libc/runtime/balloc.c b/libc/mem/balloc.c similarity index 68% rename from libc/runtime/balloc.c rename to libc/mem/balloc.c index c5078c42..aeb711ea 100644 --- a/libc/runtime/balloc.c +++ b/libc/mem/balloc.c @@ -17,16 +17,10 @@ │ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/assert.h" -#include "libc/bits/bits.h" -#include "libc/bits/popcnt.h" -#include "libc/bits/safemacros.h" -#include "libc/calls/calls.h" +#include "libc/mem/mem.h" #include "libc/runtime/buffer.h" -#include "libc/runtime/mappings.h" -#include "libc/runtime/runtime.h" -#include "libc/str/str.h" -#include "libc/sysv/consts/prot.h" + +/* TODO(jart): Delete */ #define kGuard PAGESIZE #define kGrain FRAMESIZE @@ -46,46 +40,5 @@ * @see ralloc() */ void *balloc(struct GuardedBuffer *b, unsigned a, size_t n) { - int rc; - char *p; - size_t mi, need; - struct AddrSize az; - struct MemoryCoord mc; - - assert(a >= 1); - assert(a <= kGuard); - assert(kGuard < kGrain); - assert(popcnt(a) == 1); - assert(popcnt(kGuard) == 1); - assert(popcnt(kGrain) == 1); - assert(n < 0x800000000000ul - kGrain - kGuard); - - if (n) { - az.addr = 0; - az.size = 0; - n = roundup(n, a); - need = roundup(n + kGuard, kGrain); - if (b->p) { - mc = ADDRSIZE_TO_COORD(b->p, 1); - mi = findmapping(mc.y); - if (mi && ISOVERLAPPING(mc, _mm.p[mi - 1])) { - az = COORD_TO_ADDRSIZE(_mm.p[mi - 1]); - if (az.size < need) { - rc = munmap(az.addr, az.size); - assert(rc != -1); - az.addr = NULL; - az.size = 0; - } - } - } - if (!az.size) { - if ((p = mapanon(need)) == MAP_FAILED) abort(); - if (mprotect(p + need - kGuard, kGuard, PROT_NONE) == -1) abort(); - } else { - p = az.addr; - } - b->p = p + need - kGuard - n; - } - - return b->p; + return (b->p = memalign(a, n)); } diff --git a/libc/runtime/bfree.c b/libc/mem/bfree.c similarity index 84% rename from libc/runtime/bfree.c rename to libc/mem/bfree.c index 9913494d..1e819253 100644 --- a/libc/runtime/bfree.c +++ b/libc/mem/bfree.c @@ -19,22 +19,11 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/assert.h" #include "libc/calls/calls.h" +#include "libc/mem/mem.h" #include "libc/runtime/buffer.h" -#include "libc/runtime/mappings.h" + +/* TODO(jart): Delete */ void bfree(struct GuardedBuffer *b) { - int mr; - size_t mi; - struct AddrSize az; - struct MemoryCoord mc; - if (b->p) { - mc = ADDRSIZE_TO_COORD(b->p, 1); - mi = findmapping(mc.y); - assert(mi > 0); - assert(ISOVERLAPPING(mc, _mm.p[mi - 1])); - az = COORD_TO_ADDRSIZE(_mm.p[mi - 1]); - mr = munmap(az.addr, az.size); - assert(mr != -1); - b->p = NULL; - } + free(b->p); } diff --git a/libc/mem/mem.mk b/libc/mem/mem.mk index 00285dd1..edcfc1b0 100644 --- a/libc/mem/mem.mk +++ b/libc/mem/mem.mk @@ -25,6 +25,7 @@ LIBC_MEM_A_CHECKS = \ $(LIBC_MEM_A_HDRS:%=o/$(MODE)/%.ok) LIBC_MEM_A_DIRECTDEPS = \ + LIBC_CONV \ LIBC_STR \ LIBC_FMT \ LIBC_RAND \ diff --git a/libc/mem/reallocarray.c b/libc/mem/reallocarray.c index f72569da..97f235a8 100644 --- a/libc/mem/reallocarray.c +++ b/libc/mem/reallocarray.c @@ -18,7 +18,6 @@ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/conv/conv.h" -#include "libc/conv/sizemultiply.h" #include "libc/mem/mem.h" /** @@ -29,7 +28,5 @@ * @return new address or NULL w/ errno and ptr is NOT free()'d */ void *reallocarray(void *ptr, size_t nmemb, size_t itemsize) { - size_t newsize; - sizemultiply(&newsize, nmemb, itemsize); /* punts error */ - return realloc(ptr, newsize); + return realloc(ptr, nmemb * itemsize); } diff --git a/libc/mem/unhexstr.c b/libc/mem/unhexstr.c index 73de65f4..f699ca5f 100644 --- a/libc/mem/unhexstr.c +++ b/libc/mem/unhexstr.c @@ -25,5 +25,5 @@ nodiscard void *unhexstr(const char *hexdigs) { assert(strlen(hexdigs) % 2 == 0); return unhexbuf(memalign(__BIGGEST_ALIGNMENT__, strlen(hexdigs) / 2), - strlen(hexdigs), hexdigs); + strlen(hexdigs) / 2, hexdigs); } diff --git a/libc/ncabi.h b/libc/ncabi.h deleted file mode 100644 index c2b729a4..00000000 --- a/libc/ncabi.h +++ /dev/null @@ -1,87 +0,0 @@ -#ifndef COSMOPOLITAN_LIBC_NCABI_H_ -#define COSMOPOLITAN_LIBC_NCABI_H_ -#if !(__ASSEMBLER__ + __LINKER__ + 0) -COSMOPOLITAN_C_START_ - -/** - * @fileoverview No-Clobber ABI Function Veneers. - * - * These macros map be specified alongside alongside the prototype of - * any function that promises to not clobber anything except %rax and - * flags. These macros let the compiler know about that promise. That - * allows the compiler to generate faster, smaller code. - */ - -#define NCABI_PRUNABLE asm -#define NCABI_NOPRUNE asm volatile - -#define NCABI_CALL_0(KIND, T, NAME) \ - ({ \ - T ax; \ - KIND("call\t" NAME \ - : "=a"(ax) \ - : /* no inputs */ \ - : "memory", "cc"); \ - ax; \ - }) - -#define NCABI_CALL_1(KIND, T, NAME, T1, P1) \ - ({ \ - T ax; \ - KIND("call\t" NAME : "=a"(ax) : "D"(P1) : "memory", "cc"); \ - ax; \ - }) - -#define NCABI_CALL_2(KIND, T, NAME, T1, P1, T2, P2) \ - ({ \ - T ax; \ - KIND("call\t" NAME : "=a"(ax) : "D"(P1), "S"(P2) : "memory", "cc"); \ - ax; \ - }) - -#define NCABI_CALL_3(KIND, T, NAME, T1, P1, T2, P2, T3, P3) \ - ({ \ - T ax; \ - KIND("call\t" NAME \ - : "=a"(ax) \ - : "D"(P1), "S"(P2), "d"(P3) \ - : "memory", "cc"); \ - ax; \ - }) - -#define NCABI_CALL_4(KIND, T, NAME, T1, P1, T2, P2, T3, P3, T4, P4) \ - ({ \ - T ax; \ - KIND("call\t" NAME \ - : "=a"(ax) \ - : "D"(P1), "S"(P2), "d"(P3), "c"(P4) \ - : "memory", "cc"); \ - ax; \ - }) - -#define NCABI_DECLARE_0(KIND, T, ALIAS, NAME) \ - forceinline nodebuginfo T ALIAS(void) { return NCABI_CALL_0(KIND, T, NAME); } - -#define NCABI_DECLARE_1(KIND, T, ALIAS, NAME, T1) \ - forceinline nodebuginfo T ALIAS(T1 p1) { \ - return NCABI_CALL_1(KIND, T, NAME, T1, p1); \ - } - -#define NCABI_DECLARE_2(KIND, T, ALIAS, NAME, T1, T2) \ - forceinline nodebuginfo T ALIAS(T1 p1, T2 p2) { \ - return NCABI_CALL_2(KIND, T, NAME, T1, p1, T2, p2); \ - } - -#define NCABI_DECLARE_3(KIND, T, ALIAS, NAME, T1, T2, T3) \ - forceinline nodebuginfo T ALIAS(T1 p1, T2 p2, T3 p3) { \ - return NCABI_CALL_3(KIND, T, NAME, T1, p1, T2, p2, T3, p3); \ - } - -#define NCABI_DECLARE_4(KIND, T, ALIAS, NAME, T1, T2, T3, T4) \ - forceinline nodebuginfo T ALIAS(T1 p1, T2 p2, T3 p3, T4 p4) { \ - return NCABI_CALL_4(KIND, T, NAME, T1, p1, T2, p2, T3, p3, T4, p4); \ - } - -COSMOPOLITAN_C_END_ -#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ -#endif /* COSMOPOLITAN_LIBC_NCABI_H_ */ diff --git a/libc/nexgen32e/bcmp.S b/libc/nexgen32e/bcmp.S index d7d999d5..ab813199 100644 --- a/libc/nexgen32e/bcmp.S +++ b/libc/nexgen32e/bcmp.S @@ -28,6 +28,6 @@ / @param esi second string / @param edx byte size / @return 0 if equal or nonzero -bcmp: jmp *hook$memcmp(%rip) +bcmp: jmp *__memcmp(%rip) .endfn bcmp,globl .source __FILE__ diff --git a/libc/nexgen32e/bsf.S b/libc/nexgen32e/bsf.S deleted file mode 100644 index a0686a1d..00000000 --- a/libc/nexgen32e/bsf.S +++ /dev/null @@ -1,43 +0,0 @@ -/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│ -│vi: set et ft=asm ts=8 tw=8 fenc=utf-8 :vi│ -╞══════════════════════════════════════════════════════════════════════════════╡ -│ Copyright 2020 Justine Alexandra Roberts Tunney │ -│ │ -│ This program is free software; you can redistribute it and/or modify │ -│ it under the terms of the GNU General Public License as published by │ -│ the Free Software Foundation; version 2 of the License. │ -│ │ -│ This program is distributed in the hope that it will be useful, but │ -│ WITHOUT ANY WARRANTY; without even the implied warranty of │ -│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ -│ General Public License for more details. │ -│ │ -│ You should have received a copy of the GNU General Public License │ -│ along with this program; if not, write to the Free Software │ -│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ -│ 02110-1301 USA │ -╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/macros.h" - -/ Finds lowest set bit in 𝑥. -/ -/ @param edi is 32-bit unsigned 𝑥 value -/ @return eax in range [0,32) or undefined if 𝑥 is 0 -/ @see also treasure trove of nearly identical functions -/ -/ uint32 𝑥 bsf(𝑥) tzcnt(𝑥) ffs(𝑥) bsr(𝑥) lzcnt(𝑥) -/ 0x00000000 wut 32 0 wut 32 -/ 0x00000001 0 0 1 0 31 -/ 0x80000001 0 0 1 31 0 -/ 0x80000000 31 31 32 31 0 -/ 0x00000010 4 4 5 4 27 -/ 0x08000010 4 4 5 27 4 -/ 0x08000000 27 27 28 27 4 -/ 0xffffffff 0 0 1 31 0 -/ -bsf: .leafprologue - .profilable - bsf %edi,%eax - .leafepilogue - .endfn bsf,globl - .source __FILE__ diff --git a/libc/nexgen32e/bsf.h b/libc/nexgen32e/bsf.h index 72894998..ea4d9e73 100644 --- a/libc/nexgen32e/bsf.h +++ b/libc/nexgen32e/bsf.h @@ -3,18 +3,25 @@ #if !(__ASSEMBLER__ + __LINKER__ + 0) COSMOPOLITAN_C_START_ -unsigned bsf(unsigned) libcesque pureconst; -unsigned bsfl(unsigned long) libcesque pureconst; -unsigned bsfmax(uintmax_t) libcesque pureconst; +/* + * BIT SCANNING 101 + * ctz(𝑥) 31^clz(𝑥) clz(𝑥) + * uint32 𝑥 bsf(𝑥) tzcnt(𝑥) ffs(𝑥) bsr(𝑥) lzcnt(𝑥) + * 0x00000000 wut 32 0 wut 32 + * 0x00000001 0 0 1 0 31 + * 0x80000001 0 0 1 31 0 + * 0x80000000 31 31 32 31 0 + * 0x00000010 4 4 5 4 27 + * 0x08000010 4 4 5 27 4 + * 0x08000000 27 27 28 27 4 + * 0xffffffff 0 0 1 31 0 + */ -#define bsf(X) \ - ({ \ - unsigned Res; \ - typeof(X) Word; \ - asm("bsf\t%1,%0" : "=r,r"(Word) : "r,m"(X)); \ - Res = Word; \ - Res; \ - }) +#define bsf(u) __builtin_ctz(u) +#define bsfl(u) __builtin_ctzl(u) +#define bsfll(u) __builtin_ctzll(u) + +unsigned bsfmax(uintmax_t); COSMOPOLITAN_C_END_ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ diff --git a/libc/nexgen32e/bsfl.S b/libc/nexgen32e/bsfl.S deleted file mode 100644 index 15e49573..00000000 --- a/libc/nexgen32e/bsfl.S +++ /dev/null @@ -1,44 +0,0 @@ -/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│ -│vi: set et ft=asm ts=8 tw=8 fenc=utf-8 :vi│ -╞══════════════════════════════════════════════════════════════════════════════╡ -│ Copyright 2020 Justine Alexandra Roberts Tunney │ -│ │ -│ This program is free software; you can redistribute it and/or modify │ -│ it under the terms of the GNU General Public License as published by │ -│ the Free Software Foundation; version 2 of the License. │ -│ │ -│ This program is distributed in the hope that it will be useful, but │ -│ WITHOUT ANY WARRANTY; without even the implied warranty of │ -│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ -│ General Public License for more details. │ -│ │ -│ You should have received a copy of the GNU General Public License │ -│ along with this program; if not, write to the Free Software │ -│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ -│ 02110-1301 USA │ -╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/macros.h" - -/ Finds lowest set bit in 𝑥. -/ -/ @param rdi is 64-bit unsigned 𝑥 value -/ @return eax number in range [0,64) or undefined if 𝑥 is 0 -/ @see also treasure trove of nearly identical functions -/ -/ uint32 𝑥 bsf(𝑥) tzcnt(𝑥) ffs(𝑥) bsr(𝑥) lzcnt(𝑥) -/ 0x00000000 wut 32 0 wut 32 -/ 0x00000001 0 0 1 0 31 -/ 0x80000001 0 0 1 31 0 -/ 0x80000000 31 31 32 31 0 -/ 0x00000010 4 4 5 4 27 -/ 0x08000010 4 4 5 27 4 -/ 0x08000000 27 27 28 27 4 -/ 0xffffffff 0 0 1 31 0 -/ -bsfl: .leafprologue - .profilable - bsf %rdi,%rax - .leafepilogue - .endfn bsfl,globl - .alias bsfl,bsfll - .source __FILE__ diff --git a/libc/nexgen32e/bsr.S b/libc/nexgen32e/bsr.S deleted file mode 100644 index 963fd37f..00000000 --- a/libc/nexgen32e/bsr.S +++ /dev/null @@ -1,43 +0,0 @@ -/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│ -│vi: set et ft=asm ts=8 tw=8 fenc=utf-8 :vi│ -╞══════════════════════════════════════════════════════════════════════════════╡ -│ Copyright 2020 Justine Alexandra Roberts Tunney │ -│ │ -│ This program is free software; you can redistribute it and/or modify │ -│ it under the terms of the GNU General Public License as published by │ -│ the Free Software Foundation; version 2 of the License. │ -│ │ -│ This program is distributed in the hope that it will be useful, but │ -│ WITHOUT ANY WARRANTY; without even the implied warranty of │ -│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ -│ General Public License for more details. │ -│ │ -│ You should have received a copy of the GNU General Public License │ -│ along with this program; if not, write to the Free Software │ -│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ -│ 02110-1301 USA │ -╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/macros.h" - -/ Returns binary logarithm of integer 𝑥. -/ -/ @param edi is 32-bit unsigned 𝑥 value -/ @return eax number in range [0,32) or undefined if 𝑥 is 0 -/ @see also treasure trove of nearly identical functions -/ -/ uint32 𝑥 bsf(𝑥) tzcnt(𝑥) ffs(𝑥) bsr(𝑥) lzcnt(𝑥) -/ 0x00000000 wut 32 0 wut 32 -/ 0x00000001 0 0 1 0 31 -/ 0x80000001 0 0 1 31 0 -/ 0x80000000 31 31 32 31 0 -/ 0x00000010 4 4 5 4 27 -/ 0x08000010 4 4 5 27 4 -/ 0x08000000 27 27 28 27 4 -/ 0xffffffff 0 0 1 31 0 -/ -bsr: .leafprologue - .profilable - bsr %edi,%eax - .leafepilogue - .endfn bsr,globl - .source __FILE__ diff --git a/libc/nexgen32e/bsr.h b/libc/nexgen32e/bsr.h index cefe02cf..4d77806f 100644 --- a/libc/nexgen32e/bsr.h +++ b/libc/nexgen32e/bsr.h @@ -3,26 +3,25 @@ #if !(__ASSEMBLER__ + __LINKER__ + 0) COSMOPOLITAN_C_START_ -unsigned bsr(unsigned) libcesque pureconst; -unsigned bsrl(unsigned long) libcesque pureconst; -unsigned bsrmax(uintmax_t) libcesque pureconst; +/* + * BIT SCANNING 101 + * ctz(𝑥) 31^clz(𝑥) clz(𝑥) + * uint32 𝑥 bsf(𝑥) tzcnt(𝑥) ffs(𝑥) bsr(𝑥) lzcnt(𝑥) + * 0x00000000 wut 32 0 wut 32 + * 0x00000001 0 0 1 0 31 + * 0x80000001 0 0 1 31 0 + * 0x80000000 31 31 32 31 0 + * 0x00000010 4 4 5 4 27 + * 0x08000010 4 4 5 27 4 + * 0x08000000 27 27 28 27 4 + * 0xffffffff 0 0 1 31 0 + */ -#ifdef __GNUC__ -#define bsr(X) \ - ({ \ - unsigned Word; \ - asm("bsrl\t%1,%0" : "=r,r"(Word) : "r,m"(X)); \ - Word; \ - }) -#define bsrl(X) \ - ({ \ - unsigned Res; \ - unsigned long Word; \ - asm("bsrq\t%1,%0" : "=r,r"(Word) : "r,m"(X)); \ - Res = Word; \ - Res; \ - }) -#endif +#define bsr(u) ((sizeof(unsigned) * 8 - 1) ^ __builtin_clz(u)) +#define bsrl(u) ((sizeof(unsigned long) * 8 - 1) ^ __builtin_clzl(u)) +#define bsrll(u) ((sizeof(unsigned long long) * 8 - 1) ^ __builtin_clzll(u)) + +unsigned bsrmax(uintmax_t); COSMOPOLITAN_C_END_ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ diff --git a/libc/nexgen32e/bsrl.S b/libc/nexgen32e/bsrl.S deleted file mode 100644 index 9640d022..00000000 --- a/libc/nexgen32e/bsrl.S +++ /dev/null @@ -1,44 +0,0 @@ -/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│ -│vi: set et ft=asm ts=8 tw=8 fenc=utf-8 :vi│ -╞══════════════════════════════════════════════════════════════════════════════╡ -│ Copyright 2020 Justine Alexandra Roberts Tunney │ -│ │ -│ This program is free software; you can redistribute it and/or modify │ -│ it under the terms of the GNU General Public License as published by │ -│ the Free Software Foundation; version 2 of the License. │ -│ │ -│ This program is distributed in the hope that it will be useful, but │ -│ WITHOUT ANY WARRANTY; without even the implied warranty of │ -│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ -│ General Public License for more details. │ -│ │ -│ You should have received a copy of the GNU General Public License │ -│ along with this program; if not, write to the Free Software │ -│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ -│ 02110-1301 USA │ -╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/macros.h" - -/ Returns binary logarithm of integer 𝑥. -/ -/ @param rdi is 32-bit unsigned 𝑥 value -/ @return eax number in range [0,64) or undefined if 𝑥 is 0 -/ @see also treasure trove of nearly identical functions -/ -/ uint32 𝑥 bsf(𝑥) tzcnt(𝑥) ffs(𝑥) bsr(𝑥) lzcnt(𝑥) -/ 0x00000000 wut 32 0 wut 32 -/ 0x00000001 0 0 1 0 31 -/ 0x80000001 0 0 1 31 0 -/ 0x80000000 31 31 32 31 0 -/ 0x00000010 4 4 5 4 27 -/ 0x08000010 4 4 5 27 4 -/ 0x08000000 27 27 28 27 4 -/ 0xffffffff 0 0 1 31 0 -/ -bsrl: .leafprologue - .profilable - bsr %rdi,%rax - .leafepilogue - .endfn bsrl,globl - .alias bsrl,bsrll - .source __FILE__ diff --git a/libc/nexgen32e/bzero.S b/libc/nexgen32e/bzero.S index 0267db6a..0ada5c6e 100644 --- a/libc/nexgen32e/bzero.S +++ b/libc/nexgen32e/bzero.S @@ -30,6 +30,6 @@ / @see memset(), explicit_bzero() bzero: mov %rsi,%rdx xor %esi,%esi - jmp _memset + jmp MemSet .endfn bzero,globl .source __FILE__ diff --git a/libc/nexgen32e/cmpsl.S b/libc/nexgen32e/cmpsl.S index 59c7906e..4cfb70ac 100644 --- a/libc/nexgen32e/cmpsl.S +++ b/libc/nexgen32e/cmpsl.S @@ -29,7 +29,10 @@ cmpsl: .leafprologue .profilable xor %eax,%eax cmpsl - cmovl .Lone(%rip),%eax +/ mov (%rdi),%edi +/ mov (%rsi),%esi +/ cmp %edi,%esi + setl %al cmovg .Lneg1(%rip),%eax .leafepilogue .endfn cmpsl,globl diff --git a/libc/nexgen32e/crc32.h b/libc/nexgen32e/crc32.h new file mode 100644 index 00000000..f01610d4 --- /dev/null +++ b/libc/nexgen32e/crc32.h @@ -0,0 +1,17 @@ +#ifndef COSMOPOLITAN_LIBC_NEXGEN32E_CRC32_H_ +#define COSMOPOLITAN_LIBC_NEXGEN32E_CRC32_H_ +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +extern const uint32_t kCrc32Tab[256]; + +void crc32init(uint32_t[hasatleast 256], uint32_t); +uint32_t crc32_z(uint32_t, const void *, size_t); +extern uint32_t (*const crc32c)(uint32_t, const void *, size_t) paramsnonnull(); +uint32_t crc32c$pure(uint32_t, const void *, size_t) strlenesque hidden; +uint32_t crc32c$sse42(uint32_t, const void *, size_t) strlenesque hidden; +uint32_t crc32$pclmul(uint32_t, const void *, size_t) hidden; + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_NEXGEN32E_CRC32_H_ */ diff --git a/libc/nexgen32e/crc32c-pure.c b/libc/nexgen32e/crc32c-pure.c index 69f041f6..5bbd9add 100644 --- a/libc/nexgen32e/crc32c-pure.c +++ b/libc/nexgen32e/crc32c-pure.c @@ -17,7 +17,7 @@ │ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/str/internal.h" +#include "libc/nexgen32e/crc32.h" /** * Computes Castagnoli CRC-32 on old computers. diff --git a/libc/nexgen32e/crc32c-sse42.c b/libc/nexgen32e/crc32c-sse42.c index de2f0cd4..f724f541 100644 --- a/libc/nexgen32e/crc32c-sse42.c +++ b/libc/nexgen32e/crc32c-sse42.c @@ -23,11 +23,11 @@ * Hashes data with hardware acceleration at 10GBps. * @note needs Nehalem+ c. 2008 or Bulldozer+ c. 2011 */ -uint32_t crc32c$sse42(uint32_t init, const void *data, size_t size) { +uint32_t crc32c$sse42(uint32_t init, const void *data, size_t n) { const unsigned char *p = (const unsigned char *)data; - const unsigned char *pe = (const unsigned char *)data + size; + const unsigned char *pe = (const unsigned char *)data + n; uint32_t h = init ^ 0xffffffff; - if (size >= 16 + 8) { + if (n >= 16 + 8) { while ((uintptr_t)p & 7) asm("crc32b\t%1,%0" : "+r"(h) : "rm"(*p++)); uint64_t hl = h; while (p < pe - 16ul) { diff --git a/libc/nexgen32e/crc32c.S b/libc/nexgen32e/crc32c.S index 57bebc55..147ca246 100644 --- a/libc/nexgen32e/crc32c.S +++ b/libc/nexgen32e/crc32c.S @@ -36,11 +36,9 @@ crc32c: .quad 0 .init.start 300,_init_crc32c ezlea crc32c$pure,ax -#if !IsTiny() ezlea crc32c$sse42,cx testb X86_HAVE(SSE4_2)+kCpuids(%rip) cmovnz %rcx,%rax -#endif stosq .init.end 300,_init_crc32c .source __FILE__ diff --git a/libc/nexgen32e/crc32init.S b/libc/nexgen32e/crc32init.S index af2d79d2..9af6fb9d 100644 --- a/libc/nexgen32e/crc32init.S +++ b/libc/nexgen32e/crc32init.S @@ -50,7 +50,7 @@ crc32init: dec %eax mov %eax,(%rsp) jnz 0b - movups (%rsp),%xmm1 + movdqu (%rsp),%xmm1 1: mov $8,%ecx movdqa %xmm1,%xmm3 2: movdqa %xmm3,%xmm4 diff --git a/libc/nexgen32e/crc32z.c b/libc/nexgen32e/crc32z.c index 58a2f9a0..94280ab2 100644 --- a/libc/nexgen32e/crc32z.c +++ b/libc/nexgen32e/crc32z.c @@ -18,9 +18,8 @@ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/bits/safemacros.h" +#include "libc/nexgen32e/crc32.h" #include "libc/nexgen32e/x86feature.h" -#include "libc/str/internal.h" -#include "libc/str/str.h" /** * Computes Phil Katz CRC-32 used by zip/zlib/gzip/etc. diff --git a/libc/nexgen32e/div10.greg.S b/libc/nexgen32e/div10.greg.S deleted file mode 100644 index 5690a2a3..00000000 --- a/libc/nexgen32e/div10.greg.S +++ /dev/null @@ -1,119 +0,0 @@ -/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│ -│vi: set et ft=asm ts=8 tw=8 fenc=utf-8 :vi│ -╞══════════════════════════════════════════════════════════════════════════════╡ -│ Copyright 2020 Justine Alexandra Roberts Tunney │ -│ │ -│ This program is free software; you can redistribute it and/or modify │ -│ it under the terms of the GNU General Public License as published by │ -│ the Free Software Foundation; version 2 of the License. │ -│ │ -│ This program is distributed in the hope that it will be useful, but │ -│ WITHOUT ANY WARRANTY; without even the implied warranty of │ -│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ -│ General Public License for more details. │ -│ │ -│ You should have received a copy of the GNU General Public License │ -│ along with this program; if not, write to the Free Software │ -│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ -│ 02110-1301 USA │ -╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/macros.h" - -/ Performs 128-bit div+mod by 10 without using div or mod. -/ -/ If we didn't have this one-off function, our palandprintf() -/ implementation would cause nearly everything to need a soft -/ math library. It also somehow goes faster than 64-bit IDIV. -/ -/ @param rdi:rsi is the number -/ @param rdx points to where remainder goes -/ @return rax:rdx is result of division -/ @see “Division by Invariant Integers using Multiplication” -/ @see llog10() and div10int64() is a tiny bit faster -div10: .leafprologue - .profilable - push %rbx - mov %rdx,%r8 - test %rsi,%rsi - je 1f - bsr %rsi,%r10 - xor $63,%r10d - mov $125,%r9d - sub %r10d,%r9d - cmp $64,%r9d - jne 6f - xor %eax,%eax - xor %r11d,%r11d - jmp 9f -1: test %r8,%r8 - je 3f - movabs $0xcccccccccccccccd,%rcx - mov %rdi,%rax - mul %rcx - shr $3,%rdx - add %edx,%edx - lea (%rdx,%rdx,4),%eax - mov %edi,%ecx - sub %eax,%ecx - mov %ecx,(%r8) -3: movabs $0xcccccccccccccccd,%rcx - mov %rdi,%rax - mul %rcx - mov %rdx,%rax - shr $3,%rax - xor %edi,%edi - jmp 14f -6: mov %r9d,%ecx - neg %cl - cmp $62,%r10d - jb 8f - mov %rdi,%rdx - shl %cl,%rdx - mov %rsi,%rax - mov %r9d,%ecx - shr %cl,%rax - shrd %cl,%rsi,%rdi - xor %r11d,%r11d - mov %rdi,%rsi - mov %rdx,%rdi - jmp 9f -8: mov %rdi,%r11 - shl %cl,%r11 - mov %rsi,%rax - shl %cl,%rax - mov %r9d,%ecx - shr %cl,%rdi - or %rax,%rdi - shr %cl,%rsi - xor %eax,%eax -9: add $-125,%r10d - xor %ecx,%ecx - mov $9,%r9d -10: shld $1,%rsi,%rax - shld $1,%rdi,%rsi - shld $1,%r11,%rdi - mov %r11,%rdx - add %r11,%rdx - mov %rcx,%r11 - or %rdx,%r11 - cmp %rsi,%r9 - mov $0,%ebx - sbb %rax,%rbx - sar $63,%rbx - mov %ebx,%ecx - and $1,%ecx - and $10,%ebx - sub %rbx,%rsi - sbb $0,%rax - inc %r10d - jne 10b - test %r8,%r8 - je 13f - mov %esi,(%r8) -13: lea (%rcx,%r11,2),%rax - shld $1,%rdx,%rdi -14: mov %rdi,%rdx - pop %rbx - .leafepilogue - .endfn div10,globl,hidden - .source __FILE__ diff --git a/libc/nexgen32e/div1000000000int64.greg.S b/libc/nexgen32e/div1000000000int64.S similarity index 96% rename from libc/nexgen32e/div1000000000int64.greg.S rename to libc/nexgen32e/div1000000000int64.S index caaa4c49..690ab9b9 100644 --- a/libc/nexgen32e/div1000000000int64.greg.S +++ b/libc/nexgen32e/div1000000000int64.S @@ -19,10 +19,10 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/macros.h" -/ Divides 64-bit integer by 1,000,000,000. +/ Divides 64-bit signed integer by 1,000,000,000. / / @param rdi is number to divide -/ @return truncated numerator +/ @return quotient div1000000000int64: mov $0x1a,%cl movabs $0x112e0be826d694b3,%rdx diff --git a/libc/nexgen32e/div1000000int64.greg.S b/libc/nexgen32e/div1000000int64.S similarity index 96% rename from libc/nexgen32e/div1000000int64.greg.S rename to libc/nexgen32e/div1000000int64.S index 2a5a4f5f..035c8920 100644 --- a/libc/nexgen32e/div1000000int64.greg.S +++ b/libc/nexgen32e/div1000000int64.S @@ -19,10 +19,10 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/macros.h" -/ Divides 64-bit integer by 1,000,000. +/ Divides 64-bit signed integer by 1,000,000. / / @param rdi is number to divide -/ @return truncated numerator +/ @return quotient div1000000int64: mov $0x12,%cl movabs $0x431bde82d7b634db,%rdx diff --git a/libc/nexgen32e/div10000int64.S b/libc/nexgen32e/div10000int64.S new file mode 100644 index 00000000..b163fb0a --- /dev/null +++ b/libc/nexgen32e/div10000int64.S @@ -0,0 +1,31 @@ +/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│ +│vi: set et ft=asm ts=8 tw=8 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/macros.h" + +/ Divides 64-bit signed integer by 10,000. +/ +/ @param rdi is number to divide +/ @return truncated quotient +div10000int64: + mov $11,%cl + movabs $0x346dc5d63886594b,%rdx + jmp tinydivsi + .endfn div10000int64,globl + .source __FILE__ diff --git a/libc/nexgen32e/div1000int64.greg.S b/libc/nexgen32e/div1000int64.S similarity index 97% rename from libc/nexgen32e/div1000int64.greg.S rename to libc/nexgen32e/div1000int64.S index bab8f9bc..9c310b92 100644 --- a/libc/nexgen32e/div1000int64.greg.S +++ b/libc/nexgen32e/div1000int64.S @@ -19,10 +19,10 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/macros.h" -/ Divides 64-bit integer by 1,000. +/ Divides 64-bit signed integer by 1,000. / / @param rdi is number to divide -/ @return truncated numerator +/ @return quotient div1000int64: mov $0x7,%cl movabs $0x20c49ba5e353f7cf,%rdx diff --git a/libc/nexgen32e/div100int64.S b/libc/nexgen32e/div100int64.S new file mode 100644 index 00000000..a625ef0a --- /dev/null +++ b/libc/nexgen32e/div100int64.S @@ -0,0 +1,36 @@ +/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│ +│vi: set et ft=asm ts=8 tw=8 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/macros.h" + +/ Divides 64-bit signed integer by 100. +/ +/ @param rdi is number to divide +/ @return rax has quotient +div100int64: + mov %rdi,%rax + movabs $-6640827866535438581,%rdx + imul %rdx + lea (%rdx,%rdi),%rax + sar $63,%rdi + sar $6,%rax + sub %rdi,%rax + ret + .endfn div100int64,globl + .source __FILE__ diff --git a/libc/nexgen32e/div10int64.greg.S b/libc/nexgen32e/div10int64.S similarity index 96% rename from libc/nexgen32e/div10int64.greg.S rename to libc/nexgen32e/div10int64.S index 86b6e5fb..5aff3277 100644 --- a/libc/nexgen32e/div10int64.greg.S +++ b/libc/nexgen32e/div10int64.S @@ -19,12 +19,12 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/macros.h" -/ Divides 64-bit integer by 10. +/ Divides 64-bit signed integer by 10. / / @param rdi is number to divide -/ @return truncated numerator +/ @return quotient div10int64: - mov $0x2,%cl + mov $2,%cl movabs $0x6666666666666667,%rdx jmp tinydivsi .endfn div10int64,globl diff --git a/libc/nexgen32e/doc/memrchr.c b/libc/nexgen32e/doc/memrchr.c index b74cb2a9..12ab7756 100644 --- a/libc/nexgen32e/doc/memrchr.c +++ b/libc/nexgen32e/doc/memrchr.c @@ -17,8 +17,6 @@ │ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/dce.h" -#include "libc/str/str.h" #define N 32 typedef uint8_t uint8_v _Vector_size(N); diff --git a/libc/nexgen32e/explicit_bzero.S b/libc/nexgen32e/explicit_bzero.S index 744c57d8..5ee8e5bf 100644 --- a/libc/nexgen32e/explicit_bzero.S +++ b/libc/nexgen32e/explicit_bzero.S @@ -39,12 +39,12 @@ explicit_bzero: xor %r9,%r9 xor %r10,%r10 xor %r11,%r11 - xorps %xmm0,%xmm0 - xorps %xmm1,%xmm1 - xorps %xmm2,%xmm2 - xorps %xmm3,%xmm3 - xorps %xmm4,%xmm4 - xorps %xmm5,%xmm5 + pxor %xmm0,%xmm0 + pxor %xmm1,%xmm1 + pxor %xmm2,%xmm2 + pxor %xmm3,%xmm3 + pxor %xmm4,%xmm4 + pxor %xmm5,%xmm5 .leafepilogue .endfn explicit_bzero,globl .source __FILE__ diff --git a/libc/nexgen32e/ffs.S b/libc/nexgen32e/ffs.S index 39be2372..375aea08 100644 --- a/libc/nexgen32e/ffs.S +++ b/libc/nexgen32e/ffs.S @@ -41,7 +41,7 @@ ffs: .leafprologue bsf %edi,%eax or $-1,%edx cmovz %edx,%eax - inc %eax + add $1,%eax .leafepilogue .endfn ffs,globl .source __FILE__ diff --git a/libc/nexgen32e/ffsl.S b/libc/nexgen32e/ffsl.S index 44327c06..dac385dd 100644 --- a/libc/nexgen32e/ffsl.S +++ b/libc/nexgen32e/ffsl.S @@ -41,7 +41,7 @@ ffsl: .leafprologue bsf %rdi,%rax or $-1,%edx cmovz %edx,%eax - inc %eax + add $1,%eax .leafepilogue .endfn ffsl,globl .alias ffsl,ffsll diff --git a/libc/nexgen32e/gc.S b/libc/nexgen32e/gc.S index 0460da7d..215a8121 100644 --- a/libc/nexgen32e/gc.S +++ b/libc/nexgen32e/gc.S @@ -33,9 +33,10 @@ / @param rax,rdx,xmm0,xmm1,st0,st1 is return value / @see test/libc/runtime/gc_test.c / -__gc: decq __garbage(%rip) - mov __garbage(%rip),%r8 - mov __garbage+16(%rip),%r9 +CollectGarbage: + decq g_garbage(%rip) + mov g_garbage(%rip),%r8 + mov g_garbage+16(%rip),%r9 js 9f shl $5,%r8 lea (%r9,%r8),%r8 @@ -45,28 +46,25 @@ __gc: decq __garbage(%rip) / push %rbp mov %rsp,%rbp - sub $0x40,%rsp + sub $0x20,%rsp push %rax push %rdx - fstpl -0x40(%rbp) - fstpl -0x30(%rbp) movaps %xmm0,-0x20(%rbp) movaps %xmm1,-0x10(%rbp) call *%r9 movaps -0x10(%rbp),%xmm1 movaps -0x20(%rbp),%xmm0 - fldl -0x30(%rbp) - fldl -0x40(%rbp) pop %rdx pop %rax leave ret 9: call abort - .endfn __gc,globl,hidden + .endfn CollectGarbage,globl,hidden + .source __FILE__ .bss .align 8 -__garbage: +g_garbage: .quad 0 # garbage.i .quad 0 # garbage.n .quad 0 # garbage.p @@ -76,16 +74,10 @@ __garbage: .quad 0 # garbage.p[𝑖].arg .quad 0 # garbage.p[𝑖].ret .endr - .endobj __garbage,globl,hidden + .endobj g_garbage,globl,hidden .previous .init.start 100,_init_garbage - push %rdi - ezlea __garbage+8,di - pushpop INITIAL_CAPACITY,%rax - stosq - lea 8(%rdi),%rax - stosq - pop %rdi + movb $INITIAL_CAPACITY,g_garbage+8(%rip) + movl $g_garbage+24,g_garbage+16(%rip) .init.end 100,_init_garbage - .source __FILE__ diff --git a/libc/nexgen32e/gc.h b/libc/nexgen32e/gc.h index 2183bc6a..b3b3b443 100644 --- a/libc/nexgen32e/gc.h +++ b/libc/nexgen32e/gc.h @@ -15,9 +15,9 @@ struct Garbages { } * p; }; -hidden extern struct Garbages __garbage; +hidden extern struct Garbages g_garbage; -int64_t __gc(void) hidden; +int64_t CollectGarbage(void) hidden; COSMOPOLITAN_C_END_ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ diff --git a/libc/nexgen32e/gclongjmp.S b/libc/nexgen32e/gclongjmp.S index 65977768..eb96fe14 100644 --- a/libc/nexgen32e/gclongjmp.S +++ b/libc/nexgen32e/gclongjmp.S @@ -33,8 +33,8 @@ gclongjmp: .leafprologue .profilable - .weak __garbage - lea __garbage(%rip),%r12 + .weak g_garbage + lea g_garbage(%rip),%r12 test %r12,%r12 jnz .L.unwind.destructors 0: jmp longjmp diff --git a/libc/nexgen32e/kcp437.S b/libc/nexgen32e/kcp437.S index ef845cbb..f76a2662 100644 --- a/libc/nexgen32e/kcp437.S +++ b/libc/nexgen32e/kcp437.S @@ -20,6 +20,7 @@ #include "libc/macros.h" .rodata .align 16 +.source __FILE__ / ibm cp437 unicode table w/ string literal safety / @@ -70,7 +71,7 @@ kCp437: .short 0x0040,0x0041,0x0042,0x0043,0x0044,0x0045,0x0046,0x0047 #40:@ABCDEFG .short 0x0048,0x0049,0x004a,0x004b,0x004c,0x004d,0x004e,0x004f #48:HIJKLMNO .short 0x0050,0x0051,0x0052,0x0053,0x0054,0x0055,0x0056,0x0057 #50:PQRSTUVW -.short 0x0058,0x0059,0x005a,0x005b,0x2572,0x005d,0x005e,0x005f #58:XYZ[╲]^_ +.short 0x0058,0x0059,0x005a,0x005b,0x005c,0x005d,0x005e,0x005f #58:XYZ[\]^_ .short 0x0060,0x0061,0x0062,0x0063,0x0064,0x0065,0x0066,0x0067 #60:`abcdefg .short 0x0068,0x0069,0x006a,0x006b,0x006c,0x006d,0x006e,0x006f #68:hijklmno .short 0x0070,0x0071,0x0072,0x0073,0x0074,0x0075,0x0076,0x0077 #70:pqrstuvw @@ -93,4 +94,3 @@ kCp437: .short 0x00b0,0x2219,0x00b7,0x221a,0x207f,0x00b2,0x25a0,0x03bb #f8:°∙·√ⁿ²■λ .endobj kCp437,globl .previous - .source __FILE__ diff --git a/libc/nexgen32e/kcpuids.S b/libc/nexgen32e/kcpuids.S index fd01c757..f3a2cebf 100644 --- a/libc/nexgen32e/kcpuids.S +++ b/libc/nexgen32e/kcpuids.S @@ -65,8 +65,8 @@ kCpuids:.long 0,0,0,0 # EAX=0 (Basic Processor Info) 2: pop %rax test %eax,%eax # EAX = stacklist->pop() jz 3f # EAX ≠ 0 (EOL sentinel) - cmp KCPUIDS(0H,EAX)(%r8),%eax # EAX ≤ CPUID.0 max leaf - jle 1b # CPUID too new to probe + cmp KCPUIDS(0H,EAX)(%r8),%al # EAX ≤ CPUID.0 max leaf + jbe 1b # CPUID too new to probe add $4*4,%rdi jmp 2b 3: nop diff --git a/libc/nexgen32e/kcpuids.h b/libc/nexgen32e/kcpuids.h index 7328d3d0..f104f06f 100644 --- a/libc/nexgen32e/kcpuids.h +++ b/libc/nexgen32e/kcpuids.h @@ -8,7 +8,7 @@ #define KCPUIDS_80000001H 4 #define KCPUIDS_80000007H 5 #define KCPUIDS_16H 6 -#define _KCPUIDS_LEN 7 +#define KCPUIDS_LEN 7 #define KCPUIDS_6H -1 /* TBD: Thermal and Power Management */ #define KCPUIDS_DH -1 /* TBD: Extended state features */ #define KCPUIDS_80000008H -1 /* TBD: AMD Miscellaneous */ @@ -36,7 +36,7 @@ COSMOPOLITAN_C_START_ * @note Protected with PIRO * @see X86_HAVE() */ -extern const unsigned kCpuids[_KCPUIDS_LEN][4]; +extern const unsigned kCpuids[KCPUIDS_LEN][4]; COSMOPOLITAN_C_END_ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ diff --git a/libc/nexgen32e/kstarttsc.S b/libc/nexgen32e/kstarttsc.S index b3de1246..9f57da66 100644 --- a/libc/nexgen32e/kstarttsc.S +++ b/libc/nexgen32e/kstarttsc.S @@ -34,4 +34,5 @@ kStartTsc: xchg %edx,%eax stosl .init.end 200,_init_kStartTsc + .source __FILE__ diff --git a/libc/nexgen32e/memcmp-avx2.S b/libc/nexgen32e/memcmp-avx2.S index 1387d2f6..91e1940d 100644 --- a/libc/nexgen32e/memcmp-avx2.S +++ b/libc/nexgen32e/memcmp-avx2.S @@ -28,6 +28,7 @@ / @note AVX2 requires Haswell (2014+) or Excavator (2015+) / @see libc/nexgen32e/memcmp.S (for benchmarks) / @asyncsignalsafe + .align 16 memcmp$avx2: .leafprologue .profilable @@ -58,7 +59,7 @@ memcmp$avx2: jz 5b jmp 8f 7: xor %eax,%eax -8: vxorps %ymm0,%ymm0,%ymm0 +8: vpxor %ymm0,%ymm0,%ymm0 .leafepilogue .endfn memcmp$avx2,globl,hidden .source __FILE__ diff --git a/libc/nexgen32e/memcmp-hook.S b/libc/nexgen32e/memcmp-hook.S index d8063573..b3a2fc58 100644 --- a/libc/nexgen32e/memcmp-hook.S +++ b/libc/nexgen32e/memcmp-hook.S @@ -29,9 +29,9 @@ / @return unsigned char subtraction at stop index / @asyncsignalsafe .initbss 300,_init_memcmp -hook$memcmp: +__memcmp: .quad 0 - .endobj hook$memcmp,globl,hidden + .endobj __memcmp,globl,hidden .previous .init.start 300,_init_memcmp diff --git a/libc/nexgen32e/memcmp-sse2.S b/libc/nexgen32e/memcmp-sse2.S index 0494e64f..452aec85 100644 --- a/libc/nexgen32e/memcmp-sse2.S +++ b/libc/nexgen32e/memcmp-sse2.S @@ -44,7 +44,7 @@ memcmp$sse2: movdqu (%rsi,%rcx),%xmm1 pcmpeqb %xmm1,%xmm0 pmovmskb %xmm0,%eax - subl $0xffff,%eax + sub $0xffff,%eax jz 1b bsf %eax,%eax add %rax,%rcx diff --git a/libc/nexgen32e/memcmp.S b/libc/nexgen32e/memcmp.S index 62de6938..1a9b18e9 100644 --- a/libc/nexgen32e/memcmp.S +++ b/libc/nexgen32e/memcmp.S @@ -26,7 +26,7 @@ / @param edx byte size / @return unsigned char subtraction at stop index / @asyncsignalsafe -memcmp: jmp *hook$memcmp(%rip) +memcmp: jmp *__memcmp(%rip) .endfn memcmp,globl .source __FILE__ diff --git a/libc/nexgen32e/memcpy.S b/libc/nexgen32e/memcpy.S index dd9ae4c2..ec435c19 100644 --- a/libc/nexgen32e/memcpy.S +++ b/libc/nexgen32e/memcpy.S @@ -46,17 +46,17 @@ .source __FILE__ memcpy: mov %rdi,%rax / 𝑠𝑙𝑖𝑑𝑒 + .endfn memcpy,globl / Copies memory w/ minimal impact ABI. / / @param rdi is dest / @param rsi is src / @param rdx is number of bytes -/ @clob flags,xmm3 +/ @clob flags,xmm3,xmm4 / @mode long .align 16 -_memcpy:.leafprologue - .profilable +MemCpy: .leafprologue push %rcx mov $.Lmemcpytab.ro.size,%ecx cmp %rcx,%rdx @@ -65,27 +65,29 @@ _memcpy:.leafprologue .Lanchorpoint: .L32r: cmp $1024,%rdx jae .Lerms -.L32: mov $32,%rcx +.L32: vmovdqu -32(%rsi,%rdx),%ymm4 + mov $32,%rcx 0: add $32,%rcx vmovdqu -64(%rsi,%rcx),%ymm3 vmovdqu %ymm3,-64(%rdi,%rcx) cmp %rcx,%rdx ja 0b - vmovdqu -32(%rsi,%rdx),%ymm3 - vmovdqu %ymm3,-32(%rdi,%rdx) - vxorps %ymm3,%ymm3,%ymm3 + vmovdqu %ymm4,-32(%rdi,%rdx) + vpxor %ymm4,%ymm4,%ymm4 + vpxor %ymm3,%ymm3,%ymm3 jmp .L0 .L16r: cmp $1024,%rdx jae .Lerms -.L16: mov $16,%rcx +.L16: movdqu -16(%rsi,%rdx),%xmm4 + mov $16,%rcx 0: add $16,%rcx movdqu -32(%rsi,%rcx),%xmm3 movdqu %xmm3,-32(%rdi,%rcx) cmp %rcx,%rdx ja 0b - movdqu -16(%rsi,%rdx),%xmm3 - movdqu %xmm3,-16(%rdi,%rdx) - xorps %xmm3,%xmm3 + movdqu %xmm4,-16(%rdi,%rdx) + pxor %xmm4,%xmm4 + pxor %xmm3,%xmm3 jmp .L0 .L8: push %rbx mov (%rsi),%rcx @@ -134,10 +136,9 @@ _memcpy:.leafprologue sfence movdqu -16(%rsi,%rdx),%xmm3 movdqu %xmm3,-16(%rdi,%rdx) - xorps %xmm3,%xmm3 + pxor %xmm3,%xmm3 jmp .L0 - .endfn _memcpy,globl,hidden - .endfn memcpy,globl + .endfn MemCpy,globl,hidden .initro 300,_init_memcpy memcpytab.ro: diff --git a/libc/nexgen32e/memjmpinit.S b/libc/nexgen32e/memjmpinit.S index 0e3d57dd..352d86da 100644 --- a/libc/nexgen32e/memjmpinit.S +++ b/libc/nexgen32e/memjmpinit.S @@ -29,6 +29,7 @@ / @param rdx is address of indirect branch / @param ecx is size of jump table memjmpinit: + .leafprologue setnz %r8b shl %r8b 0: xor %eax,%eax @@ -44,6 +45,6 @@ memjmpinit: add %rdx,%rax stosq lodsq - ret + .leafepilogue .endfn memjmpinit,globl,hidden .source __FILE__ diff --git a/libc/nexgen32e/memmove.S b/libc/nexgen32e/memmove.S index f34a037e..3efe08d3 100644 --- a/libc/nexgen32e/memmove.S +++ b/libc/nexgen32e/memmove.S @@ -28,10 +28,12 @@ / @param rdx is number of bytes / @return original rdi copied to rax / @asyncsignalsafe -memmove:mov %rdi,%rax +memmove: + mov %rdi,%rax / 𝑠𝑙𝑖𝑑𝑒 + .endfn MemMove,globl,hidden -_memmove: +MemMove: .leafprologue .profilable push %rcx @@ -49,6 +51,5 @@ _memmove: pop %rdi pop %rcx .leafepilogue - .endfn _memmove,globl,hidden .endfn memmove,globl .source __FILE__ diff --git a/libc/nexgen32e/mempcpy.S b/libc/nexgen32e/mempcpy.S index 9f286e54..20e4d5b8 100644 --- a/libc/nexgen32e/mempcpy.S +++ b/libc/nexgen32e/mempcpy.S @@ -18,6 +18,7 @@ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/macros.h" +.source __FILE__ / Copies memory. / @@ -27,7 +28,7 @@ / @param rsi is src / @param rdx is number of bytes / @return original rdi + rdx copied to rax -mempcpy:lea (%rdi,%rdx),%rax - jmp _memcpy +mempcpy: + lea (%rdi,%rdx),%rax + jmp MemCpy .endfn mempcpy,globl - .source __FILE__ diff --git a/libc/nexgen32e/memset.S b/libc/nexgen32e/memset.S index 22e31f10..a04b5392 100644 --- a/libc/nexgen32e/memset.S +++ b/libc/nexgen32e/memset.S @@ -36,6 +36,7 @@ / @asyncsignalsafe memset: mov %rdi,%rax / fallthrough + .endfn memset,globl / Sets memory w/ minimal-impact ABI. / @@ -44,7 +45,7 @@ memset: mov %rdi,%rax / @param edx is the number of bytes to set / @clob flags,xmm3 / @mode long -_memset:.leafprologue +MemSet: .leafprologue .profilable push %rbx push %rcx @@ -64,7 +65,7 @@ _memset:.leafprologue cmp %rcx,%rdx ja 1b vmovdqu %ymm3,-32(%rdi,%rdx) - vxorps %ymm3,%ymm3,%ymm3 + vpxor %ymm3,%ymm3,%ymm3 jmp .L0 .L16r: cmp $1024,%rdx jae .Lerms @@ -75,7 +76,7 @@ _memset:.leafprologue cmp %rcx,%rdx ja 1b movdqu %xmm3,-16(%rdi,%rdx) - xorps %xmm3,%xmm3 + pxor %xmm3,%xmm3 .L0: pop %rcx pop %rbx .leafepilogue @@ -103,8 +104,7 @@ _memset:.leafprologue pop %rdi pop %rax jmp .L0 - .endfn _memset,globl,hidden - .endfn memset,globl + .endfn MemSet,globl,hidden .initro 300,_init_memset memsettab.ro: diff --git a/libc/nexgen32e/nexgen32e.h b/libc/nexgen32e/nexgen32e.h index 187ae06e..10619500 100644 --- a/libc/nexgen32e/nexgen32e.h +++ b/libc/nexgen32e/nexgen32e.h @@ -8,14 +8,18 @@ void insertionsort(size_t n, int32_t[n]); void *doublebytes(size_t, void *); int64_t div10int64(int64_t) libcesque pureconst; +int64_t div100int64(int64_t) libcesque pureconst; int64_t div1000int64(int64_t) libcesque pureconst; +int64_t div10000int64(int64_t) libcesque pureconst; int64_t div1000000int64(int64_t) libcesque pureconst; int64_t div1000000000int64(int64_t) libcesque pureconst; -int64_t mod10int64(int64_t) libcesque pureconst; -int64_t mod1000int64(int64_t) libcesque pureconst; -int64_t mod1000000int64(int64_t) libcesque pureconst; -int64_t mod1000000000int64(int64_t) libcesque pureconst; +int64_t rem10int64(int64_t) libcesque pureconst; +int64_t rem100int64(int64_t) libcesque pureconst; +int64_t rem1000int64(int64_t) libcesque pureconst; +int64_t rem10000int64(int64_t) libcesque pureconst; +int64_t rem1000000int64(int64_t) libcesque pureconst; +int64_t rem1000000000int64(int64_t) libcesque pureconst; COSMOPOLITAN_C_END_ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ diff --git a/libc/nexgen32e/nontemporal.h b/libc/nexgen32e/nontemporal.h deleted file mode 100644 index 33d863e6..00000000 --- a/libc/nexgen32e/nontemporal.h +++ /dev/null @@ -1,66 +0,0 @@ -#ifndef COSMOPOLITAN_LIBC_NEXGEN32E_NONTEMPORAL_H_ -#define COSMOPOLITAN_LIBC_NEXGEN32E_NONTEMPORAL_H_ -#include "libc/bits/emmintrin.h" -#include "libc/nexgen32e/x86feature.h" -#if !(__ASSEMBLER__ + __LINKER__ + 0) -COSMOPOLITAN_C_START_ - -#define sfence() asm volatile("sfence" ::: "memory") -#define lfence() asm volatile("lfence" ::: "memory") - -/** - * Stores memory asynchronously, e.g. - * - * for (i = 0; i < n; ++i) - * nontemporal_store(m[i], x); - * } - * sfence(); - * - * @param MEM is an aligned xmm vector pointer - * @param REG is an xmm vector - * @return REG - */ -#define nontemporal_store(MEM, REG) \ - _Generic((REG), __m128i \ - : __movntdq, __m128 \ - : __movntps, __m128d \ - : __movntpd)(MEM, REG) - -/** - * Loads memory asynchronously, e.g. - * - * x1 = nontemporal_load(m16[0]); - * x2 = nontemporal_load(m16[1]); - * x3 = nontemporal_load(m16[2]); - * x4 = nontemporal_load(m16[3]); - * lfence(); - * - * @param REG is an xmm vector - * @param MEM is an aligned xmm vector pointer - * @return REG - */ -#define nontemporal_load(REG, MEM) __movntdqa(MEM) - -#define __DECLARE_MOVNT(OS, TS) \ - forceinline __m128##TS __movnt##OS(__m128##TS *mem, __m128##TS reg) { \ - asm("movnt" #OS "\t%1,%0" : "=m"(*mem) : "x"(reg)); \ - return reg; \ - } - -__DECLARE_MOVNT(ps, ) -__DECLARE_MOVNT(dq, i) -__DECLARE_MOVNT(pd, d) - -forceinline __m128i __movntdqa(const __m128i *mem) { - __m128i reg; - if (X86_HAVE(SSE4_1)) { - asm("movntdqa\t%1,%0" : "=x"(reg) : "m"(*mem)); - } else { - asm("movdqa\t%1,%0" : "=x"(reg) : "m"(*mem)); - } - return reg; -} - -COSMOPOLITAN_C_END_ -#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ -#endif /* COSMOPOLITAN_LIBC_NEXGEN32E_NONTEMPORAL_H_ */ diff --git a/libc/nexgen32e/mod1000000000int64.greg.S b/libc/nexgen32e/rem1000000000int64.S similarity index 96% rename from libc/nexgen32e/mod1000000000int64.greg.S rename to libc/nexgen32e/rem1000000000int64.S index 41f0db8e..114959c8 100644 --- a/libc/nexgen32e/mod1000000000int64.greg.S +++ b/libc/nexgen32e/rem1000000000int64.S @@ -22,8 +22,8 @@ / Returns 𝑥 % 1,000,000,000. / / @param rdi int64 𝑥 -/ @return rax -mod1000000000int64: +/ @return rax has remainder +rem1000000000int64: movabs $0x112e0be826d694b3,%rdx mov %rdi,%rax imul %rdx @@ -36,5 +36,5 @@ mod1000000000int64: sub %rax,%rdi mov %rdi,%rax ret - .endfn mod1000000000int64,globl + .endfn rem1000000000int64,globl .source __FILE__ diff --git a/libc/nexgen32e/mod1000000int64.greg.S b/libc/nexgen32e/rem1000000int64.S similarity index 94% rename from libc/nexgen32e/mod1000000int64.greg.S rename to libc/nexgen32e/rem1000000int64.S index c8d59d1b..c3530bba 100644 --- a/libc/nexgen32e/mod1000000int64.greg.S +++ b/libc/nexgen32e/rem1000000int64.S @@ -19,7 +19,11 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/macros.h" -mod1000000int64: +/ Returns 𝑥 % 1,000,000. +/ +/ @param rdi int64 𝑥 +/ @return rax has remainder +rem1000000int64: movabs $0x431bde82d7b634db,%rdx mov %rdi,%rax imul %rdx @@ -32,5 +36,5 @@ mod1000000int64: sub %rax,%rdi mov %rdi,%rax ret - .endfn mod1000000int64,globl + .endfn rem1000000int64,globl .source __FILE__ diff --git a/libc/nexgen32e/rem10000int64.S b/libc/nexgen32e/rem10000int64.S new file mode 100644 index 00000000..ad5415bf --- /dev/null +++ b/libc/nexgen32e/rem10000int64.S @@ -0,0 +1,40 @@ +/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│ +│vi: set et ft=asm ts=8 tw=8 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/macros.h" + +/ Returns 𝑥 % 10,000. +/ +/ @param rdi int64 𝑥 +/ @return rax has remainder +rem10000int64: + mov %rdi,%rax + movabsq $0x346dc5d63886594b,%rdx + imulq %rdx + mov %rdx,%rax + mov %rdi,%rdx + sar $11,%rax + sar $63,%rdx + sub %rdx,%rax + imulq $10000,%rax,%rax + sub %rax,%rdi + mov %rdi,%rax + ret + .endfn rem10000int64,globl + .source __FILE__ diff --git a/libc/nexgen32e/mod1000int64.greg.S b/libc/nexgen32e/rem1000int64.S similarity index 94% rename from libc/nexgen32e/mod1000int64.greg.S rename to libc/nexgen32e/rem1000int64.S index 8e16945e..a989c040 100644 --- a/libc/nexgen32e/mod1000int64.greg.S +++ b/libc/nexgen32e/rem1000int64.S @@ -19,7 +19,11 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/macros.h" -mod1000int64: +/ Returns 𝑥 % 1,000. +/ +/ @param rdi int64 𝑥 +/ @return rax has remainder +rem1000int64: movabs $0x20c49ba5e353f7cf,%rdx mov %rdi,%rax imul %rdx @@ -32,5 +36,5 @@ mod1000int64: sub %rax,%rdi mov %rdi,%rax ret - .endfn mod1000int64,globl + .endfn rem1000int64,globl .source __FILE__ diff --git a/libc/nexgen32e/rem100int64.S b/libc/nexgen32e/rem100int64.S new file mode 100644 index 00000000..63fa53a2 --- /dev/null +++ b/libc/nexgen32e/rem100int64.S @@ -0,0 +1,40 @@ +/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│ +│vi: set et ft=asm ts=8 tw=8 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/macros.h" + +/ Returns 𝑥 % 100. +/ +/ @param rdi int64 𝑥 +/ @return rax has remainder +rem100int64: + mov %rdi,%rax + movabsq $-6640827866535438581,%rdx + imul %rdx + lea (%rdx,%rdi),%rax + mov %rdi,%rdx + sar $6,%rax + sar $63,%rdx + sub %rdx,%rax + imul $100,%rax,%rax + sub %rax,%rdi + mov %rdi,%rax + ret + .endfn rem100int64,globl + .source __FILE__ diff --git a/libc/nexgen32e/mod10int64.greg.S b/libc/nexgen32e/rem10int64.S similarity index 95% rename from libc/nexgen32e/mod10int64.greg.S rename to libc/nexgen32e/rem10int64.S index efae4668..cd046931 100644 --- a/libc/nexgen32e/mod10int64.greg.S +++ b/libc/nexgen32e/rem10int64.S @@ -19,7 +19,11 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/macros.h" -mod10int64: +/ Returns 𝑥 % 10. +/ +/ @param rdi int64 𝑥 +/ @return rax has remainder +rem10int64: movabs $0x6666666666666667,%rdx mov %rdi,%rax imul %rdx @@ -33,5 +37,5 @@ mod10int64: sub %rax,%rdi mov %rdi,%rax ret - .endfn mod10int64,globl + .endfn rem10int64,globl .source __FILE__ diff --git a/libc/nexgen32e/slowcall.h b/libc/nexgen32e/slowcall.h new file mode 100644 index 00000000..5666f1a1 --- /dev/null +++ b/libc/nexgen32e/slowcall.h @@ -0,0 +1,24 @@ +#ifndef COSMOPOLITAN_LIBC_NEXGEN32E_SLOWCALL_H_ +#define COSMOPOLITAN_LIBC_NEXGEN32E_SLOWCALL_H_ +#if !(__ASSEMBLER__ + __LINKER__ + 0) + +#define slowcall(fn, arg1, arg2, arg3, arg4, arg5, arg6) \ + ({ \ + void *ax; \ + asm volatile("push\t%7\n\t" \ + "push\t%6\n\t" \ + "push\t%5\n\t" \ + "push\t%4\n\t" \ + "push\t%3\n\t" \ + "push\t%2\n\t" \ + "push\t%1\n\t" \ + "call\tslowcall" \ + : "=a"(ax) \ + : "g"(fn), "g"(arg1), "g"(arg2), "g"(arg3), "g"(arg4), \ + "g"(arg5), "g"(arg6) \ + : "memory"); \ + ax; \ + }) + +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_NEXGEN32E_SLOWCALL_H_ */ diff --git a/libc/nexgen32e/strcaseconv.S b/libc/nexgen32e/strcaseconv.S index 0fbc5803..0b8861b8 100644 --- a/libc/nexgen32e/strcaseconv.S +++ b/libc/nexgen32e/strcaseconv.S @@ -30,6 +30,7 @@ strtoupper: mov $'A-'a,%edx # adding this uppers mov $'a|'z<<8,%ecx # uint8_t range a..z jmp strcaseconv + .endfn strtoupper,globl / Mutates string to lowercase roman characters. / @@ -40,6 +41,7 @@ strtolower: mov $'a-'A,%edx # adding this lowers mov $'A|'Z<<8,%ecx # uint8_t range A..Z / 𝑠𝑙𝑖𝑑𝑒 + .endfn strtolower,globl / Support code for strtolower() and strtoupper(). / @@ -64,16 +66,29 @@ strcaseconv: test %al,%al # is it NUL? jz 3f cmp %cl,%al # is it in range? - jb 1b + jb 0b cmp %ch,%al - ja 1b + ja 0b add %dl,-1(%rsi) - jmp 1b + jmp 0b .Lsse4: movd %ecx,%xmm1 # XMM1 = ['A,'Z,0,0,...] movd %edx,%xmm2 # XMM2 = ['a-'A,'a-'A,...] pbroadcastb %xmm2 xor %ecx,%ecx 2: movdqa (%rsi,%rcx),%xmm3 +/ ┌─0:index of the LEAST significant, set, bit is used +/ │ regardless of corresponding input element validity +/ │ intres2 is returned in least significant bits of xmm0 +/ ├─1:index of the MOST significant, set, bit is used +/ │ regardless of corresponding input element validity +/ │ each bit of intres2 is expanded to byte/word +/ │┌─0:negation of intres1 is for all 16 (8) bits +/ │├─1:negation of intres1 is masked by reg/mem validity +/ ││┌─intres1 is negated (1’s complement) +/ │││┌─mode{equalany,ranges,equaleach,equalordered} +/ ││││ ┌─issigned +/ ││││ │┌─is16bit +/ u│││├┐││ pcmpistrm $0b01000100,%xmm3,%xmm1 # →XMM0 8-bit byte mask pand %xmm2,%xmm0 # won't mask after NUL paddb %xmm0,%xmm3 @@ -83,6 +98,4 @@ strcaseconv: 3: mov %rdi,%rax .leafepilogue .endfn strcaseconv - .endfn strtolower,globl - .endfn strtoupper,globl .source __FILE__ diff --git a/libc/nexgen32e/strcmp-avx.S b/libc/nexgen32e/strcmp-avx.S index a9d52a29..cce89165 100644 --- a/libc/nexgen32e/strcmp-avx.S +++ b/libc/nexgen32e/strcmp-avx.S @@ -43,7 +43,7 @@ strncmp$avx: cmp %rsi,%rdi je 1f mov $-16,%rcx - vxorps %xmm0,%xmm0,%xmm0 + vpxor %xmm0,%xmm0,%xmm0 vpcmpeqd %xmm1,%xmm1,%xmm1 3: add $16,%rcx 4: lea 16(%rcx),%rax diff --git a/libc/nexgen32e/strsak.S b/libc/nexgen32e/strsak.S index a37391b4..6f52553b 100644 --- a/libc/nexgen32e/strsak.S +++ b/libc/nexgen32e/strsak.S @@ -18,6 +18,7 @@ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/nexgen32e/x86feature.h" +#include "libc/nexgen32e/macros.h" #include "libc/macros.h" .source __FILE__ @@ -32,6 +33,7 @@ strchrnul: .profilable or $-1,%r9 jmp 0f + .endfn strchrnul,globl / Returns pointer to first instance of character, the BSD way. / @@ -41,6 +43,7 @@ strchrnul: / @note this won't return NULL if search character is NUL index: nop / 𝑠𝑙𝑖𝑑𝑒 + .endfn index,globl / Returns pointer to first instance of character. / @@ -56,6 +59,7 @@ strchr: .leafprologue or $-1,%rsi xor %r8,%r8 jmp strsak + .endfn strchr,globl / Returns pointer to first instance of character in range. / @@ -65,6 +69,7 @@ strchr: .leafprologue rawmemchr: or $-1,%rdx / 𝑠𝑙𝑖𝑑𝑒 + .endfn rawmemchr,globl / Returns pointer to first instance of character in range. / @@ -80,6 +85,7 @@ memchr: .leafprologue xor %r8,%r8 xor %r10,%r10 jmp strsak + .endfn memchr,globl / Returns length of NUL-terminated string w/ security blankets. / @@ -97,6 +103,7 @@ strnlen_s: test %rdi,%rdi jnz 0f .leafepilogue + .endfn strnlen_s,globl / Returns length of NUL-terminated string. / @@ -105,6 +112,7 @@ strnlen_s: / @asyncsignalsafe strlen: or $-1,%rsi / 𝑠𝑙𝑖𝑑𝑒 + .endfn strlen,globl / Returns length of NUL-terminated memory, with limit. / @@ -118,6 +126,7 @@ strnlen:.leafprologue 0: xor %edx,%edx mov %rdi,%r8 / 𝑠𝑙𝑖𝑑𝑒 + .endfn strnlen,globl / Swiss army knife of string character scanning. / Sixteen fast functions in one. @@ -175,12 +184,8 @@ strsak: lea -1(%rdi),%rax 2: add %rcx,%rax jmp .Lbyte #if !X86_NEED(AVX2) -.Lsse2: punpcklbw %xmm0,%xmm0 - pshuflw $0xe0,%xmm0,%xmm0 - pshufd $0x00,%xmm0,%xmm0 - punpcklbw %xmm1,%xmm1 - pshuflw $0xe0,%xmm1,%xmm1 - pshufd $0x00,%xmm1,%xmm1 +.Lsse2: pbroadcastb %xmm0 + pbroadcastb %xmm1 1: add $32,%rax sub $32,%rsi jb 9b @@ -203,14 +208,6 @@ strsak: lea -1(%rdi),%rax jmp 2b #endif .endfn strsak,globl,hidden - .endfn strnlen,globl - .endfn strlen,globl - .endfn strnlen_s,globl - .endfn memchr,globl - .endfn rawmemchr,globl - .endfn strchr,globl - .endfn index,globl - .endfn strchrnul,globl /* benchmarked on intel core i7-6700 @ 3.40GHz (skylake) includes function call overhead (unless marked otherwise) diff --git a/libc/nexgen32e/tinydivsi.greg.S b/libc/nexgen32e/tinydivsi.greg.S index 4dae771a..81dda78f 100644 --- a/libc/nexgen32e/tinydivsi.greg.S +++ b/libc/nexgen32e/tinydivsi.greg.S @@ -21,17 +21,22 @@ / Support code for fast integer division by Si units. / +/ Division by magnums is described in Hacker's Delight and is +/ usually generated automatically by compilers, but sadly not +/ when we optimize for size and idiv goes at least 10x slower +/ so we do this which saves space while avoiding build tuning +/ / @param rdi is number to divide / @param cl is magnum #1 / @param rdx is magnum #2 -/ @return truncated numerator +/ @return quotient tinydivsi: .leafprologue mov %rdi,%rax imul %rdx mov %rdx,%rax sar %cl,%rax - sar $0x3f,%rdi + sar $63,%rdi sub %rdi,%rax .leafepilogue .endfn tinydivsi,globl diff --git a/libc/nexgen32e/tinystrcmp.S b/libc/nexgen32e/tinystrcmp.S index 1323bd97..63a96093 100644 --- a/libc/nexgen32e/tinystrcmp.S +++ b/libc/nexgen32e/tinystrcmp.S @@ -19,7 +19,7 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/macros.h" -/ Compares NUL-terminated strings w/o heavy-lifting. +/ Compares strings w/ no-clobber greg abi. / / @param rdi is first non-null NUL-terminated string pointer / @param rsi is second non-null NUL-terminated string pointer @@ -30,7 +30,11 @@ tinystrcmp: .leafprologue push %rcx push %rdx + xor %eax,%eax + xor %edx,%edx xor %ecx,%ecx + cmp %rdi,%rsi + je 1f 0: movzbl (%rdi,%rcx,1),%eax movzbl (%rsi,%rcx,1),%edx test %al,%al diff --git a/libc/nexgen32e/tinystrlen.h b/libc/nexgen32e/tinystrlen.h index b2034ea7..591ddafd 100644 --- a/libc/nexgen32e/tinystrlen.h +++ b/libc/nexgen32e/tinystrlen.h @@ -1,6 +1,16 @@ #ifndef COSMOPOLITAN_LIBC_NEXGEN32E_TINYSTRLEN_H_ #define COSMOPOLITAN_LIBC_NEXGEN32E_TINYSTRLEN_H_ #if !(__ASSEMBLER__ + __LINKER__ + 0) +#if !defined(__GNUC__) || defined(__STRICT_ANSI__) + +int tinystrlen(const char *); +int tinystrnlen(const char *, size_t); +int tinystrlen16(const char16_t *); +int tinystrnlen16(const char16_t *, size_t); +int tinywcslen(const wchar_t *); +int tinywcsnlen(const wchar_t *, size_t); + +#else forceinline int tinystrlen(const char *s) { unsigned ax; @@ -40,5 +50,6 @@ forceinline int tinywcsnlen(const wchar_t *s, size_t n) { return ax; } +#endif #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ #endif /* COSMOPOLITAN_LIBC_NEXGEN32E_TINYSTRLEN_H_ */ diff --git a/libc/nexgen32e/tinystrncmp.ncabi.S b/libc/nexgen32e/tinystrncmp.ncabi.S new file mode 100644 index 00000000..546ad74c --- /dev/null +++ b/libc/nexgen32e/tinystrncmp.ncabi.S @@ -0,0 +1,56 @@ +/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│ +│vi: set et ft=asm ts=8 tw=8 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/macros.h" + +/ Compares strings w/ limit & no-clobber greg abi. +/ +/ @param %rdi is first string +/ @param %rsi is second string +/ @param %rdx is max length +/ @return <0, 0, or >0 depending on comparison +/ @clob flags only +/ @asyncsignalsafe +tinystrncmp: + .leafprologue + push %rbx + push %rcx + xor %eax,%eax + xor %ebx,%ebx + xor %ecx,%ecx + test %edx,%edx + jz 2f + cmp %rdi,%rsi + je 2f +0: cmp %edx,%ecx + jae 1f + movzbl (%rdi,%rcx,1),%eax + movzbl (%rsi,%rcx,1),%ebx + test %al,%al + jz 1f + cmp %bl,%al + jne 1f + inc %ecx + jmp 0b +1: sub %ebx,%eax +2: pop %rcx + pop %rbx + .leafepilogue + .endfn tinystrncmp,globl + .source __FILE__ diff --git a/libc/nexgen32e/vendor.h b/libc/nexgen32e/vendor.h index fe475cbe..6b389ab2 100644 --- a/libc/nexgen32e/vendor.h +++ b/libc/nexgen32e/vendor.h @@ -9,6 +9,7 @@ * ╤ ╤ * GenuineIntel * AuthenticAMD + * GenuineCosmo * NexGenDriven * AMDisbetter! * CentaurHauls @@ -27,6 +28,7 @@ * └────┐ │ * G ⊕ t = 0x33 Intel * A ⊕ A = 0x00 AMD + * G ⊕ s = 0x34 Cosmopolitan * N ⊕ v = 0x38 NexGen (Modern x86) * A ⊕ e = 0x24 AMD (Rank & File) * C ⊕ u = 0x36 Via (DBA Centaur) @@ -53,10 +55,11 @@ */ #define IsAuthenticAMD() (_KCPUIDS_VENDOR() == 0x00) #define IsGenuineIntel() (_KCPUIDS_VENDOR() == 0x33) +#define IsGenuineCosmo() (_KCPUIDS_VENDOR() == 0x34) -#define _KCPUIDS_VENDOR() \ - (((kCpuids[KCPUIDS_0][KCPUIDS_EBX] >> 000) & 0xff) ^ \ - ((kCpuids[KCPUIDS_0][KCPUIDS_ECX] >> 010) & 0xff)) +#define _KCPUIDS_VENDOR() \ + (((kCpuids[KCPUIDS_0H][KCPUIDS_EBX] >> 000) & 0xff) ^ \ + ((kCpuids[KCPUIDS_0H][KCPUIDS_EDX] >> 010) & 0xff)) #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ #endif /* COSMOPOLITAN_LIBC_NEXGEN32E_VENDOR_H_ */ diff --git a/libc/nexgen32e/x86feature.h b/libc/nexgen32e/x86feature.h index 8a964cea..f3ba0d5a 100644 --- a/libc/nexgen32e/x86feature.h +++ b/libc/nexgen32e/x86feature.h @@ -72,6 +72,7 @@ #define X86_INVPCID 1H, EBX, 10, 0, _ #define X86_INVTSC 80000007H, EDX, 8, _X86_CC_POPCNT, _ /* i.e. not a K8 */ #define X86_LA57 7H, ECX, 16, 0, _ +#define X86_LAHF_LM 80000001H, ECX, 0, 0, _ #define X86_LM 80000001H, EDX, 29, 0, _ #define X86_MCA 1H, EDX, 14, 0, _ #define X86_MCE 1H, EDX, 7, 0, _ @@ -153,7 +154,6 @@ #define X86_FMA4 80000001H, ECX, 16, 0, _ #define X86_FXSR_OPT 80000001H, EDX, 25, 0, _ #define X86_IBS 80000001H, ECX, 10, 0, _ -#define X86_LAHF_LM 80000001H, ECX, 0, 0, _ #define X86_LWP 80000001H, ECX, 15, 0, _ #define X86_MISALIGNSSE 80000001H, ECX, 7, 0, _ #define X86_MMXEXT 80000001H, EDX, 22, 0, _ diff --git a/libc/nt/accounting.h b/libc/nt/accounting.h index 8a4de280..fba01621 100644 --- a/libc/nt/accounting.h +++ b/libc/nt/accounting.h @@ -1,5 +1,6 @@ #ifndef COSMOPOLITAN_LIBC_NT_ACCOUNTING_H_ #define COSMOPOLITAN_LIBC_NT_ACCOUNTING_H_ +#include "libc/nt/thunk/msabi.h" #if !(__ASSEMBLER__ + __LINKER__ + 0) COSMOPOLITAN_C_START_ #if 0 @@ -64,6 +65,9 @@ int32_t SetProcessWorkingSetSizeEx(int64_t hProcess, uint64_t dwMaximumWorkingSetSize, uint32_t Flags); +#if ShouldUseMsabiAttribute() +#include "libc/nt/thunk/accounting.inc" +#endif /* ShouldUseMsabiAttribute() */ COSMOPOLITAN_C_END_ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ #endif /* COSMOPOLITAN_LIBC_NT_ACCOUNTING_H_ */ diff --git a/libc/nt/files.h b/libc/nt/files.h index 41013381..7b473963 100644 --- a/libc/nt/files.h +++ b/libc/nt/files.h @@ -148,9 +148,9 @@ bool32 GetFileAttributesEx( uint32_t GetCompressedFileSize(const char16_t *lpFileName, uint32_t *lpFileSizeHigh); bool32 SetFileAttributes(const char16_t *lpFileName, uint32_t dwFileAttributes); -bool32 GetFileTime(int64_t hFile, struct NtFileTime *lpCreationFileTime, - struct NtFileTime *lpLastAccessFileTime, - struct NtFileTime *lpLastWriteFileTime); +bool32 GetFileTime(int64_t hFile, struct NtFileTime *opt_lpCreationFileTime, + struct NtFileTime *opt_lpLastAccessFileTime, + struct NtFileTime *opt_lpLastWriteFileTime); bool32 SetFileTime(int64_t hFile, const struct NtFileTime *opt_lpCreationFileTime, const struct NtFileTime *opt_lpLastAccessFileTime, diff --git a/libc/nt/master.sh b/libc/nt/master.sh index 509f54f1..6443db5f 100755 --- a/libc/nt/master.sh +++ b/libc/nt/master.sh @@ -2162,8 +2162,8 @@ imp 'GetFileMUIPath' GetFileMUIPath KernelBase 550 imp 'GetFileNameFromBrowse' GetFileNameFromBrowse shell32 63 imp 'GetFileSecurity' GetFileSecurityW KernelBase 551 5 imp 'GetFileSecurityA' GetFileSecurityA advapi32 1326 5 -imp 'GetFileSize$nopenopenope' GetFileSize KernelBase 552 -imp 'GetFileSizeEx$nopenopenope' GetFileSizeEx KernelBase 553 +imp 'GetFileSize' GetFileSize KernelBase 552 m +imp 'GetFileSizeEx' GetFileSizeEx KernelBase 553 m imp 'GetFileTime' GetFileTime KernelBase 554 4 imp 'GetFileTitleA' GetFileTitleA comdlg32 111 imp 'GetFileTitle' GetFileTitleW comdlg32 112 diff --git a/libc/nt/memory.h b/libc/nt/memory.h index af80b372..8a91fab7 100644 --- a/libc/nt/memory.h +++ b/libc/nt/memory.h @@ -4,6 +4,7 @@ #include "libc/nt/enum/memflags.h" #include "libc/nt/enum/offerpriority.h" #include "libc/nt/enum/pageflags.h" +#include "libc/nt/thunk/msabi.h" #if 0 /* ░░░░ ▒▒▒░░░▒▒▒▒▒▒▒▓▓▓░ @@ -72,6 +73,9 @@ bool32 PrefetchVirtualMemory(int64_t hProcess, const uint32_t *NumberOfEntries, bool32 OfferVirtualMemory(void *inout_VirtualAddress, size_t Size, enum NtOfferPriority Priority); +#if ShouldUseMsabiAttribute() +#include "libc/nt/thunk/memory.inc" +#endif /* ShouldUseMsabiAttribute() */ COSMOPOLITAN_C_END_ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ #endif /* COSMOPOLITAN_LIBC_NT_MEMORY_H_ */ diff --git a/libc/nt/nt.mk b/libc/nt/nt.mk index c4478934..2035ee2e 100644 --- a/libc/nt/nt.mk +++ b/libc/nt/nt.mk @@ -35,7 +35,7 @@ LIBC_NT_KERNEL32_A = o/$(MODE)/libc/nt/kernel32.a LIBC_NT_KERNEL32_A_SRCS := $(wildcard libc/nt/kernel32/*.s) LIBC_NT_KERNEL32_A_OBJS = $(LIBC_NT_KERNEL32_A_SRCS:%.s=o/$(MODE)/%.o) LIBC_NT_KERNEL32_A_CHECKS = $(LIBC_NT_KERNEL32_A).pkg -LIBC_NT_KERNEL32_A_DIRECTDEPS = LIBC_STUBS LIBC_NEXGEN32E +LIBC_NT_KERNEL32_A_DIRECTDEPS = LIBC_STUBS LIBC_NT_KERNEL32_A_DEPS := \ $(call uniq,$(foreach x,$(LIBC_NT_KERNEL32_A_DIRECTDEPS),$($(x)))) @@ -56,7 +56,7 @@ LIBC_NT_ADVAPI32_A = o/$(MODE)/libc/nt/advapi32.a LIBC_NT_ADVAPI32_A_SRCS := $(wildcard libc/nt/advapi32/*.s) LIBC_NT_ADVAPI32_A_OBJS = $(LIBC_NT_ADVAPI32_A_SRCS:%.s=o/$(MODE)/%.o) LIBC_NT_ADVAPI32_A_CHECKS = $(LIBC_NT_ADVAPI32_A).pkg -LIBC_NT_ADVAPI32_A_DIRECTDEPS = LIBC_STUBS LIBC_NEXGEN32E +LIBC_NT_ADVAPI32_A_DIRECTDEPS = LIBC_STUBS LIBC_NT_ADVAPI32_A_DEPS := \ $(call uniq,$(foreach x,$(LIBC_NT_ADVAPI32_A_DIRECTDEPS),$($(x)))) @@ -77,7 +77,7 @@ LIBC_NT_COMDLG32_A = o/$(MODE)/libc/nt/comdlg32.a LIBC_NT_COMDLG32_A_SRCS := $(wildcard libc/nt/comdlg32/*.s) LIBC_NT_COMDLG32_A_OBJS = $(LIBC_NT_COMDLG32_A_SRCS:%.s=o/$(MODE)/%.o) LIBC_NT_COMDLG32_A_CHECKS = $(LIBC_NT_COMDLG32_A).pkg -LIBC_NT_COMDLG32_A_DIRECTDEPS = LIBC_STUBS LIBC_NEXGEN32E +LIBC_NT_COMDLG32_A_DIRECTDEPS = LIBC_STUBS LIBC_NT_COMDLG32_A_DEPS := \ $(call uniq,$(foreach x,$(LIBC_NT_COMDLG32_A_DIRECTDEPS),$($(x)))) @@ -98,7 +98,7 @@ LIBC_NT_GDI32_A = o/$(MODE)/libc/nt/gdi32.a LIBC_NT_GDI32_A_SRCS := $(wildcard libc/nt/gdi32/*.s) LIBC_NT_GDI32_A_OBJS = $(LIBC_NT_GDI32_A_SRCS:%.s=o/$(MODE)/%.o) LIBC_NT_GDI32_A_CHECKS = $(LIBC_NT_GDI32_A).pkg -LIBC_NT_GDI32_A_DIRECTDEPS = LIBC_STUBS LIBC_NEXGEN32E +LIBC_NT_GDI32_A_DIRECTDEPS = LIBC_STUBS LIBC_NT_GDI32_A_DEPS := \ $(call uniq,$(foreach x,$(LIBC_NT_GDI32_A_DIRECTDEPS),$($(x)))) @@ -119,7 +119,7 @@ LIBC_NT_KERNELBASE_A = o/$(MODE)/libc/nt/KernelBase.a LIBC_NT_KERNELBASE_A_SRCS := $(wildcard libc/nt/KernelBase/*.s) LIBC_NT_KERNELBASE_A_OBJS = $(LIBC_NT_KERNELBASE_A_SRCS:%.s=o/$(MODE)/%.o) LIBC_NT_KERNELBASE_A_CHECKS = $(LIBC_NT_KERNELBASE_A).pkg -LIBC_NT_KERNELBASE_A_DIRECTDEPS = LIBC_STUBS LIBC_NEXGEN32E +LIBC_NT_KERNELBASE_A_DIRECTDEPS = LIBC_STUBS LIBC_NT_KERNELBASE_A_DEPS := \ $(call uniq,$(foreach x,$(LIBC_NT_KERNELBASE_A_DIRECTDEPS),$($(x)))) @@ -142,10 +142,9 @@ LIBC_NT_NTDLL_A_SRCS_S = libc/nt/ntdllimport.S LIBC_NT_NTDLL_A_SRCS = $(LIBC_NT_NTDLL_A_SRCS_A) $(LIBC_NT_NTDLL_A_SRCS_S) LIBC_NT_NTDLL_A_OBJS = \ $(LIBC_NT_NTDLL_A_SRCS_A:%.s=o/$(MODE)/%.o) \ - $(LIBC_NT_NTDLL_A_SRCS_S:%.S=o/$(MODE)/%.o) \ - $(LIBC_NT_NTDLL_A_SRCS:%=o/$(MODE)/%.zip.o) + $(LIBC_NT_NTDLL_A_SRCS_S:%.S=o/$(MODE)/%.o) LIBC_NT_NTDLL_A_CHECKS = $(LIBC_NT_NTDLL_A).pkg -LIBC_NT_NTDLL_A_DIRECTDEPS = LIBC_STUBS LIBC_NEXGEN32E LIBC_NT_KERNELBASE +LIBC_NT_NTDLL_A_DIRECTDEPS = LIBC_STUBS LIBC_NT_KERNELBASE LIBC_NT_NTDLL_A_DEPS := \ $(call uniq,$(foreach x,$(LIBC_NT_NTDLL_A_DIRECTDEPS),$($(x)))) @@ -176,7 +175,7 @@ LIBC_NT_NETAPI32_A = o/$(MODE)/libc/nt/netapi32.a LIBC_NT_NETAPI32_A_SRCS := $(wildcard libc/nt/netapi32/*.s) LIBC_NT_NETAPI32_A_OBJS = $(LIBC_NT_NETAPI32_A_SRCS:%.s=o/$(MODE)/%.o) LIBC_NT_NETAPI32_A_CHECKS = $(LIBC_NT_NETAPI32_A).pkg -LIBC_NT_NETAPI32_A_DIRECTDEPS = LIBC_STUBS LIBC_NEXGEN32E +LIBC_NT_NETAPI32_A_DIRECTDEPS = LIBC_STUBS LIBC_NT_NETAPI32_A_DEPS := \ $(call uniq,$(foreach x,$(LIBC_NT_NETAPI32_A_DIRECTDEPS),$($(x)))) @@ -197,7 +196,7 @@ LIBC_NT_URL_A = o/$(MODE)/libc/nt/url.a LIBC_NT_URL_A_SRCS := $(wildcard libc/nt/url/*.s) LIBC_NT_URL_A_OBJS = $(LIBC_NT_URL_A_SRCS:%.s=o/$(MODE)/%.o) LIBC_NT_URL_A_CHECKS = $(LIBC_NT_URL_A).pkg -LIBC_NT_URL_A_DIRECTDEPS = LIBC_STUBS LIBC_NEXGEN32E +LIBC_NT_URL_A_DIRECTDEPS = LIBC_STUBS LIBC_NT_URL_A_DEPS := \ $(call uniq,$(foreach x,$(LIBC_NT_URL_A_DIRECTDEPS),$($(x)))) @@ -218,7 +217,7 @@ LIBC_NT_USER32_A = o/$(MODE)/libc/nt/user32.a LIBC_NT_USER32_A_SRCS := $(wildcard libc/nt/user32/*.s) LIBC_NT_USER32_A_OBJS = $(LIBC_NT_USER32_A_SRCS:%.s=o/$(MODE)/%.o) LIBC_NT_USER32_A_CHECKS = $(LIBC_NT_USER32_A).pkg -LIBC_NT_USER32_A_DIRECTDEPS = LIBC_STUBS LIBC_NEXGEN32E +LIBC_NT_USER32_A_DIRECTDEPS = LIBC_STUBS LIBC_NT_USER32_A_DEPS := \ $(call uniq,$(foreach x,$(LIBC_NT_USER32_A_DIRECTDEPS),$($(x)))) @@ -239,7 +238,7 @@ LIBC_NT_WS2_32_A = o/$(MODE)/libc/nt/ws2_32.a LIBC_NT_WS2_32_A_SRCS := $(wildcard libc/nt/ws2_32/*.s) LIBC_NT_WS2_32_A_OBJS = $(LIBC_NT_WS2_32_A_SRCS:%.s=o/$(MODE)/%.o) LIBC_NT_WS2_32_A_CHECKS = $(LIBC_NT_WS2_32_A).pkg -LIBC_NT_WS2_32_A_DIRECTDEPS = LIBC_STUBS LIBC_NEXGEN32E +LIBC_NT_WS2_32_A_DIRECTDEPS = LIBC_STUBS LIBC_NT_WS2_32_A_DEPS := \ $(call uniq,$(foreach x,$(LIBC_NT_WS2_32_A_DIRECTDEPS),$($(x)))) @@ -260,7 +259,7 @@ LIBC_NT_MSWSOCK_A = o/$(MODE)/libc/nt/MsWSock.a LIBC_NT_MSWSOCK_A_SRCS := $(wildcard libc/nt/MsWSock/*.s) LIBC_NT_MSWSOCK_A_OBJS = $(LIBC_NT_MSWSOCK_A_SRCS:%.s=o/$(MODE)/%.o) LIBC_NT_MSWSOCK_A_CHECKS = $(LIBC_NT_MSWSOCK_A).pkg -LIBC_NT_MSWSOCK_A_DIRECTDEPS = LIBC_STUBS LIBC_NEXGEN32E +LIBC_NT_MSWSOCK_A_DIRECTDEPS = LIBC_STUBS LIBC_NT_MSWSOCK_A_DEPS := \ $(call uniq,$(foreach x,$(LIBC_NT_MSWSOCK_A_DIRECTDEPS),$($(x)))) @@ -281,7 +280,7 @@ LIBC_NT_SHELL32_A = o/$(MODE)/libc/nt/shell32.a LIBC_NT_SHELL32_A_SRCS := $(wildcard libc/nt/shell32/*.s) LIBC_NT_SHELL32_A_OBJS = $(LIBC_NT_SHELL32_A_SRCS:%.s=o/$(MODE)/%.o) LIBC_NT_SHELL32_A_CHECKS = $(LIBC_NT_SHELL32_A).pkg -LIBC_NT_SHELL32_A_DIRECTDEPS = LIBC_STUBS LIBC_NEXGEN32E +LIBC_NT_SHELL32_A_DIRECTDEPS = LIBC_STUBS LIBC_NT_SHELL32_A_DEPS := \ $(call uniq,$(foreach x,$(LIBC_NT_SHELL32_A_DIRECTDEPS),$($(x)))) $(LIBC_NT_SHELL32_A): \ @@ -294,11 +293,7 @@ $(LIBC_NT_SHELL32_A).pkg: \ #─────────────────────────────────────────────────────────────────────────────── -$(LIBC_NT_OBJS): libc/nt/nt.mk o/libc/nt/codegen.inc - -.PHONY: o/libc/nt -o/libc/nt: $(LIBC_NT_LIBS) \ - $(LIBC_NT_CHECKS) +$(LIBC_NT_OBJS): o/libc/nt/codegen.inc .PHONY: o/$(MODE)/libc/nt o/$(MODE)/libc/nt: \ diff --git a/libc/nt/nt/systemthreads.h b/libc/nt/nt/systemthreads.h deleted file mode 100644 index 1a3124b7..00000000 --- a/libc/nt/nt/systemthreads.h +++ /dev/null @@ -1,23 +0,0 @@ -#ifndef COSMOPOLITAN_LIBC_NT_NT_SYSTEMTHREADS_H_ -#define COSMOPOLITAN_LIBC_NT_NT_SYSTEMTHREADS_H_ -#include "libc/nt/enum/kwaitreason.h" -#include "libc/nt/enum/threadstate.h" -#include "libc/nt/struct/clientid.h" -#if !(__ASSEMBLER__ + __LINKER__ + 0) - -struct NtSystemThreads { - int64_t KernelTime; - int64_t UserTime; - int64_t CreateTime; - uint32_t WaitTime; - void *StartAddress; - struct NtClientId ClientId; - int32_t Priority; - int32_t BasePriority; - uint32_t ContextSwitchCount; - enum NtThreadState State; - uint32_t WaitReason; -}; - -#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ -#endif /* COSMOPOLITAN_LIBC_NT_NT_SYSTEMTHREADS_H_ */ diff --git a/libc/nt/ntdllimport.S b/libc/nt/ntdllimport.S index d64e2741..bb47fed1 100644 --- a/libc/nt/ntdllimport.S +++ b/libc/nt/ntdllimport.S @@ -19,7 +19,6 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/nt/enum/status.h" #include "libc/macros.h" -.source __FILE__ / @fileoverview NTDLL.DLL Non-Mandatory Importer / diff --git a/libc/nt/process.h b/libc/nt/process.h index f70718ca..ad275989 100644 --- a/libc/nt/process.h +++ b/libc/nt/process.h @@ -76,7 +76,7 @@ bool32 SetProcessPriorityBoost(int64_t hProcess, bool32 bDisablePriorityBoost); bool32 GetProcessPriorityBoost(int64_t hProcess, bool32 *pDisablePriorityBoost); #if ShouldUseMsabiAttribute() -#include "libc/nt/thunk/msabi.h" +#include "libc/nt/thunk/process.inc" #endif /* ShouldUseMsabiAttribute() */ COSMOPOLITAN_C_END_ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ diff --git a/libc/nt/signals.h b/libc/nt/signals.h index d391f190..2d047072 100644 --- a/libc/nt/signals.h +++ b/libc/nt/signals.h @@ -30,41 +30,38 @@ ╚────────────────────────────────────────────────────────────────────────────│*/ #endif -#define kNtSignalBreakpoint 0x80000003u -#define kNtSignalIllegalInstruction 0xC000001Du -#define kNtSignalPrivInstruction 0xC0000096u -#define kNtSignalGuardPage 0x80000001u -#define kNtSignalAccessViolation 0xC0000005u -#define kNtSignalInPageError 0xC0000006u -#define kNtSignalInvalidHandle 0xC0000008u -#define kNtSignalInvalidParameter 0xC000000du -#define kNtSignalFltDenormalOperand 0xC000008Du -#define kNtSignalFltDivideByZero 0xC000008Eu -#define kNtSignalFltInexactResult 0xC000008Fu +#define kNtSignalBreakpoint 0x80000003u +#define kNtSignalIllegalInstruction 0xC000001Du +#define kNtSignalPrivInstruction 0xC0000096u +#define kNtSignalGuardPage 0x80000001u +#define kNtSignalAccessViolation 0xC0000005u +#define kNtSignalInPageError 0xC0000006u +#define kNtSignalInvalidHandle 0xC0000008u +#define kNtSignalInvalidParameter 0xC000000du +#define kNtSignalFltDenormalOperand 0xC000008Du +#define kNtSignalFltDivideByZero 0xC000008Eu +#define kNtSignalFltInexactResult 0xC000008Fu #define kNtSignalFltInvalidOperation 0xC0000090u -#define kNtSignalFltOverflow 0xC0000091u -#define kNtSignalFltStackCheck 0xC0000092u -#define kNtSignalFltUnderflow 0xC0000093u +#define kNtSignalFltOverflow 0xC0000091u +#define kNtSignalFltStackCheck 0xC0000092u +#define kNtSignalFltUnderflow 0xC0000093u #define kNtSignalIntegerDivideByZero 0xC0000094u -#define kNtSignalDllNotFound 0xC0000135u -#define kNtSignalOrdinalNotFound 0xC0000138u -#define kNtSignalEntrypointNotFound 0xC0000139u -#define kNtSignalControlCExit 0xC000013Au -#define kNtSignalDllInitFailed 0xC0000142u +#define kNtSignalDllNotFound 0xC0000135u +#define kNtSignalOrdinalNotFound 0xC0000138u +#define kNtSignalEntrypointNotFound 0xC0000139u +#define kNtSignalControlCExit 0xC000013Au +#define kNtSignalDllInitFailed 0xC0000142u #define kNtSignalFloatMultipleFaults 0xC00002B4u -#define kNtSignalFloatMultipleTraps 0xC00002B5u -#define kNtSignalAssertionFailure 0xC0000420u +#define kNtSignalFloatMultipleTraps 0xC00002B5u +#define kNtSignalAssertionFailure 0xC0000420u #if !(__ASSEMBLER__ + __LINKER__ + 0) COSMOPOLITAN_C_START_ struct NtExceptionPointers; -typedef enum NtExceptionHandlerActions (*NtTopLevelExceptionFilter)( - const struct NtExceptionPointers *ExceptionInfo); - -typedef int32_t (*NtVectoredExceptionHandler)( - struct NtExceptionPointers *ExceptionInfo); +typedef int (*NtTopLevelExceptionFilter)(const struct NtExceptionPointers *); +typedef int32_t (*NtVectoredExceptionHandler)(struct NtExceptionPointers *); enum NtErrorModeFlags SetErrorMode(enum NtErrorModeFlags uMode); diff --git a/libc/nt/struct/teb.h b/libc/nt/struct/teb.h index be7f437b..558a2fa2 100644 --- a/libc/nt/struct/teb.h +++ b/libc/nt/struct/teb.h @@ -1,6 +1,6 @@ #ifndef COSMOPOLITAN_LIBC_NT_TEB_H_ #define COSMOPOLITAN_LIBC_NT_TEB_H_ -#include "libc/bits/bits.h" +#include "libc/bits/segmentation.h" #include "libc/nt/struct/peb.h" #if !(__ASSEMBLER__ + __LINKER__ + 0) @@ -14,14 +14,14 @@ #define NtGetErr() gs((int *)(0x68)) #define NtGetVersion() \ ((NtGetPeb()->OSMajorVersion & 0xff) << 8 | NtGetPeb()->OSMinorVersion) -#define _NtGetSeh() gs((void **)(0x00)) -#define _NtGetStackHigh() gs((void **)(0x08)) -#define _NtGetStackLow() gs((void **)(0x10)) +#define _NtGetSeh() gs((void **)(0x00)) +#define _NtGetStackHigh() gs((void **)(0x08)) +#define _NtGetStackLow() gs((void **)(0x10)) #define _NtGetSubsystemTib() gs((void **)(0x18)) -#define _NtGetFib() gs((void **)(0x20)) -#define _NtGetEnv() gs((char16_t **)(0x38)) -#define _NtGetRpc() gs((void **)(0x50)) -#define _NtGetTls() gs((void **)(0x58)) +#define _NtGetFib() gs((void **)(0x20)) +#define _NtGetEnv() gs((char16_t **)(0x38)) +#define _NtGetRpc() gs((void **)(0x50)) +#define _NtGetTls() gs((void **)(0x58)) #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ #endif /* COSMOPOLITAN_LIBC_NT_TEB_H_ */ diff --git a/libc/nt/thunk/accounting.inc b/libc/nt/thunk/accounting.inc new file mode 100644 index 00000000..860f1f14 --- /dev/null +++ b/libc/nt/thunk/accounting.inc @@ -0,0 +1,11 @@ +#define GetProcessTimes(...) __imp_GetProcessTimes(__VA_ARGS__) +extern typeof(GetProcessTimes) *const __imp_GetProcessTimes __msabi; + +#define GetThreadTimes(...) __imp_GetThreadTimes(__VA_ARGS__) +extern typeof(GetThreadTimes) *const __imp_GetThreadTimes __msabi; + +#define GetUserName(...) __imp_GetUserNameW(__VA_ARGS__) +extern typeof(GetUserName) *const __imp_GetUserNameW __msabi; + +#define GetExitCodeProcess(...) __imp_GetExitCodeProcess(__VA_ARGS__) +extern typeof(GetExitCodeProcess) *const __imp_GetExitCodeProcess __msabi; diff --git a/libc/nt/thunk/files.inc b/libc/nt/thunk/files.inc index 3afabee5..b2ac7e49 100644 --- a/libc/nt/thunk/files.inc +++ b/libc/nt/thunk/files.inc @@ -1,3 +1,5 @@ +#define CopyFile(...) __imp_CopyFileW(__VA_ARGS__) #define FlushFileBuffers(...) __imp_FlushFileBuffers(__VA_ARGS__) +extern typeof(CopyFile) *const __imp_CopyFileW __msabi; extern typeof(FlushFileBuffers) *const __imp_FlushFileBuffers __msabi; diff --git a/libc/nt/thunk/memory.inc b/libc/nt/thunk/memory.inc new file mode 100644 index 00000000..fe36ff37 --- /dev/null +++ b/libc/nt/thunk/memory.inc @@ -0,0 +1,10 @@ +#define CreateFileMappingNuma(...) __imp_CreateFileMappingNumaW(__VA_ARGS__) +#define MapViewOfFileExNuma(...) __imp_MapViewOfFileExNuma(__VA_ARGS__) +#define FlushViewOfFile(...) __imp_FlushViewOfFile(__VA_ARGS__) +#define UnmapViewOfFile(...) __imp_UnmapViewOfFile(__VA_ARGS__) + +extern typeof(UnmapViewOfFile) *const __imp_UnmapViewOfFile __msabi; +extern typeof(FlushViewOfFile) *const __imp_FlushViewOfFile __msabi; +extern typeof(MapViewOfFileExNuma) *const __imp_MapViewOfFileExNuma __msabi; +extern typeof(CreateFileMappingNuma) *const + __imp_CreateFileMappingNumaW __msabi; diff --git a/libc/nt/thunk/process.inc b/libc/nt/thunk/process.inc index 5fb34189..e483b043 100644 --- a/libc/nt/thunk/process.inc +++ b/libc/nt/thunk/process.inc @@ -1,8 +1,19 @@ #define GetEnvironmentVariable(...) __imp_GetEnvironmentVariableW(__VA_ARGS__) -#define GetPriorityClass(...) __imp_GetPriorityClass(__VA_ARGS__) -#define SetPriorityClass(...) __imp_SetPriorityClass(__VA_ARGS__) - extern typeof(GetEnvironmentVariable) *const __imp_GetEnvironmentVariableW __msabi; + +#define SetEnvironmentVariable(...) __imp_SetEnvironmentVariableW(__VA_ARGS__) +extern typeof(SetEnvironmentVariable) *const + __imp_SetEnvironmentVariableW __msabi; + +#define GetPriorityClass(...) __imp_GetPriorityClass(__VA_ARGS__) extern typeof(GetPriorityClass) *const __imp_GetPriorityClass __msabi; + +#define SetPriorityClass(...) __imp_SetPriorityClass(__VA_ARGS__) extern typeof(SetPriorityClass) *const __imp_SetPriorityClass __msabi; + +#define GetCurrentProcessId(...) __imp_GetCurrentProcessId(__VA_ARGS__) +extern typeof(GetCurrentProcessId) *const __imp_GetCurrentProcessId __msabi; + +#define CreateProcess(...) __imp_CreateProcessW(__VA_ARGS__) +extern typeof(CreateProcess) *const __imp_CreateProcessW __msabi; diff --git a/libc/nt/thunk/runtime.inc b/libc/nt/thunk/runtime.inc index ea5a3faa..4d220489 100644 --- a/libc/nt/thunk/runtime.inc +++ b/libc/nt/thunk/runtime.inc @@ -1,23 +1,33 @@ -#define ExitProcess(...) __imp_ExitProcess(__VA_ARGS__) -#define FreeEnvironmentStrings(...) __imp_FreeEnvironmentStringsW(__VA_ARGS__) -#define GetCommandLine(...) __imp_GetCommandLineW(__VA_ARGS__) -#define GetEnvironmentStrings(...) __imp_GetEnvironmentStringsW(__VA_ARGS__) -#define GetStdHandle(...) __imp_GetStdHandle(__VA_ARGS__) -#define SetStdHandle(...) __imp_SetStdHandle(__VA_ARGS__) -#define ReadFile(...) __imp_ReadFile(__VA_ARGS__) -#define WriteFile(...) __imp_WriteFile(__VA_ARGS__) -#define SetDefaultDllDirectories(...) \ - __imp_SetDefaultDllDirectories(__VA_ARGS__) - +#define ExitProcess(...) __imp_ExitProcess(__VA_ARGS__) extern typeof(ExitProcess) *const __imp_ExitProcess __msabi; + +#define FreeEnvironmentStrings(...) __imp_FreeEnvironmentStringsW(__VA_ARGS__) extern typeof(FreeEnvironmentStrings) *const __imp_FreeEnvironmentStringsW __msabi; + +#define GetCommandLine(...) __imp_GetCommandLineW(__VA_ARGS__) extern typeof(GetCommandLine) *const __imp_GetCommandLineW __msabi; + +#define GetEnvironmentStrings(...) __imp_GetEnvironmentStringsW(__VA_ARGS__) extern typeof(GetEnvironmentStrings) *const __imp_GetEnvironmentStringsW __msabi; + +#define GetStdHandle(...) __imp_GetStdHandle(__VA_ARGS__) extern typeof(GetStdHandle) *const __imp_GetStdHandle __msabi; + +#define SetStdHandle(...) __imp_SetStdHandle(__VA_ARGS__) extern typeof(SetStdHandle) *const __imp_SetStdHandle __msabi; + +#define ReadFile(...) __imp_ReadFile(__VA_ARGS__) extern typeof(ReadFile) *const __imp_ReadFile __msabi; + +#define WriteFile(...) __imp_WriteFile(__VA_ARGS__) extern typeof(WriteFile) *const __imp_WriteFile __msabi; + +#define SetDefaultDllDirectories(...) \ + __imp_SetDefaultDllDirectories(__VA_ARGS__) extern typeof(SetDefaultDllDirectories) *const __imp_SetDefaultDllDirectories __msabi; + +#define GetCurrentProcess(...) __imp_GetCurrentProcess(__VA_ARGS__) +extern typeof(GetCurrentProcess) *const __imp_GetCurrentProcess __msabi; diff --git a/libc/pe.h b/libc/pe.h index b9b0239d..32c1f0f6 100644 --- a/libc/pe.h +++ b/libc/pe.h @@ -1,5 +1,6 @@ #ifndef COSMOPOLITAN_LIBC_PE_H_ #define COSMOPOLITAN_LIBC_PE_H_ +#ifndef __STRICT_ANSI__ #include "libc/dce.h" #include "libc/nt/pedef.h" #include "libc/nt/struct/imagedosheader.h" @@ -25,4 +26,5 @@ forceinline void *pecomputerva(struct NtImageDosHeader *mz, size_t mzsize, COSMOPOLITAN_C_END_ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* !ANSI */ #endif /* COSMOPOLITAN_LIBC_PE_H_ */ diff --git a/libc/rand/g_rando.S b/libc/rand/g_rando.S index 4fe478c1..e2bca92d 100644 --- a/libc/rand/g_rando.S +++ b/libc/rand/g_rando.S @@ -18,15 +18,16 @@ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/macros.h" -#include "libc/notice.inc" -.source __FILE__ .bss .align 8 -g_rando:.quad 0 - .endobj g_rando,globl,hidden +g_rando: + .quad 0 + .endobj g_rando,globl .previous .init.start 100,_init_g_rando - movq $1,g_rando(%rip) + movb $1,g_rando(%rip) .init.end 100,_init_g_rando + + .source __FILE__ diff --git a/libc/rand/lcg.h b/libc/rand/lcg.h index e6ac7f7b..bdb57d3d 100644 --- a/libc/rand/lcg.h +++ b/libc/rand/lcg.h @@ -1,7 +1,6 @@ #ifndef COSMOPOLITAN_LIBC_LCG_H_ #define COSMOPOLITAN_LIBC_LCG_H_ #if !(__ASSEMBLER__ + __LINKER__ + 0) -COSMOPOLITAN_C_START_ forceinline uint64_t KnuthLinearCongruentialGenerator(uint64_t prev[1]) { /* Knuth, D.E., "The Art of Computer Programming," Vol 2, @@ -11,6 +10,5 @@ forceinline uint64_t KnuthLinearCongruentialGenerator(uint64_t prev[1]) { return prev[0]; } -COSMOPOLITAN_C_END_ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ #endif /* COSMOPOLITAN_LIBC_LCG_H_ */ diff --git a/libc/rand/rand.ncabi.c b/libc/rand/rand.c similarity index 96% rename from libc/rand/rand.ncabi.c rename to libc/rand/rand.c index 6255a04f..e483056b 100644 --- a/libc/rand/rand.ncabi.c +++ b/libc/rand/rand.c @@ -28,4 +28,6 @@ * seed at startup by default, unless srand() is called. This makes it * useful in cases where deterministic behavior is needed. */ -int(rand)(void) { return KnuthLinearCongruentialGenerator(&g_rando) >> 33; } +int rand(void) { + return KnuthLinearCongruentialGenerator(&g_rando) >> 33; +} diff --git a/libc/rand/rand.h b/libc/rand/rand.h index 92b7e0d8..ae1a8c3c 100644 --- a/libc/rand/rand.h +++ b/libc/rand/rand.h @@ -1,6 +1,5 @@ #ifndef COSMOPOLITAN_LIBC_RAND_RAND_H_ #define COSMOPOLITAN_LIBC_RAND_RAND_H_ -#include "libc/ncabi.h" #if !(__ASSEMBLER__ + __LINKER__ + 0) COSMOPOLITAN_C_START_ /*───────────────────────────────────────────────────────────────────────────│─╗ @@ -26,11 +25,6 @@ int64_t winrandish(void); uint64_t rdrand(void); float randf(void); -#if defined(__GNUC__) && !defined(__STRICT_ANSI__) -NCABI_DECLARE_0(NCABI_NOPRUNE, int, __rand, "rand") -#define rand() __rand() -#endif - COSMOPOLITAN_C_END_ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ #endif /* COSMOPOLITAN_LIBC_RAND_RAND_H_ */ diff --git a/libc/rand/rngset.c b/libc/rand/rngset.c index 57b95bef..f5563c12 100644 --- a/libc/rand/rngset.c +++ b/libc/rand/rngset.c @@ -22,16 +22,18 @@ #include "libc/str/str.h" /** - * Fills memory with random bytes. + * Fills memory with random bytes, e.g. * - * @param seed can be rand64 - * @param reseed is number of bytes between seed() calls - * @return buf + * char buf[1024]; + * rngset(buf, sizeof(buf), rand64, -1); + * + * @param seed can be rand64() and is always called at least once + * @param reseed is bytes between seed() calls and -1 disables it + * @return original buf */ -void *rngset(void *buf, size_t size, uint64_t (*seed)(void), size_t reseed) { +void *rngset(void *buf, size_t size, uint64_t seed(void), size_t reseed) { unsigned char *p; uint64_t i, x, state; - i = 0; p = buf; state = seed(); for (i = 0; size - i >= sizeof(x); i += sizeof(x)) { diff --git a/libc/rand/xorshift.h b/libc/rand/xorshift.h index b98d8bbb..8ff8ad57 100644 --- a/libc/rand/xorshift.h +++ b/libc/rand/xorshift.h @@ -5,24 +5,11 @@ #define kMarsagliaXorshift32Seed 2463534242 #if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ -forceinline uint64_t MarsagliaXorshift64(uint64_t state[hasatleast 1]) { - uint64_t x = state[0]; - x ^= x << 13; - x ^= x >> 7; - x ^= x << 17; - state[0] = x; - return x; -} - -forceinline uint32_t MarsagliaXorshift32(uint32_t state[hasatleast 1]) { - uint32_t x = state[0]; - x ^= x << 13; - x ^= x >> 17; - x ^= x << 5; - state[0] = x; - return x; -} +uint32_t MarsagliaXorshift32(uint32_t[hasatleast 1]); +uint64_t MarsagliaXorshift64(uint64_t[hasatleast 1]); +COSMOPOLITAN_C_END_ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ #endif /* COSMOPOLITAN_LIBC_RAND_XORSHIFT_H_ */ diff --git a/libc/rand/xorshift32.c b/libc/rand/xorshift32.c new file mode 100644 index 00000000..a51522ec --- /dev/null +++ b/libc/rand/xorshift32.c @@ -0,0 +1,29 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/rand/xorshift.h" + +uint32_t MarsagliaXorshift32(uint32_t state[hasatleast 1]) { + uint32_t x = state[0]; + x ^= x << 13; + x ^= x >> 17; + x ^= x << 5; + state[0] = x; + return x; +} diff --git a/libc/rand/xorshift64.c b/libc/rand/xorshift64.c new file mode 100644 index 00000000..e8f7485f --- /dev/null +++ b/libc/rand/xorshift64.c @@ -0,0 +1,29 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/rand/xorshift.h" + +uint64_t MarsagliaXorshift64(uint64_t state[hasatleast 1]) { + uint64_t x = state[0]; + x ^= x << 13; + x ^= x >> 7; + x ^= x << 17; + state[0] = x; + return x; +} diff --git a/libc/runtime/arememoryintervalsok.c b/libc/runtime/arememoryintervalsok.c new file mode 100644 index 00000000..e05fa60e --- /dev/null +++ b/libc/runtime/arememoryintervalsok.c @@ -0,0 +1,36 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/runtime/memtrack.h" +#include "libc/runtime/runtime.h" + +bool AreMemoryIntervalsOk(const struct MemoryIntervals *mm) { + int i; + for (i = 0; i < mm->i; ++i) { + if (mm->p[i].y < mm->p[i].x) return false; + if (i) { + if (mm->h[i] || mm->h[i - 1]) { + if (mm->p[i].x <= mm->p[i - 1].y) return false; + } else { + if (mm->p[i].x <= mm->p[i - 1].y + 1) return false; + } + } + } + return true; +} diff --git a/libc/runtime/asan.greg.c b/libc/runtime/asan.greg.c index 968cd9ca..7f070744 100644 --- a/libc/runtime/asan.greg.c +++ b/libc/runtime/asan.greg.c @@ -19,31 +19,57 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/calls.h" #include "libc/stdio/stdio.h" +#include "libc/str/str.h" #include "libc/sysv/consts/fileno.h" +struct SourceLocation { + const char *filename; + int line; + int column; +}; + +struct AccessInfo { + const uint8_t *addr; + const uint8_t *first_bad_addr; + size_t size; + bool iswrite; + unsigned long ip; +}; + +struct Global { + const uint8_t *addr; + size_t size; + size_t size_with_redzone; + const void *name; + const void *module_name; + unsigned long has_cxx_init; + struct kasan_source_location *location; + char *odr_indicator; +}; + privileged void __asan_init(void) { } -privileged void __asan_register_globals(uintptr_t a, uintptr_t b) { +privileged void __asan_version_mismatch_check_v8(void) { } -privileged void __asan_unregister_globals(uintptr_t a, uintptr_t b) { +privileged void __asan_register_globals(struct Global globals[], int n) { +} + +privileged void __asan_unregister_globals(struct Global globals[], int n) { +} + +privileged void __asan_report_load_n(uint8_t *p, int n) { +} + +privileged void __asan_report_store_n(uint8_t *p, int n) { + __asan_report_load_n(p, n); } privileged void __asan_loadN(uintptr_t ptr, size_t size) { - dprintf(STDERR_FILENO, "load %p %zu"); } privileged void __asan_storeN(uintptr_t ptr, size_t size) { - dprintf(STDERR_FILENO, "store %p %zu"); -} - -privileged void __asan_report_load_n(uintptr_t ptr, size_t size) { - dprintf(STDERR_FILENO, "%s%zu%s%#p", "asan: load ", size, " bytes at ", ptr); -} - -privileged void __asan_report_store_n(uintptr_t ptr, size_t size) { - dprintf(STDERR_FILENO, "%s%zu%s%#p", "asan: store ", size, " bytes at ", ptr); } privileged uintptr_t __asan_stack_malloc(size_t size, int classid) { @@ -54,9 +80,7 @@ privileged void __asan_stack_free(uintptr_t ptr, size_t size, int classid) { } privileged void __asan_handle_no_return(void) { -} - -privileged void __asan_version_mismatch_check_v8(void) { + DebugBreak(); } privileged void __asan_alloca_poison(uintptr_t addr, uintptr_t size) { diff --git a/libc/runtime/assertfail.c b/libc/runtime/assertfail.c index f4643161..f31463f3 100644 --- a/libc/runtime/assertfail.c +++ b/libc/runtime/assertfail.c @@ -18,7 +18,7 @@ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/assert.h" -#include "libc/bits/bits.h" +#include "libc/bits/weaken.h" #include "libc/calls/calls.h" #include "libc/log/log.h" #include "libc/macros.h" diff --git a/libc/runtime/atexit.c b/libc/runtime/atexit.c new file mode 100644 index 00000000..d373e580 --- /dev/null +++ b/libc/runtime/atexit.c @@ -0,0 +1,34 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/runtime/runtime.h" + +/** + * Adds global destructor. + * + * Destructors are called in reverse order. They won't be called + * if the program aborts or _exit() is called. Invocations of this + * function are usually generated by the C++ compiler. + * + * @param rdi callback typed void(*)(T) (nullable) + * @return 0 on success or nonzero if out of space + */ +int atexit(void f(void)) { + return __cxa_atexit(f, NULL, NULL); +} diff --git a/libc/runtime/carsort.h b/libc/runtime/carsort.h new file mode 100644 index 00000000..212d60eb --- /dev/null +++ b/libc/runtime/carsort.h @@ -0,0 +1,11 @@ +#ifndef COSMOPOLITAN_LIBC_RUNTIME_CARSORT_H_ +#define COSMOPOLITAN_LIBC_RUNTIME_CARSORT_H_ +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +void carsort100(size_t, int32_t (*)[2]) paramsnonnull() nocallback nothrow; +void carsort1000(size_t, int32_t (*)[2]) paramsnonnull() nocallback nothrow; + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_RUNTIME_CARSORT_H_ */ diff --git a/libc/runtime/carsort100.c b/libc/runtime/carsort100.c new file mode 100644 index 00000000..a7c5c94c --- /dev/null +++ b/libc/runtime/carsort100.c @@ -0,0 +1,41 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/runtime/carsort.h" +#include "libc/str/str.h" + +/** + * Sorts int32 key-value arrays of trivial size. + * + * @see test/libc/alg/carsort_test.c + * @see carsort1000() if larger + */ +textstartup void carsort100(size_t n, int32_t a[n][2]) { + int32_t t[2]; + unsigned i, j; + for (i = 1; i < n; ++i) { + j = i; + memcpy(t, a[i], sizeof(t)); + while (j > 0 && t[0] < a[j - 1][0]) { + memcpy(a[j], a[j - 1], sizeof(t)); + --j; + } + memcpy(a[j], t, sizeof(t)); + } +} diff --git a/libc/runtime/heapsortcar.c b/libc/runtime/carsort1000.c similarity index 73% rename from libc/runtime/heapsortcar.c rename to libc/runtime/carsort1000.c index 1bdaa16c..2e971b0c 100644 --- a/libc/runtime/heapsortcar.c +++ b/libc/runtime/carsort1000.c @@ -17,22 +17,21 @@ │ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/alg/alg.h" +#include "libc/runtime/carsort.h" #include "libc/str/str.h" -static unsigned heapsortmax(int32_t (*A)[2], unsigned n, unsigned i, unsigned j, - unsigned k) { - unsigned m = i; - if (j < n && A[j][0] > A[m][0]) m = j; - if (k < n && A[k][0] > A[m][0]) m = k; - return m; +static textstartup size_t HeapSortMax(size_t n, int32_t A[n][2], long i, long j, + long k) { + if (j < n && A[j][0] > A[i][0]) i = j; + if (k < n && A[k][0] > A[i][0]) i = k; + return i; } -static void heapsortdown(int32_t (*A)[2], unsigned n, unsigned i) { - unsigned j; +static textstartup void HeapSortDown(size_t n, int32_t A[n][2], long i) { + size_t j; int32_t t[2]; for (;;) { - unsigned j = heapsortmax(A, n, i, 2 * i + 1, 2 * i + 2); + j = HeapSortMax(n, A, i, 2 * i + 1, 2 * i + 2); if (j == i) break; memcpy(t, A[i], sizeof(t)); memcpy(A[i], A[j], sizeof(t)); @@ -42,25 +41,21 @@ static void heapsortdown(int32_t (*A)[2], unsigned n, unsigned i) { } /** - * Sorts key-values. + * Sorts int32 key-value arrays of nontrivial size. * - * heapsortcar is a linearithmic / constant-memory / non-stable sorting - * function that's a tenth the size of the more generalized qsort() API. - * It can only sort arrays of 64-bit values, and comparisons happen by - * treating the lower 32-bits as a signed integer. - * - * @see test/libc/alg/heapsortcar_test.c + * @see test/libc/alg/carsort_test.c + * @see carsort100() if smaller */ -void heapsortcar(int32_t (*A)[2], unsigned n) { - unsigned i; +textstartup void carsort1000(size_t n, int32_t A[n][2]) { + size_t i; int32_t t[2]; for (i = ((n - 2) / 2) + 1; i > 0; i--) { - heapsortdown(A, n, i - 1); + HeapSortDown(n, A, i - 1); } for (i = 0; i < n; i++) { memcpy(t, A[n - i - 1], sizeof(t)); memcpy(A[n - i - 1], A[0], sizeof(t)); memcpy(A[0], t, sizeof(t)); - heapsortdown(A, n - i - 1, 0); + HeapSortDown(n - i - 1, A, 0); } } diff --git a/libc/runtime/close_s.c b/libc/runtime/close_s.c index 021e5a66..0e2209ec 100644 --- a/libc/runtime/close_s.c +++ b/libc/runtime/close_s.c @@ -19,18 +19,18 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/bits/bits.h" #include "libc/calls/calls.h" -#include "libc/runtime/mappings.h" #include "libc/runtime/runtime.h" /** * Closes file descriptor. * - * The caller's variable is made -1. Note that close(-1) is a no-op. + * The caller's variable is made -1 so subsequent calls are no-ops. * * @return 0 on success, or -1 w/ errno * @asyncsignalsafe */ int close_s(int *fdp) { int fd = -1; - return close(lockxchg(fdp, &fd)); + if (lockxchg(fdp, &fd) == -1) return 0; + return close(fd); } diff --git a/libc/runtime/closesymboltable.c b/libc/runtime/closesymboltable.c index 74e55179..7981892a 100644 --- a/libc/runtime/closesymboltable.c +++ b/libc/runtime/closesymboltable.c @@ -17,21 +17,23 @@ │ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/calls.h" #include "libc/runtime/ezmap.h" #include "libc/runtime/symbols.h" -#include "libc/calls/calls.h" /** * Frees symbol table. * @return 0 on success or -1 on system error */ int closesymboltable(struct SymbolTable **table) { - int rc = 0; - if (*table && (intptr_t)*table != (intptr_t)-1) { - struct SymbolTable *t = *table; + int rc; + struct SymbolTable *t; + rc = 0; + if (*table && *table != MAP_FAILED) { + t = *table; *table = NULL; - if (unmapfile(&t->mf) == -1) rc = -1; - if (munmap(t, t->scratch) == -1) rc = -1; + rc |= unmapfile(&t->mf); + rc |= munmap(t, t->scratch); } return rc; } diff --git a/libc/runtime/construct.S b/libc/runtime/construct.S new file mode 100644 index 00000000..24caf77b --- /dev/null +++ b/libc/runtime/construct.S @@ -0,0 +1,45 @@ +/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│ +│vi: set et ft=asm ts=8 tw=8 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/runtime/internal.h" +#include "libc/macros.h" +.text.startup +.source __FILE__ + +/ Calls global initialization functions. +_construct: + push %rbp + mov %rsp,%rbp + orb $RUNSTATE_INITIALIZED,g_runstate(%rip) + ezlea __init_array_start,ax # static ctors in forward order + .weak __init_array_start # could be called multiple times + ezlea __init_array_end,cx # idempotency recommended + .weak __init_array_end # @see ape/ape.lds +1: cmp %rax,%rcx + je 2f + push %rax + push %rcx + call *(%rax) + pop %rcx + pop %rax + add $8,%rax + jmp 1b +2: pop %rbp + ret + .endfn _construct,globl diff --git a/libc/runtime/cxaatexit.S b/libc/runtime/cxaatexit.S deleted file mode 100644 index 24fbd3b2..00000000 --- a/libc/runtime/cxaatexit.S +++ /dev/null @@ -1,181 +0,0 @@ -/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│ -│vi: set et ft=asm ts=8 sw=8 fenc=utf-8 :vi│ -╞══════════════════════════════════════════════════════════════════════════════╡ -│ Copyright 2020 Justine Alexandra Roberts Tunney │ -│ │ -│ This program is free software; you can redistribute it and/or modify │ -│ it under the terms of the GNU General Public License as published by │ -│ the Free Software Foundation; version 2 of the License. │ -│ │ -│ This program is distributed in the hope that it will be useful, but │ -│ WITHOUT ANY WARRANTY; without even the implied warranty of │ -│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ -│ General Public License for more details. │ -│ │ -│ You should have received a copy of the GNU General Public License │ -│ along with this program; if not, write to the Free Software │ -│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ -│ 02110-1301 USA │ -╚─────────────────────────────────────────────────────────────────────────────*/ -#include "ape/relocations.h" -#include "libc/macros.h" -.section .text.exit,"ax",@progbits -.source __FILE__ - -/ Delegates to __cxa_atexit(). -/ -/ @param rdi callback typed void(*)(void) (nullable) -/ @return 0 on success or nonzero if out of space -atexit: xor %esi,%esi - xor %edx,%edx -/ 𝑠𝑙𝑖𝑑𝑒 - -/ Registers destructor to be called upon exit(). -/ -/ Destructors are called in reverse order. They won't be called -/ if the program aborts or _exit() is called. Invocations of this -/ function are usually generated by the C++ compiler. -/ -/ @param rdi callback typed void(*)(T) (nullable) -/ @param rsi callback arg typed T (nullable) -/ @param rdx dso handle (nullable) -/ @return 0 on success or nonzero if out of space -/ @note folks have forked libc in past just to unbloat atexit() -/ @see news.ycombinator.com/item?id=20228082 -/ @preinitsafe -__cxa_atexit: - push %rbp - mov %rsp,%rbp - .profilable - push %r15 - ezlea g_cxa,cx - mov %rcx,%r15 - mov (%r15),%rax # get state->block - mov 8(%r15),%rcx # get state->offset - test %rcx,%rcx - jz 2f -0: sub $24,%rcx - mov %rdx,(%rax,%rcx) # set cb->dso - mov %rsi,8(%rax,%rcx) # set cb->arg - mov %rdi,16(%rax,%rcx) # set cb->fn - mov %rcx,8(%r15) # set state->offset - xor %eax,%eax # success -1: pop %r15 - pop %rbp - ret -2: .weak calloc - ezlea calloc,cx - test %rcx,%rcx - jz 1b # fail (no malloc) - push %rax - push %rdi - push %rsi - pushpop ATEXIT_MAX+1,%rdi - pushpop 16,%rsi - call *%rcx - pop %rsi - pop %rdi - pop %rcx # rax=new rcx=old - test %rax,%rax - jz 1b # fail (no memory) - mov $ATEXIT_MAX*8*3,%r8 - mov %rax,(%r15) # set state->block - mov %rcx,(%rax,%r8) # set block->next - mov %r8,%rcx - jmp 0b - .endfn __cxa_atexit,globl - .endfn atexit,globl - -/ Triggers destructors. -/ -/ This implementation supports DSO handles, but is optimized for -/ cases when it's called only once by exit(). -/ -/ @param rdi is dso predicate or null to destroy all -/ @see libc/exit.c -__cxa_finalize: - push %rbp - mov %rsp,%rbp - .profilable - push %r14 - push %r13 - push %r12 - mov g_cxa(%rip),%rsi - mov %rdi,%r14 -0: mov %rsi,%r12 # loop through blocks - pushpop ATEXIT_MAX,%rcx -1: lodsq #→ dso # loop through callbacks - xchg %rax,%rdx - lodsq #→ arg - xchg %rax,%rdi - lodsq #→ fn - test %rax,%rax - jz 2f # ignore empty slots - test %r14,%r14 - jmp 5f # null predicate match all - cmp %r14,%rdx - jne 2f # predicate mismatch -5: push %rsi - push %rcx - push %rcx - call *%rax - pop %rcx - pop %rcx - pop %rsi - xor %eax,%eax # clear slot (never reused) - mov %rax,-8(%rsi) -2: loop 1b - lodsq # get next block ptr - test %rax,%rax # don't free static block no. 1 - jz 3f # which always has next == NULL - test %r14,%r14 - jz 1f # don't free anything if just one dso - push %rax - mov %r12,%rdi - .weak free - call free # can't panic due to earlier test -1: pop %rsi - jmp 0b -3: pop %r12 # align stack for next call - test %r14,%r14 # no static dtor for dso exit - jnz 9f - ezlea __fini_array_end,ax # static dtors in reverse order - .weak __fini_array_end # could be called multiple times - ezlea __fini_array_start,cx # idempotency recommended - .weak __fini_array_start # or consider atexit() -8: sub $8,%rax # @see ape/ape.lds - cmp %rcx,%rax - jl 9f - push %rax - push %rcx - call *(%rax) - pop %rcx - pop %rax - jmp 8b -9: pop %r13 - pop %r14 - pop %rbp - ret - .endfn __cxa_finalize,globl,hidden - - .bss - .align 16 # static/dynamic hybrid linked list -g_cxa: .quad 0 # last block ptr: (long (*)[32][3]) - .quad 0 # block byte offset moves backwards - .endobj g_cxa -g_cxa_static: - .rept ATEXIT_MAX - .quad 0 # dso - .quad 0 # arg - .quad 0 # fn (or NULL for empty) - .endr - .quad 0 # next (always NULL in static block) - .endobj g_cxa_static - .previous - - .init.start 300,_init_g_cxa - ezlea g_cxa,cx - lea g_cxa_static-g_cxa(%rcx),%rax - mov %rax,(%rcx) - movl $ATEXIT_MAX*8*3,8(%rcx) - .init.end 300,_init_g_cxa diff --git a/libc/runtime/cxaatexit.c b/libc/runtime/cxaatexit.c new file mode 100644 index 00000000..b68ef8dd --- /dev/null +++ b/libc/runtime/cxaatexit.c @@ -0,0 +1,122 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/assert.h" +#include "libc/bits/weaken.h" +#include "libc/macros.h" +#include "libc/mem/mem.h" +#include "libc/nexgen32e/bsf.h" +#include "libc/nexgen32e/bsr.h" +#include "libc/runtime/runtime.h" +#include "libc/sysv/errfuns.h" + +static struct CxaAtexitBlocks { + struct CxaAtexitBlock { + unsigned mask; + struct CxaAtexitBlock *next; + struct CxaAtexit { + void *fp; + void *arg; + void *pred; + } p[ATEXIT_MAX]; + } * p, root; +} __cxa_blocks; + +/** + * Adds global destructor. + * + * Destructors are called in reverse order. They won't be called if the + * program aborts or _exit() is called. Invocations of this function are + * usually generated by the C++ compiler. Behavior is limitless if you + * choose to link calloc() and free(). + * + * @param fp is void(*)(T) + * @param arg is passed to callback + * @param pred can be non-null for things like dso modules + * @return 0 on success or nonzero w/ errno + * @note folks have forked libc in past just to unbloat atexit() + */ +int __cxa_atexit(void *fp, void *arg, void *pred) { + unsigned i; + struct CxaAtexitBlock *b, *b2; + b = __cxa_blocks.p; + if (!b) b = __cxa_blocks.p = &__cxa_blocks.root; + if (!~b->mask) { + if (weaken(calloc) && + (b2 = weaken(calloc)(1, sizeof(struct CxaAtexitBlock)))) { + b2->next = b; + __cxa_blocks.p = b = b2; + } else { + return enomem(); + } + } + i = bsr(~b->mask); + assert(i < ARRAYLEN(b->p)); + b->mask |= 1u << i; + b->p[i].fp = fp; + b->p[i].arg = arg; + b->p[i].pred = pred; + return 0; +} + +/** + * Triggers global destructors. + * + * @param pred can be null to match all + * @note reentrant emptor + */ +void __cxa_finalize(void *pred) { + unsigned i; + unsigned long mask; + struct CxaAtexitBlock *b, *b2; +StartOver: + if ((b = __cxa_blocks.p)) { + for (;;) { + mask = b->mask; + while (mask) { + i = bsf(mask); + mask &= ~(1u << i); + if (!pred || pred == b->p[i].pred) { + b->mask &= ~(1u << i); + if (b->p[i].fp) { + ((void (*)(void *))b->p[i].fp)(b->p[i].arg); + goto StartOver; + } + } + } + if (!pred) { + b2 = b->next; + if (b2) { + assert(b != &__cxa_blocks.root); + if (weaken(free)) { + weaken(free)(b); + } + } + __cxa_blocks.p = b2; + goto StartOver; + } else { + if (b->next) { + b = b->next; + } else { + break; + } + } + } + } +} diff --git a/libc/runtime/defer.greg.c b/libc/runtime/defer.greg.c index 6128971d..69a2b334 100644 --- a/libc/runtime/defer.greg.c +++ b/libc/runtime/defer.greg.c @@ -53,10 +53,10 @@ void __defer(struct StackFrame *frame, void *fn, void *arg) { frame2 = __builtin_frame_address(0); assert(frame2->next == frame); assert(PointerNotOwnedByParentStackFrame(frame2, frame, arg)); - if (append(&__garbage, + if (append(&g_garbage, (&(const struct Garbage){frame->next, (intptr_t)fn, (intptr_t)arg, frame->addr})) != -1) { - atomic_store(&frame->addr, (intptr_t)&__gc); + atomic_store(&frame->addr, (intptr_t)&CollectGarbage); } else { abort(); } diff --git a/libc/runtime/startmain.S b/libc/runtime/executive.S similarity index 65% rename from libc/runtime/startmain.S rename to libc/runtime/executive.S index 3fdc3422..d19775d1 100644 --- a/libc/runtime/startmain.S +++ b/libc/runtime/executive.S @@ -20,91 +20,37 @@ #include "libc/dce.h" #include "libc/macros.h" #include "libc/notice.inc" -#include "libc/runtime/internal.h" -#include "libc/sysv/consts/map.h" #include "libc/dce.h" -#include "libc/runtime/mappings.h" -#include "libc/sysv/consts/prot.h" .text.startup .source __FILE__ -/ Cosmopolitan process entrypoint. +/ Stack frame that owns process from spawn to exit. / -/ @param r12 is argc -/ @param r13 is argv -/ @param r14 is environ -/ @param r15 is auxv +/ @param edi is argc +/ @param rsi is argv +/ @param rdx is environ +/ @param rcx is auxv / @noreturn -__executive: - .frame0 +_executive: + push %rbp + mov %rsp,%rbp ezlea _base,bx - -#ifdef __FAST_MATH__ - call __fast_math -#endif - call _init -#if IsModeDbg() - call _init # _init() is idempotent -#endif - -/* -#if !IsTiny() -/ “Memory obfuscation for glibc, not for we” -/ -/ 64kb stack w/ 4kb guard alongside tuning in libc/integral/c.inc -/ e.g. -Werror=frame-larger-than=4096 is intended to guarantee no -/ stack overflow possible. We like malloc and only cleverly avoid -/ its use at the lowest levels of the runtime stack, without MMU. -/ We like this practicee because it's how Google runs production. - mov $kStackCeiling-STACKSIZE,%rdi - mov $STACKSIZE,%esi - mov $PROT_READ|PROT_WRITE,%edx - mov MAP_ANONYMOUS,%ecx - or MAP_FIXED,%ecx - or MAP_PRIVATE,%ecx - mov $-1,%r8d - xor %r9d,%r9d - call mmap - cmp $-1,%eax - je abort - lea STACKSIZE(%rax),%rsp - xor %ebp,%ebp - mov %rax,%rdi - mov $PAGESIZE,%esi - mov $PROT_NONE,%edx - call mprotect - cmp $-1,%eax - je abort -#endif -*/ - - orl $RUNSTATE_INITIALIZED,g_runstate(%rip) - ezlea __init_array_start,ax # static ctors in forward order - .weak __init_array_start # could be called multiple times - ezlea __init_array_end,cx # idempotency recommended - .weak __init_array_end # @see ape/ape.lds -1: cmp %rax,%rcx - je 2f - push %rax - push %rcx - call *(%rax) - pop %rcx - pop %rax - add $8,%rax - jmp 1b -2: nop -#if !IsTrustworthy() - mov $PROT_READ,%edi - call __piro -#endif + mov %edi,%r12d + mov %rsi,%r13 + mov %rdx,%r14 + mov %rcx,%r15 + call _spawn mov %r12,%rdi mov %r13,%rsi mov %r14,%rdx + mov %r15,%rcx .weak main call main mov %eax,%edi call exit - .endfn __executive,weak,hidden +9: .endfn _executive,weak,hidden + + ud2 #ifdef __PG__ / Enables plaintext function tracing if --ftrace flag passed. diff --git a/libc/runtime/exit.S b/libc/runtime/exit.S new file mode 100644 index 00000000..31bc9988 --- /dev/null +++ b/libc/runtime/exit.S @@ -0,0 +1,39 @@ +/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│ +│vi: set et ft=asm ts=8 tw=8 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/macros.h" +.text.exit +.source __FILE__ + +/ Exits program with grace. +/ +/ @param %dil has exit code +/ @noreturn +exit: push %rbp + mov %rsp,%rbp + push %rdi + push %rdi + xor %edi,%edi + call __cxa_finalize + pop %rdi + pop %rdi + call _Exit + .endfn exit,globl + + ud2 diff --git a/libc/runtime/ezmap.c b/libc/runtime/ezmap.c index 2ea74b29..311bee8b 100644 --- a/libc/runtime/ezmap.c +++ b/libc/runtime/ezmap.c @@ -17,6 +17,7 @@ │ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/bits/pushpop.h" #include "libc/bits/safemacros.h" #include "libc/calls/calls.h" #include "libc/limits.h" @@ -48,11 +49,10 @@ int mapfileread(const char *filename, struct MappedFile *mf) { * Releases resource returned by mapfileread(). */ int unmapfile(struct MappedFile *mf) { - int rc = 0; - rc |= munmap(mf->addr, mf->size); - rc |= close(mf->fd); - mf->fd = -1; - mf->addr = MAP_FAILED; - mf->size = 0; + int rc; + rc = 0; + rc |= munmap_s(&mf->addr, mf->size); + rc |= close_s(&mf->fd); + pushmov(&mf->size, 0); return rc; } diff --git a/libc/runtime/ezmap.h b/libc/runtime/ezmap.h index 9db11de4..f39cfc7c 100644 --- a/libc/runtime/ezmap.h +++ b/libc/runtime/ezmap.h @@ -23,13 +23,13 @@ COSMOPOLITAN_C_START_ struct MappedFile { - int64_t fd; + int fd; void *addr; size_t size; }; -int mapfileread(const char *filename, struct MappedFile *mf) hidden; -int unmapfile(struct MappedFile *mf) hidden; +int mapfileread(const char *, struct MappedFile *) hidden; +int unmapfile(struct MappedFile *) hidden; COSMOPOLITAN_C_END_ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ diff --git a/libc/runtime/findmemoryinterval.c b/libc/runtime/findmemoryinterval.c new file mode 100644 index 00000000..0efc6e31 --- /dev/null +++ b/libc/runtime/findmemoryinterval.c @@ -0,0 +1,36 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/runtime/memtrack.h" + +unsigned FindMemoryInterval(const struct MemoryIntervals *mm, int x) { + unsigned l, m, r; + l = 0; + r = mm->i; + while (l < r) { + m = (l + r) >> 1; + if (mm->p[m].x < x) { + l = m + 1; + } else { + r = m; + } + } + if (l && mm->p[l - 1].y >= x) --l; + return l; +} diff --git a/libc/runtime/free_s.c b/libc/runtime/free_s.c index 5562e438..29b55cc7 100644 --- a/libc/runtime/free_s.c +++ b/libc/runtime/free_s.c @@ -19,7 +19,6 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/bits/bits.h" #include "libc/mem/mem.h" -#include "libc/runtime/mappings.h" #include "libc/runtime/runtime.h" /** diff --git a/libc/runtime/ftrace.greg.c b/libc/runtime/ftrace.greg.c index 5907e9f1..79a9e660 100644 --- a/libc/runtime/ftrace.greg.c +++ b/libc/runtime/ftrace.greg.c @@ -98,116 +98,14 @@ privileged interruptfn void ftrace_hook(void) { RESTORE_RBX(); } -/** - * Rewrites code in memory to log function calls. - * - * We do this by searching each function for the nop instruction - * inserted by GCC when we use the -pg -mnop-mcount flags. There's no - * risk of corrupting data since the linker scripts won't mix code and - * data. - * - * Modules built with -O3 and without the profiling flags might have - * these same nop instructions, but that shouldn't be problematic since - * they're only there for the puposes of aligning jumps, and therefore - * aren't actually executed. However codebases that use huge function - * alignments with wide-nop slides could pose minor issues. Further note - * that Cosmopolitan sources are almost never intentionally written to - * use code alignment, since we've only seen a few cases where it helps. - * - * @see ape/ape.lds - */ -privileged void ftrace_install(void) { - /* TODO(jart): Is -fschedule-insns2 so aggro that we need XED here? */ - size_t i; - intptr_t addr; - sigset_t oldmask; - uint64_t code, mcode; - unsigned char *p, *pe; - const intptr_t kMcount = (intptr_t)&mcount; - const intptr_t kFtraceHook = (intptr_t)&ftrace_hook; - const intptr_t kProgramCodeStart = (intptr_t)&_ereal; - const intptr_t kPrivilegedStart = (intptr_t)&__privileged_start; - const bool kIsBinaryAligned = !(kPrivilegedStart & (PAGESIZE - 1)); - g_buf[0] = '+'; - g_buf[1] = ' '; - sigprocmask(SIG_BLOCK, &kSigsetFull, &oldmask); - if (__mprotect((void *)g_symbols->addr_base, - kPrivilegedStart - g_symbols->addr_base, - kIsBinaryAligned ? PROT_READ | PROT_WRITE - : PROT_READ | PROT_WRITE | PROT_EXEC) != -1) { - for (i = 0; i < g_symbols->count - 1; ++i) { - if (g_symbols->addr_base + g_symbols->symbols[i].addr_rva < - kProgramCodeStart) { - continue; /* skip over real mode symbols */ - } - if (g_symbols->addr_base + g_symbols->symbols[i].addr_rva >= - kPrivilegedStart) { - break; /* stop before privileged symbols */ - } - for (p = (unsigned char *)(g_symbols->addr_base + - g_symbols->symbols[i].addr_rva), - pe = (unsigned char *)(g_symbols->addr_base + - g_symbols->symbols[i + 1].addr_rva); - p < pe - 8; ++p) { - code = read64le(p); - - /* - * Test for -mrecord-mcount (w/ -fpie or -fpic) - * - * nopw 0x00(%rax,%rax,1) ← morphed by package.com - * call *mcount(%rip) ← linked w/o -static - * addr32 call mcount ← relaxed w/ -static - * addr32 call mcount ← relaxed w/ -static - * - * Note that gcc refuses to insert the six byte nop. - */ - if ((code & 0x0000FFFFFFFFFFFF) == 0x0000441F0F66 || - (code & 0x0000FFFFFFFFFFFF) == - ((((kMcount - ((intptr_t)&p[2] + 4)) << 16) | 0xE867) & - 0x0000FFFFFFFFFFFF) || - (code & 0x0000FFFFFFFFFFFF) == - ((((kMcount - ((intptr_t)&p[2] + 4)) << 16) | 0xFF15) & - 0x0000FFFFFFFFFFFF)) { - p[0] = 0x67; - p[1] = 0xE8; - addr = kFtraceHook - ((intptr_t)&p[2] + 4); - p[2] = addr >> 000; - p[3] = addr >> 010; - p[4] = addr >> 020; - p[5] = addr >> 030; - break; - } - - /* - * Test for -mnop-mcount (w/ -fno-pie) - */ - mcode = code & 0x000000FFFFFFFFFF; - if ((mcode == 0x00441F0F /* nopl 0x00(%eax,%eax,1) [canonical] */) || - (mcode == 0x00041F0F67 /* nopl (%eax,%eax,1) [older gcc] */)) { - if (p[-1] != 0x66 /* nopw 0x0(%rax,%rax,1) [donotwant] */) { - p[0] = 0xE8 /* call Jvds */; - addr = kFtraceHook - ((intptr_t)&p[1] + 4); - p[1] = addr >> 000; - p[2] = addr >> 010; - p[3] = addr >> 020; - p[4] = addr >> 030; - } - break; - } - } - } - __mprotect((void *)g_symbols->addr_base, - kPrivilegedStart - g_symbols->addr_base, PROT_READ | PROT_EXEC); - } - sigprocmask(SIG_SETMASK, &oldmask, NULL); -} - /** * Installs plaintext function tracer. Do not call. * @see libc/runtime/_init.S for documentation */ textstartup void ftrace_init(void) { + g_buf[0] = '+'; + g_buf[1] = ' '; if ((g_symbols = opensymboltable(finddebugbinary()))) { - ftrace_install(); + __hook(ftrace_hook, g_symbols); } } diff --git a/libc/runtime/gc.h b/libc/runtime/gc.h index e054d587..9c28da2d 100644 --- a/libc/runtime/gc.h +++ b/libc/runtime/gc.h @@ -21,7 +21,7 @@ struct StackFrame; * @warning do not return a gc()'d pointer * @warning do not realloc() with gc()'d pointer */ -#define gc(THING) defer(weakfree, (THING)) +#define gc(THING) defer((void *)weakfree, (void *)(THING)) /** * Same as longjmp() but runs gc() / defer() destructors. @@ -31,14 +31,14 @@ void gclongjmp(jmp_buf, int) nothrow noreturn paramsnonnull(); /** * Calls FN(ARG) when function returns. */ -#define defer(FN, ARG) \ - ({ \ - autotype(ARG) Arg = (ARG); \ - /* prevent weird opts like tail call */ \ - asm volatile("" : "+g"(Arg) : : "memory"); \ - __defer(__builtin_frame_address(0), FN, Arg); \ - asm volatile("" : "+g"(Arg) : : "memory"); \ - Arg; \ +#define defer(FN, ARG) \ + ({ \ + autotype(ARG) Arg = (ARG); \ + /* prevent weird opts like tail call */ \ + asm volatile("" : "+g"(Arg) : : "memory"); \ + __defer((struct StackFrame *)__builtin_frame_address(0), FN, Arg); \ + asm volatile("" : "+g"(Arg) : : "memory"); \ + Arg; \ }) void __defer(struct StackFrame *, void *, void *) hidden paramsnonnull((1, 2)); diff --git a/libc/runtime/getdosargv.c b/libc/runtime/getdosargv.c index 1ec8a435..c160b7f0 100644 --- a/libc/runtime/getdosargv.c +++ b/libc/runtime/getdosargv.c @@ -17,6 +17,7 @@ │ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/assert.h" #include "libc/bits/bits.h" #include "libc/bits/pushpop.h" #include "libc/bits/safemacros.h" @@ -38,11 +39,11 @@ struct DosArgv { wint_t wc; }; -static inline textwindows void decodedosargv(struct DosArgv *st) { +static textwindows void decodedosargv(struct DosArgv *st) { st->s += getutf16(st->s, &st->wc); } -static inline textwindows void appenddosargv(struct DosArgv *st, wint_t wc) { +static textwindows void appenddosargv(struct DosArgv *st, wint_t wc) { AppendChar(&st->p, st->pe, wc); } diff --git a/libc/runtime/getdosenviron.h b/libc/runtime/getdosenviron.h index ea5d1c6b..8d09aedf 100644 --- a/libc/runtime/getdosenviron.h +++ b/libc/runtime/getdosenviron.h @@ -19,6 +19,7 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #ifndef COSMOPOLITAN_LIBC_DOSENVIRON_H_ #define COSMOPOLITAN_LIBC_DOSENVIRON_H_ +#ifndef __STRICT_ANSI__ #include "libc/bits/safemacros.h" #include "libc/str/appendchar.h" #if !(__ASSEMBLER__ + __LINKER__ + 0) @@ -64,4 +65,5 @@ static inline int getdosenviron(const char16_t *env, char *buf, size_t size, } #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* !ANSI */ #endif /* COSMOPOLITAN_LIBC_DOSENVIRON_H_ */ diff --git a/libc/runtime/grow.c b/libc/runtime/grow.c index 85c88302..0ae68588 100644 --- a/libc/runtime/grow.c +++ b/libc/runtime/grow.c @@ -20,11 +20,11 @@ #include "libc/assert.h" #include "libc/bits/bits.h" #include "libc/bits/safemacros.h" +#include "libc/bits/weaken.h" #include "libc/conv/conv.h" #include "libc/conv/sizemultiply.h" #include "libc/macros.h" #include "libc/mem/mem.h" -#include "libc/runtime/mappings.h" #include "libc/runtime/runtime.h" #include "libc/str/str.h" #include "libc/sysv/errfuns.h" diff --git a/libc/runtime/hook.greg.c b/libc/runtime/hook.greg.c new file mode 100644 index 00000000..a1820e59 --- /dev/null +++ b/libc/runtime/hook.greg.c @@ -0,0 +1,125 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/calls.h" +#include "libc/calls/internal.h" +#include "libc/calls/struct/sigset.h" +#include "libc/runtime/runtime.h" +#include "libc/runtime/symbols.h" +#include "libc/sysv/consts/prot.h" + +/** + * Rewrites code in memory to hook function calls. + * + * We do this by searching each function for the nop instruction + * inserted by GCC when we use the -pg -mnop-mcount flags. There's no + * risk of corrupting data since the linker scripts won't mix code and + * data. + * + * Modules built with -O3 and without the profiling flags might have + * these same nop instructions, but that shouldn't be problematic since + * they're only there for the puposes of aligning jumps, and therefore + * aren't actually executed. However codebases that use huge function + * alignments with wide-nop slides could pose minor issues. Further note + * that Cosmopolitan sources are almost never intentionally written to + * use code alignment, since we've only seen a few cases where it helps. + * + * @see ape/ape.lds + */ +privileged void __hook(void ifunc(void), struct SymbolTable *symbols) { + size_t i; + intptr_t addr; + sigset_t oldmask; + uint64_t code, mcode; + unsigned char *p, *pe; + const intptr_t kMcount = (intptr_t)&mcount; + const intptr_t kProgramCodeStart = (intptr_t)&_ereal; + const intptr_t kPrivilegedStart = (intptr_t)&__privileged_start; + const bool kIsBinaryAligned = !(kPrivilegedStart & (PAGESIZE - 1)); + sigprocmask(SIG_BLOCK, &kSigsetFull, &oldmask); + if (mprotect((void *)symbols->addr_base, + kPrivilegedStart - symbols->addr_base, + kIsBinaryAligned ? PROT_READ | PROT_WRITE + : PROT_READ | PROT_WRITE | PROT_EXEC) != -1) { + for (i = 0; i < symbols->count - 1; ++i) { + if (symbols->addr_base + symbols->symbols[i].addr_rva < + kProgramCodeStart) { + continue; /* skip over real mode symbols */ + } + if (symbols->addr_base + symbols->symbols[i].addr_rva >= + kPrivilegedStart) { + break; /* stop before privileged symbols */ + } + for (p = (unsigned char *)(symbols->addr_base + + symbols->symbols[i].addr_rva), + pe = (unsigned char *)(symbols->addr_base + + symbols->symbols[i + 1].addr_rva); + p < pe - 8; ++p) { + code = read64le(p); + + /* + * Test for -mrecord-mcount (w/ -fpie or -fpic) + * + * nopw 0x00(%rax,%rax,1) ← morphed by package.com + * call *mcount(%rip) ← linked w/o -static + * addr32 call mcount ← relaxed w/ -static + * addr32 call mcount ← relaxed w/ -static + * + * Note that gcc refuses to insert the six byte nop. + */ + if ((code & 0x0000FFFFFFFFFFFF) == 0x0000441F0F66 || + (code & 0x0000FFFFFFFFFFFF) == + ((((kMcount - ((intptr_t)&p[2] + 4)) << 16) | 0xE867) & + 0x0000FFFFFFFFFFFF) || + (code & 0x0000FFFFFFFFFFFF) == + ((((kMcount - ((intptr_t)&p[2] + 4)) << 16) | 0xFF15) & + 0x0000FFFFFFFFFFFF)) { + p[0] = 0x67; + p[1] = 0xE8; + addr = (intptr_t)ifunc - ((intptr_t)&p[2] + 4); + p[2] = addr >> 000; + p[3] = addr >> 010; + p[4] = addr >> 020; + p[5] = addr >> 030; + break; + } + + /* + * Test for -mnop-mcount (w/ -fno-pie) + */ + mcode = code & 0x000000FFFFFFFFFF; + if ((mcode == 0x00441F0F /* nopl 0x00(%eax,%eax,1) [canonical] */) || + (mcode == 0x00041F0F67 /* nopl (%eax,%eax,1) [older gcc] */)) { + if (p[-1] != 0x66 /* nopw 0x0(%rax,%rax,1) [donotwant] */) { + p[0] = 0xE8 /* call Jvds */; + addr = (intptr_t)ifunc - ((intptr_t)&p[1] + 4); + p[1] = addr >> 000; + p[2] = addr >> 010; + p[3] = addr >> 020; + p[4] = addr >> 030; + } + break; + } + } + } + mprotect((void *)symbols->addr_base, kPrivilegedStart - symbols->addr_base, + PROT_READ | PROT_EXEC); + } + sigprocmask(SIG_SETMASK, &oldmask, NULL); +} diff --git a/libc/runtime/internal.h b/libc/runtime/internal.h index d9968d62..48674264 100644 --- a/libc/runtime/internal.h +++ b/libc/runtime/internal.h @@ -1,5 +1,6 @@ #ifndef COSMOPOLITAN_LIBC_RUNTIME_INTERNAL_H_ #define COSMOPOLITAN_LIBC_RUNTIME_INTERNAL_H_ +#ifndef __STRICT_ANSI__ #include "libc/dce.h" #include "libc/runtime/runtime.h" @@ -18,11 +19,12 @@ hidden extern unsigned g_runstate; hidden extern void *g_stacktop; void _init(void) hidden; -void __piro(int) hidden; +void _piro(int) hidden; void *__cxa_finalize(void *) hidden; -int getdosargv(const char16_t *, char *, size_t, char **, size_t) hidden; +void _executive(int, char **, char **, long (*)[2]) hidden noreturn; void __stack_chk_fail(void) noreturn relegated; void __stack_chk_fail_local(void) noreturn relegated hidden; +int getdosargv(const char16_t *, char *, size_t, char **, size_t) hidden; forceinline void AssertNeverCalledWhileTerminating(void) { if (!NoDebug() && (g_runstate & RUNSTATE_TERMINATE)) { @@ -32,4 +34,5 @@ forceinline void AssertNeverCalledWhileTerminating(void) { COSMOPOLITAN_C_END_ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* ANSI */ #endif /* COSMOPOLITAN_LIBC_RUNTIME_INTERNAL_H_ */ diff --git a/libc/runtime/isheap.c b/libc/runtime/isheap.c index c83e6b7a..3ff2f0ce 100644 --- a/libc/runtime/isheap.c +++ b/libc/runtime/isheap.c @@ -17,22 +17,20 @@ │ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/runtime/mappings.h" +#include "libc/runtime/memtrack.h" +#include "libc/runtime/runtime.h" /** - * Returns false if address can't be heap memory. + * Returns true if address isn't stack and was malloc'd or mmap'd. + * + * @assume stack addresses are always greater than heap addresses + * @assume stack memory isn't stored beneath %rsp (-mno-red-zone) */ bool isheap(void *p) { - size_t i; - struct MemoryCoord c; - if (!(kStackBottom <= (intptr_t)p && (intptr_t)p < kStackCeiling)) { - c = ADDRSIZE_TO_COORD(p, FRAMESIZE); - if ((i = findmapping(c.x))) { - return ISOVERLAPPING(_mm.p[i - 1], c); - } else { - return false; - } - } else { - return false; - } + int x, i; + register intptr_t rsp asm("rsp"); + if ((intptr_t)p >= rsp) return false; + x = (intptr_t)p >> 16; + i = FindMemoryInterval(&_mmi, x); + return i < _mmi.i && x >= _mmi.p[i].x && x <= _mmi.p[i].y; } diff --git a/libc/runtime/mapanon.c b/libc/runtime/mapanon.c index e32c4814..504080aa 100644 --- a/libc/runtime/mapanon.c +++ b/libc/runtime/mapanon.c @@ -22,7 +22,7 @@ #include "libc/sysv/consts/map.h" #include "libc/sysv/consts/prot.h" -void *__mapanon(size_t mapsize) { +void *mapanon(size_t mapsize) { return mmap(NULL, mapsize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_NONBLOCK | MAP_ANONYMOUS, -1, 0); } diff --git a/libc/runtime/mappings.h b/libc/runtime/mappings.h deleted file mode 100644 index c8814b1a..00000000 --- a/libc/runtime/mappings.h +++ /dev/null @@ -1,78 +0,0 @@ -#ifndef COSMOPOLITAN_LIBC_RUNTIME_MAPPINGS_H_ -#define COSMOPOLITAN_LIBC_RUNTIME_MAPPINGS_H_ -#include "libc/dce.h" -#include "libc/macros.h" -#include "libc/runtime/runtime.h" - -#define MMAP_MAX 300 /* TODO: crunch */ - -#define kStackCeiling 0x0000700000000000L -#define kStackBottom 0x0000600000000000L - -#define kFixedMappingsStart 0x0000100000000000L /* cosmo won't auto-assign */ -#define kFixedMappingsSize 0x0000100000000000L /* 16TB */ - -#define kMappingsStart 0x0000200000000000L /* cosmo auto-assigns here */ -#define kMappingsSize 0x0000100000000000L /* 16TB */ - -#if !(__ASSEMBLER__ + __LINKER__ + 0) -COSMOPOLITAN_C_START_ - -#define ISOVERLAPPING(C1, C2) \ - (((C1).x >= (C2).x && (C1).x <= (C2).y) || \ - ((C1).y >= (C2).x && (C1).y <= (C2).y)) - -#define ADDR_TO_COORD(ADDR) \ - (int)(((intptr_t)(ADDR) & ~(FRAMESIZE - 1)) / FRAMESIZE) - -#define COORD_TO_ADDR(COORD) (void *)((intptr_t)(COORD)*FRAMESIZE) -#define COORD_TO_SIZE(COORD) (void *)((intptr_t)(COORD)*FRAMESIZE) - -#define ADDRSIZE_TO_COORD(ADDR, SIZE) \ - ((struct MemoryCoord){ \ - .x = ADDR_TO_COORD(ADDR), \ - .y = ADDR_TO_COORD(ADDR) + \ - ((unsigned)(ROUNDUP((SIZE), FRAMESIZE) / FRAMESIZE) - 1)}) - -#define COORD_TO_ADDRSIZE(COORD) \ - ((struct AddrSize){ \ - .addr = COORD_TO_ADDR((COORD).x), \ - .size = ((size_t)((COORD).y - (COORD).x + 1) * FRAMESIZE)}) - -#define GRANULATE_ADDRSIZE(ADDR, SIZE) \ - do { \ - struct AddrSize AdSiz; \ - struct MemoryCoord MemCo; \ - MemCo = ADDRSIZE_TO_COORD(*(ADDR), *(SIZE)); \ - AdSiz = COORD_TO_ADDRSIZE(MemCo); \ - *(ADDR) = AdSiz.addr; \ - *(SIZE) = AdSiz.size; \ - } while (0) - -struct AddrSize { - void *addr; - size_t size; -}; - -/** - * Ordered inclusive 64kb-granular ranges on NexGen32e w/o PML5. - * c.𝑥 ≤ c.𝑦 so say c all. - * cₙ.𝑥 ≤ cₙ₊₁.𝑥 so say c all. - */ -struct Mappings { - size_t i; - struct MemoryCoord { - int32_t x, y; - } p[MMAP_MAX]; - int64_t h[MMAP_MAX]; -}; - -extern struct Mappings _mm; - -bool isheap(void *); -size_t findmapping(int32_t); -size_t findmapping_(int32_t, const struct MemoryCoord *, size_t); - -COSMOPOLITAN_C_END_ -#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ -#endif /* COSMOPOLITAN_LIBC_RUNTIME_MAPPINGS_H_ */ diff --git a/libc/runtime/memtrack.c b/libc/runtime/memtrack.c new file mode 100644 index 00000000..33fc2944 --- /dev/null +++ b/libc/runtime/memtrack.c @@ -0,0 +1,117 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/assert.h" +#include "libc/macros.h" +#include "libc/runtime/memtrack.h" +#include "libc/runtime/runtime.h" +#include "libc/str/str.h" +#include "libc/sysv/errfuns.h" + +static void RemoveMemoryIntervals(struct MemoryIntervals *mm, int i, int n) { + assert(i >= 0); + assert(i + n <= mm->i); + memcpy(mm->p + i, mm->p + i + n, + (intptr_t)(mm->p + mm->i) - (intptr_t)(mm->p + i + n)); + memcpy(mm->h + i, mm->h + i + n, + (intptr_t)(mm->h + mm->i) - (intptr_t)(mm->h + i + n)); + mm->i -= n; +} + +static void CreateMemoryInterval(struct MemoryIntervals *mm, int i) { + assert(i >= 0); + assert(i <= mm->i); + assert(mm->i < ARRAYLEN(mm->p)); + memmove(mm->p + i + 1, mm->p + i, + (intptr_t)(mm->p + mm->i) - (intptr_t)(mm->p + i)); + memmove(mm->h + i + 1, mm->h + i, + (intptr_t)(mm->h + mm->i) - (intptr_t)(mm->h + i)); + ++mm->i; +} + +static int PunchHole(struct MemoryIntervals *mm, int x, int y, int i) { + if (mm->i == ARRAYLEN(mm->p)) return enomem(); + CreateMemoryInterval(mm, i); + mm->p[i].y = x - 1; + mm->p[i + 1].x = y + 1; + return 0; +} + +int ReleaseMemoryIntervals(struct MemoryIntervals *mm, int x, int y, + void wincb(struct MemoryIntervals *, int, int)) { + unsigned l, r; + assert(y >= x); + assert(AreMemoryIntervalsOk(mm)); + if (!mm->i) return 0; + l = FindMemoryInterval(mm, x); + if (l == mm->i) return 0; + if (!l && y < mm->p[l].x) return 0; + if (y < mm->p[l].x) return 0; + r = FindMemoryInterval(mm, y); + if (r == mm->i || (r > l && y < mm->p[r].x)) --r; + assert(r >= l); + assert(x <= mm->p[r].y); + if (l == r && x > mm->p[l].x && y < mm->p[l].y) { + return PunchHole(mm, x, y, l); + } + if (x > mm->p[l].x && x <= mm->p[l].y) { + assert(y >= mm->p[l].y); + if (IsWindows()) return einval(); + mm->p[l].y = x - 1; + assert(mm->p[l].x <= mm->p[l].y); + ++l; + } + if (y >= mm->p[r].x && y < mm->p[r].y) { + assert(x <= mm->p[r].x); + if (IsWindows()) return einval(); + mm->p[r].x = y + 1; + assert(mm->p[r].x <= mm->p[r].y); + --r; + } + if (l <= r) { + if (IsWindows() && wincb) { + wincb(mm, l, r); + } + RemoveMemoryIntervals(mm, l, r - l + 1); + } + return 0; +} + +int TrackMemoryInterval(struct MemoryIntervals *mm, int x, int y, long h) { + unsigned i; + assert(y >= x); + assert(AreMemoryIntervalsOk(mm)); + i = FindMemoryInterval(mm, x); + if (i && x == mm->p[i - 1].y + 1 && h == mm->h[i - 1]) { + mm->p[i - 1].y = y; + if (i < mm->i && y + 1 == mm->p[i].x && h == mm->h[i]) { + mm->p[i - 1].y = mm->p[i].y; + RemoveMemoryIntervals(mm, i, 1); + } + } else if (i < mm->i && y + 1 == mm->p[i].x && h == mm->h[i]) { + mm->p[i].x = x; + } else { + if (mm->i == ARRAYLEN(mm->p)) return enomem(); + CreateMemoryInterval(mm, i); + mm->p[i].x = x; + mm->p[i].y = y; + mm->h[i] = h; + } + return 0; +} diff --git a/libc/runtime/memtrack.h b/libc/runtime/memtrack.h new file mode 100644 index 00000000..507ab768 --- /dev/null +++ b/libc/runtime/memtrack.h @@ -0,0 +1,33 @@ +#ifndef COSMOPOLITAN_LIBC_RUNTIME_MEMTRACK_H_ +#define COSMOPOLITAN_LIBC_RUNTIME_MEMTRACK_H_ +#include "libc/nexgen32e/vendor.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +#define kMappingsSize 0x100000000000 /* 16TB */ +#define kMappingsStart (IsGenuineCosmo() ? 0x300000000000 : 0x200000000000) +#define kFixedMappingsStart 0x0000100000000000 +#define kFixedMappingsSize kMappingsSize + +struct MemoryIntervals { + int i; + struct MemoryInterval { + int x; + int y; + } p[32]; + long h[32]; +}; + +extern struct MemoryIntervals _mmi; + +unsigned FindMemoryInterval(const struct MemoryIntervals *, int) nosideeffect; +bool AreMemoryIntervalsOk(const struct MemoryIntervals *) nosideeffect; +void PrintMemoryIntervals(int, const struct MemoryIntervals *); +int TrackMemoryInterval(struct MemoryIntervals *, int, int, long); +int ReleaseMemoryIntervals(struct MemoryIntervals *, int, int, + void (*)(struct MemoryIntervals *, int, int)); +void ReleaseMemoryNt(struct MemoryIntervals *, int, int); + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_RUNTIME_MEMTRACK_H_ */ diff --git a/libc/runtime/memtracknt.c b/libc/runtime/memtracknt.c new file mode 100644 index 00000000..eb738e75 --- /dev/null +++ b/libc/runtime/memtracknt.c @@ -0,0 +1,41 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/assert.h" +#include "libc/nt/memory.h" +#include "libc/nt/runtime.h" +#include "libc/runtime/memtrack.h" +#include "libc/runtime/runtime.h" + +static void *GetFrameAddr(int f) { + intptr_t a; + a = f; + a *= FRAMESIZE; + return (void *)a; +} + +void ReleaseMemoryNt(struct MemoryIntervals *mm, int l, int r) { + int i, ok; + for (i = l; i <= r; ++i) { + ok = UnmapViewOfFile(GetFrameAddr(mm->p[i].x)); + assert(ok); + ok = CloseHandle(mm->h[i]); + assert(ok); + } +} diff --git a/libc/runtime/missioncritical.h b/libc/runtime/missioncritical.h index 047e0718..fe437a70 100644 --- a/libc/runtime/missioncritical.h +++ b/libc/runtime/missioncritical.h @@ -1,9 +1,11 @@ #ifndef COSMOPOLITAN_LIBC_INTERNAL_MISSIONCRITICAL_H_ #define COSMOPOLITAN_LIBC_INTERNAL_MISSIONCRITICAL_H_ +#ifndef __STRICT_ANSI__ #include "libc/bits/bits.h" #include "libc/calls/internal.h" #include "libc/dce.h" #include "libc/nexgen32e/nexgen32e.h" +#include "libc/nexgen32e/tinystrlen.h" #include "libc/nt/console.h" #include "libc/nt/enum/version.h" #include "libc/nt/ntdll.h" @@ -140,4 +142,5 @@ interruptfn void __print(const void *, size_t); #define RESTORE_RBX() /* disabled for now b/c clang */ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* !ANSI */ #endif /* COSMOPOLITAN_LIBC_INTERNAL_MISSIONCRITICAL_H_ */ diff --git a/libc/runtime/mmap.c b/libc/runtime/mmap.c index b9ed6d28..5dc21f38 100644 --- a/libc/runtime/mmap.c +++ b/libc/runtime/mmap.c @@ -24,22 +24,33 @@ #include "libc/macros.h" #include "libc/nt/memory.h" #include "libc/nt/runtime.h" -#include "libc/runtime/mappings.h" +#include "libc/rand/rand.h" +#include "libc/runtime/memtrack.h" +#include "libc/runtime/runtime.h" #include "libc/str/str.h" #include "libc/sysv/consts/map.h" +#include "libc/sysv/consts/prot.h" #include "libc/sysv/errfuns.h" -#define VIP(X) (void *)(intptr_t)(X) +#define IP(X) (intptr_t)(X) +#define VIP(X) (void *)IP(X) +#define COORD(a) (int)(IP(a) >> 16) +#define ADDR(c) (void *)(IP(c) << 16) +#define ALIGNED(p) (!(IP(p) & (FRAMESIZE - 1))) +#define CANONICAL(p) (-0x800000000000 <= IP(p) && IP(p) <= 0x7fffffffffff) +#define LAST_COORD(a, n) (COORD(a) + (ROUNDUP(n, FRAMESIZE) >> 16) - 1) struct DirectMap { void *addr; int64_t maphandle; }; -static textwindows struct DirectMap directmap$nt(void *addr, size_t size, - unsigned prot, unsigned flags, - int fd, int64_t off) { - struct DirectMap res; +struct MemoryIntervals _mmi; + +static textwindows struct DirectMap DirectMapNt(void *addr, size_t size, + unsigned prot, unsigned flags, + int fd, int64_t off) { + struct DirectMap res; /* NT IS TORTURE */ if ((res.maphandle = CreateFileMappingNuma( fd != -1 ? g_fds.p[fd].handle : kNtInvalidHandleValue, &kNtIsInheritable, prot2nt(prot, flags), size >> 32, size, NULL, @@ -58,22 +69,48 @@ static textwindows struct DirectMap directmap$nt(void *addr, size_t size, return res; } -static struct DirectMap directmap(void *addr, size_t size, unsigned prot, +static struct DirectMap DirectMap(void *addr, size_t size, unsigned prot, unsigned flags, int fd, int64_t off) { if (!IsWindows()) { return (struct DirectMap){mmap$sysv(addr, size, prot, flags, fd, off), kNtInvalidHandleValue}; } else { - return directmap$nt(addr, size, prot, flags, fd, off); + return DirectMapNt(addr, size, prot, flags, fd, off); } } +static int UntrackMemoryIntervals(void *addr, size_t size) { + return ReleaseMemoryIntervals(&_mmi, COORD(addr), LAST_COORD(addr, size), + ReleaseMemoryNt); +} + +/** + * Releases memory pages. + * + * @param addr is a pointer within any memory mapped region the process + * has permission to control, such as address ranges returned by + * mmap(), the program image itself, etc. + * @param size is the amount of memory to unmap, which needn't be a + * multiple of FRAMESIZE, and may be a subset of that which was + * mapped previously, and may punch holes in existing mappings, + * but your mileage may vary on windows + * @return 0 on success, or -1 w/ errno + */ +int munmap(void *addr, size_t size) { + int rc; + if (!ALIGNED(addr) || !CANONICAL(addr) || !size) return einval(); + size = ROUNDUP(size, FRAMESIZE); + if (UntrackMemoryIntervals(addr, size) == -1) return -1; + if (IsWindows()) return 0; + return munmap$sysv(addr, size); +} + /** * Beseeches system for page-table entries. * * @param addr optionally requests a particular virtual base address, * which needs to be 64kb aligned if passed (for NT compatibility) - * @param size should be >0 and multiple of PAGESIZE + * @param size must be >0 and needn't be a multiple of FRAMESIZE * @param prot can have PROT_READ, PROT_WRITE, PROT_EXEC, PROT_NONE, etc. * @param flags can have MAP_ANONYMOUS, MAP_SHARED, MAP_PRIVATE, etc. * @param fd is an open()'d file descriptor whose contents shall be @@ -83,73 +120,48 @@ static struct DirectMap directmap(void *addr, size_t size, unsigned prot, * @return virtual base address of new mapping, or MAP_FAILED w/ errno */ void *mmap(void *addr, size_t size, int prot, int flags, int fd, int64_t off) { - size_t i; - intptr_t p; + int i; + long gap; struct DirectMap dm; - struct MemoryCoord c; - p = (intptr_t)addr; - - assert(!(0 < p && p < 0x200000)); - assert(-0x800000000000L <= p && p <= 0x7fffffffffffL); - assert((flags & MAP_PRIVATE) ^ (flags & MAP_SHARED)); - assert((flags & MAP_ANONYMOUS) ^ (fd != -1)); - assert(off % PAGESIZE == 0); - assert(size > 0); - - if (!(IsWindows() && fd != -1)) { - size = ROUNDUP(size, FRAMESIZE); - } - + if (!size) return VIP(einval()); + if (!ALIGNED(off)) return VIP(einval()); + if (!ALIGNED(addr)) return VIP(einval()); + if (!CANONICAL(addr)) return VIP(einval()); + if (!(!!(flags & MAP_ANONYMOUS) ^ (fd != -1))) return VIP(einval()); + if (!(!!(flags & MAP_PRIVATE) ^ !!(flags & MAP_SHARED))) return VIP(einval()); + if (!(IsWindows() && fd != -1)) size = ROUNDUP(size, FRAMESIZE); if (flags & MAP_FIXED) { - assert(addr != NULL); - assert((intptr_t)addr % FRAMESIZE == 0); - } else { - if (!addr) { - if (_mm.i) { - addr = COORD_TO_ADDR(_mm.p[_mm.i - 1].y + 1); - for (i = _mm.i; i; --i) { - if (_mm.p[i - 1].y + 1 + size / FRAMESIZE <= - ADDR_TO_COORD(kMappingsStart + kMappingsSize) && - _mm.p[i - 1].y + 1 >= ADDR_TO_COORD(kMappingsStart)) { - addr = COORD_TO_ADDR(_mm.p[i - 1].y + 1); - break; - } + if (UntrackMemoryIntervals(addr, size) == -1) { + return MAP_FAILED; + } + } else if (_mmi.i) { + if (0 && IsModeDbg()) { + addr = VIP(rand64() & 0x00007ffffffff000); + } else { + for (i = _mmi.i - 1; i > 0; --i) { + gap = _mmi.p[i].x - _mmi.p[i - 1].y - 1; + assert(gap > 0); + if (gap >= (ROUNDUP(size, FRAMESIZE) >> 16)) { + addr = ADDR(_mmi.p[i - 1].y + 1); + break; } - } else { - addr = VIP(kMappingsStart); + } + if (!addr) { + addr = ADDR(_mmi.p[_mmi.i - 1].y + 1); } } - addr = (void *)ROUNDDOWN((intptr_t)addr, FRAMESIZE); - } - - if (_mm.i == MMAP_MAX) { - return VIP(enomem()); - } - - if (flags & MAP_FIXED) { - munmap(addr, size); } else { - c = ADDRSIZE_TO_COORD(addr, size); - if ((i = findmapping(c.y)) && ISOVERLAPPING(c, _mm.p[i - 1])) { - return VIP(einval()); - } + addr = VIP(kMappingsStart); } - - dm = directmap(addr, size, prot, flags | MAP_FIXED, fd, off); - if (dm.addr == MAP_FAILED) return MAP_FAILED; - - i = findmapping(ADDR_TO_COORD(dm.addr)); - if (i < _mm.i) { - memmove(&_mm.p[i + 1], &_mm.p[i], - (intptr_t)&_mm.p[_mm.i] - (intptr_t)&_mm.p[i]); - memmove(&_mm.h[i + 1], &_mm.h[i], - (intptr_t)&_mm.h[_mm.i] - (intptr_t)&_mm.h[i]); + assert((flags & MAP_FIXED) || + (!isheap(addr) && !isheap((char *)addr + size - 1))); + dm = DirectMap(addr, size, prot, flags | MAP_FIXED, fd, off); + if (dm.addr == MAP_FAILED || dm.addr != addr) { + return MAP_FAILED; + } + if (TrackMemoryInterval(&_mmi, COORD(dm.addr), LAST_COORD(dm.addr, size), + dm.maphandle) == -1) { + _Exit(1); } - - _mm.p[i] = ADDRSIZE_TO_COORD(dm.addr, size); - _mm.h[i] = dm.maphandle; - _mm.i++; - - assert((intptr_t)dm.addr % __BIGGEST_ALIGNMENT__ == 0); return dm.addr; } diff --git a/libc/runtime/msync-nt.c b/libc/runtime/msync-nt.c index 55a30cbc..db32f80a 100644 --- a/libc/runtime/msync-nt.c +++ b/libc/runtime/msync-nt.c @@ -18,19 +18,24 @@ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/internal.h" +#include "libc/macros.h" #include "libc/nt/files.h" #include "libc/nt/memory.h" -#include "libc/runtime/mappings.h" +#include "libc/runtime/memtrack.h" textwindows int msync$nt(void *addr, size_t size, int flags) { - size_t i, j; - struct MemoryCoord c; + int x, y, l, r, i; if (!FlushViewOfFile(addr, size)) return winerr(); - j = findmapping(ADDR_TO_COORD(addr)); - c = ADDRSIZE_TO_COORD(addr, size); - for (i = j; i; --i) { - if (!ISOVERLAPPING(_mm.p[i - 1], c)) break; - FlushFileBuffers(_mm.h[i - 1]); + x = (intptr_t)addr >> 16; + y = x + (ROUNDUP(size, 65536) >> 16) - 1; + l = FindMemoryInterval(&_mmi, x); + r = FindMemoryInterval(&_mmi, y); + if (l && x <= _mmi.p[l - 1].y) --l; + if (r && y <= _mmi.p[r - 1].y) --r; + if (l < _mmi.i) { + for (i = l; i <= r; --i) { + FlushFileBuffers(_mmi.h[i - 1]); + } } return 0; } diff --git a/libc/runtime/msync.c b/libc/runtime/msync.c index f02f1f01..27c53e03 100644 --- a/libc/runtime/msync.c +++ b/libc/runtime/msync.c @@ -22,7 +22,6 @@ #include "libc/calls/internal.h" #include "libc/dce.h" #include "libc/macros.h" -#include "libc/runtime/mappings.h" #include "libc/sysv/consts/msync.h" /** diff --git a/libc/runtime/munmap.c b/libc/runtime/munmap.c deleted file mode 100644 index e7865cc2..00000000 --- a/libc/runtime/munmap.c +++ /dev/null @@ -1,106 +0,0 @@ -/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ -│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ -╞══════════════════════════════════════════════════════════════════════════════╡ -│ Copyright 2020 Justine Alexandra Roberts Tunney │ -│ │ -│ This program is free software; you can redistribute it and/or modify │ -│ it under the terms of the GNU General Public License as published by │ -│ the Free Software Foundation; version 2 of the License. │ -│ │ -│ This program is distributed in the hope that it will be useful, but │ -│ WITHOUT ANY WARRANTY; without even the implied warranty of │ -│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ -│ General Public License for more details. │ -│ │ -│ You should have received a copy of the GNU General Public License │ -│ along with this program; if not, write to the Free Software │ -│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ -│ 02110-1301 USA │ -╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/assert.h" -#include "libc/calls/calls.h" -#include "libc/calls/internal.h" -#include "libc/dce.h" -#include "libc/nt/memory.h" -#include "libc/nt/runtime.h" -#include "libc/runtime/mappings.h" -#include "libc/str/str.h" -#include "libc/sysv/consts/fileno.h" - -/** - * Releases memory pages. - * - * @param addr is a pointer within any memory mapped region the process - * has permission to control, such as address ranges returned by - * mmap(), the program image itself, etc. - * @param size is the amount of memory to unmap, which should be a - * multiple of PAGESIZE, and may be a subset of that which was - * mapped previously - * @return 0 on success, or -1 w/ errno - */ -int __munmap(void *addr, size_t size) { - int rc; - intptr_t p; - size_t i, j; - struct AddrSize a; - struct MemoryCoord c, m; - - p = (intptr_t)addr; - assert(!(0 < p && p < 0x200000)); - assert(-0x800000000000L <= p && p <= 0x7fffffffffffL); - - if (!size) return 0; - if (!addr || addr == MAP_FAILED) return 0; - if (addr == NULL && size <= 0x200000) return 0; - - addr = (void *)ROUNDDOWN((intptr_t)addr, FRAMESIZE); - size = ROUNDUP(size, FRAMESIZE); - - rc = 0; - c = ADDRSIZE_TO_COORD(addr, size); - j = findmapping(c.y); - for (i = j; i; --i) { - m = _mm.p[i - 1]; - assert(m.x <= m.y); - assert(c.y >= m.x); - if (c.x > m.y) { - break; - } else if (c.x > m.x && c.y < m.y) { - /* remove middle portion */ - assert(!"map hole punching not implemented"); - } else if (c.x > m.x && c.y >= m.y) { - /* remove righthand portion */ - assert(!"map hole punching not implemented"); - /* _mm.p[i - 1].y = c.x - 1; */ - /* i++; */ - /* break; */ - } else if (c.x <= m.x && c.y < m.y) { - /* remove lefthand portion */ - assert(!"map hole punching not implemented"); - /* _mm.p[i - 1].x = c.y + 1; */ - /* j--; */ - } else if ((m.x >= c.x && m.x <= c.y) && (m.y >= c.x && m.y <= c.y)) { - a = COORD_TO_ADDRSIZE(m); - if (!IsWindows()) { - rc |= munmap$sysv(a.addr, a.size); - } else { - if (!UnmapViewOfFile(a.addr)) rc = -1; - if (!CloseHandle(_mm.h[i - 1])) rc = -1; - } - } else { - assert(!"wut"); - } - } - - if (i < j) { - if (j < _mm.i) { - memmove(&_mm.p[i], &_mm.p[j], - (intptr_t)&_mm.p[_mm.i] - (intptr_t)&_mm.p[j]); - memmove(&_mm.h[i], &_mm.h[j], - (intptr_t)&_mm.h[_mm.i] - (intptr_t)&_mm.h[j]); - } - _mm.i -= j - i; - } - - return rc; -} diff --git a/libc/runtime/munmap_s.c b/libc/runtime/munmap_s.c index fe7097df..7fc1d6ae 100644 --- a/libc/runtime/munmap_s.c +++ b/libc/runtime/munmap_s.c @@ -20,11 +20,10 @@ #include "libc/bits/bits.h" #include "libc/bits/pushpop.h" #include "libc/calls/calls.h" -#include "libc/runtime/mappings.h" #include "libc/runtime/runtime.h" /** - * Closes memory mapping, the Cosmopolitan way. + * Closes memory mapping. * * The caller's address holder is set to MAP_FAILED (-1) which is a * no-op for subsequent invocations. diff --git a/libc/runtime/opensymboltable.c b/libc/runtime/opensymboltable.c index 8033753d..b915fb64 100644 --- a/libc/runtime/opensymboltable.c +++ b/libc/runtime/opensymboltable.c @@ -21,6 +21,7 @@ #include "libc/calls/calls.h" #include "libc/elf/def.h" #include "libc/elf/elf.h" +#include "libc/runtime/carsort.h" #include "libc/runtime/runtime.h" #include "libc/runtime/symbols.h" #include "libc/sysv/consts/map.h" @@ -32,18 +33,19 @@ * @return object freeable with closesymboltable(), or NULL w/ errno */ struct SymbolTable *opensymboltable(const char *filename) { - struct SymbolTable *t = MAP_FAILED; - const Elf64_Sym *symtab; + unsigned i, j; + struct SymbolTable *t; + const Elf64_Sym *symtab, *sym; + t = MAP_FAILED; if (filename && (t = mapanon(BIGPAGESIZE)) != MAP_FAILED && mapelfread(filename, &t->mf) && (t->name_base = getelfstringtable(t->elf, t->elfsize)) != NULL && (symtab = getelfsymboltable(t->elf, t->elfsize, &t->count)) && sizeof(struct SymbolTable) + sizeof(struct Symbol) * t->count < (t->scratch = BIGPAGESIZE)) { - unsigned j = 0; getelfvirtualaddressrange(t->elf, t->elfsize, &t->addr_base, &t->addr_end); - for (unsigned i = 0; i < t->count; ++i) { - const Elf64_Sym *sym = &symtab[i]; + for (j = i = 0; i < t->count; ++i) { + sym = &symtab[i]; if (iselfsymbolcontent(sym) && (sym->st_value >= t->addr_base && sym->st_value <= t->addr_end)) { t->symbols[j].addr_rva = (unsigned)(sym->st_value - t->addr_base); @@ -52,7 +54,7 @@ struct SymbolTable *opensymboltable(const char *filename) { } } t->count = j; - heapsortcar((int32_t(*)[2])t->symbols, t->count); + carsort1000(t->count, (void *)t->symbols); } else { closesymboltable(&t); } diff --git a/libc/runtime/piro.c b/libc/runtime/piro.c index 84f2791d..1e3248af 100644 --- a/libc/runtime/piro.c +++ b/libc/runtime/piro.c @@ -33,17 +33,16 @@ ╠──────────────────────────────────────────────────────▌▀▄─▐──▀▄─▐▄─▐▄▐▄─▐▄─▐▄─│ │ αcτµαlly pδrταblε εxεcµταblε § post-initialization read-only │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/bits/bits.h" #include "libc/bits/safemacros.h" +#include "libc/bits/weaken.h" #include "libc/calls/calls.h" -#include "libc/dce.h" #include "libc/runtime/internal.h" #include "libc/runtime/runtime.h" #include "libc/sysv/consts/prot.h" #define getaddr(section) ((intptr_t)weakaddr(section)) -static textstartup void __piro_protect(intptr_t start, intptr_t end, int prot) { +static textstartup void _piro_protect(intptr_t start, intptr_t end, int prot) { ssize_t len = end - start; if (len > 0 && start && start % PAGESIZE == 0 && len % PAGESIZE == 0) { if (mprotect((void *)(unsigned long)start, len, prot) == -1) abort(); @@ -62,10 +61,10 @@ static textstartup void __piro_protect(intptr_t start, intptr_t end, int prot) { * @see ape/ape.lds * @see libc/_start.S */ -textstartup void __piro(int prot) { +textstartup void _piro(int prot) { if (getaddr("main") < getaddr("__test_start")) { - __piro_protect(getaddr("__test_start"), getaddr("__test_end"), PROT_NONE); + _piro_protect(getaddr("__test_start"), getaddr("__test_end"), PROT_NONE); } - __piro_protect(getaddr("__ro"), getaddr("_etext"), PROT_READ); - __piro_protect(getaddr("__piro_start"), getaddr("__piro_end"), prot); + _piro_protect(getaddr("__ro"), getaddr("_etext"), PROT_READ); + _piro_protect(getaddr("__piro_start"), getaddr("__piro_end"), prot); } diff --git a/libc/runtime/printmemoryintervals.c b/libc/runtime/printmemoryintervals.c new file mode 100644 index 00000000..9689ffab --- /dev/null +++ b/libc/runtime/printmemoryintervals.c @@ -0,0 +1,45 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/calls.h" +#include "libc/log/log.h" +#include "libc/runtime/memtrack.h" + +STATIC_YOINK("ntoa"); +STATIC_YOINK("stoa"); + +void PrintMemoryIntervals(int fd, const struct MemoryIntervals *mm) { + int i, frames, maptally, gaptally; + maptally = 0; + gaptally = 0; + (dprintf)(fd, "%s%zd%s\n", "mm->i == ", mm->i, ";"); + for (i = 0; i < mm->i; ++i) { + if (i && mm->p[i].x != mm->p[i - 1].y + 1) { + frames = mm->p[i].x - mm->p[i - 1].y - 1; + gaptally += frames; + (dprintf)(fd, "%s%,zd%s\n", "/* ", frames, " */"); + } + frames = mm->p[i].y + 1 - mm->p[i].x; + maptally += frames; + (dprintf)(fd, "%s%3u%s0x%08x,0x%08x%s%,zd%s\n", "mm->p[", i, "]=={", + mm->p[i].x, mm->p[i].y, "}; /* ", frames, " */"); + } + (dprintf)(fd, "%s%,zd%s%,zd%s\n\n", "/* ", maptally, " frames mapped w/ ", + gaptally, " frames gapped */"); +} diff --git a/libc/runtime/quick_exit.c b/libc/runtime/quick_exit.c index a45e0bcc..4d3de77e 100644 --- a/libc/runtime/quick_exit.c +++ b/libc/runtime/quick_exit.c @@ -17,7 +17,7 @@ │ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/bits/bits.h" +#include "libc/bits/weaken.h" #include "libc/runtime/runtime.h" #include "libc/stdio/stdio.h" diff --git a/libc/runtime/relocate.c b/libc/runtime/relocate.c new file mode 100644 index 00000000..3192e9d5 --- /dev/null +++ b/libc/runtime/relocate.c @@ -0,0 +1,42 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/dce.h" +#include "libc/runtime/runtime.h" + +/** + * Applies fixups to 64-bit rela addresses in binary. + */ +void __relocate(void) { + ptrdiff_t skew; + unsigned char **p; + p = __relo_start; + if (p != __relo_end) { + skew = (intptr_t)p - (intptr_t)*p; + do { + if (*p) { + *p += skew; + if (!NoDebug() && !(_base <= *p && *p < _end)) { + asm("int3"); + } + } + } while (++p != __relo_end); + } + return; +} diff --git a/libc/runtime/runtime.h b/libc/runtime/runtime.h index 4d206263..51ff12fc 100644 --- a/libc/runtime/runtime.h +++ b/libc/runtime/runtime.h @@ -7,6 +7,8 @@ COSMOPOLITAN_C_START_ │ cosmopolitan § runtime ─╬─│┼ ╚────────────────────────────────────────────────────────────────────────────│*/ +struct SymbolTable; + struct StackFrame { struct StackFrame *next; intptr_t addr; @@ -14,31 +16,31 @@ struct StackFrame { typedef long jmp_buf[8] aligned(CACHELINE); -extern int g_argc; /* CRT */ -extern char **g_argv; /* CRT */ -extern char **environ; /* CRT */ -extern unsigned long *g_auxv; /* CRT */ -extern jmp_buf g_winmain; /* CRT */ -extern char *program_invocation_name; /* RII */ -extern char *program_invocation_short_name; /* RII */ -extern uint64_t g_syscount; /* RII */ -extern const uint64_t kStartTsc; /* RII */ -extern const char kTmpPath[]; /* RII */ -extern const char kNtSystemDirectory[]; /* RII */ -extern const char kNtWindowsDirectory[]; /* RII */ -extern unsigned char _base[] aligned(PAGESIZE); /* αpε */ -extern char _ehead aligned(PAGESIZE); /* αpε */ -extern char _ereal; /* αpε */ -extern char __privileged_start; /* αpε */ -extern char __test_start; /* αpε */ -extern char __ro; /* αpε */ -extern char _etext aligned(PAGESIZE); /* αpε */ -extern char __piro_start; /* αpε */ -extern char _edata aligned(PAGESIZE); /* αpε */ -extern char __piro_end; /* αpε */ -extern char _end aligned(PAGESIZE); /* αpε */ -extern uint8_t __zip_start[]; /* αpε */ -extern uint8_t __zip_end[]; /* αpε */ +extern int g_argc; /* CRT */ +extern char **g_argv; /* CRT */ +extern char **environ; /* CRT */ +extern unsigned long *g_auxv; /* CRT */ +extern jmp_buf g_winmain; /* CRT */ +extern char *program_invocation_name; /* RII */ +extern char *program_invocation_short_name; /* RII */ +extern uint64_t g_syscount; /* RII */ +extern const uint64_t kStartTsc; /* RII */ +extern const char kTmpPath[]; /* RII */ +extern const char kNtSystemDirectory[]; /* RII */ +extern const char kNtWindowsDirectory[]; /* RII */ +extern unsigned char _base[] aligned(PAGESIZE); /* αpε */ +extern unsigned char _ehead[] aligned(PAGESIZE); /* αpε */ +extern unsigned char _etext[] aligned(PAGESIZE); /* αpε */ +extern unsigned char _edata[] aligned(PAGESIZE); /* αpε */ +extern unsigned char _end[] aligned(PAGESIZE); /* αpε */ +extern unsigned char _ereal; /* αpε */ +extern unsigned char __privileged_start; /* αpε */ +extern unsigned char __test_start; /* αpε */ +extern unsigned char __ro; /* αpε */ +extern unsigned char *__relo_start[]; /* αpε */ +extern unsigned char *__relo_end[]; /* αpε */ +extern uint8_t __zip_start[]; /* αpε */ +extern uint8_t __zip_end[]; /* αpε */ long missingno(); void mcount(void); @@ -70,6 +72,8 @@ void loadxmm(void *); void peekall(void); int issetugid(void); void weakfree(void *) libcesque; +void __hook(void (*)(void), struct SymbolTable *); +bool isheap(void *); /*───────────────────────────────────────────────────────────────────────────│─╗ │ cosmopolitan § runtime » optimizations ─╬─│┼ diff --git a/libc/runtime/runtime.mk b/libc/runtime/runtime.mk index a34c41bd..e0aaddf0 100644 --- a/libc/runtime/runtime.mk +++ b/libc/runtime/runtime.mk @@ -61,6 +61,8 @@ $(LIBC_RUNTIME_A).pkg: \ o/$(MODE)/libc/runtime/asan.greg.o \ o/$(MODE)/libc/runtime/shadowargs.o \ +o/$(MODE)/libc/runtime/hook.greg.o \ +o/$(MODE)/libc/runtime/ftrace.greg.o \ o/$(MODE)/libc/runtime/__stack_chk_fail.o \ o/$(MODE)/libc/runtime/__stack_chk_guard.o: \ OVERRIDE_COPTS += \ diff --git a/libc/runtime/spawn.S b/libc/runtime/spawn.S new file mode 100644 index 00000000..701fe60a --- /dev/null +++ b/libc/runtime/spawn.S @@ -0,0 +1,51 @@ +/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│ +│vi: set et ft=asm ts=8 tw=8 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/dce.h" +#include "libc/sysv/consts/prot.h" +#include "libc/macros.h" + +/ Self-bootstraps process upon existence before calling main. +_spawn: push %rbp + mov %rsp,%rbp + +/ Tune FPU settings if -ffast-math is somehow used systemically. +#ifdef __FAST_MATH__ + call __fast_math +#endif + +/ Call decentralized initialization assembly. + call _init +#if IsModeDbg() + call _init # _init() is idempotent +#endif + +/ Call global initialization functions. + call _construct + +/ Restricts .initbss memory so it's read-only after initialization. +/ TODO: Delete this unless there's measurable performance advantage. +#if !IsTrustworthy() + mov $PROT_READ,%edi + call _piro +#endif + + pop %rbp + ret + .endfn _spawn,globl diff --git a/libc/runtime/sysconf.h b/libc/runtime/sysconf.h index b7d7b6f9..0afd83ea 100644 --- a/libc/runtime/sysconf.h +++ b/libc/runtime/sysconf.h @@ -13,8 +13,8 @@ COSMOPOLITAN_C_START_ long sysconf(int); +#if defined(__GNUC__) && !defined(__STRICT_ANSI__) #define sysconf(X) __sysconf(X) - forceinline long __sysconf(int thing) { switch (thing) { case _SC_ARG_MAX: @@ -31,6 +31,7 @@ forceinline long __sysconf(int thing) { return -1; } } +#endif /* GNU && !ANSI */ COSMOPOLITAN_C_END_ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ diff --git a/libc/runtime/winmain.greg.c b/libc/runtime/winmain.greg.c index 3bd64bc4..8165e93d 100644 --- a/libc/runtime/winmain.greg.c +++ b/libc/runtime/winmain.greg.c @@ -25,6 +25,7 @@ #include "libc/nt/runtime.h" #include "libc/runtime/getdosenviron.h" #include "libc/runtime/internal.h" +#include "libc/runtime/missioncritical.h" static void LoadFasterAndPreventHijacking(void) { unsigned wrote; @@ -34,14 +35,15 @@ static void LoadFasterAndPreventHijacking(void) { } } -noreturn textwindows int WinMain(void *hInstance, void *hPrevInstance, - const char *lpCmdLine, int nCmdShow) { +textwindows int WinMain(void *hInstance, void *hPrevInstance, + const char *lpCmdLine, int nCmdShow) { int i, count; const char16_t *cmd16, *env16; + char *argarray[512], *envarray[512]; + char argblock[ARG_MAX], envblock[ENV_MAX]; long auxarray[][2] = {{pushpop(0L), pushpop(0L)}}; - char envblock[ENV_MAX], *envarray[512], argblock[ARG_MAX], *argarray[512]; LoadFasterAndPreventHijacking(); - *(/*unconst*/ int *)&__hostos = pushpop(WINDOWS); + *(/*unconst*/ int *)&hostos = WINDOWS; cmd16 = GetCommandLine(); env16 = GetEnvironmentStrings(); count = getdosargv(cmd16, argblock, ARG_MAX, argarray, 512); @@ -50,13 +52,5 @@ noreturn textwindows int WinMain(void *hInstance, void *hPrevInstance, } getdosenviron(env16, envblock, ENV_MAX, envarray, 512); FreeEnvironmentStrings(env16); - register int argc asm("r12") = count; - register char **argv asm("r13") = argarray; - register char **envp asm("r14") = envarray; - register long(*auxv)[2] asm("r15") = auxarray; - asm volatile("jmp\t__executive" - : /* no outputs */ - : "r"(argc), "r"(argv), "r"(envp), "r"(auxv) - : "memory", "cc"); - unreachable; + _executive(count, argarray, envarray, auxarray); } diff --git a/libc/sock/internal.h b/libc/sock/internal.h index 089c43bb..d0174d9f 100644 --- a/libc/sock/internal.h +++ b/libc/sock/internal.h @@ -19,6 +19,7 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #ifndef COSMOPOLITAN_LIBC_SOCK_INTERNAL_H_ #define COSMOPOLITAN_LIBC_SOCK_INTERNAL_H_ +#ifndef __STRICT_ANSI__ #include "libc/bits/bits.h" #include "libc/nt/winsock.h" #include "libc/sock/sock.h" @@ -145,4 +146,5 @@ forceinline void sockaddr2linux(void *saddr) { COSMOPOLITAN_C_END_ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* !ANSI */ #endif /* COSMOPOLITAN_LIBC_SOCK_INTERNAL_H_ */ diff --git a/libc/sock/ipclassify.h b/libc/sock/ipclassify.h index 69c9db2b..a0afd99e 100644 --- a/libc/sock/ipclassify.h +++ b/libc/sock/ipclassify.h @@ -1,9 +1,10 @@ #ifndef COSMOPOLITAN_LIBC_SOCK_IPCLASSIFY_H_ #define COSMOPOLITAN_LIBC_SOCK_IPCLASSIFY_H_ +#ifndef __STRICT_ANSI__ #include "libc/sock/sock.h" -#include "libc/sysv/errfuns.h" #include "libc/sysv/consts/af.h" #include "libc/sysv/consts/inaddr.h" +#include "libc/sysv/errfuns.h" #if !(__ASSEMBLER__ + __LINKER__ + 0) COSMOPOLITAN_C_START_ @@ -65,4 +66,5 @@ forceinline bool ispublicip(int sin_family, void *sin_addr) { COSMOPOLITAN_C_END_ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* !ANSI */ #endif /* COSMOPOLITAN_LIBC_SOCK_IPCLASSIFY_H_ */ diff --git a/libc/sock/sock.h b/libc/sock/sock.h index 082ecd94..086975e5 100644 --- a/libc/sock/sock.h +++ b/libc/sock/sock.h @@ -1,6 +1,6 @@ #ifndef COSMOPOLITAN_LIBC_SOCK_SOCK_H_ #define COSMOPOLITAN_LIBC_SOCK_SOCK_H_ -#include "libc/bits/bits.h" +#include "libc/bits/bswap.h" #if !(__ASSEMBLER__ + __LINKER__ + 0) COSMOPOLITAN_C_START_ /*───────────────────────────────────────────────────────────────────────────│─╗ diff --git a/libc/sock/xinet_ntop.c b/libc/sock/xinet_ntop.c index ef007d4e..31f2a006 100644 --- a/libc/sock/xinet_ntop.c +++ b/libc/sock/xinet_ntop.c @@ -17,6 +17,7 @@ │ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/bits/weaken.h" #include "libc/log/log.h" #include "libc/mem/mem.h" #include "libc/runtime/runtime.h" diff --git a/libc/stdio/fclose.c b/libc/stdio/fclose.c index 3daf83af..0971f491 100644 --- a/libc/stdio/fclose.c +++ b/libc/stdio/fclose.c @@ -38,7 +38,7 @@ int fclose(FILE *f) { int rc; if (!f) return 0; /* good java behavior; glibc crashes */ - fflushunregister(f); + _fflushunregister(f); fflush(f); free_s(&f->buf); f->state = EOF; diff --git a/libc/stdio/fclose_s.c b/libc/stdio/fclose_s.c index c50ba0be..0108cdee 100644 --- a/libc/stdio/fclose_s.c +++ b/libc/stdio/fclose_s.c @@ -18,7 +18,6 @@ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/bits/bits.h" -#include "libc/runtime/mappings.h" #include "libc/stdio/stdio.h" /** diff --git a/libc/stdio/fdopen.c b/libc/stdio/fdopen.c index a1101f73..4aae88d7 100644 --- a/libc/stdio/fdopen.c +++ b/libc/stdio/fdopen.c @@ -37,7 +37,7 @@ FILE *fdopen(int fd, const char *mode) { res->reader = freadbuf; res->writer = fwritebuf; if ((res->iomode & O_ACCMODE) != O_RDONLY) { - fflushregister(res); + _fflushregister(res); } } return res; diff --git a/libc/stdio/fflush.c b/libc/stdio/fflush.c index bfa8a763..76d8a1c4 100644 --- a/libc/stdio/fflush.c +++ b/libc/stdio/fflush.c @@ -79,7 +79,7 @@ int fflush(FILE *f) { return res; } -int fflushregister(FILE *f) { +textstartup int _fflushregister(FILE *f) { size_t i; struct StdioFlush *sf; sf = &g_fflush; @@ -98,7 +98,7 @@ int fflushregister(FILE *f) { return append(&sf->handles, &f); } -void fflushunregister(FILE *f) { +void _fflushunregister(FILE *f) { size_t i; struct StdioFlush *sf; sf = &g_fflush; diff --git a/libc/stdio/fputc.c b/libc/stdio/fputc.c index 551a3bac..c6b522d9 100644 --- a/libc/stdio/fputc.c +++ b/libc/stdio/fputc.c @@ -17,7 +17,8 @@ │ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/stdio/fputc.h" +#include "libc/calls/calls.h" +#include "libc/stdio/internal.h" #include "libc/stdio/stdio.h" /** @@ -25,4 +26,21 @@ * * @return c (as unsigned char) if written or -1 w/ errno */ -int fputc(int c, FILE *f) { return __fputc(c, f); } +noinstrument int fputc(int c, FILE *f) { + if (c != -1) { + c &= 0xff; + f->buf[f->end] = c; + f->end = (f->end + 1) & (f->size - 1); + if (unlikely(f->beg == f->end || f->bufmode == _IONBF || + (f->bufmode == _IOLBF && c == '\n'))) { + if (f->writer) { + return f->writer(f); + } else if (f->beg == f->end) { + return fseteof(f); + } + } + return c; + } else { + return fseteof(f); + } +} diff --git a/libc/stdio/fputc.h b/libc/stdio/fputc.h deleted file mode 100644 index e996e644..00000000 --- a/libc/stdio/fputc.h +++ /dev/null @@ -1,36 +0,0 @@ -#ifndef COSMOPOLITAN_LIBC_STDIO_FPUTC_H_ -#define COSMOPOLITAN_LIBC_STDIO_FPUTC_H_ -#include "libc/assert.h" -#include "libc/calls/calls.h" -#include "libc/stdio/internal.h" -#include "libc/stdio/stdio.h" -#include "libc/sysv/consts/o.h" -#if !(__ASSEMBLER__ + __LINKER__ + 0) - -/** - * Writes byte to stream. - * - * @return c (as unsigned char) if written or -1 w/ errno - */ -forceinline int __fputc(int c, FILE *f) { - /* assert((f->iomode & O_ACCMODE) != O_RDONLY); */ - if (c != -1) { - unsigned char ch = (unsigned char)c; - f->buf[f->end] = ch; - f->end = (f->end + 1) & (f->size - 1); - if (f->beg == f->end || f->bufmode == _IONBF || - (f->bufmode == _IOLBF && ch == '\n')) { - if (f->writer) { - return f->writer(f); - } else if (f->beg == f->end) { - return fseteof(f); - } - } - return ch; - } else { - return fseteof(f); - } -} - -#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ -#endif /* COSMOPOLITAN_LIBC_STDIO_FPUTC_H_ */ diff --git a/libc/stdio/fputs.c b/libc/stdio/fputs.c index 2c1181af..e66fc0f8 100644 --- a/libc/stdio/fputs.c +++ b/libc/stdio/fputs.c @@ -18,7 +18,6 @@ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/errno.h" -#include "libc/stdio/fputc.h" #include "libc/stdio/stdio.h" /** @@ -36,7 +35,7 @@ int fputs(const char *s, FILE *f) { unsigned char *p = (unsigned char *)s; int res = 0; while (*p) { - if (__fputc(*p++, f) == -1) { + if (fputc(*p++, f) == -1) { if (ferror(f) == EINTR) continue; if (feof(f)) errno = f->state = EPIPE; return -1; diff --git a/libc/stdio/fputwc.c b/libc/stdio/fputwc.c index 10a1680e..6d945b9d 100644 --- a/libc/stdio/fputwc.c +++ b/libc/stdio/fputwc.c @@ -18,7 +18,6 @@ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/limits.h" -#include "libc/stdio/fputc.h" #include "libc/stdio/internal.h" #include "libc/stdio/stdio.h" #include "libc/str/str.h" @@ -35,7 +34,7 @@ wint_t fputwc(wchar_t wc, FILE *f) { if (wc != -1) { len = tpencode(buf, sizeof(buf), wc, false); for (i = 0; i < len; ++i) { - if (__fputc(buf[i], f) == -1) return -1; + if (fputc(buf[i], f) == -1) return -1; } return wc; } else { diff --git a/libc/stdio/fread.c b/libc/stdio/fread.c index bb90aaad..a1ec63ab 100644 --- a/libc/stdio/fread.c +++ b/libc/stdio/fread.c @@ -18,7 +18,6 @@ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/conv/conv.h" -#include "libc/conv/sizemultiply.h" #include "libc/errno.h" #include "libc/runtime/runtime.h" #include "libc/stdio/internal.h" @@ -34,17 +33,16 @@ */ size_t fread(void *buf, size_t stride, size_t count, FILE *f) { int c; - size_t i, bytes; + size_t i, n; unsigned char *p; - if (!sizemultiply(&bytes, stride, count)) { - return fseterr(f, EOVERFLOW); - } - for (p = buf, i = 0; i < bytes; ++i) { - if ((c = fgetc(f)) == -1) { - if (i % stride != 0) abort(); /* todo(jart) */ + for (n = stride * count, p = buf, i = 0; i < n; ++i) { + if ((c = fgetc(f)) != -1) { + p[i] = c & 0xff; + } else if (!(i % stride)) { return i / stride; + } else { + return fseterr(f, EOVERFLOW); } - p[i] = c & 0xff; } return count; } diff --git a/libc/stdio/fseteof.c b/libc/stdio/fseteof.c index 41a246b3..1c6d12d8 100644 --- a/libc/stdio/fseteof.c +++ b/libc/stdio/fseteof.c @@ -19,4 +19,6 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/stdio/internal.h" -long fseteof(FILE *f) { return fseterr(f, -1); } +long fseteof(FILE *f) { + return fseterr(f, -1); +} diff --git a/libc/stdio/fwrite.c b/libc/stdio/fwrite.c index 23ac489f..f5a7e5a4 100644 --- a/libc/stdio/fwrite.c +++ b/libc/stdio/fwrite.c @@ -17,10 +17,8 @@ │ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/conv/sizemultiply.h" #include "libc/errno.h" #include "libc/runtime/runtime.h" -#include "libc/stdio/fputc.h" #include "libc/stdio/internal.h" #include "libc/stdio/stdio.h" @@ -32,14 +30,15 @@ * @return count on success, [0,count) on EOF, 0 on error or count==0 */ size_t fwrite(const void *data, size_t stride, size_t count, FILE *f) { - int rc; - size_t i, bytes; - const unsigned char *p = (const unsigned char *)data; - if (!sizemultiply(&bytes, stride, count)) return fseterr(f, EOVERFLOW); - for (i = 0; i < bytes; ++i) { - if ((rc = __fputc(p[i], f)) == -1) { - if (i % stride != 0) abort(); /* todo(jart) */ - return i / stride; + size_t i, n; + const unsigned char *p; + for (n = stride * count, p = data, i = 0; i < n; ++i) { + if (fputc(p[i], f) == -1) { + if (!(i % stride)) { + return i / stride; + } else { + return fseterr(f, EOVERFLOW); + } } } return count; diff --git a/libc/stdio/g_stderr.c b/libc/stdio/g_stderr.c index 903ed519..7f25f1fb 100644 --- a/libc/stdio/g_stderr.c +++ b/libc/stdio/g_stderr.c @@ -26,7 +26,8 @@ FILE *stderr; hidden FILE g_stderr; hidden unsigned char g_stderr_buf[BUFSIZ] aligned(PAGESIZE); -static textstartup void g_stderr_init() { - fflushregister(stderr); +static textstartup void _init_g_stderr2() { + _fflushregister(stderr); } -const void *const g_stderr_ctor[] initarray = {g_stderr_init}; + +const void *const g_stderr_ctor[] initarray = {_init_g_stderr2}; diff --git a/libc/stdio/g_stdin.c b/libc/stdio/g_stdin.c index 0829e6d9..a0f65956 100644 --- a/libc/stdio/g_stdin.c +++ b/libc/stdio/g_stdin.c @@ -27,6 +27,7 @@ hidden FILE g_stdin; hidden unsigned char g_stdin_buf[BUFSIZ] aligned(PAGESIZE); static textstartup void g_stdin_init() { - fflushregister(stdin); + _fflushregister(stdin); } + const void *const g_stdin_ctor[] initarray = {g_stdin_init}; diff --git a/libc/stdio/g_stdout.c b/libc/stdio/g_stdout.c index af23b5fd..251807e0 100644 --- a/libc/stdio/g_stdout.c +++ b/libc/stdio/g_stdout.c @@ -29,14 +29,14 @@ FILE *stdout; hidden FILE g_stdout; hidden unsigned char g_stdout_buf[BUFSIZ] aligned(PAGESIZE); -static textstartup void g_stdout_init() { +static textstartup void _init_g_stdout2() { struct FILE *sf; sf = stdout; asm("" : "+r"(sf)); if (IsWindows() || ischardev(pushpop(sf->fd))) { sf->bufmode = _IOLBF; } - fflushregister(sf); + _fflushregister(sf); } -const void *const g_stdout_ctor[] initarray = {g_stdout_init}; +const void *const g_stdout_ctor[] initarray = {_init_g_stdout2}; diff --git a/libc/stdio/internal.h b/libc/stdio/internal.h index 3e50bf54..d89ab135 100644 --- a/libc/stdio/internal.h +++ b/libc/stdio/internal.h @@ -9,8 +9,8 @@ extern unsigned char g_stdinbuf[BUFSIZ]; extern unsigned char g_stdoutbuf[BUFSIZ]; extern unsigned char g_stderrbuf[BUFSIZ]; -int fflushregister(FILE *) hidden; -void fflushunregister(FILE *) hidden; +int _fflushregister(FILE *) hidden; +void _fflushunregister(FILE *) hidden; int freadbuf(FILE *) hidden; int fwritebuf(FILE *) hidden; diff --git a/libc/stdio/puts.c b/libc/stdio/puts.c index d9b84ead..af832410 100644 --- a/libc/stdio/puts.c +++ b/libc/stdio/puts.c @@ -17,7 +17,6 @@ │ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/stdio/fputc.h" #include "libc/stdio/stdio.h" /** diff --git a/libc/stdio/serialstdio.c b/libc/stdio/serialstdio.c index 4b1b93c2..013f9922 100644 --- a/libc/stdio/serialstdio.c +++ b/libc/stdio/serialstdio.c @@ -32,7 +32,7 @@ static void fout(FILE *f) { f->beg = (f->beg + 1) & (f->size - 1); } -static int serialstdio(FILE *f, unsigned char status, void action(FILE *f)) { +static int serialstdio(FILE *f, unsigned char status, void action(FILE *)) { int block = 1; unsigned tally = 0; while (f->end != f->beg) { @@ -47,5 +47,9 @@ static int serialstdio(FILE *f, unsigned char status, void action(FILE *f)) { return (int)tally; } -int fsreadbuf(FILE *f) { return serialstdio(f, UART_TTYDA, fin); } -int fswritebuf(FILE *f) { return serialstdio(f, UART_TTYTXR, fout); } +int fsreadbuf(FILE *f) { + return serialstdio(f, UART_TTYDA, fin); +} +int fswritebuf(FILE *f) { + return serialstdio(f, UART_TTYTXR, fout); +} diff --git a/libc/stdio/stdio.h b/libc/stdio/stdio.h index cad7d06c..38cf1776 100644 --- a/libc/stdio/stdio.h +++ b/libc/stdio/stdio.h @@ -10,17 +10,17 @@ COSMOPOLITAN_C_START_ ╚────────────────────────────────────────────────────────────────────────────│*/ typedef struct FILE { - uint8_t bufmode; /* 0: _IOFBF, etc. (ignored if fd is -1) */ - bool noclose; /* 1: for fake dup() */ - uint32_t iomode; /* 4: O_RDONLY, etc. (ignored if fd is -1) */ - int32_t state; /* 8: 0=OK, -1=EOF, >0=errno */ - int fd; /* 12: ≥0=fd, -1=closed|buffer */ - uint32_t beg; /* 16 */ - uint32_t end; /* 20 */ - uint8_t *buf; /* 24 */ - size_t size; /* 32 */ - int (*reader)(struct FILE *f); /* 40 */ - int (*writer)(struct FILE *f); /* 48 */ + uint8_t bufmode; // 0x00 _IOFBF, etc. (ignored if fd=-1) + bool noclose; // 0x01 for fake dup() + uint32_t iomode; // 0x04 O_RDONLY, etc. (ignored if fd=-1) + int32_t state; // 0x08 0=OK, -1=EOF, >0=errno + int fd; // 0x0c ≥0=fd, -1=closed|buffer + uint32_t beg; // 0x10 + uint32_t end; // 0x14 + uint8_t *buf; // 0x18 + size_t size; // 0x20 + int (*reader)(struct FILE *); // 0x28 + int (*writer)(struct FILE *); // 0x30 } FILE; extern FILE *stdin; @@ -35,7 +35,7 @@ int putc(int, FILE *) paramsnonnull(); int fflush(FILE *); int fgetc(FILE *) paramsnonnull(); int ungetc(int, FILE *) paramsnonnull(); -int fileno(FILE *) paramsnonnull(); +int fileno(FILE *) paramsnonnull() nosideeffect; int fputc(int, FILE *) paramsnonnull(); int fputs(const char *, FILE *) paramsnonnull(); int fputws(const wchar_t *, FILE *) paramsnonnull(); diff --git a/libc/stdio/stdio.mk b/libc/stdio/stdio.mk index 4fe66068..2e62bdae 100644 --- a/libc/stdio/stdio.mk +++ b/libc/stdio/stdio.mk @@ -53,6 +53,10 @@ $(LIBC_STDIO_A).pkg: \ $(LIBC_STDIO_A_OBJS) \ $(foreach x,$(LIBC_STDIO_A_DIRECTDEPS),$($(x)_A).pkg) +#o/$(MODE)/libc/stdio/fputc.o: \ + OVERRIDE_CFLAGS += \ + $(NO_MAGIC) + LIBC_STDIO_LIBS = $(foreach x,$(LIBC_STDIO_ARTIFACTS),$($(x))) LIBC_STDIO_SRCS = $(foreach x,$(LIBC_STDIO_ARTIFACTS),$($(x)_SRCS)) LIBC_STDIO_HDRS = $(foreach x,$(LIBC_STDIO_ARTIFACTS),$($(x)_HDRS)) diff --git a/libc/stdio/system.c b/libc/stdio/system.c index 84fb9081..ac2668cd 100644 --- a/libc/stdio/system.c +++ b/libc/stdio/system.c @@ -17,7 +17,7 @@ │ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/bits/bits.h" +#include "libc/bits/weaken.h" #include "libc/calls/calls.h" #include "libc/calls/hefty/ntspawn.h" #include "libc/calls/internal.h" diff --git a/libc/stdio/temp.h b/libc/stdio/temp.h index 722dc40e..9b6113f4 100644 --- a/libc/stdio/temp.h +++ b/libc/stdio/temp.h @@ -13,7 +13,7 @@ nodiscard int mkostempsm(char *, int, unsigned, int); compatfn char *mktemp(char *); int mkostempsmi(char *, int, unsigned, uint64_t *, int, - int openit(const char *, int, ...)) hidden nodiscard; + int (*)(const char *, int, ...)) hidden nodiscard; COSMOPOLITAN_C_END_ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ diff --git a/libc/stdio/vfprintf.c b/libc/stdio/vfprintf.c index 7b2b8620..39275577 100644 --- a/libc/stdio/vfprintf.c +++ b/libc/stdio/vfprintf.c @@ -19,28 +19,23 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/fmt/fmt.h" #include "libc/limits.h" -#include "libc/stdio/fputc.h" #include "libc/stdio/stdio.h" #include "libc/sysv/errfuns.h" struct state { - FILE *const f; - unsigned toto; + FILE *f; + int n; }; static int vfprintfputchar(int c, struct state *st) { - if (st->toto <= INT_MAX) { - st->toto++; - return __fputc(c, st->f); - } else { - return eoverflow(); - } + st->n++; + return fputc(c, st->f); } int(vfprintf)(FILE *f, const char *fmt, va_list va) { struct state st[1] = {{f, 0}}; if (palandprintf(vfprintfputchar, st, fmt, va) != -1) { - return st->toto; + return st->n; } else { return -1; } diff --git a/libc/str/appendchar.h b/libc/str/appendchar.h index cc39c943..643fa7be 100644 --- a/libc/str/appendchar.h +++ b/libc/str/appendchar.h @@ -1,5 +1,6 @@ #ifndef COSMOPOLITAN_LIBC_RUNTIME_APPENDCHAR_H_ #define COSMOPOLITAN_LIBC_RUNTIME_APPENDCHAR_H_ +#ifndef __STRICT_ANSI__ #include "libc/str/str.h" #include "libc/str/tpenc.h" #include "libc/str/tpencode.h" @@ -15,4 +16,5 @@ static inline void AppendChar(char **p, char *pe, wint_t wc) { } #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* !ANSI */ #endif /* COSMOPOLITAN_LIBC_RUNTIME_APPENDCHAR_H_ */ diff --git a/libc/nexgen32e/getcachesize.c b/libc/str/getcachesize.c similarity index 100% rename from libc/nexgen32e/getcachesize.c rename to libc/str/getcachesize.c diff --git a/libc/str/getutf16.ncabi.c b/libc/str/getutf16.ncabi.c index 6e5570e8..669e3a85 100644 --- a/libc/str/getutf16.ncabi.c +++ b/libc/str/getutf16.ncabi.c @@ -28,7 +28,7 @@ * @note synchronization is performed to skip leading continuations; * canonicalization and validation are performed to some extent */ -unsigned(getutf16)(const char16_t *p, wint_t *wc) { +forcealignargpointer unsigned(getutf16)(const char16_t *p, wint_t *wc) { unsigned skip = 0; while ((p[skip] & UTF16_MASK) == UTF16_CONT) skip++; if ((p[skip] & UTF16_MASK) != UTF16_MOAR) { diff --git a/libc/nexgen32e/getx86processormodel.c b/libc/str/getx86processormodel.c similarity index 100% rename from libc/nexgen32e/getx86processormodel.c rename to libc/str/getx86processormodel.c diff --git a/libc/nexgen32e/insertionsort.greg.c b/libc/str/insertionsort.greg.c similarity index 100% rename from libc/nexgen32e/insertionsort.greg.c rename to libc/str/insertionsort.greg.c diff --git a/libc/str/internal.h b/libc/str/internal.h index ec6ab016..be45024c 100644 --- a/libc/str/internal.h +++ b/libc/str/internal.h @@ -19,6 +19,7 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #ifndef COSMOPOLITAN_LIBC_STR_INTERNAL_H_ #define COSMOPOLITAN_LIBC_STR_INTERNAL_H_ +#ifndef __STRICT_ANSI__ #include "libc/str/str.h" #if !(__ASSEMBLER__ + __LINKER__ + 0) @@ -48,16 +49,13 @@ nodebuginfo forceinline bool32 iscont(wint_t c) { return (c & 0b11000000) == 0b10000000; } -extern const uint32_t kCrc32Tab[256]; - char *strstr$sse42(const char *, const char *) strlenesque hidden; char16_t *strstr16$sse42(const char16_t *, const char16_t *) strlenesque hidden; void *memmem$sse42(const void *, size_t, const void *, size_t) strlenesque hidden; -uint32_t crc32c$sse42(uint32_t, const void *, size_t) strlenesque hidden; -uint32_t crc32$pclmul(uint32_t, const void *, size_t) hidden; void sha256$x86(uint32_t[hasatleast 8], const uint8_t[hasatleast 64], uint32_t) hidden; #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* !ANSI */ #endif /* COSMOPOLITAN_LIBC_STR_INTERNAL_H_ */ diff --git a/libc/nexgen32e/knobs.c b/libc/str/knobs.c similarity index 100% rename from libc/nexgen32e/knobs.c rename to libc/str/knobs.c diff --git a/libc/str/knuthmultiplicativehash.h b/libc/str/knuthmultiplicativehash.h index 5e0fc8d7..872f1bf9 100644 --- a/libc/str/knuthmultiplicativehash.h +++ b/libc/str/knuthmultiplicativehash.h @@ -1,26 +1,15 @@ #ifndef COSMOPOLITAN_LIBC_STR_KNUTHMULTIPLICATIVEHASH_H_ #define COSMOPOLITAN_LIBC_STR_KNUTHMULTIPLICATIVEHASH_H_ #if !(__ASSEMBLER__ + __LINKER__ + 0) -COSMOPOLITAN_C_START_ forceinline uint32_t KnuthMultiplicativeHash32(const void *buf, size_t size) { - /* frozen due to presence in sqlite & promise in libc/getuid.c */ - const unsigned char *const p = (const unsigned char *)buf; - uint32_t hash = 0, kPhiPrime = 0x9e3779b1; size_t i; - for (i = 0; i < size; i++) hash = (p[i] + hash) * kPhiPrime; - return hash; + uint32_t h; + const uint32_t kPhiPrime = 0x9e3779b1; + const unsigned char *p = (const unsigned char *)buf; + for (h = i = 0; i < size; i++) h = (p[i] + h) * kPhiPrime; + return h; } -forceinline uint64_t KnuthMultiplicativeHash(const void *buf, size_t size) { - /* TODO(jart): verify w/ primary source */ - const unsigned char *const p = (const unsigned char *)buf; - uint64_t hash = 0, kPhiPrime = 0x9e3779b9925d4c17; - size_t i; - for (i = 0; i < size; i++) hash = (p[i] + hash) * kPhiPrime; - return hash; -} - -COSMOPOLITAN_C_END_ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ #endif /* COSMOPOLITAN_LIBC_STR_KNUTHMULTIPLICATIVEHASH_H_ */ diff --git a/libc/nexgen32e/kx86processormodels.c b/libc/str/kx86processormodels.c similarity index 100% rename from libc/nexgen32e/kx86processormodels.c rename to libc/str/kx86processormodels.c diff --git a/libc/nexgen32e/lz4check.c b/libc/str/lz4check.c similarity index 100% rename from libc/nexgen32e/lz4check.c rename to libc/str/lz4check.c diff --git a/libc/nexgen32e/lz4cpy.c b/libc/str/lz4cpy.initabi.c similarity index 100% rename from libc/nexgen32e/lz4cpy.c rename to libc/str/lz4cpy.initabi.c diff --git a/libc/nexgen32e/lz4decode.c b/libc/str/lz4decode.c similarity index 100% rename from libc/nexgen32e/lz4decode.c rename to libc/str/lz4decode.c diff --git a/libc/nexgen32e/memset16.c b/libc/str/memset16.c similarity index 100% rename from libc/nexgen32e/memset16.c rename to libc/str/memset16.c diff --git a/libc/str/sha256.c b/libc/str/sha256.c index 59ebc021..37cdf1c6 100644 --- a/libc/str/sha256.c +++ b/libc/str/sha256.c @@ -31,7 +31,7 @@ static void sha256_transform(uint32_t state[hasatleast 8], size_t i; uint32_t a, b, c, d, e, f, g, h, t1, t2, m[64]; for (i = 0; i < 16; ++i, data += 4) { - m[i] = data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3]; + m[i] = (uint32_t)data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3]; } for (; i < 64; ++i) { m[i] = SIG1(m[i - 2]) + m[i - 7] + SIG0(m[i - 15]) + m[i - 16]; diff --git a/libc/str/startswith.c b/libc/str/startswith.c index 5513b571..91dbfa1d 100644 --- a/libc/str/startswith.c +++ b/libc/str/startswith.c @@ -25,5 +25,10 @@ * @param prefix is also NUL-terminated */ bool(startswith)(const char *s, const char *prefix) { - return strncmp(s, prefix, strlen(prefix)) == 0; + if (s == prefix) return true; + for (;;) { + if (!*prefix) return true; + if (!*s) return false; + if (*s++ != *prefix++) return false; + } } diff --git a/libc/str/stpcpy.c b/libc/str/stpcpy.c index 949240f4..1596aeef 100644 --- a/libc/str/stpcpy.c +++ b/libc/str/stpcpy.c @@ -17,13 +17,16 @@ │ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/limits.h" #include "libc/str/str.h" /** * Copies string, returning pointer to where copying ended. + * * @see strcpy(), mempcpy() * @asyncsignalsafe */ char *stpcpy(char *dst, const char *src) { - return (char *)mempcpy(dst, src, strlen(src) + 1) - 1; + dst = memccpy(dst, src, '\0', SIZE_MAX); + return dst - 1; } diff --git a/libc/str/str.h b/libc/str/str.h index c6e080f5..980b32de 100644 --- a/libc/str/str.h +++ b/libc/str/str.h @@ -14,7 +14,7 @@ extern const uint8_t kToLower[256]; extern const uint8_t kToUpper[256]; extern const uint16_t kToLower16[256]; extern const uint8_t kBase36[256]; -extern const char16_t kCp437[256]; /** IBM Code Page 437 */ +extern const char16_t kCp437[256]; int isascii(int); int isspace(int); @@ -68,8 +68,8 @@ void *isnotplaintext(const void *, size_t) nothrow nocallback nosideeffect; #define UTF16_MOAR 0b1101100000000000 /* 0xD800..0xDBFF */ #define UTF16_CONT 0b1101110000000000 /* 0xDC00..0xDBFF */ -unsigned getutf16(const char16_t *p, wint_t *wc); -int pututf16(char16_t *s, size_t size, wint_t wc, bool awesome); +unsigned getutf16(const char16_t *, wint_t *); +int pututf16(char16_t *, size_t, wint_t, bool); int iswalnum(wint_t); int iswalpha(wint_t); int iswblank(wint_t); @@ -105,6 +105,7 @@ void *memchr(const void *, int, size_t) strlenesque; char *strchrnul(const char *, int) strlenesque returnsnonnull; void *rawmemchr(const void *, int) strlenesque returnsnonnull; void bzero(void *, size_t) paramsnonnull() libcesque; +void explicit_bzero(void *, size_t) paramsnonnull() libcesque; size_t strlen16(const char16_t *) strlenesque; size_t strnlen16(const char16_t *, size_t) strlenesque; size_t strnlen16_s(const char16_t *, size_t); @@ -165,7 +166,6 @@ char *strcpy(char *, const char *) memcpyesque; char16_t *strcpy16(char16_t *, const char16_t *) memcpyesque; char *strncat(char *, const char *, size_t) memcpyesque; char *strncpy(char *, const char *, size_t) memcpyesque; -char *_strncpy(char *, const char *, size_t) asm("strncpy") memcpyesque; char *strtok(char *, const char *) paramsnonnull((2)) libcesque; char *strtok_r(char *, const char *, char **) paramsnonnull((2, 3)); uint16_t *strcpyzbw(uint16_t *, const char *) memcpyesque; @@ -179,7 +179,6 @@ bool endswith16(const char16_t *, const char16_t *) strlenesque; bool wcsendswith(const wchar_t *, const wchar_t *) strlenesque; const char *indexdoublenulstring(const char *, unsigned) strlenesque; int getkvlin(const char *, const char *const[]); -void crc32init(uint32_t[hasatleast 256], uint32_t); wchar_t *wmemset(wchar_t *, wchar_t, size_t) memcpyesque; char16_t *memset16(char16_t *, char16_t, size_t) memcpyesque; compatfn wchar_t *wmemcpy(wchar_t *, const wchar_t *, size_t) memcpyesque; @@ -190,28 +189,14 @@ char *tinystrstr(const char *, const char *) strlenesque; char16_t *tinystrstr16(const char16_t *, const char16_t *) strlenesque; void *tinymemmem(const void *, size_t, const void *, size_t) strlenesque; -void *memtolower(void *p, size_t n); -char *strntolower(char *s, size_t n); -char *strtolower(char *s) paramsnonnull(); -char *strntoupper(char *s, size_t n); -char *strtoupper(char *s) paramsnonnull(); -char *chomp(char *line); -char16_t *chomp16(char16_t *line); -wchar_t *wchomp(wchar_t *line); - -/* gcc -Werror=stringop-truncation misunderstands strncpy() api */ -#define strncpy(DEST, SRC, N) _strncpy(DEST, SRC, N) - -#define explicit_bzero(STR, BYTES) \ - do { \ - void *Str; \ - size_t Bytes; \ - asm volatile("call\texplicit_bzero" \ - : "=D"(Str), "=S"(Bytes) \ - : "0"(STR), "1"(BYTES) \ - : "rax", "rcx", "rdx", "r8", "r9", "r10", "r11", "memory", \ - "cc", "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5"); \ - } while (0) +void *memtolower(void *, size_t); +char *strntolower(char *, size_t); +char *strtolower(char *) paramsnonnull(); +char *strntoupper(char *, size_t); +char *strtoupper(char *) paramsnonnull(); +char *chomp(char *); +char16_t *chomp16(char16_t *); +wchar_t *wchomp(wchar_t *); /*───────────────────────────────────────────────────────────────────────────│─╗ │ cosmopolitan § strings » multibyte ─╬─│┼ @@ -246,8 +231,8 @@ size_t strclen16(const char16_t *) nosideeffect; size_t strnclen16(const char16_t *, size_t) nosideeffect; typedef unsigned wctype_t; -wctype_t wctype(const char *name) strlenesque; -int iswctype(wint_t c, wctype_t type) pureconst; +wctype_t wctype(const char *) strlenesque; +int iswctype(wint_t, wctype_t) pureconst; /*───────────────────────────────────────────────────────────────────────────│─╗ │ cosmopolitan § strings » hashing ─╬─│┼ @@ -262,9 +247,6 @@ struct Sha256Ctx { uint32_t state[8]; }; -uint32_t crc32_z(uint32_t, const void *, size_t); -extern uint32_t (*const crc32c)(uint32_t, const void *, size_t) paramsnonnull(); - void sha256_init(struct Sha256Ctx *); void sha256_update(struct Sha256Ctx *, const uint8_t *, size_t); void sha256_final(struct Sha256Ctx *, uint8_t *); @@ -307,13 +289,6 @@ extern int (*const hook$wcsncmp)(const wchar_t *, const wchar_t *, size_t); : strstr16, default \ : strstr)(haystack, needle) -#define strchr(s, c) \ - _Generic(*(s), wchar_t \ - : wcschr, char16_t \ - : strchr16, default \ - : (isconstant(s) && isconstant(c) ? __builtin_strchr : _strchr))(s, \ - c) - #define strrchr(s, c) \ _Generic(*(s), wchar_t \ : wcsrchr, char16_t \ @@ -419,10 +394,28 @@ extern int (*const hook$wcsncmp)(const wchar_t *, const wchar_t *, size_t); ╚────────────────────────────────────────────────────────────────────────────│*/ #if defined(__GNUC__) && !defined(__STRICT_ANSI__) +extern int (*const __memcmp)(const void *, const void *, size_t); +#define memcmp(a, b, n) __memcmp(a, b, n) + +/* gcc -Werror=stringop-truncation misunderstands strncpy() api */ +char *_strncpy(char *, const char *, size_t) asm("strncpy") memcpyesque; +#define strncpy(DEST, SRC, N) _strncpy(DEST, SRC, N) + +#define explicit_bzero(STR, BYTES) \ + do { \ + void *Str; \ + size_t Bytes; \ + asm volatile("call\texplicit_bzero" \ + : "=D"(Str), "=S"(Bytes) \ + : "0"(STR), "1"(BYTES) \ + : "rax", "rcx", "rdx", "r8", "r9", "r10", "r11", "memory", \ + "cc", "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5"); \ + } while (0) + #ifdef UNBLOAT_STDARG #define __STR_XMM_CLOBBER #else -#define __STR_XMM_CLOBBER "xmm3", +#define __STR_XMM_CLOBBER "xmm3", "xmm4", #endif #define __memcpy_isgoodsize(SIZE) \ @@ -437,7 +430,7 @@ extern int (*const hook$wcsncmp)(const wchar_t *, const wchar_t *, size_t); #define memcpy(DEST, SRC, SIZE) \ (__memcpy_isgoodsize(SIZE) ? __builtin_memcpy(DEST, SRC, SIZE) \ - : __memcpy("_memcpy", DEST, SRC, SIZE)) + : __memcpy("MemCpy", DEST, SRC, SIZE)) #define memset(DEST, BYTE, SIZE) \ (__memset_isgoodsize(SIZE) ? __builtin_memset(DEST, BYTE, SIZE) \ @@ -445,7 +438,8 @@ extern int (*const hook$wcsncmp)(const wchar_t *, const wchar_t *, size_t); #if defined(__STDC_HOSTED__) && (defined(__SSE2__) || defined(UNBLOAT_STDARG)) -#define memmove(DEST, SRC, SIZE) __memcpy("_memmove", (DEST), (SRC), (SIZE)) +#define memmove(DEST, SRC, SIZE) __memcpy("MemMove", (DEST), (SRC), (SIZE)) + #define __memcpy(FN, DEST, SRC, SIZE) \ ({ \ void *DeSt = (DEST); \ @@ -457,16 +451,18 @@ extern int (*const hook$wcsncmp)(const wchar_t *, const wchar_t *, size_t); : __STR_XMM_CLOBBER "cc"); \ DeSt; \ }) + #define mempcpy(DEST, SRC, SIZE) \ ({ \ size_t SIze = (SIZE); \ (void *)((char *)memcpy((DEST), (SRC), SIze) + SIze); \ }) + #define __memset(DEST, BYTE, SIZE) \ ({ \ void *DeSt = (DEST); \ size_t SiZe = (SIZE); \ - asm("call\t_memset" \ + asm("call\tMemSet" \ : "=m"(*(char(*)[SiZe])(DeSt)) \ : "D"(DeSt), "S"(BYTE), "d"(SiZe) \ : __STR_XMM_CLOBBER "cc"); \ @@ -476,6 +472,7 @@ extern int (*const hook$wcsncmp)(const wchar_t *, const wchar_t *, size_t); #else /* hosted/sse2/unbloat */ #define memmove(DEST, SRC, SIZE) __memcpy((DEST), (SRC), (SIZE)) + #define mempcpy(DEST, SRC, SIZE) \ ({ \ void *Rdi, *Dest = (DEST); \ @@ -488,6 +485,7 @@ extern int (*const hook$wcsncmp)(const wchar_t *, const wchar_t *, size_t); : "cc"); \ Rdi; \ }) + #define __memcpy(FN, DEST, SRC, SIZE) \ ({ \ void *Rdi, *Dest = (DEST); \ @@ -500,6 +498,7 @@ extern int (*const hook$wcsncmp)(const wchar_t *, const wchar_t *, size_t); : "cc"); \ Dest; \ }) + #define __memset(DEST, BYTE, SIZE) \ ({ \ void *Rdi, *Dest = (DEST); \ @@ -532,8 +531,8 @@ extern int (*const hook$wcsncmp)(const wchar_t *, const wchar_t *, size_t); #define pututf16(BUF, SIZE, CH, AWESOME) __pututf16(BUF, SIZE, CH, AWESOME) #define getutf16(BUF, CHPTR) __getutf16(BUF, CHPTR) size_t _strlen(const char *s) asm("strlen") strlenesque; -char *_strchr(const char *, int) asm("strchr") strlenesque; void *_memchr(const void *, int, size_t) asm("memchr") strlenesque; + forceinline int __pututf16(char16_t *s, size_t size, wint_t wc, bool32 awesome) { if (size >= 1 && (0x00 <= wc && wc <= 0xD7FF)) { @@ -548,98 +547,25 @@ forceinline int __pututf16(char16_t *s, size_t size, wint_t wc, } int ax; asm("call\tpututf16" - : "=a"(ax), "=m"(*(char16_t(*)[size])s) + : "=a"(ax), "=m"(*(char(*)[size])s) : "D"(s), "S"(size), "d"(wc) : "cc"); return ax; } + forceinline unsigned __getutf16(const char16_t *s, wint_t *wc) { if ((0x00 <= s[0] && s[0] <= 0xD7FF)) { *wc = s[0]; return 1; } unsigned ax; - asm("call\tgetutf16" : "=a"(ax), "=m"(*wc) : "D"(s), "S"(wc), "m"(*s) : "cc"); + asm("call\tgetutf16" + : "=a"(ax), "=m"(*wc) + : "D"(s), "S"(wc), "m"(*s), "m"(*(char(*)[4])s) + : "cc"); return ax; } -/* - * GCC has builtins for these, that only do things for literals. They - * also cause the compiler to whine in a kafkaesque way when flags like - * -Werror=shadow and -Werror=implicit-function-declaration are passed. - */ -#define isascii(c) isascii_(c) -#define isspace(c) isspace_(c) -#define isalpha(c) isalpha_(c) -#define isdigit(c) isdigit_(c) -#define isalnum(c) isalnum_(c) -#define isxdigit(c) isxdigit_(c) -#define isprint(c) isprint_(c) -#define islower(c) islower_(c) -#define isupper(c) isupper_(c) -#define isblank(c) isblank_(c) -#define iscntrl(c) iscntrl_(c) -#define isgraph(c) isgraph_(c) -#define tolower(c) tolower_(c) -#define ispunct(c) ispunct_(c) -#define toupper(c) toupper_(c) -#define hextoint(c) hextoint_(c) -#define DECLARE_CTYPE(NAME, EXPR) \ - pureconst forceinline nodebuginfo int NAME(int i) { \ - unsigned char c = (unsigned char)i; \ - return (EXPR); \ - } -DECLARE_CTYPE(isascii_, 0 <= c && c <= 0x7f) -DECLARE_CTYPE(isspace_, kCtype[c] & 0x01) -DECLARE_CTYPE(isalpha_, kCtype[c] & 0x02) -DECLARE_CTYPE(isdigit_, '0' <= c && c <= '9') -DECLARE_CTYPE(isalnum_, kCtype[c] & 0x06) -DECLARE_CTYPE(isxdigit_, kCtype[c] & 0x08) -DECLARE_CTYPE(isprint_, kCtype[c] & 0x10) -DECLARE_CTYPE(islower_, 'a' <= c && c <= 'z') -DECLARE_CTYPE(isupper_, 'A' <= c && c <= 'Z') -DECLARE_CTYPE(isblank_, kCtype[c] & 0x80) -DECLARE_CTYPE(iscntrl_, !isprint_(c)) -DECLARE_CTYPE(isgraph_, isprint_(c) && (c) != ' ') -DECLARE_CTYPE(tolower_, kToLower[c]) -DECLARE_CTYPE(ispunct_, isprint(c) && !(kCtype[c] & 0x07)) -DECLARE_CTYPE(toupper_, kToUpper[c]) -DECLARE_CTYPE(hextoint_, (c + 9 * (1 & (SAR(c, 6)))) & 0xf) -#undef DECLARE_CTYPE -#define iswalnum(c) iswalnum_(c) -#define iswalpha(c) iswalpha_(c) -#define iswblank(c) iswblank_(c) -#define iswcntrl(c) iswcntrl_(c) -#define iswdigit(c) iswdigit_(c) -#define iswgraph(c) iswgraph_(c) -#define iswlower(c) iswlower_(c) -#define iswspace(c) iswspace_(c) -#define iswupper(c) iswupper_(c) -#define iswxdigit(c) iswxdigit_(c) -#define iswpunct(c) iswpunct_(c) -#define iswprint(c) iswprint_(c) -#define towlower(c) towlower_(c) -#define towupper(c) towupper_(c) -#define DECLARE_WCTYPE(R, NAME, T, EXPR) \ - forceinline nodebuginfo R NAME(T c) { \ - return EXPR; \ - } -DECLARE_WCTYPE(int, iswalnum_, wint_t, isascii(c) ? isalnum(c) : c) -DECLARE_WCTYPE(int, iswalpha_, wint_t, isascii(c) ? isalpha(c) : c) -DECLARE_WCTYPE(int, iswblank_, wint_t, isascii(c) ? isblank(c) : c) -DECLARE_WCTYPE(int, iswcntrl_, wint_t, isascii(c) ? iscntrl(c) : c) -DECLARE_WCTYPE(int, iswdigit_, wint_t, isascii(c) ? isdigit(c) : c) -DECLARE_WCTYPE(int, iswgraph_, wint_t, isascii(c) ? isgraph(c) : c) -DECLARE_WCTYPE(int, iswlower_, wint_t, isascii(c) ? islower(c) : c) -DECLARE_WCTYPE(int, iswspace_, wint_t, isascii(c) ? isspace(c) : c) -DECLARE_WCTYPE(int, iswupper_, wint_t, isascii(c) ? isupper(c) : c) -DECLARE_WCTYPE(int, iswxdigit_, wint_t, isascii(c) ? isxdigit(c) : c) -DECLARE_WCTYPE(int, iswpunct_, wint_t, !isascii(c) || ispunct(c)) -DECLARE_WCTYPE(int, iswprint_, wint_t, !isascii(c) || isprint(c)) -DECLARE_WCTYPE(unsigned, towlower_, unsigned, isascii(c) ? tolower(c) : c) -DECLARE_WCTYPE(unsigned, towupper_, unsigned, isascii(c) ? toupper(c) : c) -#undef DECLARE_WCTYPE - #endif /* __GNUC__ && !__STRICT_ANSI__ */ COSMOPOLITAN_C_END_ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ diff --git a/libc/nexgen32e/strcspn.c b/libc/str/strcspn.c similarity index 100% rename from libc/nexgen32e/strcspn.c rename to libc/str/strcspn.c diff --git a/libc/nexgen32e/strcspn16.c b/libc/str/strcspn16.c similarity index 97% rename from libc/nexgen32e/strcspn16.c rename to libc/str/strcspn16.c index f3b57d9d..46096f56 100644 --- a/libc/nexgen32e/strcspn16.c +++ b/libc/str/strcspn16.c @@ -23,6 +23,6 @@ #undef strcspn #define char char16_t #define HasCharacter HasCharacter16 -#define strcspn strcspn16 +#define strcspn strcspn16 -#include "libc/nexgen32e/strcspn.c" +#include "libc/str/strcspn.c" diff --git a/libc/nexgen32e/strpbrk.c b/libc/str/strpbrk.c similarity index 100% rename from libc/nexgen32e/strpbrk.c rename to libc/str/strpbrk.c diff --git a/libc/nexgen32e/strpbrk16.c b/libc/str/strpbrk16.c similarity index 95% rename from libc/nexgen32e/strpbrk16.c rename to libc/str/strpbrk16.c index ee9af739..19bf75ef 100644 --- a/libc/nexgen32e/strpbrk16.c +++ b/libc/str/strpbrk16.c @@ -23,6 +23,7 @@ #undef strpbrk #define char char16_t #define HasCharacter HasCharacter16 -#define strpbrk strpbrk16 +#define strpbrk strpbrk16 +#define strchr(x, y) strchr16(x, y) -#include "libc/nexgen32e/strpbrk.c" +#include "libc/str/strpbrk.c" diff --git a/libc/str/strsignal.c b/libc/str/strsignal.c index 24d6ee98..06f00cc5 100644 --- a/libc/str/strsignal.c +++ b/libc/str/strsignal.c @@ -23,14 +23,20 @@ static const char kSig[4] = "SIG"; static const char kUnknown[8] = "UNKNOWN"; + alignas(1) static const char kStrSignals[][8] = { "EXIT", "HUP", "INT", "QUIT", "ILL", "TRAP", "ABRT", "BUS", "FPE", "KILL", "USR1", "SEGV", "USR2", "PIPE", "ALRM", "TERM", "STKFLT", "CHLD", "CONT", "STOP", "TSTP", "TTIN", "TTOU", "URG", - "XCPU", "XFSZ", "VTALRM", "PROF", "WINCH", "IO", "PWR", "SYS"}; + "XCPU", "XFSZ", "VTALRM", "PROF", "WINCH", "IO", "PWR", "SYS", +}; static char g_strsignal[4 + 8]; +/** + * Returns name associated with signal code. + * @see sigaction() + */ char *strsignal(int sig) { if (0 <= sig && (unsigned)sig < ARRAYLEN(kStrSignals)) { memcpy(g_strsignal, kSig, 4); diff --git a/libc/nexgen32e/strspn.c b/libc/str/strspn.c similarity index 96% rename from libc/nexgen32e/strspn.c rename to libc/str/strspn.c index e5a3d893..0889a50d 100644 --- a/libc/nexgen32e/strspn.c +++ b/libc/str/strspn.c @@ -22,6 +22,9 @@ /** * Returns prefix length, consisting of chars in accept. + * + * @param accept is nul-terminated character set + * @see strcspn(), strtok_r() * @asyncsignalsafe */ size_t(strspn)(const char *s, const char *accept) { diff --git a/libc/nexgen32e/strspn16.c b/libc/str/strspn16.c similarity index 97% rename from libc/nexgen32e/strspn16.c rename to libc/str/strspn16.c index 4379d923..538ef2ee 100644 --- a/libc/nexgen32e/strspn16.c +++ b/libc/str/strspn16.c @@ -23,6 +23,6 @@ #undef strspn #define char char16_t #define HasCharacter HasCharacter16 -#define strspn strspn16 +#define strspn strspn16 -#include "libc/nexgen32e/strspn.c" +#include "libc/str/strspn.c" diff --git a/libc/str/strtok_r.c b/libc/str/strtok_r.c index 2e05b418..9c7cb17f 100644 --- a/libc/str/strtok_r.c +++ b/libc/str/strtok_r.c @@ -30,16 +30,17 @@ * @asyncsignalsafe */ char *strtok_r(char *s, const char *sep, char **state) { + size_t leadingseps, tokenlen; if (!s) { s = *state; if (!s) { return NULL; } } - size_t leadingseps = strspn(s, sep); + leadingseps = strspn(s, sep); s += leadingseps; if (*s) { - size_t tokenlen = strcspn(s, sep); + tokenlen = strcspn(s, sep); if (s[tokenlen]) { s[tokenlen] = '\0'; *state = &s[tokenlen + 1]; diff --git a/libc/str/tinymemccpy.c b/libc/str/tinymemccpy.c new file mode 100644 index 00000000..1992bfa4 --- /dev/null +++ b/libc/str/tinymemccpy.c @@ -0,0 +1,31 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/str/str.h" +#include "libc/str/tinymemccpy.h" + +void *tinymemccpy(void *dst, const void *src, int termchar, size_t limit) { + size_t i; + unsigned char *d; + const unsigned char *s; + for (termchar &= 0xff, d = dst, s = src, i = 0; i < limit; ++i) { + if ((d[i] = s[i]) == termchar) return d + i + 1; + } + return NULL; +} diff --git a/libc/str/tinymemccpy.h b/libc/str/tinymemccpy.h new file mode 100644 index 00000000..97144837 --- /dev/null +++ b/libc/str/tinymemccpy.h @@ -0,0 +1,10 @@ +#ifndef COSMOPOLITAN_LIBC_STR_TINYMEMCCPY_H_ +#define COSMOPOLITAN_LIBC_STR_TINYMEMCCPY_H_ +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +void *tinymemccpy(void *, const void *, int, size_t); + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_STR_TINYMEMCCPY_H_ */ diff --git a/libc/str/tinymemmem.h b/libc/str/tinymemmem.h index 1f8f89cd..c96fccd3 100644 --- a/libc/str/tinymemmem.h +++ b/libc/str/tinymemmem.h @@ -1,12 +1,13 @@ #ifndef COSMOPOLITAN_LIBC_STR_TINYSTRSTR_H_ #define COSMOPOLITAN_LIBC_STR_TINYSTRSTR_H_ +#ifndef __STRICT_ANSI__ #include "libc/str/str.h" #if !(__ASSEMBLER__ + __LINKER__ + 0) forceinline void *tinymemmemi(const void *haystk, size_t haystksize, const void *needle, size_t needlesize) { - const char *p = haystk; - const char *pe = p + haystksize; + const char *p = (const char *)haystk; + const char *pe = (const char *)haystk + haystksize; while (p < pe) { size_t i = 0; ++p; @@ -21,4 +22,5 @@ forceinline void *tinymemmemi(const void *haystk, size_t haystksize, } #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* !ANSI */ #endif /* COSMOPOLITAN_LIBC_STR_TINYMEMMEM_H_ */ diff --git a/libc/str/tpdecode.h b/libc/str/tpdecode.h index bbeff3a5..549a496c 100644 --- a/libc/str/tpdecode.h +++ b/libc/str/tpdecode.h @@ -8,11 +8,11 @@ int tpdecode(const char *, wint_t *) paramsnonnull((1)) libcesque; #ifndef __STRICT_ANSI__ #define tpdecode(S, OUT) __tpdecode(S, OUT) forceinline int __tpdecode(const char *s, wint_t *out) { + int ax; if (0 <= *s && *s <= 0x7f) { *out = *s; return 1; } - int ax; asm("call\ttpdecode" : "=a"(ax), "=m"(*(char(*)[6])s) : "D"(s), "S"(out) diff --git a/libc/str/tpdecodecb.h b/libc/str/tpdecodecb.h index 09cb926c..c7098d17 100644 --- a/libc/str/tpdecodecb.h +++ b/libc/str/tpdecodecb.h @@ -19,6 +19,7 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #ifndef COSMOPOLITAN_LIBC_STR_TPDECODECB_H_ #define COSMOPOLITAN_LIBC_STR_TPDECODECB_H_ +#include "libc/nexgen32e/bsr.h" #if !(__ASSEMBLER__ + __LINKER__ + 0) /** @@ -28,17 +29,16 @@ */ forceinline int tpdecodecb(wint_t *out, int first, int get(void *arg, uint32_t i), void *arg) { - uint32_t wc, cb, need, msb, i = 1; - if ((wc = first) == -1) return -1; - while ((wc & 0b11000000) == 0b10000000) { + uint32_t wc, cb, need, msb, j, i = 1; + if (unlikely((wc = first) == -1)) return -1; + while (unlikely((wc & 0b11000000) == 0b10000000)) { if ((wc = get(arg, i++)) == -1) return -1; } - if ((wc & 0b10000000) == 0b10000000) { - asm("bsr\t%1,%0" : "=r"(msb) : "rm"(~wc & 0xff) : "cc"); - if (!msb) msb = 1; + if (unlikely(!(0 <= wc && wc <= 0x7F))) { + msb = wc < 252 ? bsr(~wc & 0xff) : 1; need = 7 - msb; wc &= ((1u << msb) - 1) | 0b00000011; - for (uint32_t j = 1; j < need; ++j) { + for (j = 1; j < need; ++j) { if ((cb = get(arg, i++)) == -1) return -1; if ((cb & 0b11000000) == 0b10000000) { wc = wc << 6 | (cb & 0b00111111); @@ -48,7 +48,7 @@ forceinline int tpdecodecb(wint_t *out, int first, } } } - if (out) *out = (wint_t)wc; + if (likely(out)) *out = (wint_t)wc; return i; } diff --git a/libc/str/tpenc.h b/libc/str/tpenc.h index 9cc5d26a..a6cb8ded 100644 --- a/libc/str/tpenc.h +++ b/libc/str/tpenc.h @@ -6,16 +6,19 @@ COSMOPOLITAN_C_START_ uint64_t tpenc(int32_t) pureconst; #ifndef __STRICT_ANSI__ -#define tpenc(CODE) \ - ({ \ - long Buf; \ - int32_t Code = (CODE); \ - if (0 <= Code && Code <= 127) { \ - Buf = Code; \ - } else { \ - asm("call\ttpenc" : "=a"(Buf), "+D"(Code) : /* inputs */ : "cc"); \ - } \ - Buf; \ +#define tpenc(CODE) \ + ({ \ + long Buf; \ + int32_t Code = (CODE); \ + if (0 <= Code && Code <= 127) { \ + Buf = Code; \ + } else { \ + asm("call\ttpenc" \ + : "=a"(Buf), "+D"(Code) \ + : /* inputs */ \ + : "cc"); \ + } \ + Buf; \ }) #endif diff --git a/libc/str/varint.h b/libc/str/varint.h deleted file mode 100644 index a0b753dc..00000000 --- a/libc/str/varint.h +++ /dev/null @@ -1,87 +0,0 @@ -#ifndef COSMOPOLITAN_LIBC_VARINT_H_ -#define COSMOPOLITAN_LIBC_VARINT_H_ -#include "libc/limits.h" -#if !(__ASSEMBLER__ + __LINKER__ + 0) - -#define unzigzag(x) ((x >> 1) ^ -(x & 1)) -#define zigzag(x) _Generic((x), int32_t : zigzag32, default : zigzag64)(x) - -forceinline int32_t zigzag32(int32_t x) { - return (int64_t)((uint64_t)x << 1) ^ (x >> 31); -} - -forceinline int64_t zigzag64(int64_t x) { - return (int64_t)((uint64_t)x << 1) ^ (x >> 63); -} - -/** - * Copies variable-length integer to buffer. - * - * @param p needs 10+ bytes for 64-bit and 5+ for 32-bit - * @param x is unsigned number to encode - * @return pointer past last written byte - * @see writesint() which has more efficient encoding for signed numbers - */ -forceinline uint8_t *writevint(uint8_t *p, uint64_t x) { - do { - *p++ = (uint8_t)(x | 0x80); - x >>= 7; - } while (x > 0); - p[-1] &= 0x7f; - return p; -} - -/** - * Copies variable-length signed integer to buffer. - * - * @param p needs 10+ bytes for 64-bit and 5+ for 32-bit - * @param x is unsigned number to encode - * @return pointer past last written byte - */ -forceinline uint8_t *writesint(uint8_t *p, int64_t x) { - return writevint(p, zigzag(x)); -} - -/** - * Reads variable-length integer from buffer. - * - * @param x is unsigned number to encode - * @return pointer past last read byte or -1ul on error - */ -forceinline uint8_t *readvint(uint8_t *p, uint8_t *pe, uint64_t *res) { - uint8_t b; - uint64_t x, o; - x = 0; - b = 0; - while (p < pe) { - o = *p++; - x |= ((uint64_t)o & 0x7f) << b; - if (!(o & 0x80)) { - *res = x; - return p; - } - if ((b += 7) > 64) { - break; - } - } - return (uint8_t *)-1ul; -} - -/** - * Reads variable-length zig-zagged integer from buffer. - * - * @param x is unsigned number to encode - * @return pointer past last read byte or -1ul on error - */ -forceinline uint8_t *readsint(uint8_t *p, uint8_t *pe, int64_t *res) { - uint64_t u; - if ((p = readvint(p, pe, &u)) != (uint8_t *)-1ul) { - *res = unzigzag((int64_t)u); - return p; - } else { - return (uint8_t *)-1ul; - } -} - -#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ -#endif /* COSMOPOLITAN_LIBC_VARINT_H_ */ diff --git a/libc/nexgen32e/wcscspn.c b/libc/str/wcscspn.c similarity index 97% rename from libc/nexgen32e/wcscspn.c rename to libc/str/wcscspn.c index c45b61eb..b25cdb09 100644 --- a/libc/nexgen32e/wcscspn.c +++ b/libc/str/wcscspn.c @@ -23,6 +23,6 @@ #undef strcspn #define char wchar_t #define HasCharacter HasCharacterWide -#define strcspn wcscspn +#define strcspn wcscspn -#include "libc/nexgen32e/strcspn.c" +#include "libc/str/strcspn.c" diff --git a/libc/nexgen32e/wcspbrk.c b/libc/str/wcspbrk.c similarity index 95% rename from libc/nexgen32e/wcspbrk.c rename to libc/str/wcspbrk.c index 347f28ee..1a1a7676 100644 --- a/libc/nexgen32e/wcspbrk.c +++ b/libc/str/wcspbrk.c @@ -23,6 +23,7 @@ #undef strpbrk #define char wchar_t #define HasCharacter HasCharacterWide -#define strpbrk wcspbrk +#define strpbrk wcspbrk +#define strchr(x, y) wcschr(x, y) -#include "libc/nexgen32e/strpbrk.c" +#include "libc/str/strpbrk.c" diff --git a/libc/nexgen32e/wcsspn.c b/libc/str/wcsspn.c similarity index 97% rename from libc/nexgen32e/wcsspn.c rename to libc/str/wcsspn.c index a0549b31..9381a9ae 100644 --- a/libc/nexgen32e/wcsspn.c +++ b/libc/str/wcsspn.c @@ -23,6 +23,6 @@ #undef strspn #define char wchar_t #define HasCharacter HasCharacterWide -#define strspn wcsspn +#define strspn wcsspn -#include "libc/nexgen32e/strspn.c" +#include "libc/str/strspn.c" diff --git a/libc/nexgen32e/wmemset.c b/libc/str/wmemset.c similarity index 100% rename from libc/nexgen32e/wmemset.c rename to libc/str/wmemset.c diff --git a/libc/stubs/abort.S b/libc/stubs/abort.S index de14fbc2..e2445e66 100644 --- a/libc/stubs/abort.S +++ b/libc/stubs/abort.S @@ -29,4 +29,4 @@ abort: push %bp mov %sp,%bp rlcall panic int3 - .endfn abort,weak,protected + .endfn abort,weak diff --git a/libc/stubs/addvdi3.S b/libc/stubs/addvdi3.S index 20358914..9de3dcd5 100644 --- a/libc/stubs/addvdi3.S +++ b/libc/stubs/addvdi3.S @@ -18,9 +18,9 @@ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/macros.h" -.align 16 -.text.likely .source __FILE__ +.privileged +.alignfunc / Returns 𝑥+𝑦, aborting on overflow. / diff --git a/libc/stubs/addvsi3.S b/libc/stubs/addvsi3.S index 5ee51f10..5c0cdb09 100644 --- a/libc/stubs/addvsi3.S +++ b/libc/stubs/addvsi3.S @@ -18,9 +18,9 @@ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/macros.h" -.align 16 -.text.likely .source __FILE__ +.privileged +.alignfunc / Returns 𝑥+𝑦, aborting on overflow. / diff --git a/libc/stubs/addvti3.S b/libc/stubs/addvti3.S index cb32e8be..b9cfda25 100644 --- a/libc/stubs/addvti3.S +++ b/libc/stubs/addvti3.S @@ -18,9 +18,9 @@ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/macros.h" -.align 16 -.text.likely .source __FILE__ +.privileged +.alignfunc / Returns 𝑥+𝑦, aborting on overflow. / @@ -30,8 +30,9 @@ / @see -ftrapv __addvti3: mov %rdi,%rax - add %rsi,%rax - adc %rdi,%rdx + add %rdx,%rax + mov %rsi,%rdx + adc %rcx,%rdx jo 1f ret 1: push %rbp diff --git a/libc/stubs/assertfail.S b/libc/stubs/assertfail.S index afee3605..f5aef21d 100644 --- a/libc/stubs/assertfail.S +++ b/libc/stubs/assertfail.S @@ -28,6 +28,7 @@ / @mode long,legacy,real / @noreturn __assert_fail: + int3 push %bp mov %sp,%bp rlcall panic diff --git a/libc/stubs/errnolocation.S b/libc/stubs/errnolocation.S index 7a522205..cebb3f35 100644 --- a/libc/stubs/errnolocation.S +++ b/libc/stubs/errnolocation.S @@ -24,7 +24,6 @@ / Returns address of errno variable. / @note this isn't a universal definition __errno_location: - .leafprologue - lea errno(%rip),%rax - .leafepilogue + ezlea errno,ax + ret .endfn __errno_location,globl,hidden diff --git a/libc/stubs/exit.S b/libc/stubs/exit.S index 6a454d89..c11b5664 100644 --- a/libc/stubs/exit.S +++ b/libc/stubs/exit.S @@ -32,6 +32,7 @@ / @noreturn _exit: push %bp mov %sp,%bp + .weak _Exit rlcall _Exit int3 - .endfn _exit,weak,protected + .endfn _exit,weak diff --git a/libc/stubs/exit11.S b/libc/stubs/exit11.S deleted file mode 100644 index 6be10457..00000000 --- a/libc/stubs/exit11.S +++ /dev/null @@ -1,36 +0,0 @@ -/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│ -│vi: set et ft=asm ts=8 tw=8 fenc=utf-8 :vi│ -╞══════════════════════════════════════════════════════════════════════════════╡ -│ Copyright 2020 Justine Alexandra Roberts Tunney │ -│ │ -│ This program is free software; you can redistribute it and/or modify │ -│ it under the terms of the GNU General Public License as published by │ -│ the Free Software Foundation; version 2 of the License. │ -│ │ -│ This program is distributed in the hope that it will be useful, but │ -│ WITHOUT ANY WARRANTY; without even the implied warranty of │ -│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ -│ General Public License for more details. │ -│ │ -│ You should have received a copy of the GNU General Public License │ -│ along with this program; if not, write to the Free Software │ -│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ -│ 02110-1301 USA │ -╚─────────────────────────────────────────────────────────────────────────────*/ -#include "ape/macros.h" -.real -.source __FILE__ -.code16 # ∩ .code32 ∩ .code64 - -/ Thrice re-imagined Unix user process termination API stub. -/ -/ @param edi is exit code ∈ [0,256) -/ @see libc/runtime/_exit.c -/ @mode long,legacy,real -/ @asyncsignalsafe -/ @noreturn -_Exit: push %bp - mov %sp,%bp - rlcall panic - int3 - .endfn _Exit,weak,protected diff --git a/libc/stubs/ld.S b/libc/stubs/ld.S index 1c10e93d..2c87b6f2 100644 --- a/libc/stubs/ld.S +++ b/libc/stubs/ld.S @@ -34,15 +34,13 @@ __privileged_start = 0 __test_start = 0 __ro = 0 - __piro_start = 0 + __relo_start = 0 __relo_end = 0 - __piro_end = 0 .globl _base .globl ape.xlm - .globl __piro_start + .globl __relo_start .globl __relo_end - .globl __piro_end .globl __privileged_start .globl __ro .globl __test_start @@ -54,9 +52,8 @@ .weak _base .weak ape.xlm - .weak __piro_start + .weak __relo_start .weak __relo_end - .weak __piro_end .weak __privileged_start .weak __ro .weak __test_start diff --git a/libc/stubs/mulvdi3.S b/libc/stubs/mulvdi3.S index 59bd96bd..923e4ef4 100644 --- a/libc/stubs/mulvdi3.S +++ b/libc/stubs/mulvdi3.S @@ -18,9 +18,9 @@ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/macros.h" -.align 16 -.text.likely .source __FILE__ +.privileged +.alignfunc / Returns 𝑥*𝑦, aborting on overflow. / @@ -31,6 +31,11 @@ __mulvdi3: mov %rdi,%rax imul %rsi - jc __on_arithmetic_overflow + jc 1f + ret +1: push %rbp + mov %rsp,%rbp + call __on_arithmetic_overflow + pop %rbp ret .endfn __mulvdi3,globl diff --git a/libc/stubs/mulvsi3.S b/libc/stubs/mulvsi3.S index 1b50ac3c..69280efc 100644 --- a/libc/stubs/mulvsi3.S +++ b/libc/stubs/mulvsi3.S @@ -18,9 +18,9 @@ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/macros.h" -.align 16 -.text.likely .source __FILE__ +.privileged +.alignfunc / Returns 𝑥*𝑦, aborting on overflow. / @@ -31,6 +31,11 @@ __mulvsi3: mov %edi,%eax imul %esi - jc __on_arithmetic_overflow + jc 1f + ret +1: push %rbp + mov %rsp,%rbp + call __on_arithmetic_overflow + pop %rbp ret .endfn __mulvsi3,globl diff --git a/libc/stubs/mulvti3.S b/libc/stubs/mulvti3.S index 5a46deba..31979b6b 100644 --- a/libc/stubs/mulvti3.S +++ b/libc/stubs/mulvti3.S @@ -18,27 +18,119 @@ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/macros.h" -.align 16 -.text.likely .source __FILE__ +.privileged +.alignfunc / Returns 𝑥*𝑦, aborting on overflow. / -/ TODO(jart): is this ok? -/ / @param rdi:rsi is int128 𝑥 / @param rdx:rcx is int128 𝑦 / @return rdx:rax is 𝑥*𝑦 / @see -ftrapv __mulvti3: + push %rbp + mov %rsp,%rbp + push %rbx + push %rbx + push %r12 + push %r13 + push %r14 + push %r15 + mov %rdx,%r10 + mov %rdi,%rdx + xor %r11d,%r11d + mov %r10,%rax + sar $63,%rdx + sar $63,%rax + cmp %rsi,%rdx + jne 4f + cmp %rcx,%rax + jne 5f + mov %rdi,%rax + imul %r10 + mov %rax,%r14 + mov %rdx,%r8 + jmp 2f +5: mov %r10,%r12 + mov %rcx,%r13 + mov %rcx,%r8 + mov %rdi,%rbx + jmp 6f +4: cmp %rcx,%rax + jne 7f + mov %rdi,%r12 + mov %rsi,%r13 + mov %rsi,%r8 + mov %r10,%rbx +6: mov %rdi,%rax + mul %r10 + mov %rax,%r14 + mov %rbx,%rax + mov %rdx,%r15 + mul %r8 + test %r8,%r8 + jns 8f + xor %r8d,%r8d + sub %r8,%rax + sbb %rbx,%rdx +8: test %rbx,%rbx + jns 9f + sub %r12,%rax + sbb %r13,%rdx +9: mov %r15,%r8 + xor %r9d,%r9d + add %rax,%r8 + adc %rdx,%r9 + mov %r8,%rdx + sar $63,%rdx + cmp %r9,%rdx + je 2f + imul %r10,%rsi mov %rdi,%rax - imul %rdx,%rsi imul %rdi,%rcx - add %rsi,%rcx - mul %rdx - jc 1f - add %rcx,%rdx - jo 1f + mul %r10 + lea (%rsi,%rcx),%r8 + add %rdx,%r8 + mov %rax,%r14 + jmp 3f +7: mov %rsi,%r8 + mov %rcx,%rdx + mov %rdi,%rax + imul %rdi,%rdx + imul %r10,%r8 + add %rdx,%r8 + mul %r10 + mov %rax,%r14 + lea 1(%rsi),%rax + add %rdx,%r8 + cmp $1,%rax + ja 3f + lea 1(%rcx),%rax + cmp $1,%rax + ja 3f + cmp %rcx,%rsi + jne 11f + cmp %r14,%r11 + mov %r11,%rax + sbb %r8,%rax + jl 2f + jmp 3f +11: test %r8,%r8 + js 2f +3: mov $1,%r11d +2: test %r11,%r11 + je 1f + mov %r8,-8(%rbp) + call __on_arithmetic_overflow + mov -8(%rbp),%r8 +1: mov %r14,%rax + mov %r8,%rdx + pop %r15 + pop %r14 + pop %r13 + pop %r12 + pop %rbx + leave ret -1: jmp __on_arithmetic_overflow .endfn __mulvti3,globl diff --git a/libc/stubs/negvdi2.S b/libc/stubs/negvdi2.S index 35b318d6..04e5a4dc 100644 --- a/libc/stubs/negvdi2.S +++ b/libc/stubs/negvdi2.S @@ -18,9 +18,9 @@ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/macros.h" -.align 16 -.text.likely .source __FILE__ +.privileged +.alignfunc / Returns -𝑥, aborting on overflow (two's complement bane). / diff --git a/libc/stubs/negvsi2.S b/libc/stubs/negvsi2.S index 17498f22..86da3004 100644 --- a/libc/stubs/negvsi2.S +++ b/libc/stubs/negvsi2.S @@ -18,9 +18,9 @@ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/macros.h" -.align 16 -.text.likely .source __FILE__ +.privileged +.alignfunc / Returns -𝑥, aborting on overflow (two's complement bane). / diff --git a/libc/stubs/negvti2.S b/libc/stubs/negvti2.S index 77895fea..a9d95dbf 100644 --- a/libc/stubs/negvti2.S +++ b/libc/stubs/negvti2.S @@ -18,9 +18,9 @@ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/macros.h" -.align 16 -.text.likely .source __FILE__ +.privileged +.alignfunc / Returns -𝑥, aborting on overflow. / @@ -31,11 +31,14 @@ __negvti2: mov %rdi,%rax mov %rsi,%rdx neg %rax + not %rdx + cmc adc $0,%rdx -/ TODO(jart): Make sure this is correct. - jo 1f - neg %rdx jo 1f ret -1: jmp __on_arithmetic_overflow +1: push %rbp + mov %rsp,%rbp + call __on_arithmetic_overflow + pop %rbp + ret .endfn __negvti2,globl diff --git a/libc/stubs/onarithmeticoverflow.S b/libc/stubs/onarithmeticoverflow.S index 6f537e32..fc6738ad 100644 --- a/libc/stubs/onarithmeticoverflow.S +++ b/libc/stubs/onarithmeticoverflow.S @@ -18,12 +18,10 @@ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/macros.h" -.text.unlikely +.privileged .source __FILE__ / Arithmetic overflow handler. -/ -/ @see libc/testlib/arithmeticoverflowhook.S / @see -ftrapv __on_arithmetic_overflow: push %rbp diff --git a/libc/stubs/panic.S b/libc/stubs/panic.S index 4ccc602b..6c7e2002 100644 --- a/libc/stubs/panic.S +++ b/libc/stubs/panic.S @@ -30,4 +30,4 @@ panic: push %bp cli 1: hlt jmp 1b - .endfn panic,weak,protected + .endfn panic,weak diff --git a/libc/runtime/munmap-thunk.S b/libc/stubs/retpoline.S similarity index 92% rename from libc/runtime/munmap-thunk.S rename to libc/stubs/retpoline.S index 7ed2c55f..55d2d0cb 100644 --- a/libc/runtime/munmap-thunk.S +++ b/libc/stubs/retpoline.S @@ -18,16 +18,9 @@ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/macros.h" -.source __FILE__ -munmap: push %rbp - mov %rsp,%rbp - push %rbx - push %rbx - ezlea _base,bx - call __munmap - pop %rbx - pop %rbx - pop %rbp +/ See -mfunction-return=thunk +__x86_return_thunk: ret - .endfn munmap,globl + .endfn __x86_return_thunk,weak + .source __FILE__ diff --git a/libc/sysv/stackchkguard.S b/libc/stubs/stackchkguard.S similarity index 85% rename from libc/sysv/stackchkguard.S rename to libc/stubs/stackchkguard.S index 2099c060..e8069fec 100644 --- a/libc/sysv/stackchkguard.S +++ b/libc/stubs/stackchkguard.S @@ -19,7 +19,6 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/macros.h" #include "libc/notice.inc" -#include "libc/sysv/consts/auxv.h" .source __FILE__ / Canary for -fstack-protector. @@ -28,24 +27,15 @@ / The -mstack-protector-guard=global flag might need to be passed. / / @note this value is protected by piro - .initbss 301,_init___stack_chk_guard + .initbss 200,_init___stack_chk_guard __stack_chk_guard: .quad 0 .endobj __stack_chk_guard,globl .previous .init.start 301,_init___stack_chk_guard - mov kStartTsc(%rip),%rax - mov AT_RANDOM,%edx # modern linux provides this - test %edx,%edx - jz 1f - pushpop -16,%rcx -0: add $16,%rcx # ax ^= di[1][0] if di[0]=AT_RANDOM - cmpq $0,(%r15,%rcx) # %r15 is qword(*auxv)[2] - jz 1f # @see libc/crt/crt.S - cmp %rdx,(%r15,%rcx) - jne 0b - mov 8(%r15,%rcx),%rcx - xor (%rcx),%rax -1: stosq + rdtsc + stosl + xchg %edx,%eax + stosl .init.end 301,_init___stack_chk_guard diff --git a/libc/stubs/stubs.mk b/libc/stubs/stubs.mk index 397eefda..b61c9baf 100644 --- a/libc/stubs/stubs.mk +++ b/libc/stubs/stubs.mk @@ -17,7 +17,7 @@ PKGS += LIBC_STUBS LIBC_STUBS_ARTIFACTS += LIBC_STUBS_A LIBC_STUBS = $(LIBC_STUBS_A) -LIBC_STUBS_A = o/libc/stubs/stubs.a +LIBC_STUBS_A = o/$(MODE)/libc/stubs/stubs.a LIBC_STUBS_A_FILES := $(wildcard libc/stubs/*.S) LIBC_STUBS_A_HDRS = \ @@ -28,7 +28,7 @@ LIBC_STUBS_A_SRCS = \ LIBC_STUBS_A_OBJS = \ $(LIBC_STUBS_A_SRCS:%=o/$(MODE)/%.zip.o) \ - $(LIBC_STUBS_A_SRCS:%.S=o/%.o) + $(LIBC_STUBS_A_SRCS:%.S=o/$(MODE)/%.o) LIBC_STUBS_A_CHECKS = \ $(LIBC_STUBS_A).pkg \ @@ -47,7 +47,7 @@ LIBC_STUBS_LIBS = $(foreach x,$(LIBC_STUBS_ARTIFACTS),$($(x))) LIBC_STUBS_SRCS = $(foreach x,$(LIBC_STUBS_ARTIFACTS),$($(x)_SRCS)) LIBC_STUBS_CHECKS = $(foreach x,$(LIBC_STUBS_ARTIFACTS),$($(x)_CHECKS)) LIBC_STUBS_OBJS = $(foreach x,$(LIBC_STUBS_ARTIFACTS),$($(x)_OBJS)) -$(LIBC_STUBS_OBJS): $(BUILD_FILES) libc/stubs/stubs.mk -.PHONY: o/libc/stubs -o/libc/stubs: $(LIBC_STUBS_CHECKS) +.PHONY: o/$(MODE)/libc/stubs +o/$(MODE)/libc/stubs: \ + $(LIBC_STUBS_CHECKS) diff --git a/libc/stubs/subvdi3.S b/libc/stubs/subvdi3.S index 7a3fde19..5d2b857d 100644 --- a/libc/stubs/subvdi3.S +++ b/libc/stubs/subvdi3.S @@ -18,9 +18,9 @@ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/macros.h" -.align 16 -.text.likely .source __FILE__ +.privileged +.alignfunc / Returns 𝑥-𝑦, aborting on overflow. / diff --git a/libc/stubs/subvsi3.S b/libc/stubs/subvsi3.S index 91046921..0b5c3d69 100644 --- a/libc/stubs/subvsi3.S +++ b/libc/stubs/subvsi3.S @@ -18,9 +18,9 @@ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/macros.h" -.align 16 -.text.likely .source __FILE__ +.privileged +.alignfunc / Returns 𝑥-𝑦, aborting on overflow. / diff --git a/libc/stubs/subvti3.S b/libc/stubs/subvti3.S index 5c59b966..5b3cc09f 100644 --- a/libc/stubs/subvti3.S +++ b/libc/stubs/subvti3.S @@ -18,9 +18,9 @@ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/macros.h" -.align 16 -.text.likely .source __FILE__ +.privileged +.alignfunc / Returns 𝑥-𝑦, aborting on overflow. / @@ -30,8 +30,9 @@ / @see -ftrapv __subvti3: mov %rdi,%rax - sub %rsi,%rax - sbb %rdi,%rdx + sub %rdx,%rax + mov %rsi,%rdx + sbb %rcx,%rdx jo 1f ret 1: push %rbp diff --git a/libc/nexgen32e/sysv2nt.S b/libc/stubs/sysv2nt.S similarity index 100% rename from libc/nexgen32e/sysv2nt.S rename to libc/stubs/sysv2nt.S diff --git a/libc/stubs/zip.S b/libc/stubs/zip.S index 6c9e860c..4348e00c 100644 --- a/libc/stubs/zip.S +++ b/libc/stubs/zip.S @@ -18,9 +18,33 @@ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/macros.h" +#include "ape/relocations.h" +#include "libc/zip.h" +/ ZIP Central Directory. + .section .piro.data.sort.zip.3,"a",@progbits + .hidden __zip_start + .globl __zip_start + .type __zip_start,@object + .align kZipCdirAlign __zip_start: - .endfn __zip_start,weak - + .previous/* + ... + decentralized content + ... + */.section .piro.data.sort.zip.5,"a",@progbits + .align kZipCdirAlign __zip_end: - .endfn __zip_end,weak + .long kZipCdirHdrMagic # magic + .short 0 # disk + .short 0 # starting disk + .short v_zip_records # records on disk + .short v_zip_records # records + .long v_zip_cdirsize # size of central directory + .long RVA(__zip_start) # central directory offset + .short v_zip_commentsize # comment size + .endobj __zip_end,globl,hidden + .weak v_zip_records + .weak v_zip_cdirsize + .weak v_zip_commentsize + .previous diff --git a/libc/sysv/calls/__utimensat-sysv.s b/libc/sysv/calls/__utimensat-sysv.s new file mode 100644 index 00000000..dc63f6d8 --- /dev/null +++ b/libc/sysv/calls/__utimensat-sysv.s @@ -0,0 +1,2 @@ +.include "o/libc/sysv/macros.inc" +.scall __utimensat$sysv 0x00540223ffff0118 globl hidden diff --git a/libc/sysv/calls/futimens-sysv.s b/libc/sysv/calls/futimens-sysv.s new file mode 100644 index 00000000..08389390 --- /dev/null +++ b/libc/sysv/calls/futimens-sysv.s @@ -0,0 +1,2 @@ +.include "o/libc/sysv/macros.inc" +.scall futimens$sysv 0x00550222ffffffff globl hidden diff --git a/libc/sysv/calls/futimens.s b/libc/sysv/calls/futimens.s deleted file mode 100644 index f4df3fb1..00000000 --- a/libc/sysv/calls/futimens.s +++ /dev/null @@ -1,2 +0,0 @@ -.include "o/libc/sysv/macros.inc" -.scall futimens 0x00550222ffffffff globl diff --git a/libc/sysv/calls/futimes-sysv.s b/libc/sysv/calls/futimes-sysv.s new file mode 100644 index 00000000..b2a9f916 --- /dev/null +++ b/libc/sysv/calls/futimes-sysv.s @@ -0,0 +1,2 @@ +.include "o/libc/sysv/macros.inc" +.scall futimes$sysv 0x004d00ce208bffff globl hidden diff --git a/libc/sysv/calls/futimes.s b/libc/sysv/calls/futimes.s deleted file mode 100644 index d4e0336c..00000000 --- a/libc/sysv/calls/futimes.s +++ /dev/null @@ -1,2 +0,0 @@ -.include "o/libc/sysv/macros.inc" -.scall futimes 0x004d00ce208bffff globl diff --git a/libc/sysv/calls/futimesat-sysv.s b/libc/sysv/calls/futimesat-sysv.s new file mode 100644 index 00000000..c1afdd06 --- /dev/null +++ b/libc/sysv/calls/futimesat-sysv.s @@ -0,0 +1,2 @@ +.include "o/libc/sysv/macros.inc" +.scall futimesat$sysv 0xffff01eeffff0105 globl hidden diff --git a/libc/sysv/calls/futimesat.s b/libc/sysv/calls/futimesat.s deleted file mode 100644 index 59605f0e..00000000 --- a/libc/sysv/calls/futimesat.s +++ /dev/null @@ -1,2 +0,0 @@ -.include "o/libc/sysv/macros.inc" -.scall futimesat 0xffff01eeffff0105 globl hidden diff --git a/libc/sysv/calls/utimensat.s b/libc/sysv/calls/utimensat.s deleted file mode 100644 index 89fe6103..00000000 --- a/libc/sysv/calls/utimensat.s +++ /dev/null @@ -1,2 +0,0 @@ -.include "o/libc/sysv/macros.inc" -.scall utimensat 0x00540223ffff0118 globl hidden diff --git a/libc/sysv/consts.sh b/libc/sysv/consts.sh index e8221fd4..5b68920c 100755 --- a/libc/sysv/consts.sh +++ b/libc/sysv/consts.sh @@ -410,15 +410,21 @@ syscon fcntl F_GETPIPE_SZ 0x0408 0 0 0 0 # # group name GNU/Systemd XNU's Not UNIX FreeBSD OpenBSD XENIX Commentary syscon at AT_FDCWD -100 -2 -100 -100 -100 # faked nt -syscon at AT_SYMLINK_FOLLOW 0x0400 0x40 0x0400 4 0 syscon at AT_SYMLINK_NOFOLLOW 0x0100 0x20 0x0200 2 0 # TODO(jart): What should NT do? syscon at AT_REMOVEDIR 0x0200 0x80 0x0800 8 0x0200 # faked nt syscon at AT_EACCESS 0x0200 0x10 0x0100 1 0 +syscon at AT_SYMLINK_FOLLOW 0x0400 0x40 0x0400 4 0 syscon at AT_EMPTY_PATH 0x1000 0 0 0 0 # linux 2.6.39+; see unlink, O_TMPFILE, etc. syscon memfd MFD_CLOEXEC 1 0 0 0 0 syscon memfd MFD_ALLOW_SEALING 2 0 0 0 0 +# utimensat() +# +# group name GNU/Systemd XNU's Not UNIX FreeBSD OpenBSD XENIX Commentary +syscon utime UTIME_NOW 0x3fffffff 0x3fffffff -1 -2 0x3fffffff # polyfilled xnu/nt +syscon utime UTIME_OMIT 0x3ffffffe 0x3ffffffe -2 -1 0x3ffffffe # polyfilled xnu/nt + # getauxval() keys # # group name GNU/Systemd XNU's Not UNIX FreeBSD OpenBSD XENIX Commentary @@ -2701,9 +2707,6 @@ syscon misc T_FMT_AMPM 0x02002b 4 4 3 0 syscon misc UL_GETFSIZE 1 1 1 0 0 syscon misc UL_SETFSIZE 2 2 2 0 0 -syscon misc UTIME_NOW 0x3fffffff 0 -1 -2 0 -syscon misc UTIME_OMIT 0x3ffffffe 0 -2 -1 0 - syscon misc XATTR_CREATE 1 2 0 0 0 syscon misc XATTR_REPLACE 2 4 0 0 0 diff --git a/libc/sysv/consts/GLOB_ABORTED.s b/libc/sysv/consts/GLOB_ABORTED.s deleted file mode 100644 index 2c5a7356..00000000 --- a/libc/sysv/consts/GLOB_ABORTED.s +++ /dev/null @@ -1,2 +0,0 @@ -.include "libc/sysv/consts/syscon.inc" -.syscon glob GLOB_ABORTED 2 -2 -2 -2 0 diff --git a/libc/sysv/consts/GLOB_APPEND.s b/libc/sysv/consts/GLOB_APPEND.s deleted file mode 100644 index 51eb9235..00000000 --- a/libc/sysv/consts/GLOB_APPEND.s +++ /dev/null @@ -1,2 +0,0 @@ -.include "libc/sysv/consts/syscon.inc" -.syscon glob GLOB_APPEND 0x20 1 1 1 0 diff --git a/libc/sysv/consts/GLOB_DOOFFS.s b/libc/sysv/consts/GLOB_DOOFFS.s deleted file mode 100644 index 21265c62..00000000 --- a/libc/sysv/consts/GLOB_DOOFFS.s +++ /dev/null @@ -1,2 +0,0 @@ -.include "libc/sysv/consts/syscon.inc" -.syscon glob GLOB_DOOFFS 8 2 2 2 0 diff --git a/libc/sysv/consts/GLOB_ERR.s b/libc/sysv/consts/GLOB_ERR.s deleted file mode 100644 index 54e71bfd..00000000 --- a/libc/sysv/consts/GLOB_ERR.s +++ /dev/null @@ -1,2 +0,0 @@ -.include "libc/sysv/consts/syscon.inc" -.syscon glob GLOB_ERR 1 4 4 4 0 diff --git a/libc/sysv/consts/GLOB_MARK.s b/libc/sysv/consts/GLOB_MARK.s deleted file mode 100644 index e59b22a4..00000000 --- a/libc/sysv/consts/GLOB_MARK.s +++ /dev/null @@ -1,2 +0,0 @@ -.include "libc/sysv/consts/syscon.inc" -.syscon glob GLOB_MARK 2 8 8 8 0 diff --git a/libc/sysv/consts/GLOB_NOCHECK.s b/libc/sysv/consts/GLOB_NOCHECK.s deleted file mode 100644 index fd1e2341..00000000 --- a/libc/sysv/consts/GLOB_NOCHECK.s +++ /dev/null @@ -1,2 +0,0 @@ -.include "libc/sysv/consts/syscon.inc" -.syscon glob GLOB_NOCHECK 0x10 0x10 0x10 0x10 0 diff --git a/libc/sysv/consts/GLOB_NOESCAPE.s b/libc/sysv/consts/GLOB_NOESCAPE.s deleted file mode 100644 index 649644ae..00000000 --- a/libc/sysv/consts/GLOB_NOESCAPE.s +++ /dev/null @@ -1,2 +0,0 @@ -.include "libc/sysv/consts/syscon.inc" -.syscon glob GLOB_NOESCAPE 0x40 0x2000 0x2000 0x1000 0 diff --git a/libc/sysv/consts/GLOB_NOMATCH.s b/libc/sysv/consts/GLOB_NOMATCH.s deleted file mode 100644 index 19cba762..00000000 --- a/libc/sysv/consts/GLOB_NOMATCH.s +++ /dev/null @@ -1,2 +0,0 @@ -.include "libc/sysv/consts/syscon.inc" -.syscon glob GLOB_NOMATCH 3 -3 -3 -3 0 diff --git a/libc/sysv/consts/GLOB_NOSORT.s b/libc/sysv/consts/GLOB_NOSORT.s deleted file mode 100644 index 67112d81..00000000 --- a/libc/sysv/consts/GLOB_NOSORT.s +++ /dev/null @@ -1,2 +0,0 @@ -.include "libc/sysv/consts/syscon.inc" -.syscon glob GLOB_NOSORT 4 0x20 0x20 0x20 0 diff --git a/libc/sysv/consts/GLOB_NOSPACE.s b/libc/sysv/consts/GLOB_NOSPACE.s deleted file mode 100644 index 15e3a0e8..00000000 --- a/libc/sysv/consts/GLOB_NOSPACE.s +++ /dev/null @@ -1,2 +0,0 @@ -.include "libc/sysv/consts/syscon.inc" -.syscon glob GLOB_NOSPACE 1 -1 -1 -1 0 diff --git a/libc/sysv/consts/GLOB_NOSYS.s b/libc/sysv/consts/GLOB_NOSYS.s deleted file mode 100644 index 2d74a61d..00000000 --- a/libc/sysv/consts/GLOB_NOSYS.s +++ /dev/null @@ -1,2 +0,0 @@ -.include "libc/sysv/consts/syscon.inc" -.syscon glob GLOB_NOSYS 4 -4 -4 -4 0 diff --git a/libc/sysv/consts/UTIME_NOW.s b/libc/sysv/consts/UTIME_NOW.s index 509c2643..610beeb1 100644 --- a/libc/sysv/consts/UTIME_NOW.s +++ b/libc/sysv/consts/UTIME_NOW.s @@ -1,2 +1,2 @@ .include "libc/sysv/consts/syscon.inc" -.syscon misc UTIME_NOW 0x3fffffff 0 -1 -2 0 +.syscon utime UTIME_NOW 0x3fffffff 0x3fffffff -1 -2 0x3fffffff diff --git a/libc/sysv/consts/UTIME_OMIT.s b/libc/sysv/consts/UTIME_OMIT.s index db949173..6a6d6d3a 100644 --- a/libc/sysv/consts/UTIME_OMIT.s +++ b/libc/sysv/consts/UTIME_OMIT.s @@ -1,2 +1,2 @@ .include "libc/sysv/consts/syscon.inc" -.syscon misc UTIME_OMIT 0x3ffffffe 0 -2 -1 0 +.syscon utime UTIME_OMIT 0x3ffffffe 0x3ffffffe -2 -1 0x3ffffffe diff --git a/libc/sysv/consts/at.h b/libc/sysv/consts/at.h index 5503c6d7..aee4dfc3 100644 --- a/libc/sysv/consts/at.h +++ b/libc/sysv/consts/at.h @@ -14,14 +14,16 @@ hidden extern const long AT_SYMLINK_FOLLOW; hidden extern const long AT_SYMLINK_NOFOLLOW; hidden extern const long AT_REMOVEDIR; hidden extern const long AT_EACCESS; +hidden extern const long AT_EMPTY_PATH; COSMOPOLITAN_C_END_ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ -#define AT_FDCWD SYMBOLIC(AT_FDCWD) -#define AT_SYMLINK_FOLLOW SYMBOLIC(AT_SYMLINK_FOLLOW) +#define AT_FDCWD SYMBOLIC(AT_FDCWD) +#define AT_SYMLINK_FOLLOW SYMBOLIC(AT_SYMLINK_FOLLOW) #define AT_SYMLINK_NOFOLLOW SYMBOLIC(AT_SYMLINK_NOFOLLOW) -#define AT_REMOVEDIR SYMBOLIC(AT_REMOVEDIR) -#define AT_EACCESS SYMBOLIC(AT_EACCESS) +#define AT_REMOVEDIR SYMBOLIC(AT_REMOVEDIR) +#define AT_EACCESS SYMBOLIC(AT_EACCESS) +#define AT_EMPTY_PATH SYMBOLIC(AT_EMPTY_PATH) #endif /* COSMOPOLITAN_LIBC_SYSV_CONSTS_AT_H_ */ diff --git a/libc/sysv/consts/auxv.h b/libc/sysv/consts/auxv.h index 7c2a9435..3b32bc3c 100644 --- a/libc/sysv/consts/auxv.h +++ b/libc/sysv/consts/auxv.h @@ -9,7 +9,6 @@ hidden extern const long AT_BASE_PLATFORM; hidden extern const long AT_CLKTCK; hidden extern const long AT_DCACHEBSIZE; hidden extern const long AT_EGID; -hidden extern const long AT_EMPTY_PATH; hidden extern const long AT_ENTRY; hidden extern const long AT_EUID; hidden extern const long AT_EXECFD; @@ -33,30 +32,29 @@ hidden extern const long AT_UID; COSMOPOLITAN_C_END_ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ -#define AT_BASE SYMBOLIC(AT_BASE) +#define AT_BASE SYMBOLIC(AT_BASE) #define AT_BASE_PLATFORM SYMBOLIC(AT_BASE_PLATFORM) -#define AT_CLKTCK SYMBOLIC(AT_CLKTCK) -#define AT_DCACHEBSIZE SYMBOLIC(AT_DCACHEBSIZE) -#define AT_EGID SYMBOLIC(AT_EGID) -#define AT_EMPTY_PATH SYMBOLIC(AT_EMPTY_PATH) -#define AT_ENTRY SYMBOLIC(AT_ENTRY) -#define AT_EUID SYMBOLIC(AT_EUID) -#define AT_EXECFD SYMBOLIC(AT_EXECFD) -#define AT_EXECFN SYMBOLIC(AT_EXECFN) -#define AT_GID SYMBOLIC(AT_GID) -#define AT_ICACHEBSIZE SYMBOLIC(AT_ICACHEBSIZE) -#define AT_NOTELF SYMBOLIC(AT_NOTELF) -#define AT_NO_AUTOMOUNT SYMBOLIC(AT_NO_AUTOMOUNT) -#define AT_OSRELDATE SYMBOLIC(AT_OSRELDATE) -#define AT_PAGESZ SYMBOLIC(AT_PAGESZ) -#define AT_PHDR SYMBOLIC(AT_PHDR) -#define AT_PHENT SYMBOLIC(AT_PHENT) -#define AT_PHNUM SYMBOLIC(AT_PHNUM) -#define AT_PLATFORM SYMBOLIC(AT_PLATFORM) -#define AT_RANDOM SYMBOLIC(AT_RANDOM) -#define AT_SECURE SYMBOLIC(AT_SECURE) -#define AT_SYSINFO_EHDR SYMBOLIC(AT_SYSINFO_EHDR) -#define AT_UCACHEBSIZE SYMBOLIC(AT_UCACHEBSIZE) -#define AT_UID SYMBOLIC(AT_UID) +#define AT_CLKTCK SYMBOLIC(AT_CLKTCK) +#define AT_DCACHEBSIZE SYMBOLIC(AT_DCACHEBSIZE) +#define AT_EGID SYMBOLIC(AT_EGID) +#define AT_ENTRY SYMBOLIC(AT_ENTRY) +#define AT_EUID SYMBOLIC(AT_EUID) +#define AT_EXECFD SYMBOLIC(AT_EXECFD) +#define AT_EXECFN SYMBOLIC(AT_EXECFN) +#define AT_GID SYMBOLIC(AT_GID) +#define AT_ICACHEBSIZE SYMBOLIC(AT_ICACHEBSIZE) +#define AT_NOTELF SYMBOLIC(AT_NOTELF) +#define AT_NO_AUTOMOUNT SYMBOLIC(AT_NO_AUTOMOUNT) +#define AT_OSRELDATE SYMBOLIC(AT_OSRELDATE) +#define AT_PAGESZ SYMBOLIC(AT_PAGESZ) +#define AT_PHDR SYMBOLIC(AT_PHDR) +#define AT_PHENT SYMBOLIC(AT_PHENT) +#define AT_PHNUM SYMBOLIC(AT_PHNUM) +#define AT_PLATFORM SYMBOLIC(AT_PLATFORM) +#define AT_RANDOM SYMBOLIC(AT_RANDOM) +#define AT_SECURE SYMBOLIC(AT_SECURE) +#define AT_SYSINFO_EHDR SYMBOLIC(AT_SYSINFO_EHDR) +#define AT_UCACHEBSIZE SYMBOLIC(AT_UCACHEBSIZE) +#define AT_UID SYMBOLIC(AT_UID) #endif /* COSMOPOLITAN_LIBC_CALLS_AUXV_H_ */ diff --git a/libc/sysv/consts/utime.h b/libc/sysv/consts/utime.h new file mode 100644 index 00000000..1a134280 --- /dev/null +++ b/libc/sysv/consts/utime.h @@ -0,0 +1,16 @@ +#ifndef COSMOPOLITAN_LIBC_SYSV_CONSTS_UTIME_H_ +#define COSMOPOLITAN_LIBC_SYSV_CONSTS_UTIME_H_ +#include "libc/runtime/symbolic.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +hidden extern const int UTIME_NOW; +hidden extern const int UTIME_OMIT; + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ + +#define UTIME_NOW SYMBOLIC(UTIME_NOW) +#define UTIME_OMIT SYMBOLIC(UTIME_OMIT) + +#endif /* COSMOPOLITAN_LIBC_SYSV_CONSTS_UTIME_H_ */ diff --git a/libc/sysv/syscall.S b/libc/sysv/syscall.S index d85e2ef3..0a60b846 100644 --- a/libc/sysv/syscall.S +++ b/libc/sysv/syscall.S @@ -18,6 +18,7 @@ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/macros.h" +.source __FILE__ / Performs raw System Five system call. / @@ -41,4 +42,3 @@ syscall:mov %rdi,%rax mov 8(%rsp),%r9 jmp *systemfive(%rip) .endfn syscall,globl - .source __FILE__ diff --git a/libc/sysv/syscalls.sh b/libc/sysv/syscalls.sh index 87a37ffc..e6a9c116 100755 --- a/libc/sysv/syscalls.sh +++ b/libc/sysv/syscalls.sh @@ -184,8 +184,6 @@ scall settimeofday 0x0044007a207a00a4 globl scall mount 0x0015001520a700a5 globl scall reboot 0x00370037203700a9 globl scall quotactl 0x0094009420a500b3 globl -scall 'utime$sysv' 0xffffffffffff0084 globl hidden -scall 'utimes$sysv' 0x004c008a208a00eb globl hidden scall setfsuid 0xffffffffffff007a globl scall setfsgid 0xffffffffffff007b globl scall capget 0xffffffffffff007d globl @@ -282,7 +280,11 @@ scall inotify_rm_watch 0xffffffffffff00ff globl scall 'openat$sysv' 0x014101f321cf0101 globl hidden # Linux 2.6.16+ (c. 2007) scall 'mkdirat$sysv' 0x013e01f021db0102 globl hidden scall 'fchownat$sysv' 0x013b01eb21d40104 globl hidden # @asyncsignalsafe -scall futimesat 0xffff01eeffff0105 globl hidden # @asyncsignalsafe +scall 'utime$sysv' 0xffffffffffff0084 globl hidden +scall 'utimes$sysv' 0x004c008a208a00eb globl hidden +scall 'futimesat$sysv' 0xffff01eeffff0105 globl hidden # @asyncsignalsafe +scall 'futimes$sysv' 0x004d00ce208bffff globl hidden +scall 'futimens$sysv' 0x00550222ffffffff globl hidden scall '__fstatat$sysv' 0x002a022821d60106 globl hidden # a.k.a. newfstatat(); FreeBSD 12+; needs stat2linux() scall 'unlinkat$sysv' 0x014501f721d80107 globl hidden scall 'renameat$sysv' 0x014301f521d10108 globl hidden @@ -301,7 +303,7 @@ scall move_pages 0xffffffffffff0117 globl # NOTE: We view Red Hat versio #──────────────────────RHEL 5.0 LIMIT──────────────────────── # ←┬─ last gplv2 distro w/ sysv init was rhel5 c. 2007 scall '__preadv$sysv' 0x010b0121ffff0127 globl hidden # ├─ cosmopolitan at minimum requires rhel5 scall '__pwritev$sysv' 0x010c0122ffff0128 globl hidden # ├─ python modules need to work on this (pep513) -scall utimensat 0x00540223ffff0118 globl hidden # └─ end of life 2020-11-30 (extended) +scall '__utimensat$sysv' 0x00540223ffff0118 globl hidden # └─ end of life 2020-11-30 (extended) scall 'fallocate$sysv' 0xffffffffffff011d globl hidden scall 'posix_fallocate$sysv' 0xffff0212ffffffff globl hidden scall '__accept4$sysv' 0x005d021dffff0120 globl hidden # Linux 2.6.28+ @@ -380,7 +382,6 @@ scall getfh 0x00a100a120a1ffff globl scall chflags 0x002200222022ffff globl scall getfsstat 0x003e022d215bffff globl scall nfssvc 0x009b009b209bffff globl -scall futimes 0x004d00ce208bffff globl scall adjtime 0x008c008c208cffff globl scall fchflags 0x002300232023ffff globl scall '__seteuid$bsd' 0x00b700b720b7ffff globl hidden # wrapped via setreuid() @@ -424,7 +425,6 @@ scall auditon 0xffff01be215fffff globl scall msgsys 0xffff00aa20fcffff globl scall shmsys 0xffff00ab20fdffff globl #─────────────────────FREEBSD & OPENBSD────────────────────── -scall futimens 0x00550222ffffffff globl scall fhstat 0x01260229ffffffff globl scall chflagsat 0x006b021cffffffff globl scall profil 0x002c002cffffffff globl diff --git a/libc/sysv/systemfive.S b/libc/sysv/systemfive.S index 21b19846..24b0666a 100644 --- a/libc/sysv/systemfive.S +++ b/libc/sysv/systemfive.S @@ -97,7 +97,10 @@ systemfive.linux: test %eax,%eax js systemfive.enosys mov %rcx,%r10 + push %rbp + mov %rsp,%rbp syscall + pop %rbp cmp $-4095,%rax jae systemfive.error ret @@ -123,7 +126,10 @@ systemfive.bsd: cmp $0xfff,%ax jae systemfive.enosys mov %rcx,%r10 + push %rbp + mov %rsp,%rbp syscall + pop %rbp jc systemfive.errno ret systemfive.xnu: @@ -160,52 +166,62 @@ systemfive.xnu: push %rbx push %rsi testb $XNU,(%rdi) # @see libc/crt/crt.S - jnz .Lsystemfive.init.xnu + jnz systemfive.init.xnu testb $FREEBSD,(%rdi) # @see libc/crt/crt.S - jnz .Lsystemfive.init.freebsd + jnz systemfive.init.freebsd testb $WINDOWS,(%rdi) # @see libc/runtime/winmain.c - jnz .Lsystemfive.init.windows + jnz systemfive.init.windows cmpq $0,(%r15) # OpenBSD doesn't have auxv - je .Lsystemfive.init.openbsd + je systemfive.init.openbsd / default state is safe state / 𝑠𝑙𝑖𝑑𝑒 -.Lsystemfive.init.linux: +systemfive.init.linux: pushb systemfive.linux-.Lanchorpoint push $LINUX ezlea syscon.linux,si - jmp .Lsystemfive.init.os -.Lsystemfive.init.windows: + jmp systemfive.init.os +systemfive.init.windows: pushb systemfive.enosys-.Lanchorpoint push $WINDOWS ezlea syscon.windows,si - jmp .Lsystemfive.init.os -.Lsystemfive.init.freebsd: + jmp systemfive.init.os +systemfive.init.freebsd: pushb systemfive.freebsd-.Lanchorpoint push $FREEBSD ezlea syscon.freebsd,si - jmp .Lsystemfive.init.os -.Lsystemfive.init.openbsd: + jmp systemfive.init.os +systemfive.init.openbsd: pushb systemfive.openbsd-.Lanchorpoint push $OPENBSD ezlea syscon.openbsd,si - jmp .Lsystemfive.init.os -.Lsystemfive.init.xnu: + jmp systemfive.init.os +systemfive.init.xnu: pushb systemfive.xnu-.Lanchorpoint push $XNU ezlea syscon.xnu,si / 𝑠𝑙𝑖𝑑𝑒 -.Lsystemfive.init.os: +systemfive.init.os: ezlea .Lanchorpoint,cx pop %rax stosq #→ hostos pop %rax add %rcx,%rax stosq #→ systemfive -.Lsystemfive.sleb128unpacker: push %rdi - ezlea .Lsyscon.start,di - orq $-1,%r9 - ezlea .Lsyscon.end,bx + ezlea syscon.start,di + ezlea syscon.end,bx + call systemfive.sleb128unpacker + pop %rdi +/ 𝑠𝑙𝑖𝑑𝑒 +systemfive.init.done: + pop %rsi + pop %rbx + .init.end 300,_init_systemfive,globl,hidden + + .text.startup +systemfive.sleb128unpacker: + .leafprologue + or $-1,%r9 2: cmp %rbx,%rdi jnb 5f xor %ecx,%ecx @@ -228,12 +244,8 @@ systemfive.xnu: cmovne (%rdi),%rax # @see WinMain() for example stosq jmp 2b -5: pop %rdi -/ 𝑠𝑙𝑖𝑑𝑒 -.Lsystemfive.init.done: - pop %rsi - pop %rbx - .init.end 300,_init_systemfive,globl,hidden +5: .leafepilogue + .previous / Sections for varint encoded numbers. / @@ -244,11 +256,11 @@ systemfive.xnu: / @see libc/sysv/consts/syscon.h .section .piro.bss.sort.syscon.1,"aw",@nobits .align 8 -.Lsyscon.start:/* +syscon.start:/* ...decentralized quadwords... */.previous .section .piro.bss.sort.syscon.3,"aw",@nobits -.Lsyscon.end: +syscon.end: .previous .section .sort.rodata.syscon.linux.1,"a",@progbits .align 1 @@ -275,10 +287,19 @@ syscon.openbsd:/* syscon.windows:/* ...decentralized leb128... */.previous - .type .Lsyscon.start,@object - .type .Lsyscon.end,@object + + .type syscon.start,@object + .type syscon.end,@object .type syscon.linux,@object .type syscon.xnu,@object .type syscon.freebsd,@object .type syscon.openbsd,@object .type syscon.windows,@object + + .globl syscon.start + .globl syscon.end + .globl syscon.linux + .globl syscon.xnu + .globl syscon.freebsd + .globl syscon.openbsd + .globl syscon.windows diff --git a/libc/sysv/sysv.mk b/libc/sysv/sysv.mk index b42b2ae4..76a72e97 100644 --- a/libc/sysv/sysv.mk +++ b/libc/sysv/sysv.mk @@ -25,15 +25,13 @@ LIBC_SYSV_A_SRCS_S = $(filter %.S,$(LIBC_SYSV_A_FILES)) LIBC_SYSV_A_CHECKS = $(LIBC_SYSV_A).pkg LIBC_SYSV_A_DIRECTDEPS = \ - LIBC_STUBS \ - LIBC_NEXGEN32E + LIBC_STUBS LIBC_SYSV_A_FILES := \ libc/sysv/macros.h \ libc/sysv/errfuns.h \ libc/sysv/g_syscount.S \ libc/sysv/restorert.S \ - libc/sysv/stackchkguard.S \ libc/sysv/syscall.S \ libc/sysv/systemfive.S \ $(wildcard libc/sysv/stubs/*) \ @@ -45,9 +43,12 @@ LIBC_SYSV_A_SRCS = \ $(LIBC_SYSV_A_SRCS_S) LIBC_SYSV_A_OBJS = \ - $(LIBC_SYSV_A_SRCS_A:%.s=o/%.o) \ + $(LIBC_SYSV_A_SRCS_A:%.s=o/$(MODE)/%.o) \ $(LIBC_SYSV_A_SRCS_S:%.S=o/$(MODE)/%.o) \ - $(LIBC_SYSV_A_SRCS:%=o/$(MODE)/%.zip.o) + o/$(MODE)/libc/sysv/g_syscount.S.zip.o \ + o/$(MODE)/libc/sysv/restorert.S.zip.o \ + o/$(MODE)/libc/sysv/syscall.S.zip.o \ + o/$(MODE)/libc/sysv/systemfive.S.zip.o LIBC_SYSV_A_DEPS := \ $(call uniq,$(foreach x,$(LIBC_SYSV_A_DIRECTDEPS),$($(x)))) @@ -62,8 +63,7 @@ $(LIBC_SYSV_A).pkg: \ $(foreach x,$(LIBC_SYSV_A_DIRECTDEPS),$($(x)_A).pkg) $(LIBC_SYSV_A_OBJS): \ - libc/sysv/consts/syscon.inc \ - libc/sysv/sysv.mk + libc/sysv/consts/syscon.inc libc/sysv/consts/syscon.inc: libc/macros.inc @@ -74,9 +74,9 @@ LIBC_SYSV_CALLS = \ $(LIBC_SYSV_CALLS_A) LIBC_SYSV_ARTIFACTS += LIBC_SYSV_CALLS_A -LIBC_SYSV_CALLS_A = o/libc/sysv/calls.a +LIBC_SYSV_CALLS_A = o/$(MODE)/libc/sysv/calls.a LIBC_SYSV_CALLS_A_SRCS := $(wildcard libc/sysv/calls/*.s) -LIBC_SYSV_CALLS_A_OBJS = $(LIBC_SYSV_CALLS_A_SRCS:%.s=o/%.o) +LIBC_SYSV_CALLS_A_OBJS = $(LIBC_SYSV_CALLS_A_SRCS:%.s=o/$(MODE)/%.o) LIBC_SYSV_CALLS_A_CHECKS = $(LIBC_SYSV_CALLS_A).pkg LIBC_SYSV_CALLS_A_DIRECTDEPS = \ @@ -86,7 +86,6 @@ LIBC_SYSV_CALLS_A_DEPS := \ $(call uniq,$(foreach x,$(LIBC_SYSV_CALLS_A_DIRECTDEPS),$($(x)))) $(LIBC_SYSV_CALLS_A): \ - libc/sysv/calls/ \ $(LIBC_SYSV_CALLS_A).pkg \ $(LIBC_SYSV_CALLS_A_OBJS) @@ -104,9 +103,9 @@ LIBC_SYSV_MACHCALLS = \ $(LIBC_SYSV_MACHCALLS_A) LIBC_SYSV_ARTIFACTS += LIBC_SYSV_MACHCALLS_A -LIBC_SYSV_MACHCALLS_A = o/libc/sysv/machcalls.a +LIBC_SYSV_MACHCALLS_A = o/$(MODE)/libc/sysv/machcalls.a LIBC_SYSV_MACHCALLS_A_SRCS := $(wildcard libc/sysv/machcalls/*.s) -LIBC_SYSV_MACHCALLS_A_OBJS = $(LIBC_SYSV_MACHCALLS_A_SRCS:%.s=o/%.o) +LIBC_SYSV_MACHCALLS_A_OBJS = $(LIBC_SYSV_MACHCALLS_A_SRCS:%.s=o/$(MODE)/%.o) LIBC_SYSV_MACHCALLS_A_CHECKS = $(LIBC_SYSV_MACHCALLS_A).pkg LIBC_SYSV_MACHCALLS_A_DIRECTDEPS = \ @@ -129,8 +128,5 @@ $(LIBC_SYSV_MACHCALLS_A_OBJS): \ #─────────────────────────────────────────────────────────────────────────────── -.PHONY: o/libc/sysv -o/libc/sysv: $(LIBC_SYSV_CHECKS) - .PHONY: o/$(MODE)/libc/sysv o/$(MODE)/libc/sysv: $(LIBC_SYSV_CHECKS) diff --git a/libc/testlib/almostequallongdouble.c b/libc/testlib/almostequallongdouble.c index bfa974ef..b50cc5be 100644 --- a/libc/testlib/almostequallongdouble.c +++ b/libc/testlib/almostequallongdouble.c @@ -20,7 +20,7 @@ #include "libc/math.h" #include "libc/testlib/testlib.h" -#define EPSILON 0.0000000000001L +#define EPSILON 0.00000001L testonly bool testlib_almostequallongdouble(long double x, long double y) { /* TODO(jart): This algorithm has to be binary. */ diff --git a/libc/testlib/bench.h b/libc/testlib/bench.h index bb6d0f4d..eea7c6e3 100644 --- a/libc/testlib/bench.h +++ b/libc/testlib/bench.h @@ -1,6 +1,5 @@ #ifndef COSMOPOLITAN_LIBC_BENCH_H_ #define COSMOPOLITAN_LIBC_BENCH_H_ -#include "libc/bits/safemacros.h" #include "libc/nexgen32e/bench.h" #if !(__ASSEMBLER__ + __LINKER__ + 0) COSMOPOLITAN_C_START_ @@ -9,19 +8,19 @@ COSMOPOLITAN_C_START_ * @fileoverview Microbenchmarking tools. */ -#define BENCHLOOP(START, STOP, N, INIT, EXPR) \ - ({ \ - int Iter, Count; \ - long double Average, Sample, Time1, Time2; \ - for (Average = 1.0, Iter = 1, Count = (N); Iter < Count; ++Iter) { \ - INIT; \ - Time1 = START(); \ - EXPR; \ - Time2 = STOP(); \ - Sample = Time2 - Time1; \ - Average += (Sample - Average) / Iter; \ - } \ - Average; \ +#define BENCHLOOP(START, STOP, N, INIT, EXPR) \ + ({ \ + unsigned long Iter, Count; \ + double Average, Sample, Time1, Time2; \ + for (Average = 1, Iter = 1, Count = (N); Iter < Count; ++Iter) { \ + INIT; \ + Time1 = START(); \ + EXPR; \ + Time2 = STOP(); \ + Sample = Time2 - Time1; \ + Average += 1. / Iter * (Sample - Average); \ + } \ + Average; \ }) COSMOPOLITAN_C_END_ diff --git a/libc/testlib/clearxmmregisters.c b/libc/testlib/clearxmmregisters.c new file mode 100644 index 00000000..1a0c5800 --- /dev/null +++ b/libc/testlib/clearxmmregisters.c @@ -0,0 +1,31 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/testlib/testlib.h" + +void testlib_clearxmmregisters(void) { + asm("pxor\t%xmm0,%xmm0\n\t" + "pxor\t%xmm1,%xmm1\n\t" + "pxor\t%xmm2,%xmm2\n\t" + "pxor\t%xmm3,%xmm3\n\t" + "pxor\t%xmm4,%xmm4\n\t" + "pxor\t%xmm5,%xmm5\n\t" + "pxor\t%xmm6,%xmm6\n\t" + "pxor\t%xmm7,%xmm7"); +} diff --git a/libc/testlib/ezbenchreport.c b/libc/testlib/ezbenchreport.c index 713abdae..b02c6f2f 100644 --- a/libc/testlib/ezbenchreport.c +++ b/libc/testlib/ezbenchreport.c @@ -24,11 +24,12 @@ STATIC_YOINK("ntoa"); STATIC_YOINK("stoa"); +STATIC_YOINK("strnwidth"); void __testlib_ezbenchreport(const char *form, uint64_t c1, uint64_t c2) { uint64_t ns1, ns2; - ns1 = lrintl(converttickstonanos(c1)); - ns2 = lrintl(converttickstonanos(c2)); + ns1 = rintl(converttickstonanos(c1)); + ns2 = rintl(converttickstonanos(c2)); (fprintf)(stderr, VEIL("r", "%-30s l: %,10lu𝑐 %,10lu𝑛𝑠 m: %,10lu𝑐 %,10lu𝑛𝑠\n"), form, c1, ns1, c2, ns2); diff --git a/libc/testlib/fixturerunner.c b/libc/testlib/fixturerunner.c index e282219b..facac541 100644 --- a/libc/testlib/fixturerunner.c +++ b/libc/testlib/fixturerunner.c @@ -40,9 +40,9 @@ testonly void testlib_runfixtures(testfn_t *test_start, testfn_t *test_end, for (i = 0; i < count && !g_testlib_failed; ++i) { snprintf(g_fixturename, sizeof(g_fixturename), "%s_%s", fixture_start[i].group, fixture_start[i].name); - __piro(PROT_READ | PROT_WRITE); + _piro(PROT_READ | PROT_WRITE); fixture_start[i].fn(); - __piro(PROT_READ); + _piro(PROT_READ); testlib_runtestcases(test_start, test_end, NULL); } } diff --git a/libc/testlib/showerror.c b/libc/testlib/showerror.c index 811b4ed0..67c661cd 100644 --- a/libc/testlib/showerror.c +++ b/libc/testlib/showerror.c @@ -42,7 +42,7 @@ testonly void testlib_showerror(const char *file, int line, const char *func, "\t%s%s\n" "\t%s%s\n", RED2, "error", UNBOLD, BLUE1, file, line, RESET, method, "in", func, - g_fixturename, code, "want", v1, symbol, " got", v2, SUBTLE, + g_fixturename, code, "need", v1, symbol, " got", v2, SUBTLE, strerror(errno), program_invocation_name, RESET); free_s(&v1); free_s(&v2); diff --git a/libc/testlib/showerror_.c b/libc/testlib/showerror_.c index 82b21810..193a3c8b 100644 --- a/libc/testlib/showerror_.c +++ b/libc/testlib/showerror_.c @@ -60,7 +60,7 @@ testonly void testlib_showerror_(int line, const char *wantcode, ", %s)\n" "\t\t%s %s %s\n" "\t\t%s %s\n", - gotcode, "want", FREED_want, testlib_showerror_symbol, " got", + gotcode, "need", FREED_want, testlib_showerror_symbol, " got", FREED_got); } else { fprintf(stderr, diff --git a/libc/testlib/testlib.h b/libc/testlib/testlib.h index 7c81ccae..8e4a903a 100644 --- a/libc/testlib/testlib.h +++ b/libc/testlib/testlib.h @@ -1,6 +1,6 @@ #ifndef COSMOPOLITAN_LIBC_TESTLIB_H_ #define COSMOPOLITAN_LIBC_TESTLIB_H_ -#include "libc/bits/bits.h" +#include "libc/bits/weaken.h" #include "libc/runtime/gc.h" #include "libc/testlib/ugly.h" #if !(__ASSEMBLER__ + __LINKER__ + 0) @@ -15,7 +15,7 @@ COSMOPOLITAN_C_START_ * Test cases are guaranteed by the linker to be run in order, sorted by * the (SUITE, NAME) tuple passed here. */ -#define TEST(SUITE, NAME) __TEST_PROTOTYPE(SUITE, NAME, __TEST_ARRAY) +#define TEST(SUITE, NAME) __TEST_PROTOTYPE(SUITE, NAME, __TEST_ARRAY, ) /** * Declares function that globally modifies program state. @@ -48,7 +48,7 @@ COSMOPOLITAN_C_START_ */ #define BENCH(SUITE, NAME) \ STATIC_YOINK("__bench_start"); \ - __TEST_PROTOTYPE(SUITE, NAME, __BENCH_ARRAY) + __TEST_PROTOTYPE(SUITE, NAME, __BENCH_ARRAY, optimizespeed) #define ASSERT_GE(C, X) _TEST2("ASSERT_GE", C, >=, (X), #C, " ≥ ", #X, 1) #define ASSERT_GT(C, X) _TEST2("ASSERT_GT", C, >, (X), #C, " > ", #X, 1) @@ -82,9 +82,11 @@ COSMOPOLITAN_C_START_ #define ASSERT_EQ(WANT, GOT, ...) \ __TEST_EQ(assert, __FILE__, __LINE__, __FUNCTION__, #WANT, #GOT, WANT, GOT, \ __VA_ARGS__) + #define EXPECT_EQ(WANT, GOT, ...) \ __TEST_EQ(expect, __FILE__, __LINE__, __FUNCTION__, #WANT, #GOT, WANT, GOT, \ __VA_ARGS__) + #define __TEST_EQ(KIND, FILE, LINE, FUNC, WANTCODE, GOTCODE, WANT, GOT, ...) \ ({ \ autotype(GOT) Got = _I(GOT); \ @@ -272,6 +274,7 @@ void *tunbing(const char16_t *) void *tunbinga(size_t, const char16_t *) paramsnonnull() returnsnonnull returnspointerwithnoaliases nodiscard attributeallocalign((1)); +void testlib_clearxmmregisters(void); #define tgc(TMEM) \ ({ \ @@ -533,8 +536,8 @@ forceinline void assertStartsWith(FILIFU_ARGS size_t cw, const char *prefix, if (testlib_startswith(cw, s, prefix)) return; if (g_testlib_shoulddebugbreak) DebugBreak(); testlib_showerror(file, line, func, "assertStartsWith", "≠", gotcode, - testlib_formatstr(1, s, -1), - testlib_formatstr(1, prefix, -1)); + testlib_formatstr(1, prefix, -1), + testlib_formatstr(1, s, -1)); testlib_onfail2(isfatal); } diff --git a/libc/testlib/testlib.mk b/libc/testlib/testlib.mk index 6c038c4b..2044a9f8 100644 --- a/libc/testlib/testlib.mk +++ b/libc/testlib/testlib.mk @@ -43,6 +43,7 @@ LIBC_TESTLIB_A_SRCS_C = \ libc/testlib/almostequallongdouble.c \ libc/testlib/hexequals.c \ libc/testlib/binequals.c \ + libc/testlib/clearxmmregisters.c \ libc/testlib/formatbool.c \ libc/testlib/formatrange.c \ libc/testlib/globals.c \ diff --git a/libc/testlib/testmain.c b/libc/testlib/testmain.c index 69e6491a..a10d526b 100644 --- a/libc/testlib/testmain.c +++ b/libc/testlib/testmain.c @@ -74,6 +74,7 @@ testonly int main(int argc, char *argv[]) { showcrashreports(); g_testlib_shoulddebugbreak = IsDebuggerPresent(false); getpid$sysv(); /* make strace easier to read */ + testlib_clearxmmregisters(); testlib_runalltests(); if (!g_testlib_failed && runbenchmarks_ && weaken(testlib_runallbenchmarks)) { weaken(testlib_runallbenchmarks)(); diff --git a/libc/testlib/testmem.c b/libc/testlib/testmem.c index e95356b1..b66d4ad5 100644 --- a/libc/testlib/testmem.c +++ b/libc/testlib/testmem.c @@ -94,7 +94,7 @@ static void testmem_fini(void) { } } -static void testmem_init(void) { +static textstartup void testmem_init(void) { atexit(testmem_fini); g_testmem.p = g_testmem_scratch[0]; g_testmem.n = ARRAYLEN(g_testmem_scratch[0]); diff --git a/libc/testlib/testrunner.c b/libc/testlib/testrunner.c index 025b5e80..68448e0c 100644 --- a/libc/testlib/testrunner.c +++ b/libc/testlib/testrunner.c @@ -17,8 +17,8 @@ │ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/bits/bits.h" #include "libc/bits/safemacros.h" +#include "libc/bits/weaken.h" #include "libc/calls/internal.h" #include "libc/errno.h" #include "libc/nt/process.h" @@ -70,6 +70,7 @@ testonly void testlib_runtestcases(testfn_t *start, testfn_t *end, SetLastError(0); getpid$sysv(); if (warmup) warmup(); + testlib_clearxmmregisters(); (*fn)(); getpid$sysv(); if (weaken(TearDown)) weaken(TearDown)(); diff --git a/libc/testlib/ugly.h b/libc/testlib/ugly.h index e51f1760..241a65cd 100644 --- a/libc/testlib/ugly.h +++ b/libc/testlib/ugly.h @@ -14,10 +14,10 @@ #define __BENCH_ARRAY(S) \ _Section(".piro.relo.sort.bench.2." #S ",\"aw\",@init_array #") -#define __TEST_PROTOTYPE(S, N, A) \ +#define __TEST_PROTOTYPE(S, N, A, K) \ testonly void S##_##N(void); \ alignas(8) const void *const S##_##N##_ptr[] A(S) = {S##_##N}; \ - testonly void S##_##N(void) + testonly K void S##_##N(void) #define __TEST_SECTION(NAME, CONTENT) \ ".section " NAME "\n" CONTENT "\n\t.previous\n" diff --git a/libc/time/clock_gettime.c b/libc/time/clock_gettime.c index 56004bb2..afc4f6a3 100644 --- a/libc/time/clock_gettime.c +++ b/libc/time/clock_gettime.c @@ -44,7 +44,7 @@ * @param clockid can be CLOCK_REALTIME, CLOCK_MONOTONIC, etc. noting * that on Linux CLOCK_MONOTONIC is redefined to use the monotonic * clock that's actually monotonic lool - * @param out_ts is where the nanoseconds are stored + * @param out_ts is where the nanoseconds are stored if non-NULL * @return 0 on success or -1 w/ errno on error * @error ENOSYS if clockid isn't available; in which case this function * guarantees an ordinary timestamp is still stored to out_ts; and @@ -56,21 +56,28 @@ int clock_gettime(int clockid, struct timespec *out_ts) { /* TODO(jart): Just ignore O/S for MONOTONIC and measure RDTSC on start */ if (!IsWindows()) { if (!IsXnu()) { - out_ts->tv_sec = 0; - out_ts->tv_nsec = 0; + if (out_ts) { + out_ts->tv_sec = 0; + out_ts->tv_nsec = 0; + } return clock_gettime$sysv(clockid, out_ts); } else { + int rc; static_assert(sizeof(struct timeval) == sizeof(struct timespec)); - out_ts->tv_sec = 0; - out_ts->tv_nsec = 0; - int rc = gettimeofday$sysv((struct timeval *)out_ts, NULL); - out_ts->tv_nsec *= 1000; + if (out_ts) { + out_ts->tv_sec = 0; + out_ts->tv_nsec = 0; + } + rc = gettimeofday$sysv((struct timeval *)out_ts, NULL); + if (out_ts) { + out_ts->tv_nsec *= 1000; + } return rc; } } else { struct NtFileTime ft; GetSystemTimeAsFileTime(&ft); - filetimetotimespec(out_ts, ft); + *out_ts = filetimetotimespec(ft); return 0; } } diff --git a/libc/time/dsleep.c b/libc/time/dsleep.c index c9cb1858..6a8acc41 100644 --- a/libc/time/dsleep.c +++ b/libc/time/dsleep.c @@ -29,7 +29,7 @@ long double dsleep(long double secs) { struct timespec dur, rem; dur.tv_sec = secs; dur.tv_nsec = secs * 1e9; - dur.tv_nsec = mod1000000000int64(dur.tv_nsec); + dur.tv_nsec = rem1000000000int64(dur.tv_nsec); if (secs > 1e-6) { nanosleep(&dur, &rem); secs = rem.tv_nsec; diff --git a/libc/time/futimens.c b/libc/time/futimens.c new file mode 100644 index 00000000..69c2fd15 --- /dev/null +++ b/libc/time/futimens.c @@ -0,0 +1,31 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/time/time.h" + +/** + * Sets atime/mtime on file descriptor. + * + * @param ts is atime/mtime, or null for current time + * @note better than microsecond precision on most platforms + * @see fstat() for reading timestamps + */ +int futimens(int fd, const struct timespec ts[hasatleast 2]) { + return utimensat(fd, NULL, ts, 0); +} diff --git a/libc/time/futimes.c b/libc/time/futimes.c new file mode 100644 index 00000000..65728204 --- /dev/null +++ b/libc/time/futimes.c @@ -0,0 +1,40 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/time/time.h" + +/** + * Sets atime/mtime on file descriptor. + * + * @param ts is atime/mtime, or null for current time + * @note better than microsecond precision on most platforms + * @see fstat() for reading timestamps + */ +int futimes(int fd, const struct timeval tv[hasatleast 2]) { + struct timespec ts[2]; + if (tv) { + ts[0].tv_sec = tv[0].tv_sec; + ts[0].tv_nsec = tv[0].tv_usec * 1000; + ts[1].tv_sec = tv[1].tv_sec; + ts[1].tv_nsec = tv[1].tv_usec * 1000; + return utimensat(fd, NULL, ts, 0); + } else { + return utimensat(fd, NULL, NULL, 0); + } +} diff --git a/libc/time/futimesat.c b/libc/time/futimesat.c new file mode 100644 index 00000000..2683c822 --- /dev/null +++ b/libc/time/futimesat.c @@ -0,0 +1,40 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/time/time.h" + +/** + * Changes last accessed/modified times on file. + * + * @param times is access/modified and NULL means now + * @return 0 on success or -1 w/ errno + * @see utimensat() which uses nanos + */ +int futimesat(int dirfd, const char *pathname, const struct timeval tv[2]) { + struct timespec ts[2]; + if (tv) { + ts[0].tv_sec = tv[0].tv_sec; + ts[0].tv_nsec = tv[0].tv_usec * 1000; + ts[1].tv_sec = tv[1].tv_sec; + ts[1].tv_nsec = tv[1].tv_usec * 1000; + return utimensat(dirfd, pathname, ts, 0); + } else { + return utimensat(dirfd, pathname, NULL, 0); + } +} diff --git a/libc/time/gettimeofday.c b/libc/time/gettimeofday.c index 8d74e360..6cf9ca64 100644 --- a/libc/time/gettimeofday.c +++ b/libc/time/gettimeofday.c @@ -24,7 +24,7 @@ /** * Returns system wall time in microseconds. * - * @param tv points to timeval that receives result + * @param tv points to timeval that receives result if non-NULL * @param tz receives UTC timezone if non-NULL * @return always zero * @see clock_gettime() for nanosecond precision diff --git a/libc/time/localtime.c b/libc/time/localtime.c index 302fe321..12ee429e 100644 --- a/libc/time/localtime.c +++ b/libc/time/localtime.c @@ -1,7 +1,9 @@ +#include "libc/bits/initializer.h" #include "libc/calls/calls.h" #include "libc/macros.h" #include "libc/math.h" #include "libc/mem/mem.h" +#include "libc/nexgen32e/nexgen32e.h" #include "libc/runtime/runtime.h" #include "libc/str/str.h" #include "libc/sysv/consts/o.h" @@ -871,9 +873,9 @@ const int32_t offset; */ m1 = (rulep->r_mon + 9) % 12 + 1; yy0 = (rulep->r_mon <= 2) ? (year - 1) : year; - yy1 = yy0 / 100; - yy2 = yy0 % 100; - dow = ((26 * m1 - 2) / 10 + + yy1 = div100int64(yy0); + yy2 = rem100int64(yy0); + dow = (div10int64(26 * m1 - 2) + 1 + yy2 + yy2 / 4 + yy1 / 4 - 2 * yy1) % 7; if (dow < 0) dow += DAYSPERWEEK; @@ -1440,7 +1442,7 @@ pureconst static int leaps_thru_end_of(y) register const int y; { - return (y >= 0) ? (y / 4 - y / 100 + y / 400) : + return (y >= 0) ? (y / 4 - div100int64(y) + y / 400) : -(leaps_thru_end_of(-(y + 1)) + 1); } diff --git a/libc/time/nanosleep.c b/libc/time/nanosleep.c index 2d364b7d..e31ecb50 100644 --- a/libc/time/nanosleep.c +++ b/libc/time/nanosleep.c @@ -23,11 +23,13 @@ #include "libc/conv/conv.h" #include "libc/dce.h" #include "libc/macros.h" +#include "libc/nexgen32e/nexgen32e.h" #include "libc/nt/enum/status.h" #include "libc/nt/errors.h" #include "libc/nt/nt/time.h" #include "libc/nt/synchronization.h" #include "libc/sock/internal.h" +#include "libc/str/str.h" #include "libc/sysv/errfuns.h" /** @@ -35,24 +37,28 @@ */ int nanosleep(const struct timespec *req, struct timespec *rem) { long res, millis, hectonanos; + if (!req) return efault(); if (!IsWindows()) { if (!IsXnu()) { return nanosleep$sysv(req, rem); } else { - return select$sysv(0, 0, 0, 0, /* lool */ - &(struct timeval){req->tv_sec, req->tv_nsec / 1000}); + return select$sysv( + 0, 0, 0, 0, /* lool */ + &(struct timeval){req->tv_sec, div1000int64(req->tv_nsec)}); } } else { + if (rem) memcpy(rem, req, sizeof(*rem)); if (req->tv_sec && req->tv_nsec) { - hectonanos = MAX(1, req->tv_sec * 10000000L + req->tv_nsec / 100L); + hectonanos = MAX(1, req->tv_sec * 10000000L + div100int64(req->tv_nsec)); } else { hectonanos = 1; } if (NtError(NtDelayExecution(true, &hectonanos))) { - millis = hectonanos / 10000; + millis = div10000int64(hectonanos); res = SleepEx(millis, true); if (res == kNtWaitIoCompletion) return eintr(); } + if (rem) memset(rem, 0, sizeof(*rem)); return 0; } } diff --git a/libc/time/now.c b/libc/time/now.c index 928fec00..7ea7adcf 100644 --- a/libc/time/now.c +++ b/libc/time/now.c @@ -18,6 +18,7 @@ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/bits/bits.h" +#include "libc/bits/initializer.h" #include "libc/bits/safemacros.h" #include "libc/calls/calls.h" #include "libc/dce.h" @@ -27,6 +28,7 @@ #include "libc/time/time.h" static struct Now { + bool once; uint64_t k0; long double r0, cpn; } now_; @@ -37,25 +39,7 @@ static struct Now { */ long double (*nowl)(void); -long double converttickstonanos(uint64_t ticks) { - return ticks * now_.cpn; /* pico scale */ -} - -long double converttickstoseconds(uint64_t ticks) { - return 1 / 1e9 * converttickstonanos(ticks); -} - -static long double nowl$sys(void) { - return dtime(CLOCK_REALTIME); -} - -static long double nowl$art(void) { - uint64_t ticks; - ticks = unsignedsubtract(rdtsc(), now_.k0); - return now_.r0 + converttickstoseconds(ticks); -} - -static long double GetSample(void) { +static long double GetTimeSample(void) { uint64_t tick1, tick2; long double time1, time2; sched_yield(); @@ -71,17 +55,41 @@ static long double MeasureNanosPerCycle(void) { int i; long double avg, samp; for (avg = 1.0L, i = 1; i < 5; ++i) { - samp = GetSample(); + samp = GetTimeSample(); avg += (samp - avg) / i; } return avg; } -INITIALIZER(301, _init_time, { +static void InitTime(void) { + now_.cpn = MeasureNanosPerCycle(); + now_.r0 = dtime(CLOCK_REALTIME); + now_.k0 = rdtsc(); + now_.once = true; +} + +long double converttickstonanos(uint64_t ticks) { + if (!now_.once) InitTime(); + return ticks * now_.cpn; /* pico scale */ +} + +long double converttickstoseconds(uint64_t ticks) { + return 1 / 1e9 * converttickstonanos(ticks); +} + +long double nowl$sys(void) { + return dtime(CLOCK_REALTIME); +} + +long double nowl$art(void) { + uint64_t ticks; + if (!now_.once) InitTime(); + ticks = unsignedsubtract(rdtsc(), now_.k0); + return now_.r0 + converttickstoseconds(ticks); +} + +INITIALIZER(301, _init_nowl, { if (X86_HAVE(INVTSC)) { - now_.cpn = MeasureNanosPerCycle(); - now_.r0 = dtime(CLOCK_REALTIME); - now_.k0 = rdtsc(); nowl = nowl$art; } else { nowl = nowl$sys; diff --git a/libc/time/strftime.c b/libc/time/strftime.c index 07c76c31..4cfe17ce 100644 --- a/libc/time/strftime.c +++ b/libc/time/strftime.c @@ -19,6 +19,7 @@ #include "libc/calls/calls.h" #include "libc/fmt/fmt.h" #include "libc/macros.h" +#include "libc/nexgen32e/nexgen32e.h" #include "libc/time/struct/tm.h" #include "libc/time/time.h" #include "libc/tzfile.h" @@ -100,7 +101,7 @@ static char *strftime_timefmt(char *pt, const char *ptlim, const char *format, ** something completely different. ** (ado, 5/24/93) */ - pt = strftime_conv(pt, ptlim, (t->tm_year + TM_YEAR_BASE) / 100, + pt = strftime_conv(pt, ptlim, div100int64(t->tm_year + TM_YEAR_BASE), "%02d"); continue; case 'D': diff --git a/libc/time/struct/utimbuf.h b/libc/time/struct/utimbuf.h new file mode 100644 index 00000000..b5f5e35b --- /dev/null +++ b/libc/time/struct/utimbuf.h @@ -0,0 +1,11 @@ +#ifndef COSMOPOLITAN_LIBC_TIME_STRUCT_UTIMBUF_H_ +#define COSMOPOLITAN_LIBC_TIME_STRUCT_UTIMBUF_H_ +#if !(__ASSEMBLER__ + __LINKER__ + 0) + +struct utimbuf { + int64_t actime; /* access time */ + int64_t modtime; /* modified time */ +}; + +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_TIME_STRUCT_UTIMBUF_H_ */ diff --git a/libc/time/time.h b/libc/time/time.h index e20d76e5..f5092149 100644 --- a/libc/time/time.h +++ b/libc/time/time.h @@ -1,13 +1,14 @@ #ifndef COSMOPOLITAN_LIBC_TIME_TIME_H_ #define COSMOPOLITAN_LIBC_TIME_TIME_H_ +#include "libc/calls/struct/timespec.h" +#include "libc/calls/struct/timeval.h" #if !(__ASSEMBLER__ + __LINKER__ + 0) COSMOPOLITAN_C_START_ struct itimerval; -struct timespec; -struct timeval; struct timezone; struct tm; +struct utimbuf; extern const char kWeekdayNameShort[7][4]; extern const char kWeekdayName[7][10]; @@ -20,12 +21,12 @@ extern long CLOCKS_PER_SEC; int64_t clock(void); int64_t time(int64_t *); int gettimeofday(struct timeval *, struct timezone *); -int clock_gettime(int, struct timespec *) paramsnonnull(); +int clock_gettime(int, struct timespec *); int clock_getres(int, struct timespec *); int sleep(uint32_t); int usleep(uint32_t); -int nanosleep(const struct timespec *, struct timespec *) paramsnonnull((1)); +int nanosleep(const struct timespec *, struct timespec *); unsigned alarm(unsigned); int getitimer(int, struct itimerval *) paramsnonnull(); int setitimer(int, const struct itimerval *, struct itimerval *) @@ -51,7 +52,12 @@ char *asctime_r(const struct tm *, char * /*[64]*/); char *ctime(const int64_t *); char *ctime_r(const int64_t *, char * /*[64]*/); -int utimes(const char *, const struct timeval *); +int futimens(int, const struct timespec[2]); +int utimensat(int, const char *, const struct timespec[2], int); +int utimes(const char *, const struct timeval[2]); +int utime(const char *, const struct utimbuf *); +int futimes(int, const struct timeval[2]); +int futimesat(int, const char *, const struct timeval[2]); long double dtime(int); long double dsleep(long double); diff --git a/libc/time/times.c b/libc/time/times.c index 870f7bf8..25e04f1c 100644 --- a/libc/time/times.c +++ b/libc/time/times.c @@ -25,39 +25,31 @@ #include "libc/conv/conv.h" #include "libc/dce.h" #include "libc/nt/accounting.h" -#include "libc/nt/process.h" #include "libc/nt/runtime.h" -#include "libc/nt/struct/filetime.h" #include "libc/runtime/sysconf.h" #include "libc/sysv/consts/rusage.h" #include "libc/time/time.h" -/** - * Returns accounting data for process on time-sharing system. - */ -long times(struct tms *out_times) { +static noinline long times2(struct tms *out_times, struct rusage *ru) { + long tick; struct timeval tv; - long tick = sysconf(_SC_CLK_TCK); + tick = sysconf(_SC_CLK_TCK); if (!IsWindows()) { - struct rusage ru; - if (getrusage(RUSAGE_SELF, &ru) == -1) return -1; - out_times->tms_utime = convertmicros(&ru.ru_utime, tick); - out_times->tms_stime = convertmicros(&ru.ru_stime, tick); - if (getrusage(RUSAGE_CHILDREN, &ru) == -1) return -1; - out_times->tms_cutime = convertmicros(&ru.ru_utime, tick); - out_times->tms_cstime = convertmicros(&ru.ru_stime, tick); + if (getrusage(RUSAGE_SELF, ru) == -1) return -1; + out_times->tms_utime = convertmicros(&ru->ru_utime, tick); + out_times->tms_stime = convertmicros(&ru->ru_stime, tick); + if (getrusage(RUSAGE_CHILDREN, ru) == -1) return -1; + out_times->tms_cutime = convertmicros(&ru->ru_utime, tick); + out_times->tms_cstime = convertmicros(&ru->ru_stime, tick); } else { - struct NtFileTime CreationFileTime; - struct NtFileTime ExitFileTime; - struct NtFileTime KernelFileTime; - struct NtFileTime UserFileTime; - if (!GetProcessTimes(GetCurrentProcess(), &CreationFileTime, &ExitFileTime, - &KernelFileTime, &UserFileTime)) { + struct NtFileTime CreationTime, ExitTime, KernelTime, UserTime; + if (!GetProcessTimes(GetCurrentProcess(), &CreationTime, &ExitTime, + &KernelTime, &UserTime)) { return winerr(); } - filetimetotimeval(&tv, UserFileTime); + filetimetotimeval(&tv, UserTime); out_times->tms_utime = convertmicros(&tv, tick); - filetimetotimeval(&tv, KernelFileTime); + filetimetotimeval(&tv, KernelTime); out_times->tms_stime = convertmicros(&tv, tick); out_times->tms_cutime = 0; out_times->tms_cstime = 0; @@ -65,3 +57,11 @@ long times(struct tms *out_times) { if (gettimeofday(&tv, NULL) == -1) return -1; return convertmicros(&tv, tick); } + +/** + * Returns accounting data for process on time-sharing system. + */ +long times(struct tms *out_times) { + struct rusage ru; + return times2(out_times, &ru); +} diff --git a/libc/time/utime.c b/libc/time/utime.c index 2bbbf38a..78fd2227 100644 --- a/libc/time/utime.c +++ b/libc/time/utime.c @@ -18,9 +18,9 @@ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/str/str.h" -#include "libc/calls/struct/timeval.h" +#include "libc/sysv/consts/at.h" +#include "libc/time/struct/utimbuf.h" #include "libc/time/time.h" -#include "libc/time/utime.h" /** * Changes last accessed/modified times on file. @@ -29,11 +29,14 @@ * @return 0 on success or -1 w/ errno */ int utime(const char *path, const struct utimbuf *times) { - struct timeval tv[2]; - memset(tv, 0, sizeof(tv)); + struct timespec ts[2]; if (times) { - tv[0].tv_sec = times->actime; - tv[1].tv_sec = times->modtime; + ts[0].tv_sec = times->actime; + ts[0].tv_nsec = 0; + ts[1].tv_sec = times->modtime; + ts[1].tv_nsec = 0; + return utimensat(AT_FDCWD, path, ts, 0); + } else { + return utimensat(AT_FDCWD, path, NULL, 0); } - return utimes(path, times ? tv : NULL); } diff --git a/libc/time/utime.h b/libc/time/utime.h deleted file mode 100644 index d8f19c7d..00000000 --- a/libc/time/utime.h +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef COSMOPOLITAN_LIBC_CALLS_UTIME_H_ -#define COSMOPOLITAN_LIBC_CALLS_UTIME_H_ -#if !(__ASSEMBLER__ + __LINKER__ + 0) -COSMOPOLITAN_C_START_ - -struct utimbuf { - int64_t actime; /* access time */ - int64_t modtime; /* modified time */ -}; - -int utime(const char *path, const struct utimbuf *times) paramsnonnull((1)); - -COSMOPOLITAN_C_END_ -#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ -#endif /* COSMOPOLITAN_LIBC_CALLS_UTIME_H_ */ diff --git a/libc/calls/copyfile.c b/libc/time/utimensat-nt.c similarity index 56% rename from libc/calls/copyfile.c rename to libc/time/utimensat-nt.c index 279eddb3..8a4f780a 100644 --- a/libc/calls/copyfile.c +++ b/libc/time/utimensat-nt.c @@ -17,48 +17,70 @@ │ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/calls/calls.h" #include "libc/calls/internal.h" -#include "libc/calls/struct/stat.h" -#include "libc/dce.h" +#include "libc/conv/conv.h" +#include "libc/nt/createfile.h" +#include "libc/nt/enum/creationdisposition.h" #include "libc/nt/files.h" +#include "libc/nt/runtime.h" +#include "libc/nt/synchronization.h" #include "libc/sysv/consts/at.h" -#include "libc/sysv/consts/madv.h" -#include "libc/sysv/consts/o.h" +#include "libc/sysv/consts/utime.h" +#include "libc/sysv/errfuns.h" +#include "libc/time/time.h" -int copyfile(const char *frompath, const char *topath, bool dontoverwrite) { - if (IsWindows()) { - char16_t frompath16[PATH_MAX], topath16[PATH_MAX]; - if (mkntpath(frompath, frompath16) == -1) return -1; - if (mkntpath(topath, topath16) == -1) return -1; - if (CopyFile(frompath16, topath16, dontoverwrite)) { - return 0; +textwindows int utimensat$nt(int dirfd, const char *path, + const struct timespec ts[2], int flags) { + int i, rc; + int64_t fh; + bool closeme; + uint16_t path16[PATH_MAX]; + struct NtFileTime ft[2], *ftp[2]; + if (flags) return einval(); + if (path) { + if (dirfd == AT_FDCWD) { + if (mkntpath(path, path16) == -1) return -1; + if ((fh = CreateFile(path16, kNtFileWriteAttributes, kNtFileShareRead, + NULL, kNtOpenExisting, kNtFileAttributeNormal, 0)) != + -1) { + closeme = true; + } else { + return winerr(); + } } else { - return winerr(); + return einval(); + } + } else if (isfdindex(dirfd)) { + fh = g_fds.p[dirfd].handle; + closeme = false; + } else { + return ebadf(); + } + if (!ts || ts[0].tv_nsec == UTIME_NOW || ts[1].tv_nsec == UTIME_NOW) { + GetSystemTimeAsFileTime(ft); + } + if (ts) { + for (i = 0; i < 2; ++i) { + if (ts[i].tv_nsec == UTIME_NOW) { + ftp[i] = ft; + } else if (ts[i].tv_nsec == UTIME_OMIT) { + ftp[i] = NULL; + } else { + ft[i] = timespectofiletime(ts[i]); + ftp[i] = &ft[i]; + } } } else { - struct stat st; - ssize_t transferred; - int rc, fromfd, tofd; - int64_t inoffset, outoffset; - rc = -1; - if ((fromfd = openat$sysv(AT_FDCWD, frompath, O_RDONLY, 0)) != -1) { - if (fstat$sysv(fromfd, &st) != -1 && - (tofd = openat$sysv(AT_FDCWD, topath, - O_WRONLY | O_CREAT | (dontoverwrite ? O_EXCL : 0), - st.st_mode & 0777)) != -1) { - inoffset = 0; - outoffset = 0; - while (st.st_size && (transferred = copy_file_range( - fromfd, &inoffset, tofd, &outoffset, - st.st_size, 0)) != -1) { - st.st_size -= transferred; - } - if (!st.st_size) rc = 0; - rc |= close$sysv(tofd); - } - rc |= close$sysv(fromfd); - } - return rc; + ftp[0] = ft; + ftp[1] = ft; } + if (SetFileTime(fh, NULL, ftp[0], ftp[1])) { + rc = 0; + } else { + rc = winerr(); + } + if (closeme) { + CloseHandle(fh); + } + return rc; } diff --git a/libc/time/utimensat-sysv.c b/libc/time/utimensat-sysv.c new file mode 100644 index 00000000..0bb50434 --- /dev/null +++ b/libc/time/utimensat-sysv.c @@ -0,0 +1,30 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/internal.h" +#include "libc/time/time.h" + +int utimensat$sysv(int dirfd, const char *path, const struct timespec ts[2], + int flags) { + if (!IsXnu()) { + return __utimensat$sysv(dirfd, path, ts, flags); + } else { + return utimensat$xnu(dirfd, path, ts, flags); + } +} diff --git a/test/dsp/tty/rgb2xterm256_test.c b/libc/time/utimensat-xnu.c similarity index 65% rename from test/dsp/tty/rgb2xterm256_test.c rename to libc/time/utimensat-xnu.c index d5ff1ab3..0e29d3d5 100644 --- a/test/dsp/tty/rgb2xterm256_test.c +++ b/libc/time/utimensat-xnu.c @@ -17,31 +17,47 @@ │ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "dsp/tty/rgb2xterm256.h" -#include "libc/testlib/ezbench.h" -#include "libc/testlib/testlib.h" +#include "libc/calls/internal.h" +#include "libc/nexgen32e/nexgen32e.h" +#include "libc/sysv/consts/at.h" +#include "libc/sysv/consts/utime.h" +#include "libc/sysv/errfuns.h" +#include "libc/time/time.h" -TEST(rgb2xterm256, test) { - EXPECT_EQ(196, rgb2xterm256v2(0xff, 0x00, 0x00)); /* red */ - EXPECT_EQ(46, rgb2xterm256v2(0x00, 0xff, 0x00)); /* green */ - EXPECT_EQ(21, rgb2xterm256v2(0x00, 0x00, 0xff)); /* blue */ - EXPECT_EQ(226, rgb2xterm256v2(0xff, 0xff, 0x00)); /* yellow */ - EXPECT_EQ(208, rgb2xterm256v2(0xff, 0x80, 0x00)); /* orange */ -} - -TEST(rgb2xterm256, testRedBlack) { - EXPECT_EQ(16, rgb2xterm256v2(0, 0, 0)); - EXPECT_EQ(16, rgb2xterm256v2(12, 0, 0)); - EXPECT_EQ(232, rgb2xterm256v2(13, 0, 0)); - EXPECT_EQ(233, rgb2xterm256v2(39, 0, 0)); - EXPECT_EQ(233, rgb2xterm256v2(40, 0, 0)); - EXPECT_EQ(52, rgb2xterm256v2(53, 0, 0)); - EXPECT_EQ(88, rgb2xterm256v2(115, 0, 0)); - EXPECT_EQ(88, rgb2xterm256v2(116, 0, 0)); -} - -//////////////////////////////////////////////////////////////////////////////// - -BENCH(rgb2xterm256v2, bench) { - EZBENCH(donothing, rgb2xterm256v2(0xff, 0x80, 0x00)); +int utimensat$xnu(int dirfd, const char *path, const struct timespec ts[2], + int flags) { + int i; + struct timeval now, tv[2]; + if (flags) return einval(); + if (!ts || ts[0].tv_nsec == UTIME_NOW || ts[1].tv_nsec == UTIME_NOW) { + gettimeofday(&now, NULL); + } + if (ts) { + for (i = 0; i < 2; ++i) { + if (ts[i].tv_nsec == UTIME_NOW) { + tv[i] = now; + } else if (ts[i].tv_nsec == UTIME_OMIT) { + return einval(); + } else { + tv[i].tv_sec = ts[i].tv_sec; + tv[i].tv_usec = div1000int64(ts[i].tv_nsec); + } + } + } else { + tv[0] = now; + tv[1] = now; + } + if (path) { + if (dirfd == AT_FDCWD) { + return utimes$sysv(path, tv); + } else { + return enosys(); + } + } else { + if (dirfd != AT_FDCWD) { + return futimes$sysv(dirfd, tv); + } else { + return einval(); + } + } } diff --git a/libc/time/utimensat.c b/libc/time/utimensat.c new file mode 100644 index 00000000..e24e8203 --- /dev/null +++ b/libc/time/utimensat.c @@ -0,0 +1,37 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/calls.h" +#include "libc/calls/internal.h" + +/** + * Sets atime/mtime on file, the modern way. + * + * @param ts is atime/mtime, or null for current time + * @param flags can have AT_SYMLINK_NOFOLLOW + * @note no rhel5 support + */ +int utimensat(int dirfd, const char *path, + const struct timespec ts[hasatleast 2], int flags) { + if (!IsWindows()) { + return utimensat$sysv(dirfd, path, ts, flags); + } else { + return utimensat$nt(dirfd, path, ts, flags); + } +} diff --git a/libc/time/utimes.c b/libc/time/utimes.c index cbb264e8..2ca13b9d 100644 --- a/libc/time/utimes.c +++ b/libc/time/utimes.c @@ -17,57 +17,25 @@ │ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/calls/internal.h" -#include "libc/calls/struct/timeval.h" -#include "libc/conv/conv.h" -#include "libc/dce.h" -#include "libc/nt/createfile.h" -#include "libc/nt/enum/accessmask.h" -#include "libc/nt/files.h" -#include "libc/nt/runtime.h" -#include "libc/nt/struct/filetime.h" -#include "libc/runtime/runtime.h" -#include "libc/str/str.h" -#include "libc/sysv/errfuns.h" +#include "libc/sysv/consts/at.h" #include "libc/time/time.h" -static int utimes$nt(const char *path, const struct timeval times[2]) { - int rc; - int64_t fh; - struct timeval tv; - struct NtFileTime accessed; - struct NtFileTime modified; - uint16_t path16[PATH_MAX]; - if (mkntpath(path, path16) == -1) return -1; - if (times) { - accessed = timevaltofiletime(×[0]); - modified = timevaltofiletime(×[1]); - } else { - gettimeofday(&tv, NULL); - accessed = timevaltofiletime(&tv); - modified = timevaltofiletime(&tv); - } - if ((fh = CreateFile(path16, kNtGenericWrite, kNtFileShareRead, NULL, - kNtOpenExisting, kNtFileAttributeNormal, 0)) != -1 && - SetFileTime(fh, NULL, &accessed, &modified)) { - rc = 0; - } else { - rc = winerr(); - } - CloseHandle(fh); - return rc; -} - /** * Changes last accessed/modified times on file. * * @param times is access/modified and NULL means now * @return 0 on success or -1 w/ errno + * @see stat() */ -int utimes(const char *path, const struct timeval times[hasatleast 2]) { - if (!IsWindows()) { - return utimes$sysv(path, times); +int utimes(const char *path, const struct timeval tv[hasatleast 2]) { + struct timespec ts[2]; + if (tv) { + ts[0].tv_sec = tv[0].tv_sec; + ts[0].tv_nsec = tv[0].tv_usec * 1000; + ts[1].tv_sec = tv[1].tv_sec; + ts[1].tv_nsec = tv[1].tv_usec * 1000; + return utimensat(AT_FDCWD, path, ts, 0); } else { - return utimes$nt(path, times); + return utimensat(AT_FDCWD, path, NULL, 0); } } diff --git a/libc/tinymath/c2rangr.S b/libc/tinymath/c2rangr.S index 258b53be..f32bd801 100644 --- a/libc/tinymath/c2rangr.S +++ b/libc/tinymath/c2rangr.S @@ -38,9 +38,9 @@ c2rangr:push %rbp ret 1: fldpi fadd %st - fxch %st(1) + fxch 2: fprem1 - fnstsw %ax + fnstsw test $FPU_C2>>8,%ah jnz 2b fstp %st(1) diff --git a/libc/tinymath/cbrt.S b/libc/tinymath/cbrt.S index dc07a57e..1ed64a05 100644 --- a/libc/tinymath/cbrt.S +++ b/libc/tinymath/cbrt.S @@ -1,4 +1,4 @@ -/*-*- mode:asm; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│ +/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│ │vi: set et ft=asm ts=8 tw=8 fenc=utf-8 :vi│ ╞══════════════════════════════════════════════════════════════════════════════╡ │ Copyright 2020 Justine Alexandra Roberts Tunney │ @@ -27,3 +27,4 @@ tinymath_cbrt: jmp __cbrt .endfn tinymath_cbrt,globl .alias tinymath_cbrt,cbrt + .source __FILE__ diff --git a/libc/tinymath/cbrtf.S b/libc/tinymath/cbrtf.S index 58eb2dae..cdf9857e 100644 --- a/libc/tinymath/cbrtf.S +++ b/libc/tinymath/cbrtf.S @@ -1,4 +1,4 @@ -/*-*- mode:asm; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│ +/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│ │vi: set et ft=asm ts=8 tw=8 fenc=utf-8 :vi│ ╞══════════════════════════════════════════════════════════════════════════════╡ │ Copyright 2020 Justine Alexandra Roberts Tunney │ @@ -33,3 +33,4 @@ tinymath_cbrtf: ret .endfn tinymath_cbrtf,globl .alias tinymath_cbrtf,cbrtf + .source __FILE__ diff --git a/libc/tinymath/cbrtl.S b/libc/tinymath/cbrtl.S index d2534500..0b168452 100644 --- a/libc/tinymath/cbrtl.S +++ b/libc/tinymath/cbrtl.S @@ -1,4 +1,4 @@ -/*-*- mode:asm; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│ +/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│ │vi: set et ft=asm ts=8 tw=8 fenc=utf-8 :vi│ ╞══════════════════════════════════════════════════════════════════════════════╡ │ Copyright 2020 Justine Alexandra Roberts Tunney │ @@ -37,3 +37,4 @@ tinymath_cbrtl: ret .endfn tinymath_cbrtl,globl .alias tinymath_cbrtl,cbrtl + .source __FILE__ diff --git a/libc/tinymath/copysignl.S b/libc/tinymath/copysignl.S index db62d454..44e36582 100644 --- a/libc/tinymath/copysignl.S +++ b/libc/tinymath/copysignl.S @@ -26,7 +26,7 @@ tinymath_copysignl: .profilable fldt 32(%rbp) fxam - fnstsw %ax + fnstsw fstp %st fldt 16(%rbp) testb $2,%ah diff --git a/libc/tinymath/cprojl.S b/libc/tinymath/cprojl.S index cc47ba60..dbbba04f 100644 --- a/libc/tinymath/cprojl.S +++ b/libc/tinymath/cprojl.S @@ -20,39 +20,22 @@ #include "libc/macros.h" .source __FILE__ +/ Projects into Rienmann sphere. +/ +/ @param z is complex long double passed on stack +/ @note needs sse3 tinymath_cprojl: .profilable sub $24,%rsp fldt 32(%rsp) - fnstcw 14(%rsp) - movzwl 14(%rsp),%eax - orb $12,%ah - movw %ax,12(%rsp) - fldcw 12(%rsp) - fistpq (%rsp) - fldcw 14(%rsp) - movq (%rsp),%rsi + fisttpq 8(%rsp) fldt 48(%rsp) + movq 8(%rsp),%rsi mov %rsi,%rax - fldcw 12(%rsp) - fistpq (%rsp) - fldcw 14(%rsp) - movq (%rsp),%rcx + fisttpq 8(%rsp) + mov 8(%rsp),%rcx add $24,%rsp mov %rcx,%rdx ret .endfn tinymath_cprojl,globl .alias tinymath_cprojl,cprojl - -/ TODO(jart): -/ sub $24,%rsp -/ fldt 32(%rsp) -/ fisttpq 8(%rsp) -/ fldt 48(%rsp) -/ movq 8(%rsp),%rsi -/ mov %rsi,%rax -/ fisttpq 8(%rsp) -/ movq 8(%rsp),%rcx -/ add $24,%rsp -/ mov %rcx,%rdx -/ ret diff --git a/libc/tinymath/exp2.S b/libc/tinymath/exp2.S index a7f39f8b..a939eff7 100644 --- a/libc/tinymath/exp2.S +++ b/libc/tinymath/exp2.S @@ -24,6 +24,8 @@ / / @param 𝑥 is a double passed in the lower quadword of %xmm0 / @return result in lower quadword of %xmm0 -exp2: ezlea exp2l,ax +tinymath_exp2: + ezlea tinymath_exp2l,ax jmp _d2ld2 - .endfn exp2,globl + .endfn tinymath_exp2,globl + .alias tinymath_exp2,exp2 diff --git a/libc/tinymath/exp2f.S b/libc/tinymath/exp2f.S index 0b6997ae..b7e0a8a6 100644 --- a/libc/tinymath/exp2f.S +++ b/libc/tinymath/exp2f.S @@ -20,6 +20,12 @@ #include "libc/macros.h" .source __FILE__ -exp2f: ezlea exp2f,ax +/ Returns 2^𝑥. +/ +/ @param 𝑥 is a float passed in the lower quarter of %xmm0 +/ @return result in lower quarter of %xmm0 +tinymath_exp2f: + ezlea tinymath_exp2l,ax jmp _f2ld2 - .endfn exp2f,globl + .endfn tinymath_exp2f,globl + .alias tinymath_exp2f,exp2f diff --git a/libc/tinymath/exp2l.S b/libc/tinymath/exp2l.S index 41387667..6ced34b5 100644 --- a/libc/tinymath/exp2l.S +++ b/libc/tinymath/exp2l.S @@ -20,8 +20,12 @@ #include "libc/macros.h" .source __FILE__ -/ Returns 2^x. -exp2l: push %rbp +/ Returns 2^𝑥. +/ +/ @param 𝑥 is an 80-bit long double passed on stack in 16-bytes +/ @return result of exponentiation on FPU stack in %st +tinymath_exp2l: + push %rbp mov %rsp,%rbp .profilable fldt 16(%rbp) @@ -35,7 +39,8 @@ exp2l: push %rbp fstp %st(1) pop %rbp ret - .endfn exp2l,globl + .endfn tinymath_exp2l,globl + .alias tinymath_exp2l,exp2l .rodata.cst4 .Lone: .float 1.0 diff --git a/libc/tinymath/expm1.S b/libc/tinymath/expm1.S index f4f4295e..97fac375 100644 --- a/libc/tinymath/expm1.S +++ b/libc/tinymath/expm1.S @@ -20,6 +20,12 @@ #include "libc/macros.h" .source __FILE__ -expm1: ezlea expm1l,ax +/ Returns 𝑒^x-1. +/ +/ @param 𝑥 is double scalar in low half of %xmm0 +/ @return double scalar in low half of %xmm0 +tinymath_expm1: + ezlea tinymath_expm1l,ax jmp _d2ld2 - .endfn expm1,globl + .endfn tinymath_expm1,globl + .alias tinymath_expm1,expm1 diff --git a/libc/tinymath/expm1f.S b/libc/tinymath/expm1f.S index 35e5b900..3cfef3c2 100644 --- a/libc/tinymath/expm1f.S +++ b/libc/tinymath/expm1f.S @@ -20,6 +20,12 @@ #include "libc/macros.h" .source __FILE__ -expm1f: ezlea expm1l,ax +/ Returns 𝑒^x-1. +/ +/ @param 𝑥 is float scalar in low quarter of %xmm0 +/ @return float scalar in low quarter of %xmm0 +tinymath_expm1f: + ezlea tinymath_expm1l,ax jmp _f2ld2 - .endfn expm1f,globl + .endfn tinymath_expm1f,globl + .alias tinymath_expm1f,expm1f diff --git a/libc/tinymath/expm1l.S b/libc/tinymath/expm1l.S index e15763cd..3378c61d 100644 --- a/libc/tinymath/expm1l.S +++ b/libc/tinymath/expm1l.S @@ -20,8 +20,12 @@ #include "libc/macros.h" .source __FILE__ -/ Returns exp(𝑥) - 1. -expm1l: push %rbp +/ Returns 𝑒^x-1. +/ +/ @param 𝑥 is an 80-bit long double passed on stack in 16-bytes +/ @return result of exponentiation on FPU stack in %st +tinymath_expm1l: + push %rbp mov %rsp,%rbp .profilable fldt 16(%rbp) @@ -41,7 +45,8 @@ expm1l: push %rbp faddp %st,%st(1) pop %rbp ret - .endfn expm1l,globl + .endfn tinymath_expm1l,globl + .alias tinymath_expm1l,expm1l .rodata.cst4 .Lone: .float 1.0 diff --git a/libc/tinymath/f2ld2.S b/libc/tinymath/f2ld2.S index 0d7c8cb7..f7db0fd5 100644 --- a/libc/tinymath/f2ld2.S +++ b/libc/tinymath/f2ld2.S @@ -18,7 +18,6 @@ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/macros.h" -.source __FILE__ / Thunks float(*fn)(float,float) -> long double fn. / @@ -41,3 +40,4 @@ _f2ld2: push %rbp leave ret .endfn _f2ld2,globl,hidden + .source __FILE__ diff --git a/libc/tinymath/fmodl.S b/libc/tinymath/fmodl.S index 98a6d07a..dd48151d 100644 --- a/libc/tinymath/fmodl.S +++ b/libc/tinymath/fmodl.S @@ -17,6 +17,7 @@ │ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "ape/lib/pc.h" #include "libc/macros.h" .source __FILE__ @@ -27,8 +28,8 @@ tinymath_fmodl: fldt 32(%rbp) fldt 16(%rbp) 1: fprem - fnstsw %ax - testb $4,%ah + fnstsw + test $FPU_C2>>8,%ah jnz 1b fstp %st(1) pop %rbp diff --git a/libc/tinymath/ilogb.S b/libc/tinymath/ilogb.S index 9cb0a10a..985874f0 100644 --- a/libc/tinymath/ilogb.S +++ b/libc/tinymath/ilogb.S @@ -18,10 +18,25 @@ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/macros.h" -.source __FILE__ +/ Returns log₂ₓ exponent part of double. +/ +/ @param 𝑥 is double passed in %xmm0 +/ @return result in %eax +/ @note needs sse3 tinymath_ilogb: - ezlea tinymath_ilogbl,ax - jmp _d2ld2 + push %rbp + mov %rsp,%rbp + .profilable + push %rax + movsd %xmm0,(%rsp) + fldl (%rsp) + fxtract + fstp %st + fisttpl (%rsp) + mov (%rsp),%eax + leave + ret .endfn tinymath_ilogb,globl .alias tinymath_ilogb,ilogb + .source __FILE__ diff --git a/libc/tinymath/ilogbf.S b/libc/tinymath/ilogbf.S index 36e2ccf1..9b92cddd 100644 --- a/libc/tinymath/ilogbf.S +++ b/libc/tinymath/ilogbf.S @@ -18,10 +18,25 @@ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/macros.h" -.source __FILE__ +/ Returns log₂ₓ exponent part of float. +/ +/ @param 𝑥 is float passed in %xmm0 +/ @return result in %eax +/ @note needs sse3 tinymath_ilogbf: - ezlea tinymath_ilogbl,ax - jmp _f2ld2 + push %rbp + mov %rsp,%rbp + .profilable + push %rax + movss %xmm0,(%rsp) + flds (%rsp) + fxtract + fstp %st + fisttpl (%rsp) + mov (%rsp),%eax + leave + ret .endfn tinymath_ilogbf,globl .alias tinymath_ilogbf,ilogbf + .source __FILE__ diff --git a/libc/tinymath/ilogbl.S b/libc/tinymath/ilogbl.S index fbe36db6..97535971 100644 --- a/libc/tinymath/ilogbl.S +++ b/libc/tinymath/ilogbl.S @@ -18,38 +18,24 @@ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/macros.h" -.source __FILE__ +/ Returns log₂ₓ exponent part of long double. +/ +/ @param 𝑥 is long double passed on stack +/ @return result in %eax +/ @note needs sse3 tinymath_ilogbl: + push %rbp + mov %rsp,%rbp .profilable - sub $24,%rsp - fldt 32(%rsp) - fnstcw 14(%rsp) - movzwl 14(%rsp),%eax - orb $12,%ah - movw %ax,12(%rsp) + fldt 16(%rbp) fxtract fstp %st - fldcw 12(%rsp) - fistpl 8(%rsp) - fldcw 14(%rsp) - movl 8(%rsp),%eax - add $24,%rsp + push %rax + fisttpl (%rsp) + mov (%rsp),%eax + leave ret .endfn tinymath_ilogbl,globl .alias tinymath_ilogbl,ilogbl - -/* - TODO(jart) -.globl ilogbl -.type ilogbl,@function -ilogbl: sub $24,%rsp - fldt 32(%rsp) - fxtract - fstp %st - fisttpl 12(%rsp) - movl 12(%rsp),%eax - add $24,%rsp - ret -.size ilogbl,.-ilogbl -*/ + .source __FILE__ diff --git a/libc/tinymath/log1p.S b/libc/tinymath/log1p.S index 6384cf16..cd67d530 100644 --- a/libc/tinymath/log1p.S +++ b/libc/tinymath/log1p.S @@ -20,7 +20,12 @@ #include "libc/macros.h" .source __FILE__ -log1p: push %rbp +/ Returns log(𝟷+𝑥). +/ +/ @param 𝑥 is double scalar in low half of %xmm0 +/ @return double scalar in low half of %xmm0 +tinymath_log1p: + push %rbp mov %rsp,%rbp .profilable push %rax @@ -48,12 +53,11 @@ log1p: push %rbp fstpl (%rsp) vmovsd (%rsp),%xmm0 jmp 0b - .endfn log1p,globl + .endfn tinymath_log1p,globl + .alias tinymath_log1p,log1p - .section .rodata.cst16,"aM",@progbits,16 - .align 16 + .rodata.cst16 .LC16: .long 205731576 .long 2515933592 .long 16381 .long 0 - .previous diff --git a/libc/tinymath/log1pf.S b/libc/tinymath/log1pf.S index 768a341a..ee5c6bed 100644 --- a/libc/tinymath/log1pf.S +++ b/libc/tinymath/log1pf.S @@ -20,7 +20,12 @@ #include "libc/macros.h" .source __FILE__ -log1pf: push %rbp +/ Returns log(𝟷+𝑥). +/ +/ @param 𝑥 is float scalar in low quarter of %xmm0 +/ @return float scalar in low quarter of %xmm0 +tinymath_log1pf: + push %rbp mov %rsp,%rbp .profilable push %rax @@ -46,12 +51,11 @@ log1pf: push %rbp fxch %st(1) fyl2x jmp 1b - .endfn log1pf,globl + .endfn tinymath_log1pf,globl + .alias tinymath_log1pf,log1pf - .section .rodata.cst16,"aM",@progbits,16 - .align 16 + .rodata.cst16 .LC16: .long 205731576 .long 2515933592 .long 16381 .long 0 - .previous diff --git a/libc/tinymath/log1pl.S b/libc/tinymath/log1pl.S index 121119b0..2eb8ba66 100644 --- a/libc/tinymath/log1pl.S +++ b/libc/tinymath/log1pl.S @@ -20,7 +20,12 @@ #include "libc/macros.h" .source __FILE__ -log1pl: push %rbp +/ Returns log(𝟷+𝑥). +/ +/ @param 𝑥 is an 80-bit long double passed on stack in 16-bytes +/ @return result of exponentiation on FPU stack in %st +tinymath_log1pl: + push %rbp mov %rsp,%rbp .profilable fldt 16(%rbp) @@ -42,12 +47,11 @@ log1pl: push %rbp fxch %st(1) fyl2x jmp 0b - .endfn log1pl,globl + .endfn tinymath_log1pl,globl + .alias tinymath_log1pl,log1pl - .section .rodata.cst16,"aM",@progbits,16 - .align 16 + .rodata.cst16 .LC16: .long 205731576 .long 2515933592 .long 16381 .long 0 - .previous diff --git a/libc/tinymath/log2l.S b/libc/tinymath/log2l.S index f4bfbd28..f739da61 100644 --- a/libc/tinymath/log2l.S +++ b/libc/tinymath/log2l.S @@ -24,6 +24,7 @@ / / @param 𝑥 is an 80-bit long double passed on stack in 16-bytes / @return result in %st +/ @see ilogbl() log2l: push %rbp mov %rsp,%rbp .profilable diff --git a/libc/tinymath/logb.S b/libc/tinymath/logb.S index ec7e805a..e14ecdf7 100644 --- a/libc/tinymath/logb.S +++ b/libc/tinymath/logb.S @@ -18,18 +18,14 @@ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/macros.h" -.source __FILE__ -logb: push %rbp - mov %rsp,%rbp - .profilable - push %rax - movsd %xmm0,(%rsp) - fldl (%rsp) - fxtract - fstp %st - fstpl (%rsp) - movsd (%rsp),%xmm0 - leave - ret - .endfn logb,globl +/ Returns log₂ₓ exponent part of double. +/ +/ @param 𝑥 is double passed in %xmm0 +/ @return result in %xmm0 +tinymath_logb: + ezlea tinymath_logbl,ax + jmp _d2ld2 + .endfn tinymath_logb,globl + .alias tinymath_logb,logb + .source __FILE__ diff --git a/libc/tinymath/logbf.S b/libc/tinymath/logbf.S index d7b3d7df..8538dac1 100644 --- a/libc/tinymath/logbf.S +++ b/libc/tinymath/logbf.S @@ -18,18 +18,14 @@ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/macros.h" -.source __FILE__ -logbf: push %rbp - mov %rsp,%rbp - .profilable - push %rax - movss %xmm0,-4(%rbp) - flds -4(%rbp) - fxtract - fstp %st - fstps -4(%rbp) - movss -4(%rbp),%xmm0 - leave - ret - .endfn logbf,globl +/ Returns log₂ₓ exponent part of float. +/ +/ @param 𝑥 is float passed in %xmm0 +/ @return result in %xmm0 +tinymath_logbf: + ezlea tinymath_logbl,ax + jmp _f2ld2 + .endfn tinymath_logbf,globl + .alias tinymath_logbf,logbf + .source __FILE__ diff --git a/libc/tinymath/logbl.S b/libc/tinymath/logbl.S index d860f46d..07c4a64b 100644 --- a/libc/tinymath/logbl.S +++ b/libc/tinymath/logbl.S @@ -18,9 +18,13 @@ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/macros.h" -.source __FILE__ -logbl: push %rbp +/ Returns log₂ₓ exponent part of long double. +/ +/ @param 𝑥 is long double passed on stack +/ @return result in %st0 +tinymath_logbl: + push %rbp mov %rsp,%rbp .profilable fldt 16(%rbp) @@ -28,4 +32,6 @@ logbl: push %rbp fstp %st pop %rbp ret - .endfn logbl,globl + .endfn tinymath_logbl,globl + .alias tinymath_logbl,logbl + .source __FILE__ diff --git a/libc/tinymath/lroundl.S b/libc/tinymath/lroundl.S index d174fec6..baa2d0bf 100644 --- a/libc/tinymath/lroundl.S +++ b/libc/tinymath/lroundl.S @@ -34,7 +34,7 @@ tinymath_lroundl: or $0b00000100,%dh # →-∞ mov %dx,-4(%rbp) fxam - fnstsw %ax + fnstsw fabs test $FPU_C1>>8,%ah fadds .Lhalf(%rip) diff --git a/libc/tinymath/remainderl.S b/libc/tinymath/remainderl.S index 99b60868..942fac1d 100644 --- a/libc/tinymath/remainderl.S +++ b/libc/tinymath/remainderl.S @@ -28,9 +28,9 @@ tinymath_remainderl: fldt 32(%rbp) fldt 16(%rbp) 1: fprem1 - fnstsw %ax + fnstsw test $FPU_C2>>8,%ah - jne 1b + jnz 1b fstp %st(1) pop %rbp ret diff --git a/libc/tinymath/roundl.S b/libc/tinymath/roundl.S index 8f87f931..4f4413da 100644 --- a/libc/tinymath/roundl.S +++ b/libc/tinymath/roundl.S @@ -19,8 +19,11 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "ape/lib/pc.h" #include "libc/macros.h" -.source __FILE__ +/ Rounds to nearest integer, away from zero. +/ +/ @param 𝑥 is an 80-bit long double passed on stack in 16-bytes +/ @return result of exponentiation on FPU stack in %st tinymath_roundl: push %rbp mov %rsp,%rbp @@ -30,10 +33,10 @@ tinymath_roundl: fnstcw -8(%rbp) movzwl -8(%rbp),%edx and $0b11110011,%dh # RC (Rounding Control) - or $0b00000100,%dh # →-∞ + or $0b00000100,%dh # →-∞ a.k.a. floor() mov %dx,-4(%rbp) - fxam - fnstsw %ax + fxam # C1 is set to sign bit + fnstsw fabs test $FPU_C1>>8,%ah fadds .Lhalf(%rip) @@ -46,6 +49,7 @@ tinymath_roundl: ret .endfn tinymath_roundl,globl .alias tinymath_roundl,roundl + .source __FILE__ .rodata.cst4 .Lhalf: .float .5 diff --git a/libc/tinymath/scalbnf.S b/libc/tinymath/scalbnf.S index a7c4f649..6c7a7bb1 100644 --- a/libc/tinymath/scalbnf.S +++ b/libc/tinymath/scalbnf.S @@ -20,16 +20,21 @@ #include "libc/macros.h" .source __FILE__ -tinymath_scalbnl: +tinymath_scalbnf: + push %rbp + mov %rsp,%rbp .profilable - sub $24,%rsp - fldt 32(%rsp) - movl %edi,12(%rsp) - fildl 12(%rsp) + push %rax + movss %xmm0,-4(%rbp) + flds -4(%rbp) + movl %edi,-4(%rbp) + fildl -4(%rbp) fxch %st(1) - add $24,%rsp fscale fstp %st(1) + fstps -4(%rbp) + movss -4(%rbp),%xmm0 + leave ret - .endfn tinymath_scalbnl,globl - .alias tinymath_scalbnl,scalbnl + .endfn tinymath_scalbnf,globl + .alias tinymath_scalbnf,scalbnf diff --git a/libc/tinymath/scalbnl.S b/libc/tinymath/scalbnl.S index 6c7a7bb1..8e97cc5f 100644 --- a/libc/tinymath/scalbnl.S +++ b/libc/tinymath/scalbnl.S @@ -20,21 +20,22 @@ #include "libc/macros.h" .source __FILE__ -tinymath_scalbnf: +/ Returns 𝑥 × 𝑟ʸ where 𝑟 is radix of hardware architecture. +/ +/ @param 𝑥 is long double passed on stack +/ @param 𝑦 is exponent via %edi +/ @see FLT_RADIX +tinymath_scalbnl: push %rbp mov %rsp,%rbp .profilable - push %rax - movss %xmm0,-4(%rbp) - flds -4(%rbp) - movl %edi,-4(%rbp) - fildl -4(%rbp) - fxch %st(1) + fldt 16(%rsp) + push %rdi + fildl (%rsp) + fxch fscale fstp %st(1) - fstps -4(%rbp) - movss -4(%rbp),%xmm0 leave ret - .endfn tinymath_scalbnf,globl - .alias tinymath_scalbnf,scalbnf + .endfn tinymath_scalbnl,globl + .alias tinymath_scalbnl,scalbnl diff --git a/libc/tinymath/sincosl.S b/libc/tinymath/sincosl.S index 96f47be9..78a97c42 100644 --- a/libc/tinymath/sincosl.S +++ b/libc/tinymath/sincosl.S @@ -17,6 +17,7 @@ │ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "ape/lib/pc.h" #include "libc/macros.h" .source __FILE__ @@ -31,10 +32,22 @@ tinymath_sincosl: mov %rsp,%rbp .profilable fldt 16(%rbp) - fsincos +0: fsincos + fstsw + test $FPU_C2>>8,%ah + jnz 1f fstpt (%rsi) fstpt (%rdi) pop %rbp ret +1: fldpi + fadd %st + fxch +2: fprem1 + fnstsw + test $FPU_C2>>8,%ah + jnz 2b + fstp %st(1) + jmp 0b .endfn tinymath_sincosl,globl .alias tinymath_sincosl,sincosl diff --git a/libc/tinymath/truncl.S b/libc/tinymath/truncl.S index a50d93cb..3966629a 100644 --- a/libc/tinymath/truncl.S +++ b/libc/tinymath/truncl.S @@ -26,7 +26,7 @@ tinymath_truncl: fldt 32(%rsp) fnstcw 14(%rsp) movzwl 14(%rsp),%eax - or $12,%ah + or $0b1100,%ah # round to zero mov %ax,12(%rsp) fldcw 12(%rsp) frndint diff --git a/libc/unicode/kcombiningchars.S b/libc/unicode/kcombiningchars.S index cf5b1eee..b05c5e58 100644 --- a/libc/unicode/kcombiningchars.S +++ b/libc/unicode/kcombiningchars.S @@ -36,11 +36,11 @@ kCombiningChars: .init.start 400,_init_kCombiningChars push %rsi - mov $1257,%edx + mov $1203,%edx call lz4cpy mov %rax,%rdi pop %rsi - add $1264,%rsi + add $1208,%rsi .init.end 400,_init_kCombiningChars / The data below is sparse, as evidenced by: @@ -74,112 +74,105 @@ kCombiningCharsCtor: / o/libc/str/CombiningChars.bin.lz4 .initro 400,_init_kCombiningChars kCombiningCharsLz4: - .byte 0x16,0x00,0x01,0x00,0x24,0x40,0x01,0x0a #▬ ☺ $@☺◙ - .byte 0x00,0x4f,0x81,0x10,0x01,0x00,0x01,0x00 # Oü►☺ ☺  - .byte 0x2c,0x7b,0x3c,0x00,0xfc,0xff,0xe0,0xaf #,{< ⁿλα» - .byte 0xff,0x01,0x00,0x3f,0x20,0x00,0x30,0x38 #λ☺ ?  08 - .byte 0x00,0x0c,0x2f,0xf8,0x03,0x5a,0x00,0x0d # ♀/°♥Z ♪ - .byte 0x10,0xfe,0x49,0x00,0x23,0xbf,0xb6,0x0e #►■I .┐╢♫ - .byte 0x00,0x42,0x3f,0x00,0xff,0x17,0x39,0x00 # B? λ↨9  - .byte 0x48,0xff,0xff,0x00,0x00,0xb7,0x00,0xf7 #Hλλ  ╖ ≈ - .byte 0x01,0xc0,0xbf,0x9f,0x3d,0x00,0x00,0x00 #☺└┐ƒ=    - .byte 0x80,0x02,0x00,0x00,0x00,0xff,0xff,0xff #Ç☻   λλλ - .byte 0x07,0x1a,0x00,0x13,0xff,0x28,0x00,0x91 #•→ ‼λ( æ - .byte 0xf8,0x0f,0x20,0x00,0x00,0xc0,0xfb,0xef #°☼   └√∩ - .byte 0x3e,0x0e,0x00,0x1b,0x0e,0x8a,0x00,0x26 #>♫ ←♫è & - .byte 0xff,0xff,0x37,0x00,0xa2,0x14,0xfe,0x21 #λλ7 ó¶■! - .byte 0xfe,0x00,0x0c,0x00,0x00,0x00,0x02,0x10 #■ ♀   ☻► - .byte 0x00,0x40,0x10,0x1e,0x20,0x00,0x10,0x00 # @►▲  ►  - .byte 0x23,0x40,0x06,0x10,0x00,0x20,0x86,0x39 #.@♠►  å9 - .byte 0x1a,0x00,0x24,0x23,0x00,0x10,0x00,0x21 #→ $. ► ! - .byte 0xbe,0x21,0x20,0x00,0x13,0xfc,0x30,0x00 #╛!  ‼ⁿ0  - .byte 0x41,0x90,0x1e,0x20,0x40,0x40,0x00,0x13 #AÉ▲ @@ ‼ - .byte 0x04,0x5e,0x00,0x22,0x01,0x20,0x08,0x00 #♦^ .☺ ◘  - .byte 0x13,0x11,0x93,0x00,0x38,0xc1,0x3d,0x60 #‼◄ô 8┴=` - .byte 0x60,0x00,0x31,0x90,0x40,0x30,0x40,0x00 #` 1É@0@  - .byte 0x13,0x00,0x0f,0x01,0x13,0x18,0x70,0x00 #‼ ☼☺‼↑p  - .byte 0x06,0x9f,0x00,0x27,0x04,0x5c,0x0d,0x00 #♠ƒ '♦\♪  - .byte 0x48,0xf2,0x07,0x80,0x7f,0x1d,0x00,0x45 #H≥•Ç⌂↔ E - .byte 0xf2,0x1f,0x00,0x3f,0x0d,0x00,0x43,0x03 #≥▼ ?♪ C♥ - .byte 0x00,0x00,0xa0,0x57,0x00,0x50,0xfe,0x7f #  áW P■⌂ - .byte 0xdf,0xe0,0xff,0x41,0x01,0x28,0x1f,0x40 #▀αλA☺(▼@ - .byte 0x2f,0x00,0xff,0x00,0xe0,0xfd,0x66,0x00 #/ λ α²f  - .byte 0x00,0x00,0xc3,0x01,0x00,0x1e,0x00,0x64 #  ├☺ ▲ d - .byte 0x20,0x00,0x20,0x7a,0x01,0x05,0x1f,0xff #   z☺♣▼λ - .byte 0x01,0x00,0x00,0x0f,0x13,0x02,0x18,0x2f #☺  ☼‼☻↑/ - .byte 0xe0,0x00,0x01,0x00,0x62,0x13,0x1c,0x04 #α ☺ b‼∟♦ - .byte 0x00,0x26,0x0c,0x00,0x42,0x01,0x52,0xb0 # &♀ B☺R░ - .byte 0x3f,0x40,0xfe,0x0f,0xe8,0x00,0x1a,0x78 #?@■☼Φ →x - .byte 0x2e,0x00,0x26,0x60,0x00,0x85,0x01,0x04 #. &` à☺♦ - .byte 0x14,0x00,0x4f,0x87,0x01,0x04,0x0e,0x60 #¶ Oç☺♦♫` - .byte 0x00,0x07,0x23,0x80,0x09,0x3f,0x03,0x53 # •.Ç○?♥S - .byte 0x7f,0xe5,0x1f,0xf8,0x9f,0x2a,0x01,0x05 #⌂σ▼°ƒ*☺♣ - .byte 0x8e,0x01,0x11,0x0f,0x06,0x00,0x32,0xd0 #Ä☺◄☼♠ 2╨ - .byte 0x17,0x04,0x70,0x02,0x01,0xd0,0x01,0x23 #↨♦p☻☺╨☺. - .byte 0x3c,0x3b,0x32,0x00,0x13,0xa3,0xde,0x01 #<;2 ‼ú▐☺ - .byte 0x2f,0xf0,0xcf,0x58,0x00,0x00,0x6f,0xf7 #/≡╧X  o≈ - .byte 0xff,0xfd,0x21,0x10,0x03,0x8c,0x01,0x0c #λ²!►♥î☺♀ - .byte 0x1f,0xfb,0x15,0x01,0x24,0x40,0xa0,0x03 #▼√§☺$@á♥ - .byte 0xe0,0x00,0x02,0x00,0x72,0x60,0x00,0xf8 #α ☻ r` ° - .byte 0x00,0x00,0x00,0x7c,0x15,0x00,0x2c,0xdf #   |§ ,▀ - .byte 0xff,0x62,0x00,0x2f,0x01,0x00,0x01,0x00 #λb /☺ ☺  - .byte 0xff,0x6b,0x1d,0x80,0xff,0x01,0x1c,0x80 #λk↔Çλ☺∟Ç - .byte 0xa2,0x01,0x0f,0x68,0x00,0x32,0x19,0x3c #ó☺☼h 2↓< - .byte 0x0e,0x00,0x2f,0x1e,0x00,0x01,0x00,0xff #♫ /▲ ☺ λ + .byte 0x1f,0x00,0x01,0x00,0x4c,0x19,0xff,0x01 #▼ ☺ L↓λ☺ + .byte 0x00,0x0f,0x30,0x00,0x0f,0x2f,0xf8,0x03 # ☼0 ☼/°♥ + .byte 0x22,0x00,0x0d,0x10,0xfe,0x49,0x00,0x23 #. ♪►■I . + .byte 0xbf,0xb6,0x0e,0x00,0x42,0x3f,0x00,0xff #┐╢♫ B⁇ λ + .byte 0x17,0x39,0x00,0x00,0x5e,0x00,0x17,0x01 #↨9  ^ ↨☺ + .byte 0x28,0x00,0x92,0xc0,0xbf,0x9f,0x3d,0x00 #( Æ└┐ƒ=  + .byte 0x00,0x00,0x80,0x02,0x86,0x00,0x17,0x07 #  Ç☻å ↨• + .byte 0x1a,0x00,0x13,0xff,0x28,0x00,0x91,0xf8 #→ ‼λ( æ° + .byte 0x0f,0x20,0x00,0x00,0xc0,0xfb,0xef,0x3e #☼   └√∩> + .byte 0x0e,0x00,0x1b,0x0e,0x8a,0x00,0x26,0xff #♫ ←♫è &λ + .byte 0xff,0x37,0x00,0xa2,0x14,0xfe,0x21,0xfe #λ7 ó¶■!■ + .byte 0x00,0x0c,0x00,0x00,0x00,0x02,0x10,0x00 # ♀   ☻►  + .byte 0x40,0x10,0x1e,0x20,0x00,0x10,0x00,0x23 #@►▲  ► . + .byte 0x40,0x06,0x10,0x00,0x20,0x86,0x39,0x1a #@♠►  å9→ + .byte 0x00,0x24,0x23,0x00,0x10,0x00,0x21,0xbe # $. ► !╛ + .byte 0x21,0x20,0x00,0x13,0xfc,0x30,0x00,0x41 #!  ‼ⁿ0 A + .byte 0x90,0x1e,0x20,0x40,0x40,0x00,0x13,0x04 #É▲ @@ ‼♦ + .byte 0x5e,0x00,0x22,0x01,0x20,0x08,0x00,0x13 #^ .☺ ◘ ‼ + .byte 0x11,0x93,0x00,0x38,0xc1,0x3d,0x60,0x60 #◄ô 8┴=`` + .byte 0x00,0x31,0x90,0x40,0x30,0x40,0x00,0x13 # 1É@0@ ‼ + .byte 0x00,0x0f,0x01,0x13,0x18,0x70,0x00,0x06 # ☼☺‼↑p ♠ + .byte 0x9f,0x00,0x27,0x04,0x5c,0x0d,0x00,0x48 #ƒ ‘♦\♪ H + .byte 0xf2,0x07,0x80,0x7f,0x1d,0x00,0x45,0xf2 #≥•Ç⌂↔ E≥ + .byte 0x1f,0x00,0x3f,0x0d,0x00,0x43,0x03,0x00 #▼ ⁇♪ C♥  + .byte 0x00,0xa0,0x57,0x00,0x50,0xfe,0x7f,0xdf # áW P■⌂▀ + .byte 0xe0,0xff,0x41,0x01,0x28,0x1f,0x40,0x2f #αλA☺(▼@/ + .byte 0x00,0xff,0x00,0xe0,0xfd,0x66,0x00,0x00 # λ α²f   + .byte 0x00,0xc3,0x01,0x00,0x1e,0x00,0x64,0x20 # ├☺ ▲ d + .byte 0x00,0x20,0xcc,0x01,0x0b,0x0f,0xd2,0x01 #  ╠☺♂☼╥☺ + .byte 0x1d,0x06,0x66,0x00,0x1f,0x00,0x01,0x00 #↔♠f ▼ ☺  + .byte 0x62,0x13,0x1c,0x04,0x00,0x26,0x0c,0x00 #b‼∟♦ &♀  + .byte 0x42,0x01,0x52,0xb0,0x3f,0x40,0xfe,0x0f #B☺R░⁇@■☼ + .byte 0xe8,0x00,0x1a,0x78,0x2e,0x00,0x26,0x60 #Φ →x. &` + .byte 0x00,0x85,0x01,0x04,0x14,0x00,0x4f,0x87 # à☺♦¶ Oç + .byte 0x01,0x04,0x0e,0x60,0x00,0x07,0x22,0x80 #☺♦♫` •.Ç + .byte 0x09,0x08,0x00,0x63,0x40,0x7f,0xe5,0x1f #○◘ c@⌂σ▼ + .byte 0xf8,0x9f,0x2a,0x01,0x05,0x8e,0x01,0x11 #°ƒ*☺♣Ä☺◄ + .byte 0x0f,0x06,0x00,0x32,0xd0,0x17,0x04,0x70 #☼♠ 2╨↨♦p + .byte 0x02,0x01,0xd0,0x01,0x23,0x3c,0x3b,0x32 #☻☺╨☺.<;2 + .byte 0x00,0x13,0xa3,0xde,0x01,0x2f,0xf0,0xcf # ‼ú▐☺/≡╧ + .byte 0x58,0x00,0x00,0x6f,0xf7,0xff,0xfd,0x21 #X  o≈λ²! + .byte 0x10,0x03,0x8c,0x01,0x0c,0x1f,0xfb,0x1f #►♥î☺♀▼√▼ + .byte 0x01,0x2e,0x52,0xf8,0x00,0x00,0x00,0x7c #☺.R°   | + .byte 0x0b,0x00,0x2c,0xdf,0xff,0x62,0x00,0x2f #♂ ,▀λb / + .byte 0x01,0x00,0x01,0x00,0xff,0x6b,0x1d,0x80 #☺ ☺ λk↔Ç + .byte 0xff,0x01,0x1c,0x80,0xa2,0x01,0x0f,0x68 #λ☺∟Çó☺☼h + .byte 0x00,0x32,0x19,0x3c,0x0e,0x00,0x2f,0x06 # 2↓<♫ /♠ + .byte 0x00,0x01,0x00,0xff,0xff,0xff,0xff,0xff # ☺ λλλλλ .byte 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff #λλλλλλλλ - .byte 0xff,0xff,0xff,0xff,0xff,0xb3,0x20,0x80 #λλλλλ│ Ç - .byte 0xf7,0xf6,0x12,0x27,0xc0,0x00,0xfb,0x12 #≈÷↕'└ √↕ - .byte 0x58,0xff,0xff,0x7f,0x00,0x03,0x24,0x00 #Xλλ⌂ ♥$  - .byte 0x1a,0x06,0x33,0x00,0x23,0x44,0x08,0xf4 #→♠3 .D◘⌠ - .byte 0x11,0x0b,0x4b,0x00,0x11,0x30,0x60,0x0f #◄♂K ◄0`☼ - .byte 0x11,0x03,0x70,0x0f,0x62,0xc0,0x3f,0x00 #◄♥p☼b└?  - .byte 0x00,0x80,0xff,0x46,0x00,0x02,0x10,0x14 # ÇλF ☻►¶ - .byte 0x20,0xc8,0x33,0x06,0x00,0x05,0x29,0x13 # ╚3♠ ♣)‼ - .byte 0x52,0x7e,0x66,0x00,0x08,0x10,0xf8,0x13 #R~f ◘►°‼ - .byte 0x02,0x11,0x00,0x21,0x9d,0xc1,0x43,0x12 #☻◄ !¥┴C↕ - .byte 0x19,0x30,0x66,0x13,0x1c,0x08,0x64,0x00 #↓0f‼∟◘d  - .byte 0x2f,0x20,0x21,0x96,0x0a,0xff,0xff,0xff #/ !û◙λλλ - .byte 0xff,0xff,0xff,0xff,0xff,0xff,0xdb,0x1e #λλλλλλ█▲ - .byte 0x40,0x13,0x00,0x3f,0xfc,0xff,0x03,0x5d #@‼ ?ⁿλ♥] - .byte 0x00,0x34,0x00,0x02,0x1a,0x0f,0x06,0x1a # 4 ☻→☼♠→ - .byte 0x08,0x1d,0x80,0xdc,0x1f,0x0a,0x91,0x0a #◘↔Ç▄▼◙æ◙ - .byte 0x1f,0x0e,0x7f,0x00,0x2c,0x1f,0x20,0x1d #▼♫⌂ ,▼ ↔ - .byte 0x00,0x09,0x0e,0x74,0x00,0x2f,0xc0,0x07 # ○♫t /└• - .byte 0xdd,0x01,0xbd,0x22,0x6e,0xf0,0x23,0x1e #▌☺╜.n≡.▲ - .byte 0x0f,0x1c,0x00,0x01,0x1f,0x60,0x64,0x00 #☼∟ ☺▼`d  - .byte 0x34,0x1f,0xf0,0x44,0x00,0x30,0x05,0xf4 #4▼≡D 0♣⌠ - .byte 0x20,0x0b,0x18,0x00,0x1a,0x02,0xb1,0x1e # ♂↑ →☻▒▲ - .byte 0x03,0x72,0x1c,0x24,0x78,0x26,0xda,0x01 #♥r∟$x&┌☺ - .byte 0x00,0xf0,0x0c,0x35,0x80,0xef,0x1f,0x32 # ≡♀5Ç∩▼2 - .byte 0x02,0x02,0x20,0x00,0x29,0xc0,0x7f,0x26 #☻☻  )└⌂& - .byte 0x1c,0x3f,0x80,0xd3,0x40,0x7c,0x02,0x01 #∟?Ç╙@|☻☺ - .byte 0x26,0xf8,0x07,0xc0,0x20,0x00,0x7e,0x00 #&°•└  ~  - .byte 0x3f,0xc0,0x1f,0x1f,0xc7,0x02,0x06,0x19 #?└▼▼╟☻♠↓ - .byte 0x5c,0x28,0x03,0x3f,0xf8,0x85,0x0d,0xb1 #\(♥?°à♪▒ - .byte 0x1c,0x0b,0x22,0xb0,0x01,0xa3,0x0d,0x04 #∟♂.░☺ú♪♦ - .byte 0x30,0x00,0x19,0xa7,0xde,0x00,0x29,0x28 #0 ↓º▐ )( - .byte 0xbf,0x78,0x20,0x2f,0xbc,0x0f,0x38,0x0e #┐x /╝☼8♫ - .byte 0x0d,0x2f,0xff,0x06,0x96,0x01,0x20,0x10 #♪/λ♠û☺ ► - .byte 0x0c,0x74,0x00,0x11,0xfe,0xd2,0x02,0x52 #♀t ◄■╥☻R - .byte 0xf8,0x79,0x80,0x00,0x7e,0x4c,0x03,0x2f #°yÇ ~L♥/ - .byte 0xfc,0x7f,0xdb,0x03,0x20,0x28,0x7f,0xbf #ⁿ⌂█♥ (⌂┐ - .byte 0x1c,0x04,0x3b,0xff,0xfc,0x6d,0x20,0x00 #∟♦;λⁿm   - .byte 0x26,0x7e,0xb4,0x21,0x00,0x1f,0xa3,0x58 #&~┤! ▼úX - .byte 0x00,0x18,0x1f,0x18,0x23,0x07,0xff,0xff # ↑▼↑.•λλ - .byte 0x96,0x2f,0xff,0x01,0xfb,0x0d,0xff,0xff #û/λ☺√♪λλ - .byte 0xff,0xff,0xff,0xff,0xc9,0x04,0xf0,0x0a #λλλλ╔♦≡◙ - .byte 0x1f,0x7f,0x1c,0x19,0x70,0x04,0x08,0x00 #▼⌂∟↓p♦◘  - .byte 0x1f,0x07,0x30,0x18,0xff,0xff,0xff,0xff #▼•0↑λλλλ - .byte 0xff,0xff,0xff,0xff,0xff,0x96,0x2f,0x60 #λλλλλû/` - .byte 0x0f,0x5f,0x25,0xff,0xff,0x87,0x5d,0x03 #☼_%λλç]♥ - .byte 0xf8,0xff,0xe7,0x0f,0x30,0x34,0x05,0x66 #°λτ☼04♣f - .byte 0x37,0x0f,0xba,0x14,0xe2,0x01,0x01,0x00 #7☼║¶Γ☺☺  - .byte 0x12,0x7f,0x2d,0x3a,0x20,0x1f,0x20,0x01 #↕⌂-: ▼ ☺ - .byte 0x26,0x3f,0xf8,0xfe,0xff,0xc0,0x00,0x97 #&?°■λ└ ù - .byte 0x5f,0x7f,0xff,0xff,0xf9,0xdb,0x13,0x0e #_⌂λλ∙█‼♫ - .byte 0x0e,0x1f,0x7f,0xb9,0x1a,0x24,0x0f,0xda #♫▼⌂╣→$☼┌ - .byte 0x01,0xa9,0x0a,0xf4,0x00,0x1f,0xf0,0x37 #☺⌐◙⌠ ▼≡7 - .byte 0x0f,0xff,0x44,0x2f,0xf8,0x00,0x01,0x00 #☼λD/° ☺  + .byte 0xff,0xb3,0x20,0x80,0xf7,0xf6,0x12,0x28 #λ│ Ç≈÷↕( + .byte 0xc0,0x00,0xfb,0x12,0x0f,0x33,0x00,0x0c #└ √↕☼3 ♀ + .byte 0x23,0x44,0x08,0xf4,0x11,0x0b,0x18,0x00 #.D◘⌠◄♂↑  + .byte 0x11,0x30,0x60,0x0f,0x11,0x03,0x70,0x0f #◄0`☼◄♥p☼ + .byte 0x62,0xc0,0x3f,0x00,0x00,0x80,0xff,0x4c #b└⁇  ÇλL + .byte 0x00,0x02,0x10,0x14,0x20,0xc8,0x33,0x06 # ☻►¶ ╚3♠ + .byte 0x00,0x05,0x29,0x13,0x52,0x7e,0x66,0x00 # ♣)‼R~f  + .byte 0x08,0x10,0xf8,0x13,0x02,0x11,0x00,0x21 #◘►°‼☻◄ ! + .byte 0x9d,0xc1,0x43,0x12,0x2f,0x30,0x40,0x7c #¥┴C↕/0@| + .byte 0x00,0x0a,0x2f,0x20,0x21,0x96,0x0a,0xff # ◙/ !û◙λ + .byte 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff #λλλλλλλλ + .byte 0xdb,0x1f,0x40,0x60,0x1f,0x4b,0x2f,0x00 #█▼@`▼K/  + .byte 0x00,0x06,0x1a,0x08,0x1f,0x80,0x3f,0x00 # ♠→◘▼Ç⁇  + .byte 0x0c,0x1f,0x0e,0x7f,0x00,0x2c,0x1f,0x20 #♀▼♫⌂ ,▼ + .byte 0x1d,0x00,0x09,0x0e,0x3e,0x1c,0x2f,0xc0 #↔ ○♫>∟/└ + .byte 0x07,0xdd,0x01,0xbd,0x22,0x6e,0xf0,0x23 #•▌☺╜.n≡. + .byte 0x1e,0x0f,0x1c,0x00,0x01,0x1f,0x60,0x64 #▲☼∟ ☺▼`d + .byte 0x00,0x34,0x1f,0xf0,0x44,0x00,0x30,0x05 # 4▼≡D 0♣ + .byte 0xf4,0x20,0x0b,0x18,0x00,0x1a,0x02,0xb1 #⌠ ♂↑ →☻▒ + .byte 0x1e,0x03,0x72,0x1c,0x24,0x78,0x26,0xda #▲♥r∟$x&┌ + .byte 0x01,0x00,0xf0,0x0c,0x33,0x80,0xef,0x1f #☺ ≡♀3Ç∩▼ + .byte 0x2e,0x00,0x13,0x08,0x52,0x0d,0x48,0xc0 #. ‼◘R♪H└ + .byte 0x7f,0x00,0x1e,0x66,0x02,0x1f,0xd3,0xe4 #⌂ ▲f☻▼╙Σ + .byte 0x02,0x01,0x36,0x80,0xf8,0x07,0xc0,0x20 #☻☺6Ç°•└ + .byte 0x00,0x7e,0x00,0x3f,0xc0,0x1f,0x1f,0xc7 # ~ ⁇└▼▼╟ + .byte 0x02,0x06,0x19,0x5c,0x28,0x03,0x3f,0xf8 #☻♠↓\(♥⁇° + .byte 0x85,0x0d,0xb1,0x1c,0x0b,0x22,0xb0,0x01 #à♪▒∟♂.░☺ + .byte 0xa3,0x0d,0x04,0x30,0x00,0x19,0xa7,0xde #ú♪♦0 ↓º▐ + .byte 0x00,0x29,0x28,0xbf,0x78,0x20,0x2f,0xbc # )(┐x /╝ + .byte 0x0f,0x38,0x0e,0x0d,0x1f,0xff,0xf4,0x1c #☼8♫♪▼λ⌠∟ + .byte 0x20,0x20,0xf0,0x0c,0x74,0x00,0x11,0xfe # ≡♀t ◄■ + .byte 0xd2,0x02,0x52,0xf8,0x79,0x80,0x00,0x7e #╥☻R°yÇ ~ + .byte 0x4c,0x03,0x3f,0xfc,0x7f,0x03,0x4c,0x00 #L♥⁇ⁿ⌂♥L  + .byte 0x1f,0x17,0x7f,0xb1,0x00,0x5b,0xfc,0xff #▼↨⌂▒ [ⁿλ + .byte 0xff,0xfc,0x6d,0x20,0x00,0x26,0x7e,0xb4 #λⁿm  &~┤ + .byte 0x21,0x00,0x1f,0xa3,0x58,0x00,0x18,0x1f #! ▼úX ↑▼ + .byte 0x18,0x23,0x07,0xff,0xff,0x96,0x2f,0xff #↑.•λλû/λ + .byte 0x01,0xfb,0x0d,0xff,0xff,0xff,0xff,0xff #☺√♪λλλλλ + .byte 0xff,0xc9,0x04,0xf0,0x0a,0x1f,0x7f,0x1c #λ╔♦≡◙▼⌂∟ + .byte 0x19,0x70,0x04,0x08,0x00,0x1f,0x07,0x30 #↓p♦◘ ▼•0 + .byte 0x18,0xff,0xff,0xff,0xff,0xff,0xff,0xff #↑λλλλλλλ + .byte 0xff,0xff,0x96,0x2f,0x60,0x0f,0x5f,0x25 #λλû/`☼_% + .byte 0xff,0xff,0x87,0x5d,0x03,0xf8,0xff,0xe7 #λλç]♥°λτ + .byte 0x0f,0x30,0x34,0x05,0x66,0x37,0x0f,0xba #☼04♣f7☼║ + .byte 0x14,0xe2,0x01,0x01,0x00,0x12,0x7f,0x2d #¶Γ☺☺ ↕⌂- + .byte 0x3a,0x20,0x1f,0x20,0x01,0x26,0x3f,0xf8 #: ▼ ☺&⁇° + .byte 0xfe,0xff,0xc0,0x00,0x97,0x5f,0x7f,0xff #■λ└ ù_⌂λ + .byte 0xff,0xf9,0xdb,0x13,0x0e,0x0e,0x1f,0x7f #λ∙█‼♫♫▼⌂ + .byte 0xb9,0x1a,0x24,0x0f,0xda,0x01,0xa9,0x0a #╣→$☼┌☺⌐◙ + .byte 0xf4,0x00,0x3f,0xf0,0x07,0x00,0x01,0x00 #⌠ ⁇≡• ☺  .byte 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff #λλλλλλλλ .byte 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff #λλλλλλλλ .byte 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff #λλλλλλλλ @@ -228,9 +221,9 @@ kCombiningCharsLz4: .byte 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff #λλλλλλλλ .byte 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff #λλλλλλλλ .byte 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff #λλλλλλλλ - .byte 0xff,0xff,0xee,0x57,0x02,0x00,0x00,0x00 #λλεW☻    - .byte 0xff,0x01,0x00,0x0c,0x20,0x00,0x1f,0xff #λ☺ ♀  ▼λ - .byte 0x01,0x00,0x07,0x50,0xff,0xff,0xff,0x00 #☺ •Pλλλ  - .byte 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + .byte 0xff,0xff,0xff,0xff,0x46,0x57,0x02,0x00 #λλλλFW☻  + .byte 0x00,0x00,0xff,0x01,0x00,0x0c,0x20,0x00 #  λ☺ ♀   + .byte 0x1f,0xff,0x01,0x00,0x07,0x50,0xff,0xff #▼λ☺ •Pλλ + .byte 0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00 .endobj kCombiningCharsLz4,globl,hidden .previous diff --git a/libc/x/xstrmul.c b/libc/x/xstrmul.c index a41eb80a..651ccc55 100644 --- a/libc/x/xstrmul.c +++ b/libc/x/xstrmul.c @@ -17,7 +17,6 @@ │ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/conv/sizemultiply.h" #include "libc/log/check.h" #include "libc/str/str.h" #include "libc/x/x.h" diff --git a/libc/zipos/find.c b/libc/zipos/find.c index 5b581861..146282aa 100644 --- a/libc/zipos/find.c +++ b/libc/zipos/find.c @@ -25,15 +25,18 @@ #include "libc/zip.h" #include "libc/zipos/zipos.h" -ssize_t __zipos_find(const struct ZiposUri *name) { +ssize_t __zipos_find(struct Zipos *zipos, const struct ZiposUri *name) { size_t i, cf; - assert(ZIP_CDIR_MAGIC(__zip_end) == kZipCdirHdrMagic); - for (i = 0, cf = ZIP_CDIR_OFFSET(__zip_end); i < ZIP_CDIR_RECORDS(__zip_end); - ++i, cf += ZIP_CFILE_HDRSIZE(&_base[0] + cf)) { - assert(ZIP_CFILE_MAGIC(&_base[0] + cf) == kZipCfileHdrMagic); - if (name->len == ZIP_CFILE_NAMESIZE(&_base[0] + cf) && - memcmp(name->path, ZIP_CFILE_NAME(&_base[0] + cf), name->len) == 0) { - return cf; + if ((zipos = __zipos_get())) { + assert(ZIP_CDIR_MAGIC(zipos->cdir) == kZipCdirHdrMagic); + for (i = 0, cf = ZIP_CDIR_OFFSET(zipos->cdir); + i < ZIP_CDIR_RECORDS(zipos->cdir); + ++i, cf += ZIP_CFILE_HDRSIZE(zipos->map + cf)) { + assert(ZIP_CFILE_MAGIC(zipos->map + cf) == kZipCfileHdrMagic); + if (name->len == ZIP_CFILE_NAMESIZE(zipos->map + cf) && + memcmp(name->path, ZIP_CFILE_NAME(zipos->map + cf), name->len) == 0) { + return cf; + } } } return -1; diff --git a/libc/zipos/fstat.c b/libc/zipos/fstat.c index d33e435a..98312d1d 100644 --- a/libc/zipos/fstat.c +++ b/libc/zipos/fstat.c @@ -26,5 +26,5 @@ * @asyncsignalsafe */ int __zipos_fstat(const struct ZiposHandle *h, struct stat *st) { - return __zipos_stat_impl(h->cfile, st); + return __zipos_stat_impl(__zipos_get(), h->cfile, st); } diff --git a/libc/zipos/get.c b/libc/zipos/get.c new file mode 100644 index 00000000..6df01710 --- /dev/null +++ b/libc/zipos/get.c @@ -0,0 +1,65 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/calls.h" +#include "libc/calls/struct/stat.h" +#include "libc/limits.h" +#include "libc/runtime/runtime.h" +#include "libc/sysv/consts/auxv.h" +#include "libc/sysv/consts/map.h" +#include "libc/sysv/consts/o.h" +#include "libc/sysv/consts/prot.h" +#include "libc/zip.h" +#include "libc/zipos/zipos.h" + +/** + * Returns pointer to zip central directory of current executable. + */ +struct Zipos *__zipos_get(void) { + static struct Zipos zipos; + static bool once; + const char *exe; + size_t mapsize; + uint8_t *cdir; + void *map; + int fd; + if (!once) { + if (ZIP_CDIR_MAGIC(__zip_end) == kZipCdirHdrMagic) { + zipos.map = _base; + zipos.cdir = __zip_end; + } else { + exe = (const char *)getauxval(AT_EXECFN); + if ((fd = open(exe, O_RDONLY)) != -1) { + if ((mapsize = getfiledescriptorsize(fd)) != SIZE_MAX && + (map = mmap(NULL, mapsize, PROT_READ, MAP_SHARED, fd, 0)) != + MAP_FAILED) { + if ((cdir = zipfindcentraldir(map, mapsize))) { + zipos.map = map; + zipos.cdir = cdir; + } else { + munmap(map, mapsize); + } + } + close(fd); + } + } + once = true; + } + return zipos.cdir ? &zipos : NULL; +} diff --git a/libc/zipos/open.c b/libc/zipos/open.c index 8e5d89bd..0cb29c80 100644 --- a/libc/zipos/open.c +++ b/libc/zipos/open.c @@ -23,10 +23,10 @@ #include "libc/calls/internal.h" #include "libc/calls/struct/sigset.h" #include "libc/calls/struct/stat.h" -#include "libc/conv/sizemultiply.h" #include "libc/dce.h" #include "libc/macros.h" #include "libc/mem/mem.h" +#include "libc/nexgen32e/crc32.h" #include "libc/runtime/rbx.h" #include "libc/runtime/runtime.h" #include "libc/str/str.h" @@ -83,37 +83,39 @@ static int __zipos_inflate_tiny(struct ZiposHandle *h, uint8_t *data, return undeflate(h->mem, h->size, data, size, &ds) != -1 ? 0 : eio(); } -static int __zipos_load(size_t cf, unsigned flags, int mode) { +static int __zipos_load(struct Zipos *zipos, size_t cf, unsigned flags, + int mode) { int fd; size_t lf; struct ZiposHandle *h; - lf = ZIP_CFILE_OFFSET(&_base[0] + cf); - assert(ZIP_LFILE_MAGIC(&_base[0] + lf) == kZipLfileHdrMagic); - assert(ZIP_LFILE_COMPRESSIONMETHOD(&_base[0] + lf) == kZipCompressionNone || - ZIP_LFILE_COMPRESSIONMETHOD(&_base[0] + lf) == kZipCompressionDeflate); + lf = ZIP_CFILE_OFFSET(zipos->map + cf); + assert(ZIP_LFILE_MAGIC(zipos->map + lf) == kZipLfileHdrMagic); + assert(ZIP_LFILE_COMPRESSIONMETHOD(zipos->map + lf) == kZipCompressionNone || + ZIP_LFILE_COMPRESSIONMETHOD(zipos->map + lf) == + kZipCompressionDeflate); if ((fd = createfd()) == -1) return -1; if (!(h = calloc(1, sizeof(*h)))) return -1; h->cfile = cf; - if ((h->size = ZIP_LFILE_UNCOMPRESSEDSIZE(&_base[0] + lf))) { - if (ZIP_LFILE_COMPRESSIONMETHOD(&_base[0] + lf)) { - assert(ZIP_LFILE_COMPRESSEDSIZE(&_base[0] + lf)); + if ((h->size = ZIP_LFILE_UNCOMPRESSEDSIZE(zipos->map + lf))) { + if (ZIP_LFILE_COMPRESSIONMETHOD(zipos->map + lf)) { + assert(ZIP_LFILE_COMPRESSEDSIZE(zipos->map + lf)); h->mapsize = ROUNDUP(h->size + FRAMESIZE, FRAMESIZE); if ((h->map = mapanon(h->mapsize)) != MAP_FAILED) { h->mem = h->map + FRAMESIZE / 2; if ((IsTiny() ? __zipos_inflate_tiny : __zipos_inflate_fast)( - h, ZIP_LFILE_CONTENT(&_base[0] + lf), - ZIP_LFILE_COMPRESSEDSIZE(&_base[0] + lf)) == -1) { + h, ZIP_LFILE_CONTENT(zipos->map + lf), + ZIP_LFILE_COMPRESSEDSIZE(zipos->map + lf)) == -1) { fd = -1; } } else { fd = -1; } } else { - h->mem = ZIP_LFILE_CONTENT(&_base[0] + lf); + h->mem = ZIP_LFILE_CONTENT(zipos->map + lf); } } if (!IsTiny() && fd != -1 && - crc32_z(0, h->mem, h->size) != ZIP_LFILE_CRC32(&_base[0] + lf)) { + crc32_z(0, h->mem, h->size) != ZIP_LFILE_CRC32(zipos->map + lf)) { fd = eio(); } if (fd != -1) { @@ -136,12 +138,17 @@ int __zipos_open(const struct ZiposUri *name, unsigned flags, int mode) { int fd; ssize_t cf; sigset_t oldmask; + struct Zipos *zipos; assert((flags & O_ACCMODE) == O_RDONLY); sigprocmask(SIG_BLOCK, &kSigsetFull, &oldmask); - if ((cf = __zipos_find(name)) != -1) { - fd = __zipos_load(cf, flags, mode); + if ((zipos = __zipos_get())) { + if ((cf = __zipos_find(zipos, name)) != -1) { + fd = __zipos_load(zipos, cf, flags, mode); + } else { + fd = enoent(); + } } else { - fd = enoent(); + fd = enoexec(); } sigprocmask(SIG_SETMASK, &oldmask, NULL); return fd; diff --git a/libc/zipos/stat-impl.c b/libc/zipos/stat-impl.c index 9f082085..db621cb8 100644 --- a/libc/zipos/stat-impl.c +++ b/libc/zipos/stat-impl.c @@ -22,17 +22,23 @@ #include "libc/calls/struct/stat.h" #include "libc/runtime/rbx.h" #include "libc/str/str.h" +#include "libc/sysv/errfuns.h" #include "libc/zip.h" #include "libc/zipos/zipos.h" -int __zipos_stat_impl(size_t cf, struct stat *st) { - memset(st, 0, sizeof(*st)); - if (ZIP_CFILE_FILEATTRCOMPAT(&_base[0] + cf) == kZipOsUnix) { - st->st_mode = ZIP_CFILE_EXTERNALATTRIBUTES(&_base[0] + cf) >> 16; +int __zipos_stat_impl(struct Zipos *zipos, size_t cf, struct stat *st) { + if (zipos && st) { + memset(st, 0, sizeof(*st)); + if (ZIP_CFILE_FILEATTRCOMPAT(zipos->map + cf) == kZipOsUnix) { + st->st_mode = ZIP_CFILE_EXTERNALATTRIBUTES(zipos->map + cf) >> 16; + } else { + st->st_mode = 0100644; + } + st->st_size = ZIP_CFILE_UNCOMPRESSEDSIZE(zipos->map + cf); + st->st_blocks = + roundup(ZIP_CFILE_COMPRESSEDSIZE(zipos->map + cf), 512) / 512; + return 0; } else { - st->st_mode = 0100644; + return einval(); } - st->st_size = ZIP_CFILE_UNCOMPRESSEDSIZE(&_base[0] + cf); - st->st_blocks = roundup(ZIP_CFILE_COMPRESSEDSIZE(&_base[0] + cf), 512) / 512; - return 0; } diff --git a/libc/zipos/stat.c b/libc/zipos/stat.c index 8586dcba..f27039d2 100644 --- a/libc/zipos/stat.c +++ b/libc/zipos/stat.c @@ -28,9 +28,14 @@ */ int __zipos_stat(const struct ZiposUri *name, struct stat *st) { ssize_t cf; - if ((cf = __zipos_find(name)) != -1) { - return __zipos_stat_impl(cf, st); + struct Zipos *zipos; + if ((zipos = __zipos_get())) { + if ((cf = __zipos_find(zipos, name)) != -1) { + return __zipos_stat_impl(zipos, cf, st); + } else { + return enoent(); + } } else { - return enoent(); + return enoexec(); } } diff --git a/libc/zipos/zipcentraldir.S b/libc/zipos/zipcentraldir.S deleted file mode 100644 index 335a603b..00000000 --- a/libc/zipos/zipcentraldir.S +++ /dev/null @@ -1,58 +0,0 @@ -/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│ -│vi: set et ft=asm ts=8 tw=8 fenc=utf-8 :vi│ -╞══════════════════════════════════════════════════════════════════════════════╡ -│ Copyright 2020 Justine Alexandra Roberts Tunney │ -│ │ -│ This program is free software; you can redistribute it and/or modify │ -│ it under the terms of the GNU General Public License as published by │ -│ the Free Software Foundation; version 2 of the License. │ -│ │ -│ This program is distributed in the hope that it will be useful, but │ -│ WITHOUT ANY WARRANTY; without even the implied warranty of │ -│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ -│ General Public License for more details. │ -│ │ -│ You should have received a copy of the GNU General Public License │ -│ along with this program; if not, write to the Free Software │ -│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ -│ 02110-1301 USA │ -╚─────────────────────────────────────────────────────────────────────────────*/ -#include "ape/relocations.h" -#include "libc/zip.h" -#include "libc/macros.h" - -/ ZIP Central Directory. - .section .piro.data.sort.zip.3,"a",@progbits - .hidden __zip_start - .globl __zip_start - .type __zip_start,@object - .align kZipCdirAlign -__zip_start: - .previous/* - ... - decentralized content - ... - */.section .piro.data.sort.zip.5,"a",@progbits - .align kZipCdirAlign -__zip_end: - .long kZipCdirHdrMagic # magic - .short 0 # disk - .short 0 # starting disk - .short v_zip_records # records on disk - .short v_zip_records # records - .long v_zip_cdirsize # size of central directory - .long RVA(__zip_start) # central directory offset - .short v_zip_commentsize # comment size - .endobj __zip_end,globl,hidden - .weak v_zip_records - .weak v_zip_cdirsize - .weak v_zip_commentsize - .previous - - .yoink __zipos_close - .yoink __zipos_fstat - .yoink __zipos_open - .yoink __zipos_parseuri - .yoink __zipos_read - .yoink __zipos_stat - .source __FILE__ diff --git a/libc/zipos/zipos.S b/libc/zipos/zipos.S new file mode 100644 index 00000000..567c2316 --- /dev/null +++ b/libc/zipos/zipos.S @@ -0,0 +1,29 @@ +/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│ +│vi: set et ft=asm ts=8 tw=8 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/macros.h" + + .yoink __zip_start + .yoink __zip_end + .yoink __zipos_close + .yoink __zipos_fstat + .yoink __zipos_open + .yoink __zipos_parseuri + .yoink __zipos_read + .yoink __zipos_stat diff --git a/libc/zipos/zipos.h b/libc/zipos/zipos.h index 6ca3661f..cb387267 100644 --- a/libc/zipos/zipos.h +++ b/libc/zipos/zipos.h @@ -6,6 +6,11 @@ COSMOPOLITAN_C_START_ struct stat; struct iovec; +struct Zipos { + uint8_t *map; + uint8_t *cdir; +}; + struct ZiposUri { const char *path; size_t len; @@ -22,13 +27,14 @@ struct ZiposHandle { extern const char kZiposSchemePrefix[4]; +struct Zipos *__zipos_get(void); ssize_t __zipos_parseuri(const char *, struct ZiposUri *); -ssize_t __zipos_find(const struct ZiposUri *); +ssize_t __zipos_find(struct Zipos *, const struct ZiposUri *); int __zipos_close(struct ZiposHandle *); int __zipos_open(const struct ZiposUri *, unsigned, int); int __zipos_stat(const struct ZiposUri *, struct stat *); int __zipos_fstat(const struct ZiposHandle *, struct stat *); -int __zipos_stat_impl(size_t, struct stat *); +int __zipos_stat_impl(struct Zipos *, size_t, struct stat *); ssize_t __zipos_read(struct ZiposHandle *, const struct iovec *, size_t, ssize_t); ssize_t __zipos_write(struct ZiposHandle *, const struct iovec *, size_t, diff --git a/libc/zipos/zipos.mk b/libc/zipos/zipos.mk index 49632418..63e47390 100644 --- a/libc/zipos/zipos.mk +++ b/libc/zipos/zipos.mk @@ -4,13 +4,17 @@ PKGS += LIBC_ZIPOS LIBC_ZIPOS_ARTIFACTS += LIBC_ZIPOS_A -LIBC_ZIPOS = $(LIBC_ZIPOS_A_DEPS) $(LIBC_ZIPOS_A) LIBC_ZIPOS_A = o/$(MODE)/libc/zipos/zipos.a LIBC_ZIPOS_A_FILES := $(wildcard libc/zipos/*) LIBC_ZIPOS_A_HDRS = $(filter %.h,$(LIBC_ZIPOS_A_FILES)) LIBC_ZIPOS_A_SRCS_S = $(filter %.S,$(LIBC_ZIPOS_A_FILES)) LIBC_ZIPOS_A_SRCS_C = $(filter %.c,$(LIBC_ZIPOS_A_FILES)) +LIBC_ZIPOS = \ + $(LIBC_ZIPOS_A_DEPS) \ + $(LIBC_ZIPOS_A) \ + o/$(MODE)/libc/zipos/zipos.o + LIBC_ZIPOS_A_SRCS = \ $(LIBC_ZIPOS_A_SRCS_S) \ $(LIBC_ZIPOS_A_SRCS_C) diff --git a/test/ape/lib/test.mk b/test/ape/lib/test.mk index bc56a3cd..b633a592 100644 --- a/test/ape/lib/test.mk +++ b/test/ape/lib/test.mk @@ -5,12 +5,14 @@ PKGS += TEST_APE_LIB TEST_APE_LIB_SRCS := $(wildcard test/ape/lib/*.c) TEST_APE_LIB_SRCS_TEST = $(filter %_test.c,$(TEST_APE_LIB_SRCS)) -TEST_APE_LIB_COMS = $(TEST_APE_LIB_OBJS:%.o=%.com) TEST_APE_LIB_OBJS = \ $(TEST_APE_LIB_SRCS:%=o/$(MODE)/%.zip.o) \ $(TEST_APE_LIB_SRCS:%.c=o/$(MODE)/%.o) +TEST_APE_LIB_COMS = \ + $(TEST_APE_LIB_SRCS:%.c=o/$(MODE)/%.com) + TEST_APE_LIB_BINS = \ $(TEST_APE_LIB_COMS) \ $(TEST_APE_LIB_COMS:%=%.dbg) diff --git a/test/dsp/core/test.mk b/test/dsp/core/test.mk index cd4c9576..35a469cf 100644 --- a/test/dsp/core/test.mk +++ b/test/dsp/core/test.mk @@ -5,13 +5,15 @@ PKGS += TEST_DSP_CORE TEST_DSP_CORE_SRCS := $(wildcard test/dsp/core/*.c) TEST_DSP_CORE_SRCS_TEST = $(filter %_test.c,$(TEST_DSP_CORE_SRCS)) -TEST_DSP_CORE_COMS = $(TEST_DSP_CORE_OBJS:%.o=%.com) TEST_DSP_CORE_BINS = $(TEST_DSP_CORE_COMS) $(TEST_DSP_CORE_COMS:%=%.dbg) TEST_DSP_CORE_OBJS = \ $(TEST_DSP_CORE_SRCS:%=o/$(MODE)/%.zip.o) \ $(TEST_DSP_CORE_SRCS:%.c=o/$(MODE)/%.o) +TEST_DSP_CORE_COMS = \ + $(TEST_DSP_CORE_SRCS:%.c=o/$(MODE)/%.com) + TEST_DSP_CORE_TESTS = \ $(TEST_DSP_CORE_SRCS_TEST:%.c=o/$(MODE)/%.com.ok) diff --git a/test/dsp/scale/magikarp_test.c b/test/dsp/scale/magikarp_test.c index 05b2cd43..39ca33db 100644 --- a/test/dsp/scale/magikarp_test.c +++ b/test/dsp/scale/magikarp_test.c @@ -67,7 +67,7 @@ TEST(magikarp, testConvolve2) { cDecimate2xUint8x8(256, tgc(tunbing(u" ☺☻♥♦♣♠•◘○◙♂♀♪♫☼►◄↕‼¶§▬↨↑↓→←∟↔▲▼" u" !“#$%&‘()*+,-./0123456789:;<=>⁇" - u"@ABCDEFGHIJKLMNOPQRSTUVWXYZ[╲]^_" + u"@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_" u"`abcdefghijklmnopqrstuvwxyz{|}~⌂" u"ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜ¢£¥€ƒ" u"áíóúñѪº¿⌐¬½¼¡«»░▒▓│┤╡╢╖╕╣║╗╝╜╛┐" diff --git a/test/dsp/scale/test.mk b/test/dsp/scale/test.mk index be459fd2..966b4170 100644 --- a/test/dsp/scale/test.mk +++ b/test/dsp/scale/test.mk @@ -5,13 +5,15 @@ PKGS += TEST_DSP_SCALE TEST_DSP_SCALE_SRCS := $(wildcard test/dsp/scale/*.c) TEST_DSP_SCALE_SRCS_TEST = $(filter %_test.c,$(TEST_DSP_SCALE_SRCS)) -TEST_DSP_SCALE_COMS = $(TEST_DSP_SCALE_OBJS:%.o=%.com) TEST_DSP_SCALE_BINS = $(TEST_DSP_SCALE_COMS) $(TEST_DSP_SCALE_COMS:%=%.dbg) TEST_DSP_SCALE_OBJS = \ $(TEST_DSP_SCALE_SRCS:%=o/$(MODE)/%.zip.o) \ $(TEST_DSP_SCALE_SRCS:%.c=o/$(MODE)/%.o) +TEST_DSP_SCALE_COMS = \ + $(TEST_DSP_SCALE_SRCS:%.c=o/$(MODE)/%.com) + TEST_DSP_SCALE_TESTS = \ $(TEST_DSP_SCALE_SRCS_TEST:%.c=o/$(MODE)/%.com.ok) diff --git a/test/dsp/tty/rgb2ansi_test.c b/test/dsp/tty/rgb2ansi_test.c index c98b6328..714805e7 100644 --- a/test/dsp/tty/rgb2ansi_test.c +++ b/test/dsp/tty/rgb2ansi_test.c @@ -23,7 +23,7 @@ struct TtyRgb res; TEST(rgb2ansi, testDesaturatedPurple_isQuantizedBetterThanEuclideanDistance) { - ttyquantinit(kTtyQuantXterm256, kTtyQuantRgb, kTtyBlocksUnicode); + ttyquantsetup(kTtyQuantXterm256, kTtyQuantRgb, kTtyBlocksUnicode); /* * the challenge to the xterm256 palette is that it was likely diff --git a/test/dsp/tty/test.mk b/test/dsp/tty/test.mk index 4481046f..1717b9be 100644 --- a/test/dsp/tty/test.mk +++ b/test/dsp/tty/test.mk @@ -5,13 +5,15 @@ PKGS += TEST_DSP_TTY TEST_DSP_TTY_SRCS := $(wildcard test/dsp/tty/*.c) TEST_DSP_TTY_SRCS_TEST = $(filter %_test.c,$(TEST_DSP_TTY_SRCS)) -TEST_DSP_TTY_COMS = $(TEST_DSP_TTY_OBJS:%.o=%.com) TEST_DSP_TTY_BINS = $(TEST_DSP_TTY_COMS) $(TEST_DSP_TTY_COMS:%=%.dbg) TEST_DSP_TTY_OBJS = \ $(TEST_DSP_TTY_SRCS:%=o/$(MODE)/%.zip.o) \ $(TEST_DSP_TTY_SRCS:%.c=o/$(MODE)/%.o) +TEST_DSP_TTY_COMS = \ + $(TEST_DSP_TTY_SRCS:%.c=o/$(MODE)/%.com) + TEST_DSP_TTY_TESTS = \ $(TEST_DSP_TTY_SRCS_TEST:%.c=o/$(MODE)/%.com.ok) diff --git a/test/dsp/tty/ttyraster_test.c b/test/dsp/tty/ttyraster_test.c index 371a0878..4e19df17 100644 --- a/test/dsp/tty/ttyraster_test.c +++ b/test/dsp/tty/ttyraster_test.c @@ -32,7 +32,7 @@ static const struct TtyRgb kRed = {0xff, 0, 0, 196}; char vtbuf[128]; void ttyraster_true_setup(void) { - ttyquantinit(kTtyQuantTrue, kTtyQuantRgb, kTtyBlocksUnicode); + ttyquantsetup(kTtyQuantTrue, kTtyQuantRgb, kTtyBlocksUnicode); } void ttyraster2x2_true(void) { @@ -51,7 +51,7 @@ TEST(ttyraster, testCorner) { } TEST(ttyraster, testFullBlock_favorsSpace) { - ttyquantinit(kTtyQuantTrue, kTtyQuantRgb, kTtyBlocksUnicode); + ttyquantsetup(kTtyQuantTrue, kTtyQuantRgb, kTtyBlocksUnicode); ttyraster(vtbuf, (struct TtyRgb *)(unsigned[2][2]){ {DARKRED, DARKRED}, @@ -62,7 +62,7 @@ TEST(ttyraster, testFullBlock_favorsSpace) { } TEST(ttyraster, testFullBlock_favorsUnicodeWhenCurrenttFgMatchesButNotBg) { - ttyquantinit(kTtyQuantTrue, kTtyQuantRgb, kTtyBlocksUnicode); + ttyquantsetup(kTtyQuantTrue, kTtyQuantRgb, kTtyBlocksUnicode); ttyraster(vtbuf, (struct TtyRgb *)(unsigned[2][4]){ {DARKRED, GRAY1, GRAY1, GRAY1}, @@ -73,7 +73,7 @@ TEST(ttyraster, testFullBlock_favorsUnicodeWhenCurrenttFgMatchesButNotBg) { } TEST(ttyraster, testFullBlock_forcesSwitchBackToSpaceForRuns) { - ttyquantinit(kTtyQuantTrue, kTtyQuantRgb, kTtyBlocksUnicode); + ttyquantsetup(kTtyQuantTrue, kTtyQuantRgb, kTtyBlocksUnicode); ttyraster(vtbuf, (struct TtyRgb *)(unsigned[2][8]){ {DARKRED, GRAY1, GRAY1, GRAY1, GRAY1, GRAY1, GRAY1, GRAY1}, @@ -86,7 +86,7 @@ TEST(ttyraster, testFullBlock_forcesSwitchBackToSpaceForRuns) { //////////////////////////////////////////////////////////////////////////////// TEST(ttyraster_cp437, testSide) { - ttyquantinit(kTtyQuantTrue, kTtyQuantRgb, kTtyBlocksCp437); + ttyquantsetup(kTtyQuantTrue, kTtyQuantRgb, kTtyBlocksCp437); ttyraster(vtbuf, (const struct TtyRgb *)(unsigned[2][2]){ {DARKRED, GRAY1}, @@ -99,7 +99,7 @@ TEST(ttyraster_cp437, testSide) { //////////////////////////////////////////////////////////////////////////////// void ttyraster_xterm256_setup(void) { - ttyquantinit(kTtyQuantXterm256, kTtyQuantRgb, kTtyBlocksUnicode); + ttyquantsetup(kTtyQuantXterm256, kTtyQuantRgb, kTtyBlocksUnicode); } void ttyraster2x2_xterm256(void) { diff --git a/test/libc/alg/tarjan_test.c b/test/libc/alg/tarjan_test.c index 7e252ba6..cb967dbe 100644 --- a/test/libc/alg/tarjan_test.c +++ b/test/libc/alg/tarjan_test.c @@ -24,23 +24,22 @@ STATIC_YOINK("realloc"); TEST(tarjan, empty_doesNothing) { - uint32_t sorted_vertices[1] = {-1u}; - uint32_t edges[][2] = {{0, 0}}; - uint32_t vertex_count = 0; - uint32_t edge_count = 0; + int sorted_vertices[1] = {-1}; + int edges[][2] = {{0, 0}}; + int vertex_count = 0; + int edge_count = 0; tarjan(vertex_count, (void *)edges, edge_count, sorted_vertices, NULL, NULL); - ASSERT_EQ(-1u, sorted_vertices[0]); + ASSERT_EQ(-1, sorted_vertices[0]); } TEST(tarjan, topologicalSort_noCycles) { enum VertexIndex { A = 0, B = 1, C = 2, D = 3 }; const char *const vertices[] = {[A] = "A", [B] = "B", [C] = "C", [D] = "D"}; - uint32_t edges[][2] = { - {A /* depends on → */, B /* which must come before A */}, - {A /* depends on → */, C /* which must come before A */}, - {A /* depends on → */, D /* which must come before A */}, - {B /* depends on → */, C /* which must come before B */}, - {B /* depends on → */, D /* which must come before B */}}; + int edges[][2] = {{A /* depends on → */, B /* which must come before A */}, + {A /* depends on → */, C /* which must come before A */}, + {A /* depends on → */, D /* which must come before A */}, + {B /* depends on → */, C /* which must come before B */}, + {B /* depends on → */, D /* which must come before B */}}; /* $ tsort <nameservers.i); - EXPECT_EQ(AF_INET, rv->nameservers.p[0].sin_family); - EXPECT_EQ(DNS_PORT, ntohs(rv->nameservers.p[0].sin_port)); - EXPECT_STREQ("127.0.0.1", FormatIp(&rv->nameservers.p[0])); + ASSERT_EQ(0, parseresolvconf(rv, f)); + ASSERT_EQ(0, rv->nameservers.i); freeresolvconf(&rv); fclose(f); } diff --git a/test/libc/dns/test.mk b/test/libc/dns/test.mk index cf7936a1..399b83f5 100644 --- a/test/libc/dns/test.mk +++ b/test/libc/dns/test.mk @@ -5,12 +5,14 @@ PKGS += TEST_LIBC_DNS TEST_LIBC_DNS_SRCS := $(wildcard test/libc/dns/*.c) TEST_LIBC_DNS_SRCS_TEST = $(filter %_test.c,$(TEST_LIBC_DNS_SRCS)) -TEST_LIBC_DNS_COMS = $(TEST_LIBC_DNS_OBJS:%.o=%.com) TEST_LIBC_DNS_OBJS = \ $(TEST_LIBC_DNS_SRCS:%=o/$(MODE)/%.zip.o) \ $(TEST_LIBC_DNS_SRCS:%.c=o/$(MODE)/%.o) +TEST_LIBC_DNS_COMS = \ + $(TEST_LIBC_DNS_SRCS:%.c=o/$(MODE)/%.com) + TEST_LIBC_DNS_BINS = \ $(TEST_LIBC_DNS_COMS) \ $(TEST_LIBC_DNS_COMS:%=%.dbg) diff --git a/test/libc/fmt/palandprintf_test.c b/test/libc/fmt/palandprintf_test.c index b051dc27..2a39050e 100644 --- a/test/libc/fmt/palandprintf_test.c +++ b/test/libc/fmt/palandprintf_test.c @@ -27,12 +27,15 @@ #include "libc/bits/progn.h" #include "libc/bits/pushpop.h" #include "libc/bits/safemacros.h" +#include "libc/conv/itoa.h" #include "libc/errno.h" #include "libc/fmt/fmt.h" +#include "libc/limits.h" #include "libc/math.h" #include "libc/mem/mem.h" #include "libc/runtime/gc.h" #include "libc/str/str.h" +#include "libc/testlib/ezbench.h" #include "libc/testlib/testlib.h" #include "libc/x/x.h" @@ -479,10 +482,8 @@ TEST(sprintf, test_types) { TEST(sprintf, testOverflow_truncationNotSaturation) { errno = 0; EXPECT_STREQ("13398", Format("%hu", 0x123456UL)); - EXPECT_EQ(ERANGE, errno); errno = 0; EXPECT_STREQ("Test16 65535", Format("%s%hhi %hu", "Test", 10000, 0xFFFFFFFF)); - EXPECT_EQ(ERANGE, errno); } TEST(sprintf, test_pointer) { @@ -598,6 +599,10 @@ TEST(xasprintf, test) { free(pp); } +TEST(xasprintf, nullPointer) { + ASSERT_STREQ("000000000000", gc(xasprintf("%p", NULL))); +} + TEST(xasprintf, pointer_doesntShowNonCanonicalZeroes) { ASSERT_STREQ("100000000010", gc(xasprintf("%p", 0x0000100000000010))); ASSERT_STREQ("0x100000000010", gc(xasprintf("%#p", 0x0000100000000010))); @@ -610,6 +615,19 @@ TEST(xasprintf, nonCanonicalPointer_discardsHighBits_ratherThanSaturate) { ASSERT_STREQ("0x7fffffffffff", gc(xasprintf("%#p", 0x7fffffffffff))); } +TEST(xasprintf, hugeNtoa) { + ASSERT_STREQ( + "0b1111111111111111111111111111111111111111111111111111111111111111111111" + "1111111111111111111111111111111111111111111111111111111111", + gc(xasprintf("%#jb", UINT128_MAX))); +} + +TEST(xasprintf, twosBane) { + ASSERT_STREQ("-2147483648", gc(xasprintf("%d", 0x80000000))); + ASSERT_STREQ("-9223372036854775808", + gc(xasprintf("%ld", 0x8000000000000000))); +} + TEST(snprintf, testFixedWidthString_wontOverrunInput) { const int N = 2; char *buf = tmalloc(N + 1); @@ -651,3 +669,17 @@ TEST(snprintf, testFixedWidthStringIsNull_wontLeakMemory) { EXPECT_BINEQ(u"NULL             ", buf); tfree(buf); } + +TEST(snprintf, twosBaneWithTypePromotion) { + int16_t x = 0x8000; + EXPECT_STREQ("-32768", Format("%hd", x)); +} + +BENCH(palandprintf, bench) { + EZBENCH2("snprintf %x", donothing, Format("%x", VEIL("r", INT_MIN))); + EZBENCH2("snprintf %d", donothing, Format("%d", VEIL("r", INT_MIN))); + EZBENCH2("snprintf %s", donothing, Format("%s", VEIL("r", "hi (╯°□°)╯"))); + EZBENCH2("snprintf %hs", donothing, Format("%hs", VEIL("r", u"hi (╯°□°)╯"))); + EZBENCH2("snprintf %ls", donothing, Format("%ls", VEIL("r", L"hi (╯°□°)╯"))); + EZBENCH2("int64toarray", donothing, int64toarray_radix10(-3, buffer)); +} diff --git a/test/libc/fmt/sprintf_s.inc b/test/libc/fmt/sprintf_s.inc index 31e17a19..f815d0e3 100644 --- a/test/libc/fmt/sprintf_s.inc +++ b/test/libc/fmt/sprintf_s.inc @@ -40,10 +40,10 @@ TEST(SUITE(sprintf), testCharacterCounting) { TEST(SUITE(snprintf), testTableFlip) { EXPECT_STREQ("Table flip ", Format("%-20ls", L"Table flip")); - EXPECT_STREQ("(╯°□°)╯︵ ┻━┻ ", Format("%-20ls", L"(╯°□°)╯︵ ┻━┻")); - EXPECT_STREQ("(╯°□°)╯︵ ┻━┻ ", Format("%-20hs", u"(╯°□°)╯︵ ┻━┻")); + EXPECT_STREQ("(╯°□°)╯︵L┻━┻ ", Format("%-20ls", L"(╯°□°)╯︵L┻━┻")); + EXPECT_STREQ("(╯°□°)╯︵u┻━┻ ", Format("%-20hs", u"(╯°□°)╯︵u┻━┻")); EXPECT_STREQ("ちゃぶ台返し ", Format("%-20ls", L"ちゃぶ台返し")); - EXPECT_STREQ(" (╯°□°)╯︵ ┻━┻", Format("%20ls", L"(╯°□°)╯︵ ┻━┻")); + EXPECT_STREQ(" (╯°□°)╯︵L┻━┻", Format("%20ls", L"(╯°□°)╯︵L┻━┻")); EXPECT_STREQ(" ちゃぶ台返し", Format("%20ls", L"ちゃぶ台返し")); } diff --git a/test/libc/fmt/strerror_test.c b/test/libc/fmt/strerror_test.c index f79e05ac..6b87c7f3 100644 --- a/test/libc/fmt/strerror_test.c +++ b/test/libc/fmt/strerror_test.c @@ -30,6 +30,11 @@ * libc/sysv/kErrnoNames.S needs updating. */ +TEST(strerror, e2big) { + if (IsTiny()) return; + EXPECT_STARTSWITH("E2BIG", strerror(E2BIG)); +} + TEST(strerror, enosys) { if (IsTiny()) return; EXPECT_STARTSWITH("ENOSYS", strerror(ENOSYS)); diff --git a/test/libc/fmt/test.mk b/test/libc/fmt/test.mk index 4a422081..98c6bc94 100644 --- a/test/libc/fmt/test.mk +++ b/test/libc/fmt/test.mk @@ -5,7 +5,6 @@ PKGS += TEST_LIBC_FMT TEST_LIBC_FMT_SRCS := $(wildcard test/libc/fmt/*.c) TEST_LIBC_FMT_SRCS_TEST = $(filter %_test.c,$(TEST_LIBC_FMT_SRCS)) -TEST_LIBC_FMT_COMS = $(TEST_LIBC_FMT_OBJS:%.o=%.com) TEST_LIBC_FMT_BINS = $(TEST_LIBC_FMT_COMS) $(TEST_LIBC_FMT_COMS:%=%.dbg) TEST_LIBC_FMT_TESTS = $(TEST_LIBC_FMT_SRCS_TEST:%.c=o/$(MODE)/%.com.ok) @@ -13,11 +12,15 @@ TEST_LIBC_FMT_OBJS = \ $(TEST_LIBC_FMT_SRCS:%=o/$(MODE)/%.zip.o) \ $(TEST_LIBC_FMT_SRCS:%.c=o/$(MODE)/%.o) +TEST_LIBC_FMT_COMS = \ + $(TEST_LIBC_FMT_SRCS:%.c=o/$(MODE)/%.com) + TEST_LIBC_FMT_CHECKS = \ $(TEST_LIBC_FMT_SRCS_TEST:%.c=o/$(MODE)/%.com.runs) TEST_LIBC_FMT_DIRECTDEPS = \ LIBC_CALLS_HEFTY \ + LIBC_CONV \ LIBC_FMT \ LIBC_MEM \ LIBC_NEXGEN32E \ @@ -45,9 +48,7 @@ o/$(MODE)/test/libc/fmt/%.com.dbg: \ $(APE) @$(APELINK) -$(TEST_LIBC_FMT_OBJS): \ - $(BUILD_FILES) \ - test/libc/fmt/test.mk +$(TEST_LIBC_FMT_OBJS): test/libc/fmt/test.mk .PHONY: o/$(MODE)/test/libc/fmt o/$(MODE)/test/libc/fmt: \ diff --git a/test/libc/intrin/intrin_test.c b/test/libc/intrin/intrin_test.c new file mode 100644 index 00000000..b4b90b36 --- /dev/null +++ b/test/libc/intrin/intrin_test.c @@ -0,0 +1,2108 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/bits/progn.h" +#include "libc/intrin/pabsb.h" +#include "libc/intrin/pabsd.h" +#include "libc/intrin/pabsw.h" +#include "libc/intrin/packssdw.h" +#include "libc/intrin/packsswb.h" +#include "libc/intrin/packusdw.h" +#include "libc/intrin/packuswb.h" +#include "libc/intrin/paddb.h" +#include "libc/intrin/paddd.h" +#include "libc/intrin/paddq.h" +#include "libc/intrin/paddsb.h" +#include "libc/intrin/paddsw.h" +#include "libc/intrin/paddusb.h" +#include "libc/intrin/paddusw.h" +#include "libc/intrin/paddw.h" +#include "libc/intrin/pand.h" +#include "libc/intrin/pandn.h" +#include "libc/intrin/pavgb.h" +#include "libc/intrin/pavgw.h" +#include "libc/intrin/pcmpeqb.h" +#include "libc/intrin/pcmpeqd.h" +#include "libc/intrin/pcmpeqw.h" +#include "libc/intrin/pcmpgtb.h" +#include "libc/intrin/pcmpgtd.h" +#include "libc/intrin/pcmpgtw.h" +#include "libc/intrin/phaddd.h" +#include "libc/intrin/phaddsw.h" +#include "libc/intrin/phaddw.h" +#include "libc/intrin/phsubd.h" +#include "libc/intrin/phsubsw.h" +#include "libc/intrin/phsubw.h" +#include "libc/intrin/pmaddubsw.h" +#include "libc/intrin/pmaddwd.h" +#include "libc/intrin/pmaxsw.h" +#include "libc/intrin/pmaxub.h" +#include "libc/intrin/pminsw.h" +#include "libc/intrin/pminub.h" +#include "libc/intrin/pmulhrsw.h" +#include "libc/intrin/pmulhuw.h" +#include "libc/intrin/pmulhw.h" +#include "libc/intrin/pmulld.h" +#include "libc/intrin/pmullw.h" +#include "libc/intrin/pmuludq.h" +#include "libc/intrin/por.h" +#include "libc/intrin/psadbw.h" +#include "libc/intrin/pshufb.h" +#include "libc/intrin/pshufd.h" +#include "libc/intrin/pshufhw.h" +#include "libc/intrin/pshuflw.h" +#include "libc/intrin/pshufw.h" +#include "libc/intrin/psignb.h" +#include "libc/intrin/psignd.h" +#include "libc/intrin/psignw.h" +#include "libc/intrin/pslld.h" +#include "libc/intrin/pslldq.h" +#include "libc/intrin/psllq.h" +#include "libc/intrin/psllw.h" +#include "libc/intrin/psrad.h" +#include "libc/intrin/psraw.h" +#include "libc/intrin/psrld.h" +#include "libc/intrin/psrldq.h" +#include "libc/intrin/psrlq.h" +#include "libc/intrin/psrlw.h" +#include "libc/intrin/psubb.h" +#include "libc/intrin/psubq.h" +#include "libc/intrin/psubsb.h" +#include "libc/intrin/psubsw.h" +#include "libc/intrin/psubusb.h" +#include "libc/intrin/psubusw.h" +#include "libc/intrin/psubw.h" +#include "libc/intrin/punpckhbw.h" +#include "libc/intrin/punpckhdq.h" +#include "libc/intrin/punpckhqdq.h" +#include "libc/intrin/punpckhwd.h" +#include "libc/intrin/punpcklbw.h" +#include "libc/intrin/punpckldq.h" +#include "libc/intrin/punpcklqdq.h" +#include "libc/intrin/punpcklwd.h" +#include "libc/intrin/pxor.h" +#include "libc/intrin/shufpd.h" +#include "libc/intrin/shufps.h" +#include "libc/limits.h" +#include "libc/log/check.h" +#include "libc/nexgen32e/kcpuids.h" +#include "libc/rand/lcg.h" +#include "libc/rand/rand.h" +#include "libc/runtime/gc.h" +#include "libc/stdio/stdio.h" +#include "libc/str/str.h" +#include "libc/testlib/ezbench.h" +#include "libc/testlib/testlib.h" +#include "libc/x/x.h" +#include "tool/viz/lib/formatstringtable-testlib.h" + +uint64_t g_rando = 1; + +forceinline uint64_t Rando(void) { + return KnuthLinearCongruentialGenerator(&g_rando) >> 32 << 32 | + KnuthLinearCongruentialGenerator(&g_rando) >> 32; +} + +noinline void RngSet(void *mem, size_t size) { + uint64_t coin; + DCHECK(size % 8 == 0); + for (size >>= 3; size--;) { + coin = Rando(); + memcpy((char *)mem + size * 8, &coin, 8); + } +} + +FIXTURE(intrin, disableHardwareExtensions) { + memset((/*unconst*/ void *)kCpuids, 0, sizeof(kCpuids)); +} + +TEST(punpcklwd, test) { + uint16_t a[8] = {1, 02, 03, 04, 05, 06, 07, 8}; + uint16_t b[8] = {9, 10, 11, 12, 13, 14, 15, 16}; + uint16_t c[8]; + punpcklwd(c, a, b); + ASSERT_EQ(1, c[0]); + ASSERT_EQ(9, c[1]); + ASSERT_EQ(2, c[2]); + ASSERT_EQ(10, c[3]); + ASSERT_EQ(3, c[4]); + ASSERT_EQ(11, c[5]); + ASSERT_EQ(4, c[6]); + ASSERT_EQ(12, c[7]); +} + +TEST(punpcklwd, pure) { + uint16_t a[8] = {1, 02, 03, 04, 05, 06, 07, 8}; + uint16_t b[8] = {9, 10, 11, 12, 13, 14, 15, 16}; + uint16_t c[8]; + punpcklwd(c, a, b); + ASSERT_EQ(1, c[0]); + ASSERT_EQ(9, c[1]); + ASSERT_EQ(2, c[2]); + ASSERT_EQ(10, c[3]); + ASSERT_EQ(3, c[4]); + ASSERT_EQ(11, c[5]); + ASSERT_EQ(4, c[6]); + ASSERT_EQ(12, c[7]); +} + +TEST(punpcklwd, testAlias) { + uint16_t a[8] = {1, 02, 03, 04, 05, 06, 07, 8}; + uint16_t b[8] = {9, 10, 11, 12, 13, 14, 15, 16}; + punpcklwd(a, a, b); + ASSERT_EQ(1, a[0]); + ASSERT_EQ(9, a[1]); + ASSERT_EQ(2, a[2]); + ASSERT_EQ(10, a[3]); + ASSERT_EQ(3, a[4]); + ASSERT_EQ(11, a[5]); + ASSERT_EQ(4, a[6]); + ASSERT_EQ(12, a[7]); +} + +TEST(punpcklwd, pureAlias) { + uint16_t a[8] = {1, 02, 03, 04, 05, 06, 07, 8}; + uint16_t b[8] = {9, 10, 11, 12, 13, 14, 15, 16}; + (punpcklwd)(a, a, b); + ASSERT_EQ(1, a[0]); + ASSERT_EQ(9, a[1]); + ASSERT_EQ(2, a[2]); + ASSERT_EQ(10, a[3]); + ASSERT_EQ(3, a[4]); + ASSERT_EQ(11, a[5]); + ASSERT_EQ(4, a[6]); + ASSERT_EQ(12, a[7]); +} + +TEST(punpcklwd, testAlias2) { + uint16_t a[8] = {1, 02, 03, 04, 05, 06, 07, 8}; + uint16_t b[8] = {9, 10, 11, 12, 13, 14, 15, 16}; + punpcklwd(b, a, b); + ASSERT_EQ(1, b[0]); + ASSERT_EQ(9, b[1]); + ASSERT_EQ(2, b[2]); + ASSERT_EQ(10, b[3]); + ASSERT_EQ(3, b[4]); + ASSERT_EQ(11, b[5]); + ASSERT_EQ(4, b[6]); + ASSERT_EQ(12, b[7]); +} + +TEST(punpcklwd, pureAlias2) { + uint16_t a[8] = {1, 02, 03, 04, 05, 06, 07, 8}; + uint16_t b[8] = {9, 10, 11, 12, 13, 14, 15, 16}; + (punpcklwd)(b, a, b); + ASSERT_EQ(1, b[0]); + ASSERT_EQ(9, b[1]); + ASSERT_EQ(2, b[2]); + ASSERT_EQ(10, b[3]); + ASSERT_EQ(3, b[4]); + ASSERT_EQ(11, b[5]); + ASSERT_EQ(4, b[6]); + ASSERT_EQ(12, b[7]); +} + +TEST(punpcklqdq, test) { + uint64_t a[2] = {1, 2}; + uint64_t b[2] = {3, 4}; + uint64_t c[2]; + punpcklqdq(c, a, b); + ASSERT_EQ(1, c[0]); + ASSERT_EQ(3, c[1]); +} + +TEST(punpcklqdq, pure) { + uint64_t a[2] = {1, 2}; + uint64_t b[2] = {3, 4}; + uint64_t c[2]; + (punpcklqdq)(c, a, b); + ASSERT_EQ(1, c[0]); + ASSERT_EQ(3, c[1]); +} + +TEST(punpcklqdq, testAlias) { + uint64_t a[2] = {1, 2}; + uint64_t b[2] = {3, 4}; + punpcklqdq(a, a, b); + ASSERT_EQ(1, a[0]); + ASSERT_EQ(3, a[1]); +} + +TEST(punpcklqdq, pureAlias) { + uint64_t a[2] = {1, 2}; + uint64_t b[2] = {3, 4}; + (punpcklqdq)(a, a, b); + ASSERT_EQ(1, a[0]); + ASSERT_EQ(3, a[1]); +} + +TEST(punpckldq, test) { + uint32_t a[4] = {1, 2, 3, 4}; + uint32_t b[4] = {5, 6, 7, 8}; + uint32_t c[4]; + punpckldq(c, a, b); + ASSERT_EQ(1, c[0]); + ASSERT_EQ(5, c[1]); + ASSERT_EQ(2, c[2]); + ASSERT_EQ(6, c[3]); +} + +TEST(punpckldq, pure) { + uint32_t a[4] = {1, 2, 3, 4}; + uint32_t b[4] = {5, 6, 7, 8}; + uint32_t c[4]; + punpckldq(c, a, b); + ASSERT_EQ(1, c[0]); + ASSERT_EQ(5, c[1]); + ASSERT_EQ(2, c[2]); + ASSERT_EQ(6, c[3]); +} + +TEST(punpckldq, testAlias) { + uint32_t a[4] = {1, 2, 3, 4}; + uint32_t b[4] = {5, 6, 7, 8}; + punpckldq(a, a, b); + ASSERT_EQ(1, a[0]); + ASSERT_EQ(5, a[1]); + ASSERT_EQ(2, a[2]); + ASSERT_EQ(6, a[3]); +} + +TEST(punpckldq, pureAlias) { + uint32_t a[4] = {1, 2, 3, 4}; + uint32_t b[4] = {5, 6, 7, 8}; + (punpckldq)(a, a, b); + ASSERT_EQ(1, a[0]); + ASSERT_EQ(5, a[1]); + ASSERT_EQ(2, a[2]); + ASSERT_EQ(6, a[3]); +} + +TEST(punpckldq, testAlias2) { + uint32_t a[4] = {1, 2, 3, 4}; + uint32_t b[4] = {5, 6, 7, 8}; + punpckldq(b, a, b); + ASSERT_EQ(1, b[0]); + ASSERT_EQ(5, b[1]); + ASSERT_EQ(2, b[2]); + ASSERT_EQ(6, b[3]); +} + +TEST(punpckldq, pureAlias2) { + uint32_t a[4] = {1, 2, 3, 4}; + uint32_t b[4] = {5, 6, 7, 8}; + (punpckldq)(b, a, b); + ASSERT_EQ(1, b[0]); + ASSERT_EQ(5, b[1]); + ASSERT_EQ(2, b[2]); + ASSERT_EQ(6, b[3]); +} + +TEST(punpcklqdq, fuzz) { + int i, j; + uint64_t x[2], y[2], a[2], b[2]; + for (i = 0; i < 100; ++i) { + RngSet(x, sizeof(x)); + RngSet(y, sizeof(y)); + punpcklqdq(a, x, y); + (punpcklqdq)(b, x, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + } +} + +TEST(punpckldq, fuzz) { + int i, j; + uint32_t x[4], y[4], a[4], b[4]; + for (i = 0; i < 100; ++i) { + RngSet(x, sizeof(x)); + RngSet(y, sizeof(y)); + punpckldq(a, x, y); + (punpckldq)(b, x, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + } +} + +TEST(punpcklbw, fuzz) { + int i, j; + uint8_t x[16], y[16], a[16], b[16]; + for (i = 0; i < 100; ++i) { + RngSet(x, sizeof(x)); + RngSet(y, sizeof(y)); + punpcklbw(a, x, y); + (punpcklbw)(b, x, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + } +} + +TEST(punpckhwd, test) { + uint16_t a[8] = {1, 02, 03, 04, 05, 06, 07, 8}; + uint16_t b[8] = {9, 10, 11, 12, 13, 14, 15, 16}; + uint16_t c[8]; + punpckhwd(c, a, b); + ASSERT_EQ(5, c[0]); + ASSERT_EQ(13, c[1]); + ASSERT_EQ(6, c[2]); + ASSERT_EQ(14, c[3]); + ASSERT_EQ(7, c[4]); + ASSERT_EQ(15, c[5]); + ASSERT_EQ(8, c[6]); + ASSERT_EQ(16, c[7]); +} + +TEST(punpckhwd, pure) { + uint16_t a[8] = {1, 02, 03, 04, 05, 06, 07, 8}; + uint16_t b[8] = {9, 10, 11, 12, 13, 14, 15, 16}; + uint16_t c[8]; + punpckhwd(c, a, b); + ASSERT_EQ(5, c[0]); + ASSERT_EQ(13, c[1]); + ASSERT_EQ(6, c[2]); + ASSERT_EQ(14, c[3]); + ASSERT_EQ(7, c[4]); + ASSERT_EQ(15, c[5]); + ASSERT_EQ(8, c[6]); + ASSERT_EQ(16, c[7]); +} + +TEST(punpckhwd, testAlias) { + uint16_t a[8] = {1, 02, 03, 04, 05, 06, 07, 8}; + uint16_t b[8] = {9, 10, 11, 12, 13, 14, 15, 16}; + punpckhwd(a, a, b); + ASSERT_EQ(5, a[0]); + ASSERT_EQ(13, a[1]); + ASSERT_EQ(6, a[2]); + ASSERT_EQ(14, a[3]); + ASSERT_EQ(7, a[4]); + ASSERT_EQ(15, a[5]); + ASSERT_EQ(8, a[6]); + ASSERT_EQ(16, a[7]); +} + +TEST(punpckhwd, pureAlias) { + uint16_t a[8] = {1, 02, 03, 04, 05, 06, 07, 8}; + uint16_t b[8] = {9, 10, 11, 12, 13, 14, 15, 16}; + (punpckhwd)(a, a, b); + ASSERT_EQ(5, a[0]); + ASSERT_EQ(13, a[1]); + ASSERT_EQ(6, a[2]); + ASSERT_EQ(14, a[3]); + ASSERT_EQ(7, a[4]); + ASSERT_EQ(15, a[5]); + ASSERT_EQ(8, a[6]); + ASSERT_EQ(16, a[7]); +} + +TEST(punpckhwd, testAlias2) { + uint16_t a[8] = {1, 02, 03, 04, 05, 06, 07, 8}; + uint16_t b[8] = {9, 10, 11, 12, 13, 14, 15, 16}; + punpckhwd(b, a, b); + ASSERT_EQ(5, b[0]); + ASSERT_EQ(13, b[1]); + ASSERT_EQ(6, b[2]); + ASSERT_EQ(14, b[3]); + ASSERT_EQ(7, b[4]); + ASSERT_EQ(15, b[5]); + ASSERT_EQ(8, b[6]); + ASSERT_EQ(16, b[7]); +} + +TEST(punpckhwd, pureAlias2) { + uint16_t a[8] = {1, 02, 03, 04, 05, 06, 07, 8}; + uint16_t b[8] = {9, 10, 11, 12, 13, 14, 15, 16}; + (punpckhwd)(b, a, b); + ASSERT_EQ(5, b[0]); + ASSERT_EQ(13, b[1]); + ASSERT_EQ(6, b[2]); + ASSERT_EQ(14, b[3]); + ASSERT_EQ(7, b[4]); + ASSERT_EQ(15, b[5]); + ASSERT_EQ(8, b[6]); + ASSERT_EQ(16, b[7]); +} + +TEST(punpckhqdq, test) { + uint64_t a[2] = {1, 2}; + uint64_t b[2] = {3, 4}; + uint64_t c[2]; + punpckhqdq(c, a, b); + ASSERT_EQ(2, c[0]); + ASSERT_EQ(4, c[1]); +} + +TEST(punpckhqdq, pure) { + uint64_t a[2] = {1, 2}; + uint64_t b[2] = {3, 4}; + uint64_t c[2]; + (punpckhqdq)(c, a, b); + ASSERT_EQ(2, c[0]); + ASSERT_EQ(4, c[1]); +} + +TEST(punpckhqdq, testAlias) { + uint64_t a[2] = {1, 2}; + uint64_t b[2] = {3, 4}; + punpckhqdq(a, a, b); + ASSERT_EQ(2, a[0]); + ASSERT_EQ(4, a[1]); +} + +TEST(punpckhqdq, pureAlias) { + uint64_t a[2] = {1, 2}; + uint64_t b[2] = {3, 4}; + (punpckhqdq)(a, a, b); + ASSERT_EQ(2, a[0]); + ASSERT_EQ(4, a[1]); +} + +TEST(punpckhdq, test) { + uint32_t a[4] = {1, 2, 3, 4}; + uint32_t b[4] = {5, 6, 7, 8}; + uint32_t c[4]; + punpckhdq(c, a, b); + ASSERT_EQ(3, c[0]); + ASSERT_EQ(7, c[1]); + ASSERT_EQ(4, c[2]); + ASSERT_EQ(8, c[3]); +} + +TEST(punpckhdq, pure) { + uint32_t a[4] = {1, 2, 3, 4}; + uint32_t b[4] = {5, 6, 7, 8}; + uint32_t c[4]; + punpckhdq(c, a, b); + ASSERT_EQ(3, c[0]); + ASSERT_EQ(7, c[1]); + ASSERT_EQ(4, c[2]); + ASSERT_EQ(8, c[3]); +} + +TEST(punpckhdq, testAlias) { + uint32_t a[4] = {1, 2, 3, 4}; + uint32_t b[4] = {5, 6, 7, 8}; + punpckhdq(a, a, b); + ASSERT_EQ(3, a[0]); + ASSERT_EQ(7, a[1]); + ASSERT_EQ(4, a[2]); + ASSERT_EQ(8, a[3]); +} + +TEST(punpckhdq, pureAlias) { + uint32_t a[4] = {1, 2, 3, 4}; + uint32_t b[4] = {5, 6, 7, 8}; + (punpckhdq)(a, a, b); + ASSERT_EQ(3, a[0]); + ASSERT_EQ(7, a[1]); + ASSERT_EQ(4, a[2]); + ASSERT_EQ(8, a[3]); +} + +TEST(punpckhdq, testAlias2) { + uint32_t a[4] = {1, 2, 3, 4}; + uint32_t b[4] = {5, 6, 7, 8}; + punpckhdq(b, a, b); + ASSERT_EQ(3, b[0]); + ASSERT_EQ(7, b[1]); + ASSERT_EQ(4, b[2]); + ASSERT_EQ(8, b[3]); +} + +TEST(punpckhdq, pureAlias2) { + uint32_t a[4] = {1, 2, 3, 4}; + uint32_t b[4] = {5, 6, 7, 8}; + (punpckhdq)(b, a, b); + ASSERT_EQ(3, b[0]); + ASSERT_EQ(7, b[1]); + ASSERT_EQ(4, b[2]); + ASSERT_EQ(8, b[3]); +} + +TEST(punpckhwd, fuzz) { + int i, j; + uint16_t x[8], y[8], a[8], b[8]; + for (i = 0; i < 100; ++i) { + RngSet(x, sizeof(x)); + RngSet(y, sizeof(y)); + punpckhwd(a, x, y); + (punpckhwd)(b, x, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + } +} + +TEST(punpckhqdq, fuzz) { + int i, j; + uint64_t x[2], y[2], a[2], b[2]; + for (i = 0; i < 100; ++i) { + RngSet(x, sizeof(x)); + RngSet(y, sizeof(y)); + punpckhqdq(a, x, y); + (punpckhqdq)(b, x, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + } +} + +TEST(punpckhdq, fuzz) { + int i, j; + uint32_t x[4], y[4], a[4], b[4]; + for (i = 0; i < 100; ++i) { + RngSet(x, sizeof(x)); + RngSet(y, sizeof(y)); + punpckhdq(a, x, y); + (punpckhdq)(b, x, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + } +} + +TEST(punpckhbw, fuzz) { + int i, j; + uint8_t x[16], y[16], a[16], b[16]; + for (i = 0; i < 100; ++i) { + RngSet(x, sizeof(x)); + RngSet(y, sizeof(y)); + punpckhbw(a, x, y); + (punpckhbw)(b, x, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + } +} + +TEST(psubq, fuzz) { + int i, j; + int64_t x[2], y[2], a[2], b[2]; + for (i = 0; i < 100; ++i) { + RngSet(x, sizeof(x)); + RngSet(y, sizeof(y)); + psubq(a, x, y); + (psubq)(b, x, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + } +} + +TEST(psrawv, testSmallShift) { + int16_t A[8] = {-1, -2, SHRT_MIN, 2}; + uint64_t B[2] = {1}; + psrawv(A, A, B); + ASSERT_EQ(-1, A[0]); + ASSERT_EQ(-1, A[1]); + ASSERT_EQ(-16384, A[2]); + ASSERT_EQ(1, A[3]); + ASSERT_EQ(0, A[4]); +} + +TEST(psraw, testSmallShift) { + int16_t A[8] = {-1, -2, SHRT_MIN, 2}; + psraw(A, A, 1); + ASSERT_EQ(-1, A[0]); + ASSERT_EQ(-1, A[1]); + ASSERT_EQ(-16384, A[2]); + ASSERT_EQ(1, A[3]); + ASSERT_EQ(0, A[4]); +} + +TEST(psraw, pureSmallShift) { + int16_t A[8] = {-1, -2, SHRT_MIN, 2}; + (psraw)(A, A, 1); + ASSERT_EQ(-1, A[0]); + ASSERT_EQ(-1, A[1]); + ASSERT_EQ(-16384, A[2]); + ASSERT_EQ(1, A[3]); + ASSERT_EQ(0, A[4]); +} + +TEST(psraw, testBigShift_saturatesCount) { + int16_t A[8] = {-1, -2, SHRT_MIN, 2}; + psraw(A, A, 77); + ASSERT_EQ(-1, A[0]); + ASSERT_EQ(-1, A[1]); + ASSERT_EQ(-1, A[2]); + ASSERT_EQ(0, A[3]); + ASSERT_EQ(0, A[4]); +} + +TEST(psraw, pureBigShift_saturatesCount) { + int16_t A[8] = {-1, -2, SHRT_MIN, 2}; + (psraw)(A, A, 77); + ASSERT_EQ(-1, A[0]); + ASSERT_EQ(-1, A[1]); + ASSERT_EQ(-1, A[2]); + ASSERT_EQ(0, A[3]); + ASSERT_EQ(0, A[4]); +} + +TEST(psradv, testSmallShift) { + int32_t A[8] = {-1, -2, INT32_MIN, 2}; + uint64_t B[2] = {1}; + psradv(A, A, B); + ASSERT_EQ(-1, A[0]); + ASSERT_EQ(-1, A[1]); + ASSERT_EQ(-1073741824, A[2]); + ASSERT_EQ(1, A[3]); + ASSERT_EQ(0, A[4]); +} + +TEST(psradv, test) { + int i, j; + int32_t x[4], a[4], b[4]; + uint64_t y[2]; + for (i = 0; i < 100; ++i) { + RngSet(x, sizeof(x)); + for (j = 0; j < 2; ++j) { + y[j] = Rando() % 64; + } + psradv(a, x, y); + (psradv)(b, x, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + } +} + +TEST(psrad, testSmallShift) { + int32_t A[4] = {-1, -2, INT32_MIN, 2}; + psrad(A, A, 1); + ASSERT_EQ(-1, A[0]); + ASSERT_EQ(-1, A[1]); + ASSERT_EQ(-1073741824, A[2]); + ASSERT_EQ(1, A[3]); +} + +TEST(psrad, pureSmallShift) { + int32_t A[4] = {-1, -2, INT32_MIN, 2}; + (psrad)(A, A, 1); + ASSERT_EQ(-1, A[0]); + ASSERT_EQ(-1, A[1]); + ASSERT_EQ(-1073741824, A[2]); + ASSERT_EQ(1, A[3]); +} + +TEST(psrad, testBigShift_saturatesCount) { + int32_t A[4] = {-1, -2, INT32_MIN, 2}; + psrad(A, A, 77); + ASSERT_EQ(-1, A[0]); + ASSERT_EQ(-1, A[1]); + ASSERT_EQ(-1, A[2]); + ASSERT_EQ(0, A[3]); +} + +TEST(psrad, pureBigShift_saturatesCount) { + int32_t A[4] = {-1, -2, INT32_MIN, 2}; + (psrad)(A, A, 77); + ASSERT_EQ(-1, A[0]); + ASSERT_EQ(-1, A[1]); + ASSERT_EQ(-1, A[2]); + ASSERT_EQ(0, A[3]); +} + +TEST(psllwv, test) { + int i, j; + uint16_t x[8], a[8], b[8]; + uint64_t y[2]; + for (i = 0; i < 32; ++i) { + RngSet(x, sizeof(x)); + for (j = 0; j < 2; ++j) { + y[j] = Rando() % 300; + } + psllwv(a, x, y); + (psllwv)(b, x, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + } +} + +TEST(psllw, testSmallShift) { + uint16_t A[8] = {0, 1, 0xffff, 2}; + psllw(A, A, 1); + ASSERT_EQ(0, A[0]); + ASSERT_EQ(2, A[1]); + ASSERT_EQ(0xfffe, A[2]); + ASSERT_EQ(4, A[3]); +} + +TEST(psllwv, testSmallShift) { + uint16_t A[8] = {0, 1, 0xffff, 2}; + uint64_t B[2] = {1}; + psllwv(A, A, B); + ASSERT_EQ(0, A[0]); + ASSERT_EQ(2, A[1]); + ASSERT_EQ(0xfffe, A[2]); + ASSERT_EQ(4, A[3]); +} + +TEST(pslldv, test) { + int i, j; + uint32_t x[4], a[4], b[4]; + uint64_t y[2]; + for (i = 0; i < 32; ++i) { + RngSet(x, sizeof(x)); + for (j = 0; j < 2; ++j) { + y[j] = Rando() % 300; + } + pslldv(a, x, y); + (pslldv)(b, x, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + } +} + +TEST(pslld, testSmallShift) { + uint32_t A[8] = {0, 1, 0xffffffff, 2}; + pslld(A, A, 1); + ASSERT_EQ(0, A[0]); + ASSERT_EQ(2, A[1]); + ASSERT_EQ(0xfffffffe, A[2]); + ASSERT_EQ(4, A[3]); +} + +TEST(pslldv, testSmallShift) { + uint32_t A[8] = {0, 1, 0xffffffff, 2}; + uint64_t B[2] = {1}; + pslldv(A, A, B); + ASSERT_EQ(0, A[0]); + ASSERT_EQ(2, A[1]); + ASSERT_EQ(0xfffffffe, A[2]); + ASSERT_EQ(4, A[3]); +} + +TEST(pmulhuw, test) { + uint16_t x[8] = {0, 0xffff, 0x0000, 0x0001, 0x8000}; + uint16_t y[8] = {0, 0xffff, 0xffff, 0xffff, 0x8000}; + uint16_t z[8]; + pmulhuw(z, x, y); + ASSERT_EQ(0x0000 /*0000*/, z[0]); + ASSERT_EQ(0xfffe /*0001*/, z[1]); + ASSERT_EQ(0x0000 /*0000*/, z[2]); + ASSERT_EQ(0x0000 /*ffff*/, z[3]); + ASSERT_EQ(0x4000 /*0000*/, z[4]); +} + +TEST(pmulhuw, pure) { + uint16_t x[8] = {0, 0xffff, 0x0000, 0x0001, 0x8000}; + uint16_t y[8] = {0, 0xffff, 0xffff, 0xffff, 0x8000}; + uint16_t z[8]; + (pmulhuw)(z, x, y); + ASSERT_EQ(0x0000 /*0000*/, z[0]); + ASSERT_EQ(0xfffe /*0001*/, z[1]); + ASSERT_EQ(0x0000 /*0000*/, z[2]); + ASSERT_EQ(0x0000 /*ffff*/, z[3]); + ASSERT_EQ(0x4000 /*0000*/, z[4]); +} + +TEST(pmulhuw, fuzz) { + int i, j; + uint16_t x[8], y[8], a[8], b[8]; + for (i = 0; i < 100; ++i) { + RngSet(x, sizeof(x)); + RngSet(y, sizeof(y)); + pmulhuw(a, x, y); + (pmulhuw)(b, x, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + pmulhuw(a, (void *)a, y); + (pmulhuw)(b, (void *)b, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + } +} + +TEST(pmulhw, fuzz) { + int i, j; + int16_t x[8], y[8], a[8], b[8]; + for (i = 0; i < 1000; ++i) { + RngSet(x, sizeof(x)); + RngSet(y, sizeof(y)); + pmulhw(a, x, y); + (pmulhw)(b, x, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + pmulhw(a, (void *)a, y); + (pmulhw)(b, (void *)b, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + } +} + +TEST(pmullw, fuzz) { + int i, j; + int16_t x[8], y[8], a[8], b[8]; + for (i = 0; i < 1000; ++i) { + RngSet(x, sizeof(x)); + RngSet(y, sizeof(y)); + pmullw(a, x, y); + (pmullw)(b, x, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + pmullw(a, (void *)a, y); + (pmullw)(b, (void *)b, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + } +} + +TEST(pmulld, fuzz) { + int i, j; + int32_t x[4], y[4], a[4], b[4]; + for (i = 0; i < 1000; ++i) { + RngSet(x, sizeof(x)); + RngSet(y, sizeof(y)); + pmulld(a, x, y); + (pmulld)(b, x, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + pmulld(a, (void *)a, y); + (pmulld)(b, (void *)b, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + } +} + +TEST(pmuludq, fuzz) { + int i, j; + uint32_t x[4], y[4]; + uint64_t a[2], b[2]; + for (i = 0; i < 1000; ++i) { + RngSet(x, sizeof(x)); + RngSet(y, sizeof(y)); + pmuludq(a, x, y); + (pmuludq)(b, x, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + pmuludq(a, (void *)a, y); + (pmuludq)(b, (void *)b, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + } +} + +TEST(pmaddwd, fuzz) { + int i, j; + int16_t x[8], y[8]; + int32_t a[4], b[4]; + for (i = 0; i < 1000; ++i) { + RngSet(x, sizeof(x)); + RngSet(y, sizeof(y)); + pmaddwd(a, x, y); + (pmaddwd)(b, x, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + pmaddwd(a, (void *)a, y); + (pmaddwd)(b, (void *)b, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + } +} + +TEST(phaddw, fuzz) { + int i, j; + int16_t x[8], y[8]; + int16_t a[8], b[8]; + for (i = 0; i < 1000; ++i) { + RngSet(x, sizeof(x)); + RngSet(y, sizeof(y)); + phaddw(a, x, y); + (phaddw)(b, x, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + phaddw(a, (void *)a, y); + (phaddw)(b, (void *)b, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + } +} + +TEST(phaddd, fuzz) { + int i, j; + int32_t x[4], y[4]; + int32_t a[4], b[4]; + for (i = 0; i < 1000; ++i) { + RngSet(x, sizeof(x)); + RngSet(y, sizeof(y)); + phaddd(a, x, y); + (phaddd)(b, x, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + phaddd(a, (void *)a, y); + (phaddd)(b, (void *)b, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + } +} + +TEST(phsubw, fuzz) { + int i, j; + int16_t x[8], y[8]; + int16_t a[8], b[8]; + for (i = 0; i < 1000; ++i) { + RngSet(x, sizeof(x)); + RngSet(y, sizeof(y)); + phsubw(a, x, y); + (phsubw)(b, x, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + phsubw(a, (void *)a, y); + (phsubw)(b, (void *)b, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + } +} + +TEST(phsubd, fuzz) { + int i, j; + int32_t x[4], y[4]; + int32_t a[4], b[4]; + for (i = 0; i < 1000; ++i) { + RngSet(x, sizeof(x)); + RngSet(y, sizeof(y)); + phsubd(a, x, y); + (phsubd)(b, x, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + phsubd(a, (void *)a, y); + (phsubd)(b, (void *)b, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + } +} + +TEST(phaddsw, fuzz) { + int i, j; + int16_t x[8], y[8]; + int16_t a[8], b[8]; + for (i = 0; i < 1000; ++i) { + RngSet(x, sizeof(x)); + RngSet(y, sizeof(y)); + phaddsw(a, x, y); + (phaddsw)(b, x, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + phaddsw(a, (void *)a, y); + (phaddsw)(b, (void *)b, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + } +} + +TEST(phsubsw, fuzz) { + int i, j; + int16_t x[8], y[8]; + int16_t a[8], b[8]; + for (i = 0; i < 1000; ++i) { + RngSet(x, sizeof(x)); + RngSet(y, sizeof(y)); + phsubsw(a, x, y); + (phsubsw)(b, x, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + phsubsw(a, (void *)a, y); + (phsubsw)(b, (void *)b, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + } +} + +TEST(phaddw, testOverflow_wrapsAround) { + short M[2][8] = { + {0x7fff, 0, 0x7fff, 1, 13004, -30425, 20777, -16389}, + {-28040, 13318, -1336, -24798, -13876, 3599, -7346, -23575}, + }; + phaddw(M[0], M[0], M[1]); + EXPECT_SHRTMATRIXEQ(2, 8, M, "\n\ + 32767 -32768 -17421 4388 -14722 -26134 -10277 -30921\n\ +-28040 13318 -1336 -24798 -13876 3599 -7346 -23575"); +} + +TEST(phaddw, testAliasing_isOk) { + short M[1][8] = { + {0, 1, 2, 3, 4, 5, 6, 7}, + }; + phaddw(M[0], M[0], M[0]); + EXPECT_SHRTMATRIXEQ(1, 8, M, "\n\ + 1 5 9 13 1 5 9 13"); +} + +TEST(phaddsw, testOverflow_saturates) { + short M[2][8] = { + {0x7fff, 0, 0x7fff, 1, 0x7fff, 0x7fff, 20777, -16389}, + {-28040, 13318, -1336, -24798, -13876, 3599, -7346, -23575}, + }; + phaddsw(M[0], M[0], M[1]); + EXPECT_SHRTMATRIXEQ(2, 8, M, "\n\ + 32767 32767 32767 4388 -14722 -26134 -10277 -30921\n\ +-28040 13318 -1336 -24798 -13876 3599 -7346 -23575"); +} + +TEST(phaddsw, testAliasing_isOk) { + short M[1][8] = {{0, 1, 2, 3, 4, 5, 6, 7}}; + phaddsw(M[0], M[0], M[0]); + EXPECT_SHRTMATRIXEQ(1, 8, M, "\n\ + 1 5 9 13 1 5 9 13"); +} + +TEST(pcmpgtb, test) { + int i, j; + int8_t x[16], y[16], a[16], b[16]; + for (i = 0; i < 100; ++i) { + RngSet(x, sizeof(x)); + RngSet(y, sizeof(y)); + static int count; + pcmpgtb(a, x, y); + (pcmpgtb)(b, x, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + } +} + +TEST(pcmpeqb, test) { + int i, j; + uint8_t x[16], y[16], a[16], b[16]; + for (i = 0; i < 100; ++i) { + RngSet(x, sizeof(x)); + RngSet(y, sizeof(y)); + pcmpeqb(a, x, y); + (pcmpeqb)(b, x, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + } +} + +TEST(pcmpeqd, test) { + int i, j; + int32_t x[4], y[4], a[4], b[4]; + for (i = 0; i < 100; ++i) { + RngSet(x, sizeof(x)); + RngSet(y, sizeof(y)); + pcmpeqd(a, x, y); + (pcmpeqd)(b, x, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + } +} + +TEST(pcmpgtd, test) { + int i, j; + int32_t x[4], y[4], a[4], b[4]; + for (i = 0; i < 100; ++i) { + RngSet(x, sizeof(x)); + RngSet(y, sizeof(y)); + pcmpgtd(a, x, y); + (pcmpgtd)(b, x, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + } +} + +TEST(pcmpeqw, test) { + int i, j; + int16_t x[8], y[8], a[8], b[8]; + for (i = 0; i < 100; ++i) { + RngSet(x, sizeof(x)); + RngSet(y, sizeof(y)); + pcmpeqw(a, x, y); + (pcmpeqw)(b, x, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + } +} + +TEST(pcmpgtw, test) { + int i, j; + int16_t x[8], y[8], a[8], b[8]; + for (i = 0; i < 100; ++i) { + RngSet(x, sizeof(x)); + RngSet(y, sizeof(y)); + pcmpgtw(a, x, y); + (pcmpgtw)(b, x, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + } +} + +TEST(por, fuzz) { + int i, j; + uint64_t x[2], y[2], a[2], b[2]; + for (i = 0; i < 100; ++i) { + for (j = 0; j < 2; ++j) x[j] = Rando(); + for (j = 0; j < 2; ++j) y[j] = Rando(); + por(a, x, y); + (por)(b, x, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + por(a, (void *)a, y); + (por)(b, (void *)b, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + } +} + +TEST(pxor, fuzz) { + int i, j; + uint64_t x[2], y[2], a[2], b[2]; + for (i = 0; i < 100; ++i) { + for (j = 0; j < 2; ++j) x[j] = Rando(); + for (j = 0; j < 2; ++j) y[j] = Rando(); + pxor(a, x, y); + (pxor)(b, x, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + pxor(a, (void *)a, y); + (pxor)(b, (void *)b, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + } +} + +TEST(pand, fuzz) { + int i, j; + uint64_t x[2], y[2], a[2], b[2]; + for (i = 0; i < 100; ++i) { + for (j = 0; j < 2; ++j) x[j] = Rando(); + for (j = 0; j < 2; ++j) y[j] = Rando(); + pand(a, x, y); + (pand)(b, x, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + pand(a, (void *)a, y); + (pand)(b, (void *)b, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + } +} + +TEST(pandn, fuzz) { + int i, j; + uint64_t x[2], y[2], a[2], b[2]; + for (i = 0; i < 100; ++i) { + for (j = 0; j < 2; ++j) x[j] = Rando(); + for (j = 0; j < 2; ++j) y[j] = Rando(); + pandn(a, x, y); + (pandn)(b, x, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + pandn(a, (void *)a, y); + (pandn)(b, (void *)b, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + } +} + +TEST(paddq, fuzz) { + int i, j; + int64_t x[2], y[2], a[2], b[2]; + for (i = 0; i < 100; ++i) { + for (j = 0; j < 2; ++j) x[j] = Rando(); + for (j = 0; j < 2; ++j) y[j] = Rando(); + paddq(a, x, y); + (paddq)(b, x, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + paddq(a, (void *)a, y); + (paddq)(b, (void *)b, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + } +} + +TEST(pavgb, fuzz) { + int i, j; + uint8_t x[16], y[16], a[16], b[16]; + for (i = 0; i < 100; ++i) { + RngSet(x, sizeof(x)); + RngSet(y, sizeof(y)); + pavgb(a, x, y); + (pavgb)(b, x, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + pavgb(a, (void *)a, y); + (pavgb)(b, (void *)b, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + } +} + +TEST(pavgw, fuzz) { + int i, j; + uint16_t x[8], y[8], a[8], b[8]; + for (i = 0; i < 100; ++i) { + RngSet(x, sizeof(x)); + RngSet(y, sizeof(y)); + pavgw(a, x, y); + (pavgw)(b, x, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + pavgw(a, (void *)a, y); + (pavgw)(b, (void *)b, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + } +} + +TEST(punpcklwd, fuzz) { + int i, j; + uint16_t x[8], y[8], a[8], b[8]; + for (i = 0; i < 100; ++i) { + RngSet(x, sizeof(x)); + RngSet(y, sizeof(y)); + punpcklwd(a, x, y); + (punpcklwd)(b, x, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + punpcklwd(a, a, y); + (punpcklwd)(b, b, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + punpcklwd(a, y, a); + (punpcklwd)(b, y, b); + ASSERT_EQ(0, memcmp(a, b, 16)); + } +} + +TEST(pminub, fuzz) { + int i, j; + uint8_t x[16], y[16], a[16], b[16]; + for (i = 0; i < 100; ++i) { + RngSet(x, sizeof(x)); + RngSet(y, sizeof(y)); + pminub(a, x, y); + (pminub)(b, x, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + pminub(a, (void *)a, y); + (pminub)(b, (void *)b, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + } +} + +TEST(pminsw, fuzz) { + int i, j; + int16_t x[8], y[8], a[8], b[8]; + for (i = 0; i < 100; ++i) { + RngSet(x, sizeof(x)); + RngSet(y, sizeof(y)); + pminsw(a, x, y); + (pminsw)(b, x, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + pminsw(a, (void *)a, y); + (pminsw)(b, (void *)b, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + } +} + +TEST(pmaxub, fuzz) { + int i, j; + uint8_t x[16], y[16], a[16], b[16]; + for (i = 0; i < 100; ++i) { + RngSet(x, sizeof(x)); + RngSet(y, sizeof(y)); + pmaxub(a, x, y); + (pmaxub)(b, x, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + pmaxub(a, (void *)a, y); + (pmaxub)(b, (void *)b, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + } +} + +TEST(pmaxsw, fuzz) { + int i, j; + int16_t x[8], y[8], a[8], b[8]; + for (i = 0; i < 100; ++i) { + RngSet(x, sizeof(x)); + RngSet(y, sizeof(y)); + pmaxsw(a, x, y); + (pmaxsw)(b, x, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + pmaxsw(a, (void *)a, y); + (pmaxsw)(b, (void *)b, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + } +} + +TEST(paddw, test) { + int16_t A[8] = {7}; + int16_t B[8] = {11}; + int16_t C[8]; + paddw(C, A, B); + ASSERT_EQ(18, C[0]); +} + +TEST(paddw, testOverflow_wrapsAround) { + int16_t A[8] = {SHRT_MAX, SHRT_MIN}; + int16_t B[8] = {1, -1}; + paddw(A, A, B); + ASSERT_EQ(SHRT_MIN, A[0]); + ASSERT_EQ(SHRT_MAX, A[1]); +} + +TEST(paddw, fuzz) { + int i, j; + int16_t x[8], y[8], a[8], b[8]; + for (i = 0; i < 100; ++i) { + RngSet(x, sizeof(x)); + RngSet(y, sizeof(y)); + paddw(a, x, y); + (paddw)(b, x, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + paddw(a, (void *)a, y); + (paddw)(b, (void *)b, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + } +} + +TEST(paddsw, test) { + int16_t A[8] = {7}; + int16_t B[8] = {11}; + int16_t C[8]; + paddsw(C, A, B); + ASSERT_EQ(18, C[0]); +} + +TEST(paddsw, testOverflow_saturates) { + int16_t A[8] = {SHRT_MAX, SHRT_MIN}; + int16_t B[8] = {1, -1}; + paddsw(A, A, B); + ASSERT_EQ(SHRT_MAX, A[0]); + ASSERT_EQ(SHRT_MIN, A[1]); +} + +TEST(paddusw, fuzz) { + int i, j; + uint16_t x[8], y[8], a[8], b[8]; + for (i = 0; i < 100; ++i) { + RngSet(x, sizeof(x)); + RngSet(y, sizeof(y)); + paddusw(a, x, y); + (paddusw)(b, x, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + paddusw(a, (void *)a, y); + (paddusw)(b, (void *)b, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + } +} + +TEST(psubb, fuzz) { + int i, j; + int8_t x[16], y[16], a[16], b[16]; + for (i = 0; i < 100; ++i) { + RngSet(x, sizeof(x)); + RngSet(y, sizeof(y)); + psubb(a, x, y); + (psubb)(b, x, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + psubb(a, (void *)a, y); + (psubb)(b, (void *)b, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + } +} + +TEST(psubw, fuzz) { + int i, j; + int16_t x[8], y[8], a[8], b[8]; + for (i = 0; i < 100; ++i) { + RngSet(x, sizeof(x)); + RngSet(y, sizeof(y)); + psubw(a, x, y); + (psubw)(b, x, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + psubw(a, (void *)a, y); + (psubw)(b, (void *)b, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + } +} + +TEST(psubusw, fuzz) { + int i, j; + uint16_t x[8], y[8], a[8], b[8]; + for (i = 0; i < 100; ++i) { + RngSet(x, sizeof(x)); + RngSet(y, sizeof(y)); + psubusw(a, x, y); + (psubusw)(b, x, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + psubusw(a, (void *)a, y); + (psubusw)(b, (void *)b, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + } +} + +TEST(paddusb, fuzz) { + int i, j; + uint8_t x[16], y[16], a[16], b[16]; + for (i = 0; i < 100; ++i) { + RngSet(x, sizeof(x)); + RngSet(y, sizeof(y)); + paddusb(a, x, y); + (paddusb)(b, x, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + paddusb(a, (void *)a, y); + (paddusb)(b, (void *)b, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + } +} + +TEST(psubusb, fuzz) { + int i, j; + uint8_t x[16], y[16], a[16], b[16]; + for (i = 0; i < 100; ++i) { + RngSet(x, sizeof(x)); + RngSet(y, sizeof(y)); + psubusb(a, x, y); + (psubusb)(b, x, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + psubusb(a, (void *)a, y); + (psubusb)(b, (void *)b, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + } +} + +TEST(pabsb, fuzz) { + int i, j; + int8_t x[16]; + uint8_t a[16], b[16]; + for (i = 0; i < 100; ++i) { + RngSet(x, sizeof(x)); + pabsb(a, x); + (pabsb)(b, x); + ASSERT_EQ(0, memcmp(a, b, 16)); + } +} + +TEST(pabsw, fuzz) { + int i, j; + int16_t x[8]; + uint16_t a[8], b[8]; + for (i = 0; i < 100; ++i) { + RngSet(x, sizeof(x)); + pabsw(a, x); + (pabsw)(b, x); + ASSERT_EQ(0, memcmp(a, b, 16)); + } +} + +TEST(pabsd, fuzz) { + int i, j; + int32_t x[4]; + uint32_t a[4], b[4]; + for (i = 0; i < 100; ++i) { + RngSet(x, sizeof(x)); + pabsd(a, x); + (pabsd)(b, x); + ASSERT_EQ(0, memcmp(a, b, 16)); + } +} + +TEST(psignb, fuzz) { + int i, j; + int8_t x[16], y[16], a[16], b[16]; + for (i = 0; i < 100; ++i) { + RngSet(x, sizeof(x)); + RngSet(y, sizeof(y)); + psignb(a, x, y); + (psignb)(b, x, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + psignb(a, (void *)a, y); + (psignb)(b, (void *)b, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + } +} + +TEST(psignw, fuzz) { + int i, j; + int16_t x[8], y[8], a[8], b[8]; + for (i = 0; i < 100; ++i) { + RngSet(x, sizeof(x)); + RngSet(y, sizeof(y)); + psignw(a, x, y); + (psignw)(b, x, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + psignw(a, (void *)a, y); + (psignw)(b, (void *)b, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + } +} + +TEST(psignd, fuzz) { + int i, j; + int32_t x[4], y[4], a[4], b[4]; + for (i = 0; i < 100; ++i) { + RngSet(x, sizeof(x)); + RngSet(y, sizeof(y)); + psignd(a, x, y); + (psignd)(b, x, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + psignd(a, (void *)a, y); + (psignd)(b, (void *)b, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + } +} + +TEST(paddb, fuzz) { + int i, j; + int8_t x[16], y[16], a[16], b[16]; + for (i = 0; i < 100; ++i) { + RngSet(x, sizeof(x)); + RngSet(y, sizeof(y)); + paddb(a, x, y); + (paddb)(b, x, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + paddb(a, (void *)a, y); + (paddb)(b, (void *)b, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + } +} + +TEST(paddsb, fuzz) { + int i, j; + int8_t x[16], y[16], a[16], b[16]; + for (i = 0; i < 100; ++i) { + RngSet(x, sizeof(x)); + RngSet(y, sizeof(y)); + paddsb(a, x, y); + (paddsb)(b, x, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + paddsb(a, (void *)a, y); + (paddsb)(b, (void *)b, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + } +} + +TEST(paddsw, fuzz) { + int i, j; + int16_t x[8], y[8], a[8], b[8]; + for (i = 0; i < 100; ++i) { + RngSet(x, sizeof(x)); + RngSet(y, sizeof(y)); + paddsw(a, x, y); + (paddsw)(b, x, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + paddsw(a, (void *)a, y); + (paddsw)(b, (void *)b, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + } +} + +TEST(psubsb, fuzz) { + int i, j; + int8_t x[16], y[16], a[16], b[16]; + for (i = 0; i < 100; ++i) { + RngSet(x, sizeof(x)); + RngSet(y, sizeof(y)); + psubsb(a, x, y); + (psubsb)(b, x, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + psubsb(a, (void *)a, y); + (psubsb)(b, (void *)b, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + } +} + +TEST(psubsw, fuzz) { + int i, j; + int16_t x[8], y[8], a[8], b[8]; + for (i = 0; i < 100; ++i) { + RngSet(x, sizeof(x)); + RngSet(y, sizeof(y)); + psubsw(a, x, y); + (psubsw)(b, x, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + psubsw(a, (void *)a, y); + (psubsw)(b, (void *)b, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + } +} + +TEST(paddd, fuzz) { + int i, j; + int32_t x[4], y[4], a[4], b[4]; + for (i = 0; i < 100; ++i) { + RngSet(x, sizeof(x)); + RngSet(y, sizeof(y)); + paddd(a, x, y); + (paddd)(b, x, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + paddd(a, (void *)a, y); + (paddd)(b, (void *)b, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + } +} + +TEST(pshufb, fuzz) { + int i, j; + uint8_t x[16], y[16], a[16], b[16]; + for (i = 0; i < 100; ++i) { + RngSet(x, sizeof(x)); + RngSet(y, sizeof(y)); + pshufb(a, x, y); + (pshufb)(b, x, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + pshufb(a, (void *)a, y); + (pshufb)(b, (void *)b, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + } +} + +TEST(pshufd, fuzz) { + int i, j; + int32_t x[4], a[4], b[4]; + for (i = 0; i < 100; ++i) { + for (j = 0; j < 4; ++j) x[j] = Rando(); +#define T(IMM) \ + pshufd(a, x, IMM); \ + (pshufd)(b, x, IMM); \ + ASSERT_EQ(0, memcmp(a, b, 16)); \ + pshufd(a, (void *)a, IMM); \ + (pshufd)(b, (void *)b, IMM); \ + ASSERT_EQ(0, memcmp(a, b, 16)) + T(0b00000011); + T(0b00000110); + T(0b00001100); + T(0b00011000); + T(0b00110000); + T(0b01100000); + T(0b11000000); + T(0b10000000); +#undef T + } +} + +TEST(pshuflw, fuzz) { + int i, j; + int16_t x[8], a[8], b[8]; + for (i = 0; i < 100; ++i) { + for (j = 0; j < 8; ++j) x[j] = Rando(); +#define T(IMM) \ + pshuflw(a, x, IMM); \ + (pshuflw)(b, x, IMM); \ + ASSERT_EQ(0, memcmp(a, b, 16)); \ + pshuflw(a, (void *)a, IMM); \ + (pshuflw)(b, (void *)b, IMM); \ + ASSERT_EQ(0, memcmp(a, b, 16)) + T(0b00000011); + T(0b00000110); + T(0b00001100); + T(0b00011000); + T(0b00110000); + T(0b01100000); + T(0b11000000); + T(0b10000000); +#undef T + } +} + +TEST(pshufhw, fuzz) { + int i, j; + int16_t x[8], a[8], b[8]; + for (i = 0; i < 100; ++i) { + for (j = 0; j < 8; ++j) x[j] = Rando(); +#define T(IMM) \ + pshufhw(a, x, IMM); \ + (pshufhw)(b, x, IMM); \ + ASSERT_EQ(0, memcmp(a, b, 16)); \ + pshufhw(a, (void *)a, IMM); \ + (pshufhw)(b, (void *)b, IMM); \ + ASSERT_EQ(0, memcmp(a, b, 16)) + T(0b00000011); + T(0b00000110); + T(0b00001100); + T(0b00011000); + T(0b00110000); + T(0b01100000); + T(0b11000000); + T(0b10000000); +#undef T + } +} + +TEST(shufps, fuzz) { + int i, j; + float x[4], a[4], b[4]; + for (i = 0; i < 100; ++i) { + for (j = 0; j < 4; ++j) x[j] = Rando() % INT_MAX; +#define T(IMM) \ + shufps(a, x, IMM); \ + (shufps)(b, x, IMM); \ + ASSERT_EQ(0, memcmp(a, b, 16)); \ + shufps(a, (void *)a, IMM); \ + (shufps)(b, (void *)b, IMM); \ + ASSERT_EQ(0, memcmp(a, b, 16)) + T(0b00000011); + T(0b00000110); + T(0b00001100); + T(0b00011000); + T(0b00110000); + T(0b01100000); + T(0b11000000); + T(0b10000000); +#undef T + } +} + +TEST(shufpd, fuzz) { + int i, j; + double x[2], a[2], b[2]; + for (i = 0; i < 100; ++i) { + for (j = 0; j < 2; ++j) x[j] = Rando() % INT_MAX; +#define T(IMM) \ + shufpd(a, x, IMM); \ + (shufpd)(b, x, IMM); \ + ASSERT_EQ(0, memcmp(a, b, 16)); \ + shufpd(a, (void *)a, IMM); \ + (shufpd)(b, (void *)b, IMM); \ + ASSERT_EQ(0, memcmp(a, b, 16)) + T(0b00000000); + T(0b00000001); + T(0b00000010); + T(0b00000011); +#undef T + } +} + +TEST(packuswb, test) { + const short S[8] = {0, 128, -128, 255, SHRT_MAX, SHRT_MIN, 0, 0}; + unsigned char B[16] = {0}; + packuswb(B, S, S); + ASSERT_EQ(0, B[0]); + ASSERT_EQ(128, B[1]); + ASSERT_EQ(0, B[2]); + ASSERT_EQ(255, B[3]); + ASSERT_EQ(255, B[4]); + ASSERT_EQ(0, B[5]); + ASSERT_EQ(0, B[6]); + ASSERT_EQ(0, B[7]); + ASSERT_EQ(0, B[8]); + ASSERT_EQ(128, B[9]); + ASSERT_EQ(0, B[10]); + ASSERT_EQ(255, B[11]); + ASSERT_EQ(255, B[12]); + ASSERT_EQ(0, B[13]); + ASSERT_EQ(0, B[14]); + ASSERT_EQ(0, B[15]); +} + +TEST(packsswb, test) { + const short S[8] = {0, 128, -128, 255, SHRT_MAX, SHRT_MIN, 0, 0}; + signed char B[16] = {0}; + packsswb(B, S, S); + ASSERT_EQ(0, B[0]); + ASSERT_EQ(127, B[1]); + ASSERT_EQ(-128, B[2]); + ASSERT_EQ(127, B[3]); + ASSERT_EQ(127, B[4]); + ASSERT_EQ(-128, B[5]); + ASSERT_EQ(0, B[6]); + ASSERT_EQ(0, B[7]); + ASSERT_EQ(0, B[8]); + ASSERT_EQ(127, B[9]); + ASSERT_EQ(-128, B[10]); + ASSERT_EQ(127, B[11]); + ASSERT_EQ(127, B[12]); + ASSERT_EQ(-128, B[13]); + ASSERT_EQ(0, B[14]); + ASSERT_EQ(0, B[15]); +} + +TEST(packssdw, testAlias) { + int i, j; + union { + int16_t out[8]; + int32_t in1[4]; + } u; + int16_t a[8], b[8]; + int32_t x[4], y[4]; + for (i = 0; i < 100; ++i) { + RngSet(x, sizeof(x)); + RngSet(y, sizeof(y)); + memcpy(u.in1, x, sizeof(x)); + packssdw(u.out, u.in1, y); + memcpy(a, u.out, sizeof(u.out)); + memcpy(u.in1, x, sizeof(x)); + (packssdw)(u.out, u.in1, y); + memcpy(b, u.out, sizeof(u.out)); + ASSERT_EQ(0, memcmp(a, b, 16)); + } +} + +TEST(packusdw, test) { + int i, j; + int32_t x[4], y[4]; + uint16_t a[8], b[8]; + for (i = 0; i < 100; ++i) { + RngSet(x, sizeof(x)); + RngSet(y, sizeof(y)); + packusdw(a, x, y); + (packusdw)(b, x, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + } +} + +TEST(packuswb, fuzz) { + int i, j; + int16_t x[8], y[8]; + uint8_t a[16], b[16]; + for (i = 0; i < 100; ++i) { + RngSet(x, sizeof(x)); + RngSet(y, sizeof(y)); + packuswb(a, x, y); + (packuswb)(b, x, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + packuswb(a, x, x); + (packuswb)(b, x, x); + ASSERT_EQ(0, memcmp(a, b, 16)); + } +} + +TEST(packssdw, test) { + int i, j; + int32_t x[4], y[4]; + int16_t a[8], b[8]; + for (i = 0; i < 100; ++i) { + RngSet(x, sizeof(x)); + RngSet(y, sizeof(y)); + packssdw(a, x, y); + (packssdw)(b, x, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + } +} + +TEST(psllwv, fuzz) { + int i, j; + uint64_t y[2]; + uint16_t x[8], a[8], b[8]; + for (i = 0; i < 100; ++i) { + for (j = 0; j < 8; ++j) x[j] = Rando(); + for (j = 0; j < 2; ++j) y[j] = Rando() % 64; + psllwv(a, x, y); + (psllwv)(b, x, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + psllwv(a, (void *)a, y); + (psllwv)(b, (void *)b, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + } +} + +TEST(pslldv, fuzz) { + int i, j; + uint64_t y[2]; + uint32_t x[4], a[4], b[4]; + for (i = 0; i < 100; ++i) { + for (j = 0; j < 4; ++j) x[j] = Rando(); + for (j = 0; j < 2; ++j) y[j] = Rando() % 64; + pslldv(a, x, y); + (pslldv)(b, x, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + pslldv(a, (void *)a, y); + (pslldv)(b, (void *)b, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + } +} + +TEST(psllqv, fuzz) { + int i, j; + uint64_t y[2]; + uint64_t x[2], a[2], b[2]; + for (i = 0; i < 100; ++i) { + for (j = 0; j < 2; ++j) x[j] = Rando(); + for (j = 0; j < 2; ++j) y[j] = Rando() % 64; + psllqv(a, x, y); + (psllqv)(b, x, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + psllqv(a, (void *)a, y); + (psllqv)(b, (void *)b, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + } +} + +TEST(psrlwv, fuzz) { + int i, j; + uint64_t y[2]; + uint16_t x[8], a[8], b[8]; + for (i = 0; i < 100; ++i) { + for (j = 0; j < 8; ++j) x[j] = Rando(); + for (j = 0; j < 2; ++j) y[j] = Rando() % 64; + psrlwv(a, x, y); + (psrlwv)(b, x, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + psrlwv(a, (void *)a, y); + (psrlwv)(b, (void *)b, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + } +} + +TEST(psrldv, fuzz) { + int i, j; + uint64_t y[2]; + uint32_t x[4], a[4], b[4]; + for (i = 0; i < 100; ++i) { + for (j = 0; j < 4; ++j) x[j] = Rando(); + for (j = 0; j < 2; ++j) y[j] = Rando() % 64; + psrldv(a, x, y); + (psrldv)(b, x, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + psrldv(a, (void *)a, y); + (psrldv)(b, (void *)b, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + } +} + +TEST(psrlqv, fuzz) { + int i, j; + uint64_t y[2]; + uint64_t x[2], a[2], b[2]; + for (i = 0; i < 100; ++i) { + for (j = 0; j < 2; ++j) x[j] = Rando(); + for (j = 0; j < 2; ++j) y[j] = Rando() % 64; + psrlqv(a, x, y); + (psrlqv)(b, x, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + psrlqv(a, (void *)a, y); + (psrlqv)(b, (void *)b, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + } +} + +TEST(psrawv, fuzz) { + int i, j; + uint64_t y[2]; + int16_t x[8], a[8], b[8]; + for (i = 0; i < 100; ++i) { + for (j = 0; j < 8; ++j) x[j] = Rando(); + for (j = 0; j < 2; ++j) y[j] = Rando() % 64; + psrawv(a, x, y); + (psrawv)(b, x, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + psrawv(a, (void *)a, y); + (psrawv)(b, (void *)b, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + } +} + +TEST(psradv, fuzz) { + int i, j; + uint64_t y[2]; + int32_t x[4], a[4], b[4]; + for (i = 0; i < 100; ++i) { + for (j = 0; j < 4; ++j) x[j] = Rando(); + for (j = 0; j < 2; ++j) y[j] = Rando() % 64; + psradv(a, x, y); + (psradv)(b, x, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + psradv(a, (void *)a, y); + (psradv)(b, (void *)b, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + } +} + +TEST(psrldq, fuzz) { + int i, n; + uint8_t x[16], a[16], b[16]; + for (i = 0; i < 100; ++i) { + memset(a, -1, sizeof(a)); + memset(b, -1, sizeof(b)); + RngSet(x, sizeof(x)); + n = Rando() % 20; + psrldq(a, x, n); + (psrldq)(b, x, n); + ASSERT_EQ(0, memcmp(a, b, 16), "%d\n\t%`#.16s\n\t%`#.16s\n\t%`#.16s", n, x, + a, b); + n = Rando() % 20; + psrldq(a, a, n); + (psrldq)(b, b, n); + ASSERT_EQ(0, memcmp(a, b, 16), "%d\n\t%`#.16s\n\t%`#.16s\n\t%`#.16s", n, x, + a, b); + } +} + +TEST(pslldq, fuzz) { + int i, n; + uint8_t x[16], a[16], b[16]; + for (i = 0; i < 100; ++i) { + memset(a, -1, sizeof(a)); + memset(b, -1, sizeof(b)); + RngSet(x, sizeof(x)); + n = Rando() % 20; + pslldq(a, x, n); + (pslldq)(b, x, n); + ASSERT_EQ(0, memcmp(a, b, 16), "%d\n\t%`#.16s\n\t%`#.16s\n\t%`#.16s", n, x, + a, b); + n = Rando() % 20; + pslldq(a, a, n); + (pslldq)(b, b, n); + ASSERT_EQ(0, memcmp(a, b, 16), "%d\n\t%`#.16s\n\t%`#.16s\n\t%`#.16s", n, x, + a, b); + } +} + +TEST(psadbw, test) { + int i, j; + uint64_t a[2], b[2]; + uint8_t x[16], y[16]; + for (i = 0; i < 100; ++i) { + RngSet(x, sizeof(x)); + RngSet(y, sizeof(y)); + psadbw(a, x, y); + (psadbw)(b, x, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + } +} + +TEST(pmulhrsw, fuzz) { + int i, j; + int16_t x[8], y[8], a[8], b[8]; + for (i = 0; i < 1000; ++i) { + RngSet(x, sizeof(x)); + RngSet(y, sizeof(y)); + pmulhrsw(a, x, y); + (pmulhrsw)(b, x, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + pmulhrsw(a, (void *)a, y); + (pmulhrsw)(b, (void *)b, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + } +} + +TEST(pmaddubsw, fuzz) { + int i, j; + int8_t y[16]; + uint8_t x[16]; + int16_t a[8], b[8]; + for (i = 0; i < 1000; ++i) { + RngSet(x, sizeof(x)); + RngSet(y, sizeof(y)); + pmaddubsw(a, x, y); + (pmaddubsw)(b, x, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + pmaddubsw(a, (void *)a, y); + (pmaddubsw)(b, (void *)b, y); + ASSERT_EQ(0, memcmp(a, b, 16)); + } +} + +TEST(pshufw, fuzz) { + int i, j; + uint8_t y; + int16_t x[4], a[4], b[4]; + for (i = 0; i < 100; ++i) { + for (j = 0; j < 4; ++j) x[j] = Rando(); + pshufw(a, x, 0b10111111); + (pshufw)(b, x, 0b10111111); + ASSERT_EQ(0, memcmp(a, b, 8)); + pshufw(a, (void *)a, 0b10111111); + (pshufw)(b, (void *)b, 0b10111111); + ASSERT_EQ(0, memcmp(a, b, 8)); + pshufw(a, x, 0b00001000); + (pshufw)(b, x, 0b00001000); + ASSERT_EQ(0, memcmp(a, b, 8)); + pshufw(a, x, 0b00010001); + (pshufw)(b, x, 0b00010001); + ASSERT_EQ(0, memcmp(a, b, 8)); + pshufw(a, x, 0b01110100); + (pshufw)(b, x, 0b01110100); + ASSERT_EQ(0, memcmp(a, b, 8)); + pshufw(a, x, 0b01101101); + (pshufw)(b, x, 0b01101101); + ASSERT_EQ(0, memcmp(a, b, 8)); + pshufw(a, x, 0b10011011); + (pshufw)(b, x, 0b10011011); + ASSERT_EQ(0, memcmp(a, b, 8)); + pshufw(a, x, 0b10111000); + (pshufw)(b, x, 0b10111000); + ASSERT_EQ(0, memcmp(a, b, 8)); + pshufw(a, x, 0b11000111); + (pshufw)(b, x, 0b11000111); + ASSERT_EQ(0, memcmp(a, b, 8)); + } +} + +TEST(pcmpeqw, test2) { + int16_t kNumbers16[] = {0, 1, 2, 123, 0xffff, 0xfffe, 0x8000, 0x8001, 0x8080}; + int i, j, k; + int16_t a[8], b[8], x[8], y[8]; + for (i = 0; i < ARRAYLEN(kNumbers16); ++i) { + for (j = 0; j < ARRAYLEN(kNumbers16); ++j) { + for (k = 0; k < 8; ++k) { + x[k] = kNumbers16[(i + k) % ARRAYLEN(kNumbers16)]; + y[k] = kNumbers16[(j + k) % ARRAYLEN(kNumbers16)]; + } + pcmpeqw(a, x, y); + (pcmpeqw)(b, x, y); + EXPECT_EQ(0, memcmp(a, b, 16)); + } + } +} + +BENCH(psrldq, bench) { + volatile uint8_t A[16]; + volatile uint8_t B[16]; + EZBENCH2("psrldq const 𝑖", donothing, PROGN(psrldq(A, B, 7))); + EZBENCH2("psrldq var 𝑖", donothing, PROGN(psrldq(A, B, VEIL("r", 7)))); + EZBENCH2("psrldq ansi", donothing, PROGN((psrldq)(A, B, 7))); +} + +BENCH(pslldq, bench) { + volatile uint8_t A[16]; + volatile uint8_t B[16]; + EZBENCH2("pslldq const 𝑖", donothing, PROGN(pslldq(A, B, 7))); + EZBENCH2("pslldq var 𝑖", donothing, PROGN(pslldq(A, B, VEIL("r", 7)))); + EZBENCH2("pslldq ansi", donothing, PROGN((pslldq)(A, B, 7))); +} diff --git a/test/libc/intrin/palignr_test.c b/test/libc/intrin/palignr_test.c index 7d2b9249..7374a76b 100644 --- a/test/libc/intrin/palignr_test.c +++ b/test/libc/intrin/palignr_test.c @@ -17,71 +17,216 @@ │ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/bits/progn.h" #include "libc/intrin/palignr.h" +#include "libc/rand/rand.h" +#include "libc/testlib/ezbench.h" #include "libc/testlib/testlib.h" -const int A[4] = {1, 2, 3, 4}; -const int B[4] = {5, 6, 7, 8}; - TEST(palignr, testLeftpad) { + const int A[4] = {1, 2, 3, 4}; + const int B[4] = {5, 6, 7, 8}; int C[4] = {0}; palignr(C, B, A, 12); - EXPECT_EQ(4, C[0]); - EXPECT_EQ(5, C[1]); - EXPECT_EQ(6, C[2]); - EXPECT_EQ(7, C[3]); + ASSERT_EQ(4, C[0]); + ASSERT_EQ(5, C[1]); + ASSERT_EQ(6, C[2]); + ASSERT_EQ(7, C[3]); +} + +TEST(palignr, testLeftpad_variableImmediate) { + const int A[4] = {1, 2, 3, 4}; + const int B[4] = {5, 6, 7, 8}; + int C[4] = {0}; + palignr(C, B, A, VEIL("r", 12)); + ASSERT_EQ(4, C[0]); + ASSERT_EQ(5, C[1]); + ASSERT_EQ(6, C[2]); + ASSERT_EQ(7, C[3]); } TEST(palignr, test0) { + const int A[4] = {1, 2, 3, 4}; + const int B[4] = {5, 6, 7, 8}; int C[4]; palignr(C, B, A, 0); - EXPECT_EQ(1, C[0]); - EXPECT_EQ(2, C[1]); - EXPECT_EQ(3, C[2]); - EXPECT_EQ(4, C[3]); + ASSERT_EQ(1, C[0]); + ASSERT_EQ(2, C[1]); + ASSERT_EQ(3, C[2]); + ASSERT_EQ(4, C[3]); } TEST(palignr, test4) { + const int A[4] = {1, 2, 3, 4}; + const int B[4] = {5, 6, 7, 8}; int C[4]; palignr(C, B, A, 4); - EXPECT_EQ(2, C[0]); - EXPECT_EQ(3, C[1]); - EXPECT_EQ(4, C[2]); - EXPECT_EQ(5, C[3]); + ASSERT_EQ(2, C[0]); + ASSERT_EQ(3, C[1]); + ASSERT_EQ(4, C[2]); + ASSERT_EQ(5, C[3]); } TEST(palignr, test12) { + const int A[4] = {1, 2, 3, 4}; + const int B[4] = {5, 6, 7, 8}; int C[4]; palignr(C, B, A, 12); - EXPECT_EQ(4, C[0]); - EXPECT_EQ(5, C[1]); - EXPECT_EQ(6, C[2]); - EXPECT_EQ(7, C[3]); + ASSERT_EQ(4, C[0]); + ASSERT_EQ(5, C[1]); + ASSERT_EQ(6, C[2]); + ASSERT_EQ(7, C[3]); } TEST(palignr, test16) { + const int A[4] = {1, 2, 3, 4}; + const int B[4] = {5, 6, 7, 8}; int C[4]; palignr(C, B, A, 16); - EXPECT_EQ(5, C[0]); - EXPECT_EQ(6, C[1]); - EXPECT_EQ(7, C[2]); - EXPECT_EQ(8, C[3]); + ASSERT_EQ(5, C[0]); + ASSERT_EQ(6, C[1]); + ASSERT_EQ(7, C[2]); + ASSERT_EQ(8, C[3]); } TEST(palignr, test20) { + const int A[4] = {1, 2, 3, 4}; + const int B[4] = {5, 6, 7, 8}; int C[4] = {-1, -1, -1, -1}; palignr(C, B, A, 20); - EXPECT_EQ(6, C[0]); - EXPECT_EQ(7, C[1]); - EXPECT_EQ(8, C[2]); - EXPECT_EQ(0, C[3]); + ASSERT_EQ(6, C[0]); + ASSERT_EQ(7, C[1]); + ASSERT_EQ(8, C[2]); + ASSERT_EQ(0, C[3]); } -TEST(palignr, test32) { +TEST(palignrc, testLeftpad) { + const int A[4] = {1, 2, 3, 4}; + const int B[4] = {5, 6, 7, 8}; + int C[4] = {0}; + (palignr)(C, B, A, 12); + ASSERT_EQ(4, C[0]); + ASSERT_EQ(5, C[1]); + ASSERT_EQ(6, C[2]); + ASSERT_EQ(7, C[3]); +} + +TEST(palignrc, testLeftpad_variableImmediate) { + const int A[4] = {1, 2, 3, 4}; + const int B[4] = {5, 6, 7, 8}; + int C[4] = {0}; + (palignr)(C, B, A, VEIL("r", 12)); + ASSERT_EQ(4, C[0]); + ASSERT_EQ(5, C[1]); + ASSERT_EQ(6, C[2]); + ASSERT_EQ(7, C[3]); +} + +TEST(palignrc, test0) { + const int A[4] = {1, 2, 3, 4}; + const int B[4] = {5, 6, 7, 8}; + int C[4]; + (palignr)(C, B, A, 0); + ASSERT_EQ(1, C[0]); + ASSERT_EQ(2, C[1]); + ASSERT_EQ(3, C[2]); + ASSERT_EQ(4, C[3]); +} + +TEST(palignrc, test4) { + const int A[4] = {1, 2, 3, 4}; + const int B[4] = {5, 6, 7, 8}; + int C[4]; + (palignr)(C, B, A, 4); + ASSERT_EQ(2, C[0]); + ASSERT_EQ(3, C[1]); + ASSERT_EQ(4, C[2]); + ASSERT_EQ(5, C[3]); +} + +TEST(palignrc, test12) { + const int A[4] = {1, 2, 3, 4}; + const int B[4] = {5, 6, 7, 8}; + int C[4]; + (palignr)(C, B, A, 12); + ASSERT_EQ(4, C[0]); + ASSERT_EQ(5, C[1]); + ASSERT_EQ(6, C[2]); + ASSERT_EQ(7, C[3]); +} + +TEST(palignrc, test16) { + const int A[4] = {1, 2, 3, 4}; + const int B[4] = {5, 6, 7, 8}; + int C[4]; + (palignr)(C, B, A, 16); + ASSERT_EQ(5, C[0]); + ASSERT_EQ(6, C[1]); + ASSERT_EQ(7, C[2]); + ASSERT_EQ(8, C[3]); +} + +TEST(palignrc, test20) { + const int A[4] = {1, 2, 3, 4}; + const int B[4] = {5, 6, 7, 8}; + int C[4] = {-1, -1, -1, -1}; + (palignr)(C, B, A, 20); + ASSERT_EQ(6, C[0]); + ASSERT_EQ(7, C[1]); + ASSERT_EQ(8, C[2]); + ASSERT_EQ(0, C[3]); +} + +TEST(palignr, test32orHigher_clearsOutput) { + const int A[4] = {1, 2, 3, 4}; + const int B[4] = {5, 6, 7, 8}; int C[4] = {-1, -1, -1, -1}; palignr(C, B, A, 32); - EXPECT_EQ(0, C[0]); - EXPECT_EQ(0, C[1]); - EXPECT_EQ(0, C[2]); - EXPECT_EQ(0, C[3]); + ASSERT_EQ(0, C[0]); + ASSERT_EQ(0, C[1]); + ASSERT_EQ(0, C[2]); + ASSERT_EQ(0, C[3]); + C[0] = 43; + palignr(C, B, A, 123); + ASSERT_EQ(0, C[0]); + ASSERT_EQ(0, C[1]); + ASSERT_EQ(0, C[2]); + ASSERT_EQ(0, C[3]); +} + +TEST(palignrv, test32orHigher_clearsOutput) { + const int A[4] = {1, 2, 3, 4}; + const int B[4] = {5, 6, 7, 8}; + int C[4] = {-1, -1, -1, -1}; + palignr(C, B, A, VEIL("r", 32)); + ASSERT_EQ(0, C[0]); + ASSERT_EQ(0, C[1]); + ASSERT_EQ(0, C[2]); + ASSERT_EQ(0, C[3]); + C[0] = 43; + palignr(C, B, A, VEIL("r", 123)); + ASSERT_EQ(0, C[0]); + ASSERT_EQ(0, C[1]); + ASSERT_EQ(0, C[2]); + ASSERT_EQ(0, C[3]); +} + +TEST(palignrc, test32orHigher_clearsOutput) { + const int A[4] = {1, 2, 3, 4}; + const int B[4] = {5, 6, 7, 8}; + int C[4] = {-1, -1, -1, -1}; + (palignr)(C, B, A, 32); + ASSERT_EQ(0, C[0]); + ASSERT_EQ(0, C[1]); + ASSERT_EQ(0, C[2]); + ASSERT_EQ(0, C[3]); +} + +BENCH(palignr, bench) { + volatile __intrin_xmm_t A; + volatile __intrin_xmm_t B; + EZBENCH2("palignr const 𝑖", donothing, PROGN(palignr(&A, &A, &B, 7))); + EZBENCH2("palignr var 𝑖", donothing, + PROGN(palignr(&A, &A, &B, VEIL("r", 7)))); + EZBENCH2("palignr ansi", donothing, PROGN((palignr)(&A, &A, &B, 7))); } diff --git a/test/libc/intrin/pshuf_test.c b/test/libc/intrin/pshuf_test.c new file mode 100644 index 00000000..e1e361b1 --- /dev/null +++ b/test/libc/intrin/pshuf_test.c @@ -0,0 +1,162 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/pshufd.h" +#include "libc/intrin/pshufhw.h" +#include "libc/intrin/pshuflw.h" +#include "libc/rand/rand.h" +#include "libc/str/str.h" +#include "libc/testlib/testlib.h" + +#define T(f, m) \ + f(a, x, m); \ + (f)(b, x, m); \ + EXPECT_EQ(0, memcmp(a, b, 16)) + +TEST(pshuflw, test) { + int i, j; + int16_t x[8], a[8], b[8]; + for (i = 0; i < 10; ++i) { + for (j = 0; j < 8; ++j) x[j] = rand(); + T(pshuflw, 0b00000000); + T(pshuflw, 0b00000001); + T(pshuflw, 0b00000011); + T(pshuflw, 0b00001100); + T(pshuflw, 0b00001101); + T(pshuflw, 0b00001111); + T(pshuflw, 0b00110000); + T(pshuflw, 0b00110001); + T(pshuflw, 0b00110011); + T(pshuflw, 0b00111100); + T(pshuflw, 0b00111101); + T(pshuflw, 0b00111111); + T(pshuflw, 0b01000000); + T(pshuflw, 0b01000001); + T(pshuflw, 0b01000011); + T(pshuflw, 0b01001100); + T(pshuflw, 0b01001101); + T(pshuflw, 0b01001111); + T(pshuflw, 0b01110000); + T(pshuflw, 0b01110001); + T(pshuflw, 0b01110011); + T(pshuflw, 0b01111100); + T(pshuflw, 0b01111101); + T(pshuflw, 0b01111111); + T(pshuflw, 0b11000000); + T(pshuflw, 0b11000001); + T(pshuflw, 0b11000011); + T(pshuflw, 0b11001100); + T(pshuflw, 0b11001101); + T(pshuflw, 0b11001111); + T(pshuflw, 0b11110000); + T(pshuflw, 0b11110001); + T(pshuflw, 0b11110011); + T(pshuflw, 0b11111100); + T(pshuflw, 0b11111101); + T(pshuflw, 0b11111111); + } +} + +TEST(pshufhw, test) { + int i, j; + int16_t x[8], a[8], b[8]; + for (i = 0; i < 10; ++i) { + for (j = 0; j < 8; ++j) x[j] = rand(); + T(pshufhw, 0b00000000); + T(pshufhw, 0b00000001); + T(pshufhw, 0b00000011); + T(pshufhw, 0b00001100); + T(pshufhw, 0b00001101); + T(pshufhw, 0b00001111); + T(pshufhw, 0b00110000); + T(pshufhw, 0b00110001); + T(pshufhw, 0b00110011); + T(pshufhw, 0b00111100); + T(pshufhw, 0b00111101); + T(pshufhw, 0b00111111); + T(pshufhw, 0b01000000); + T(pshufhw, 0b01000001); + T(pshufhw, 0b01000011); + T(pshufhw, 0b01001100); + T(pshufhw, 0b01001101); + T(pshufhw, 0b01001111); + T(pshufhw, 0b01110000); + T(pshufhw, 0b01110001); + T(pshufhw, 0b01110011); + T(pshufhw, 0b01111100); + T(pshufhw, 0b01111101); + T(pshufhw, 0b01111111); + T(pshufhw, 0b11000000); + T(pshufhw, 0b11000001); + T(pshufhw, 0b11000011); + T(pshufhw, 0b11001100); + T(pshufhw, 0b11001101); + T(pshufhw, 0b11001111); + T(pshufhw, 0b11110000); + T(pshufhw, 0b11110001); + T(pshufhw, 0b11110011); + T(pshufhw, 0b11111100); + T(pshufhw, 0b11111101); + T(pshufhw, 0b11111111); + } +} + +TEST(pshufd, test) { + int i, j; + int32_t x[4], a[4], b[4]; + for (i = 0; i < 10; ++i) { + for (j = 0; j < 4; ++j) x[j] = rand(); + T(pshufd, 0b00000000); + T(pshufd, 0b00000001); + T(pshufd, 0b00000011); + T(pshufd, 0b00001100); + T(pshufd, 0b00001101); + T(pshufd, 0b00001111); + T(pshufd, 0b00110000); + T(pshufd, 0b00110001); + T(pshufd, 0b00110011); + T(pshufd, 0b00111100); + T(pshufd, 0b00111101); + T(pshufd, 0b00111111); + T(pshufd, 0b01000000); + T(pshufd, 0b01000001); + T(pshufd, 0b01000011); + T(pshufd, 0b01001100); + T(pshufd, 0b01001101); + T(pshufd, 0b01001111); + T(pshufd, 0b01110000); + T(pshufd, 0b01110001); + T(pshufd, 0b01110011); + T(pshufd, 0b01111100); + T(pshufd, 0b01111101); + T(pshufd, 0b01111111); + T(pshufd, 0b11000000); + T(pshufd, 0b11000001); + T(pshufd, 0b11000011); + T(pshufd, 0b11001100); + T(pshufd, 0b11001101); + T(pshufd, 0b11001111); + T(pshufd, 0b11110000); + T(pshufd, 0b11110001); + T(pshufd, 0b11110011); + T(pshufd, 0b11111100); + T(pshufd, 0b11111101); + T(pshufd, 0b11111111); + } +} diff --git a/test/libc/intrin/test.mk b/test/libc/intrin/test.mk index 3a714a8c..aad71604 100644 --- a/test/libc/intrin/test.mk +++ b/test/libc/intrin/test.mk @@ -5,12 +5,14 @@ PKGS += TEST_LIBC_INTRIN TEST_LIBC_INTRIN_SRCS := $(wildcard test/libc/intrin/*.c) TEST_LIBC_INTRIN_SRCS_TEST = $(filter %_test.c,$(TEST_LIBC_INTRIN_SRCS)) -TEST_LIBC_INTRIN_COMS = $(TEST_LIBC_INTRIN_OBJS:%.o=%.com) TEST_LIBC_INTRIN_OBJS = \ $(TEST_LIBC_INTRIN_SRCS:%=o/$(MODE)/%.zip.o) \ $(TEST_LIBC_INTRIN_SRCS:%.c=o/$(MODE)/%.o) +TEST_LIBC_INTRIN_COMS = \ + $(TEST_LIBC_INTRIN_SRCS:%.c=o/$(MODE)/%.com) + TEST_LIBC_INTRIN_BINS = \ $(TEST_LIBC_INTRIN_COMS) \ $(TEST_LIBC_INTRIN_COMS:%=%.dbg) @@ -22,12 +24,17 @@ TEST_LIBC_INTRIN_CHECKS = \ $(TEST_LIBC_INTRIN_SRCS_TEST:%.c=o/$(MODE)/%.com.runs) TEST_LIBC_INTRIN_DIRECTDEPS = \ + LIBC_FMT \ LIBC_INTRIN \ LIBC_NEXGEN32E \ + LIBC_RAND \ + LIBC_LOG \ LIBC_STUBS \ + LIBC_TESTLIB \ LIBC_TINYMATH \ - TOOL_VIZ_LIB \ - LIBC_TESTLIB + LIBC_RUNTIME \ + LIBC_X \ + TOOL_VIZ_LIB TEST_LIBC_INTRIN_DEPS := \ $(call uniq,$(foreach x,$(TEST_LIBC_INTRIN_DIRECTDEPS),$($(x)))) diff --git a/test/libc/math/ilogb_test.c b/test/libc/math/ilogb_test.c new file mode 100644 index 00000000..20f2a687 --- /dev/null +++ b/test/libc/math/ilogb_test.c @@ -0,0 +1,29 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/math.h" +#include "libc/testlib/testlib.h" + +TEST(ilogb, test) { + EXPECT_EQ(0x7fffffff, ilogb(INFINITY)); + EXPECT_EQ(0, ilogb(1)); + EXPECT_EQ(1, ilogb(2)); + EXPECT_EQ(2, ilogb(4)); + EXPECT_EQ(63, ilogb(1e19)); +} diff --git a/test/libc/math/test.mk b/test/libc/math/test.mk index e40458ed..56ee4e0d 100644 --- a/test/libc/math/test.mk +++ b/test/libc/math/test.mk @@ -5,12 +5,14 @@ PKGS += TEST_LIBC_MATH TEST_LIBC_MATH_SRCS := $(wildcard test/libc/math/*.c) TEST_LIBC_MATH_SRCS_TEST = $(filter %_test.c,$(TEST_LIBC_MATH_SRCS)) -TEST_LIBC_MATH_COMS = $(TEST_LIBC_MATH_OBJS:%.o=%.com) TEST_LIBC_MATH_OBJS = \ $(TEST_LIBC_MATH_SRCS:%=o/$(MODE)/%.zip.o) \ $(TEST_LIBC_MATH_SRCS:%.c=o/$(MODE)/%.o) +TEST_LIBC_MATH_COMS = \ + $(TEST_LIBC_MATH_SRCS:%.c=o/$(MODE)/%.com) + TEST_LIBC_MATH_BINS = \ $(TEST_LIBC_MATH_COMS) \ $(TEST_LIBC_MATH_COMS:%=%.dbg) @@ -23,7 +25,7 @@ TEST_LIBC_MATH_CHECKS = \ TEST_LIBC_MATH_DIRECTDEPS = \ LIBC_CALLS_HEFTY \ LIBC_FMT \ - LIBC_TINYMATH \ + LIBC_MATH \ LIBC_MEM \ LIBC_RUNTIME \ LIBC_STUBS \ diff --git a/test/libc/mem/malloc_test.c b/test/libc/mem/malloc_test.c index 6d2af58a..b0e65e8b 100644 --- a/test/libc/mem/malloc_test.c +++ b/test/libc/mem/malloc_test.c @@ -18,7 +18,59 @@ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/bits/bits.h" +#include "libc/bits/safemacros.h" +#include "libc/calls/calls.h" +#include "libc/calls/struct/stat.h" +#include "libc/macros.h" #include "libc/mem/mem.h" +#include "libc/rand/rand.h" +#include "libc/runtime/memtrack.h" +#include "libc/runtime/runtime.h" +#include "libc/stdio/stdio.h" +#include "libc/str/str.h" +#include "libc/sysv/consts/map.h" +#include "libc/sysv/consts/o.h" +#include "libc/sysv/consts/prot.h" #include "libc/testlib/testlib.h" -TEST(malloc, test) { free(malloc(123)); } +#define N 1024 +#define M 20 + +TEST(malloc, test) { + static struct stat st; + static volatile int i, j, k, *A[4096], fds[M], *maps[M], mapsizes[M]; + memset(fds, -1, sizeof(fds)); + memset(maps, -1, sizeof(maps)); + for (i = 0; i < N * M; ++i) { + j = rand() % ARRAYLEN(A); + if (A[j]) { + ASSERT_EQ(j, A[j][0]); + A[j] = realloc(A[j], max(sizeof(int), rand() % N)); + ASSERT_NE(NULL, A[j]); + ASSERT_EQ(j, A[j][0]); + free(A[j]); + A[j] = NULL; + } else { + A[j] = malloc(max(sizeof(int), rand() % N)); + ASSERT_NE(NULL, A[j]); + A[j][0] = j; + } + if (i % M == 0) { + k = rand() % M; + if (fds[k] == -1) { + ASSERT_NE(-1, (fds[k] = open(program_invocation_name, O_RDONLY))); + ASSERT_NE(-1, fstat(fds[k], &st)); + ASSERT_NE(MAP_FAILED, + (maps[k] = mmap(NULL, (mapsizes[k] = st.st_size), PROT_READ, + MAP_SHARED, fds[k], 0))); + } else { + ASSERT_NE(-1, munmap(maps[k], mapsizes[k])); + ASSERT_NE(-1, close(fds[k])); + fds[k] = -1; + } + } + } + for (i = 0; i < ARRAYLEN(A); ++i) free(A[i]); + for (i = 0; i < ARRAYLEN(maps); ++i) munmap(maps[i], mapsizes[i]); + for (i = 0; i < ARRAYLEN(fds); ++i) close(fds[i]); +} diff --git a/test/libc/mem/test.mk b/test/libc/mem/test.mk index 56dfe494..342c1c22 100644 --- a/test/libc/mem/test.mk +++ b/test/libc/mem/test.mk @@ -5,12 +5,14 @@ PKGS += TEST_LIBC_MEM TEST_LIBC_MEM_SRCS := $(wildcard test/libc/mem/*.c) TEST_LIBC_MEM_SRCS_TEST = $(filter %_test.c,$(TEST_LIBC_MEM_SRCS)) -TEST_LIBC_MEM_COMS = $(TEST_LIBC_MEM_OBJS:%.o=%.com) TEST_LIBC_MEM_OBJS = \ $(TEST_LIBC_MEM_SRCS:%=o/$(MODE)/%.zip.o) \ $(TEST_LIBC_MEM_SRCS:%.c=o/$(MODE)/%.o) +TEST_LIBC_MEM_COMS = \ + $(TEST_LIBC_MEM_SRCS:%.c=o/$(MODE)/%.com) + TEST_LIBC_MEM_BINS = \ $(TEST_LIBC_MEM_COMS) \ $(TEST_LIBC_MEM_COMS:%=%.dbg) @@ -22,7 +24,14 @@ TEST_LIBC_MEM_CHECKS = \ TEST_LIBC_MEM_DIRECTDEPS = \ LIBC_MEM \ + LIBC_CALLS \ LIBC_STUBS \ + LIBC_NEXGEN32E \ + LIBC_SYSV \ + LIBC_FMT \ + LIBC_RUNTIME \ + LIBC_STDIO \ + LIBC_RAND \ LIBC_TESTLIB TEST_LIBC_MEM_DEPS := \ diff --git a/test/libc/nexgen32e/crc32_test.c b/test/libc/nexgen32e/crc32_test.c index d4a9e186..44374106 100644 --- a/test/libc/nexgen32e/crc32_test.c +++ b/test/libc/nexgen32e/crc32_test.c @@ -18,25 +18,40 @@ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/macros.h" +#include "libc/nexgen32e/crc32.h" #include "libc/nexgen32e/x86feature.h" +#include "libc/str/knuthmultiplicativehash.h" #include "libc/str/str.h" +#include "libc/testlib/ezbench.h" #include "libc/testlib/hyperion.h" #include "libc/testlib/testlib.h" - -uint32_t crc32(uint32_t, const void *, int); -uint32_t crc32$pclmul(uint32_t, const void *, size_t); -uint32_t crc32$pclmul2(uint32_t, const void *, size_t); +#include "third_party/zlib/zlib.h" TEST(crc32, testBigText) { size_t size; + void *hyperion; size = kHyperionSize; - EXPECT_EQ(0xe9ded8e6, crc32(0, kHyperion, size)); - EXPECT_EQ(0xe9ded8e6, crc32_z(0, kHyperion, size)); + hyperion = kHyperion; + EXPECT_EQ(0xe9ded8e6, crc32(0, hyperion, size)); + EXPECT_EQ(0xe9ded8e6, crc32_z(0, hyperion, size)); if (X86_HAVE(PCLMUL)) { size = ROUNDDOWN(size, 64); - EXPECT_EQ(0xc7adc04f, crc32(0, kHyperion, size)); - EXPECT_EQ(0xc7adc04f, crc32_z(0, kHyperion, size)); + EXPECT_EQ(0xc7adc04f, crc32(0, hyperion, size)); + EXPECT_EQ(0xc7adc04f, crc32_z(0, hyperion, size)); EXPECT_EQ(0xc7adc04f, - 0xffffffffu ^ crc32$pclmul(0 ^ 0xffffffffu, kHyperion, size)); + 0xffffffffu ^ crc32$pclmul(0 ^ 0xffffffffu, hyperion, size)); } } + +#define TESTSTR "libc/calls/typedef/sighandler_t.h" + +BENCH(crc32c, bench) { + EZBENCH2("crc32c", donothing, + EXPROPRIATE(crc32c(0, VEIL("r", TESTSTR), sizeof(TESTSTR) - 1))); +} + +BENCH(KnuthMultiplicativeHash32, bench) { + EZBENCH2("KMP", donothing, + EXPROPRIATE(KnuthMultiplicativeHash32(VEIL("r", TESTSTR), + sizeof(TESTSTR) - 1))); +} diff --git a/test/libc/nexgen32e/kcp437_test.c b/test/libc/nexgen32e/kcp437_test.c new file mode 100644 index 00000000..2f8b2a27 --- /dev/null +++ b/test/libc/nexgen32e/kcp437_test.c @@ -0,0 +1,30 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/nexgen32e/nexgen32e.h" +#include "libc/str/str.h" +#include "libc/testlib/testlib.h" +#include "libc/unicode/unicode.h" + +TEST(kcp437, test) { + long i; + for (i = 0; i < 256; ++i) { + EXPECT_EQ(1, wcwidth(kCp437[i]), "%lc", kCp437[i]); + } +} diff --git a/test/libc/nexgen32e/memeqmask_test.c b/test/libc/nexgen32e/memeqmask_test.c index 74dacca0..900fbbd8 100644 --- a/test/libc/nexgen32e/memeqmask_test.c +++ b/test/libc/nexgen32e/memeqmask_test.c @@ -20,7 +20,6 @@ #include "libc/bits/bits.h" #include "libc/runtime/buffer.h" #include "libc/runtime/gc.h" -#include "libc/runtime/mappings.h" #include "libc/stdio/stdio.h" #include "libc/str/str.h" #include "libc/testlib/testlib.h" diff --git a/test/libc/str/memmove_test.c b/test/libc/nexgen32e/memmove_test.c similarity index 87% rename from test/libc/str/memmove_test.c rename to test/libc/nexgen32e/memmove_test.c index 8c821f0f..156ba766 100644 --- a/test/libc/str/memmove_test.c +++ b/test/libc/nexgen32e/memmove_test.c @@ -25,12 +25,15 @@ #define N 256 #define S 7 +long i, j, n; +char *b1, *b2; + TEST(memmove, overlapping) { - for (size_t i = 0; i < N; i += S) { - for (size_t j = 10; j < N; j += S) { - char *b1 = tmalloc(N); - char *b2 = tmalloc(N); - size_t n = min(N - i, N - j); + for (i = 0; i < N; i += S) { + for (j = 10; j < N; j += S) { + b1 = tmalloc(N); + b2 = tmalloc(N); + n = min(N - i, N - j); memcpy(b2, b1 + i, n); ASSERT_EQ(b1 + j, memmove(b1 + j, b1 + i, n)); ASSERT_EQ(0, memcmp(b1 + j, b2, n)); @@ -41,11 +44,11 @@ TEST(memmove, overlapping) { } TEST(memmove, overlappingDirect) { - for (size_t i = 0; i < N; i += S) { - for (size_t j = 10; j < N; j += S) { - char *b1 = tmalloc(N); - char *b2 = tmalloc(N); - size_t n = min(N - i, N - j); + for (i = 0; i < N; i += S) { + for (j = 10; j < N; j += S) { + b1 = tmalloc(N); + b2 = tmalloc(N); + n = min(N - i, N - j); memcpy(b2, b1 + i, n); ASSERT_EQ(b1 + j, (memmove)(b1 + j, b1 + i, n)); ASSERT_EQ(0, memcmp(b1 + j, b2, n)); diff --git a/test/libc/intrin/packuswb_test.c b/test/libc/nexgen32e/memset_test.c similarity index 60% rename from test/libc/intrin/packuswb_test.c rename to test/libc/nexgen32e/memset_test.c index bbd4eef9..99842f56 100644 --- a/test/libc/intrin/packuswb_test.c +++ b/test/libc/nexgen32e/memset_test.c @@ -17,54 +17,55 @@ │ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/intrin/packsswb.h" -#include "libc/intrin/packuswb.h" -#include "libc/limits.h" -#include "libc/nexgen32e/kcpuids.h" -#include "libc/str/str.h" +#include "libc/nexgen32e/nexgen32e.h" #include "libc/testlib/testlib.h" -const short S[8] = {0, 128, -128, 255, SHRT_MAX, SHRT_MIN, 0, 0}; - -TEST(packuswb, test) { - unsigned char B[16] = {0}; - packuswb(B, S, S); - EXPECT_EQ(0, B[0]); - EXPECT_EQ(128, B[1]); - EXPECT_EQ(0, B[2]); - EXPECT_EQ(255, B[3]); - EXPECT_EQ(255, B[4]); - EXPECT_EQ(0, B[5]); - EXPECT_EQ(0, B[6]); - EXPECT_EQ(0, B[7]); - EXPECT_EQ(0, B[8]); - EXPECT_EQ(128, B[9]); - EXPECT_EQ(0, B[10]); - EXPECT_EQ(255, B[11]); - EXPECT_EQ(255, B[12]); - EXPECT_EQ(0, B[13]); - EXPECT_EQ(0, B[14]); - EXPECT_EQ(0, B[15]); +TEST(memset, testMulTrick4) { + long i, j; + unsigned long x; + long di, si, dx, ax; + volatile uint8_t *b; + b = tgc(tmalloc(4)); + for (i = 0; i < 255; ++i) { + for (j = -1; j < 1; ++j) { + x = j; + x &= ~0xff; + x |= i; + asm volatile("call\tmemset" + : "=D"(di), "=S"(si), "=d"(dx), "=a"(ax) + : "0"(b), "1"(x), "2"(4) + : "rcx", "memory", "cc"); + ASSERT_EQ(x & 0xff, b[0]); + ASSERT_EQ(x & 0xff, b[1]); + ASSERT_EQ(x & 0xff, b[2]); + ASSERT_EQ(x & 0xff, b[3]); + } + } } -TEST(packsswb, test) { - const short S[8] = {0, 128, -128, 255, SHRT_MAX, SHRT_MIN, 0, 0}; - signed char B[16] = {0}; - packsswb(B, S, S); - EXPECT_EQ(0, B[0]); - EXPECT_EQ(127, B[1]); - EXPECT_EQ(-128, B[2]); - EXPECT_EQ(127, B[3]); - EXPECT_EQ(127, B[4]); - EXPECT_EQ(-128, B[5]); - EXPECT_EQ(0, B[6]); - EXPECT_EQ(0, B[7]); - EXPECT_EQ(0, B[8]); - EXPECT_EQ(127, B[9]); - EXPECT_EQ(-128, B[10]); - EXPECT_EQ(127, B[11]); - EXPECT_EQ(127, B[12]); - EXPECT_EQ(-128, B[13]); - EXPECT_EQ(0, B[14]); - EXPECT_EQ(0, B[15]); +TEST(memset, testMulTrick8) { + long i, j; + unsigned long x; + long di, si, dx, ax; + volatile uint8_t *b; + b = tgc(tmalloc(8)); + for (i = 0; i < 255; ++i) { + for (j = -1; j < 1; ++j) { + x = j; + x &= ~0xff; + x |= i; + asm volatile("call\tmemset" + : "=D"(di), "=S"(si), "=d"(dx), "=a"(ax) + : "0"(b), "1"(x), "2"(8) + : "rcx", "memory", "cc"); + ASSERT_EQ(x & 0xff, b[0]); + ASSERT_EQ(x & 0xff, b[1]); + ASSERT_EQ(x & 0xff, b[2]); + ASSERT_EQ(x & 0xff, b[3]); + ASSERT_EQ(x & 0xff, b[4]); + ASSERT_EQ(x & 0xff, b[5]); + ASSERT_EQ(x & 0xff, b[6]); + ASSERT_EQ(x & 0xff, b[7]); + } + } } diff --git a/test/libc/nexgen32e/sidiv_test.c b/test/libc/nexgen32e/sidiv_test.c new file mode 100644 index 00000000..9ff32d66 --- /dev/null +++ b/test/libc/nexgen32e/sidiv_test.c @@ -0,0 +1,48 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/nexgen32e/nexgen32e.h" +#include "libc/testlib/testlib.h" + +TEST(sidiv, smoke) { + EXPECT_EQ(13373133731337 / 10, div10int64(13373133731337)); + EXPECT_EQ(13373133731337 / 100, div100int64(13373133731337)); + EXPECT_EQ(13373133731337 / 1000, div1000int64(13373133731337)); + EXPECT_EQ(13373133731337 / 10000, div10000int64(13373133731337)); + EXPECT_EQ(13373133731337 / 1000000, div1000000int64(13373133731337)); + EXPECT_EQ(13373133731337 / 1000000000, div1000000000int64(13373133731337)); +} + +TEST(sirem, smoke) { + EXPECT_EQ(13373133731337 % 10, rem10int64(13373133731337)); + EXPECT_EQ(13373133731337 % 100, rem100int64(13373133731337)); + EXPECT_EQ(13373133731337 % 1000, rem1000int64(13373133731337)); + EXPECT_EQ(13373133731337 % 10000, rem10000int64(13373133731337)); + EXPECT_EQ(13373133731337 % 1000000, rem1000000int64(13373133731337)); + EXPECT_EQ(13373133731337 % 1000000000, rem1000000000int64(13373133731337)); +} + +TEST(rem, euclid) { + ASSERT_EQ(-2, rem10int64(-12)); + ASSERT_EQ(-1, rem10int64(-1)); + ASSERT_EQ(0, rem10int64(0)); + ASSERT_EQ(1, rem10int64(1)); + ASSERT_EQ(9, rem10int64(9)); + ASSERT_EQ(1, rem10int64(11)); +} diff --git a/test/libc/nexgen32e/strtolower_test.c b/test/libc/nexgen32e/strcaseconv_test.c similarity index 74% rename from test/libc/nexgen32e/strtolower_test.c rename to test/libc/nexgen32e/strcaseconv_test.c index 68a4b87c..be014a55 100644 --- a/test/libc/nexgen32e/strtolower_test.c +++ b/test/libc/nexgen32e/strcaseconv_test.c @@ -24,25 +24,31 @@ #include "libc/testlib/testlib.h" TEST(strtolower, testAligned) { - char s[128] = "AZCDabcdABCDabcdABCDabcdABCDabcdABCDabcd"; - EXPECT_STREQ("azcdabcdabcdabcdabcdabcdabcdabcdabcdabcd", strtolower(s)); + EXPECT_STREQ("azcdabcdabcdabcd", + strtolower(tgc(tstrdup("AZCDabcdABCDabcd")))); + EXPECT_STREQ("azcdabcdabcdabcdabcdabcdabcdabcd", + strtolower(tgc(tstrdup("AZCDabcdABCDabcdABCDabcdABCDabcd")))); } TEST(strtolower, testUnaligned) { - char s[128] = "AZCDabcdABCDabcdABCDabcdABCDabcdABCDabcd"; - strtolower(s + 1); - EXPECT_STREQ("Azcdabcdabcdabcdabcdabcdabcdabcdabcdabcd", s); + EXPECT_STREQ("1", strtolower(tgc(tstrdup("1")))); + EXPECT_STREQ( + "zcdabcdabcdabcdabcdabcdabcdabc", + strtolower((char *)tgc(tstrdup("AZCDabcdABCDabcdABCDabcdABCDabc")) + 1)); } TEST(strtoupper, testAligned) { - char s[128] = "AZCDabcdABCDabcdA0CDabcdABCDabcdABCDabcd"; - EXPECT_STREQ("AZCDABCDABCDABCDA0CDABCDABCDABCDABCDABCD", strtoupper(s)); + EXPECT_STREQ("AZCDABCDABCDABCD", + strtoupper(tgc(tstrdup("AZCDabcdABCDabcd")))); + EXPECT_STREQ("AZCDABCDABCDABCDABCDABCDABCDABCD", + strtoupper(tgc(tstrdup("AZCDabcdABCDabcdABCDabcdABCDabcd")))); } TEST(strtoupper, testUnaligned) { - char s[128] = "aZCDabcdABCDabcdABCDabcdABCDabcdABCDabcd"; - strtoupper(s + 1); - EXPECT_STREQ("aZCDABCDABCDABCDABCDABCDABCDABCDABCDABCD", s); + EXPECT_STREQ("1", strtoupper(tgc(tstrdup("1")))); + EXPECT_STREQ( + "ZCDABCDABCDABCDABCDABCDABCDABC", + strtoupper((char *)tgc(tstrdup("AZCDabcdABCDabcdABCDabcdABCDabc")) + 1)); } BENCH(strtolower, bench) { diff --git a/test/libc/nexgen32e/test.mk b/test/libc/nexgen32e/test.mk index 8cb7cebd..4b8bbf68 100644 --- a/test/libc/nexgen32e/test.mk +++ b/test/libc/nexgen32e/test.mk @@ -13,7 +13,7 @@ TEST_LIBC_NEXGEN32E_OBJS = \ $(TEST_LIBC_NEXGEN32E_SRCS:%.c=o/$(MODE)/%.o) TEST_LIBC_NEXGEN32E_COMS = \ - $(TEST_LIBC_NEXGEN32E_OBJS:%.o=%.com) + $(TEST_LIBC_NEXGEN32E_SRCS:%.c=o/$(MODE)/%.com) TEST_LIBC_NEXGEN32E_BINS = \ $(TEST_LIBC_NEXGEN32E_COMS) \ @@ -38,6 +38,7 @@ TEST_LIBC_NEXGEN32E_DIRECTDEPS = \ LIBC_RUNTIME \ LIBC_STUBS \ LIBC_STR \ + LIBC_UNICODE \ LIBC_TESTLIB \ LIBC_X \ TOOL_VIZ_LIB diff --git a/test/libc/rand/test.mk b/test/libc/rand/test.mk index 6586ea6a..188cbcf8 100644 --- a/test/libc/rand/test.mk +++ b/test/libc/rand/test.mk @@ -5,13 +5,15 @@ PKGS += TEST_LIBC_RAND TEST_LIBC_RAND_SRCS := $(wildcard test/libc/rand/*.c) TEST_LIBC_RAND_SRCS_TEST = $(filter %_test.c,$(TEST_LIBC_RAND_SRCS)) -TEST_LIBC_RAND_COMS = $(TEST_LIBC_RAND_OBJS:%.o=%.com) TEST_LIBC_RAND_BINS = $(TEST_LIBC_RAND_COMS) $(TEST_LIBC_RAND_COMS:%=%.dbg) TEST_LIBC_RAND_OBJS = \ $(TEST_LIBC_RAND_SRCS:%=o/$(MODE)/%.zip.o) \ $(TEST_LIBC_RAND_SRCS:%.c=o/$(MODE)/%.o) +TEST_LIBC_RAND_COMS = \ + $(TEST_LIBC_RAND_SRCS:%.c=o/$(MODE)/%.com) + TEST_LIBC_RAND_TESTS = $(TEST_LIBC_RAND_SRCS_TEST:%.c=o/$(MODE)/%.com.ok) TEST_LIBC_RAND_CHECKS = \ @@ -45,9 +47,7 @@ o/$(MODE)/test/libc/rand/%.com.dbg: \ $(APE) @$(APELINK) -$(TEST_LIBC_RAND_OBJS): \ - $(BUILD_FILES) \ - test/libc/rand/test.mk +$(TEST_LIBC_RAND_OBJS): test/libc/rand/test.mk $(TEST_LIBC_RAND_OBJS): \ DEFAULT_CCFLAGS += \ diff --git a/test/libc/runtime/arch_prctl_test.c b/test/libc/runtime/arch_prctl_test.c index 8faece1c..7b7972d8 100644 --- a/test/libc/runtime/arch_prctl_test.c +++ b/test/libc/runtime/arch_prctl_test.c @@ -17,8 +17,9 @@ │ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/dce.h" +#include "libc/bits/segmentation.h" #include "libc/calls/calls.h" +#include "libc/dce.h" #include "libc/testlib/testlib.h" TEST(arch_prctl, fs) { diff --git a/test/libc/runtime/balloc_test.c b/test/libc/runtime/balloc_test.c deleted file mode 100644 index dee08c88..00000000 --- a/test/libc/runtime/balloc_test.c +++ /dev/null @@ -1,105 +0,0 @@ -/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ -│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ -╞══════════════════════════════════════════════════════════════════════════════╡ -│ Copyright 2020 Justine Alexandra Roberts Tunney │ -│ │ -│ This program is free software; you can redistribute it and/or modify │ -│ it under the terms of the GNU General Public License as published by │ -│ the Free Software Foundation; version 2 of the License. │ -│ │ -│ This program is distributed in the hope that it will be useful, but │ -│ WITHOUT ANY WARRANTY; without even the implied warranty of │ -│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ -│ General Public License for more details. │ -│ │ -│ You should have received a copy of the GNU General Public License │ -│ along with this program; if not, write to the Free Software │ -│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ -│ 02110-1301 USA │ -╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/bits/safemacros.h" -#include "libc/calls/calls.h" -#include "libc/calls/struct/sigaction.h" -#include "libc/calls/ucontext.h" -#include "libc/runtime/buffer.h" -#include "libc/runtime/runtime.h" -#include "libc/stdio/stdio.h" -#include "libc/str/str.h" -#include "libc/sysv/consts/fileno.h" -#include "libc/sysv/consts/prot.h" -#include "libc/sysv/consts/sa.h" -#include "libc/sysv/consts/sig.h" -#include "libc/testlib/testlib.h" -#include "libc/x/x.h" -#include "third_party/xed/x86.h" - -char *p; -bool segfaulted_; -struct GuardedBuffer b_; -struct sigaction oldsegv_; -struct XedDecodedInst xedd_; - -void RestrictPage(void *addr, unsigned flags) { - addr = (void *)rounddown((intptr_t)addr, PAGESIZE); - EXPECT_NE(-1, mprotect(addr, PAGESIZE, flags)); -} - -void OnSegLol(int sig, struct siginfo *si, struct ucontext *uc) { - size_t i; - uint8_t *rip; - segfaulted_ = true; - rip = (uint8_t *)uc->uc_mcontext.rip; - RestrictPage(rip, PROT_READ | PROT_WRITE | PROT_EXEC); - ASSERT_EQ(XED_ERROR_NONE, - xed_instruction_length_decode(xed_decoded_inst_zero_set_mode( - &xedd_, XED_MACHINE_MODE_LONG_64), - rip, XED_MAX_INSTRUCTION_BYTES)); - for (i = 0; i < xedd_.decoded_length; ++i) rip[i] = 0x90; /* NOP */ - RestrictPage(rip, PROT_READ | PROT_EXEC); -} - -void SetUp(void) { - segfaulted_ = false; - memset(&b_, 0, sizeof(b_)); - ASSERT_NE(-1, xsigaction(SIGSEGV, OnSegLol, SA_RESETHAND | SA_RESTART, 0, - &oldsegv_)); -} - -void TearDown(void) { - EXPECT_NE(-1, sigaction(SIGSEGV, &oldsegv_, NULL)); - bfree(&b_); - EXPECT_EQ(NULL, b_.p); -} - -TEST(balloc, createsGuardPage) { - ASSERT_NE(NULL, (p = balloc(&b_, 1, 1))); - EXPECT_EQ(p, b_.p); - p[0] = '.'; - ASSERT_FALSE(segfaulted_); - /* TODO(jart): fix me!!! */ - /* p[1 + __BIGGEST_ALIGNMENT__] = '!'; */ - /* EXPECT_TRUE(segfaulted_); */ -} - -TEST(balloc, aligned_roundsUp) { - ASSERT_NE(NULL, (p = balloc(&b_, 128, 1))); - EXPECT_EQ(0, (intptr_t)b_.p & 127); - p[127] = '.'; - ASSERT_FALSE(segfaulted_); - /* TODO(jart): fix me!!! */ - /* p[128 + __BIGGEST_ALIGNMENT__] = '!'; */ - /* EXPECT_TRUE(segfaulted_); */ -} - -TEST(balloc, multipleCalls_avoidsNeedlessSyscalls) { - size_t c; - c = g_syscount; - ASSERT_NE(NULL, (p = balloc(&b_, 1, 31337))); - EXPECT_GT(g_syscount, c); - c = g_syscount; - ASSERT_NE(NULL, (p = balloc(&b_, 1, 31337 / 2))); - EXPECT_EQ(g_syscount, c); - c = g_syscount; - ASSERT_NE(NULL, (p = balloc(&b_, 1, 31337 * 2))); - EXPECT_GT(g_syscount, c); -} diff --git a/test/libc/runtime/carsort_test.c b/test/libc/runtime/carsort_test.c new file mode 100644 index 00000000..9239db7d --- /dev/null +++ b/test/libc/runtime/carsort_test.c @@ -0,0 +1,75 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/alg/alg.h" +#include "libc/macros.h" +#include "libc/rand/rand.h" +#include "libc/runtime/carsort.h" +#include "libc/str/str.h" +#include "libc/testlib/ezbench.h" +#include "libc/testlib/testlib.h" + +const int32_t kUnsorted[][2] = { + {4, 'a'}, {65, 'b'}, {2, 'c'}, {-1, 'G'}, {-31, 'd'}, {0, 'e'}, + {99, 'f'}, {2, 'g'}, {83, 'h'}, {782, 'i'}, {1, 'j'}, +}; + +const int32_t kGolden[][2] = { + {-31, 'd'}, {-1, 'G'}, {0, 'e'}, {1, 'j'}, {2, 'c'}, {2, 'g'}, + {4, 'a'}, {65, 'b'}, {83, 'h'}, {99, 'f'}, {782, 'i'}, +}; + +int32_t A[ARRAYLEN(kUnsorted)][2]; +int32_t B[2100][2]; + +TEST(carsort100, test) { + memcpy(A, kUnsorted, sizeof(A)); + carsort100(ARRAYLEN(A), A); + ASSERT_EQ(0, memcmp(&A[0], &kGolden[0], sizeof(kUnsorted))); +} + +TEST(carsort1000, test) { + memcpy(A, kUnsorted, sizeof(A)); + carsort1000(ARRAYLEN(A), A); + ASSERT_EQ(0, memcmp(&A[0], &kGolden[0], sizeof(kUnsorted))); +} + +TEST(qsort, test) { + memcpy(A, kUnsorted, sizeof(A)); + qsort(A, ARRAYLEN(A), 8, cmpsl); + ASSERT_EQ(0, memcmp(&A[0], &kGolden[0], sizeof(kUnsorted))); +} + +BENCH(carsort, benchMedium) { + EZBENCH2("medium carsort100", rngset(B, sizeof(B), rand64, -1), + carsort100(ARRAYLEN(B), B)); + EZBENCH2("medium carsort1000", rngset(B, sizeof(B), rand64, -1), + carsort1000(ARRAYLEN(B), B)); + EZBENCH2("medium qsort", rngset(B, sizeof(B), rand64, -1), + qsort(B, ARRAYLEN(B), 8, cmpsl)); +} + +BENCH(carsort, benchSmall) { + EZBENCH2("small carsort100", memcpy(A, kUnsorted, sizeof(A)), + carsort100(ARRAYLEN(A), A)); + EZBENCH2("small carsort1000", memcpy(A, kUnsorted, sizeof(A)), + carsort1000(ARRAYLEN(A), A)); + EZBENCH2("small qsort", memcpy(A, kUnsorted, sizeof(A)), + qsort(A, ARRAYLEN(A), 8, cmpsl)); +} diff --git a/test/libc/runtime/grow_test.c b/test/libc/runtime/grow_test.c index 51c474b0..bfba721f 100644 --- a/test/libc/runtime/grow_test.c +++ b/test/libc/runtime/grow_test.c @@ -21,7 +21,6 @@ #include "libc/limits.h" #include "libc/macros.h" #include "libc/mem/mem.h" -#include "libc/runtime/mappings.h" #include "libc/stdio/stdio.h" #include "libc/str/str.h" #include "libc/testlib/testlib.h" diff --git a/test/libc/runtime/itsatrap_test.c b/test/libc/runtime/itsatrap_test.c index 214552ee..ce221b7a 100644 --- a/test/libc/runtime/itsatrap_test.c +++ b/test/libc/runtime/itsatrap_test.c @@ -36,7 +36,7 @@ * @see __addvsi3, __mulvsi3, etc. */ -bool overflowed_; +volatile bool overflowed_; void __on_arithmetic_overflow(void) { overflowed_ = true; @@ -174,6 +174,16 @@ TEST(__mulvdi3, standAndDeliver_aNegativeTimesANegativeEqualsAPositive) { EXPECT_FALSE(overflowed_); } +TEST(__mulvdi3, testOverflow) { + volatile int64_t x; + x = 3037000500; + x *= 3037000499; + EXPECT_FALSE(overflowed_); + x = 3037000500; + x *= 3037000500; + EXPECT_TRUE(overflowed_); +} + /* 32-BIT SIGNED ADDITION */ TEST(__addvsi3, testMin1) { @@ -246,3 +256,99 @@ TEST(__subvdi3, testMax1) { EXPECT_EQ(LONG_MAX - 1, VEIL("r", LONG_MAX) - 1); EXPECT_FALSE(overflowed_); } + +/* 128-BIT SIGNED ADDITION */ + +TEST(__addvti3, testMath) { + volatile int128_t x; + x = 2; + x += 2; + EXPECT_EQ(4, x); + x = -2; + x += -2; + EXPECT_EQ(-4, x); + x = UINT64_MAX; + x += 1; + EXPECT_EQ((int128_t)UINT64_MAX + 1, x); + EXPECT_FALSE(overflowed_); +} + +TEST(__addvti3, testOverflow) { + volatile int128_t x; + x = INT128_MAX; + x += 1; + EXPECT_TRUE(overflowed_); +} + +/* 128-BIT SIGNED SUBTRACTION */ + +TEST(__subvti3, testMath) { + volatile int128_t x; + x = -2; + x -= 2; + EXPECT_EQ(-4, x); + x = UINT64_MIN; + x -= 1; + EXPECT_EQ((int128_t)UINT64_MIN - 1, x); + EXPECT_FALSE(overflowed_); +} + +TEST(__subvti3, testOverflow) { + volatile int128_t x; + x = INT128_MIN; + x -= 1; + EXPECT_TRUE(overflowed_); +} + +/* 128-BIT SIGNED NEGATION */ + +TEST(__negvti3, testMath) { + volatile int128_t x; + x = -2; + x = -x; + EXPECT_EQ(2, x); + EXPECT_FALSE(overflowed_); + x = INT128_MAX; + x = -x; + EXPECT_EQ(INT128_MIN + 1, x); + EXPECT_FALSE(overflowed_); + x = (uint128_t)0x8000000000000000 << 64 | 0x8000000000000000; + x = -x; + EXPECT_EQ((uint128_t)0x7fffffffffffffff << 64 | 0x8000000000000000, x); + EXPECT_FALSE(overflowed_); +} + +TEST(__negvti3, testOverflow) { + volatile int128_t x; + x = INT128_MIN; + x = -x; + EXPECT_TRUE(overflowed_); +} + +/* 128-BIT SIGNED MULTIPLICATION */ + +TEST(__mulvti3, testMath) { + volatile int128_t x; + x = 7; + x *= 11; + EXPECT_EQ(77, x); + EXPECT_FALSE(overflowed_); + x = 0x1fffffffffffffff; + x *= 0x1fffffffffffffff; + EXPECT_EQ((uint128_t)0x3ffffffffffffff << 64 | 0xc000000000000001, x); + EXPECT_FALSE(overflowed_); + x = -0x1fffffffffffffff; + x *= 0x1fffffffffffffff; + EXPECT_EQ((uint128_t)0xfc00000000000000 << 64 | 0x3fffffffffffffff, x); + EXPECT_FALSE(overflowed_); +} + +TEST(__mulvti3, testOverflow) { + volatile int128_t x; + x = 0xb504f333f9de5be0; + x *= 0xb504f333f9de6d28; + EXPECT_FALSE(overflowed_); + x = 0xb504f333f9de5be0; + x *= 0xb504f333f9de6d29; + EXPECT_TRUE(overflowed_); +} diff --git a/test/libc/runtime/memtrack_test.c b/test/libc/runtime/memtrack_test.c new file mode 100644 index 00000000..f3b0a524 --- /dev/null +++ b/test/libc/runtime/memtrack_test.c @@ -0,0 +1,289 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/errno.h" +#include "libc/limits.h" +#include "libc/log/check.h" +#include "libc/mem/mem.h" +#include "libc/runtime/memtrack.h" +#include "libc/runtime/runtime.h" +#include "libc/stdio/stdio.h" +#include "libc/str/str.h" +#include "libc/testlib/testlib.h" + +static bool AreMemoryIntervalsEqual(const struct MemoryIntervals *mm1, + const struct MemoryIntervals *mm2) { + if (mm1->i != mm2->i) return false; + if (memcmp(mm1->p, mm2->p, mm1->i * sizeof(*mm2->p)) != 0) return false; + if (memcmp(mm1->h, mm2->h, mm1->i * sizeof(*mm2->h)) != 0) return false; + return true; +} + +static void PrintMemoryInterval(const struct MemoryIntervals *mm) { + int i; + for (i = 0; i < mm->i; ++i) { + if (i) fprintf(stderr, ","); + fprintf(stderr, "{%d,%d}", mm->p[i].x, mm->p[i].y); + } + fprintf(stderr, "\n"); +} + +static void CheckMemoryIntervalsEqual(const struct MemoryIntervals *mm1, + const struct MemoryIntervals *mm2) { + if (!AreMemoryIntervalsEqual(mm1, mm2)) { + PrintMemoryInterval(mm1); + PrintMemoryInterval(mm2); + CHECK(!"memory intervals not equal"); + exit(1); + } +} + +static void CheckMemoryIntervalsAreOk(const struct MemoryIntervals *mm) { + if (!AreMemoryIntervalsOk(mm)) { + PrintMemoryInterval(mm); + CHECK(!"memory intervals not ok"); + exit(1); + } +} + +static void RunTrackMemoryIntervalTest(const struct MemoryIntervals t[2], int x, + int y, long h) { + struct MemoryIntervals *mm; + mm = memcpy(memalign(alignof(*t), sizeof(*t)), t, sizeof(*t)); + CheckMemoryIntervalsAreOk(mm); + CHECK_NE(-1, TrackMemoryInterval(mm, x, y, h)); + CheckMemoryIntervalsAreOk(mm); + CheckMemoryIntervalsEqual(mm, t + 1); + free(mm); +} + +static void RunReleaseMemoryIntervalsTest(const struct MemoryIntervals t[2], + int x, int y) { + struct MemoryIntervals *mm; + mm = memcpy(memalign(alignof(*t), sizeof(*t)), t, sizeof(*t)); + CheckMemoryIntervalsAreOk(mm); + CHECK_NE(-1, ReleaseMemoryIntervals(mm, x, y, NULL)); + CheckMemoryIntervalsAreOk(mm); + CheckMemoryIntervalsEqual(t + 1, mm); + free(mm); +} + +TEST(TrackMemoryInterval, TestEmpty) { + static const struct MemoryIntervals mm[2] = { + {0, {}, {}}, + {1, {{2, 2}}, {0}}, + }; + RunTrackMemoryIntervalTest(mm, 2, 2, 0); +} + +TEST(TrackMemoryInterval, TestFull) { + int i; + struct MemoryIntervals *mm; + mm = calloc(1, sizeof(struct MemoryIntervals)); + for (i = 0; i < ARRAYLEN(mm->p); ++i) { + CheckMemoryIntervalsAreOk(mm); + CHECK_NE(-1, TrackMemoryInterval(mm, i, i, i)); + CheckMemoryIntervalsAreOk(mm); + } + CHECK_EQ(-1, TrackMemoryInterval(mm, i, i, i)); + CHECK_EQ(ENOMEM, errno); + CheckMemoryIntervalsAreOk(mm); + free(mm); +} + +TEST(TrackMemoryInterval, TestAppend) { + static const struct MemoryIntervals mm[2] = { + {1, {{2, 2}}, {0}}, + {1, {{2, 3}}, {0}}, + }; + RunTrackMemoryIntervalTest(mm, 3, 3, 0); +} + +TEST(TrackMemoryInterval, TestPrepend) { + static const struct MemoryIntervals mm[2] = { + {1, {{2, 2}}, {0}}, + {1, {{1, 2}}, {0}}, + }; + RunTrackMemoryIntervalTest(mm, 1, 1, 0); +} + +TEST(TrackMemoryInterval, TestFillHole) { + static const struct MemoryIntervals mm[2] = { + {4, {{1, 1}, {3, 4}, {5, 5}, {6, 8}}, {0, 0, 1, 0}}, + {3, {{1, 4}, {5, 5}, {6, 8}}, {0, 1, 0}}, + }; + RunTrackMemoryIntervalTest(mm, 2, 2, 0); +} + +TEST(TrackMemoryInterval, TestAppend2) { + static const struct MemoryIntervals mm[2] = { + {1, {{2, 2}}, {0}}, + {2, {{2, 2}, {3, 3}}, {0, 1}}, + }; + RunTrackMemoryIntervalTest(mm, 3, 3, 1); +} + +TEST(TrackMemoryInterval, TestPrepend2) { + static const struct MemoryIntervals mm[2] = { + {1, {{2, 2}}, {0}}, + {2, {{1, 1}, {2, 2}}, {1, 0}}, + }; + RunTrackMemoryIntervalTest(mm, 1, 1, 1); +} + +TEST(TrackMemoryInterval, TestFillHole2) { + static const struct MemoryIntervals mm[2] = { + {4, {{1, 1}, {3, 4}, {5, 5}, {6, 8}}, {0, 0, 1, 0}}, + {5, {{1, 1}, {2, 2}, {3, 4}, {5, 5}, {6, 8}}, {0, 1, 0, 1, 0}}, + }; + RunTrackMemoryIntervalTest(mm, 2, 2, 1); +} + +TEST(FindMemoryInterval, Test) { + static const struct MemoryIntervals mm[1] = { + {4, + { + [0] = {1, 1}, + [1] = {3, 4}, + [2] = {5, 5}, + [3] = {6, 8}, + }, + {0, 0, 1, 0}}, + }; + EXPECT_EQ(0, FindMemoryInterval(mm, 0)); + EXPECT_EQ(0, FindMemoryInterval(mm, 1)); + EXPECT_EQ(1, FindMemoryInterval(mm, 2)); + EXPECT_EQ(1, FindMemoryInterval(mm, 3)); + EXPECT_EQ(1, FindMemoryInterval(mm, 4)); + EXPECT_EQ(2, FindMemoryInterval(mm, 5)); + EXPECT_EQ(3, FindMemoryInterval(mm, 6)); + EXPECT_EQ(3, FindMemoryInterval(mm, 7)); + EXPECT_EQ(3, FindMemoryInterval(mm, 8)); + EXPECT_EQ(4, FindMemoryInterval(mm, 9)); +} + +TEST(ReleaseMemoryIntervals, TestEmpty) { + static const struct MemoryIntervals mm[2] = { + {0, {}, {}}, + {0, {}, {}}, + }; + RunReleaseMemoryIntervalsTest(mm, 2, 2); +} + +TEST(ReleaseMemoryIntervals, TestRemoveElement_UsesInclusiveRange) { + static const struct MemoryIntervals mm[2] = { + {3, {{0, 0}, {2, 2}, {4, 4}}, {}}, + {2, {{0, 0}, {4, 4}}, {}}, + }; + RunReleaseMemoryIntervalsTest(mm, 2, 2); +} + +TEST(ReleaseMemoryIntervals, TestPunchHole) { + static const struct MemoryIntervals mm[2] = { + {1, {{0, 9}}, {}}, + {2, {{0, 3}, {6, 9}}, {}}, + }; + RunReleaseMemoryIntervalsTest(mm, 4, 5); +} + +TEST(ReleaseMemoryIntervals, TestShortenLeft) { + static const struct MemoryIntervals mm[2] = { + {1, {{0, 9}}, {}}, + {1, {{0, 7}}, {}}, + }; + RunReleaseMemoryIntervalsTest(mm, 8, 9); +} + +TEST(ReleaseMemoryIntervals, TestShortenRight) { + static const struct MemoryIntervals mm[2] = { + {1, {{0, 9}}, {}}, + {1, {{3, 9}}, {}}, + }; + RunReleaseMemoryIntervalsTest(mm, 0, 2); +} + +TEST(ReleaseMemoryIntervals, TestShortenLeft2) { + static const struct MemoryIntervals mm[2] = { + {1, {{0, 9}}, {}}, + {1, {{0, 7}}, {}}, + }; + RunReleaseMemoryIntervalsTest(mm, 8, 11); +} + +TEST(ReleaseMemoryIntervals, TestShortenRight2) { + static const struct MemoryIntervals mm[2] = { + {1, {{0, 9}}, {}}, + {1, {{3, 9}}, {}}, + }; + RunReleaseMemoryIntervalsTest(mm, -3, 2); +} + +TEST(ReleaseMemoryIntervals, TestZeroZero) { + static const struct MemoryIntervals mm[2] = { + {1, {{3, 9}}, {}}, + {1, {{3, 9}}, {}}, + }; + RunReleaseMemoryIntervalsTest(mm, 0, 0); +} + +TEST(ReleaseMemoryIntervals, TestNoopLeft) { + static const struct MemoryIntervals mm[2] = { + {1, {{3, 9}}, {}}, + {1, {{3, 9}}, {}}, + }; + RunReleaseMemoryIntervalsTest(mm, 1, 2); +} + +TEST(ReleaseMemoryIntervals, TestNoopRight) { + static const struct MemoryIntervals mm[2] = { + {1, {{3, 9}}, {}}, + {1, {{3, 9}}, {}}, + }; + RunReleaseMemoryIntervalsTest(mm, 10, 10); +} + +TEST(ReleaseMemoryIntervals, TestBigFree) { + static const struct MemoryIntervals mm[2] = { + {2, {{0, 3}, {6, 9}}, {}}, + {0, {}, {}}, + }; + RunReleaseMemoryIntervalsTest(mm, INT_MIN, INT_MAX); +} + +TEST(ReleaseMemoryIntervals, TestWeirdGap) { + static const struct MemoryIntervals mm[2] = { + {3, {{10, 10}, {20, 20}, {30, 30}}, {}}, + {2, {{10, 10}, {30, 30}}, {}}, + }; + RunReleaseMemoryIntervalsTest(mm, 15, 25); +} + +TEST(ReleaseMemoryIntervals, TestOutOfMemory) { + int i; + struct MemoryIntervals *mm; + mm = calloc(1, sizeof(struct MemoryIntervals)); + for (i = 0; i < ARRAYLEN(mm->p); ++i) { + CHECK_NE(-1, TrackMemoryInterval(mm, i * 10, i * 10 + 8, 0)); + } + CheckMemoryIntervalsAreOk(mm); + CHECK_EQ(-1, ReleaseMemoryIntervals(mm, 4, 4, NULL)); + CHECK_EQ(ENOMEM, errno); + CheckMemoryIntervalsAreOk(mm); + free(mm); +} diff --git a/test/libc/runtime/mmap_test.c b/test/libc/runtime/mmap_test.c index 8cb1c06f..58fb4dbb 100644 --- a/test/libc/runtime/mmap_test.c +++ b/test/libc/runtime/mmap_test.c @@ -21,8 +21,9 @@ #include "libc/bits/xchg.h" #include "libc/calls/calls.h" #include "libc/fmt/fmt.h" +#include "libc/mem/mem.h" #include "libc/runtime/gc.h" -#include "libc/runtime/mappings.h" +#include "libc/runtime/memtrack.h" #include "libc/runtime/runtime.h" #include "libc/stdio/stdio.h" #include "libc/str/str.h" @@ -33,71 +34,17 @@ #include "libc/testlib/testlib.h" #include "libc/x/x.h" -unsigned m1; - -TEST(mmap, testMapUnmapAnonAnyAddr) { - void *p; - m1 = _mm.i; - EXPECT_NE(MAP_FAILED, (p = mmap(NULL, FRAMESIZE, PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANONYMOUS, -1, 0))); - EXPECT_EQ(m1 + 1, _mm.i); - EXPECT_NE(-1, munmap(p, FRAMESIZE)); - EXPECT_EQ(m1 + 0, _mm.i); -} - -TEST(mmap, testMunmapUnmapsMultiple) { - void *p1, *p2; - m1 = _mm.i; - EXPECT_NE(MAP_FAILED, (p1 = mmap(NULL, FRAMESIZE, PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANONYMOUS, -1, 0))); - EXPECT_NE(MAP_FAILED, (p2 = mmap(NULL, FRAMESIZE, PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANONYMOUS, -1, 0))); - if ((intptr_t)p1 > (intptr_t)p2) xchg(&p1, &p2); - EXPECT_EQ(m1 + 2, _mm.i); - EXPECT_NE(-1, munmap(p1, (intptr_t)p2 + (intptr_t)FRAMESIZE - (intptr_t)p1)); - EXPECT_EQ(m1 + 0, _mm.i); -} - -TEST(mmap, testPartialUnmapRight) { - if (1) return; /* naaah */ - char *p; - m1 = _mm.i; - EXPECT_NE(MAP_FAILED, (p = mmap(NULL, FRAMESIZE * 2, PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANONYMOUS, -1, 0))); - EXPECT_EQ(m1 + 1, _mm.i); - EXPECT_NE(-1, munmap(p + FRAMESIZE, FRAMESIZE)); - EXPECT_EQ(m1 + 1, _mm.i); - EXPECT_NE(-1, munmap(p, FRAMESIZE)); - EXPECT_EQ(m1 + 0, _mm.i); -} - -TEST(mmap, testPartialUnmapLeft) { - if (1) return; /* naaah */ - char *p; - m1 = _mm.i; - EXPECT_NE(MAP_FAILED, (p = mmap(NULL, FRAMESIZE * 2, PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANONYMOUS, -1, 0))); - EXPECT_EQ(m1 + 1, _mm.i); - EXPECT_NE(-1, munmap(p, FRAMESIZE)); - EXPECT_EQ(m1 + 1, _mm.i); - EXPECT_NE(-1, munmap(p + FRAMESIZE, FRAMESIZE)); - EXPECT_EQ(m1 + 0, _mm.i); -} - TEST(mmap, testMapFile) { int fd; char *p; char path[PATH_MAX]; sprintf(path, "%s%s.%d", kTmpPath, program_invocation_short_name, getpid()); - m1 = _mm.i; ASSERT_NE(-1, (fd = open(path, O_CREAT | O_TRUNC | O_RDWR, 0644))); EXPECT_EQ(5, write(fd, "hello", 5)); EXPECT_NE(-1, fdatasync(fd)); EXPECT_NE(MAP_FAILED, (p = mmap(NULL, 5, PROT_READ, MAP_PRIVATE, fd, 0))); - EXPECT_EQ(m1 + 1, _mm.i); EXPECT_STREQ("hello", p); EXPECT_NE(-1, munmap(p, 5)); - EXPECT_EQ(m1 + 0, _mm.i); EXPECT_NE(-1, close(fd)); EXPECT_NE(-1, unlink(path)); } @@ -106,14 +53,12 @@ TEST(mmap, testMapFile_fdGetsClosed_makesNoDifference) { int fd; char *p, buf[16], path[PATH_MAX]; sprintf(path, "%s%s.%d", kTmpPath, program_invocation_short_name, getpid()); - m1 = _mm.i; ASSERT_NE(-1, (fd = open(path, O_CREAT | O_TRUNC | O_RDWR, 0644))); EXPECT_EQ(5, write(fd, "hello", 5)); EXPECT_NE(-1, fdatasync(fd)); EXPECT_NE(MAP_FAILED, (p = mmap(NULL, 5, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0))); EXPECT_NE(-1, close(fd)); - EXPECT_EQ(m1 + 1, _mm.i); EXPECT_STREQ("hello", p); p[1] = 'a'; EXPECT_NE(-1, msync(p, PAGESIZE, MS_SYNC)); @@ -122,12 +67,11 @@ TEST(mmap, testMapFile_fdGetsClosed_makesNoDifference) { EXPECT_STREQN("hallo", buf, 5); EXPECT_NE(-1, close(fd)); EXPECT_NE(-1, munmap(p, 5)); - EXPECT_EQ(m1 + 0, _mm.i); EXPECT_NE(-1, unlink(path)); } TEST(mmap, testMapFixed_destroysEverythingInItsPath) { - m1 = _mm.i; + unsigned m1 = _mmi.i; EXPECT_NE(MAP_FAILED, mmap((void *)(kFixedMappingsStart + FRAMESIZE * 0), FRAMESIZE, PROT_READ | PROT_WRITE, MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); @@ -137,10 +81,31 @@ TEST(mmap, testMapFixed_destroysEverythingInItsPath) { EXPECT_NE(MAP_FAILED, mmap((void *)(kFixedMappingsStart + FRAMESIZE * 2), FRAMESIZE, PROT_READ | PROT_WRITE, MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); - ASSERT_EQ(m1 + 3, _mm.i); EXPECT_NE(MAP_FAILED, mmap((void *)(kFixedMappingsStart + FRAMESIZE * 0), FRAMESIZE * 3, PROT_READ | PROT_WRITE, MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); - ASSERT_EQ(m1 + 1, _mm.i); + ASSERT_GT(_mmi.i, m1); EXPECT_NE(-1, munmap((void *)kFixedMappingsStart, FRAMESIZE * 3)); + ASSERT_EQ(m1, _mmi.i); +} + +TEST(isheap, nullPtr) { + ASSERT_FALSE(isheap(NULL)); +} + +TEST(isheap, stackMemory) { + int boop; + ASSERT_FALSE(isheap(&boop)); +} + +TEST(isheap, malloc) { + ASSERT_TRUE(isheap(gc(malloc(1)))); +} + +TEST(isheap, emptyMalloc) { + ASSERT_TRUE(isheap(gc(malloc(0)))); +} + +TEST(isheap, mallocOffset) { + ASSERT_TRUE(isheap((char *)gc(malloc(131072)) + 100000)); } diff --git a/test/libc/runtime/test.mk b/test/libc/runtime/test.mk index 072560b0..224a7e9c 100644 --- a/test/libc/runtime/test.mk +++ b/test/libc/runtime/test.mk @@ -5,12 +5,14 @@ PKGS += TEST_LIBC_RUNTIME TEST_LIBC_RUNTIME_SRCS := $(wildcard test/libc/runtime/*.c) TEST_LIBC_RUNTIME_SRCS_TEST = $(filter %_test.c,$(TEST_LIBC_RUNTIME_SRCS)) -TEST_LIBC_RUNTIME_COMS = $(TEST_LIBC_RUNTIME_OBJS:%.o=%.com) TEST_LIBC_RUNTIME_OBJS = \ $(TEST_LIBC_RUNTIME_SRCS:%=o/$(MODE)/%.zip.o) \ $(TEST_LIBC_RUNTIME_SRCS:%.c=o/$(MODE)/%.o) +TEST_LIBC_RUNTIME_COMS = \ + $(TEST_LIBC_RUNTIME_SRCS:%.c=o/$(MODE)/%.com) + TEST_LIBC_RUNTIME_BINS = \ $(TEST_LIBC_RUNTIME_COMS) \ $(TEST_LIBC_RUNTIME_COMS:%=%.dbg) @@ -22,13 +24,16 @@ TEST_LIBC_RUNTIME_CHECKS = \ $(TEST_LIBC_RUNTIME_SRCS_TEST:%.c=o/$(MODE)/%.com.runs) TEST_LIBC_RUNTIME_DIRECTDEPS = \ + LIBC_ALG \ LIBC_CALLS \ LIBC_CALLS_HEFTY \ LIBC_FMT \ LIBC_MEM \ LIBC_NEXGEN32E \ + LIBC_RAND \ LIBC_RUNTIME \ LIBC_STDIO \ + LIBC_LOG \ LIBC_STR \ LIBC_STUBS \ LIBC_SYSV \ @@ -60,6 +65,7 @@ o/$(MODE)/test/libc/runtime/getenv_test.com.runs: \ o/$(MODE)/test/libc/runtime/getenv_test.com @HELLO=THERE build/runit $@ $< +o/$(MODE)/test/libc/runtime/fun_test.o \ o/$(MODE)/test/libc/runtime/itsatrap_test.o: \ OVERRIDE_CFLAGS += \ -fno-sanitize=all \ diff --git a/test/libc/sock/test.mk b/test/libc/sock/test.mk index dbe2c69d..740a12f5 100644 --- a/test/libc/sock/test.mk +++ b/test/libc/sock/test.mk @@ -5,12 +5,14 @@ PKGS += TEST_LIBC_SOCK TEST_LIBC_SOCK_SRCS := $(wildcard test/libc/sock/*.c) TEST_LIBC_SOCK_SRCS_TEST = $(filter %_test.c,$(TEST_LIBC_SOCK_SRCS)) -TEST_LIBC_SOCK_COMS = $(TEST_LIBC_SOCK_OBJS:%.o=%.com) TEST_LIBC_SOCK_OBJS = \ $(TEST_LIBC_SOCK_SRCS:%=o/$(MODE)/%.zip.o) \ $(TEST_LIBC_SOCK_SRCS:%.c=o/$(MODE)/%.o) +TEST_LIBC_SOCK_COMS = \ + $(TEST_LIBC_SOCK_SRCS:%.c=o/$(MODE)/%.com) + TEST_LIBC_SOCK_BINS = \ $(TEST_LIBC_SOCK_COMS) \ $(TEST_LIBC_SOCK_COMS:%=%.dbg) @@ -51,9 +53,7 @@ o/$(MODE)/test/libc/sock/%.com.dbg: \ $(APE) @$(APELINK) -$(TEST_LIBC_SOCK_OBJS): \ - $(BUILD_FILES) \ - test/libc/sock/test.mk +$(TEST_LIBC_SOCK_OBJS): test/libc/sock/test.mk .PHONY: o/$(MODE)/test/libc/sock o/$(MODE)/test/libc/sock: \ diff --git a/test/libc/stdio/test.mk b/test/libc/stdio/test.mk index d6e56863..2089ff5d 100644 --- a/test/libc/stdio/test.mk +++ b/test/libc/stdio/test.mk @@ -5,12 +5,14 @@ PKGS += TEST_LIBC_STDIO TEST_LIBC_STDIO_SRCS := $(wildcard test/libc/stdio/*.c) TEST_LIBC_STDIO_SRCS_TEST = $(filter %_test.c,$(TEST_LIBC_STDIO_SRCS)) -TEST_LIBC_STDIO_COMS = $(TEST_LIBC_STDIO_OBJS:%.o=%.com) TEST_LIBC_STDIO_OBJS = \ $(TEST_LIBC_STDIO_SRCS:%=o/$(MODE)/%.zip.o) \ $(TEST_LIBC_STDIO_SRCS:%.c=o/$(MODE)/%.o) +TEST_LIBC_STDIO_COMS = \ + $(TEST_LIBC_STDIO_SRCS:%.c=o/$(MODE)/%.com) + TEST_LIBC_STDIO_BINS = \ $(TEST_LIBC_STDIO_COMS) \ $(TEST_LIBC_STDIO_COMS:%=%.dbg) diff --git a/test/libc/str/crc32c_test.c b/test/libc/str/crc32c_test.c index 3c1d61bb..720e9151 100644 --- a/test/libc/str/crc32c_test.c +++ b/test/libc/str/crc32c_test.c @@ -18,6 +18,7 @@ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/dce.h" +#include "libc/nexgen32e/crc32.h" #include "libc/nexgen32e/x86feature.h" #include "libc/str/str.h" #include "libc/testlib/testlib.h" @@ -40,9 +41,10 @@ TEST(crc32c, test) { strlen(hyperion) - strlen(FANATICS))); } -uint32_t crc32c$pure(uint32_t, const char *, size_t) hidden; -uint32_t crc32c$sse42(uint32_t, const char *, size_t) hidden; -FIXTURE(crc32c, pure) { *(void **)(&crc32c) = (void *)crc32c$pure; } +FIXTURE(crc32c, pure) { + *(void **)(&crc32c) = (void *)crc32c$pure; +} + FIXTURE(crc32c, sse42) { if (X86_HAVE(SSE4_2)) { *(void **)(&crc32c) = (void *)crc32c$sse42; diff --git a/test/libc/str/memccpy_test.c b/test/libc/str/memccpy_test.c index 4f2c0b3e..34d7453f 100644 --- a/test/libc/str/memccpy_test.c +++ b/test/libc/str/memccpy_test.c @@ -17,9 +17,14 @@ │ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/stdio/stdio.h" #include "libc/str/str.h" +#include "libc/str/tinymemccpy.h" +#include "libc/testlib/ezbench.h" #include "libc/testlib/testlib.h" +void *memccpy2(void *, const void *, int, size_t); + TEST(memccpy, testStringCopy) { char buf[16]; EXPECT_EQ(buf + 3, memccpy(buf, "hi", '\0', sizeof(buf))); @@ -35,3 +40,21 @@ TEST(memccpy, testZeroLength_doesNothing) { char buf[1]; EXPECT_EQ(NULL, memccpy(buf, "hi", '\0', 0)); } + +TEST(memccpy, memcpy) { + unsigned n, n2; + char *b1, *b2, *b3, *e1, *e2; + for (n = 0; n < 1026; ++n) { + b1 = tmalloc(n); + b2 = tmalloc(n); + b3 = tmalloc(n); + e1 = tinymemccpy(b2, b1, 31337, n); + e2 = memccpy(b3, b1, 31337, n); + n2 = e1 ? e1 - b1 : n; + ASSERT_EQ(e1, e2); + ASSERT_EQ(0, memcmp(b2, b3, n2)); + tfree(b3); + tfree(b2); + tfree(b1); + } +} diff --git a/test/libc/str/regex_test.c b/test/libc/str/regex_test.c new file mode 100644 index 00000000..b89b87df --- /dev/null +++ b/test/libc/str/regex_test.c @@ -0,0 +1,33 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/str/str.h" +#include "libc/testlib/testlib.h" +#include "third_party/regex/regex.h" + +TEST(regex, test) { + regex_t rx; + EXPECT_EQ(REG_OK, regcomp(&rx, "^[A-Za-z\x7f-\uffff]{2}$", REG_EXTENDED)); + EXPECT_EQ(REG_OK, regexec(&rx, "AZ", 0, NULL, 0)); + EXPECT_EQ(REG_OK, regexec(&rx, "→→", 0, NULL, 0)); + EXPECT_EQ(REG_NOMATCH, regexec(&rx, "A", 0, NULL, 0)); + EXPECT_EQ(REG_NOMATCH, regexec(&rx, "→", 0, NULL, 0)); + EXPECT_EQ(REG_NOMATCH, regexec(&rx, "0", 0, NULL, 0)); + regfree(&rx); +} diff --git a/test/libc/str/sha256_test.c b/test/libc/str/sha256_test.c index 76dcac3b..b950401d 100644 --- a/test/libc/str/sha256_test.c +++ b/test/libc/str/sha256_test.c @@ -18,6 +18,7 @@ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/str/str.h" +#include "libc/testlib/ezbench.h" #include "libc/testlib/hyperion.h" #include "libc/testlib/testlib.h" @@ -30,8 +31,16 @@ uint8_t *sha256(const char *s) { return hash; } +TEST(sha256, testEmpty) { + EXPECT_BINEQ( + "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + sha256("")); +} + TEST(sha256, test) { - EXPECT_BINEQ(u",≥M║_░ú♫&Φ;*┼╣Γ€←▬▲╲▼ºB^s♦3bôïÿ$", sha256("hello")); + EXPECT_BINEQ(u",≥M║_░ú♫&Φ;*┼╣Γ€←▬▲\\▼ºB^s♦3bôïÿ$", sha256("hello")); + EXPECT_BINEQ("2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b98", + sha256("hello")); } TEST(sha256, testNontrivialSize) { diff --git a/test/libc/str/strcmp_test.c b/test/libc/str/strcmp_test.c index c7cb07fd..7ef413e5 100644 --- a/test/libc/str/strcmp_test.c +++ b/test/libc/str/strcmp_test.c @@ -551,7 +551,8 @@ void longstringislong_dupe(size_t size, char data[size], char dupe[size]) { BENCH(bench_00_strcmp, bench) { size_t size; char *dupe, *data; - size = ROUNDDOWN(getcachesize(kCpuCacheTypeData, 1) / 2, PAGESIZE); + size = ROUNDDOWN(MAX(FRAMESIZE, getcachesize(kCpuCacheTypeData, 1)) / 2, + PAGESIZE); data = tgc(tmalloc(size)); dupe = tgc(tmalloc(size)); EZBENCH2("strcmp [identity]", longstringislong(size, data), @@ -569,7 +570,8 @@ BENCH(bench_00_strcmp, bench) { BENCH(bench_01_strcasecmp, bench) { size_t size; char *dupe, *data; - size = ROUNDDOWN(getcachesize(kCpuCacheTypeData, 1) / 2, PAGESIZE); + size = ROUNDDOWN(MAX(FRAMESIZE, getcachesize(kCpuCacheTypeData, 1)) / 2, + PAGESIZE); data = tgc(tmalloc(size)); dupe = tgc(tmalloc(size)); EZBENCH2("strcasecmp [identity]", longstringislong(size, data), diff --git a/test/libc/str/strlen_test.c b/test/libc/str/strlen_test.c index 7199b799..75efd04b 100644 --- a/test/libc/str/strlen_test.c +++ b/test/libc/str/strlen_test.c @@ -86,7 +86,7 @@ TEST(strnlen, nulNotFound_ReturnsSize) { for (unsigned i = 0; i < ARRAYLEN(sizes); ++i) { char *buf = tmalloc(sizes[i]); memset(buf, ' ', sizes[i]); - ASSERT_EQ(sizes[i], strnlen(buf, sizes[i])); + ASSERT_EQ(sizes[i], strnlen(buf, sizes[i]), "%d", sizes[i]); tfree(buf); } } diff --git a/test/libc/str/strrchr_test.c b/test/libc/str/strrchr_test.c index 44dd5bbe..614819f7 100644 --- a/test/libc/str/strrchr_test.c +++ b/test/libc/str/strrchr_test.c @@ -21,25 +21,29 @@ #include "libc/testlib/testlib.h" #define T(NAME) NAME -#define S(S) S -#define C(C) C +#define S(S) S +#define C(C) C #include "test/libc/str/strrchr_test.inc" #undef C #undef S #undef T -#define T(NAME) NAME##16 -#define S(S) u##S -#define C(C) u##C +#define T(NAME) NAME##16 +#define S(S) u##S +#define C(C) u##C +#define strchr(x, y) strchr16(x, y) #include "test/libc/str/strrchr_test.inc" +#undef strchr #undef C #undef S #undef T -#define T(NAME) NAME##32 -#define S(S) L##S -#define C(C) L##C +#define T(NAME) NAME##32 +#define S(S) L##S +#define C(C) L##C +#define strchr(x, y) wcschr(x, y) #include "test/libc/str/strrchr_test.inc" +#undef strchr #undef C #undef S #undef T diff --git a/test/libc/str/test.mk b/test/libc/str/test.mk index 2bf473db..1f6a1985 100644 --- a/test/libc/str/test.mk +++ b/test/libc/str/test.mk @@ -5,12 +5,14 @@ PKGS += TEST_LIBC_STR TEST_LIBC_STR_SRCS := $(wildcard test/libc/str/*.c) TEST_LIBC_STR_SRCS_TEST = $(filter %_test.c,$(TEST_LIBC_STR_SRCS)) -TEST_LIBC_STR_COMS = $(TEST_LIBC_STR_OBJS:%.o=%.com) TEST_LIBC_STR_OBJS = \ $(TEST_LIBC_STR_SRCS:%=o/$(MODE)/%.zip.o) \ $(TEST_LIBC_STR_SRCS:%.c=o/$(MODE)/%.o) +TEST_LIBC_STR_COMS = \ + $(TEST_LIBC_STR_SRCS:%.c=o/$(MODE)/%.com) + TEST_LIBC_STR_BINS = \ $(TEST_LIBC_STR_COMS) \ $(TEST_LIBC_STR_COMS:%=%.dbg) @@ -38,6 +40,7 @@ TEST_LIBC_STR_DIRECTDEPS = \ LIBC_LOG \ LIBC_X \ LIBC_ZIPOS \ + THIRD_PARTY_REGEX \ THIRD_PARTY_ZLIB TEST_LIBC_STR_DEPS := \ diff --git a/test/libc/str/undeflate_test.c b/test/libc/str/undeflate_test.c index d84bf042..c9dbe793 100644 --- a/test/libc/str/undeflate_test.c +++ b/test/libc/str/undeflate_test.c @@ -23,6 +23,7 @@ #include "libc/log/check.h" #include "libc/macros.h" #include "libc/mem/mem.h" +#include "libc/nexgen32e/crc32.h" #include "libc/runtime/gc.h" #include "libc/runtime/rbx.h" #include "libc/runtime/runtime.h" @@ -50,6 +51,7 @@ TEST(undeflate, testEmbeddedPlaintextConstant) { TEST(undeflate, testStatCentralDirectory_notFound_noSysCalls) { uint64_t c; struct stat st; + stat("zip:doge.txt", &st); /* warmup */ c = g_syscount; ASSERT_EQ(-1, stat("zip:doge.txt", &st)); ASSERT_EQ(0, g_syscount - c); diff --git a/test/libc/str/varint_test.c b/test/libc/str/varint_test.c deleted file mode 100644 index d5c41bc2..00000000 --- a/test/libc/str/varint_test.c +++ /dev/null @@ -1,301 +0,0 @@ -/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ -│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ -╞══════════════════════════════════════════════════════════════════════════════╡ -│ Copyright 2020 Justine Alexandra Roberts Tunney │ -│ │ -│ This program is free software; you can redistribute it and/or modify │ -│ it under the terms of the GNU General Public License as published by │ -│ the Free Software Foundation; version 2 of the License. │ -│ │ -│ This program is distributed in the hope that it will be useful, but │ -│ WITHOUT ANY WARRANTY; without even the implied warranty of │ -│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ -│ General Public License for more details. │ -│ │ -│ You should have received a copy of the GNU General Public License │ -│ along with this program; if not, write to the Free Software │ -│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ -│ 02110-1301 USA │ -╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/fmt/bing.h" -#include "libc/limits.h" -#include "libc/macros.h" -#include "libc/runtime/gc.h" -#include "libc/str/str.h" -#include "libc/str/varint.h" -#include "libc/testlib/testlib.h" -#include "libc/x/x.h" - -#define TV(X, B, I) __FILE__, __LINE__, #X, X, #B, B, #I, I - -static const struct VarintTestVectors { - const char *file; - int line; - const char *xcode; - uint64_t x; - const char *bcode; - uint16_t b[11]; - const char *icode; - uint8_t i; -} kv[] = { - {TV(0x0000000000000000ul, u"          ", 1)}, - {TV(0x0000000000000001ul, u"☺         ", 1)}, - {TV(0x0000000000000003ul, u"♥         ", 1)}, - {TV(0x0000000000000007ul, u"•         ", 1)}, - {TV(0x000000000000000ful, u"☼         ", 1)}, - {TV(0x000000000000001ful, u"▼         ", 1)}, - {TV(0x000000000000003ful, u"⁇         ", 1)}, - {TV(0x000000000000007ful, u"⌂         ", 1)}, - {TV(0x00000000000000fful, u"λ☺        ", 2)}, - {TV(0x00000000000001fful, u"λ♥        ", 2)}, - {TV(0x00000000000003fful, u"λ•        ", 2)}, - {TV(0x00000000000007fful, u"λ☼        ", 2)}, - {TV(0x0000000000000ffful, u"λ▼        ", 2)}, - {TV(0x0000000000001ffful, u"λ⁇        ", 2)}, - {TV(0x0000000000003ffful, u"λ⌂        ", 2)}, - {TV(0x0000000000007ffful, u"λλ☺       ", 3)}, - {TV(0x000000000000fffful, u"λλ♥       ", 3)}, - {TV(0x000000000001fffful, u"λλ•       ", 3)}, - {TV(0x000000000003fffful, u"λλ☼       ", 3)}, - {TV(0x000000000007fffful, u"λλ▼       ", 3)}, - {TV(0x00000000000ffffful, u"λλ⁇       ", 3)}, - {TV(0x00000000001ffffful, u"λλ⌂       ", 3)}, - {TV(0x00000000003ffffful, u"λλλ☺      ", 4)}, - {TV(0x00000000007ffffful, u"λλλ♥      ", 4)}, - {TV(0x0000000000fffffful, u"λλλ•      ", 4)}, - {TV(0x0000000001fffffful, u"λλλ☼      ", 4)}, - {TV(0x0000000003fffffful, u"λλλ▼      ", 4)}, - {TV(0x0000000007fffffful, u"λλλ⁇      ", 4)}, - {TV(0x000000000ffffffful, u"λλλ⌂      ", 4)}, - {TV(0x000000001ffffffful, u"λλλλ☺     ", 5)}, - {TV(0x000000003ffffffful, u"λλλλ♥     ", 5)}, - {TV(0x000000007ffffffful, u"λλλλ•     ", 5)}, - {TV(0x00000000fffffffful, u"λλλλ☼     ", 5)}, - {TV(0x00000001fffffffful, u"λλλλ▼     ", 5)}, - {TV(0x00000003fffffffful, u"λλλλ⁇     ", 5)}, - {TV(0x00000007fffffffful, u"λλλλ⌂     ", 5)}, - {TV(0x0000000ffffffffful, u"λλλλλ☺    ", 6)}, - {TV(0x0000001ffffffffful, u"λλλλλ♥    ", 6)}, - {TV(0x0000003ffffffffful, u"λλλλλ•    ", 6)}, - {TV(0x0000007ffffffffful, u"λλλλλ☼    ", 6)}, - {TV(0x000000fffffffffful, u"λλλλλ▼    ", 6)}, - {TV(0x000001fffffffffful, u"λλλλλ⁇    ", 6)}, - {TV(0x000003fffffffffful, u"λλλλλ⌂    ", 6)}, - {TV(0x000007fffffffffful, u"λλλλλλ☺   ", 7)}, - {TV(0x00000ffffffffffful, u"λλλλλλ♥   ", 7)}, - {TV(0x00001ffffffffffful, u"λλλλλλ•   ", 7)}, - {TV(0x00003ffffffffffful, u"λλλλλλ☼   ", 7)}, - {TV(0x00007ffffffffffful, u"λλλλλλ▼   ", 7)}, - {TV(0x0000fffffffffffful, u"λλλλλλ⁇   ", 7)}, - {TV(0x0001fffffffffffful, u"λλλλλλ⌂   ", 7)}, - {TV(0x0003fffffffffffful, u"λλλλλλλ☺  ", 8)}, - {TV(0x0007fffffffffffful, u"λλλλλλλ♥  ", 8)}, - {TV(0x000ffffffffffffful, u"λλλλλλλ•  ", 8)}, - {TV(0x001ffffffffffffful, u"λλλλλλλ☼  ", 8)}, - {TV(0x003ffffffffffffful, u"λλλλλλλ▼  ", 8)}, - {TV(0x007ffffffffffffful, u"λλλλλλλ⁇  ", 8)}, - {TV(0x00fffffffffffffful, u"λλλλλλλ⌂  ", 8)}, - {TV(0x01fffffffffffffful, u"λλλλλλλλ☺ ", 9)}, - {TV(0x03fffffffffffffful, u"λλλλλλλλ♥ ", 9)}, - {TV(0x07fffffffffffffful, u"λλλλλλλλ• ", 9)}, - {TV(0x0ffffffffffffffful, u"λλλλλλλλ☼ ", 9)}, - {TV(0x1ffffffffffffffful, u"λλλλλλλλ▼ ", 9)}, - {TV(0x3ffffffffffffffful, u"λλλλλλλλ⁇ ", 9)}, - {TV(0x7ffffffffffffffful, u"λλλλλλλλ⌂ ", 9)}, - {TV(0xfffffffffffffffful, u"λλλλλλλλλ☺", 10)}, -}; - -const struct ZigzagTestVectors { - const char *file; - int line; - const char *xcode; - int64_t x; - const char *bcode; - uint16_t b[11]; - const char *icode; - uint8_t i; -} kz[] = { - {TV(0, u" ", 1)}, - {TV(1, u"☻", 1)}, - {TV(-1, u"☺", 1)}, - {TV(2, u"♦", 1)}, - {TV(-2, u"♥", 1)}, - {TV(4, u"◘", 1)}, - {TV(-4, u"•", 1)}, - {TV(8, u"►", 1)}, - {TV(-8, u"☼", 1)}, - {TV(16, u" ", 1)}, - {TV(-16, u"▼", 1)}, - {TV(32, u"@", 1)}, - {TV(-32, u"⁇", 1)}, - {TV(64, u"Ç☺", 2)}, - {TV(-64, u"⌂", 1)}, - {TV(128, u"Ç☻", 2)}, - {TV(-128, u"λ☺", 2)}, - {TV(256, u"Ç♦", 2)}, - {TV(-256, u"λ♥", 2)}, - {TV(512, u"Ç◘", 2)}, - {TV(-512, u"λ•", 2)}, - {TV(1024, u"Ç►", 2)}, - {TV(-1024, u"λ☼", 2)}, - {TV(2048, u"Ç ", 2)}, - {TV(-2048, u"λ▼", 2)}, - {TV(4096, u"Ç@", 2)}, - {TV(-4096, u"λ⁇", 2)}, - {TV(8192, u"ÇÇ☺", 3)}, - {TV(-8192, u"λ⌂", 2)}, - {TV(16384, u"ÇÇ☻", 3)}, - {TV(-16384, u"λλ☺", 3)}, - {TV(32768, u"ÇÇ♦", 3)}, - {TV(-32768, u"λλ♥", 3)}, - {TV(65536, u"ÇÇ◘", 3)}, - {TV(-65536, u"λλ•", 3)}, - {TV(131072, u"ÇÇ►", 3)}, - {TV(-131072, u"λλ☼", 3)}, - {TV(262144, u"ÇÇ ", 3)}, - {TV(-262144, u"λλ▼", 3)}, - {TV(524288, u"ÇÇ@", 3)}, - {TV(-524288, u"λλ⁇", 3)}, - {TV(1048576, u"ÇÇÇ☺", 4)}, - {TV(-1048576, u"λλ⌂", 3)}, - {TV(2097152, u"ÇÇÇ☻", 4)}, - {TV(-2097152, u"λλλ☺", 4)}, - {TV(4194304, u"ÇÇÇ♦", 4)}, - {TV(-4194304, u"λλλ♥", 4)}, - {TV(8388608, u"ÇÇÇ◘", 4)}, - {TV(-8388608, u"λλλ•", 4)}, - {TV(16777216, u"ÇÇÇ►", 4)}, - {TV(-16777216, u"λλλ☼", 4)}, - {TV(33554432, u"ÇÇÇ ", 4)}, - {TV(-33554432, u"λλλ▼", 4)}, - {TV(67108864, u"ÇÇÇ@", 4)}, - {TV(-67108864, u"λλλ⁇", 4)}, - {TV(134217728, u"ÇÇÇÇ☺", 5)}, - {TV(-134217728, u"λλλ⌂", 4)}, - {TV(268435456, u"ÇÇÇÇ☻", 5)}, - {TV(-268435456, u"λλλλ☺", 5)}, - {TV(536870912, u"ÇÇÇÇ♦", 5)}, - {TV(-536870912, u"λλλλ♥", 5)}, - {TV(1073741824, u"ÇÇÇÇ◘", 5)}, - {TV(-1073741824, u"λλλλ•", 5)}, - {TV(2147483648, u"ÇÇÇÇ►", 5)}, - {TV(-2147483648, u"λλλλ☼", 5)}, - {TV(4294967296, u"ÇÇÇÇ ", 5)}, - {TV(-4294967296, u"λλλλ▼", 5)}, - {TV(8589934592, u"ÇÇÇÇ@", 5)}, - {TV(-8589934592, u"λλλλ⁇", 5)}, - {TV(17179869184, u"ÇÇÇÇÇ☺", 6)}, - {TV(-17179869184, u"λλλλ⌂", 5)}, - {TV(34359738368, u"ÇÇÇÇÇ☻", 6)}, - {TV(-34359738368, u"λλλλλ☺", 6)}, - {TV(68719476736, u"ÇÇÇÇÇ♦", 6)}, - {TV(-68719476736, u"λλλλλ♥", 6)}, - {TV(137438953472, u"ÇÇÇÇÇ◘", 6)}, - {TV(-137438953472, u"λλλλλ•", 6)}, - {TV(274877906944, u"ÇÇÇÇÇ►", 6)}, - {TV(-274877906944, u"λλλλλ☼", 6)}, - {TV(549755813888, u"ÇÇÇÇÇ ", 6)}, - {TV(-549755813888, u"λλλλλ▼", 6)}, - {TV(1099511627776, u"ÇÇÇÇÇ@", 6)}, - {TV(-1099511627776, u"λλλλλ⁇", 6)}, - {TV(2199023255552, u"ÇÇÇÇÇÇ☺", 7)}, - {TV(-2199023255552, u"λλλλλ⌂", 6)}, - {TV(4398046511104, u"ÇÇÇÇÇÇ☻", 7)}, - {TV(-4398046511104, u"λλλλλλ☺", 7)}, - {TV(8796093022208, u"ÇÇÇÇÇÇ♦", 7)}, - {TV(-8796093022208, u"λλλλλλ♥", 7)}, - {TV(17592186044416, u"ÇÇÇÇÇÇ◘", 7)}, - {TV(-17592186044416, u"λλλλλλ•", 7)}, - {TV(35184372088832, u"ÇÇÇÇÇÇ►", 7)}, - {TV(-35184372088832, u"λλλλλλ☼", 7)}, - {TV(70368744177664, u"ÇÇÇÇÇÇ ", 7)}, - {TV(-70368744177664, u"λλλλλλ▼", 7)}, - {TV(140737488355328, u"ÇÇÇÇÇÇ@", 7)}, - {TV(-140737488355328, u"λλλλλλ⁇", 7)}, - {TV(281474976710656, u"ÇÇÇÇÇÇÇ☺", 8)}, - {TV(-281474976710656, u"λλλλλλ⌂", 7)}, - {TV(562949953421312, u"ÇÇÇÇÇÇÇ☻", 8)}, - {TV(-562949953421312, u"λλλλλλλ☺", 8)}, - {TV(1125899906842624, u"ÇÇÇÇÇÇÇ♦", 8)}, - {TV(-1125899906842624, u"λλλλλλλ♥", 8)}, - {TV(2251799813685248, u"ÇÇÇÇÇÇÇ◘", 8)}, - {TV(-2251799813685248, u"λλλλλλλ•", 8)}, - {TV(4503599627370496, u"ÇÇÇÇÇÇÇ►", 8)}, - {TV(-4503599627370496, u"λλλλλλλ☼", 8)}, - {TV(9007199254740992, u"ÇÇÇÇÇÇÇ ", 8)}, - {TV(-9007199254740992, u"λλλλλλλ▼", 8)}, - {TV(18014398509481984, u"ÇÇÇÇÇÇÇ@", 8)}, - {TV(-18014398509481984, u"λλλλλλλ⁇", 8)}, - {TV(36028797018963968, u"ÇÇÇÇÇÇÇÇ☺", 9)}, - {TV(-36028797018963968, u"λλλλλλλ⌂", 8)}, - {TV(72057594037927936, u"ÇÇÇÇÇÇÇÇ☻", 9)}, - {TV(-72057594037927936, u"λλλλλλλλ☺", 9)}, - {TV(144115188075855872, u"ÇÇÇÇÇÇÇÇ♦", 9)}, - {TV(-144115188075855872, u"λλλλλλλλ♥", 9)}, - {TV(288230376151711744, u"ÇÇÇÇÇÇÇÇ◘", 9)}, - {TV(-288230376151711744, u"λλλλλλλλ•", 9)}, - {TV(576460752303423488, u"ÇÇÇÇÇÇÇÇ►", 9)}, - {TV(-576460752303423488, u"λλλλλλλλ☼", 9)}, - {TV(1152921504606846976, u"ÇÇÇÇÇÇÇÇ ", 9)}, - {TV(-1152921504606846976, u"λλλλλλλλ▼", 9)}, - {TV(2305843009213693952, u"ÇÇÇÇÇÇÇÇ@", 9)}, - {TV(-2305843009213693952, u"λλλλλλλλ⁇", 9)}, - {TV(4611686018427387904, u"ÇÇÇÇÇÇÇÇÇ☺", 10)}, - {TV(-4611686018427387904, u"λλλλλλλλ⌂", 9)}, - {TV(INT64_MIN, u"λλλλλλλλλ☺", 10)}, - {TV(INT64_MAX, u"■λλλλλλλλ☺", 10)}, /* wut */ -}; - -TEST(writevint, test) { - size_t i; - uint8_t b[10], *p; - for (i = 0; i < ARRAYLEN(kv); ++i) { - memset(b, 0, sizeof(b)); - p = writevint(b, kv[i].x); - __TEST_EQ(assert, kz[i].file, kz[i].line, __FUNCTION__, kz[i].icode, - kz[i].xcode, kv[i].i, p - b); - assertBinaryEquals$cp437(kz[i].file, kz[i].line, __FUNCTION__, kv[i].b, b, - strlen(kv[i].b), "", false); - } -} - -TEST(readvint, test) { - size_t i; - uint64_t x; - uint8_t *b, *p; - for (i = 0; i < ARRAYLEN(kv); ++i) { - b = gc(unbingstr(kv[i].b)); - p = readvint(b, b + strlen(kv[i].b), &x); - __TEST_EQ(assert, kv[i].file, kv[i].line, __FUNCTION__, kv[i].icode, - kv[i].xcode, kv[i].i, (intptr_t)(p - b)); - assertBinaryEquals$cp437(kv[i].file, kv[i].line, __FUNCTION__, kv[i].b, b, - strlen(kv[i].b), "", false); - } -} - -TEST(writesint, test) { - size_t i; - uint8_t b[10], *p; - for (i = 0; i < ARRAYLEN(kz); ++i) { - memset(b, 0, sizeof(b)); - p = writesint(b, kz[i].x); - __TEST_EQ(assert, kz[i].file, kz[i].line, __FUNCTION__, kz[i].icode, - kz[i].xcode, kz[i].i, (intptr_t)(p - b)); - assertBinaryEquals$cp437(kz[i].file, kz[i].line, __FUNCTION__, kz[i].b, b, - strlen(kz[i].b), "", false); - } -} - -TEST(readsint, test) { - size_t i; - int64_t x; - uint8_t *b, *p; - for (i = 0; i < ARRAYLEN(kz); ++i) { - b = gc(unbingstr(kz[i].b)); - p = readsint(b, b + strlen(kz[i].b), &x); - __TEST_EQ(assert, kz[i].file, kz[i].line, __FUNCTION__, kz[i].icode, - kz[i].xcode, kz[i].i, (intptr_t)(p - b)); - assertBinaryEquals$cp437(kz[i].file, kz[i].line, __FUNCTION__, kz[i].b, b, - strlen(kz[i].b), "", false); - } -} diff --git a/test/libc/time/dsleep_test.c b/test/libc/time/dsleep_test.c index f11b81e6..646b1875 100644 --- a/test/libc/time/dsleep_test.c +++ b/test/libc/time/dsleep_test.c @@ -30,7 +30,7 @@ TEST(fastdiv, test) { long x = 123000000321; EXPECT_EQ(123, div1000000000int64(x)); - EXPECT_EQ(321, mod1000000000int64(x)); + EXPECT_EQ(321, rem1000000000int64(x)); } TEST(dsleep, test) { diff --git a/test/libc/time/test.mk b/test/libc/time/test.mk index 7ea990e3..c8164e3c 100644 --- a/test/libc/time/test.mk +++ b/test/libc/time/test.mk @@ -5,13 +5,15 @@ PKGS += TEST_LIBC_TIME TEST_LIBC_TIME_SRCS := $(wildcard test/libc/time/*.c) TEST_LIBC_TIME_SRCS_TEST = $(filter %_test.c,$(TEST_LIBC_TIME_SRCS)) -TEST_LIBC_TIME_COMS = $(TEST_LIBC_TIME_OBJS:%.o=%.com) TEST_LIBC_TIME_BINS = $(TEST_LIBC_TIME_COMS) $(TEST_LIBC_TIME_COMS:%=%.dbg) TEST_LIBC_TIME_OBJS = \ $(TEST_LIBC_TIME_SRCS:%=o/$(MODE)/%.zip.o) \ $(TEST_LIBC_TIME_SRCS:%.c=o/$(MODE)/%.o) +TEST_LIBC_TIME_COMS = \ + $(TEST_LIBC_TIME_SRCS:%.c=o/$(MODE)/%.com) + TEST_LIBC_TIME_TESTS = $(TEST_LIBC_TIME_SRCS_TEST:%.c=o/$(MODE)/%.com.ok) TEST_LIBC_TIME_CHECKS = \ $(TEST_LIBC_TIME_SRCS_TEST:%.c=o/$(MODE)/%.com.runs) diff --git a/test/libc/conv/sizemultiply_test.c b/test/libc/tinymath/logb_test.c similarity index 71% rename from test/libc/conv/sizemultiply_test.c rename to test/libc/tinymath/logb_test.c index 5d583de9..29f79df2 100644 --- a/test/libc/conv/sizemultiply_test.c +++ b/test/libc/tinymath/logb_test.c @@ -17,34 +17,37 @@ │ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/conv/conv.h" -#include "libc/conv/sizemultiply.h" #include "libc/limits.h" +#include "libc/math.h" #include "libc/testlib/testlib.h" +#include "libc/tinymath/tinymath.h" -size_t out; - -TEST(sizemultiply, testMultiplication) { - EXPECT_TRUE(sizemultiply(&out, 7, 11)); - EXPECT_EQ(7 * 11, out); - EXPECT_TRUE(sizemultiply(&out, 11, 7)); - EXPECT_EQ(7 * 11, out); - EXPECT_TRUE(sizemultiply(&out, 0, 11)); - EXPECT_EQ(0 * 11, out); - EXPECT_TRUE(sizemultiply(&out, 11, 0)); - EXPECT_EQ(0 * 11, out); +TEST(ilogb, yolo) { + EXPECT_EQ(0, ilogb(1)); + EXPECT_EQ(1, ilogb(2)); + EXPECT_EQ(2, ilogb(4)); + EXPECT_EQ(63, ilogb(1e19)); + EXPECT_EQ(0, ilogbf(1)); + EXPECT_EQ(1, ilogbf(2)); + EXPECT_EQ(2, ilogbf(4)); + EXPECT_EQ(63, ilogb(1e19)); + EXPECT_EQ(0, ilogbl(1)); + EXPECT_EQ(1, ilogbl(2)); + EXPECT_EQ(2, ilogbl(4)); + EXPECT_EQ(63, ilogbl(1e19)); } -TEST(sizemultiply, testOverflow_alwaysReturnsSizeMax) { - EXPECT_FALSE(sizemultiply(&out, 7, SIZE_MAX)); - EXPECT_EQ(SIZE_MAX, out); - EXPECT_FALSE(sizemultiply(&out, SIZE_MAX, 7)); - EXPECT_EQ(SIZE_MAX, out); -} - -TEST(sizemultiply, testOverflow_closeButNoCigar) { - EXPECT_TRUE(sizemultiply(&out, SIZE_MAX, 1)); - EXPECT_EQ(SIZE_MAX, out); - EXPECT_TRUE(sizemultiply(&out, 1, SIZE_MAX)); - EXPECT_EQ(SIZE_MAX, out); +TEST(logb, yolo) { + EXPECT_EQ(0, (int)logb(1)); + EXPECT_EQ(1, (int)logb(2)); + EXPECT_EQ(2, (int)logb(4)); + EXPECT_EQ(63, (int)logb(1e19)); + EXPECT_EQ(0, (int)logbf(1)); + EXPECT_EQ(1, (int)logbf(2)); + EXPECT_EQ(2, (int)logbf(4)); + EXPECT_EQ(63, (int)logb(1e19)); + EXPECT_EQ(0, (int)logbl(1)); + EXPECT_EQ(1, (int)logbl(2)); + EXPECT_EQ(2, (int)logbl(4)); + EXPECT_EQ(63, (int)logbl(1e19)); } diff --git a/test/libc/tinymath/round_test.c b/test/libc/tinymath/round_test.c index e189424d..649bbde2 100644 --- a/test/libc/tinymath/round_test.c +++ b/test/libc/tinymath/round_test.c @@ -27,15 +27,6 @@ float tinymath_roundf$k8(float); double tinymath_round$k8(double); -TEST(round, test) { - EXPECT_STREQ("-3", gc(xdtoa(tinymath_round(-2.5)))); - EXPECT_STREQ("-2", gc(xdtoa(tinymath_round(-1.5)))); - EXPECT_STREQ("-1", gc(xdtoa(tinymath_round(-.5)))); - EXPECT_STREQ("1", gc(xdtoa(tinymath_round(.5)))); - EXPECT_STREQ("2", gc(xdtoa(tinymath_round(1.5)))); - EXPECT_STREQ("3", gc(xdtoa(tinymath_round(2.5)))); -} - TEST(round, testCornerCases) { EXPECT_STREQ("-0", gc(xdtoa(tinymath_round(-0.0)))); EXPECT_STREQ("nan", gc(xdtoa(tinymath_round(NAN)))); @@ -44,6 +35,105 @@ TEST(round, testCornerCases) { EXPECT_STREQ("-inf", gc(xdtoa(tinymath_round(-INFINITY)))); } +TEST(round, test) { + EXPECT_STREQ("-3", gc(xdtoa(tinymath_round(-2.5)))); + EXPECT_STREQ("-2", gc(xdtoa(tinymath_round(-1.5)))); + EXPECT_STREQ("-1", gc(xdtoa(tinymath_round(-.5)))); + EXPECT_STREQ("-0", gc(xdtoa(tinymath_round(-.4)))); + EXPECT_STREQ("0", gc(xdtoa(tinymath_round(.4)))); + EXPECT_STREQ("1", gc(xdtoa(tinymath_round(.5)))); + EXPECT_STREQ("2", gc(xdtoa(tinymath_round(1.5)))); + EXPECT_STREQ("3", gc(xdtoa(tinymath_round(2.5)))); +} + +TEST(roundf, test) { + EXPECT_STREQ("-3", gc(xdtoa(tinymath_roundf(-2.5)))); + EXPECT_STREQ("-2", gc(xdtoa(tinymath_roundf(-1.5)))); + EXPECT_STREQ("-1", gc(xdtoa(tinymath_roundf(-.5)))); + EXPECT_STREQ("-0", gc(xdtoa(tinymath_roundf(-.4)))); + EXPECT_STREQ("0", gc(xdtoa(tinymath_roundf(.4)))); + EXPECT_STREQ("1", gc(xdtoa(tinymath_roundf(.5)))); + EXPECT_STREQ("2", gc(xdtoa(tinymath_roundf(1.5)))); + EXPECT_STREQ("3", gc(xdtoa(tinymath_roundf(2.5)))); +} + +TEST(roundl, test) { + EXPECT_STREQ("-3", gc(xdtoa(tinymath_roundl(-2.5)))); + EXPECT_STREQ("-2", gc(xdtoa(tinymath_roundl(-1.5)))); + EXPECT_STREQ("-1", gc(xdtoa(tinymath_roundl(-.5)))); + EXPECT_STREQ("-0", gc(xdtoa(tinymath_roundl(-.4)))); + EXPECT_STREQ("0", gc(xdtoa(tinymath_roundl(.4)))); + EXPECT_STREQ("1", gc(xdtoa(tinymath_roundl(.5)))); + EXPECT_STREQ("2", gc(xdtoa(tinymath_roundl(1.5)))); + EXPECT_STREQ("3", gc(xdtoa(tinymath_roundl(2.5)))); +} + +TEST(nearbyint, test) { + EXPECT_STREQ("-2", gc(xdtoa(tinymath_nearbyint(-2.5)))); + EXPECT_STREQ("-2", gc(xdtoa(tinymath_nearbyint(-1.5)))); + EXPECT_STREQ("-0", gc(xdtoa(tinymath_nearbyint(-.5)))); + EXPECT_STREQ("-0", gc(xdtoa(tinymath_nearbyint(-.4)))); + EXPECT_STREQ("0", gc(xdtoa(tinymath_nearbyint(.4)))); + EXPECT_STREQ("0", gc(xdtoa(tinymath_nearbyint(.5)))); + EXPECT_STREQ("2", gc(xdtoa(tinymath_nearbyint(1.5)))); + EXPECT_STREQ("2", gc(xdtoa(tinymath_nearbyint(2.5)))); +} + +TEST(nearbyintf, test) { + EXPECT_STREQ("-2", gc(xdtoa(tinymath_nearbyintf(-2.5)))); + EXPECT_STREQ("-2", gc(xdtoa(tinymath_nearbyintf(-1.5)))); + EXPECT_STREQ("-0", gc(xdtoa(tinymath_nearbyintf(-.5)))); + EXPECT_STREQ("-0", gc(xdtoa(tinymath_nearbyintf(-.4)))); + EXPECT_STREQ("0", gc(xdtoa(tinymath_nearbyintf(.4)))); + EXPECT_STREQ("0", gc(xdtoa(tinymath_nearbyintf(.5)))); + EXPECT_STREQ("2", gc(xdtoa(tinymath_nearbyintf(1.5)))); + EXPECT_STREQ("2", gc(xdtoa(tinymath_nearbyintf(2.5)))); +} + +TEST(nearbyintl, test) { + EXPECT_STREQ("-2", gc(xdtoa(tinymath_nearbyintl(-2.5)))); + EXPECT_STREQ("-2", gc(xdtoa(tinymath_nearbyintl(-1.5)))); + EXPECT_STREQ("-0", gc(xdtoa(tinymath_nearbyintl(-.5)))); + EXPECT_STREQ("-0", gc(xdtoa(tinymath_nearbyintl(-.4)))); + EXPECT_STREQ("0", gc(xdtoa(tinymath_nearbyintl(.4)))); + EXPECT_STREQ("0", gc(xdtoa(tinymath_nearbyintl(.5)))); + EXPECT_STREQ("2", gc(xdtoa(tinymath_nearbyintl(1.5)))); + EXPECT_STREQ("2", gc(xdtoa(tinymath_nearbyintl(2.5)))); +} + +TEST(rint, test) { + EXPECT_STREQ("-2", gc(xdtoa(tinymath_rint(-2.5)))); + EXPECT_STREQ("-2", gc(xdtoa(tinymath_rint(-1.5)))); + EXPECT_STREQ("-0", gc(xdtoa(tinymath_rint(-.5)))); + EXPECT_STREQ("-0", gc(xdtoa(tinymath_rint(-.4)))); + EXPECT_STREQ("0", gc(xdtoa(tinymath_rint(.4)))); + EXPECT_STREQ("0", gc(xdtoa(tinymath_rint(.5)))); + EXPECT_STREQ("2", gc(xdtoa(tinymath_rint(1.5)))); + EXPECT_STREQ("2", gc(xdtoa(tinymath_rint(2.5)))); +} + +TEST(rintf, test) { + EXPECT_STREQ("-2", gc(xdtoa(tinymath_rintf(-2.5)))); + EXPECT_STREQ("-2", gc(xdtoa(tinymath_rintf(-1.5)))); + EXPECT_STREQ("-0", gc(xdtoa(tinymath_rintf(-.5)))); + EXPECT_STREQ("-0", gc(xdtoa(tinymath_rintf(-.4)))); + EXPECT_STREQ("0", gc(xdtoa(tinymath_rintf(.4)))); + EXPECT_STREQ("0", gc(xdtoa(tinymath_rintf(.5)))); + EXPECT_STREQ("2", gc(xdtoa(tinymath_rintf(1.5)))); + EXPECT_STREQ("2", gc(xdtoa(tinymath_rintf(2.5)))); +} + +TEST(rintl, test) { + EXPECT_STREQ("-2", gc(xdtoa(tinymath_rintl(-2.5)))); + EXPECT_STREQ("-2", gc(xdtoa(tinymath_rintl(-1.5)))); + EXPECT_STREQ("-0", gc(xdtoa(tinymath_rintl(-.5)))); + EXPECT_STREQ("-0", gc(xdtoa(tinymath_rintl(-.4)))); + EXPECT_STREQ("0", gc(xdtoa(tinymath_rintl(.4)))); + EXPECT_STREQ("0", gc(xdtoa(tinymath_rintl(.5)))); + EXPECT_STREQ("2", gc(xdtoa(tinymath_rintl(1.5)))); + EXPECT_STREQ("2", gc(xdtoa(tinymath_rintl(2.5)))); +} + TEST(lround, test) { EXPECT_EQ(-3, tinymath_lround(-2.5)); EXPECT_EQ(-2, tinymath_lround(-1.5)); @@ -54,15 +144,6 @@ TEST(lround, test) { EXPECT_EQ(3, tinymath_lround(2.5)); } -TEST(roundf, test) { - EXPECT_STREQ("-3", gc(xdtoa(tinymath_roundf(-2.5)))); - EXPECT_STREQ("-2", gc(xdtoa(tinymath_roundf(-1.5)))); - EXPECT_STREQ("-1", gc(xdtoa(tinymath_roundf(-.5)))); - EXPECT_STREQ("1", gc(xdtoa(tinymath_roundf(.5)))); - EXPECT_STREQ("2", gc(xdtoa(tinymath_roundf(1.5)))); - EXPECT_STREQ("3", gc(xdtoa(tinymath_roundf(2.5)))); -} - TEST(roundf, testCornerCases) { EXPECT_STREQ("-0", gc(xdtoa(tinymath_roundf(-0.0)))); EXPECT_STREQ("nan", gc(xdtoa(tinymath_roundf(NAN)))); diff --git a/test/libc/tinymath/test.mk b/test/libc/tinymath/test.mk index 1f10bf02..3cab2a52 100644 --- a/test/libc/tinymath/test.mk +++ b/test/libc/tinymath/test.mk @@ -5,17 +5,20 @@ PKGS += TEST_LIBC_TINYMATH TEST_LIBC_TINYMATH_SRCS := $(wildcard test/libc/tinymath/*.c) TEST_LIBC_TINYMATH_SRCS_TEST = $(filter %_test.c,$(TEST_LIBC_TINYMATH_SRCS)) -TEST_LIBC_TINYMATH_COMS = $(TEST_LIBC_TINYMATH_OBJS:%.o=%.com) TEST_LIBC_TINYMATH_OBJS = \ $(TEST_LIBC_TINYMATH_SRCS:%=o/$(MODE)/%.zip.o) \ $(TEST_LIBC_TINYMATH_SRCS:%.c=o/$(MODE)/%.o) +TEST_LIBC_TINYMATH_COMS = \ + $(TEST_LIBC_TINYMATH_SRCS:%.c=o/$(MODE)/%.com) + TEST_LIBC_TINYMATH_BINS = \ $(TEST_LIBC_TINYMATH_COMS) \ $(TEST_LIBC_TINYMATH_COMS:%=%.dbg) -TEST_LIBC_TINYMATH_TESTS = $(TEST_LIBC_TINYMATH_SRCS_TEST:%.c=o/$(MODE)/%.com.ok) +TEST_LIBC_TINYMATH_TESTS = \ + $(TEST_LIBC_TINYMATH_SRCS_TEST:%.c=o/$(MODE)/%.com.ok) TEST_LIBC_TINYMATH_CHECKS = \ $(TEST_LIBC_TINYMATH_SRCS_TEST:%.c=o/$(MODE)/%.com.runs) diff --git a/test/libc/unicode/test.mk b/test/libc/unicode/test.mk index b75762dd..305a2883 100644 --- a/test/libc/unicode/test.mk +++ b/test/libc/unicode/test.mk @@ -5,12 +5,14 @@ PKGS += TEST_LIBC_UNICODE TEST_LIBC_UNICODE_SRCS := $(wildcard test/libc/unicode/*.c) TEST_LIBC_UNICODE_SRCS_TEST = $(filter %_test.c,$(TEST_LIBC_UNICODE_SRCS)) -TEST_LIBC_UNICODE_COMS = $(TEST_LIBC_UNICODE_OBJS:%.o=%.com) TEST_LIBC_UNICODE_OBJS = \ $(TEST_LIBC_UNICODE_SRCS:%=o/$(MODE)/%.zip.o) \ $(TEST_LIBC_UNICODE_SRCS:%.c=o/$(MODE)/%.o) +TEST_LIBC_UNICODE_COMS = \ + $(TEST_LIBC_UNICODE_SRCS:%.c=o/$(MODE)/%.com) + TEST_LIBC_UNICODE_BINS = \ $(TEST_LIBC_UNICODE_COMS) \ $(TEST_LIBC_UNICODE_COMS:%=%.dbg) diff --git a/test/libc/unicode/wcwidth_test.c b/test/libc/unicode/wcwidth_test.c index b644c4fa..c26fc839 100644 --- a/test/libc/unicode/wcwidth_test.c +++ b/test/libc/unicode/wcwidth_test.c @@ -61,21 +61,6 @@ TEST(wcwidth, testCjkWidesAndCombiningLowLines_widthIsNotLength) { /*────────────────────────────────────────────────────┴─*/ } -TEST(strwidth, testEmoji_cosmoHelpsYouBuildInclusiveProductsEasily) { - /* ┌─If this line is solid your terminal - │ is respectful and inclusive towards - │ our friends w/ rich and interesting - │ backgrounds that aren't Anglo-Saxon - │ - │ ┌─This line being solid, means your - │ │ terminal needs a patch to address - │ │ issues concerning racism - │ │ - ──────────────────────────────────────┼─┼─*/ - EXPECT_EQ(02, wcswidth(/**/ L"👦🏿" /*- │ - */)); - /*────────────────────────────────────┼─┼─*/ -} - TEST(strwidth, tab) { EXPECT_EQ(32, strwidth("mov 0x0(%rip),%rcx \t")); } diff --git a/test/libc/x/test.mk b/test/libc/x/test.mk index c0415657..0d4e70af 100644 --- a/test/libc/x/test.mk +++ b/test/libc/x/test.mk @@ -5,12 +5,14 @@ PKGS += TEST_LIBC_X TEST_LIBC_X_SRCS := $(wildcard test/libc/x/*.c) TEST_LIBC_X_SRCS_TEST = $(filter %_test.c,$(TEST_LIBC_X_SRCS)) -TEST_LIBC_X_COMS = $(TEST_LIBC_X_OBJS:%.o=%.com) TEST_LIBC_X_OBJS = \ $(TEST_LIBC_X_SRCS:%=o/$(MODE)/%.zip.o) \ $(TEST_LIBC_X_SRCS:%.c=o/$(MODE)/%.o) +TEST_LIBC_X_COMS = \ + $(TEST_LIBC_X_SRCS:%.c=o/$(MODE)/%.com) + TEST_LIBC_X_BINS = \ $(TEST_LIBC_X_COMS) \ $(TEST_LIBC_X_COMS:%=%.dbg) @@ -29,7 +31,8 @@ TEST_LIBC_X_DIRECTDEPS = \ LIBC_RUNTIME \ LIBC_X \ LIBC_STUBS \ - LIBC_TESTLIB + LIBC_TESTLIB \ + THIRD_PARTY_DTOA TEST_LIBC_X_DEPS := \ $(call uniq,$(foreach x,$(TEST_LIBC_X_DIRECTDEPS),$($(x)))) diff --git a/test/libc/xed/test.mk b/test/libc/xed/test.mk index f58cc65f..b1c9b78a 100644 --- a/test/libc/xed/test.mk +++ b/test/libc/xed/test.mk @@ -51,12 +51,14 @@ $(TEST_LIBC_XED_TESTLIB_A): \ PKGS += TEST_LIBC_XED TEST_LIBC_XED_SRCS = $(filter %_test.c,$(TEST_LIBC_XED_FILES)) -TEST_LIBC_XED_COMS = $(TEST_LIBC_XED_OBJS:%.o=%.com) TEST_LIBC_XED_OBJS = \ $(TEST_LIBC_XED_SRCS:%=o/$(MODE)/%.zip.o) \ $(TEST_LIBC_XED_SRCS:%.c=o/$(MODE)/%.o) +TEST_LIBC_XED_COMS = \ + $(TEST_LIBC_XED_SRCS:%.c=o/$(MODE)/%.com) + TEST_LIBC_XED_BINS = \ $(TEST_LIBC_XED_COMS) \ $(TEST_LIBC_XED_COMS:%=%.dbg) diff --git a/test/libc/xed/x86ild_lib.c b/test/libc/xed/x86ild_lib.c index b6252b50..02217553 100644 --- a/test/libc/xed/x86ild_lib.c +++ b/test/libc/xed/x86ild_lib.c @@ -41,7 +41,7 @@ testonly int ild(const char16_t *codez) { error = xed_instruction_length_decode( xed_decoded_inst_zero_set_mode(&xedd, XED_MACHINE_MODE_LONG_64), gc(unbingx86op(codez)), strlen16(codez) + 16); - return error == XED_ERROR_NONE ? xedd.decoded_length : -error; + return error == XED_ERROR_NONE ? xedd.length : -error; } /** @@ -53,7 +53,7 @@ testonly int ildreal(const char16_t *codez) { error = xed_instruction_length_decode( xed_decoded_inst_zero_set_mode(&xedd, XED_MACHINE_MODE_REAL), gc(unbingx86op(codez)), strlen16(codez) + 16); - return error == XED_ERROR_NONE ? xedd.decoded_length : -error; + return error == XED_ERROR_NONE ? xedd.length : -error; } /** @@ -65,5 +65,5 @@ testonly int ildlegacy(const char16_t *codez) { error = xed_instruction_length_decode( xed_decoded_inst_zero_set_mode(&xedd, XED_MACHINE_MODE_LEGACY_32), gc(unbingx86op(codez)), strlen16(codez) + 16); - return error == XED_ERROR_NONE ? xedd.decoded_length : -error; + return error == XED_ERROR_NONE ? xedd.length : -error; } diff --git a/test/libc/xed/x86ild_popular_i86_test.c b/test/libc/xed/x86ild_popular_i86_test.c index fc86101c..b271b356 100644 --- a/test/libc/xed/x86ild_popular_i86_test.c +++ b/test/libc/xed/x86ild_popular_i86_test.c @@ -1416,7 +1416,7 @@ TEST(x86ild, test_415C) { ISA_SET: I86 SHORT: pop r12 */ - EXPECT_EQ(2, ild(u"A╲")); + EXPECT_EQ(2, ild(u"A\\")); } TEST(x86ild, test_488B04C500000000) { diff --git a/test/libc/xed/x86ild_test.c b/test/libc/xed/x86ild_test.c index c24635f3..c58ee2c8 100644 --- a/test/libc/xed/x86ild_test.c +++ b/test/libc/xed/x86ild_test.c @@ -18,6 +18,7 @@ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/runtime/gc.h" +#include "libc/testlib/ezbench.h" #include "libc/testlib/testlib.h" #include "test/libc/xed/lib.h" #include "third_party/xed/x86.h" @@ -94,6 +95,24 @@ TEST(x86ild, testAmd3dnow) { 0, xed_instruction_length_decode( xed_decoded_inst_zero_set_mode(&xedd, XED_MACHINE_MODE_LEGACY_32), gc(unbingx86op(u"☼☼╚ª")), 4)); - ASSERT_EQ(true, xedd.operands.amd3dnow); - ASSERT_EQ(0xa6, xedd.operands.nominal_opcode); + ASSERT_EQ(true, (int)xedd.op.amd3dnow); + ASSERT_EQ(0xa6, xedd.op.opcode); +} + +TEST(x86ild, testPopToMemory) { + ASSERT_EQ(3, ild(u"ÅF◘")); /* 8f 46 08 */ +} + +TEST(x86ild, testFinit) { + ASSERT_EQ(1, ild(u"¢█π")); /* 9B DB E3: fwait */ + ASSERT_EQ(2, ild(u"█π")); /* DB E3: fninit */ +} + +BENCH(x86ild, bench) { + uint8_t *x = gc(unbingx86op(u"b▓m◘Pî±   ►")); + struct XedDecodedInst xedd; + EZBENCH2("x86ild", donothing, + xed_instruction_length_decode( + xed_decoded_inst_zero_set_mode(&xedd, XED_MACHINE_MODE_LONG_64), + x, XED_MAX_INSTRUCTION_BYTES)); } diff --git a/test/net/http/test.mk b/test/net/http/test.mk index 17a77729..ba9c9065 100644 --- a/test/net/http/test.mk +++ b/test/net/http/test.mk @@ -5,13 +5,15 @@ PKGS += TEST_NET_HTTP TEST_NET_HTTP_SRCS := $(wildcard test/net/http/*.c) TEST_NET_HTTP_SRCS_TEST = $(filter %_test.c,$(TEST_NET_HTTP_SRCS)) -TEST_NET_HTTP_COMS = $(TEST_NET_HTTP_OBJS:%.o=%.com) TEST_NET_HTTP_BINS = $(TEST_NET_HTTP_COMS) $(TEST_NET_HTTP_COMS:%=%.dbg) TEST_NET_HTTP_OBJS = \ $(TEST_NET_HTTP_SRCS:%=o/$(MODE)/%.zip.o) \ $(TEST_NET_HTTP_SRCS:%.c=o/$(MODE)/%.o) +TEST_NET_HTTP_COMS = \ + $(TEST_NET_HTTP_SRCS:%.c=o/$(MODE)/%.com) + TEST_NET_HTTP_TESTS = \ $(TEST_NET_HTTP_SRCS_TEST:%.c=o/$(MODE)/%.com.ok) diff --git a/test/net/http/uriparse_test.c b/test/net/http/uriparse_test.c index 77370020..8650f5ea 100644 --- a/test/net/http/uriparse_test.c +++ b/test/net/http/uriparse_test.c @@ -18,6 +18,7 @@ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/bits/bits.h" +#include "libc/bits/initializer.h" #include "libc/errno.h" #include "libc/log/log.h" #include "libc/macros.h" @@ -69,7 +70,7 @@ static struct UriMem { struct UriKeyval params[4], queries[4]; } urimem_; -INITIALIZER(100, _init_uri, { +static textstartup void init() { uri.segs.n = ARRAYLEN(urimem_.segs); uri.segs.p = urimem_.segs; uri.params.n = ARRAYLEN(urimem_.params); @@ -78,7 +79,9 @@ INITIALIZER(100, _init_uri, { uri.queries.p = urimem_.queries; uri.paramsegs.n = ARRAYLEN(urimem_.paramsegs); uri.paramsegs.p = urimem_.paramsegs; -}) +} + +const void *const g_name_ctor[] initarray = {init}; TEST(uriparse, sipPstnUri) { EXPECT_NE(-1, URIPARSE("sip:+12125650666")); diff --git a/test/tool/build/lib/alu_test.c b/test/tool/build/lib/alu_test.c new file mode 100644 index 00000000..36fbd73a --- /dev/null +++ b/test/tool/build/lib/alu_test.c @@ -0,0 +1,106 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/assert.h" +#include "libc/limits.h" +#include "libc/runtime/runtime.h" +#include "libc/stdio/stdio.h" +#include "libc/testlib/testlib.h" +#include "test/tool/build/lib/optest.h" +#include "tool/build/lib/alu.h" +#include "tool/build/lib/flags.h" + +#define NATIVE_ALU2(MODE, INSTRUCTION) \ + asm("pushf\n\t" \ + "andl\t%3,(%%rsp)\n\t" \ + "orl\t%4,(%%rsp)\n\t" \ + "popf\n\t" INSTRUCTION "\t%" MODE "2,%" MODE "0\n\t" \ + "pushf\n\t" \ + "pop\t%q1" \ + : "+r"(x), "=rm"(*f) \ + : "r"(y), "i"(~FMASK), "r"(*f & FMASK) \ + : "cc") + +#define NATIVE_ALU2_ANYBITS(INSTRUCTION, MUTATING) \ + switch (w) { \ + case 0: \ + NATIVE_ALU2("b", INSTRUCTION); \ + if (MUTATING) x &= 0xff; \ + return x; \ + case 1: \ + NATIVE_ALU2("w", INSTRUCTION); \ + if (MUTATING) x &= 0xffff; \ + return x; \ + case 2: \ + NATIVE_ALU2("k", INSTRUCTION); \ + return x; \ + case 3: \ + NATIVE_ALU2("q", INSTRUCTION); \ + return x; \ + default: \ + abort(); \ + } + +int64_t RunGolden(char w, int h, uint64_t x, uint64_t y, uint32_t *f) { + bool rw; + rw = !(h & ALU_TEST); + switch (h & 7) { + case ALU_OR: + NATIVE_ALU2_ANYBITS("or", rw); + case ALU_AND: + if (rw) { + NATIVE_ALU2_ANYBITS("and", rw); + } else { + NATIVE_ALU2_ANYBITS("test", rw); + } + case ALU_XOR: + NATIVE_ALU2_ANYBITS("xor", rw); + case ALU_SBB: + NATIVE_ALU2_ANYBITS("sbb", rw); + case ALU_CMP: + rw = false; + NATIVE_ALU2_ANYBITS("cmp", rw); + case ALU_SUB: + NATIVE_ALU2_ANYBITS("sub", rw); + case ALU_ADC: + NATIVE_ALU2_ANYBITS("adc", rw); + case ALU_ADD: + NATIVE_ALU2_ANYBITS("add", rw); + default: + abort(); + } +} + +const uint8_t kAluOps[] = { + ALU_ADD, ALU_OR, ALU_ADC, ALU_SBB, ALU_AND, ALU_SUB, ALU_XOR, ALU_CMP, ALU_AND | ALU_TEST, +}; + +const char *const kAluNames[] = { + [ALU_ADD] = "add", [ALU_OR] = "or", [ALU_ADC] = "adc", + [ALU_SBB] = "sbb", [ALU_AND] = "and", [ALU_SUB] = "sub", + [ALU_XOR] = "xor", [ALU_CMP] = "cmp", [ALU_AND | ALU_TEST] = "test", +}; + +int64_t RunOpTest(char w, int h, uint64_t x, uint64_t y, uint32_t *f) { + return Alu(w, h, x, y, f); +} + +TEST(alu, test) { + RunOpTests(kAluOps, ARRAYLEN(kAluOps), kAluNames); +} diff --git a/test/tool/build/lib/bitscan_test.c b/test/tool/build/lib/bitscan_test.c new file mode 100644 index 00000000..babab3dd --- /dev/null +++ b/test/tool/build/lib/bitscan_test.c @@ -0,0 +1,112 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/macros.h" +#include "libc/testlib/testlib.h" +#include "test/tool/build/lib/numbers.h" +#include "tool/build/lib/bitscan.h" +#include "tool/build/lib/flags.h" + +#define OSZ 00000000040 +#define REXW 00000004000 + +struct Machine m[1]; +struct XedDecodedInst xedd[1]; + +void SetUp(void) { + m->xedd = xedd; +} + +TEST(bsr64, test) { + bool zf; + uint64_t i, w, x, a, b; + m->xedd->op.rde = REXW; + for (i = 0; i < ARRAYLEN(kNumbers); ++i) { + x = kNumbers[i]; + a = AluBsr(m, 0, x); + asm("bsrq\t%2,%0" : "=r"(b), "=@ccz"(zf) : "r"(x)); + ASSERT_EQ(zf, GetFlag(m->flags, FLAGS_ZF)); + if (!zf) ASSERT_EQ(a, b); + } +} + +TEST(bsr32, test) { + bool zf; + uint32_t i, w, x, a, b; + m->xedd->op.rde = 0; + for (i = 0; i < ARRAYLEN(kNumbers); ++i) { + x = kNumbers[i]; + a = AluBsr(m, 0, x); + asm("bsrl\t%2,%0" : "=r"(b), "=@ccz"(zf) : "r"(x)); + ASSERT_EQ(zf, GetFlag(m->flags, FLAGS_ZF)); + if (!zf) ASSERT_EQ(a, b); + } +} + +TEST(bsr16, test) { + bool zf; + uint16_t i, w, x, a, b; + m->xedd->op.rde = OSZ; + for (i = 0; i < ARRAYLEN(kNumbers); ++i) { + x = kNumbers[i]; + a = AluBsr(m, 0, x); + asm("bsrw\t%2,%0" : "=r"(b), "=@ccz"(zf) : "r"(x)); + ASSERT_EQ(zf, GetFlag(m->flags, FLAGS_ZF)); + if (!zf) ASSERT_EQ(a, b); + } +} + +TEST(bsf64, test) { + bool zf; + uint64_t i, w, x, a, b; + m->xedd->op.rde = REXW; + for (i = 0; i < ARRAYLEN(kNumbers); ++i) { + x = kNumbers[i]; + a = AluBsf(m, 0, x); + asm("bsfq\t%2,%0" : "=r"(b), "=@ccz"(zf) : "r"(x)); + ASSERT_EQ(zf, GetFlag(m->flags, FLAGS_ZF)); + if (!zf) ASSERT_EQ(a, b); + } +} + +TEST(bsf32, test) { + bool zf; + uint32_t i, w, x, a, b; + m->xedd->op.rde = 0; + for (i = 0; i < ARRAYLEN(kNumbers); ++i) { + x = kNumbers[i]; + a = AluBsf(m, 0, x); + asm("bsfl\t%2,%0" : "=r"(b), "=@ccz"(zf) : "r"(x)); + ASSERT_EQ(zf, GetFlag(m->flags, FLAGS_ZF)); + if (!zf) ASSERT_EQ(a, b); + } +} + +TEST(bsf16, test) { + bool zf; + uint16_t i, w, x, a, b; + m->xedd->op.rde = OSZ; + for (i = 0; i < ARRAYLEN(kNumbers); ++i) { + x = kNumbers[i]; + a = AluBsf(m, 0, x); + asm("bsfw\t%2,%0" : "=r"(b), "=@ccz"(zf) : "r"(x)); + ASSERT_EQ(zf, GetFlag(m->flags, FLAGS_ZF)); + if (!zf) ASSERT_EQ(a, b, "%#lx", x); + } +} diff --git a/test/tool/build/lib/bsu_test.c b/test/tool/build/lib/bsu_test.c new file mode 100644 index 00000000..75245e53 --- /dev/null +++ b/test/tool/build/lib/bsu_test.c @@ -0,0 +1,225 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/macros.h" +#include "libc/runtime/runtime.h" +#include "libc/stdio/stdio.h" +#include "libc/testlib/testlib.h" +#include "test/tool/build/lib/numbers.h" +#include "test/tool/build/lib/optest.h" +#include "tool/build/lib/alu.h" +#include "tool/build/lib/flags.h" +#include "tool/build/lib/machine.h" + +#define NATIVE_ALU2(MODE, INSTRUCTION) \ + asm("pushf\n\t" \ + "andl\t%3,(%%rsp)\n\t" \ + "orl\t%4,(%%rsp)\n\t" \ + "popf\n\t" INSTRUCTION "\t%b2,%" MODE "0\n\t" \ + "pushf\n\t" \ + "pop\t%q1" \ + : "+r"(x), "=r"(*f) \ + : "c"(y), "i"(~FMASK), "r"(*f & FMASK) \ + : "cc") + +#define NATIVE_ALU2_ANYBITS(INSTRUCTION, MUTATING) \ + switch (w) { \ + case 0: \ + NATIVE_ALU2("b", INSTRUCTION); \ + if (MUTATING) x &= 0xff; \ + return x; \ + case 1: \ + NATIVE_ALU2("w", INSTRUCTION); \ + if (MUTATING) x &= 0xffff; \ + return x; \ + case 2: \ + NATIVE_ALU2("k", INSTRUCTION); \ + return x; \ + case 3: \ + NATIVE_ALU2("q", INSTRUCTION); \ + return x; \ + default: \ + abort(); \ + } + +int64_t RunGolden(char w, int h, uint64_t x, uint64_t y, uint32_t *f) { + switch (h & 7) { + case BSU_ROR: + NATIVE_ALU2_ANYBITS("ror", true); + case BSU_SHL: + NATIVE_ALU2_ANYBITS("shl", true); + case BSU_SAL: + NATIVE_ALU2_ANYBITS("sal", true); + case BSU_RCR: + NATIVE_ALU2_ANYBITS("rcr", true); + case BSU_SAR: + NATIVE_ALU2_ANYBITS("sar", true); + case BSU_SHR: + NATIVE_ALU2_ANYBITS("shr", true); + case BSU_RCL: + NATIVE_ALU2_ANYBITS("rcl", true); + case BSU_ROL: + NATIVE_ALU2_ANYBITS("rol", true); + default: + abort(); + } +} + +const uint8_t kBsuOps[] = { + BSU_SHR, BSU_SHL, BSU_ROL, BSU_ROR, BSU_RCL, + BSU_RCR, BSU_SHL, BSU_SAL, BSU_SAR, +}; + +const char *const kBsuNames[] = { + [BSU_ROL] = "rol", [BSU_ROR] = "ror", [BSU_RCL] = "rcl", [BSU_RCR] = "rcr", + [BSU_SHL] = "shl", [BSU_SHR] = "shr", [BSU_SAL] = "sal", [BSU_SAR] = "sar", +}; + +int64_t RunOpTest(char w, int h, uint64_t x, uint64_t y, uint32_t *f) { + return Bsu(w, h, x, y, f); +} + +void FixupUndefOpTestFlags(char w, int h, uint64_t x, uint64_t y, + uint32_t goldenflags, uint32_t *flags) { + y &= w == 3 ? 0x3F : 0x1F; + if (y != 0 && y != 1) { + *flags = SetFlag(*flags, FLAGS_OF, GetFlag(goldenflags, FLAGS_OF)); + } +} + +TEST(bsu, test) { + RunOpTests(kBsuOps, ARRAYLEN(kBsuOps), kBsuNames); +} + +TEST(shrd32, smoke) { + int i, j, k; + uint8_t b, cf, of; + uint32_t x, y, z1, f, z2, unflag; + for (i = 0; i < ARRAYLEN(kNumbers); ++i) { + x = kNumbers[i]; + for (j = 0; j < ARRAYLEN(kNumbers); ++j) { + y = kNumbers[j]; + for (k = 0; k < ARRAYLEN(kNumbers); ++k) { + f = 0; + cf = 0; + of = 0; + b = kNumbers[k]; + z1 = BsuDoubleShift(2, x, y, b, true, &f); + z2 = x; + asm("xor\t%1,%1\n\t" + "shrd\t%5,%4,%0" + : "+rm"(z2), "=&r"(unflag), "=@ccc"(cf), "=@cco"(of) + : "r"(y), "c"(b) + : "cc"); + ASSERT_EQ(z2, z1); + ASSERT_EQ(cf, GetFlag(f, FLAGS_CF)); + if (b <= 1) ASSERT_EQ(of, GetFlag(f, FLAGS_OF)); + } + } + } +} + +TEST(shld16, smoke) { + uint32_t f; + int i, j, k; + uint8_t b, cf, of; + uint16_t x, y, z1, z2, unflag; + for (i = 0; i < ARRAYLEN(kNumbers); ++i) { + x = kNumbers[i]; + for (j = 0; j < ARRAYLEN(kNumbers); ++j) { + y = kNumbers[j]; + for (k = 0; k < ARRAYLEN(kNumbers); ++k) { + f = 0; + cf = 0; + of = 0; + b = kNumbers[k]; + if (b > 16) continue; + z1 = BsuDoubleShift(1, x, y, b, false, &f); + z2 = x; + asm("xor\t%1,%1\n\t" + "shldw\t%5,%4,%0" + : "+rm"(z2), "=&r"(unflag), "=@ccc"(cf), "=@cco"(of) + : "r"(y), "c"(b) + : "cc"); + ASSERT_EQ(z2, z1); + ASSERT_EQ(cf, GetFlag(f, FLAGS_CF)); + if (b <= 1) ASSERT_EQ(of, GetFlag(f, FLAGS_OF)); + } + } + } +} + +TEST(shld32, smoke) { + int i, j, k; + uint8_t b, cf, of; + uint32_t x, y, z1, f, z2, unflag; + for (i = 0; i < ARRAYLEN(kNumbers); ++i) { + x = kNumbers[i]; + for (j = 0; j < ARRAYLEN(kNumbers); ++j) { + y = kNumbers[j]; + for (k = 0; k < ARRAYLEN(kNumbers); ++k) { + f = 0; + cf = 0; + of = 0; + b = kNumbers[k]; + if (b > 32) continue; + z1 = BsuDoubleShift(2, x, y, b, false, &f); + z2 = x; + asm("xor\t%1,%1\n\t" + "shld\t%5,%4,%0" + : "+rm"(z2), "=&r"(unflag), "=@ccc"(cf), "=@cco"(of) + : "r"(y), "c"(b) + : "cc"); + ASSERT_EQ(z2, z1); + ASSERT_EQ(cf, GetFlag(f, FLAGS_CF)); + if (b <= 1) ASSERT_EQ(of, GetFlag(f, FLAGS_OF)); + } + } + } +} + +TEST(shld64, smoke) { + uint32_t f; + int i, j, k; + uint8_t b, cf, of; + uint64_t x, y, z1, z2, unflag; + for (i = 0; i < ARRAYLEN(kNumbers); ++i) { + x = kNumbers[i]; + for (j = 0; j < ARRAYLEN(kNumbers); ++j) { + y = kNumbers[j]; + for (k = 0; k < ARRAYLEN(kNumbers); ++k) { + f = 0; + cf = 0; + of = 0; + b = kNumbers[k]; + if (b > 64) continue; + z1 = BsuDoubleShift(3, x, y, b, false, &f); + z2 = x; + asm("xor\t%1,%1\n\t" + "shldq\t%5,%4,%0" + : "+rm"(z2), "=&r"(unflag), "=@ccc"(cf), "=@cco"(of) + : "r"(y), "c"(b) + : "cc"); + ASSERT_EQ(z2, z1); + ASSERT_EQ(cf, GetFlag(f, FLAGS_CF)); + if (b <= 1) ASSERT_EQ(of, GetFlag(f, FLAGS_OF)); + } + } + } +} diff --git a/test/tool/build/lib/disinst_test.c b/test/tool/build/lib/disinst_test.c new file mode 100644 index 00000000..d1e647ff --- /dev/null +++ b/test/tool/build/lib/disinst_test.c @@ -0,0 +1,88 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/testlib/testlib.h" +#include "tool/build/lib/dis.h" + +char b1[64]; +char b2[64]; +struct Dis d[1]; +struct DisBuilder b = {d, d->xedd, 0}; + +TEST(DisInst, testInt3) { + uint8_t op[] = {0xcc}; + xed_decoded_inst_zero_set_mode(d->xedd, XED_MACHINE_MODE_LONG_64); + ASSERT_EQ(0, xed_instruction_length_decode(d->xedd, op, sizeof(op))); + DisInst(b, b1, DisSpec(d->xedd, b2)); + EXPECT_STREQ("int3 ", b1); +} + +TEST(DisInst, testImmMem_needsSuffix) { + uint8_t op[] = {0x80, 0x3c, 0x07, 0x00}; + xed_decoded_inst_zero_set_mode(d->xedd, XED_MACHINE_MODE_LONG_64); + ASSERT_EQ(0, xed_instruction_length_decode(d->xedd, op, sizeof(op))); + DisInst(b, b1, DisSpec(d->xedd, b2)); + EXPECT_STREQ("cmpb $0,(%rdi,%rax)", b1); +} + +TEST(DisInst, testImmReg_doesntNeedSuffix) { + uint8_t op[] = {0xb8, 0x08, 0x70, 0x40, 0x00}; + xed_decoded_inst_zero_set_mode(d->xedd, XED_MACHINE_MODE_LONG_64); + ASSERT_EQ(0, xed_instruction_length_decode(d->xedd, op, sizeof(op))); + DisInst(b, b1, DisSpec(d->xedd, b2)); + EXPECT_STREQ("mov $0x407008,%eax", b1); +} + +TEST(DisInst, testPuttingOnTheRiz) { + static uint8_t ops[][15] = { + {0x8d, 0b00110100, 0b00100110}, + {0x67, 0x8d, 0b00110100, 0b11100110}, + {0x67, 0x8d, 0b10110100, 0b11100101, 0, 0, 0, 0}, + {0x8d, 0b00110100, 0b11100101, 0x37, 0x13, 0x03, 0}, + {0x8d, 0b10110100, 0b11100101, 0, 0, 0, 0}, + }; + xed_decoded_inst_zero_set_mode(d->xedd, XED_MACHINE_MODE_LONG_64); + ASSERT_EQ(0, xed_instruction_length_decode(d->xedd, ops[0], sizeof(ops[0]))); + DisInst(b, b1, DisSpec(d->xedd, b2)); + EXPECT_STREQ("lea (%rsi),%esi", b1); + xed_decoded_inst_zero_set_mode(d->xedd, XED_MACHINE_MODE_LONG_64); + ASSERT_EQ(0, xed_instruction_length_decode(d->xedd, ops[1], sizeof(ops[1]))); + DisInst(b, b1, DisSpec(d->xedd, b2)); + EXPECT_STREQ("lea (%esi,%eiz,8),%esi", b1); + xed_decoded_inst_zero_set_mode(d->xedd, XED_MACHINE_MODE_LONG_64); + ASSERT_EQ(0, xed_instruction_length_decode(d->xedd, ops[2], sizeof(ops[2]))); + DisInst(b, b1, DisSpec(d->xedd, b2)); + EXPECT_STREQ("lea 0(%ebp,%eiz,8),%esi", b1); + xed_decoded_inst_zero_set_mode(d->xedd, XED_MACHINE_MODE_LONG_64); + ASSERT_EQ(0, xed_instruction_length_decode(d->xedd, ops[3], sizeof(ops[3]))); + DisInst(b, b1, DisSpec(d->xedd, b2)); + EXPECT_STREQ("lea 0x31337,%esi", b1); + xed_decoded_inst_zero_set_mode(d->xedd, XED_MACHINE_MODE_LONG_64); + ASSERT_EQ(0, xed_instruction_length_decode(d->xedd, ops[4], sizeof(ops[4]))); + DisInst(b, b1, DisSpec(d->xedd, b2)); + EXPECT_STREQ("lea 0(%rbp,%riz,8),%esi", b1); +} + +TEST(DisInst, testSibIndexOnly) { + uint8_t op[] = {76, 141, 4, 141, 0, 0, 0, 0}; /* lea 0x0(,%rcx,4),%r8 */ + xed_decoded_inst_zero_set_mode(d->xedd, XED_MACHINE_MODE_LONG_64); + ASSERT_EQ(0, xed_instruction_length_decode(d->xedd, op, sizeof(op))); + DisInst(b, b1, DisSpec(d->xedd, b2)); + EXPECT_STREQ("lea 0(,%rcx,4),%r8", b1); +} diff --git a/test/tool/build/lib/divmul_test.c b/test/tool/build/lib/divmul_test.c new file mode 100644 index 00000000..f1831b66 --- /dev/null +++ b/test/tool/build/lib/divmul_test.c @@ -0,0 +1,606 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/bits/progn.h" +#include "libc/calls/struct/sigaction.h" +#include "libc/log/check.h" +#include "libc/runtime/gc.h" +#include "libc/sysv/consts/sa.h" +#include "libc/sysv/consts/sig.h" +#include "libc/testlib/ezbench.h" +#include "libc/testlib/testlib.h" +#include "libc/x/x.h" +#include "tool/build/lib/divmul.h" +#include "tool/build/lib/endian.h" +#include "tool/build/lib/flags.h" + +#define CX 1 +#define OSZ 00000000040 +#define REXW 00000004000 +#define RM(x) (0000000700 & ((x) << 006)) +#define MOD(x) (0060000000 & ((x) << 026)) + +jmp_buf sigfpejmp; +struct Machine m[1]; +struct sigaction oldsigfpe[1]; +struct XedDecodedInst xedd[1]; + +void OnSigFpe(void) { + /* ProTip: gdb -ex 'handle SIGFPE nostop noprint pass' */ + longjmp(sigfpejmp, 1); +} + +void SetUp(void) { + m->xedd = xedd; + InitMachine(m); + CHECK_NE(-1, xsigaction(SIGFPE, OnSigFpe, SA_NODEFER, 0, oldsigfpe)); +} + +void TearDown(void) { + m->xedd = xedd; + CHECK_NE(-1, sigaction(SIGFPE, oldsigfpe, NULL)); +} + +TEST(imul8, test) { + static const uint8_t A[] = {0x00, 0x01, 0x80, 0x7F, 0x81, 0x7E, 0xFF, 0xBF}; + int i, j; + int16_t ax; + bool cf, of; + m->xedd->op.rde = MOD(3) | RM(CX); + for (i = 0; i < ARRAYLEN(A); ++i) { + for (j = 0; j < ARRAYLEN(A); ++j) { + Write8(m->ax, A[i]); + Write8(m->cx, A[j]); + OpMulAxAlEbSigned(m); + asm volatile("imulb\t%3" + : "=a"(ax), "=@ccc"(cf), "=@cco"(of) + : "q"(A[j]), "0"(A[i]) + : "cc"); + EXPECT_EQ(ax, (int16_t)Read16(m->ax)); + EXPECT_EQ(cf, GetFlag(m->flags, FLAGS_CF)); + EXPECT_EQ(of, GetFlag(m->flags, FLAGS_OF)); + } + } +} + +TEST(imul16, test) { + static const uint16_t A[] = {0x0000, 0x0001, 0x8000, 0x7FFF, 0x8001, 0x7FFE, + 0xFFFF, 0xBeef, 0x00b5, 0x00b6, 0xb504, 0xb505}; + int i, j; + bool cf, of; + uint16_t dx, ax; + m->xedd->op.rde = OSZ | MOD(3) | RM(CX); + for (i = 0; i < ARRAYLEN(A); ++i) { + for (j = 0; j < ARRAYLEN(A); ++j) { + Write16(m->ax, A[i]); + Write16(m->cx, A[j]); + OpMulRdxRaxEvqpSigned(m); + asm("imulw\t%4" + : "=d"(dx), "=a"(ax), "=@ccc"(cf), "=@cco"(of) + : "r"(A[j]), "1"(A[i]) + : "cc"); + EXPECT_EQ((int32_t)((uint32_t)dx << 16 | ax), + (int32_t)((uint32_t)Read16(m->dx) << 16 | Read16(m->ax))); + EXPECT_EQ(cf, GetFlag(m->flags, FLAGS_CF)); + EXPECT_EQ(of, GetFlag(m->flags, FLAGS_OF)); + } + } +} + +TEST(imul32, test) { + static const uint32_t A[] = {0x00000000, 0x00000001, 0x80000000, 0x7FFFFFFF, + 0x80000001, 0x7FFFFFFE, 0xFFFFFFFF, 0xDeadBeef, + 0x000000b6, 0x0000b504, 0x0000b505, 0xb504f334}; + int i, j; + bool cf, of; + uint32_t dx, ax; + m->xedd->op.rde = MOD(3) | RM(CX); + for (i = 0; i < ARRAYLEN(A); ++i) { + for (j = 0; j < ARRAYLEN(A); ++j) { + Write32(m->ax, A[i]); + Write32(m->cx, A[j]); + OpMulRdxRaxEvqpSigned(m); + asm("imull\t%4" + : "=d"(dx), "=a"(ax), "=@ccc"(cf), "=@cco"(of) + : "r"(A[j]), "1"(A[i]) + : "cc"); + EXPECT_EQ((int64_t)((uint64_t)dx << 32 | ax), + (int64_t)((uint64_t)Read32(m->dx) << 32 | Read32(m->ax))); + EXPECT_EQ(cf, GetFlag(m->flags, FLAGS_CF)); + EXPECT_EQ(of, GetFlag(m->flags, FLAGS_OF)); + } + } +} + +TEST(imul64, test) { + static const uint64_t A[] = {0x00000000, 0x00000001, 0x80000000, 0x7FFFFFFF, + 0x80000001, 0x7FFFFFFE, 0xFFFFFFFF, 0xDeadBeef, + 0x000000b6, 0x0000b504, 0x0000b505, 0xb504f334}; + int i, j; + bool cf, of; + uint64_t dx, ax; + m->xedd->op.rde = REXW | MOD(3) | RM(CX); + for (i = 0; i < ARRAYLEN(A); ++i) { + for (j = 0; j < ARRAYLEN(A); ++j) { + Write64(m->ax, A[i]); + Write64(m->cx, A[j]); + OpMulRdxRaxEvqpSigned(m); + asm("imulq\t%4" + : "=d"(dx), "=a"(ax), "=@ccc"(cf), "=@cco"(of) + : "r"(A[j]), "1"(A[i]) + : "cc"); + EXPECT_EQ((int128_t)((uint128_t)dx << 64 | ax), + (int128_t)((uint128_t)Read64(m->dx) << 64 | Read64(m->ax))); + EXPECT_EQ(cf, GetFlag(m->flags, FLAGS_CF)); + EXPECT_EQ(of, GetFlag(m->flags, FLAGS_OF)); + } + } +} + +TEST(mul8, test) { + static const uint8_t A[] = {0x00, 0x01, 0x80, 0x7F, 0x81, 0x7E, 0xFF, 0xb6}; + int i, j; + uint16_t ax; + bool cf, of; + m->xedd->op.rde = MOD(3) | RM(CX); + for (i = 0; i < ARRAYLEN(A); ++i) { + for (j = 0; j < ARRAYLEN(A); ++j) { + Write8(m->ax, A[i]); + Write8(m->cx, A[j]); + OpMulAxAlEbUnsigned(m); + asm volatile("mulb\t%3" + : "=a"(ax), "=@ccc"(cf), "=@cco"(of) + : "q"(A[j]), "0"(A[i]) + : "cc"); + EXPECT_EQ(ax, Read16(m->ax)); + EXPECT_EQ(cf, GetFlag(m->flags, FLAGS_CF)); + EXPECT_EQ(of, GetFlag(m->flags, FLAGS_OF)); + } + } +} + +TEST(mul16, test) { + static const uint16_t A[] = {0x0000, 0x0001, 0x8000, 0x7FFF, + 0x8001, 0x7FFE, 0xFFFF, 0x00b6}; + int i, j; + bool cf, of; + uint16_t dx, ax; + m->xedd->op.rde = OSZ | MOD(3) | RM(CX); + for (i = 0; i < ARRAYLEN(A); ++i) { + for (j = 0; j < ARRAYLEN(A); ++j) { + Write16(m->ax, A[i]); + Write16(m->cx, A[j]); + OpMulRdxRaxEvqpUnsigned(m); + asm("mulw\t%4" + : "=d"(dx), "=a"(ax), "=@ccc"(cf), "=@cco"(of) + : "r"(A[j]), "1"(A[i]) + : "cc"); + EXPECT_EQ((uint32_t)((uint32_t)dx << 16 | ax), + (uint32_t)((uint32_t)Read16(m->dx) << 16 | Read16(m->ax))); + EXPECT_EQ(cf, GetFlag(m->flags, FLAGS_CF)); + EXPECT_EQ(of, GetFlag(m->flags, FLAGS_OF)); + } + } +} + +TEST(mul32, test) { + static const uint32_t A[] = {0x00000000, 0x00000001, 0x80000000, 0x7FFFFFFF, + 0x80000001, 0x7FFFFFFE, 0xFFFFFFFF, 0x000000b5, + 0x000000b6, 0x0000b504, 0x0000b505, 0xb504f334}; + int i, j; + bool cf, of; + uint32_t dx, ax; + m->xedd->op.rde = MOD(3) | RM(CX); + for (i = 0; i < ARRAYLEN(A); ++i) { + for (j = 0; j < ARRAYLEN(A); ++j) { + Write32(m->ax, A[i]); + Write32(m->cx, A[j]); + OpMulRdxRaxEvqpUnsigned(m); + asm("mull\t%4" + : "=d"(dx), "=a"(ax), "=@ccc"(cf), "=@cco"(of) + : "r"(A[j]), "1"(A[i]) + : "cc"); + EXPECT_EQ((uint64_t)((uint64_t)dx << 32 | ax), + (uint64_t)((uint64_t)Read32(m->dx) << 32 | Read32(m->ax))); + EXPECT_EQ(cf, GetFlag(m->flags, FLAGS_CF)); + EXPECT_EQ(of, GetFlag(m->flags, FLAGS_OF)); + } + } +} + +TEST(mul64, test) { + static const uint64_t A[] = {0x00000000, 0x00000001, 0x80000000, 0x7FFFFFFF, + 0x80000001, 0x7FFFFFFE, 0xFFFFFFFF, 0x000000b6, + 0x0000b504, 0x0000b505, 0xb504f333, 0xb504f334}; + int i, j; + bool cf, of; + uint64_t dx, ax; + m->xedd->op.rde = REXW | MOD(3) | RM(CX); + for (i = 0; i < ARRAYLEN(A); ++i) { + for (j = 0; j < ARRAYLEN(A); ++j) { + Write64(m->ax, A[i]); + Write64(m->cx, A[j]); + OpMulRdxRaxEvqpUnsigned(m); + asm("mulq\t%4" + : "=d"(dx), "=a"(ax), "=@ccc"(cf), "=@cco"(of) + : "r"(A[j]), "1"(A[i]) + : "cc"); + EXPECT_EQ((uint128_t)((uint128_t)dx << 64 | ax), + (uint128_t)((uint128_t)Read64(m->dx) << 64 | Read64(m->ax))); + EXPECT_EQ(cf, GetFlag(m->flags, FLAGS_CF)); + EXPECT_EQ(of, GetFlag(m->flags, FLAGS_OF)); + } + } +} + +TEST(idiv8, test) { + static const uint8_t A[] = {0x00, 0x01, 0x80, 0x7F, 0x81, 0x7E, 0xFF, 0xBF}; + uint16_t remquo; + bool gotthrow, gotsigfpe; + int8_t i, j, k, w, x, a, b; + int8_t quotient, remainder; + m->xedd->op.rde = MOD(3) | RM(CX); + for (i = 0; i < ARRAYLEN(A); ++i) { + for (j = 0; j < ARRAYLEN(A); ++j) { + for (k = 0; k < ARRAYLEN(A); ++k) { + m->ax[1] = A[i]; + m->ax[0] = A[j]; + m->cx[0] = A[k]; + gotthrow = false; + gotsigfpe = false; + if (!setjmp(m->onhalt)) { + OpDivAlAhAxEbSigned(m); + } else { + gotthrow = true; + } + if (!setjmp(sigfpejmp)) { + asm("idivb\t%1" + : "=a"(remquo) + : "q"(A[k]), "0"((int16_t)(A[i] << 8 | A[j])) + : "cc"); + } else { + gotsigfpe = true; + } + EXPECT_EQ(gotsigfpe, gotthrow); + if (!gotsigfpe && !gotthrow) { + quotient = (int8_t)remquo; + remainder = (int8_t)(remquo >> 8); + EXPECT_EQ(quotient, (int8_t)m->ax[0]); + EXPECT_EQ(remainder, (int8_t)m->ax[1]); + } + } + } + } +} + +TEST(idiv16, test) { + static const uint16_t A[] = {0x0000, 0x0001, 0x8000, 0x7FFF, + 0x8001, 0x7FFE, 0xFFFF, 0xBeef}; + bool gotthrow, gotsigfpe; + int16_t i, j, k, w, x, a, b; + int16_t quotient, remainder; + m->xedd->op.rde = OSZ | MOD(3) | RM(CX); + for (i = 0; i < ARRAYLEN(A); ++i) { + for (j = 0; j < ARRAYLEN(A); ++j) { + for (k = 0; k < ARRAYLEN(A); ++k) { + memcpy(m->dx, &A[i], 2); + memcpy(m->ax, &A[j], 2); + memcpy(m->cx, &A[k], 2); + if (!setjmp(m->onhalt)) { + gotthrow = false; + OpDivRdxRaxEvqpSigned(m); + } else { + gotthrow = true; + } + if (!setjmp(sigfpejmp)) { + gotsigfpe = false; + asm("idivw\t%2" + : "=d"(remainder), "=a"(quotient) + : "r"(A[k]), "0"(A[i]), "1"(A[j]) + : "cc"); + } else { + gotsigfpe = true; + } + EXPECT_EQ(gotsigfpe, gotthrow); + if (!gotsigfpe && !gotthrow) { + EXPECT_EQ(quotient, (int16_t)Read16(m->ax)); + EXPECT_EQ(remainder, (int16_t)Read16(m->dx)); + } + } + } + } +} + +TEST(idiv32, test) { + static const uint32_t A[] = {0x00000000, 0x00000001, 0x80000000, 0x7FFFFFFF, + 0x80000001, 0x7FFFFFFE, 0xFFFFFFFF, 0xDeadBeef}; + bool gotthrow, gotsigfpe; + int32_t i, j, k, w, x, a, b; + int32_t quotient, remainder; + m->xedd->op.rde = MOD(3) | RM(CX); + for (i = 0; i < ARRAYLEN(A); ++i) { + for (j = 0; j < ARRAYLEN(A); ++j) { + for (k = 0; k < ARRAYLEN(A); ++k) { + memcpy(m->dx, &A[i], 4); + memcpy(m->ax, &A[j], 4); + memcpy(m->cx, &A[k], 4); + if (!setjmp(m->onhalt)) { + gotthrow = false; + OpDivRdxRaxEvqpSigned(m); + } else { + gotthrow = true; + } + if (!setjmp(sigfpejmp)) { + gotsigfpe = false; + asm("idivl\t%2" + : "=d"(remainder), "=a"(quotient) + : "r"(A[k]), "0"(A[i]), "1"(A[j]) + : "cc"); + } else { + gotsigfpe = true; + } + EXPECT_EQ(gotsigfpe, gotthrow); + if (!gotsigfpe && !gotthrow) { + EXPECT_EQ(quotient, (int32_t)Read32(m->ax)); + EXPECT_EQ(remainder, (int32_t)Read32(m->dx)); + } + } + } + } +} + +TEST(idiv64, test) { + static const uint64_t A[] = {0x0000000000000000, 0x0000000000000001, + 0x8000000000000000, 0x7FFFFFFFFFFFFFFF, + 0x8000000000000001, 0x7FFFFFFFFFFFFFFE, + 0xFFFFFFFFFFFFFFFF, 0x00DeadBeefCafe00}; + bool gotthrow, gotsigfpe; + int64_t i, j, k, w, x, a, b; + int64_t quotient, remainder; + m->xedd->op.rde = REXW | MOD(3) | RM(CX); + for (i = 0; i < ARRAYLEN(A); ++i) { + for (j = 0; j < ARRAYLEN(A); ++j) { + for (k = 0; k < ARRAYLEN(A); ++k) { + memcpy(m->dx, &A[i], 8); + memcpy(m->ax, &A[j], 8); + memcpy(m->cx, &A[k], 8); + if (!setjmp(m->onhalt)) { + gotthrow = false; + OpDivRdxRaxEvqpSigned(m); + } else { + gotthrow = true; + } + if (!setjmp(sigfpejmp)) { + gotsigfpe = false; + asm("idivq\t%2" + : "=d"(remainder), "=a"(quotient) + : "r"(A[k]), "0"(A[i]), "1"(A[j]) + : "cc"); + } else { + gotsigfpe = true; + } + EXPECT_EQ(gotsigfpe, gotthrow); + if (!gotsigfpe && !gotthrow) { + EXPECT_EQ(quotient, (int64_t)Read64(m->ax)); + EXPECT_EQ(remainder, (int64_t)Read64(m->dx)); + } + } + } + } +} + +TEST(div, test) { + static const uint8_t A[] = {0x00, 0x01, 0x80, 0x7F, 0x81, 0x7E, 0xFF, 0xBF}; + uint16_t remquo; + bool gotthrow, gotsigfpe; + uint8_t i, j, k, w, x, a, b; + uint8_t quotient, remainder; + m->xedd->op.rde = MOD(3) | RM(CX); + for (i = 0; i < ARRAYLEN(A); ++i) { + for (j = 0; j < ARRAYLEN(A); ++j) { + for (k = 0; k < ARRAYLEN(A); ++k) { + m->ax[1] = A[i]; + m->ax[0] = A[j]; + m->cx[0] = A[k]; + gotthrow = false; + gotsigfpe = false; + if (!setjmp(m->onhalt)) { + OpDivAlAhAxEbUnsigned(m); + } else { + gotthrow = true; + } + if (!setjmp(sigfpejmp)) { + asm("divb\t%1" + : "=a"(remquo) + : "q"(A[k]), "0"((uint16_t)(A[i] << 8 | A[j])) + : "cc"); + } else { + gotsigfpe = true; + } + EXPECT_EQ(gotsigfpe, gotthrow); + if (!gotsigfpe && !gotthrow) { + quotient = (uint8_t)remquo; + remainder = (uint8_t)(remquo >> 8); + EXPECT_EQ(quotient, (uint8_t)m->ax[0]); + EXPECT_EQ(remainder, (uint8_t)m->ax[1]); + } + } + } + } +} + +TEST(div16, test) { + static const uint16_t A[] = {0x0000, 0x0001, 0x8000, 0x7FFF, + 0x8001, 0x7FFE, 0xFFFF, 0xBeef}; + bool gotthrow, gotsigfpe; + uint16_t i, j, k, w, x, a, b; + uint16_t quotient, remainder; + m->xedd->op.rde = OSZ | MOD(3) | RM(CX); + for (i = 0; i < ARRAYLEN(A); ++i) { + for (j = 0; j < ARRAYLEN(A); ++j) { + for (k = 0; k < ARRAYLEN(A); ++k) { + memcpy(m->dx, &A[i], 2); + memcpy(m->ax, &A[j], 2); + memcpy(m->cx, &A[k], 2); + if (!setjmp(m->onhalt)) { + gotthrow = false; + OpDivRdxRaxEvqpUnsigned(m); + } else { + gotthrow = true; + } + if (!setjmp(sigfpejmp)) { + gotsigfpe = false; + asm("divw\t%2" + : "=d"(remainder), "=a"(quotient) + : "r"(A[k]), "0"(A[i]), "1"(A[j]) + : "cc"); + } else { + gotsigfpe = true; + } + EXPECT_EQ(gotsigfpe, gotthrow); + if (!gotsigfpe && !gotthrow) { + EXPECT_EQ(quotient, (uint16_t)Read16(m->ax)); + EXPECT_EQ(remainder, (uint16_t)Read16(m->dx)); + } + } + } + } +} + +TEST(div32, test) { + static const uint32_t A[] = {0x00000000, 0x00000001, 0x80000000, 0x7FFFFFFF, + 0x80000001, 0x7FFFFFFE, 0xFFFFFFFF, 0xDeadBeef}; + bool gotthrow, gotsigfpe; + uint32_t i, j, k, w, x, a, b; + uint32_t quotient, remainder; + m->xedd->op.rde = MOD(3) | RM(CX); + for (i = 0; i < ARRAYLEN(A); ++i) { + for (j = 0; j < ARRAYLEN(A); ++j) { + for (k = 0; k < ARRAYLEN(A); ++k) { + memcpy(m->dx, &A[i], 4); + memcpy(m->ax, &A[j], 4); + memcpy(m->cx, &A[k], 4); + if (!setjmp(m->onhalt)) { + gotthrow = false; + OpDivRdxRaxEvqpUnsigned(m); + } else { + gotthrow = true; + } + if (!setjmp(sigfpejmp)) { + gotsigfpe = false; + asm("divl\t%2" + : "=d"(remainder), "=a"(quotient) + : "r"(A[k]), "0"(A[i]), "1"(A[j]) + : "cc"); + } else { + gotsigfpe = true; + } + EXPECT_EQ(gotsigfpe, gotthrow); + if (!gotsigfpe && !gotthrow) { + EXPECT_EQ(quotient, (uint32_t)Read32(m->ax)); + EXPECT_EQ(remainder, (uint32_t)Read32(m->dx)); + } + } + } + } +} + +TEST(div64, test) { + static const uint64_t A[] = {0x0000000000000000, 0x0000000000000001, + 0x8000000000000000, 0x7FFFFFFFFFFFFFFF, + 0x8000000000000001, 0x7FFFFFFFFFFFFFFE, + 0xFFFFFFFFFFFFFFFF, 0x00DeadBeefCafe00}; + bool gotthrow, gotsigfpe; + uint64_t i, j, k, w, x, a, b; + uint64_t quotient, remainder; + m->xedd->op.rde = REXW | MOD(3) | RM(CX); + for (i = 0; i < ARRAYLEN(A); ++i) { + for (j = 0; j < ARRAYLEN(A); ++j) { + for (k = 0; k < ARRAYLEN(A); ++k) { + memcpy(m->dx, &A[i], 8); + memcpy(m->ax, &A[j], 8); + memcpy(m->cx, &A[k], 8); + if (!setjmp(m->onhalt)) { + gotthrow = false; + OpDivRdxRaxEvqpUnsigned(m); + } else { + gotthrow = true; + } + if (!setjmp(sigfpejmp)) { + gotsigfpe = false; + asm("divq\t%2" + : "=d"(remainder), "=a"(quotient) + : "r"(A[k]), "0"(A[i]), "1"(A[j]) + : "cc"); + } else { + gotsigfpe = true; + } + EXPECT_EQ(gotsigfpe, gotthrow); + if (!gotsigfpe && !gotthrow) { + EXPECT_EQ(quotient, (uint64_t)Read64(m->ax)); + EXPECT_EQ(remainder, (uint64_t)Read64(m->dx)); + } + } + } + } +} + +BENCH(imul, bench) { + volatile register int8_t x8, y8; + volatile register int16_t x16, y16; + volatile register int32_t x32, y32; + volatile register int64_t x64, y64; + EZBENCH2("imul8", PROGN(x8 = 7, y8 = 18), y8 *= x8); + EZBENCH2("imul16", PROGN(x16 = 123, y16 = 116), y16 *= x16); + EZBENCH2("imul32", PROGN(x32 = 0x238943, y32 = 0x238), y32 *= x32); + EZBENCH2("imul64", PROGN(x64 = 0x23894329838932, y64 = 0x238), y64 *= x64); +} + +BENCH(idiv, bench) { + volatile register int8_t x8, y8; + volatile register int16_t x16, y16; + volatile register int32_t x32, y32; + volatile register int64_t x64, y64; + EZBENCH2("idiv8", PROGN(x8 = 7, y8 = 18), x8 /= y8); + EZBENCH2("idiv16", PROGN(x16 = 123, y16 = 116), x16 /= y16); + EZBENCH2("idiv32", PROGN(x32 = 0x238943298, y32 = 0x238), x32 /= y32); + EZBENCH2("idiv64", PROGN(x64 = 0x23894329838932, y64 = 0x238), x64 /= y64); +} + +BENCH(mul, bench) { + volatile register uint8_t x8, y8; + volatile register uint16_t x16, y16; + volatile register uint32_t x32, y32; + volatile register uint64_t x64, y64; + EZBENCH2("mul8", PROGN(x8 = 7, y8 = 18), y8 *= x8); + EZBENCH2("mul16", PROGN(x16 = 123, y16 = 116), y16 *= x16); + EZBENCH2("mul32", PROGN(x32 = 0x238943, y32 = 0x238), y32 *= x32); + EZBENCH2("mul64", PROGN(x64 = 0x23894329838932, y64 = 0x238), y64 *= x64); +} + +BENCH(div, bench) { + volatile register uint8_t x8, y8; + volatile register uint16_t x16, y16; + volatile register uint32_t x32, y32; + volatile register uint64_t x64, y64; + EZBENCH2("div8", PROGN(x8 = 7, y8 = 18), x8 /= y8); + EZBENCH2("div16", PROGN(x16 = 123, y16 = 116), x16 /= y16); + EZBENCH2("div32", PROGN(x32 = 0x238943298, y32 = 0x238), x32 /= y32); + EZBENCH2("div64", PROGN(x64 = 0x23894329838932, y64 = 0x238), x64 /= y64); +} diff --git a/test/tool/build/lib/machine_test.c b/test/tool/build/lib/machine_test.c new file mode 100644 index 00000000..f21be6a9 --- /dev/null +++ b/test/tool/build/lib/machine_test.c @@ -0,0 +1,298 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/bits/progn.h" +#include "libc/fmt/bing.h" +#include "libc/math.h" +#include "libc/mem/mem.h" +#include "libc/runtime/gc.h" +#include "libc/stdio/stdio.h" +#include "libc/testlib/ezbench.h" +#include "libc/testlib/testlib.h" +#include "libc/x/x.h" +#include "tool/build/lib/endian.h" +#include "tool/build/lib/fpu.h" +#include "tool/build/lib/machine.h" +#include "tool/build/lib/memory.h" + +const uint8_t kPi80[] = { + 0xd9, 0xe8, // fld1 + 0xb8, 0x0a, 0x00, 0x00, 0x00, // mov $0xa,%eax + 0x31, 0xd2, // xor %edx,%edx + 0xd9, 0xee, // fldz + 0x48, 0x98, // cltq + 0x48, 0x39, 0xc2, // cmp %rax,%rdx + 0xd9, 0x05, 0x1a, 0x00, 0x00, 0x00, // flds 0x1a(%rip) + 0x7d, 0x13, // jge 2b + 0xde, 0xc1, // faddp + 0x48, 0xff, 0xc2, // inc %rdx + 0xd9, 0xfa, // fsqrt + 0xd9, 0x05, 0x0f, 0x00, 0x00, 0x00, // flds 15(%rip) + 0xd8, 0xc9, // fmul %st(1),%st + 0xde, 0xca, // fmulp %st,%st(2) + 0xeb, 0xe2, // jmp d + 0xdd, 0xd9, // fstp %st(1) + 0xde, 0xf1, // fdivp + 0xf4, // hlt + 0x00, 0x00, 0x00, 0x40, // .float 2.0 + 0x00, 0x00, 0x00, 0x3f, // .float 0.5 +}; + +const uint8_t kTenthprime[] = { + 0x31, 0xd2, // xor %edx,%edx + 0x45, 0x31, 0xc0, // xor %r8d,%r8d + 0x31, 0xc9, // xor %ecx,%ecx + 0xbe, 0x03, 0x00, 0x00, 0x00, // mov $0x3,%esi + 0x41, 0xff, 0xc0, // inc %r8d + 0x44, 0x89, 0xc0, // mov %r8d,%eax + 0x83, 0xf9, 0x0a, // cmp $0xa,%ecx + 0x74, 0x0b, // je 20 + 0x99, // cltd + 0xf7, 0xfe, // idiv %esi + 0x83, 0xfa, 0x01, // cmp $0x1,%edx + 0x83, 0xd9, 0xff, // sbb $-1,%ecx + 0xeb, 0xea, // jmp a + 0xf4, // hlt +}; + +const uint8_t kTenthprime2[] = { + 0xE8, 0x11, 0x00, 0x00, 0x00, // + 0xF4, // + 0x89, 0xF8, // + 0xB9, 0x03, 0x00, 0x00, 0x00, // + 0x99, // + 0xF7, 0xF9, // + 0x85, 0xD2, // + 0x0F, 0x95, 0xC0, // + 0xC3, // + 0x55, // + 0x48, 0x89, 0xE5, // + 0x31, 0xF6, // + 0x45, 0x31, 0xC0, // + 0x44, 0x89, 0xC7, // + 0xE8, 0xDF, 0xFF, 0xFF, 0xFF, // + 0x0F, 0xB6, 0xC0, // + 0x66, 0x83, 0xF8, 0x01, // + 0x83, 0xDE, 0xFF, // + 0x41, 0xFF, 0xC0, // + 0x83, 0xFE, 0x0A, // + 0x75, 0xE6, // + 0x44, 0x89, 0xC0, // + 0x5D, // + 0xC3, // +}; + +int64_t base; +uint8_t *real; +size_t realsize; +struct Machine *m; + +void SetUp(void) { + base = 0; + m = NewMachine(); + realsize = 0x10000; + real = tmemalign(PAGESIZE, ROUNDUP(realsize, PAGESIZE)); + RegisterMemory(m, base, real, realsize); + m->ip = base; + Write64(m->sp, m->ip + realsize); +} + +void TearDown(void) { + ResetRam(m); + tfree(real); + free(m); +} + +int ExecuteUntilHalt(struct Machine *m) { + int rc; + if (!(rc = setjmp(m->onhalt))) { + for (;;) { + LoadInstruction(m); + ExecuteInstruction(m); + } + } else { + return rc; + } +} + +TEST(machine, test) { + memcpy(real, kTenthprime, sizeof(kTenthprime)); + ASSERT_EQ(kMachineHalt, ExecuteUntilHalt(m)); + ASSERT_EQ(15, Read32(m->ax)); +} + +TEST(machine, testFpu) { + memcpy(real, kPi80, sizeof(kPi80)); + ASSERT_EQ(kMachineHalt, ExecuteUntilHalt(m)); + ASSERT_TRUE(fabs(3.14159 - FpuPop(m)) < 0.0001); + m->ip = base; + ASSERT_EQ(kMachineHalt, ExecuteUntilHalt(m)); + ASSERT_TRUE(fabs(3.14159 - FpuPop(m)) < 0.0001); +} + +BENCH(machine, benchPrimeNumberPrograms) { + memcpy(real, kTenthprime2, sizeof(kTenthprime2)); + EZBENCH2("tenthprime2", m->ip = base, ExecuteUntilHalt(m)); + ASSERT_EQ(15, Read32(m->ax)); + memcpy(real, kTenthprime, sizeof(kTenthprime)); + EZBENCH2("tenthprime", m->ip = base, ExecuteUntilHalt(m)); + ASSERT_EQ(15, Read32(m->ax)); +} + +BENCH(machine, benchFpu) { + memcpy(real, kPi80, sizeof(kPi80)); + EZBENCH2("pi80", m->ip = base, PROGN(ExecuteUntilHalt(m), FpuPop(m))); +} + +BENCH(machine, benchLoadExec2) { + uint8_t kMovCode[] = {0xbe, 0x03, 0x00, 0x00, 0x00}; + memcpy(real, kMovCode, sizeof(kMovCode)); + LoadInstruction(m); + EZBENCH2("mov", m->ip = base, ExecuteInstruction(m)); +} + +BENCH(machine, benchLoadExec3) { + uint8_t kMovdCode[] = {0x66, 0x0f, 0x6e, 0xc0}; + Write64(m->ax, 0); + memcpy(real, kMovdCode, sizeof(kMovdCode)); + LoadInstruction(m); + EZBENCH2("movd", m->ip = base, ExecuteInstruction(m)); +} + +BENCH(machine, benchLoadExec4) { + uint8_t kAddpsRegregCode[] = {0x0f, 0x58, 0xC0}; + uint8_t kAddpsMemregCode[] = {0x0f, 0x58, 0x00}; + Write64(m->ax, 0); + memcpy(real, kAddpsRegregCode, sizeof(kAddpsRegregCode)); + LoadInstruction(m); + EZBENCH2("addps reg reg", m->ip = base, ExecuteInstruction(m)); + memcpy(real, kAddpsMemregCode, sizeof(kAddpsMemregCode)); + LoadInstruction(m); + EZBENCH2("addps mem reg", m->ip = base, ExecuteInstruction(m)); +} + +BENCH(machine, benchLoadExec5) { + uint8_t kPaddwRegregCode[] = {0x66, 0x0F, 0xFD, 0xC0}; + uint8_t kPaddwMemregCode[] = {0x66, 0x0F, 0xFD, 0x00}; + Write64(m->ax, 0); + memcpy(real, kPaddwRegregCode, sizeof(kPaddwRegregCode)); + LoadInstruction(m); + EZBENCH2("paddw", m->ip = base, ExecuteInstruction(m)); + memcpy(real, kPaddwMemregCode, sizeof(kPaddwMemregCode)); + LoadInstruction(m); + EZBENCH2("paddw mem", m->ip = base, ExecuteInstruction(m)); +} + +BENCH(machine, benchLoadExec6) { + uint8_t kPsubqRegregCode[] = {0x66, 0x0F, 0xFB, 0xC0}; + uint8_t kPsubqMemregCode[] = {0x66, 0x0F, 0xFB, 0x00}; + Write64(m->ax, 0); + memcpy(real, kPsubqRegregCode, sizeof(kPsubqRegregCode)); + LoadInstruction(m); + EZBENCH2("psubq", m->ip = base, ExecuteInstruction(m)); + memcpy(real, kPsubqMemregCode, sizeof(kPsubqMemregCode)); + LoadInstruction(m); + EZBENCH2("psubq mem", m->ip = base, ExecuteInstruction(m)); +} + +BENCH(machine, benchAddqMem) { + uint8_t kAddMemregCode[] = {0x48, 0x03, 0x08}; + Write64(m->ax, 0); + memcpy(real, kAddMemregCode, sizeof(kAddMemregCode)); + LoadInstruction(m); + EZBENCH2("addq mem", m->ip = base, ExecuteInstruction(m)); +} + +BENCH(machine, benchAddlMem) { + uint8_t kAddMemregCode[] = {0x03, 0x08}; + Write64(m->ax, 0); + memcpy(real, kAddMemregCode, sizeof(kAddMemregCode)); + LoadInstruction(m); + EZBENCH2("addl mem", m->ip = base, ExecuteInstruction(m)); +} + +BENCH(machine, benchAddq) { + uint8_t kAddqCode[] = {0x48, 0x01, 0xd8}; + Write64(m->ax, 0); + memcpy(real, kAddqCode, sizeof(kAddqCode)); + LoadInstruction(m); + EZBENCH2("addq", m->ip = base, ExecuteInstruction(m)); +} + +BENCH(machine, benchAddb) { + uint8_t kAddbCode[] = {0x00, 0xd8}; + Write64(m->ax, 0); + memcpy(real, kAddbCode, sizeof(kAddbCode)); + LoadInstruction(m); + EZBENCH2("addb", m->ip = base, ExecuteInstruction(m)); +} + +BENCH(machine, benchXorReg) { + memcpy(real, kTenthprime, sizeof(kTenthprime)); + LoadInstruction(m); + EZBENCH2("xor", m->ip = base, ExecuteInstruction(m)); +} + +BENCH(machine, benchLoadExec8) { + uint8_t kFchsCode[] = {0xd9, 0xe0}; + Write64(m->ax, 0); + OpFinit(m); + *FpuSt(m, 0) = M_PI; + FpuSetTag(m, 0, kFpuTagValid); + memcpy(real, kFchsCode, sizeof(kFchsCode)); + LoadInstruction(m); + EZBENCH2("fchs", m->ip = base, ExecuteInstruction(m)); +} + +BENCH(machine, benchPushpop) { + uint8_t kPushpop[] = {0x50, 0x58}; + Write64(m->ax, 0); + memcpy(real, kPushpop, sizeof(kPushpop)); + EZBENCH2("pushpop", m->ip = base, + PROGN(LoadInstruction(m), ExecuteInstruction(m), LoadInstruction(m), + ExecuteInstruction(m))); +} + +BENCH(machine, benchPause) { + uint8_t kPause[] = {0xf3, 0x90}; + Write64(m->ax, 0); + memcpy(real, kPause, sizeof(kPause)); + LoadInstruction(m); + EZBENCH2("pause", m->ip = base, ExecuteInstruction(m)); +} + +BENCH(machine, benchClc) { + uint8_t kClc[] = {0xf8}; + Write64(m->ax, 0); + memcpy(real, kClc, sizeof(kClc)); + LoadInstruction(m); + EZBENCH2("clc", m->ip = base, ExecuteInstruction(m)); +} + +BENCH(machine, benchNop) { + uint8_t kNop[] = {0x90}; + Write64(m->ax, 0); + memcpy(real, kNop, sizeof(kNop)); + LoadInstruction(m); + EZBENCH2("nop", m->ip = base, ExecuteInstruction(m)); +} + +TEST(machine, sizeIsReasonable) { + ASSERT_LE(sizeof(struct Machine), 65536); +} diff --git a/test/tool/build/lib/modrm_test.c b/test/tool/build/lib/modrm_test.c new file mode 100644 index 00000000..4a63a2fd --- /dev/null +++ b/test/tool/build/lib/modrm_test.c @@ -0,0 +1,105 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/mem/mem.h" +#include "libc/runtime/gc.h" +#include "libc/testlib/testlib.h" +#include "third_party/xed/x86.h" +#include "tool/build/lib/endian.h" +#include "tool/build/lib/machine.h" +#include "tool/build/lib/modrm.h" + +TEST(modrm, testAddressSizeOverride_isNotPresent_keepsWholeExpression) { + struct Machine *m = gc(NewMachine()); + struct XedDecodedInst *xedd = gc(calloc(1, sizeof(struct XedDecodedInst))); + uint8_t op[] = {0x8d, 0x04, 0x03}; /* lea (%rbx,%rax,1),%eax */ + m->xedd = xedd; + Write64(m->bx, 0x2); + Write64(m->ax, 0xffffffff); + xed_decoded_inst_zero_set_mode(xedd, XED_MACHINE_MODE_LONG_64); + ASSERT_EQ(0, xed_instruction_length_decode(xedd, op, sizeof(op))); + EXPECT_EQ(0x100000001, ComputeAddress(m)); +} + +TEST(modrm, testAddressSizeOverride_isPresent_modulesWholeExpression) { + struct Machine *m = gc(NewMachine()); + struct XedDecodedInst *xedd = gc(calloc(1, sizeof(struct XedDecodedInst))); + uint8_t op[] = {0x67, 0x8d, 0x04, 0x03}; /* lea (%ebx,%eax,1),%eax */ + m->xedd = xedd; + Write64(m->bx, 0x2); + Write64(m->ax, 0xffffffff); + xed_decoded_inst_zero_set_mode(xedd, XED_MACHINE_MODE_LONG_64); + ASSERT_EQ(0, xed_instruction_length_decode(xedd, op, sizeof(op))); + EXPECT_EQ(0x000000001, ComputeAddress(m)); +} + +TEST(modrm, testOverflow_doesntTriggerTooling) { + struct Machine *m = gc(NewMachine()); + struct XedDecodedInst *xedd = gc(calloc(1, sizeof(struct XedDecodedInst))); + uint8_t op[] = {0x8d, 0x04, 0x03}; /* lea (%rbx,%rax,1),%eax */ + m->xedd = xedd; + Write64(m->bx, 0x0000000000000001); + Write64(m->ax, 0x7fffffffffffffff); + xed_decoded_inst_zero_set_mode(xedd, XED_MACHINE_MODE_LONG_64); + ASSERT_EQ(0, xed_instruction_length_decode(xedd, op, sizeof(op))); + EXPECT_EQ(0x8000000000000000ull, (uint64_t)ComputeAddress(m)); +} + +TEST(modrm, testPuttingOnTheRiz) { + struct Machine *m = gc(NewMachine()); + static uint8_t ops[][15] = { + {0x8d, 0b00110100, 0b00100110}, // lea (%rsi),%esi + {0x67, 0x8d, 0b00110100, 0b11100110}, // lea (%esi,%eiz,8),%esi + {103, 141, 180, 229, 55, 19, 3, 0}, // lea 0x31337(%ebp,%eiz,8),%esi + {141, 52, 229, 55, 19, 3, 0}, // lea 0x31337,%esi + }; + m->xedd = gc(calloc(1, sizeof(struct XedDecodedInst))); + Write64(m->si, 0x100000001); + Write64(m->bp, 0x200000002); + xed_decoded_inst_zero_set_mode(m->xedd, XED_MACHINE_MODE_LONG_64); + ASSERT_EQ(0, xed_instruction_length_decode(m->xedd, ops[0], sizeof(ops[0]))); + EXPECT_EQ(0x100000001, ComputeAddress(m)); + xed_decoded_inst_zero_set_mode(m->xedd, XED_MACHINE_MODE_LONG_64); + ASSERT_EQ(0, xed_instruction_length_decode(m->xedd, ops[1], sizeof(ops[1]))); + EXPECT_EQ(0x000000001, ComputeAddress(m)); + xed_decoded_inst_zero_set_mode(m->xedd, XED_MACHINE_MODE_LONG_64); + ASSERT_EQ(0, xed_instruction_length_decode(m->xedd, ops[2], sizeof(ops[2]))); + EXPECT_EQ(0x31339, ComputeAddress(m)); + xed_decoded_inst_zero_set_mode(m->xedd, XED_MACHINE_MODE_LONG_64); + ASSERT_EQ(0, xed_instruction_length_decode(m->xedd, ops[3], sizeof(ops[3]))); + EXPECT_EQ(0x31337, ComputeAddress(m)); +} + +TEST(modrm, testSibIndexOnly) { + // mod = 0b00 (0) + // reg = 0b000 (0) + // rm = 0b100 (4) + // scale = 0b10 (2) + // index = 0b001 (1) + // base = 0b101 (5) + struct Machine *m = gc(NewMachine()); + struct XedDecodedInst *xedd = gc(calloc(1, sizeof(struct XedDecodedInst))); + uint8_t op[] = {76, 141, 4, 141, 0, 0, 0, 0}; /* lea 0x0(,%rcx,4),%r8 */ + m->xedd = xedd; + Write64(m->bp, 0x123); + Write64(m->cx, 0x123); + xed_decoded_inst_zero_set_mode(xedd, XED_MACHINE_MODE_LONG_64); + ASSERT_EQ(0, xed_instruction_length_decode(xedd, op, sizeof(op))); + EXPECT_EQ(0x123 * 4, (uint64_t)ComputeAddress(m)); +} diff --git a/test/tool/build/lib/numbers.c b/test/tool/build/lib/numbers.c new file mode 100644 index 00000000..ca45837b --- /dev/null +++ b/test/tool/build/lib/numbers.c @@ -0,0 +1,57 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "test/tool/build/lib/numbers.h" + +const uint64_t kNumbers[102] = { + 0x0000000000000000, 0x0000000080000000, 0x0000000000008000, + 0x0000000000000001, 0x0000000000000002, 0x0000000000000003, + 0x0000000000000004, 0x0000000000000005, 0x0000000000000006, + 0x0000000000000007, 0x0000000000000008, 0x0000000000000009, + 0x000000000000000a, 0x000000000000000b, 0x000000000000000c, + 0x000000000000000d, 0x000000000000000e, 0x000000000000000f, + 0x0000000000000010, 0x0000000000000011, 0x0000000000000012, + 0x0000000000000013, 0x0000000000000014, 0x0000000000000015, + 0x0000000000000016, 0x0000000000000017, 0x0000000000000018, + 0x0000000000000019, 0x000000000000001a, 0x000000000000001b, + 0x000000000000001c, 0x000000000000001d, 0x000000000000001e, + 0x000000000000001f, 0x0000000000000020, 0x0000000000000040, + 0x8000000000000000, 0x0000000000000080, 0x0000000000000002, + 0x0000000000000001, 0x0000000000000004, 0x00000000C0000000, + 0xC000000000000000, 0x000000000000C000, 0x00000000000000C0, + 0x0000000000000003, 0x000000000000E000, 0x00000000E0000000, + 0xE000000000000000, 0x00000000000000E0, 0x000000000000001F, + 0x00000000000000FC, 0x000000000000003F, 0x000000000000007F, + 0x00000000000000FB, 0x00000000000000FD, 0x00000000000000FE, + 0x00000000000000FF, 0x000000000000FF1F, 0x0000000000001FFF, + 0x000000000000FFFC, 0x0000000000003FFF, 0x000000000000FF3F, + 0x000000000000FFFD, 0x000000000000FFFE, 0x000000000000FFFB, + 0x000000000000FF7F, 0x0000000000007FFF, 0x000000000000FFFF, + 0x00000000FFFF1FFF, 0x00000000FFFFFF1F, 0x000000001FFFFFFF, + 0x00000000FFFFFF3F, 0x00000000FFFF3FFF, 0x00000000FFFFFFFC, + 0x000000003FFFFFFF, 0x00000000FFFFFF7F, 0x00000000FFFFFFFD, + 0x00000000FFFFFFFE, 0x00000000FFFFFFFB, 0x000000007FFFFFFF, + 0x00000000FFFF7FFF, 0x00000000FFFFFFFF, 0xFFFFFFFF1FFFFFFF, + 0x1FFFFFFFFFFFFFFF, 0xFFFFFFFFFFFF1FFF, 0xFFFFFFFFFFFFFF1F, + 0xFFFFFFFFFFFFFFFC, 0xFFFFFFFFFFFF3FFF, 0xFFFFFFFF3FFFFFFF, + 0xFFFFFFFFFFFFFF3F, 0x3FFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFD, + 0xFFFFFFFFFFFFFFFE, 0xFFFFFFFFFFFFFFFB, 0xFFFFFFFF7FFFFFFF, + 0x7FFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFF7F, 0xFFFFFFFFFFFF7FFF, + 0xFFFFFFFFFFFFFFFF, 0x00DeadBeefCafe00, 0x0000031337000000, +}; diff --git a/test/tool/build/lib/numbers.h b/test/tool/build/lib/numbers.h new file mode 100644 index 00000000..6ab84b2d --- /dev/null +++ b/test/tool/build/lib/numbers.h @@ -0,0 +1,10 @@ +#ifndef COSMOPOLITAN_TEST_TOOL_BUILD_LIB_NUMBERS_H_ +#define COSMOPOLITAN_TEST_TOOL_BUILD_LIB_NUMBERS_H_ +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +extern const uint64_t kNumbers[102]; + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_TEST_TOOL_BUILD_LIB_NUMBERS_H_ */ diff --git a/test/tool/build/lib/optest.c b/test/tool/build/lib/optest.c new file mode 100644 index 00000000..8022e346 --- /dev/null +++ b/test/tool/build/lib/optest.c @@ -0,0 +1,94 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/bits/weaken.h" +#include "libc/macros.h" +#include "libc/runtime/runtime.h" +#include "libc/stdio/stdio.h" +#include "test/tool/build/lib/numbers.h" +#include "test/tool/build/lib/optest.h" +#include "tool/build/lib/flags.h" + +const char kOpSuffix[] = {'b', 'w', 'l', 'q'}; + +void(RunOpTests)(const uint8_t *ops, size_t n, const char *const *opnames, + const char *file, int line, const char *func) { + uint64_t x, y; + uint64_t xn, xp; + uint32_t f0, f1, f2; + long failed, succeeded; + int w, h, i, j, c, z, s, o, p; + failed = 0; + succeeded = 0; + for (w = 0; w < 4; ++w) { + for (h = 0; h < n; ++h) { + for (z = 0; z < 2; ++z) { + for (o = 0; o < 2; ++o) { + for (s = 0; s < 2; ++s) { + for (i = 0; i < ARRAYLEN(kNumbers); ++i) { + for (j = 0; j < ARRAYLEN(kNumbers); ++j) { + for (c = 0; c < 2; ++c) { + x = kNumbers[i]; + y = kNumbers[j]; + f2 = f1 = f0 = c << FLAGS_CF | z << FLAGS_ZF | s << FLAGS_SF | + o << FLAGS_OF; + xn = RunGolden(w, ops[h], x, y, &f1); + xp = RunOpTest(w, ops[h], x, y, &f2); + if (weaken(FixupUndefOpTestFlags)) { + FixupUndefOpTestFlags(w, ops[h], x, y, f1, &f2); + } + if (xn == xp && (f1 & FMASK) == (f2 & FMASK)) { + succeeded++; + } else if (failed++ < 10) { + fprintf(stderr, + "%s:%d:%s: %s%c failed\n\t" + "𝑥 %016x\n\t" + "𝑦 %016x %c%c%c%c 0NPlODITSZKA1PVC\n\t" + "𝑥ₙ %016x %c%c%c%c %016b\n\t" + "𝑥ₚ %016x %c%c%c%c %016b\n", + file, line, func, opnames[ops[h]], kOpSuffix[w], x, + y, ".C"[c], ".Z"[z], ".S"[s], ".O"[o], xn, + ".C"[!!(f1 & (1 << FLAGS_CF))], + ".Z"[!!(f1 & (1 << FLAGS_ZF))], + ".S"[!!(f1 & (1 << FLAGS_SF))], + ".O"[!!(f1 & (1 << FLAGS_OF))], f1, xp, + ".C"[!!(f2 & (1 << FLAGS_CF))], + ".Z"[!!(f2 & (1 << FLAGS_ZF))], + ".S"[!!(f2 & (1 << FLAGS_SF))], + ".O"[!!(f2 & (1 << FLAGS_OF))], f2); + } + } + } + } + } + } + } + } + } + if (failed) { + fprintf(stderr, + "\n" + "passing: %d%%\n" + "succeeded: %,ld\n" + "failed: %,ld\n" + "\n", + (int)((1 - (double)failed / succeeded) * 100), succeeded, failed); + exit(1); + } +} diff --git a/test/tool/build/lib/optest.h b/test/tool/build/lib/optest.h new file mode 100644 index 00000000..b2ed6e1f --- /dev/null +++ b/test/tool/build/lib/optest.h @@ -0,0 +1,21 @@ +#ifndef COSMOPOLITAN_TEST_TOOL_BUILD_LIB_OPTEST_H_ +#define COSMOPOLITAN_TEST_TOOL_BUILD_LIB_OPTEST_H_ +#include "tool/build/lib/flags.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +#define FMASK (1 << FLAGS_CF | 1 << FLAGS_ZF | 1 << FLAGS_SF | 1 << FLAGS_OF) + +void RunOpTests(const uint8_t *, size_t, const char *const *, const char *, int, + const char *); + +int64_t RunGolden(char, int, uint64_t, uint64_t, uint32_t *); +int64_t RunOpTest(char, int, uint64_t, uint64_t, uint32_t *); +void FixupUndefOpTestFlags(char, int, uint64_t, uint64_t, uint32_t, uint32_t *); + +#define RunOpTests(ops, n, names) \ + RunOpTests(ops, n, names, __FILE__, __LINE__, __func__) + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_TEST_TOOL_BUILD_LIB_OPTEST_H_ */ diff --git a/test/tool/build/lib/pml4t_test.c b/test/tool/build/lib/pml4t_test.c new file mode 100644 index 00000000..9d1b73ae --- /dev/null +++ b/test/tool/build/lib/pml4t_test.c @@ -0,0 +1,150 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/log/check.h" +#include "libc/macros.h" +#include "libc/mem/mem.h" +#include "libc/stdio/stdio.h" +#include "libc/testlib/ezbench.h" +#include "libc/testlib/testlib.h" +#include "tool/build/lib/memory.h" +#include "tool/build/lib/pml4t.h" + +struct Unmapped { + size_t i; + struct UnmappedEntry { + void *addr; + size_t size; + } p[8]; +}; + +struct Unmapped unmapped; +uint64_t *pt2, *pt3, *pt4; +pml4t_t cr3; + +void *NewPage(void) { + void *p; + p = tmemalign(4096, 4096); + CHECK_ALIGNED(4096, p); + memset(p, 0, 4096); + return p; +} + +void FreePage(void *p) { + tfree(p); +} + +int FakeMunmap(void *addr, size_t size) { + return 0; +} + +int MockMunmap(void *addr, size_t size) { + CHECK_LT(unmapped.i, ARRAYLEN(unmapped.p)); + unmapped.p[unmapped.i].addr = addr; + unmapped.p[unmapped.i].size = size; + unmapped.i++; + return 0; +} + +void SetUp(void) { + memset(cr3, 0, sizeof(cr3)); +} + +void TearDown(void) { + unmapped.i = 0; + FreePml4t(cr3, -0x800000000000, 0x800000000000, FreePage, FakeMunmap); +} + +TEST(pml4t, testHighestAddress) { + ASSERT_NE(-1, + RegisterPml4t(cr3, 0x7fffffffe000, 0x3133700000, 0x2000, NewPage)); + ASSERT_TRUE(IsValidPage(cr3[255])); + pt2 = UnmaskPageAddr(cr3[255]); + ASSERT_TRUE(IsValidPage(pt2[511])); + pt3 = UnmaskPageAddr(pt2[511]); + ASSERT_TRUE(IsValidPage(pt3[511])); + pt4 = UnmaskPageAddr(pt3[511]); + ASSERT_TRUE(IsValidPage(pt4[510])); + ASSERT_TRUE(IsValidPage(pt4[511])); + EXPECT_EQ(0x3133700000, UnmaskPageAddr(pt4[510])); + EXPECT_EQ(0x3133701000, UnmaskPageAddr(pt4[511])); +} + +TEST(pml4t, testLowestAddress) { + ASSERT_NE(-1, + RegisterPml4t(cr3, -0x800000000000, 0x31337000, 0x2000, NewPage)); + ASSERT_TRUE(IsValidPage(cr3[256])); + pt2 = UnmaskPageAddr(cr3[256]); + ASSERT_TRUE(IsValidPage(pt2[0])); + pt3 = UnmaskPageAddr(pt2[0]); + ASSERT_TRUE(IsValidPage(pt3[0])); + pt4 = UnmaskPageAddr(pt3[0]); + ASSERT_TRUE(IsValidPage(pt4[0])); + ASSERT_TRUE(IsValidPage(pt4[1])); +} + +TEST(pml4t, testOverlapsExistingRegistration_overwritesRegistration) { + ASSERT_NE(-1, + RegisterPml4t(cr3, 0x7fffffffe000, 0x31337000, 0x1000, NewPage)); + ASSERT_TRUE(IsValidPage(cr3[255])); + pt2 = UnmaskPageAddr(cr3[255]); + ASSERT_TRUE(IsValidPage(pt2[511])); + pt3 = UnmaskPageAddr(pt2[511]); + ASSERT_TRUE(IsValidPage(pt3[511])); + pt4 = UnmaskPageAddr(pt3[511]); + EXPECT_TRUE(IsValidPage(pt4[510])); + EXPECT_EQ(0x31337000, UnmaskPageAddr(pt4[510])); + ASSERT_NE(-1, RegisterPml4t(cr3, 0x7fffffffe000, 0x31337000 + 0x1000, 0x1000, + NewPage)); + EXPECT_TRUE(IsValidPage(pt4[510])); + EXPECT_EQ(0x31337000 + 0x1000, UnmaskPageAddr(pt4[510])); +} + +TEST(pml4t, testFindPml4t_holeTooSmall_skipsOver) { + ASSERT_NE(-1, RegisterPml4t(cr3, 0x700000000, 0, 0x1000, NewPage)); + ASSERT_NE(-1, RegisterPml4t(cr3, 0x700005000, 0, 0x1000, NewPage)); + ASSERT_EQ(0x700001000, FindPml4t(cr3, 0x700000000, 0x01000, NewPage)); + ASSERT_EQ(0x700006000, FindPml4t(cr3, 0x700000000, 0x10000, NewPage)); +} + +TEST(pml4t, testFreePmlt) { + ASSERT_NE(-1, RegisterPml4t(cr3, 0x000005000, 0x123000, 0x2000, NewPage)); + ASSERT_NE(-1, RegisterPml4t(cr3, 0x000000000, 0x321000, 0x1000, NewPage)); + ASSERT_NE(-1, FreePml4t(cr3, -0x800000000000, 0x800000000000, FreePage, + MockMunmap)); + ASSERT_EQ(2, unmapped.i); + EXPECT_EQ(0x321000, unmapped.p[0].addr); + EXPECT_EQ(0x1000, unmapped.p[0].size); + EXPECT_EQ(0x123000, unmapped.p[1].addr); + EXPECT_EQ(0x2000, unmapped.p[1].size); +} + +BENCH(pml4t, bench) { + EZBENCH2("RegisterPml4t 1mb fresh", + FreePml4t(cr3, -0x800000000000, 0x800000000000, free, FakeMunmap), + RegisterPml4t(cr3, 0x00100000, 0x31337000, 0x00100000, MallocPage)); + EZBENCH2("RegisterPml4t 1gb fresh", + FreePml4t(cr3, -0x800000000000, 0x800000000000, free, FakeMunmap), + RegisterPml4t(cr3, 0x40000000, 0x31337000, 0x40000000, MallocPage)); + EZBENCH2("RegisterPml4t 1mb overwrite", donothing, + RegisterPml4t(cr3, 0x00100000, 0x31337000, 0x00100000, MallocPage)); + EZBENCH2("RegisterPml4t 1gb overwrite", donothing, + RegisterPml4t(cr3, 0x40000000, 0x31337000, 0x40000000, MallocPage)); + FreePml4t(cr3, -0x800000000000, 0x800000000000, free, FakeMunmap); +} diff --git a/test/tool/build/lib/test.mk b/test/tool/build/lib/test.mk index 886990be..88a00835 100644 --- a/test/tool/build/lib/test.mk +++ b/test/tool/build/lib/test.mk @@ -3,14 +3,21 @@ PKGS += TEST_TOOL_BUILD_LIB -TEST_TOOL_BUILD_LIB_SRCS := $(wildcard test/tool/build/lib/*.c) +TEST_TOOL_BUILD_LIB = $(TOOL_BUILD_LIB_A_DEPS) $(TOOL_BUILD_LIB_A) +TEST_TOOL_BUILD_LIB_A = o/$(MODE)/test/tool/build/lib/buildlib.a +TEST_TOOL_BUILD_LIB_FILES := $(wildcard test/tool/build/lib/*) +TEST_TOOL_BUILD_LIB_SRCS = $(filter %.c,$(TEST_TOOL_BUILD_LIB_FILES)) TEST_TOOL_BUILD_LIB_SRCS_TEST = $(filter %_test.c,$(TEST_TOOL_BUILD_LIB_SRCS)) +TEST_TOOL_BUILD_LIB_HDRS = $(filter %.h,$(TEST_TOOL_BUILD_LIB_FILES)) TEST_TOOL_BUILD_LIB_COMS = $(TEST_TOOL_BUILD_LIB_OBJS:%.o=%.com) TEST_TOOL_BUILD_LIB_OBJS = \ $(TEST_TOOL_BUILD_LIB_SRCS:%=o/$(MODE)/%.zip.o) \ $(TEST_TOOL_BUILD_LIB_SRCS:%.c=o/$(MODE)/%.o) +TEST_TOOL_BUILD_LIB_COMS = \ + $(TEST_TOOL_BUILD_LIB_SRCS:%.c=o/$(MODE)/%.com) + TEST_TOOL_BUILD_LIB_BINS = \ $(TEST_TOOL_BUILD_LIB_COMS) \ $(TEST_TOOL_BUILD_LIB_COMS:%=%.dbg) @@ -19,34 +26,49 @@ TEST_TOOL_BUILD_LIB_TESTS = \ $(TEST_TOOL_BUILD_LIB_SRCS_TEST:%.c=o/$(MODE)/%.com.ok) TEST_TOOL_BUILD_LIB_CHECKS = \ + $(TEST_TOOL_BUILD_LIB_HDRS:%=o/$(MODE)/%.ok) \ $(TEST_TOOL_BUILD_LIB_SRCS_TEST:%.c=o/$(MODE)/%.com.runs) TEST_TOOL_BUILD_LIB_DIRECTDEPS = \ LIBC_X \ + LIBC_CALLS \ LIBC_MEM \ LIBC_NEXGEN32E \ LIBC_RUNTIME \ + LIBC_FMT \ + LIBC_STDIO \ + LIBC_LOG \ + LIBC_SYSV \ LIBC_STUBS \ + LIBC_UNICODE \ LIBC_TESTLIB \ - TOOL_BUILD_LIB + TOOL_BUILD_LIB \ + THIRD_PARTY_COMPILER_RT \ + THIRD_PARTY_XED TEST_TOOL_BUILD_LIB_DEPS := \ $(call uniq,$(foreach x,$(TEST_TOOL_BUILD_LIB_DIRECTDEPS),$($(x)))) -o/$(MODE)/test/tool/build/lib/buildlib.pkg: \ +$(TEST_TOOL_BUILD_LIB_A): \ + test/tool/build/lib/ \ + $(TEST_TOOL_BUILD_LIB_A).pkg \ + $(TEST_TOOL_BUILD_LIB_OBJS) + +$(TEST_TOOL_BUILD_LIB_A).pkg: \ $(TEST_TOOL_BUILD_LIB_OBJS) \ $(foreach x,$(TEST_TOOL_BUILD_LIB_DIRECTDEPS),$($(x)_A).pkg) o/$(MODE)/test/tool/build/lib/%.com.dbg: \ $(TEST_TOOL_BUILD_LIB_DEPS) \ + $(TEST_TOOL_BUILD_LIB_A) \ o/$(MODE)/test/tool/build/lib/%.o \ - o/$(MODE)/test/tool/build/lib/buildlib.pkg \ + $(TEST_TOOL_BUILD_LIB_A).pkg \ $(LIBC_TESTMAIN) \ $(CRT) \ $(APE) @$(APELINK) -.PHONY: o/$(MODE)/test/tool/build/lib +.PHONY: o/$(MODE)/test/tool/build/lib o/$(MODE)/test/tool/build/lib: \ $(TEST_TOOL_BUILD_LIB_BINS) \ $(TEST_TOOL_BUILD_LIB_CHECKS) diff --git a/test/tool/build/lib/x87_test.c b/test/tool/build/lib/x87_test.c new file mode 100644 index 00000000..672d0975 --- /dev/null +++ b/test/tool/build/lib/x87_test.c @@ -0,0 +1,39 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/testlib/testlib.h" +#include "tool/build/lib/x87.h" + +TEST(x87, fprem) { + ASSERT_LDBL_EQ(1, fprem(1, -1.5, NULL)); + ASSERT_LDBL_EQ(1.1766221079117338e-14L, + fprem(12300000000000000.L, .0000000000000123L, NULL)); +} + +TEST(x87, fprem1) { + ASSERT_LDBL_EQ(-.5, fprem1(1, -1.5, NULL)); + ASSERT_LDBL_EQ(-5.337789208826618e-16, + fprem1(12300000000000000.L, .0000000000000123L, NULL)); +} + +TEST(x87, fpremFlags) { + uint32_t sw = 0xffff; + ASSERT_LDBL_EQ(1, fprem(1, -1.5, &sw)); + ASSERT_EQ(0b1011100011111111, sw); +} diff --git a/test/tool/build/lib/xlaterrno_test.c b/test/tool/build/lib/xlaterrno_test.c new file mode 100644 index 00000000..4cd4768e --- /dev/null +++ b/test/tool/build/lib/xlaterrno_test.c @@ -0,0 +1,28 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/errno.h" +#include "libc/testlib/testlib.h" +#include "tool/build/lib/xlaterrno.h" + +TEST(xlaterrno, test) { + EXPECT_EQ(95, XlatErrno(EOPNOTSUPP)); + EXPECT_EQ(90, XlatErrno(EMSGSIZE)); + EXPECT_EQ(133, XlatErrno(EHWPOISON)); +} diff --git a/test/tool/viz/lib/test.mk b/test/tool/viz/lib/test.mk index 631862fb..25244705 100644 --- a/test/tool/viz/lib/test.mk +++ b/test/tool/viz/lib/test.mk @@ -5,12 +5,14 @@ PKGS += TEST_TOOL_VIZ_LIB TEST_TOOL_VIZ_LIB_SRCS := $(wildcard test/tool/viz/lib/*.c) TEST_TOOL_VIZ_LIB_SRCS_TEST = $(filter %_test.c,$(TEST_TOOL_VIZ_LIB_SRCS)) -TEST_TOOL_VIZ_LIB_COMS = $(TEST_TOOL_VIZ_LIB_OBJS:%.o=%.com) TEST_TOOL_VIZ_LIB_OBJS = \ $(TEST_TOOL_VIZ_LIB_SRCS:%=o/$(MODE)/%.zip.o) \ $(TEST_TOOL_VIZ_LIB_SRCS:%.c=o/$(MODE)/%.o) +TEST_TOOL_VIZ_LIB_COMS = \ + $(TEST_TOOL_VIZ_LIB_SRCS:%.c=o/$(MODE)/%.com) + TEST_TOOL_VIZ_LIB_BINS = \ $(TEST_TOOL_VIZ_LIB_COMS) \ $(TEST_TOOL_VIZ_LIB_COMS:%=%.dbg) diff --git a/third_party/bzip2/bzip2.mk b/third_party/bzip2/bzip2.mk index a2461c93..f5a51785 100644 --- a/third_party/bzip2/bzip2.mk +++ b/third_party/bzip2/bzip2.mk @@ -27,9 +27,7 @@ o/$(MODE)/third_party/bzip2/µbunzip2.com.dbg: \ $(APE) @$(APELINK) -$(THIRD_PARTY_BZIP2_OBJS): \ - $(BUILD_FILES) \ - third_party/bzip2/bzip2.mk +$(THIRD_PARTY_BZIP2_OBJS): third_party/bzip2/bzip2.mk .PHONY: o/$(MODE)/third_party/bzip2 o/$(MODE)/third_party/bzip2: $(THIRD_PARTY_BZIP2_BINS) diff --git a/third_party/compiler_rt/compiler_rt.mk b/third_party/compiler_rt/compiler_rt.mk index 33f16292..36992336 100644 --- a/third_party/compiler_rt/compiler_rt.mk +++ b/third_party/compiler_rt/compiler_rt.mk @@ -43,16 +43,13 @@ $(THIRD_PARTY_COMPILER_RT_A).pkg: \ $(foreach x,$(THIRD_PARTY_COMPILER_RT_A_DIRECTDEPS),$($(x)_A).pkg) $(THIRD_PARTY_COMPILER_RT_A_OBJS): \ - DEFAULT_COPTS += \ - -DCRT_HAS_128BIT - -o/$(MODE)/third_party/compiler_rt/multc3.o \ -o/$(MODE)/third_party/compiler_rt/divtc3.o: \ - DEFAULT_COPTS += \ - -w + DEFAULT_CFLAGS += \ + $(OLD_CODE) \ + -DCRT_HAS_128BIT THIRD_PARTY_COMPILER_RT_LIBS = $(foreach x,$(THIRD_PARTY_COMPILER_RT_ARTIFACTS),$($(x))) THIRD_PARTY_COMPILER_RT_SRCS = $(foreach x,$(THIRD_PARTY_COMPILER_RT_ARTIFACTS),$($(x)_SRCS)) +THIRD_PARTY_COMPILER_RT_HDRS = $(foreach x,$(THIRD_PARTY_COMPILER_RT_ARTIFACTS),$($(x)_HDRS)) THIRD_PARTY_COMPILER_RT_CHECKS = $(foreach x,$(THIRD_PARTY_COMPILER_RT_ARTIFACTS),$($(x)_CHECKS)) THIRD_PARTY_COMPILER_RT_OBJS = $(foreach x,$(THIRD_PARTY_COMPILER_RT_ARTIFACTS),$($(x)_OBJS)) diff --git a/third_party/compiler_rt/divmodti4.c b/third_party/compiler_rt/divmodti4.c new file mode 100644 index 00000000..4816b8e1 --- /dev/null +++ b/third_party/compiler_rt/divmodti4.c @@ -0,0 +1,64 @@ +#if 0 +/*─────────────────────────────────────────────────────────────────╗ +│ To the extent possible under law, Justine Tunney has waived │ +│ all copyright and related or neighboring rights to division, │ +│ as it is written in the following disclaimers: │ +│ • http://unlicense.org/ │ +│ • http://creativecommons.org/publicdomain/zero/1.0/ │ +╚─────────────────────────────────────────────────────────────────*/ +#endif +#include "libc/calls/calls.h" +#include "third_party/compiler_rt/int_lib.h" + +/** + * Divides 128-bit signed integers w/ remainder. + * + * @param a is numerator + * @param b is denominator + * @param opt_out_rem receives euclidean division remainder if not null + * @return quotient or result of division + * @note rounds towards zero + */ +COMPILER_RT_ABI ti_int __divmodti4(ti_int a, ti_int b, tu_int *opt_out_rem) { + int k; + tu_int r; + ti_int sa, sb, sq, sr, x, y, q; + k = sizeof(ti_int) * CHAR_BIT - 1; + if (b < 0 && a == ((ti_int)1 << k)) { + volatile int x = 0; + x = 1 / x; // raise(SIGFPE) + } + sa = a >> k; // sa = a < 0 ? -1 : 0 + sb = b >> k; // sb = b < 0 ? -1 : 0 + x = (a ^ sa) - sa; // negate if sa == -1 + y = (b ^ sb) - sb; // negate if sb == -1 + sq = sa ^ sb; // sign of quotient + sr = sa; // sign of remainder + q = __udivmodti4(x, y, &r); // unsigned divide + q = (q ^ sq) - sq; // fix quotient sign + r = (r ^ sr) - sr; // fix remainder sign + if (opt_out_rem) *opt_out_rem = r; + return q; +} + +/* + + Intel Kabylake i9-9900 @ 3.10GHz Client Grade + + idiv32 l: 27𝑐 9𝑛𝑠 + idiv64 l: 27𝑐 9𝑛𝑠 + divmodti4 small / small l: 42𝑐 14𝑛𝑠 + divmodti4 small / large l: 14𝑐 5𝑛𝑠 + divmodti4 large / small l: 92𝑐 30𝑛𝑠 + divmodti4 large / large l: 209𝑐 68𝑛𝑠 + + Intel Kabylake i3-8100 @ 3.60GHz Client Grade + + idiv32 l: 51𝑐 14𝑛𝑠 + idiv64 l: 51𝑐 14𝑛𝑠 + divmodti4 small / small l: 83𝑐 23𝑛𝑠 + divmodti4 small / large l: 26𝑐 7𝑛𝑠 + divmodti4 large / small l: 175𝑐 48𝑛𝑠 + divmodti4 large / large l: 389𝑐 107𝑛𝑠 + +*/ diff --git a/third_party/compiler_rt/divti3.c b/third_party/compiler_rt/divti3.c index 48bbe58a..b2bbc7aa 100644 --- a/third_party/compiler_rt/divti3.c +++ b/third_party/compiler_rt/divti3.c @@ -1,36 +1,22 @@ -/* clang-format off */ -/* ===-- divti3.c - Implement __divti3 -------------------------------------=== - * - * The LLVM Compiler Infrastructure - * - * This file is dual licensed under the MIT and the University of Illinois Open - * Source Licenses. See LICENSE.TXT for details. - * - * ===----------------------------------------------------------------------=== - * - * This file implements __divti3 for the compiler_rt library. - * - * ===----------------------------------------------------------------------=== - */ - -STATIC_YOINK("huge_compiler_rt_license"); - +#if 0 +/*─────────────────────────────────────────────────────────────────╗ +│ To the extent possible under law, Justine Tunney has waived │ +│ all copyright and related or neighboring rights to division, │ +│ as it is written in the following disclaimers: │ +│ • http://unlicense.org/ │ +│ • http://creativecommons.org/publicdomain/zero/1.0/ │ +╚─────────────────────────────────────────────────────────────────*/ +#endif #include "third_party/compiler_rt/int_lib.h" -#ifdef CRT_HAS_128BIT - -/* Returns: a / b */ - -COMPILER_RT_ABI ti_int -__divti3(ti_int a, ti_int b) -{ - const int bits_in_tword_m1 = (int)(sizeof(ti_int) * CHAR_BIT) - 1; - ti_int s_a = a >> bits_in_tword_m1; /* s_a = a < 0 ? -1 : 0 */ - ti_int s_b = b >> bits_in_tword_m1; /* s_b = b < 0 ? -1 : 0 */ - a = (a ^ s_a) - s_a; /* negate if s_a == -1 */ - b = (b ^ s_b) - s_b; /* negate if s_b == -1 */ - s_a ^= s_b; /* sign of quotient */ - return (__udivmodti4(a, b, (tu_int*)0) ^ s_a) - s_a; /* negate if s_a == -1 */ +/** + * Divides 128-bit signed integers. + * + * @param a is numerator + * @param b is denominator + * @return quotient or result of division + * @note rounds towards zero + */ +COMPILER_RT_ABI ti_int __divti3(ti_int a, ti_int b) { + return __divmodti4(a, b, NULL); } - -#endif /* CRT_HAS_128BIT */ diff --git a/third_party/compiler_rt/int_lib.h b/third_party/compiler_rt/int_lib.h index 9e80c3a6..65e84f78 100644 --- a/third_party/compiler_rt/int_lib.h +++ b/third_party/compiler_rt/int_lib.h @@ -16,6 +16,7 @@ #ifndef INT_LIB_H #define INT_LIB_H +#define CRT_HAS_128BIT 1 /* Assumption: lool univac arithmetic */ /* Assumption: lool cray signed shift */ @@ -74,6 +75,7 @@ COMPILER_RT_ABI su_int __udivmodsi4(su_int a, su_int b, su_int* rem); COMPILER_RT_ABI du_int __udivmoddi4(du_int a, du_int b, du_int* rem); #ifdef CRT_HAS_128BIT COMPILER_RT_ABI si_int __clzti2(ti_int a); +COMPILER_RT_ABI ti_int __divmodti4(ti_int a, ti_int b, tu_int *rem); COMPILER_RT_ABI tu_int __udivmodti4(tu_int a, tu_int b, tu_int* rem); #endif diff --git a/third_party/compiler_rt/udivmodti4.c b/third_party/compiler_rt/udivmodti4.c index e0109aa8..5acefffd 100644 --- a/third_party/compiler_rt/udivmodti4.c +++ b/third_party/compiler_rt/udivmodti4.c @@ -1,241 +1,137 @@ -/* clang-format off */ -/* ===-- udivmodti4.c - Implement __udivmodti4 -----------------------------=== - * - * The LLVM Compiler Infrastructure - * - * This file is dual licensed under the MIT and the University of Illinois Open - * Source Licenses. See LICENSE.TXT for details. - * - * ===----------------------------------------------------------------------=== - * - * This file implements __udivmodti4 for the compiler_rt library. - * - * ===----------------------------------------------------------------------=== - */ - -STATIC_YOINK("huge_compiler_rt_license"); - +#if 0 +/*─────────────────────────────────────────────────────────────────╗ +│ To the extent possible under law, Justine Tunney has waived │ +│ all copyright and related or neighboring rights to division, │ +│ as it is written in the following disclaimers: │ +│ • http://unlicense.org/ │ +│ • http://creativecommons.org/publicdomain/zero/1.0/ │ +╚─────────────────────────────────────────────────────────────────*/ +#endif #include "third_party/compiler_rt/int_lib.h" -#ifdef CRT_HAS_128BIT - -/* Effects: if rem != 0, *rem = a % b - * Returns: a / b +/** + * Returns 128 bit division result by 64 bit. + * + * Result must fit in 64 bits. Remainder is stored in r. + * + * @see libdivide libdivide_128_div_64_to_64() division fallback + * @see Knuth, Volume 2, section 4.3.1, Algorithm D for correctness proof + * @see https://danlark.org/2020/06/14/128-bit-division/ */ - -/* Translated from Figure 3-40 of The PowerPC Compiler Writer's Guide */ - -COMPILER_RT_ABI tu_int -__udivmodti4(tu_int a, tu_int b, tu_int* rem) -{ - const unsigned n_udword_bits = sizeof(du_int) * CHAR_BIT; - const unsigned n_utword_bits = sizeof(tu_int) * CHAR_BIT; - utwords n; - n.all = a; - utwords d; - d.all = b; - utwords q; - utwords r; - unsigned sr; - /* special cases, X is unknown, K != 0 */ - if (n.s.high == 0) - { - if (d.s.high == 0) - { - /* 0 X - * --- - * 0 X - */ - if (rem) - *rem = n.s.low % d.s.low; - return n.s.low / d.s.low; - } - /* 0 X - * --- - * K X - */ - if (rem) - *rem = n.s.low; - return 0; - } - /* n.s.high != 0 */ - if (d.s.low == 0) - { - if (d.s.high == 0) - { - /* K X - * --- - * 0 0 - */ - if (rem) - *rem = n.s.high % d.s.low; - return n.s.high / d.s.low; - } - /* d.s.high != 0 */ - if (n.s.low == 0) - { - /* K 0 - * --- - * K 0 - */ - if (rem) - { - r.s.high = n.s.high % d.s.high; - r.s.low = 0; - *rem = r.all; - } - return n.s.high / d.s.high; - } - /* K K - * --- - * K 0 - */ - if ((d.s.high & (d.s.high - 1)) == 0) /* if d is a power of 2 */ - { - if (rem) - { - r.s.low = n.s.low; - r.s.high = n.s.high & (d.s.high - 1); - *rem = r.all; - } - return n.s.high >> __builtin_ctzll(d.s.high); - } - /* K K - * --- - * K 0 - */ - sr = __builtin_clzll(d.s.high) - __builtin_clzll(n.s.high); - /* 0 <= sr <= n_udword_bits - 2 or sr large */ - if (sr > n_udword_bits - 2) - { - if (rem) - *rem = n.all; - return 0; - } - ++sr; - /* 1 <= sr <= n_udword_bits - 1 */ - /* q.all = n.all << (n_utword_bits - sr); */ - q.s.low = 0; - q.s.high = n.s.low << (n_udword_bits - sr); - /* r.all = n.all >> sr; */ - r.s.high = n.s.high >> sr; - r.s.low = (n.s.high << (n_udword_bits - sr)) | (n.s.low >> sr); - } - else /* d.s.low != 0 */ - { - if (d.s.high == 0) - { - /* K X - * --- - * 0 K - */ - if ((d.s.low & (d.s.low - 1)) == 0) /* if d is a power of 2 */ - { - if (rem) - *rem = n.s.low & (d.s.low - 1); - if (d.s.low == 1) - return n.all; - sr = __builtin_ctzll(d.s.low); - q.s.high = n.s.high >> sr; - q.s.low = (n.s.high << (n_udword_bits - sr)) | (n.s.low >> sr); - return q.all; - } - /* K X - * --- - * 0 K - */ - sr = 1 + n_udword_bits + __builtin_clzll(d.s.low) - - __builtin_clzll(n.s.high); - /* 2 <= sr <= n_utword_bits - 1 - * q.all = n.all << (n_utword_bits - sr); - * r.all = n.all >> sr; - */ - if (sr == n_udword_bits) - { - q.s.low = 0; - q.s.high = n.s.low; - r.s.high = 0; - r.s.low = n.s.high; - } - else if (sr < n_udword_bits) // 2 <= sr <= n_udword_bits - 1 - { - q.s.low = 0; - q.s.high = n.s.low << (n_udword_bits - sr); - r.s.high = n.s.high >> sr; - r.s.low = (n.s.high << (n_udword_bits - sr)) | (n.s.low >> sr); - } - else // n_udword_bits + 1 <= sr <= n_utword_bits - 1 - { - q.s.low = n.s.low << (n_utword_bits - sr); - q.s.high = (n.s.high << (n_utword_bits - sr)) | - (n.s.low >> (sr - n_udword_bits)); - r.s.high = 0; - r.s.low = n.s.high >> (sr - n_udword_bits); - } - } - else - { - /* K X - * --- - * K K - */ - sr = __builtin_clzll(d.s.high) - __builtin_clzll(n.s.high); - /*0 <= sr <= n_udword_bits - 1 or sr large */ - if (sr > n_udword_bits - 1) - { - if (rem) - *rem = n.all; - return 0; - } - ++sr; - /* 1 <= sr <= n_udword_bits - * q.all = n.all << (n_utword_bits - sr); - * r.all = n.all >> sr; - */ - q.s.low = 0; - if (sr == n_udword_bits) - { - q.s.high = n.s.low; - r.s.high = 0; - r.s.low = n.s.high; - } - else - { - r.s.high = n.s.high >> sr; - r.s.low = (n.s.high << (n_udword_bits - sr)) | (n.s.low >> sr); - q.s.high = n.s.low << (n_udword_bits - sr); - } - } - } - /* Not a special case - * q and r are initialized with: - * q.all = n.all << (n_utword_bits - sr); - * r.all = n.all >> sr; - * 1 <= sr <= n_utword_bits - 1 - */ - su_int carry = 0; - for (; sr > 0; --sr) - { - /* r:q = ((r:q) << 1) | carry */ - r.s.high = (r.s.high << 1) | (r.s.low >> (n_udword_bits - 1)); - r.s.low = (r.s.low << 1) | (q.s.high >> (n_udword_bits - 1)); - q.s.high = (q.s.high << 1) | (q.s.low >> (n_udword_bits - 1)); - q.s.low = (q.s.low << 1) | carry; - /* carry = 0; - * if (r.all >= d.all) - * { - * r.all -= d.all; - * carry = 1; - * } - */ - const ti_int s = (ti_int)(d.all - r.all - 1) >> (n_utword_bits - 1); - carry = s & 1; - r.all -= d.all & s; - } - q.all = (q.all << 1) | carry; - if (rem) - *rem = r.all; - return q.all; +forceinline du_int udiv128by64to64default(du_int u1, du_int u0, du_int v, + du_int *r) { + const unsigned n_udword_bits = sizeof(du_int) * CHAR_BIT; + const du_int b = 1ULL << (n_udword_bits / 2); // Number base (32 bits) + du_int un1, un0; // Norm. dividend LSD's + du_int vn1, vn0; // Norm. divisor digits + du_int q1, q0; // Quotient digits + du_int un64, un21, un10; // Dividend digit pairs + du_int rhat; // Remainder + si_int s; // Normalization shift + s = __builtin_clzll(v); + if (s > 0) { + // Normalize the divisor. + v = v << s; + un64 = (u1 << s) | (u0 >> (n_udword_bits - s)); + un10 = u0 << s; // Shift dividend left + } else { + // Avoid undefined behavior of (u0 >> 64). + un64 = u1; + un10 = u0; + } + // Break divisor up into two 32-bit digits. + vn1 = v >> (n_udword_bits / 2); + vn0 = v & 0xFFFFFFFF; + // Break right half of dividend into two digits. + un1 = un10 >> (n_udword_bits / 2); + un0 = un10 & 0xFFFFFFFF; + // Compute the first quotient digit, q1. + q1 = un64 / vn1; + rhat = un64 - q1 * vn1; + // q1 has at most error 2. No more than 2 iterations. + while (q1 >= b || q1 * vn0 > b * rhat + un1) { + q1 = q1 - 1; + rhat = rhat + vn1; + if (rhat >= b) break; + } + un21 = un64 * b + un1 - q1 * v; + // Compute the second quotient digit. + q0 = un21 / vn1; + rhat = un21 - q0 * vn1; + // q0 has at most error 2. No more than 2 iterations. + while (q0 >= b || q0 * vn0 > b * rhat + un0) { + q0 = q0 - 1; + rhat = rhat + vn1; + if (rhat >= b) break; + } + *r = (un21 * b + un0 - q0 * v) >> s; + return q1 * b + q0; } -#endif /* CRT_HAS_128BIT */ +forceinline du_int udiv128by64to64(du_int u1, du_int u0, du_int v, du_int *r) { +#ifdef __x86_64__ + du_int result; + asm("div\t%2" : "=a"(result), "=d"(*r) : "r"(v), "a"(u0), "d"(u1) : "cc"); + return result; +#else + return udiv128by64to64default(u1, u0, v, r); +#endif +} + +/** + * Performs 128-bit unsigned division and remainder. + * + * @param a is dividend + * @param b is divisor + * @param rem receives remainder if not NULL + */ +COMPILER_RT_ABI tu_int __udivmodti4(tu_int a, tu_int b, tu_int *rem) { + const unsigned n_utword_bits = sizeof(tu_int) * CHAR_BIT; + utwords dividend, divisor, quotient, remainder; + si_int shift; + dividend.all = a; + divisor.all = b; + if (divisor.all > dividend.all) { + if (rem) *rem = dividend.all; + return 0; + } + // When the divisor fits in 64 bits, we can use an optimized path. + if (divisor.s.high == 0) { + remainder.s.high = 0; + if (dividend.s.high < divisor.s.low) { + // The result fits in 64 bits. + quotient.s.low = udiv128by64to64(dividend.s.high, dividend.s.low, + divisor.s.low, &remainder.s.low); + quotient.s.high = 0; + } else { + // First, divide with the high part to get the remainder in + // dividend.s.high. After that dividend.s.high < divisor.s.low. + quotient.s.high = dividend.s.high / divisor.s.low; + dividend.s.high = dividend.s.high % divisor.s.low; + quotient.s.low = udiv128by64to64(dividend.s.high, dividend.s.low, + divisor.s.low, &remainder.s.low); + } + if (rem) *rem = remainder.all; + return quotient.all; + } + // 0 <= shift <= 63. + shift = __builtin_clzll(divisor.s.high) - __builtin_clzll(dividend.s.high); + divisor.all <<= shift; + quotient.s.high = 0; + quotient.s.low = 0; + for (; shift >= 0; --shift) { + quotient.s.low <<= 1; + // Branch free version of. + // if (dividend.all >= divisor.all) + // { + // dividend.all -= divisor.all; + // carry = 1; + // } + ti_int s = (ti_int)(divisor.all - dividend.all - 1) >> (n_utword_bits - 1); + quotient.s.low |= s & 1; + dividend.all -= divisor.all & s; + divisor.all >>= 1; + } + if (rem) *rem = dividend.all; + return quotient.all; +} diff --git a/third_party/compiler_rt/udivti3.c b/third_party/compiler_rt/udivti3.c index f4a6b362..8aa79bd3 100644 --- a/third_party/compiler_rt/udivti3.c +++ b/third_party/compiler_rt/udivti3.c @@ -1,30 +1,14 @@ -/* clang-format off */ -/* ===-- udivti3.c - Implement __udivti3 -----------------------------------=== - * - * The LLVM Compiler Infrastructure - * - * This file is dual licensed under the MIT and the University of Illinois Open - * Source Licenses. See LICENSE.TXT for details. - * - * ===----------------------------------------------------------------------=== - * - * This file implements __udivti3 for the compiler_rt library. - * - * ===----------------------------------------------------------------------=== - */ - -STATIC_YOINK("huge_compiler_rt_license"); - +#if 0 +/*─────────────────────────────────────────────────────────────────╗ +│ To the extent possible under law, Justine Tunney has waived │ +│ all copyright and related or neighboring rights to division, │ +│ as it is written in the following disclaimers: │ +│ • http://unlicense.org/ │ +│ • http://creativecommons.org/publicdomain/zero/1.0/ │ +╚─────────────────────────────────────────────────────────────────*/ +#endif #include "third_party/compiler_rt/int_lib.h" -#ifdef CRT_HAS_128BIT - -/* Returns: a / b */ - -COMPILER_RT_ABI tu_int -__udivti3(tu_int a, tu_int b) -{ - return __udivmodti4(a, b, 0); +COMPILER_RT_ABI tu_int __udivti3(tu_int a, tu_int b) { + return __udivmodti4(a, b, NULL); } - -#endif /* CRT_HAS_128BIT */ diff --git a/third_party/ctags/COPYING b/third_party/ctags/COPYING new file mode 100644 index 00000000..60549be5 --- /dev/null +++ b/third_party/ctags/COPYING @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) 19yy + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) 19yy name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/third_party/ctags/README.txt b/third_party/ctags/README.txt new file mode 100644 index 00000000..9ef1cceb --- /dev/null +++ b/third_party/ctags/README.txt @@ -0,0 +1,1195 @@ +CTAGS(1) Exuberant Ctags CTAGS(1) + + + +𝐍𝐀𝐌𝐄 + ctags - Generate tag files for source code + + + +𝐒𝐘𝐍𝐎𝐏𝐒𝐈𝐒 + 𝗰𝘁𝗮𝗴𝘀 [𝗼𝗽𝘁𝗶𝗼𝗻𝘀] [f̲i̲l̲e̲(̲s̲)̲] + + 𝗲𝘁𝗮𝗴𝘀 [𝗼𝗽𝘁𝗶𝗼𝗻𝘀] [f̲i̲l̲e̲(̲s̲)̲] + + + +𝐃𝐄𝐒𝐂𝐑𝐈𝐏𝐓𝐈𝐎𝐍 + The 𝗰𝘁𝗮𝗴𝘀 and 𝗲𝘁𝗮𝗴𝘀 programs (hereinafter collectively referred + to as 𝗰𝘁𝗮𝗴𝘀, except where distinguished) generate an index (or + "tag") file for a variety of language objects found in f̲i̲l̲e̲(̲s̲)̲. + This tag file allows these items to be quickly and easily located + by a text editor or other utility. A "tag" signifies a language + object for which an index entry is available (or, alternatively, + the index entry created for that object). + + Alternatively, 𝗰𝘁𝗮𝗴𝘀 can generate a cross reference file which + lists, in human readable form, information about the various + source objects found in a set of language files. + + Tag index files are supported by numerous editors, which allow + the user to locate the object associated with a name appearing in + a source file and jump to the file and line which defines the + name. Those known about at the time of this release are: + + 𝐕𝗶(1) and its derivatives (e.g. Elvis, Vim, Vile, Lemmy), + 𝐂𝐑𝗶𝐒𝐏, 𝐄𝗺𝗮𝗰𝘀, 𝐅𝐓𝐄 (Folding Text Editor), 𝐉𝐄𝐃, 𝗷𝐄𝗱𝗶𝘁, 𝐌𝗶𝗻𝗲𝗱, + 𝐍𝐄𝗱𝗶𝘁 (Nirvana Edit), 𝐓𝐒𝐄 (The SemWare Editor), 𝐔𝗹𝘁𝗿𝗮𝐄𝗱𝗶𝘁, + 𝐖𝗼𝗿𝗸𝐒𝗽𝗮𝗰𝗲, 𝐗𝟮, 𝐙𝗲𝘂𝘀 + + 𝐂𝘁𝗮𝗴𝘀 is capable of generating different kinds of tags for each + of many different languages. For a complete list of supported + languages, the names by which they are recognized, and the kinds + of tags which are generated for each, see the --𝗹𝗶𝘀𝘁-𝗹𝗮𝗻𝗴𝘂𝗮𝗴𝗲𝘀 + and --𝗹𝗶𝘀𝘁-𝗸𝗶𝗻𝗱𝘀 options. + + + +𝐒𝐎𝐔𝐑𝐂𝐄 𝐅𝐈𝐋𝐄𝐒 + Unless the --𝗹𝗮𝗻𝗴𝘂𝗮𝗴𝗲-𝗳𝗼𝗿𝗰𝗲 option is specified, the language of + each source file is automatically selected based upon a mapping + of file names to languages. The mappings in effect for each lan‐ + guage may be display using the --𝗹𝗶𝘀𝘁-𝗺𝗮𝗽𝘀 option and may be + changed using the --𝗹𝗮𝗻𝗴𝗺𝗮𝗽 option. On platforms which support + it, if the name of a file is not mapped to a language and the + file is executable, the first line of the file is checked to see + if the file is a "#!" script for a recognized language. + + By default, all other files names are ignored. This permits run‐ + ning 𝗰𝘁𝗮𝗴𝘀 on all files in either a single directory (e.g. "ctags + *"), or on all files in an entire source directory tree (e.g. + "ctags -R"), since only those files whose names are mapped to + languages will be scanned. + + [The reason that .h extensions are mapped to C++ files rather + than C files is because it is common to use .h extensions in C++, + and no harm results in treating them as C++ files.] + + + +𝐎𝐏𝐓𝐈𝐎𝐍𝐒 + Despite the wealth of available options, defaults are set so that + 𝗰𝘁𝗮𝗴𝘀 is most commonly executed without any options (e.g. "ctags + *", or "ctags -R"), which will create a tag file in the current + directory for all recognized source files. The options described + below are provided merely to allow custom tailoring to meet spe‐ + cial needs. + + Note that spaces separating the single-letter options from their + parameters are optional. + + Note also that the boolean parameters to the long form options + (those beginning with "--" and that take a "[̲=̲y̲e̲s̲|n̲o̲]̲" parameter) + may be omitted, in which case "=y̲e̲s̲" is implied. (e.g. --𝘀𝗼𝗿𝘁 is + equivalent to --𝘀𝗼𝗿𝘁=y̲e̲s̲). Note further that "=1̲" and "=o̲n̲" are + considered synonyms for "=y̲e̲s̲", and that "=0̲" and "=o̲f̲f̲" are con‐ + sidered synonyms for "=n̲o̲". + + Some options are either ignored or useful only when used while + running in etags mode (see -𝗲 option). Such options will be + noted. + + Most options may appear anywhere on the command line, affecting + only those files which follow the option. A few options, however, + must appear before the first file name and will be noted as such. + + Options taking language names will accept those names in either + upper or lower case. See the --𝗹𝗶𝘀𝘁-𝗹𝗮𝗻𝗴𝘂𝗮𝗴𝗲𝘀 option for a com‐ + plete list of the built-in language names. + + + -𝗮 Equivalent to --𝗮𝗽𝗽𝗲𝗻𝗱. + + + -𝐁 Use backward searching patterns (e.g. ?pattern?). [Ignored + in etags mode] + + + -𝗲 Enable etags mode, which will create a tag file for use with + the Emacs editor. Alternatively, if 𝗰𝘁𝗮𝗴𝘀 is invoked by a + name containing the string "etags" (either by renaming, or + creating a link to, the executable), etags mode will be en‐ + abled. This option must appear before the first file name. + + + -𝗳 t̲a̲g̲f̲i̲l̲e̲ + Use the name specified by t̲a̲g̲f̲i̲l̲e̲ for the tag file (default + is "tags", or "TAGS" when running in etags mode). If t̲a̲g̲f̲i̲l̲e̲ + is specified as "-", then the tag file is written to stan‐ + dard output instead. 𝐂𝘁𝗮𝗴𝘀 will stubbornly refuse to take + orders if t̲a̲g̲f̲i̲l̲e̲ exists and its first line contains some‐ + thing other than a valid tags line. This will save your neck + if you mistakenly type "ctags -f *.c", which would otherwise + overwrite your first C file with the tags generated by the + rest! It will also refuse to accept a multi-character file + name which begins with a '-' (dash) character, since this + most likely means that you left out the tag file name and + this option tried to grab the next option as the file name. + If you really want to name your output tag file "-ugly", + specify it as "./-ugly". This option must appear before the + first file name. If this option is specified more than once, + only the last will apply. + + + -𝐅 Use forward searching patterns (e.g. /pattern/) (default). + [Ignored in etags mode] + + + -𝗵 l̲i̲s̲t̲ + Specifies a list of file extensions, separated by periods, + which are to be interpreted as include (or header) files. To + indicate files having no extension, use a period not fol‐ + lowed by a non-period character (e.g. ".", "..x", ".x."). + This option only affects how the scoping of a particular + kinds of tags is interpreted (i.e. whether or not they are + considered as globally visible or visible only within the + file in which they are defined); it does not map the exten‐ + sion to any particular language. Any tag which is located in + a non-include file and cannot be seen (e.g. linked to) from + another file is considered to have file-limited (e.g. + static) scope. No kind of tag appearing in an include file + will be considered to have file-limited scope. If the first + character in the list is a plus sign, then the extensions in + the list will be appended to the current list; otherwise, + the list will replace the current list. See, also, the + --𝗳𝗶𝗹𝗲-𝘀𝗰𝗼𝗽𝗲 option. The default list is + ".h.H.hh.hpp.hxx.h++.inc.def". To restore the default list, + specify -𝗵 d̲e̲f̲a̲u̲l̲t̲. Note that if an extension supplied to + this option is not already mapped to a particular language + (see 𝐒𝐎𝐔𝐑𝐂𝐄 𝐅𝐈𝐋𝐄𝐒, above), you will also need to use either + the --𝗹𝗮𝗻𝗴𝗺𝗮𝗽 or --𝗹𝗮𝗻𝗴𝘂𝗮𝗴𝗲-𝗳𝗼𝗿𝗰𝗲 option. + + + -𝐈 i̲d̲e̲n̲t̲i̲f̲i̲e̲r̲-̲l̲i̲s̲t̲ + Specifies a list of identifiers which are to be specially + handled while parsing C and C++ source files. This option is + specifically provided to handle special cases arising + through the use of preprocessor macros. When the identifiers + listed are simple identifiers, these identifiers will be ig‐ + nored during parsing of the source files. If an identifier + is suffixed with a '+' character, 𝗰𝘁𝗮𝗴𝘀 will also ignore any + parenthesis-enclosed argument list which may immediately + follow the identifier in the source files. If two identi‐ + fiers are separated with the '=' character, the first iden‐ + tifiers is replaced by the second identifiers for parsing + purposes. The list of identifiers may be supplied directly + on the command line or read in from a separate file. If the + first character of i̲d̲e̲n̲t̲i̲f̲i̲e̲r̲-̲l̲i̲s̲t̲ is '@', '.' or a pathname + separator ('/' or '\'), or the first two characters specify + a drive letter (e.g. "C:"), the parameter i̲d̲e̲n̲t̲i̲f̲i̲e̲r̲-̲l̲i̲s̲t̲ + will be interpreted as a filename from which to read a list + of identifiers, one per input line. Otherwise, i̲d̲e̲n̲t̲i̲‐̲ + f̲i̲e̲r̲-̲l̲i̲s̲t̲ is a list of identifiers (or identifier pairs) to + be specially handled, each delimited by a either a comma or + by white space (in which case the list should be quoted to + keep the entire list as one command line argument). Multiple + -𝐈 options may be supplied. To clear the list of ignore + identifiers, supply a single dash ("-") for i̲d̲e̲n̲t̲i̲f̲i̲e̲r̲-̲l̲i̲s̲t̲. + + This feature is useful when preprocessor macros are used in + such a way that they cause syntactic confusion due to their + presence. Indeed, this is the best way of working around a + number of problems caused by the presence of syntax-busting + macros in source files (see 𝐂𝐀𝐕𝐄𝐀𝐓𝐒, below). Some examples + will illustrate this point. + + int foo ARGDECL4(void *, ptr, long int, nbytes) + + + In the above example, the macro "ARGDECL4" would be mistak‐ + enly interpreted to be the name of the function instead of + the correct name of "foo". Specifying -𝐈 A̲R̲G̲D̲E̲C̲L̲4̲ results in + the correct behavior. + + /* creates an RCS version string in module */ + MODULE_VERSION("$Revision: 750 $") + + + In the above example the macro invocation looks too much + like a function definition because it is not followed by a + semicolon (indeed, it could even be followed by a global + variable definition that would look much like a K&R style + function parameter declaration). In fact, this seeming func‐ + tion definition could possibly even cause the rest of the + file to be skipped over while trying to complete the defini‐ + tion. Specifying -𝐈 M̲O̲D̲U̲L̲E̲_V̲E̲R̲S̲I̲O̲N̲+̲ would avoid such a prob‐ + lem. + + CLASS Example { + // your content here + }; + + + The example above uses "CLASS" as a preprocessor macro which + expands to something different for each platform. For in‐ + stance CLASS may be defined as "class __declspec(dllexport)" + on Win32 platforms and simply "class" on UNIX. Normally, + the absence of the C++ keyword "class" would cause the + source file to be incorrectly parsed. Correct behavior can + be restored by specifying -𝐈 C̲L̲A̲S̲S̲=̲c̲l̲a̲s̲s̲. + + + -𝐋 f̲i̲l̲e̲ + Read from f̲i̲l̲e̲ a list of file names for which tags should be + generated. If f̲i̲l̲e̲ is specified as "-", then file names are + read from standard input. File names read using this option + are processed following file names appearing on the command + line. Options are also accepted in this input. If this op‐ + tion is specified more than once, only the last will apply. + 𝐍𝗼𝘁𝗲: f̲i̲l̲e̲ is read in line-oriented mode, where a new line + is the only delimiter and non-trailing white space is con‐ + sidered significant, in order that file names containing + spaces may be supplied (however, trailing white space is + stripped from lines); this can affect how options are parsed + if included in the input. + + + -𝗻 Equivalent to --𝗲𝘅𝗰𝗺𝗱=n̲u̲m̲b̲e̲r̲. + + + -𝐍 Equivalent to --𝗲𝘅𝗰𝗺𝗱=p̲a̲t̲t̲e̲r̲n̲. + + + -𝗼 t̲a̲g̲f̲i̲l̲e̲ + Equivalent to -𝗳 t̲a̲g̲f̲i̲l̲e̲. + + + -𝐑 Equivalent to --𝗿𝗲𝗰𝘂𝗿𝘀𝗲. + + + -𝘂 Equivalent to --𝘀𝗼𝗿𝘁=n̲o̲ (i.e. "unsorted"). + + + -𝐕 Equivalent to --𝘃𝗲𝗿𝗯𝗼𝘀𝗲. + + + -𝘄 This option is silently ignored for backward-compatibility + with the ctags of SVR4 Unix. + + + -𝘅 Print a tabular, human-readable cross reference (xref) file + to standard output instead of generating a tag file. The in‐ + formation contained in the output includes: the tag name; + the kind of tag; the line number, file name, and source line + (with extra white space condensed) of the file which defines + the tag. No tag file is written and all options affecting + tag file output will be ignored. Example applications for + this feature are generating a listing of all functions lo‐ + cated in a source file (e.g. 𝗰𝘁𝗮𝗴𝘀 -𝘅 --𝗰-𝗸𝗶𝗻𝗱𝘀=f̲ f̲i̲l̲e̲), or + generating a list of all externally visible global variables + located in a source file (e.g. 𝗰𝘁𝗮𝗴𝘀 -𝘅 --𝗰-𝗸𝗶𝗻𝗱𝘀=v̲ + --𝗳𝗶𝗹𝗲-𝘀𝗰𝗼𝗽𝗲=n̲o̲ f̲i̲l̲e̲). This option must appear before the + first file name. + + + --𝗮𝗽𝗽𝗲𝗻𝗱[=y̲e̲s̲|n̲o̲] + Indicates whether tags generated from the specified files + should be appended to those already present in the tag file + or should replace them. This option is off by default. This + option must appear before the first file name. + + + --𝗲𝘁𝗮𝗴𝘀-𝗶𝗻𝗰𝗹𝘂𝗱𝗲=f̲i̲l̲e̲ + Include a reference to f̲i̲l̲e̲ in the tag file. This option may + be specified as many times as desired. This supports Emacs' + capability to use a tag file which "includes" other tag + files. [Available only in etags mode] + + + --𝗲𝘅𝗰𝗹𝘂𝗱𝗲=[p̲a̲t̲t̲e̲r̲n̲] + Add p̲a̲t̲t̲e̲r̲n̲ to a list of excluded files and directories. + This option may be specified as many times as desired. For + each file name considered by 𝗰𝘁𝗮𝗴𝘀, each p̲a̲t̲t̲e̲r̲n̲ specified + using this option will be compared against both the complete + path (e.g. some/path/base.ext) and the base name (e.g. + base.ext) of the file, thus allowing patterns which match a + given file name irrespective of its path, or match only a + specific path. If appropriate support is available from the + runtime library of your C compiler, then p̲a̲t̲t̲e̲r̲n̲ may contain + the usual shell wildcards (not regular expressions) common + on Unix (be sure to quote the option parameter to protect + the wildcards from being expanded by the shell before being + passed to 𝗰𝘁𝗮𝗴𝘀; also be aware that wildcards can match the + slash character, '/'). You can determine if shell wildcards + are available on your platform by examining the output of + the --𝘃𝗲𝗿𝘀𝗶𝗼𝗻 option, which will include "+wildcards" in the + compiled feature list; otherwise, p̲a̲t̲t̲e̲r̲n̲ is matched against + file names using a simple textual comparison. + + If p̲a̲t̲t̲e̲r̲n̲ begins with the character '@', then the rest of + the string is interpreted as a file name from which to read + exclusion patterns, one per line. If p̲a̲t̲t̲e̲r̲n̲ is empty, the + list of excluded patterns is cleared. Note that at program + startup, the default exclude list contains "EIFGEN", "SCCS", + "RCS", and "CVS", which are names of directories for which + it is generally not desirable to descend while processing + the --𝗿𝗲𝗰𝘂𝗿𝘀𝗲 option. + + + --𝗲𝘅𝗰𝗺𝗱=t̲y̲p̲e̲ + Determines the type of EX command used to locate tags in the + source file. [Ignored in etags mode] + + The valid values for t̲y̲p̲e̲ (either the entire word or the + first letter is accepted) are: + + + n̲u̲m̲b̲e̲r̲ Use only line numbers in the tag file for locating + tags. This has four advantages: + 1. Significantly reduces the size of the resulting + tag file. + 2. Eliminates failures to find tags because the + line defining the tag has changed, causing the + pattern match to fail (note that some editors, + such as 𝘃𝗶𝗺, are able to recover in many such + instances). + 3. Eliminates finding identical matching, but in‐ + correct, source lines (see 𝐁𝐔𝐆𝐒, below). + 4. Retains separate entries in the tag file for + lines which are identical in content. In p̲a̲t̲‐̲ + t̲e̲r̲n̲ mode, duplicate entries are dropped be‐ + cause the search patterns they generate are + identical, making the duplicate entries use‐ + less. + + + However, this option has one significant drawback: + changes to the source files can cause the line num‐ + bers recorded in the tag file to no longer corre‐ + spond to the lines in the source file, causing + jumps to some tags to miss the target definition by + one or more lines. Basically, this option is best + used when the source code to which it is applied is + not subject to change. Selecting this option type + causes the following options to be ignored: -𝐁𝐅. + + + p̲a̲t̲t̲e̲r̲n̲ Use only search patterns for all tags, rather than + the line numbers usually used for macro defini‐ + tions. This has the advantage of not referencing + obsolete line numbers when lines have been added or + removed since the tag file was generated. + + + m̲i̲x̲e̲d̲ In this mode, patterns are generally used with a + few exceptions. For C, line numbers are used for + macro definition tags. This was the default format + generated by the original 𝗰𝘁𝗮𝗴𝘀 and is, therefore, + retained as the default for this option. For For‐ + tran, line numbers are used for common blocks be‐ + cause their corresponding source lines are gener‐ + ally identical, making pattern searches useless for + finding all matches. + + + --𝗲𝘅𝘁𝗿𝗮=[̲+̲|̲-̲]̲f̲l̲a̲g̲s̲ + Specifies whether to include extra tag entries for certain + kinds of information. The parameter f̲l̲a̲g̲s̲ is a set of one- + letter flags, each representing one kind of extra tag entry + to include in the tag file. If f̲l̲a̲g̲s̲ is preceded by either + the '+' or '-' character, the effect of each flag is added + to, or removed from, those currently enabled; otherwise the + flags replace any current settings. The meaning of each flag + is as follows: + + + f̲ Include an entry for the base file name of every + source file (e.g. "example.c"), which addresses the + first line of the file. + + q̲ Include an extra class-qualified tag entry for each + tag which is a member of a class (for languages for + which this information is extracted; currently C++, + Eiffel, and Java). The actual form of the qualified + tag depends upon the language from which the tag was + derived (using a form that is most natural for how + qualified calls are specified in the language). For + C++, it is in the form "class::member"; for Eiffel + and Java, it is in the form "class.member". This may + allow easier location of a specific tags when multi‐ + ple occurrences of a tag name occur in the tag file. + Note, however, that this could potentially more than + double the size of the tag file. + + + --𝗳𝗶𝗲𝗹𝗱𝘀=[̲+̲|̲-̲]̲f̲l̲a̲g̲s̲ + Specifies the available extension fields which are to be in‐ + cluded in the entries of the tag file (see 𝐓𝐀𝐆 𝐅𝐈𝐋𝐄 𝐅𝐎𝐑𝐌𝐀𝐓, + below, for more information). The parameter f̲l̲a̲g̲s̲ is a set + of one-letter flags, each representing one type of extension + field to include, with the following meanings (disabled by + default unless indicated): + + + a̲ Access (or export) of class members + f̲ File-restricted scoping [enabled] + i̲ Inheritance information + k̲ Kind of tag as a single letter [enabled] + K̲ Kind of tag as full name + l̲ Language of source file containing tag + m̲ Implementation information + n̲ Line number of tag definition + s̲ Scope of tag definition [enabled] + S̲ Signature of routine (e.g. prototype or parameter + list) + z̲ Include the "kind:" key in kind field + t̲ Type and name of a variable or typedef as "typeref:" + field [enabled] + + Each letter or group of letters may be preceded by either + '+' to add it to the default set, or '-' to exclude it. In + the absence of any preceding '+' or '-' sign, only those + kinds explicitly listed in f̲l̲a̲g̲s̲ will be included in the + output (i.e. overriding the default set). This option is ig‐ + nored if the option --𝗳𝗼𝗿𝗺𝗮𝘁=1̲ has been specified. The de‐ + fault value of this option is f̲k̲s̲t̲. + + + --𝗳𝗶𝗹𝗲-𝘀𝗰𝗼𝗽𝗲[=y̲e̲s̲|n̲o̲] + Indicates whether tags scoped only for a single file (i.e. + tags which cannot be seen outside of the file in which they + are defined, such as "static" tags) should be included in + the output. See, also, the -𝗵 option. This option is enabled + by default. + + + --𝗳𝗶𝗹𝘁𝗲𝗿[=y̲e̲s̲|n̲o̲] + Causes 𝗰𝘁𝗮𝗴𝘀 to behave as a filter, reading source file + names from standard input and printing their tags to stan‐ + dard output on a file-by-file basis. If --𝘀𝗼𝗿𝘁𝗲𝗱 is enabled, + tags are sorted only within the source file in which they + are defined. File names are read from standard input in + line-oriented input mode (see note for -𝐋 option) and only + after file names listed on the command line or from any file + supplied using the -𝐋 option. When this option is enabled, + the options -𝗳, -𝗼, and --𝘁𝗼𝘁𝗮𝗹𝘀 are ignored. This option is + quite esoteric and is disabled by default. This option must + appear before the first file name. + + + --𝗳𝗶𝗹𝘁𝗲𝗿-𝘁𝗲𝗿𝗺𝗶𝗻𝗮𝘁𝗼𝗿=s̲t̲r̲i̲n̲g̲ + Specifies a string to print to standard output following the + tags for each file name parsed when the --𝗳𝗶𝗹𝘁𝗲𝗿 option is + enabled. This may permit an application reading the output + of ctags to determine when the output for each file is fin‐ + ished. Note that if the file name read is a directory and + --𝗿𝗲𝗰𝘂𝗿𝘀𝗲 is enabled, this string will be printed only once + at the end of all tags found for by descending the direc‐ + tory. This string will always be separated from the last tag + line for the file by its terminating newline. This option + is quite esoteric and is empty by default. This option must + appear before the first file name. + + + --𝗳𝗼𝗿𝗺𝗮𝘁=l̲e̲v̲e̲l̲ + Change the format of the output tag file. Currently the only + valid values for l̲e̲v̲e̲l̲ are 1̲ or 2̲. Level 1 specifies the + original tag file format and level 2 specifies a new ex‐ + tended format containing extension fields (but in a manner + which retains backward-compatibility with original 𝘃𝗶(1) im‐ + plementations). The default level is 2. This option must ap‐ + pear before the first file name. [Ignored in etags mode] + + + --𝗵𝗲𝗹𝗽 + Prints to standard output a detailed usage description, and + then exits. + + + --𝗶𝗳𝟬[=y̲e̲s̲|n̲o̲] + Indicates a preference as to whether code within an "#if 0" + branch of a preprocessor conditional should be examined for + non-macro tags (macro tags are always included). Because the + intent of this construct is to disable code, the default + value of this option is n̲o̲. Note that this indicates a pref‐ + erence only and does not guarantee skipping code within an + "#if 0" branch, since the fall-back algorithm used to gener‐ + ate tags when preprocessor conditionals are too complex fol‐ + lows all branches of a conditional. This option is disabled + by default. + + + --<𝐋𝐀𝐍𝐆>-𝗸𝗶𝗻𝗱𝘀=[̲+̲|̲-̲]̲k̲i̲n̲d̲s̲ + Specifies a list of language-specific kinds of tags (or + kinds) to include in the output file for a particular lan‐ + guage, where <𝐋𝐀𝐍𝐆> is case-insensitive and is one of the + built-in language names (see the --𝗹𝗶𝘀𝘁-𝗹𝗮𝗻𝗴𝘂𝗮𝗴𝗲𝘀 option for + a complete list). The parameter k̲i̲n̲d̲s̲ is a group of one-let‐ + ter flags designating kinds of tags (particular to the lan‐ + guage) to either include or exclude from the output. The + specific sets of flags recognized for each language, their + meanings and defaults may be list using the --𝗹𝗶𝘀𝘁-𝗸𝗶𝗻𝗱𝘀 op‐ + tion. Each letter or group of letters may be preceded by ei‐ + ther '+' to add it to, or '-' to remove it from, the default + set. In the absence of any preceding '+' or '-' sign, only + those kinds explicitly listed in k̲i̲n̲d̲s̲ will be included in + the output (i.e. overriding the default for the specified + language). + + As an example for the C language, in order to add prototypes + and external variable declarations to the default set of tag + kinds, but exclude macros, use --𝗰-𝗸𝗶𝗻𝗱𝘀=+̲p̲x̲-̲d̲; to include + only tags for functions, use --𝗰-𝗸𝗶𝗻𝗱𝘀=f̲. + + + --𝗹𝗮𝗻𝗴𝗱𝗲𝗳=n̲a̲m̲e̲ + Defines a new user-defined language, n̲a̲m̲e̲, to be parsed with + regular expressions. Once defined, n̲a̲m̲e̲ may be used in other + options taking language names. The typical use of this op‐ + tion is to first define the language, then map file names to + it using -̲-̲l̲a̲n̲g̲m̲a̲p̲, then specify regular expressions using + -̲-̲r̲e̲g̲e̲x̲-̲<̲L̲A̲N̲G̲>̲ to define how its tags are found. + + + --𝗹𝗮𝗻𝗴𝗺𝗮𝗽=m̲a̲p̲[̲,̲m̲a̲p̲[̲.̲.̲.̲]̲]̲ + Controls how file names are mapped to languages (see the + --𝗹𝗶𝘀𝘁-𝗺𝗮𝗽𝘀 option). Each comma-separated m̲a̲p̲ consists of + the language name (either a built-in or user-defined lan‐ + guage), a colon, and a list of file extensions and/or file + name patterns. A file extension is specified by preceding + the extension with a period (e.g. ".c"). A file name pattern + is specified by enclosing the pattern in parentheses (e.g. + "([Mm]akefile)"). If appropriate support is available from + the runtime library of your C compiler, then the file name + pattern may contain the usual shell wildcards common on Unix + (be sure to quote the option parameter to protect the wild‐ + cards from being expanded by the shell before being passed + to 𝗰𝘁𝗮𝗴𝘀). You can determine if shell wildcards are avail‐ + able on your platform by examining the output of the --𝘃𝗲𝗿‐ + 𝘀𝗶𝗼𝗻 option, which will include "+wildcards" in the compiled + feature list; otherwise, the file name patterns are matched + against file names using a simple textual comparison. When + mapping a file extension, it will first be unmapped from any + other languages. + + If the first character in a map is a plus sign, then the ex‐ + tensions and file name patterns in that map will be appended + to the current map for that language; otherwise, the map + will replace the current map. For example, to specify that + only files with extensions of .c and .x are to be treated as + C language files, use "--𝗹𝗮𝗻𝗴𝗺𝗮𝗽=c̲:̲.̲c̲.̲x̲"; to also add files + with extensions of .j as Java language files, specify + "--𝗹𝗮𝗻𝗴𝗺𝗮𝗽=c̲:̲.̲c̲.̲x̲,̲j̲a̲v̲a̲:̲+̲.̲j̲". To map makefiles (e.g. files + named either "Makefile", "makefile", or having the extension + ".mak") to a language called "make", specify + "--𝗹𝗮𝗻𝗴𝗺𝗮𝗽=m̲a̲k̲e̲:̲(̲[̲M̲m̲]̲a̲k̲e̲f̲i̲l̲e̲)̲.̲m̲a̲k̲". To map files having no + extension, specify a period not followed by a non-period + character (e.g. ".", "..x", ".x."). To clear the mapping for + a particular language (thus inhibiting automatic generation + of tags for that language), specify an empty extension list + (e.g. "--𝗹𝗮𝗻𝗴𝗺𝗮𝗽=f̲o̲r̲t̲r̲a̲n̲:̲"). To restore the default lan‐ + guage mappings for all a particular language, supply the + keyword "default" for the mapping. To specify restore the + default language mappings for all languages, specify + "--𝗹𝗮𝗻𝗴𝗺𝗮𝗽=d̲e̲f̲a̲u̲l̲t̲". Note that file extensions are tested + before file name patterns when inferring the language of a + file. + + + --𝗹𝗮𝗻𝗴𝘂𝗮𝗴𝗲-𝗳𝗼𝗿𝗰𝗲=l̲a̲n̲g̲u̲a̲g̲e̲ + By default, 𝗰𝘁𝗮𝗴𝘀 automatically selects the language of a + source file, ignoring those files whose language cannot be + determined (see 𝐒𝐎𝐔𝐑𝐂𝐄 𝐅𝐈𝐋𝐄𝐒, above). This option forces the + specified l̲a̲n̲g̲u̲a̲g̲e̲ (case-insensitive; either built-in or + user-defined) to be used for every supplied file instead of + automatically selecting the language based upon its exten‐ + sion. In addition, the special value a̲u̲t̲o̲ indicates that the + language should be automatically selected (which effectively + disables this option). + + + --𝗹𝗮𝗻𝗴𝘂𝗮𝗴𝗲𝘀=[̲+̲|̲-̲]̲l̲i̲s̲t̲ + Specifies the languages for which tag generation is enabled, + with l̲i̲s̲t̲ containing a comma-separated list of language + names (case-insensitive; either built-in or user-defined). + If the first language of l̲i̲s̲t̲ is not preceded by either a + '+' or '-', the current list will be cleared before adding + or removing the languages in l̲i̲s̲t̲. Until a '-' is encoun‐ + tered, each language in the list will be added to the cur‐ + rent list. As either the '+' or '-' is encountered in the + list, the languages following it are added or removed from + the current list, respectively. Thus, it becomes simple to + replace the current list with a new one, or to add or remove + languages from the current list. The actual list of files + for which tags will be generated depends upon the language + extension mapping in effect (see the --𝗹𝗮𝗻𝗴𝗺𝗮𝗽 option). Note + that all languages, including user-defined languages are en‐ + abled unless explicitly disabled using this option. Language + names included in l̲i̲s̲t̲ may be any built-in language or one + previously defined with --𝗹𝗮𝗻𝗴𝗱𝗲𝗳. The default is "all", + which is also accepted as a valid argument. See the + --𝗹𝗶𝘀𝘁-𝗹𝗮𝗻𝗴𝘂𝗮𝗴𝗲𝘀 option for a complete list of the built-in + language names. + + + --𝗹𝗶𝗰𝗲𝗻𝘀𝗲 + Prints a summary of the software license to standard output, + and then exits. + + + --𝗹𝗶𝗻𝗲-𝗱𝗶𝗿𝗲𝗰𝘁𝗶𝘃𝗲𝘀[=y̲e̲s̲|n̲o̲] + Specifies whether "#line" directives should be recognized. + These are present in the output of preprocessors and contain + the line number, and possibly the file name, of the original + source file(s) from which the preprocessor output file was + generated. When enabled, this option will cause 𝗰𝘁𝗮𝗴𝘀 to + generate tag entries marked with the file names and line + numbers of their locations original source file(s), instead + of their actual locations in the preprocessor output. The + actual file names placed into the tag file will have the + same leading path components as the preprocessor output + file, since it is assumed that the original source files are + located relative to the preprocessor output file (unless, of + course, the #line directive specifies an absolute path). + This option is off by default. 𝐍𝗼𝘁𝗲: This option is gener‐ + ally only useful when used together with the --𝗲𝘅𝗰𝗺𝗱=n̲u̲m̲b̲e̲r̲ + (-𝗻) option. Also, you may have to use either the --𝗹𝗮𝗻𝗴𝗺𝗮𝗽 + or --𝗹𝗮𝗻𝗴𝘂𝗮𝗴𝗲-𝗳𝗼𝗿𝗰𝗲 option if the extension of the pre‐ + processor output file is not known to 𝗰𝘁𝗮𝗴𝘀. + + + --𝗹𝗶𝗻𝗸𝘀[=y̲e̲s̲|n̲o̲] + Indicates whether symbolic links (if supported) should be + followed. When disabled, symbolic links are ignored. This + option is on by default. + + + --𝗹𝗶𝘀𝘁-𝗸𝗶𝗻𝗱𝘀[=l̲a̲n̲g̲u̲a̲g̲e̲|a̲l̲l̲] + Lists the tag kinds recognized for either the specified lan‐ + guage or all languages, and then exits. Each kind of tag + recorded in the tag file is represented by a one-letter + flag, which is also used to filter the tags placed into the + output through use of the --<𝐋𝐀𝐍𝐆>-𝗸𝗶𝗻𝗱𝘀 option. Note that + some languages and/or tag kinds may be implemented using + regular expressions and may not be available if regex sup‐ + port is not compiled into 𝗰𝘁𝗮𝗴𝘀 (see the --𝗿𝗲𝗴𝗲𝘅-<𝐋𝐀𝐍𝐆> op‐ + tion). Each kind listed is enabled unless followed by + "[off]". + + + --𝗹𝗶𝘀𝘁-𝗺𝗮𝗽𝘀[=l̲a̲n̲g̲u̲a̲g̲e̲|a̲l̲l̲] + Lists the file extensions and file name patterns which asso‐ + ciate a file name with a language for either the specified + language or all languages, and then exits. See the --𝗹𝗮𝗻𝗴𝗺𝗮𝗽 + option, and 𝐒𝐎𝐔𝐑𝐂𝐄 𝐅𝐈𝐋𝐄𝐒, above. + + + --𝗹𝗶𝘀𝘁-𝗹𝗮𝗻𝗴𝘂𝗮𝗴𝗲𝘀 + Lists the names of the languages understood by 𝗰𝘁𝗮𝗴𝘀, and + then exits. These language names are case insensitive and + may be used in the --𝗹𝗮𝗻𝗴𝘂𝗮𝗴𝗲-𝗳𝗼𝗿𝗰𝗲, --𝗹𝗮𝗻𝗴𝘂𝗮𝗴𝗲𝘀, + --<𝐋𝐀𝐍𝐆>-𝗸𝗶𝗻𝗱𝘀, and --𝗿𝗲𝗴𝗲𝘅-<𝐋𝐀𝐍𝐆> options. + + + --𝗼𝗽𝘁𝗶𝗼𝗻𝘀=f̲i̲l̲e̲ + Read additional options from f̲i̲l̲e̲. The file should contain + one option per line. As a special case, if --𝗼𝗽𝘁𝗶𝗼𝗻𝘀=N̲O̲N̲E̲ is + specified as the first option on the command line, it will + disable the automatic reading of any configuration options + from either a file or the environment (see 𝐅𝐈𝐋𝐄𝐒). + + + --𝗿𝗲𝗰𝘂𝗿𝘀𝗲[=y̲e̲s̲|n̲o̲] + Recurse into directories encountered in the list of supplied + files. If the list of supplied files is empty and no file + list is specified with the -𝐋 option, then the current di‐ + rectory (i.e. ".") is assumed. Symbolic links are followed. + If you don't like these behaviors, either explicitly specify + the files or pipe the output of 𝗳𝗶𝗻𝗱(1) into 𝗰𝘁𝗮𝗴𝘀 -𝐋- in‐ + stead. 𝐍𝗼𝘁𝗲: This option is not supported on all platforms + at present. It is available if the output of the --𝗵𝗲𝗹𝗽 op‐ + tion includes this option. See, also, the --𝗲𝘅𝗰𝗹𝘂𝗱𝗲 to + limit recursion. + + + --𝗿𝗲𝗴𝗲𝘅-<𝐋𝐀𝐍𝐆>=/̲r̲e̲g̲e̲x̲p̲/̲r̲e̲p̲l̲a̲c̲e̲m̲e̲n̲t̲/̲[̲k̲i̲n̲d̲-̲s̲p̲e̲c̲/̲]̲[̲f̲l̲a̲g̲s̲]̲ + The /̲r̲e̲g̲e̲x̲p̲/̲r̲e̲p̲l̲a̲c̲e̲m̲e̲n̲t̲/̲ pair define a regular expression + replacement pattern, similar in style to 𝘀𝗲𝗱 substitution + commands, with which to generate tags from source files + mapped to the named language, <𝐋𝐀𝐍𝐆>, (case-insensitive; ei‐ + ther a built-in or user-defined language). The regular ex‐ + pression, r̲e̲g̲e̲x̲p̲, defines an extended regular expression + (roughly that used by 𝗲𝗴𝗿𝗲𝗽(1)), which is used to locate a + single source line containing a tag and may specify tab + characters using \t. When a matching line is found, a tag + will be generated for the name defined by r̲e̲p̲l̲a̲c̲e̲m̲e̲n̲t̲, which + generally will contain the special back-references \1 + through \9 to refer to matching sub-expression groups within + r̲e̲g̲e̲x̲p̲. The '/' separator characters shown in the parameter + to the option can actually be replaced by any character. + Note that whichever separator character is used will have to + be escaped with a backslash ('\') character wherever it is + used in the parameter as something other than a separator. + The regular expression defined by this option is added to + the current list of regular expressions for the specified + language unless the parameter is omitted, in which case the + current list is cleared. + + Unless modified by f̲l̲a̲g̲s̲, r̲e̲g̲e̲x̲p̲ is interpreted as a Posix + extended regular expression. The r̲e̲p̲l̲a̲c̲e̲m̲e̲n̲t̲ should expand + for all matching lines to a non-empty string of characters, + or a warning message will be reported. An optional kind + specifier for tags matching r̲e̲g̲e̲x̲p̲ may follow r̲e̲p̲l̲a̲c̲e̲m̲e̲n̲t̲, + which will determine what kind of tag is reported in the + "kind" extension field (see 𝐓𝐀𝐆 𝐅𝐈𝐋𝐄 𝐅𝐎𝐑𝐌𝐀𝐓, below). The + full form of k̲i̲n̲d̲-̲s̲p̲e̲c̲ is in the form of a single letter, a + comma, a name (without spaces), a comma, a description, fol‐ + lowed by a separator, which specify the short and long forms + of the kind value and its textual description (displayed us‐ + ing --𝗹𝗶𝘀𝘁-𝗸𝗶𝗻𝗱𝘀). Either the kind name and/or the descrip‐ + tion may be omitted. If k̲i̲n̲d̲-̲s̲p̲e̲c̲ is omitted, it defaults to + "r̲,̲r̲e̲g̲e̲x̲". Finally, f̲l̲a̲g̲s̲ are one or more single-letter + characters having the following effect upon the interpreta‐ + tion of r̲e̲g̲e̲x̲p̲: + + + b̲ The pattern is interpreted as a Posix basic regular + expression. + + e̲ The pattern is interpreted as a Posix extended regu‐ + lar expression (default). + + i̲ The regular expression is to be applied in a case-in‐ + sensitive manner. + + Note that this option is available only if 𝗰𝘁𝗮𝗴𝘀 was com‐ + piled with support for regular expressions, which depends + upon your platform. You can determine if support for regular + expressions is compiled in by examining the output of the + --𝘃𝗲𝗿𝘀𝗶𝗼𝗻 option, which will include "+regex" in the com‐ + piled feature list. + + For more information on the regular expressions used by + 𝗰𝘁𝗮𝗴𝘀, see either the 𝗿𝗲𝗴𝗲𝘅(𝟱,𝟳) man page, or the GNU info + documentation for regex (e.g. "info regex"). + + + --𝘀𝗼𝗿𝘁[=y̲e̲s̲|n̲o̲|f̲o̲l̲d̲c̲a̲s̲e̲] + Indicates whether the tag file should be sorted on the tag + name (default is y̲e̲s̲). Note that the original 𝘃𝗶(1) required + sorted tags. The f̲o̲l̲d̲c̲a̲s̲e̲ value specifies case insensitive + (or case-folded) sorting. Fast binary searches of tag files + sorted with case-folding will require special support from + tools using tag files, such as that found in the ctags read‐ + tags library, or Vim version 6.2 or higher (using "set ig‐ + norecase"). This option must appear before the first file + name. [Ignored in etags mode] + + + --𝘁𝗮𝗴-𝗿𝗲𝗹𝗮𝘁𝗶𝘃𝗲[=y̲e̲s̲|n̲o̲] + Indicates that the file paths recorded in the tag file + should be relative to the directory containing the tag file, + rather than relative to the current directory, unless the + files supplied on the command line are specified with abso‐ + lute paths. This option must appear before the first file + name. The default is y̲e̲s̲ when running in etags mode (see the + -𝗲 option), n̲o̲ otherwise. + + + --𝘁𝗼𝘁𝗮𝗹𝘀[=y̲e̲s̲|n̲o̲] + Prints statistics about the source files read and the tag + file written during the current invocation of 𝗰𝘁𝗮𝗴𝘀. This + option is off by default. This option must appear before + the first file name. + + + --𝘃𝗲𝗿𝗯𝗼𝘀𝗲[=y̲e̲s̲|n̲o̲] + Enable verbose mode. This prints out information on option + processing and a brief message describing what action is be‐ + ing taken for each file considered by 𝗰𝘁𝗮𝗴𝘀. Normally, 𝗰𝘁𝗮𝗴𝘀 + does not read command line arguments until after options are + read from the configuration files (see 𝐅𝐈𝐋𝐄𝐒, below) and the + 𝐂𝐓𝐀𝐆𝐒 environment variable. However, if this option is the + first argument on the command line, it will take effect be‐ + fore any options are read from these sources. The default is + n̲o̲. + + + --𝘃𝗲𝗿𝘀𝗶𝗼𝗻 + Prints a version identifier for 𝗰𝘁𝗮𝗴𝘀 to standard output, + and then exits. This is guaranteed to always contain the + string "Exuberant Ctags". + + + +𝐎𝐏𝐄𝐑𝐀𝐓𝐈𝐎𝐍𝐀𝐋 𝐃𝐄𝐓𝐀𝐈𝐋𝐒 + As 𝗰𝘁𝗮𝗴𝘀 considers each file name in turn, it tries to determine + the language of the file by applying the following three tests in + order: if the file extension has been mapped to a language, if + the file name matches a shell pattern mapped to a language, and + finally if the file is executable and its first line specifies an + interpreter using the Unix-style "#!" specification (if supported + on the platform). If a language was identified, the file is + opened and then the appropriate language parser is called to op‐ + erate on the currently open file. The parser parses through the + file and adds an entry to the tag file for each language object + it is written to handle. See 𝐓𝐀𝐆 𝐅𝐈𝐋𝐄 𝐅𝐎𝐑𝐌𝐀𝐓, below, for details + on these entries. + + This implementation of 𝗰𝘁𝗮𝗴𝘀 imposes no formatting requirements + on C code as do legacy implementations. Older implementations of + ctags tended to rely upon certain formatting assumptions in order + to help it resolve coding dilemmas caused by preprocessor condi‐ + tionals. + + In general, 𝗰𝘁𝗮𝗴𝘀 tries to be smart about conditional preproces‐ + sor directives. If a preprocessor conditional is encountered + within a statement which defines a tag, 𝗰𝘁𝗮𝗴𝘀 follows only the + first branch of that conditional (except in the special case of + "#if 0", in which case it follows only the last branch). The rea‐ + son for this is that failing to pursue only one branch can result + in ambiguous syntax, as in the following example: + + #ifdef TWO_ALTERNATIVES + struct { + #else + union { + #endif + short a; + long b; + } + + Both branches cannot be followed, or braces become unbalanced and + 𝗰𝘁𝗮𝗴𝘀 would be unable to make sense of the syntax. + + If the application of this heuristic fails to properly parse a + file, generally due to complicated and inconsistent pairing + within the conditionals, 𝗰𝘁𝗮𝗴𝘀 will retry the file using a dif‐ + ferent heuristic which does not selectively follow conditional + preprocessor branches, but instead falls back to relying upon a + closing brace ("}") in column 1 as indicating the end of a block + once any brace imbalance results from following a #if conditional + branch. + + 𝐂𝘁𝗮𝗴𝘀 will also try to specially handle arguments lists enclosed + in double sets of parentheses in order to accept the following + conditional construct: + + extern void foo __ARGS((int one, char two)); + + Any name immediately preceding the "((" will be automatically ig‐ + nored and the previous name will be used. + + C++ operator definitions are specially handled. In order for con‐ + sistency with all types of operators (overloaded and conversion), + the operator name in the tag file will always be preceded by the + string "operator " (i.e. even if the actual operator definition + was written as "operator<<"). + + After creating or appending to the tag file, it is sorted by the + tag name, removing identical tag lines. + + + +𝐓𝐀𝐆 𝐅𝐈𝐋𝐄 𝐅𝐎𝐑𝐌𝐀𝐓 + When not running in etags mode, each entry in the tag file con‐ + sists of a separate line, each looking like this in the most gen‐ + eral case: + + tag_namefile_nameex_cmd;"extension_fields + + The fields and separators of these lines are specified as fol‐ + lows: + + 1. tag name + 2. single tab character + 3. name of the file in which the object associated with the + tag is located + 4. single tab character + 5. EX command used to locate the tag within the file; gener‐ + ally a search pattern (either /pattern/ or ?pattern?) or + line number (see --𝗲𝘅𝗰𝗺𝗱). Tag file format 2 (see --𝗳𝗼𝗿‐ + 𝗺𝗮𝘁) extends this EX command under certain circumstances + to include a set of extension fields (described below) + embedded in an EX comment immediately appended to the EX + command, which leaves it backward-compatible with origi‐ + nal 𝘃𝗶(1) implementations. + + A few special tags are written into the tag file for internal + purposes. These tags are composed in such a way that they always + sort to the top of the file. Therefore, the first two characters + of these tags are used a magic number to detect a tag file for + purposes of determining whether a valid tag file is being over‐ + written rather than a source file. + + Note that the name of each source file will be recorded in the + tag file exactly as it appears on the command line. Therefore, if + the path you specified on the command line was relative to the + current directory, then it will be recorded in that same manner + in the tag file. See, however, the --𝘁𝗮𝗴-𝗿𝗲𝗹𝗮𝘁𝗶𝘃𝗲 option for how + this behavior can be modified. + + Extension fields are tab-separated key-value pairs appended to + the end of the EX command as a comment, as described above. These + key value pairs appear in the general form "k̲e̲y̲:v̲a̲l̲u̲e̲". Their + presence in the lines of the tag file are controlled by the + --𝗳𝗶𝗲𝗹𝗱𝘀 option. The possible keys and the meaning of their val‐ + ues are as follows: + + + a̲c̲c̲e̲s̲s̲ Indicates the visibility of this class member, where + v̲a̲l̲u̲e̲ is specific to the language. + + + f̲i̲l̲e̲ Indicates that the tag has file-limited visibility. + This key has no corresponding value. + + + k̲i̲n̲d̲ Indicates the type, or kind, of tag. Its value is ei‐ + ther one of the corresponding one-letter flags de‐ + scribed under the various --<𝐋𝐀𝐍𝐆>-𝗸𝗶𝗻𝗱𝘀 options + above, or a full name. It is permitted (and is, in + fact, the default) for the key portion of this field + to be omitted. The optional behaviors are controlled + with the --𝗳𝗶𝗲𝗹𝗱𝘀 option. + + + i̲m̲p̲l̲e̲m̲e̲n̲t̲a̲t̲i̲o̲n̲ + When present, this indicates a limited implementation + (abstract vs. concrete) of a routine or class, where + v̲a̲l̲u̲e̲ is specific to the language ("virtual" or "pure + virtual" for C++; "abstract" for Java). + + + i̲n̲h̲e̲r̲i̲t̲s̲ When present, v̲a̲l̲u̲e̲. is a comma-separated list of + classes from which this class is derived (i.e. inher‐ + its from). + + + s̲i̲g̲n̲a̲t̲u̲r̲e̲ When present, v̲a̲l̲u̲e̲ is a language-dependent represen‐ + tation of the signature of a routine. A routine sig‐ + nature in its complete form specifies the return type + of a routine and its formal argument list. This ex‐ + tension field is presently supported only for C-based + languages and does not include the return type. + + + In addition, information on the scope of the tag definition may + be available, with the key portion equal to some language-depen‐ + dent construct name and its value the name declared for that con‐ + struct in the program. This scope entry indicates the scope in + which the tag was found. For example, a tag generated for a C + structure member would have a scope looking like "struct:myS‐ + truct". + + + +𝐇𝐎𝐖 𝐓𝐎 𝐔𝐒𝐄 𝐖𝐈𝐓𝐇 𝐕𝐈 + Vi will, by default, expect a tag file by the name "tags" in the + current directory. Once the tag file is built, the following com‐ + mands exercise the tag indexing feature: + + 𝘃𝗶 -𝘁 𝘁𝗮𝗴 Start vi and position the cursor at the file and line + where "tag" is defined. + + :𝘁𝗮 𝘁𝗮𝗴 Find a tag. + + 𝐂𝘁𝗿𝗹-] Find the tag under the cursor. + + 𝐂𝘁𝗿𝗹-𝐓 Return to previous location before jump to tag (not + widely implemented). + + + +𝐇𝐎𝐖 𝐓𝐎 𝐔𝐒𝐄 𝐖𝐈𝐓𝐇 𝐆𝐍𝐔 𝐄𝐌𝐀𝐂𝐒 + Emacs will, by default, expect a tag file by the name "TAGS" in + the current directory. Once the tag file is built, the following + commands exercise the tag indexing feature: + + 𝐌-𝘅 𝘃𝗶𝘀𝗶𝘁-𝘁𝗮𝗴𝘀-𝘁𝗮𝗯𝗹𝗲 <𝐑𝐄𝐓> 𝐅𝐈𝐋𝐄 <𝐑𝐄𝐓> + Select the tag file, "FILE", to use. + + 𝐌-. [𝐓𝐀𝐆] <𝐑𝐄𝐓> + Find the first definition of TAG. The default tag is + the identifier under the cursor. + + 𝐌-* Pop back to where you previously invoked "M-.". + + 𝐂-𝘂 𝐌-. Find the next definition for the last tag. + + + For more commands, see the T̲a̲g̲s̲ topic in the Emacs info document. + + + +𝐇𝐎𝐖 𝐓𝐎 𝐔𝐒𝐄 𝐖𝐈𝐓𝐇 𝐍𝐄𝐃𝐈𝐓 + NEdit version 5.1 and later can handle the new extended tag file + format (see --𝗳𝗼𝗿𝗺𝗮𝘁). To make NEdit use the tag file, select + "File->Load Tags File". To jump to the definition for a tag, + highlight the word, then press Ctrl-D. NEdit 5.1 can can read + multiple tag files from different directories. Setting the X re‐ + source nedit.tagFile to the name of a tag file instructs NEdit to + automatically load that tag file at startup time. + + + +𝐂𝐀𝐕𝐄𝐀𝐓𝐒 + Because 𝗰𝘁𝗮𝗴𝘀 is neither a preprocessor nor a compiler, use of + preprocessor macros can fool 𝗰𝘁𝗮𝗴𝘀 into either missing tags or + improperly generating inappropriate tags. Although 𝗰𝘁𝗮𝗴𝘀 has been + designed to handle certain common cases, this is the single big‐ + gest cause of reported problems. In particular, the use of pre‐ + processor constructs which alter the textual syntax of C can fool + 𝗰𝘁𝗮𝗴𝘀. You can work around many such problems by using the -𝐈 op‐ + tion. + + Note that since 𝗰𝘁𝗮𝗴𝘀 generates patterns for locating tags (see + the --𝗲𝘅𝗰𝗺𝗱 option), it is entirely possible that the wrong line + may be found by your editor if there exists another source line + which is identical to the line containing the tag. The following + example demonstrates this condition: + + int variable; + + /* ... */ + void foo(variable) + int variable; + { + /* ... */ + } + + Depending upon which editor you use and where in the code you + happen to be, it is possible that the search pattern may locate + the local parameter declaration in foo() before it finds the ac‐ + tual global variable definition, since the lines (and therefore + their search patterns are identical). This can be avoided by use + of the --𝗲𝘅𝗰𝗺𝗱=n̲ option. + + + +𝐁𝐔𝐆𝐒 + 𝐂𝘁𝗮𝗴𝘀 has more options than 𝗹𝘀(1). + + When parsing a C++ member function definition (e.g. "class‐ + Name::function"), 𝗰𝘁𝗮𝗴𝘀 cannot determine whether the scope speci‐ + fier is a class name or a namespace specifier and always lists it + as a class name in the scope portion of the extension fields. + Also, if a C++ function is defined outside of the class declara‐ + tion (the usual case), the access specification (i.e. public, + protected, or private) and implementation information (e.g. vir‐ + tual, pure virtual) contained in the function declaration are not + known when the tag is generated for the function definition. It + will, however be available for prototypes (e.g --𝗰++-𝗸𝗶𝗻𝗱𝘀=+̲p̲). + + No qualified tags are generated for language objects inherited + into a class. + + + +𝐄𝐍𝐕𝐈𝐑𝐎𝐍𝐌𝐄𝐍𝐓 𝐕𝐀𝐑𝐈𝐀𝐁𝐋𝐄𝐒 + 𝐂𝐓𝐀𝐆𝐒 If this environment variable exists, it will be expected + to contain a set of default options which are read when + 𝗰𝘁𝗮𝗴𝘀 starts, after the configuration files listed in + 𝐅𝐈𝐋𝐄𝐒, below, are read, but before any command line op‐ + tions are read. Options appearing on the command line + will override options specified in this variable. Only + options will be read from this variable. Note that all + white space in this variable is considered a separator, + making it impossible to pass an option parameter contain‐ + ing an embedded space. If this is a problem, use a con‐ + figuration file instead. + + + 𝐄𝐓𝐀𝐆𝐒 Similar to the 𝐂𝐓𝐀𝐆𝐒 variable above, this variable, if + found, will be read when 𝗲𝘁𝗮𝗴𝘀 starts. If this variable + is not found, 𝗲𝘁𝗮𝗴𝘀 will try to use 𝐂𝐓𝐀𝐆𝐒 instead. + + + 𝐓𝐌𝐏𝐃𝐈𝐑 On Unix-like hosts where mkstemp() is available, the + value of this variable specifies the directory in which + to place temporary files. This can be useful if the size + of a temporary file becomes too large to fit on the par‐ + tition holding the default temporary directory defined at + compilation time. 𝗰𝘁𝗮𝗴𝘀 creates temporary files only if + either (1) an emacs-style tag file is being generated, + (2) the tag file is being sent to standard output, or (3) + the program was compiled to use an internal sort algo‐ + rithm to sort the tag files instead of the the sort util‐ + ity of the operating system. If the sort utility of the + operating system is being used, it will generally observe + this variable also. Note that if 𝗰𝘁𝗮𝗴𝘀 is setuid, the + value of TMPDIR will be ignored. + + + +𝐅𝐈𝐋𝐄𝐒 + /̲c̲t̲a̲g̲s̲.̲c̲n̲f̲ (̲o̲n̲ M̲S̲D̲O̲S̲,̲ M̲S̲W̲i̲n̲d̲o̲w̲s̲ o̲n̲l̲y̲)̲ + /̲e̲t̲c̲/̲c̲t̲a̲g̲s̲.̲c̲o̲n̲f̲ + /̲u̲s̲r̲/̲l̲o̲c̲a̲l̲/̲e̲t̲c̲/̲c̲t̲a̲g̲s̲.̲c̲o̲n̲f̲ + $̲H̲O̲M̲E̲/̲.̲c̲t̲a̲g̲s̲ + $̲H̲O̲M̲E̲/̲c̲t̲a̲g̲s̲.̲c̲n̲f̲ (̲o̲n̲ M̲S̲D̲O̲S̲,̲ M̲S̲W̲i̲n̲d̲o̲w̲s̲ o̲n̲l̲y̲)̲ + .̲c̲t̲a̲g̲s̲ + c̲t̲a̲g̲s̲.̲c̲n̲f̲ (̲o̲n̲ M̲S̲D̲O̲S̲,̲ M̲S̲W̲i̲n̲d̲o̲w̲s̲ o̲n̲l̲y̲)̲ + If any of these configuration files exist, each will be + expected to contain a set of default options which are + read in the order listed when 𝗰𝘁𝗮𝗴𝘀 starts, but before the + 𝐂𝐓𝐀𝐆𝐒 environment variable is read or any command line op‐ + tions are read. This makes it possible to set up site- + wide, personal or project-level defaults. It is possible + to compile 𝗰𝘁𝗮𝗴𝘀 to read an additional configuration file + before any of those shown above, which will be indicated + if the output produced by the --𝘃𝗲𝗿𝘀𝗶𝗼𝗻 option lists the + "custom-conf" feature. Options appearing in the 𝐂𝐓𝐀𝐆𝐒 en‐ + vironment variable or on the command line will override + options specified in these files. Only options will be + read from these files. Note that the option files are read + in line-oriented mode in which spaces are significant + (since shell quoting is not possible). Each line of the + file is read as one command line parameter (as if it were + quoted with single quotes). Therefore, use new lines to + indicate separate command-line arguments. + + + t̲a̲g̲s̲ The default tag file created by 𝗰𝘁𝗮𝗴𝘀. + + T̲A̲G̲S̲ The default tag file created by 𝗲𝘁𝗮𝗴𝘀. + + +𝐒𝐄𝐄 𝐀𝐋𝐒𝐎 + The official Exuberant Ctags web site at: + + http://ctags.sourceforge.net + + Also 𝗲𝘅(1), 𝘃𝗶(1), 𝗲𝗹𝘃𝗶𝘀, or, better yet, 𝘃𝗶𝗺, the official edi‐ + tor of 𝗰𝘁𝗮𝗴𝘀. For more information on 𝘃𝗶𝗺, see the VIM Pages web + site at: + + http://www.vim.org/ + + + +𝐀𝐔𝐓𝐇𝐎𝐑 + Darren Hiebert + http://DarrenHiebert.com/ + + + +𝐌𝐎𝐓𝐈𝐕𝐀𝐓𝐈𝐎𝐍 + "Think ye at all times of rendering some service to every member + of the human race." + + "All effort and exertion put forth by man from the fullness of + his heart is worship, if it is prompted by the highest motives + and the will to do service to humanity." + + -- From the Baha'i Writings + + + +𝐂𝐑𝐄𝐃𝐈𝐓𝐒 + This version of 𝗰𝘁𝗮𝗴𝘀 was originally derived from and inspired by + the ctags program by Steve Kirkendall that + comes with the Elvis vi clone (though virtually none of the orig‐ + inal code remains). + + Credit is also due Bram Moolenaar , the author of + 𝘃𝗶𝗺, who has devoted so much of his time and energy both to de‐ + veloping the editor as a service to others, and to helping the + orphans of Uganda. + + The section entitled "HOW TO USE WITH GNU EMACS" was shamelessly + stolen from the info page for GNU 𝗲𝘁𝗮𝗴𝘀. + + + +Darren Hiebert Version 5.9~svn20110310 CTAGS(1) diff --git a/third_party/ctags/ant.c b/third_party/ctags/ant.c new file mode 100644 index 00000000..da8d3c29 --- /dev/null +++ b/third_party/ctags/ant.c @@ -0,0 +1,35 @@ +/* + * $Id$ + * + * Copyright (c) 2008, David Fishburn + * + * This source code is released for free distribution under the terms of the + * GNU General Public License. + * + * This module contains functions for generating tags for Ant language files. + */ +#include "third_party/ctags/general.h" +/* must always come first */ +#include "third_party/ctags/parse.h" + +/* + * FUNCTION DEFINITIONS + */ + +static void installAntRegex(const langType language) { + addTagRegex(language, "^[ \t]*<[ \t]*project[^>]+name=\"([^\"]+)\".*", "\\1", + "p,project,projects", NULL); + addTagRegex(language, "^[ \t]*<[ \t]*target[^>]+name=\"([^\"]+)\".*", "\\1", + "t,target,targets", NULL); +} + +extern parserDefinition* AntParser() { + static const char* const extensions[] = {"build.xml", NULL}; + parserDefinition* const def = parserNew("Ant"); + def->extensions = extensions; + def->initialize = installAntRegex; + def->regex = TRUE; + return def; +} + +/* vi:set tabstop=4 shiftwidth=4: */ diff --git a/third_party/ctags/args.c b/third_party/ctags/args.c new file mode 100644 index 00000000..37cec32a --- /dev/null +++ b/third_party/ctags/args.c @@ -0,0 +1,238 @@ +/* + * $Id: args.c 536 2007-06-02 06:09:00Z elliotth $ + * + * Copyright (c) 1999-2002, Darren Hiebert + * + * This source code is released for free distribution under the terms of the + * GNU General Public License. + * + * This module contains functions for reading command line arguments. + */ +#include "third_party/ctags/general.h" +/* must always come first */ +#include "libc/calls/calls.h" +#include "libc/str/str.h" +#include "third_party/ctags/args.h" +#include "third_party/ctags/debug.h" +#include "third_party/ctags/routines.h" + +/* + * FUNCTION DEFINITIONS + */ + +static char* nextStringArg(const char** const next) { + char* result = NULL; + const char* start; + + Assert(*next != NULL); + for (start = *next; isspace((int)*start); ++start) + ; + if (*start == '\0') + *next = start; + else { + size_t length; + const char* end; + + for (end = start; *end != '\0' && !isspace((int)*end); ++end) + ; + length = end - start; + Assert(length > 0); + result = xMalloc(length + 1, char); + strncpy(result, start, length); + result[length] = '\0'; + *next = end; + } + return result; +} + +static char* nextStringLine(const char** const next) { + char* result = NULL; + size_t length; + const char* end; + + Assert(*next != NULL); + for (end = *next; *end != '\n' && *end != '\0'; ++end) + ; + length = end - *next; + if (length > 0) { + result = xMalloc(length + 1, char); + strncpy(result, *next, length); + result[length] = '\0'; + } + if (*end == '\n') + ++end; + else if (*end == '\r') { + ++end; + if (*end == '\n') ++end; + } + *next = end; + return result; +} + +static char* nextString(const Arguments* const current, + const char** const next) { + char* result; + if (current->lineMode) + result = nextStringLine(next); + else + result = nextStringArg(next); + return result; +} + +static char* nextFileArg(FILE* const fp) { + char* result = NULL; + Assert(fp != NULL); + if (!feof(fp)) { + vString* vs = vStringNew(); + int c; + do + c = fgetc(fp); + while (isspace(c)); + + if (c != EOF) { + do { + vStringPut(vs, c); + c = fgetc(fp); + } while (c != EOF && !isspace(c)); + vStringTerminate(vs); + Assert(vStringLength(vs) > 0); + result = xMalloc(vStringLength(vs) + 1, char); + strcpy(result, vStringValue(vs)); + } + vStringDelete(vs); + } + return result; +} + +static char* nextFileLine(FILE* const fp) { + char* result = NULL; + if (!feof(fp)) { + vString* vs = vStringNew(); + int c; + + Assert(fp != NULL); + c = fgetc(fp); + while (c != EOF) { + if (c != '\n' && c != '\r') + vStringPut(vs, c); + else if (vStringLength(vs) > 0) + break; + c = fgetc(fp); + } + if (c != EOF || vStringLength(vs) > 0) { + if (c == '\r') { + c = fgetc(fp); + if (c != '\n') c = ungetc(c, fp); + } + vStringTerminate(vs); + vStringStripTrailing(vs); + result = xMalloc(vStringLength(vs) + 1, char); + strcpy(result, vStringValue(vs)); + } + vStringDelete(vs); + } + return result; +} + +static char* nextFileString(const Arguments* const current, FILE* const fp) { + char* result; + if (current->lineMode) + result = nextFileLine(fp); + else + result = nextFileArg(fp); + return result; +} + +extern Arguments* argNewFromString(const char* const string) { + Arguments* result = xMalloc(1, Arguments); + memset(result, 0, sizeof(Arguments)); + result->type = ARG_STRING; + result->u.stringArgs.string = string; + result->u.stringArgs.item = string; + result->u.stringArgs.next = string; + result->item = nextString(result, &result->u.stringArgs.next); + return result; +} + +extern Arguments* argNewFromArgv(char* const* const argv) { + Arguments* result = xMalloc(1, Arguments); + memset(result, 0, sizeof(Arguments)); + result->type = ARG_ARGV; + result->u.argvArgs.argv = argv; + result->u.argvArgs.item = result->u.argvArgs.argv; + result->item = *result->u.argvArgs.item; + return result; +} + +extern Arguments* argNewFromFile(FILE* const fp) { + Arguments* result = xMalloc(1, Arguments); + memset(result, 0, sizeof(Arguments)); + result->type = ARG_FILE; + result->u.fileArgs.fp = fp; + result->item = nextFileString(result, result->u.fileArgs.fp); + return result; +} + +extern Arguments* argNewFromLineFile(FILE* const fp) { + Arguments* result = xMalloc(1, Arguments); + memset(result, 0, sizeof(Arguments)); + result->type = ARG_FILE; + result->lineMode = TRUE; + result->u.fileArgs.fp = fp; + result->item = nextFileString(result, result->u.fileArgs.fp); + return result; +} + +extern char* argItem(const Arguments* const current) { + Assert(current != NULL); + Assert(!argOff(current)); + return current->item; +} + +extern boolean argOff(const Arguments* const current) { + Assert(current != NULL); + return (boolean)(current->item == NULL); +} + +extern void argSetWordMode(Arguments* const current) { + Assert(current != NULL); + current->lineMode = FALSE; +} + +extern void argSetLineMode(Arguments* const current) { + Assert(current != NULL); + current->lineMode = TRUE; +} + +extern void argForth(Arguments* const current) { + Assert(current != NULL); + Assert(!argOff(current)); + switch (current->type) { + case ARG_STRING: + if (current->item != NULL) eFree(current->item); + current->u.stringArgs.item = current->u.stringArgs.next; + current->item = nextString(current, ¤t->u.stringArgs.next); + break; + case ARG_ARGV: + ++current->u.argvArgs.item; + current->item = *current->u.argvArgs.item; + break; + case ARG_FILE: + if (current->item != NULL) eFree(current->item); + current->item = nextFileString(current, current->u.fileArgs.fp); + break; + default: + Assert("Invalid argument type" == NULL); + break; + } +} + +extern void argDelete(Arguments* const current) { + Assert(current != NULL); + if (current->type == ARG_STRING && current->item != NULL) + eFree(current->item); + memset(current, 0, sizeof(Arguments)); + eFree(current); +} + +/* vi:set tabstop=4 shiftwidth=4: */ diff --git a/third_party/ctags/args.h b/third_party/ctags/args.h new file mode 100644 index 00000000..5388faad --- /dev/null +++ b/third_party/ctags/args.h @@ -0,0 +1,47 @@ +#ifndef _ARGS_H +#define _ARGS_H +#include "third_party/ctags/general.h" +/* must always come first */ +#include "libc/stdio/stdio.h" + +/* + * DATA DECLARATIONS + */ + +typedef enum { ARG_NONE, ARG_STRING, ARG_ARGV, ARG_FILE } argType; + +typedef struct sArgs { + argType type; + union { + struct sStringArgs { + const char* string; + const char* next; + const char* item; + } stringArgs; + struct sArgvArgs { + char* const* argv; + char* const* item; + } argvArgs; + struct sFileArgs { + FILE* fp; + } fileArgs; + } u; + char* item; + boolean lineMode; +} Arguments; + +/* + * FUNCTION PROTOTYPES + */ +extern Arguments* argNewFromString(const char* const string); +extern Arguments* argNewFromArgv(char* const* const argv); +extern Arguments* argNewFromFile(FILE* const fp); +extern Arguments* argNewFromLineFile(FILE* const fp); +extern char* argItem(const Arguments* const current); +extern boolean argOff(const Arguments* const current); +extern void argSetWordMode(Arguments* const current); +extern void argSetLineMode(Arguments* const current); +extern void argForth(Arguments* const current); +extern void argDelete(Arguments* const current); + +#endif /* _ARGS_H */ diff --git a/third_party/ctags/asm.c b/third_party/ctags/asm.c new file mode 100644 index 00000000..e60328d0 --- /dev/null +++ b/third_party/ctags/asm.c @@ -0,0 +1,298 @@ +/* + * $Id: asm.c 536 2007-06-02 06:09:00Z elliotth $ + * + * Copyright (c) 2000-2003, Darren Hiebert + * + * This source code is released for free distribution under the terms of the + * GNU General Public License. + * + * This module contains functions for generating tags for assembly language + * files. + */ +#include "third_party/ctags/general.h" +/* must always come first */ +#include "third_party/ctags/debug.h" +#include "third_party/ctags/keyword.h" +#include "third_party/ctags/parse.h" +#include "third_party/ctags/read.h" +#include "third_party/ctags/routines.h" +#include "third_party/ctags/vstring.h" + +/* + * DATA DECLARATIONS + */ +typedef enum { K_NONE = -1, K_DEFINE, K_LABEL, K_MACRO, K_TYPE } AsmKind; + +typedef enum { + OP_UNDEFINED = -1, + OP_ALIGN, + OP_COLON_EQUAL, + OP_END, + OP_ENDM, + OP_ENDMACRO, + OP_ENDP, + OP_ENDS, + OP_EQU, + OP_EQUAL, + OP_LABEL, + OP_MACRO, + OP_PROC, + OP_RECORD, + OP_SECTIONS, + OP_SET, + OP_STRUCT, + OP_LAST +} opKeyword; + +typedef struct { + const char *operator; + opKeyword keyword; +} asmKeyword; + +typedef struct { + opKeyword keyword; + AsmKind kind; +} opKind; + +/* + * DATA DEFINITIONS + */ +static langType Lang_asm; + +static kindOption AsmKinds[] = { + {TRUE, 'd', "define", "defines"}, + {TRUE, 'l', "label", "labels"}, + {TRUE, 'm', "macro", "macros"}, + {TRUE, 't', "type", "types (structs and records)"}}; + +static const asmKeyword AsmKeywords[] = { + {"align", OP_ALIGN}, {"endmacro", OP_ENDMACRO}, {"endm", OP_ENDM}, + {"end", OP_END}, {"endp", OP_ENDP}, {"ends", OP_ENDS}, + {"equ", OP_EQU}, {"label", OP_LABEL}, {"macro", OP_MACRO}, + {":=", OP_COLON_EQUAL}, {"=", OP_EQUAL}, {"proc", OP_PROC}, + {"record", OP_RECORD}, {"sections", OP_SECTIONS}, {"set", OP_SET}, + {"struct", OP_STRUCT}}; + +static const opKind OpKinds[] = { + /* must be ordered same as opKeyword enumeration */ + {OP_ALIGN, K_NONE}, {OP_COLON_EQUAL, K_DEFINE}, {OP_END, K_NONE}, + {OP_ENDM, K_NONE}, {OP_ENDMACRO, K_NONE}, {OP_ENDP, K_NONE}, + {OP_ENDS, K_NONE}, {OP_EQU, K_DEFINE}, {OP_EQUAL, K_DEFINE}, + {OP_LABEL, K_LABEL}, {OP_MACRO, K_MACRO}, {OP_PROC, K_LABEL}, + {OP_RECORD, K_TYPE}, {OP_SECTIONS, K_NONE}, {OP_SET, K_DEFINE}, + {OP_STRUCT, K_TYPE}}; + +/* + * FUNCTION DEFINITIONS + */ +static void buildAsmKeywordHash(void) { + const size_t count = sizeof(AsmKeywords) / sizeof(AsmKeywords[0]); + size_t i; + for (i = 0; i < count; ++i) { + const asmKeyword *const p = AsmKeywords + i; + addKeyword(p->operator, Lang_asm,(int) p->keyword); + } +} + +static opKeyword analyzeOperator(const vString *const op) { + vString *keyword = vStringNew(); + opKeyword result; + + vStringCopyToLower(keyword, op); + result = (opKeyword)lookupKeyword(vStringValue(keyword), Lang_asm); + vStringDelete(keyword); + return result; +} + +static boolean isInitialSymbolCharacter(int c) { + return (boolean)(c != '\0' && (isalpha(c) || strchr("_$", c) != NULL)); +} + +static boolean isSymbolCharacter(int c) { + /* '?' character is allowed in AMD 29K family */ + return (boolean)(c != '\0' && (isalnum(c) || strchr("_$?", c) != NULL)); +} + +static boolean readPreProc(const unsigned char *const line) { + boolean result; + const unsigned char *cp = line; + vString *name = vStringNew(); + while (isSymbolCharacter((int)*cp)) { + vStringPut(name, *cp); + ++cp; + } + vStringTerminate(name); + result = (boolean)(strcmp(vStringValue(name), "define") == 0); + if (result) { + while (isspace((int)*cp)) ++cp; + vStringClear(name); + while (isSymbolCharacter((int)*cp)) { + vStringPut(name, *cp); + ++cp; + } + vStringTerminate(name); + makeSimpleTag(name, AsmKinds, K_DEFINE); + } + vStringDelete(name); + return result; +} + +static AsmKind operatorKind(const vString *const operator, + boolean * const found) { + AsmKind result = K_NONE; + const opKeyword kw = analyzeOperator(operator); + *found = (boolean)(kw != OP_UNDEFINED); + if (*found) { + result = OpKinds[kw].kind; + Assert(OpKinds[kw].keyword == kw); + } + return result; +} + +/* We must check for "DB", "DB.L", "DCB.W" (68000) + */ +static boolean isDefineOperator(const vString *const operator) { + const unsigned char *const op = (unsigned char *)vStringValue(operator); + const size_t length = vStringLength(operator); + const boolean result = + (boolean)(length > 0 && toupper((int)*op) == 'D' && + (length == 2 || (length == 4 && (int)op[2] == '.') || + (length == 5 && (int)op[3] == '.'))); + return result; +} + +static void makeAsmTag(const vString *const name, const vString *const operator, + const boolean labelCandidate, + const boolean nameFollows) { + if (vStringLength(name) > 0) { + boolean found; + const AsmKind kind = operatorKind(operator, & found); + if (found) { + if (kind != K_NONE) makeSimpleTag(name, AsmKinds, kind); + } else if (isDefineOperator(operator)) { + if (!nameFollows) makeSimpleTag(name, AsmKinds, K_DEFINE); + } else if (labelCandidate) { + operatorKind(name, &found); + if (!found) makeSimpleTag(name, AsmKinds, K_LABEL); + } + } +} + +static const unsigned char *readSymbol(const unsigned char *const start, + vString *const sym) { + const unsigned char *cp = start; + vStringClear(sym); + if (isInitialSymbolCharacter((int)*cp)) { + while (isSymbolCharacter((int)*cp)) { + vStringPut(sym, *cp); + ++cp; + } + vStringTerminate(sym); + } + return cp; +} + +static const unsigned char *readOperator(const unsigned char *const start, + vString *const operator) { + const unsigned char *cp = start; + vStringClear(operator); + while (*cp != '\0' && !isspace((int)*cp)) { + vStringPut(operator, * cp); + ++cp; + } + vStringTerminate(operator); + return cp; +} + +static void findAsmTags(void) { + vString *name = vStringNew(); + vString *operator= vStringNew(); + const unsigned char *line; + boolean inCComment = FALSE; + + while ((line = fileReadLine()) != NULL) { + const unsigned char *cp = line; + boolean labelCandidate = (boolean)(!isspace((int)*cp)); + boolean nameFollows = FALSE; + const boolean isComment = + (boolean)(*cp != '\0' && strchr(";*@", *cp) != NULL); + + /* skip comments */ + if (strncmp((const char *)cp, "/*", (size_t)2) == 0) { + inCComment = TRUE; + cp += 2; + } + if (inCComment) { + do { + if (strncmp((const char *)cp, "*/", (size_t)2) == 0) { + inCComment = FALSE; + cp += 2; + break; + } + ++cp; + } while (*cp != '\0'); + } + if (isComment || inCComment) continue; + + /* read preprocessor defines */ + if (*cp == '#') { + ++cp; + readPreProc(cp); + continue; + } + + /* skip white space */ + while (isspace((int)*cp)) ++cp; + + /* read symbol */ + cp = readSymbol(cp, name); + if (vStringLength(name) > 0 && *cp == ':') { + labelCandidate = TRUE; + ++cp; + } + + if (!isspace((int)*cp) && *cp != '\0') continue; + + /* skip white space */ + while (isspace((int)*cp)) ++cp; + + /* skip leading dot */ +#if 0 + if (*cp == '.') + ++cp; +#endif + + cp = readOperator(cp, operator); + + /* attempt second read of symbol */ + if (vStringLength(name) == 0) { + while (isspace((int)*cp)) ++cp; + cp = readSymbol(cp, name); + nameFollows = TRUE; + } + makeAsmTag(name, operator, labelCandidate, nameFollows); + } + vStringDelete(name); + vStringDelete(operator); +} + +static void initialize(const langType language) { + Lang_asm = language; + buildAsmKeywordHash(); +} + +extern parserDefinition *AsmParser(void) { + static const char *const extensions[] = {"asm", "ASM", "s", "S", NULL}; + static const char *const patterns[] = { + "*.A51", "*.29[kK]", "*.[68][68][kKsSxX]", "*.[xX][68][68]", NULL}; + parserDefinition *def = parserNew("Asm"); + def->kinds = AsmKinds; + def->kindCount = KIND_COUNT(AsmKinds); + def->extensions = extensions; + def->patterns = patterns; + def->parser = findAsmTags; + def->initialize = initialize; + return def; +} + +/* vi:set tabstop=4 shiftwidth=4: */ diff --git a/third_party/ctags/asp.c b/third_party/ctags/asp.c new file mode 100644 index 00000000..fd72494d --- /dev/null +++ b/third_party/ctags/asp.c @@ -0,0 +1,255 @@ +/* + * $Id: asp.c 711 2009-07-04 16:52:11Z dhiebert $ + * + * Copyright (c) 2000, Patrick Dehne + * + * This source code is released for free distribution under the terms of the + * GNU General Public License. + * + * This module contains functions for generating tags for the ASP (Active + * Server Pages) web page scripting language. + */ +#include "third_party/ctags/general.h" +/* must always come first */ +#include "third_party/ctags/parse.h" +#include "third_party/ctags/read.h" +#include "third_party/ctags/vstring.h" + +/* + * DATA DEFINITIONS + */ +typedef enum { K_CONST, K_CLASS, K_FUNCTION, K_SUB, K_DIM } aspKind; + +static kindOption AspKinds[] = {{TRUE, 'd', "constant", "constants"}, + {TRUE, 'c', "class", "classes"}, + {TRUE, 'f', "function", "functions"}, + {TRUE, 's', "subroutine", "subroutines"}, + {TRUE, 'v', "variable", "variables"}}; + +/* + * FUNCTION DEFINITIONS + */ + +static void findAspTags(void) { + vString *name = vStringNew(); + const unsigned char *line; + + while ((line = fileReadLine()) != NULL) { + const unsigned char *cp = line; + + while (*cp != '\0') { + /* jump over whitespace */ + while (isspace((int)*cp)) cp++; + + /* jump over strings */ + if (*cp == '"') { + cp++; + while (*cp != '"' && *cp != '\0') cp++; + } + + /* jump over comments */ + else if (*cp == '\'') + break; + + /* jump over end function/sub lines */ + else if (strncasecmp((const char *)cp, "end", (size_t)3) == 0) { + cp += 3; + if (isspace((int)*cp)) { + while (isspace((int)*cp)) ++cp; + + if (strncasecmp((const char *)cp, "function", (size_t)8) == 0) { + cp += 8; + break; + } + + else if (strncasecmp((const char *)cp, "sub", (size_t)3) == 0) { + cp += 3; + break; + } + } + } + + /* jump over exit function/sub lines */ + else if (strncasecmp((const char *)cp, "exit", (size_t)4) == 0) { + cp += 4; + if (isspace((int)*cp)) { + while (isspace((int)*cp)) ++cp; + + if (strncasecmp((const char *)cp, "function", (size_t)8) == 0) { + cp += 8; + break; + } + + else if (strncasecmp((const char *)cp, "sub", (size_t)3) == 0) { + cp += 3; + break; + } + } + } + + /* class member? */ + else if (strncasecmp((const char *)cp, "public", (size_t)6) == 0) { + cp += 6; + if (isspace((int)*cp)) { + while (isspace((int)*cp)) ++cp; + if (strncasecmp((const char *)cp, "function", (size_t)8) == 0) { + cp += 8; + while (isspace((int)*cp)) ++cp; + while (isalnum((int)*cp) || *cp == '_') { + vStringPut(name, (int)*cp); + ++cp; + } + vStringTerminate(name); + makeSimpleTag(name, AspKinds, K_FUNCTION); + vStringClear(name); + } else if (strncasecmp((const char *)cp, "sub", (size_t)3) == 0) { + cp += 3; + while (isspace((int)*cp)) ++cp; + while (isalnum((int)*cp) || *cp == '_') { + vStringPut(name, (int)*cp); + ++cp; + } + vStringTerminate(name); + makeSimpleTag(name, AspKinds, K_SUB); + vStringClear(name); + } else { + while (isalnum((int)*cp) || *cp == '_') { + vStringPut(name, (int)*cp); + ++cp; + } + vStringTerminate(name); + makeSimpleTag(name, AspKinds, K_DIM); + vStringClear(name); + } + } + } else if (strncasecmp((const char *)cp, "private", (size_t)7) == 0) { + cp += 7; + if (isspace((int)*cp)) { + while (isspace((int)*cp)) ++cp; + if (strncasecmp((const char *)cp, "function", (size_t)8) == 0) { + cp += 8; + while (isspace((int)*cp)) ++cp; + while (isalnum((int)*cp) || *cp == '_') { + vStringPut(name, (int)*cp); + ++cp; + } + vStringTerminate(name); + makeSimpleTag(name, AspKinds, K_FUNCTION); + vStringClear(name); + } else if (strncasecmp((const char *)cp, "sub", (size_t)3) == 0) { + cp += 3; + while (isspace((int)*cp)) ++cp; + while (isalnum((int)*cp) || *cp == '_') { + vStringPut(name, (int)*cp); + ++cp; + } + vStringTerminate(name); + makeSimpleTag(name, AspKinds, K_SUB); + vStringClear(name); + } else { + while (isalnum((int)*cp) || *cp == '_') { + vStringPut(name, (int)*cp); + ++cp; + } + vStringTerminate(name); + makeSimpleTag(name, AspKinds, K_DIM); + vStringClear(name); + } + } + } + + /* function? */ + else if (strncasecmp((const char *)cp, "function", (size_t)8) == 0) { + cp += 8; + + if (isspace((int)*cp)) { + while (isspace((int)*cp)) ++cp; + while (isalnum((int)*cp) || *cp == '_') { + vStringPut(name, (int)*cp); + ++cp; + } + vStringTerminate(name); + makeSimpleTag(name, AspKinds, K_FUNCTION); + vStringClear(name); + } + } + + /* sub? */ + else if (strncasecmp((const char *)cp, "sub", (size_t)3) == 0) { + cp += 3; + if (isspace((int)*cp)) { + while (isspace((int)*cp)) ++cp; + while (isalnum((int)*cp) || *cp == '_') { + vStringPut(name, (int)*cp); + ++cp; + } + vStringTerminate(name); + makeSimpleTag(name, AspKinds, K_SUB); + vStringClear(name); + } + } + + /* dim variable? */ + else if (strncasecmp((const char *)cp, "dim", (size_t)3) == 0) { + cp += 3; + if (isspace((int)*cp)) { + while (isspace((int)*cp)) ++cp; + while (isalnum((int)*cp) || *cp == '_') { + vStringPut(name, (int)*cp); + ++cp; + } + vStringTerminate(name); + makeSimpleTag(name, AspKinds, K_DIM); + vStringClear(name); + } + } + + /* class declaration? */ + else if (strncasecmp((const char *)cp, "class", (size_t)5) == 0) { + cp += 5; + if (isspace((int)*cp)) { + while (isspace((int)*cp)) ++cp; + while (isalnum((int)*cp) || *cp == '_') { + vStringPut(name, (int)*cp); + ++cp; + } + vStringTerminate(name); + makeSimpleTag(name, AspKinds, K_CLASS); + vStringClear(name); + } + } + + /* const declaration? */ + else if (strncasecmp((const char *)cp, "const", (size_t)5) == 0) { + cp += 5; + if (isspace((int)*cp)) { + while (isspace((int)*cp)) ++cp; + while (isalnum((int)*cp) || *cp == '_') { + vStringPut(name, (int)*cp); + ++cp; + } + vStringTerminate(name); + makeSimpleTag(name, AspKinds, K_CONST); + vStringClear(name); + } + } + + /* nothing relevant */ + else if (*cp != '\0') + cp++; + } + } + vStringDelete(name); +} + +extern parserDefinition *AspParser(void) { + static const char *const extensions[] = {"asp", "asa", NULL}; + parserDefinition *def = parserNew("Asp"); + def->kinds = AspKinds; + def->kindCount = KIND_COUNT(AspKinds); + def->extensions = extensions; + def->parser = findAspTags; + return def; +} + +/* vi:set tabstop=4 shiftwidth=4: */ diff --git a/third_party/ctags/awk.c b/third_party/ctags/awk.c new file mode 100644 index 00000000..a44a6d94 --- /dev/null +++ b/third_party/ctags/awk.c @@ -0,0 +1,63 @@ +/* + * $Id: awk.c 443 2006-05-30 04:37:13Z darren $ + * + * Copyright (c) 2000-2002, Darren Hiebert + * + * This source code is released for free distribution under the terms of the + * GNU General Public License. + * + * This module contains functions for generating tags for AWK functions. + */ +#include "third_party/ctags/general.h" +/* must always come first */ +#include "libc/str/str.h" +#include "third_party/ctags/parse.h" +#include "third_party/ctags/read.h" +#include "third_party/ctags/vstring.h" + +/* + * DATA DEFINITIONS + */ +typedef enum eAwkKinds { K_FUNCTION } awkKind; + +static kindOption AwkKinds[] = {{TRUE, 'f', "function", "functions"}}; + +/* + * FUNCTION DEFINITIONS + */ + +static void findAwkTags(void) { + vString *name = vStringNew(); + const unsigned char *line; + + while ((line = fileReadLine()) != NULL) { + if (strncmp((const char *)line, "function", (size_t)8) == 0 && + isspace((int)line[8])) { + const unsigned char *cp = line + 8; + + while (isspace((int)*cp)) ++cp; + while (isalnum((int)*cp) || *cp == '_') { + vStringPut(name, (int)*cp); + ++cp; + } + vStringTerminate(name); + while (isspace((int)*cp)) ++cp; + if (*cp == '(') makeSimpleTag(name, AwkKinds, K_FUNCTION); + vStringClear(name); + if (*cp != '\0') ++cp; + } + } + vStringDelete(name); +} + +extern parserDefinition *AwkParser() { + static const char *const extensions[] = {"awk", "gawk", "mawk", NULL}; + parserDefinition *def = parserNew("Awk"); + def->kinds = AwkKinds; + def->kindCount = KIND_COUNT(AwkKinds); + def->extensions = extensions; + def->parser = findAwkTags; + return def; +} + +/* vi:set tabstop=4 shiftwidth=4: */ diff --git a/third_party/ctags/basic.c b/third_party/ctags/basic.c new file mode 100644 index 00000000..ba827125 --- /dev/null +++ b/third_party/ctags/basic.c @@ -0,0 +1,175 @@ +/* + * $Id:$ + * + * Copyright (c) 2000-2006, Darren Hiebert, Elias Pschernig + * + * This source code is released for free distribution under the terms of the + * GNU General Public License. + * + * This module contains functions for generating tags for BlitzBasic + * (BlitzMax), PureBasic and FreeBasic language files. For now, this is kept + * quite simple - but feel free to ask for more things added any time - + * patches are of course most welcome. + */ +#include "third_party/ctags/general.h" +/* must always come first */ +#include "third_party/ctags/options.h" +#include "third_party/ctags/parse.h" +#include "third_party/ctags/read.h" +#include "third_party/ctags/routines.h" +#include "third_party/ctags/vstring.h" + +/* + * DATA DEFINITIONS + */ +typedef enum { + K_CONST, + K_FUNCTION, + K_LABEL, + K_TYPE, + K_VARIABLE, + K_ENUM +} BasicKind; + +typedef struct { + char const *token; + BasicKind kind; + int skip; +} KeyWord; + +static kindOption BasicKinds[] = { + {TRUE, 'c', "constant", "constants"}, {TRUE, 'f', "function", "functions"}, + {TRUE, 'l', "label", "labels"}, {TRUE, 't', "type", "types"}, + {TRUE, 'v', "variable", "variables"}, {TRUE, 'g', "enum", "enumerations"}, +}; + +static KeyWord blitzbasic_keywords[] = { + {"const", K_CONST, 0}, {"global", K_VARIABLE, 0}, + {"dim", K_VARIABLE, 0}, {"function", K_FUNCTION, 0}, + {"type", K_TYPE, 0}, {NULL, 0, 0}, +}; + +static KeyWord purebasic_keywords[] = { + {"newlist", K_VARIABLE, 0}, + {"global", K_VARIABLE, 0}, + {"dim", K_VARIABLE, 0}, + {"procedure", K_FUNCTION, 0}, + {"interface", K_TYPE, 0}, + {"structure", K_TYPE, 0}, + {NULL, 0, 0}, +}; + +static KeyWord freebasic_keywords[] = { + {"const", K_CONST, 0}, + {"dim as", K_VARIABLE, 1}, + {"dim", K_VARIABLE, 0}, + {"common", K_VARIABLE, 0}, + {"function", K_FUNCTION, 0}, + {"sub", K_FUNCTION, 0}, + {"private sub", K_FUNCTION, 0}, + {"public sub", K_FUNCTION, 0}, + {"private function", K_FUNCTION, 0}, + {"public function", K_FUNCTION, 0}, + {"type", K_TYPE, 0}, + {"enum", K_ENUM, 0}, + {NULL, 0, 0}, +}; + +/* + * FUNCTION DEFINITIONS + */ + +/* Match the name of a tag (function, variable, type, ...) starting at pos. */ +static char const *extract_name(char const *pos, vString *name) { + while (isspace(*pos)) pos++; + vStringClear(name); + for (; *pos && !isspace(*pos) && *pos != '(' && *pos != ','; pos++) + vStringPut(name, *pos); + vStringTerminate(name); + return pos; +} + +/* Match a keyword starting at p (case insensitive). */ +static int match_keyword(const char *p, KeyWord const *kw) { + vString *name; + size_t i; + int j; + for (i = 0; i < strlen(kw->token); i++) { + if (tolower(p[i]) != kw->token[i]) return 0; + } + name = vStringNew(); + p += i; + for (j = 0; j < 1 + kw->skip; j++) { + p = extract_name(p, name); + } + makeSimpleTag(name, BasicKinds, kw->kind); + vStringDelete(name); + return 1; +} + +/* Match a "label:" style label. */ +static void match_colon_label(char const *p) { + char const *end = p + strlen(p) - 1; + while (isspace(*end)) end--; + if (*end == ':') { + vString *name = vStringNew(); + vStringNCatS(name, p, end - p); + makeSimpleTag(name, BasicKinds, K_LABEL); + vStringDelete(name); + } +} + +/* Match a ".label" style label. */ +static void match_dot_label(char const *p) { + if (*p == '.') { + vString *name = vStringNew(); + extract_name(p + 1, name); + makeSimpleTag(name, BasicKinds, K_LABEL); + vStringDelete(name); + } +} + +static void findBasicTags(void) { + const char *line; + const char *extension = fileExtension(vStringValue(File.name)); + KeyWord *keywords; + + if (strcmp(extension, "bb") == 0) + keywords = blitzbasic_keywords; + else if (strcmp(extension, "pb") == 0) + keywords = purebasic_keywords; + else + keywords = freebasic_keywords; + + while ((line = (const char *)fileReadLine()) != NULL) { + const char *p = line; + KeyWord const *kw; + + while (isspace(*p)) p++; + + /* Empty line? */ + if (!*p) continue; + + /* In Basic, keywords always are at the start of the line. */ + for (kw = keywords; kw->token; kw++) + if (match_keyword(p, kw)) break; + + /* Is it a label? */ + if (strcmp(extension, "bb") == 0) + match_dot_label(p); + else + match_colon_label(p); + } +} + +parserDefinition *BasicParser(void) { + static char const *extensions[] = {"bas", "bi", "bb", "pb", NULL}; + parserDefinition *def = parserNew("Basic"); + def->kinds = BasicKinds; + def->kindCount = KIND_COUNT(BasicKinds); + def->extensions = extensions; + def->parser = findBasicTags; + return def; +} + +/* vi:set tabstop=4 shiftwidth=4: */ diff --git a/third_party/ctags/beta.c b/third_party/ctags/beta.c new file mode 100644 index 00000000..e7466c19 --- /dev/null +++ b/third_party/ctags/beta.c @@ -0,0 +1,273 @@ +/* + * $Id: beta.c 536 2007-06-02 06:09:00Z elliotth $ + * + * Copyright (c) 1999-2000, Mjlner Informatics + * + * Written by Erik Corry + * + * This source code is released for free distribution under the terms of the + * GNU General Public License. + * + * This module contains functions for generating tags for BETA language + * files. + */ +#include "third_party/ctags/general.h" +/* must always come first */ +#include "libc/calls/calls.h" +#include "libc/str/str.h" +#include "third_party/ctags/entry.h" +#include "third_party/ctags/parse.h" +#include "third_party/ctags/read.h" +#include "third_party/ctags/routines.h" +#include "third_party/ctags/vstring.h" + +/* + * MACROS + */ +#define isbident(c) (identarray[(unsigned char)(c)]) + +/* + * DATA DEFINITIONS + */ +typedef enum { K_FRAGMENT, K_PATTERN, K_SLOT, K_VIRTUAL } betaKind; + +static kindOption BetaKinds[] = { + {TRUE, 'f', "fragment", "fragment definitions"}, + {FALSE, 'p', "pattern", "all patterns"}, + {TRUE, 's', "slot", "slots (fragment uses)"}, + {TRUE, 'v', "virtual", "patterns (virtual or rebound)"}}; + +/* clang-format off */ +/* [A-Z_a-z0-9] */ +static const char identarray[256] ={ +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0-15 */ +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 16-31 */ +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 32-47 !"#$%&'()*+'-./ */ +1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, /* 48-63 0123456789:;<=>? */ +0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 64-79 @ABCDEFGHIJKLMNO */ +1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, /* 80-95 PQRSTUVWXYZ [\]^_ */ +0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 96-111 `abcdefghijklmno */ +1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, /* 112-127 pqrstuvwxyz{|}~ */ +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 128- */ +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; /* -255 */ +/* clang-format on */ + +/* + * FUNCTION DEFINITIONS + */ + +static void makeBetaTag(const char* const name, const betaKind kind) { + if (BetaKinds[kind].enabled) { + tagEntryInfo e; + initTagEntry(&e, name); + e.kindName = BetaKinds[kind].name; + e.kind = BetaKinds[kind].letter; + makeTagEntry(&e); + } +} + +static void findBetaTags(void) { + vString* line = vStringNew(); + boolean incomment = FALSE; + boolean inquote = FALSE; + boolean dovirtuals = BetaKinds[K_VIRTUAL].enabled; + boolean dopatterns = BetaKinds[K_PATTERN].enabled; + + do { + boolean foundfragmenthere = FALSE; + /* find fragment definition (line that starts and ends with --) */ + int last; + int first; + int c; + + vStringClear(line); + + while ((c = fileGetc()) != EOF && c != '\n' && c != '\r') + vStringPut(line, c); + + vStringTerminate(line); + + last = vStringLength(line) - 1; + first = 0; + /* skip white space at start and end of line */ + while (last && isspace((int)vStringChar(line, last))) last--; + while (first < last && isspace((int)vStringChar(line, first))) first++; + /* if line still has a reasonable length and ... */ + if (last - first > 4 && (vStringChar(line, first) == '-' && + vStringChar(line, first + 1) == '-' && + vStringChar(line, last) == '-' && + vStringChar(line, last - 1) == '-')) { + if (!incomment && !inquote) { + foundfragmenthere = TRUE; + /* skip past -- and whitespace. Also skip back past 'dopart' + or 'attributes' to the :. We have to do this because there + is no sensible way to include whitespace in a ctags token + so the conventional space after the ':' would mess us up */ + last -= 2; + first += 2; + while (last && vStringChar(line, last) != ':') last--; + while (last && (isspace((int)vStringChar(line, last - 1)))) last--; + while (first < last && (isspace((int)vStringChar(line, first)) || + vStringChar(line, first) == '-')) + first++; + /* If there's anything left it is a fragment title */ + if (first < last - 1) { + vStringChar(line, last) = 0; + if (strcasecmp("LIB", vStringValue(line) + first) && + strcasecmp("PROGRAM", vStringValue(line) + first)) { + makeBetaTag(vStringValue(line) + first, K_FRAGMENT); + } + } + } + } else { + int pos = 0; + int len = vStringLength(line); + if (inquote) goto stringtext; + if (incomment) goto commenttext; + programtext: + for (; pos < len; pos++) { + if (vStringChar(line, pos) == '\'') { + pos++; + inquote = TRUE; + goto stringtext; + } + if (vStringChar(line, pos) == '{') { + pos++; + incomment = TRUE; + goto commenttext; + } + if (vStringChar(line, pos) == '(' && pos < len - 1 && + vStringChar(line, pos + 1) == '*') { + pos += 2; + incomment = TRUE; + goto commenttext; + } + /* + * SLOT definition looks like this: + * <> + * or + * <> + */ + if (!foundfragmenthere && vStringChar(line, pos) == '<' && + pos + 1 < len && vStringChar(line, pos + 1) == '<' && + strstr(vStringValue(line) + pos, ">>")) { + /* Found slot name, get start and end */ + int eoname; + char c2; + pos += 2; /* skip past << */ + /* skip past space before SLOT */ + while (pos < len && isspace((int)vStringChar(line, pos))) pos++; + /* skip past SLOT */ + if (pos + 4 <= len && + !strncasecmp(vStringValue(line) + pos, "SLOT", (size_t)4)) + pos += 4; + /* skip past space after SLOT */ + while (pos < len && isspace((int)vStringChar(line, pos))) pos++; + eoname = pos; + /* skip to end of name */ + while (eoname < len && (c2 = vStringChar(line, eoname)) != '>' && + c2 != ':' && !isspace((int)c2)) + eoname++; + if (eoname < len) { + vStringChar(line, eoname) = 0; + if (strcasecmp("LIB", vStringValue(line) + pos) && + strcasecmp("PROGRAM", vStringValue(line) + pos) && + strcasecmp("SLOT", vStringValue(line) + pos)) { + makeBetaTag(vStringValue(line) + pos, K_SLOT); + } + } + if (eoname + 1 < len) { + pos = eoname + 1; + } else { + pos = len; + continue; + } + } + /* Only patterns that are virtual, extensions of virtuals or + * final bindings are normally included so as not to overload + * totally. + * That means one of the forms name:: name:< or name::< + */ + if (!foundfragmenthere && vStringChar(line, pos) == ':' && + (dopatterns || + (dovirtuals && (vStringChar(line, pos + 1) == ':' || + vStringChar(line, pos + 1) == '<')))) { + /* Found pattern name, get start and end */ + int eoname = pos; + int soname; + while (eoname && isspace((int)vStringChar(line, eoname - 1))) + eoname--; + foundanothername: + /* terminate right after name */ + vStringChar(line, eoname) = 0; + soname = eoname; + while (soname && isbident(vStringChar(line, soname - 1))) { + soname--; + } + if (soname != eoname) { + makeBetaTag(vStringValue(line) + soname, K_PATTERN); + /* scan back past white space */ + while (soname && isspace((int)vStringChar(line, soname - 1))) + soname--; + if (soname && vStringChar(line, soname - 1) == ',') { + /* we found a new pattern name before comma */ + eoname = soname; + goto foundanothername; + } + } + } + } + goto endofline; + commenttext: + for (; pos < len; pos++) { + if (vStringChar(line, pos) == '*' && pos < len - 1 && + vStringChar(line, pos + 1) == ')') { + pos += 2; + incomment = FALSE; + goto programtext; + } + if (vStringChar(line, pos) == '}') { + pos++; + incomment = FALSE; + goto programtext; + } + } + goto endofline; + stringtext: + for (; pos < len; pos++) { + if (vStringChar(line, pos) == '\\') { + if (pos < len - 1) pos++; + } else if (vStringChar(line, pos) == '\'') { + pos++; + /* support obsolete '' syntax */ + if (pos < len && vStringChar(line, pos) == '\'') { + continue; + } + inquote = FALSE; + goto programtext; + } + } + } + endofline: + inquote = FALSE; /* This shouldn't really make a difference */ + } while (!feof(File.fp)); + vStringDelete(line); +} + +extern parserDefinition* BetaParser(void) { + static const char* const extensions[] = {"bet", NULL}; + parserDefinition* def = parserNew("BETA"); + def->kinds = BetaKinds; + def->kindCount = KIND_COUNT(BetaKinds); + def->extensions = extensions; + def->parser = findBetaTags; + return def; +} + +/* vi:set tabstop=4 shiftwidth=4: */ diff --git a/third_party/ctags/c.c b/third_party/ctags/c.c new file mode 100644 index 00000000..84ebb35f --- /dev/null +++ b/third_party/ctags/c.c @@ -0,0 +1,2966 @@ +/* + * $Id: c.c 689 2008-12-13 21:17:36Z elliotth $ + * + * Copyright (c) 1996-2003, Darren Hiebert + * + * This source code is released for free distribution under the terms of the + * GNU General Public License. + * + * This module contains functions for parsing and scanning C, C++ and Java + * source files. + */ +#include "third_party/ctags/general.h" +/* must always come first */ +#include "libc/fmt/fmt.h" +#include "third_party/ctags/debug.h" +#include "third_party/ctags/entry.h" +#include "third_party/ctags/get.h" +#include "third_party/ctags/keyword.h" +#include "third_party/ctags/options.h" +#include "third_party/ctags/parse.h" +#include "third_party/ctags/read.h" +#include "third_party/ctags/routines.h" + +/* + * MACROS + */ + +#define activeToken(st) ((st)->token[(int)(st)->tokenIndex]) +#define parentDecl(st) \ + ((st)->parent == NULL ? DECL_NONE : (st)->parent->declaration) +#define isType(token, t) (boolean)((token)->type == (t)) +#define insideEnumBody(st) \ + ((st)->parent == NULL ? FALSE \ + : (boolean)((st)->parent->declaration == DECL_ENUM)) +#define isExternCDecl(st, c) \ + (boolean)((c) == STRING_SYMBOL && !(st)->haveQualifyingName && \ + (st)->scope == SCOPE_EXTERN) + +#define isOneOf(c, s) (boolean)(strchr((s), (c)) != NULL) + +#define isHighChar(c) ((c) != EOF && (unsigned char)(c) >= 0xc0) + +/* + * DATA DECLARATIONS + */ + +enum { NumTokens = 3 }; + +typedef enum eException { + ExceptionNone, + ExceptionEOF, + ExceptionFormattingError, + ExceptionBraceFormattingError +} exception_t; + +/* Used to specify type of keyword. + */ +typedef enum eKeywordId { + KEYWORD_NONE = -1, + KEYWORD_ATTRIBUTE, + KEYWORD_ABSTRACT, + KEYWORD_BOOLEAN, + KEYWORD_BYTE, + KEYWORD_BAD_STATE, + KEYWORD_BAD_TRANS, + KEYWORD_BIND, + KEYWORD_BIND_VAR, + KEYWORD_BIT, + KEYWORD_CASE, + KEYWORD_CATCH, + KEYWORD_CHAR, + KEYWORD_CLASS, + KEYWORD_CONST, + KEYWORD_CONSTRAINT, + KEYWORD_COVERAGE_BLOCK, + KEYWORD_COVERAGE_DEF, + KEYWORD_DEFAULT, + KEYWORD_DELEGATE, + KEYWORD_DELETE, + KEYWORD_DO, + KEYWORD_DOUBLE, + KEYWORD_ELSE, + KEYWORD_ENUM, + KEYWORD_EXPLICIT, + KEYWORD_EXTERN, + KEYWORD_EXTENDS, + KEYWORD_EVENT, + KEYWORD_FINAL, + KEYWORD_FLOAT, + KEYWORD_FOR, + KEYWORD_FOREACH, + KEYWORD_FRIEND, + KEYWORD_FUNCTION, + KEYWORD_GOTO, + KEYWORD_IF, + KEYWORD_IMPLEMENTS, + KEYWORD_IMPORT, + KEYWORD_INLINE, + KEYWORD_INT, + KEYWORD_INOUT, + KEYWORD_INPUT, + KEYWORD_INTEGER, + KEYWORD_INTERFACE, + KEYWORD_INTERNAL, + KEYWORD_LOCAL, + KEYWORD_LONG, + KEYWORD_M_BAD_STATE, + KEYWORD_M_BAD_TRANS, + KEYWORD_M_STATE, + KEYWORD_M_TRANS, + KEYWORD_MUTABLE, + KEYWORD_NAMESPACE, + KEYWORD_NEW, + KEYWORD_NEWCOV, + KEYWORD_NATIVE, + KEYWORD_OPERATOR, + KEYWORD_OUTPUT, + KEYWORD_OVERLOAD, + KEYWORD_OVERRIDE, + KEYWORD_PACKED, + KEYWORD_PORT, + KEYWORD_PACKAGE, + KEYWORD_PRIVATE, + KEYWORD_PROGRAM, + KEYWORD_PROTECTED, + KEYWORD_PUBLIC, + KEYWORD_REGISTER, + KEYWORD_RETURN, + KEYWORD_SHADOW, + KEYWORD_STATE, + KEYWORD_SHORT, + KEYWORD_SIGNED, + KEYWORD_STATIC, + KEYWORD_STRING, + KEYWORD_STRUCT, + KEYWORD_SWITCH, + KEYWORD_SYNCHRONIZED, + KEYWORD_TASK, + KEYWORD_TEMPLATE, + KEYWORD_THIS, + KEYWORD_THROW, + KEYWORD_THROWS, + KEYWORD_TRANSIENT, + KEYWORD_TRANS, + KEYWORD_TRANSITION, + KEYWORD_TRY, + KEYWORD_TYPEDEF, + KEYWORD_TYPENAME, + KEYWORD_UINT, + KEYWORD_ULONG, + KEYWORD_UNION, + KEYWORD_UNSIGNED, + KEYWORD_USHORT, + KEYWORD_USING, + KEYWORD_VIRTUAL, + KEYWORD_VOID, + KEYWORD_VOLATILE, + KEYWORD_WCHAR_T, + KEYWORD_WHILE +} keywordId; + +/* Used to determine whether keyword is valid for the current language and + * what its ID is. + */ +typedef struct sKeywordDesc { + const char *name; + keywordId id; + short isValid[5]; /* indicates languages for which kw is valid */ +} keywordDesc; + +/* Used for reporting the type of object parsed by nextToken (). + */ +typedef enum eTokenType { + TOKEN_NONE, /* none */ + TOKEN_ARGS, /* a parenthetical pair and its contents */ + TOKEN_BRACE_CLOSE, + TOKEN_BRACE_OPEN, + TOKEN_COLON, /* the colon character */ + TOKEN_COMMA, /* the comma character */ + TOKEN_DOUBLE_COLON, /* double colon indicates nested-name-specifier */ + TOKEN_KEYWORD, + TOKEN_NAME, /* an unknown name */ + TOKEN_PACKAGE, /* a Java package name */ + TOKEN_PAREN_NAME, /* a single name in parentheses */ + TOKEN_SEMICOLON, /* the semicolon character */ + TOKEN_SPEC, /* a storage class specifier, qualifier, type, etc. */ + TOKEN_COUNT +} tokenType; + +/* This describes the scoping of the current statement. + */ +typedef enum eTagScope { + SCOPE_GLOBAL, /* no storage class specified */ + SCOPE_STATIC, /* static storage class */ + SCOPE_EXTERN, /* external storage class */ + SCOPE_FRIEND, /* declares access only */ + SCOPE_TYPEDEF, /* scoping depends upon context */ + SCOPE_COUNT +} tagScope; + +typedef enum eDeclaration { + DECL_NONE, + DECL_BASE, /* base type (default) */ + DECL_CLASS, + DECL_ENUM, + DECL_EVENT, + DECL_FUNCTION, + DECL_IGNORE, /* non-taggable "declaration" */ + DECL_INTERFACE, + DECL_NAMESPACE, + DECL_NOMANGLE, /* C++ name demangling block */ + DECL_PACKAGE, + DECL_PROGRAM, /* Vera program */ + DECL_STRUCT, + DECL_TASK, /* Vera task */ + DECL_UNION, + DECL_COUNT +} declType; + +typedef enum eVisibilityType { + ACCESS_UNDEFINED, + ACCESS_LOCAL, + ACCESS_PRIVATE, + ACCESS_PROTECTED, + ACCESS_PUBLIC, + ACCESS_DEFAULT, /* Java-specific */ + ACCESS_COUNT +} accessType; + +/* Information about the parent class of a member (if any). + */ +typedef struct sMemberInfo { + accessType access; /* access of current statement */ + accessType accessDefault; /* access default for current statement */ +} memberInfo; + +typedef struct sTokenInfo { + tokenType type; + keywordId keyword; + vString *name; /* the name of the token */ + unsigned long lineNumber; /* line number of tag */ + fpos_t filePosition; /* file position of line containing name */ +} tokenInfo; + +typedef enum eImplementation { + IMP_DEFAULT, + IMP_ABSTRACT, + IMP_VIRTUAL, + IMP_PURE_VIRTUAL, + IMP_COUNT +} impType; + +/* Describes the statement currently undergoing analysis. + */ +typedef struct sStatementInfo { + tagScope scope; + declType declaration; /* specifier associated with TOKEN_SPEC */ + boolean gotName; /* was a name parsed yet? */ + boolean haveQualifyingName; /* do we have a name we are considering? */ + boolean gotParenName; /* was a name inside parentheses parsed yet? */ + boolean gotArgs; /* was a list of parameters parsed yet? */ + boolean isPointer; /* is 'name' a pointer? */ + boolean inFunction; /* are we inside of a function? */ + boolean assignment; /* have we handled an '='? */ + boolean notVariable; /* has a variable declaration been disqualified ? */ + impType implementation; /* abstract or concrete implementation? */ + unsigned int tokenIndex; /* currently active token */ + tokenInfo *token[(int)NumTokens]; + tokenInfo *context; /* accumulated scope of current statement */ + tokenInfo *blockName; /* name of current block */ + memberInfo member; /* information regarding parent class/struct */ + vString *parentClasses; /* parent classes */ + struct sStatementInfo *parent; /* statement we are nested within */ +} statementInfo; + +/* Describes the type of tag being generated. + */ +typedef enum eTagType { + TAG_UNDEFINED, + TAG_CLASS, /* class name */ + TAG_ENUM, /* enumeration name */ + TAG_ENUMERATOR, /* enumerator (enumeration value) */ + TAG_EVENT, /* event */ + TAG_FIELD, /* field (Java) */ + TAG_FUNCTION, /* function definition */ + TAG_INTERFACE, /* interface declaration */ + TAG_LOCAL, /* local variable definition */ + TAG_MEMBER, /* structure, class or interface member */ + TAG_METHOD, /* method declaration */ + TAG_NAMESPACE, /* namespace name */ + TAG_PACKAGE, /* package name */ + TAG_PROGRAM, /* program name */ + TAG_PROPERTY, /* property name */ + TAG_PROTOTYPE, /* function prototype or declaration */ + TAG_STRUCT, /* structure name */ + TAG_TASK, /* task name */ + TAG_TYPEDEF, /* typedef name */ + TAG_UNION, /* union name */ + TAG_VARIABLE, /* variable definition */ + TAG_EXTERN_VAR, /* external variable declaration */ + TAG_COUNT /* must be last */ +} tagType; + +typedef struct sParenInfo { + boolean isPointer; + boolean isParamList; + boolean isKnrParamList; + boolean isNameCandidate; + boolean invalidContents; + boolean nestedArgs; + unsigned int parameterCount; +} parenInfo; + +/* + * DATA DEFINITIONS + */ + +static jmp_buf Exception; + +static langType Lang_c; +static langType Lang_cpp; +static langType Lang_csharp; +static langType Lang_java; +static langType Lang_vera; +static vString *Signature; +static boolean CollectingSignature; + +/* Number used to uniquely identify anonymous structs and unions. */ +static int AnonymousID = 0; + +/* Used to index into the CKinds table. */ +typedef enum { + CK_UNDEFINED = -1, + CK_CLASS, + CK_DEFINE, + CK_ENUMERATOR, + CK_FUNCTION, + CK_ENUMERATION, + CK_LOCAL, + CK_MEMBER, + CK_NAMESPACE, + CK_PROTOTYPE, + CK_STRUCT, + CK_TYPEDEF, + CK_UNION, + CK_VARIABLE, + CK_EXTERN_VARIABLE +} cKind; + +static kindOption CKinds[] = { + {TRUE, 'c', "class", "classes"}, + {TRUE, 'd', "macro", "macro definitions"}, + {TRUE, 'e', "enumerator", "enumerators (values inside an enumeration)"}, + {TRUE, 'f', "function", "function definitions"}, + {TRUE, 'g', "enum", "enumeration names"}, + {FALSE, 'l', "local", "local variables"}, + {TRUE, 'm', "member", "class, struct, and union members"}, + {TRUE, 'n', "namespace", "namespaces"}, + {FALSE, 'p', "prototype", "function prototypes"}, + {TRUE, 's', "struct", "structure names"}, + {TRUE, 't', "typedef", "typedefs"}, + {TRUE, 'u', "union", "union names"}, + {TRUE, 'v', "variable", "variable definitions"}, + {FALSE, 'x', "externvar", "external and forward variable declarations"}, +}; + +typedef enum { + CSK_UNDEFINED = -1, + CSK_CLASS, + CSK_DEFINE, + CSK_ENUMERATOR, + CSK_EVENT, + CSK_FIELD, + CSK_ENUMERATION, + CSK_INTERFACE, + CSK_LOCAL, + CSK_METHOD, + CSK_NAMESPACE, + CSK_PROPERTY, + CSK_STRUCT, + CSK_TYPEDEF +} csharpKind; + +static kindOption CsharpKinds[] = { + {TRUE, 'c', "class", "classes"}, + {TRUE, 'd', "macro", "macro definitions"}, + {TRUE, 'e', "enumerator", "enumerators (values inside an enumeration)"}, + {TRUE, 'E', "event", "events"}, + {TRUE, 'f', "field", "fields"}, + {TRUE, 'g', "enum", "enumeration names"}, + {TRUE, 'i', "interface", "interfaces"}, + {FALSE, 'l', "local", "local variables"}, + {TRUE, 'm', "method", "methods"}, + {TRUE, 'n', "namespace", "namespaces"}, + {TRUE, 'p', "property", "properties"}, + {TRUE, 's', "struct", "structure names"}, + {TRUE, 't', "typedef", "typedefs"}, +}; + +/* Used to index into the JavaKinds table. */ +typedef enum { + JK_UNDEFINED = -1, + JK_CLASS, + JK_ENUM_CONSTANT, + JK_FIELD, + JK_ENUM, + JK_INTERFACE, + JK_LOCAL, + JK_METHOD, + JK_PACKAGE, + JK_ACCESS, + JK_CLASS_PREFIX +} javaKind; + +static kindOption JavaKinds[] = { + {TRUE, 'c', "class", "classes"}, + {TRUE, 'e', "enum constant", "enum constants"}, + {TRUE, 'f', "field", "fields"}, + {TRUE, 'g', "enum", "enum types"}, + {TRUE, 'i', "interface", "interfaces"}, + {FALSE, 'l', "local", "local variables"}, + {TRUE, 'm', "method", "methods"}, + {TRUE, 'p', "package", "packages"}, +}; + +/* Used to index into the VeraKinds table. */ +typedef enum { + VK_UNDEFINED = -1, + VK_CLASS, + VK_DEFINE, + VK_ENUMERATOR, + VK_FUNCTION, + VK_ENUMERATION, + VK_LOCAL, + VK_MEMBER, + VK_PROGRAM, + VK_PROTOTYPE, + VK_TASK, + VK_TYPEDEF, + VK_VARIABLE, + VK_EXTERN_VARIABLE +} veraKind; + +static kindOption VeraKinds[] = { + {TRUE, 'c', "class", "classes"}, + {TRUE, 'd', "macro", "macro definitions"}, + {TRUE, 'e', "enumerator", "enumerators (values inside an enumeration)"}, + {TRUE, 'f', "function", "function definitions"}, + {TRUE, 'g', "enum", "enumeration names"}, + {FALSE, 'l', "local", "local variables"}, + {TRUE, 'm', "member", "class, struct, and union members"}, + {TRUE, 'p', "program", "programs"}, + {FALSE, 'P', "prototype", "function prototypes"}, + {TRUE, 't', "task", "tasks"}, + {TRUE, 'T', "typedef", "typedefs"}, + {TRUE, 'v', "variable", "variable definitions"}, + {FALSE, 'x', "externvar", "external variable declarations"}}; + +static const keywordDesc KeywordTable[] = { + /* C++ */ + /* ANSI C | C# Java */ + /* | | | | Vera */ + /* keyword keyword ID | | | | | */ + {"__attribute__", KEYWORD_ATTRIBUTE, {1, 1, 1, 0, 0}}, + {"abstract", KEYWORD_ABSTRACT, {0, 0, 1, 1, 0}}, + {"bad_state", KEYWORD_BAD_STATE, {0, 0, 0, 0, 1}}, + {"bad_trans", KEYWORD_BAD_TRANS, {0, 0, 0, 0, 1}}, + {"bind", KEYWORD_BIND, {0, 0, 0, 0, 1}}, + {"bind_var", KEYWORD_BIND_VAR, {0, 0, 0, 0, 1}}, + {"bit", KEYWORD_BIT, {0, 0, 0, 0, 1}}, + {"boolean", KEYWORD_BOOLEAN, {0, 0, 0, 1, 0}}, + {"byte", KEYWORD_BYTE, {0, 0, 0, 1, 0}}, + {"case", KEYWORD_CASE, {1, 1, 1, 1, 0}}, + {"catch", KEYWORD_CATCH, {0, 1, 1, 0, 0}}, + {"char", KEYWORD_CHAR, {1, 1, 1, 1, 0}}, + {"class", KEYWORD_CLASS, {0, 1, 1, 1, 1}}, + {"const", KEYWORD_CONST, {1, 1, 1, 1, 0}}, + {"constraint", KEYWORD_CONSTRAINT, {0, 0, 0, 0, 1}}, + {"coverage_block", KEYWORD_COVERAGE_BLOCK, {0, 0, 0, 0, 1}}, + {"coverage_def", KEYWORD_COVERAGE_DEF, {0, 0, 0, 0, 1}}, + {"do", KEYWORD_DO, {1, 1, 1, 1, 0}}, + {"default", KEYWORD_DEFAULT, {1, 1, 1, 1, 0}}, + {"delegate", KEYWORD_DELEGATE, {0, 0, 1, 0, 0}}, + {"delete", KEYWORD_DELETE, {0, 1, 0, 0, 0}}, + {"double", KEYWORD_DOUBLE, {1, 1, 1, 1, 0}}, + {"else", KEYWORD_ELSE, {1, 1, 1, 1, 0}}, + {"enum", KEYWORD_ENUM, {1, 1, 1, 1, 1}}, + {"event", KEYWORD_EVENT, {0, 0, 1, 0, 1}}, + {"explicit", KEYWORD_EXPLICIT, {0, 1, 1, 0, 0}}, + {"extends", KEYWORD_EXTENDS, {0, 0, 0, 1, 1}}, + {"extern", KEYWORD_EXTERN, {1, 1, 1, 0, 1}}, + {"final", KEYWORD_FINAL, {0, 0, 0, 1, 0}}, + {"float", KEYWORD_FLOAT, {1, 1, 1, 1, 0}}, + {"for", KEYWORD_FOR, {1, 1, 1, 1, 0}}, + {"foreach", KEYWORD_FOREACH, {0, 0, 1, 0, 0}}, + {"friend", KEYWORD_FRIEND, {0, 1, 0, 0, 0}}, + {"function", KEYWORD_FUNCTION, {0, 0, 0, 0, 1}}, + {"goto", KEYWORD_GOTO, {1, 1, 1, 1, 0}}, + {"if", KEYWORD_IF, {1, 1, 1, 1, 0}}, + {"implements", KEYWORD_IMPLEMENTS, {0, 0, 0, 1, 0}}, + {"import", KEYWORD_IMPORT, {0, 0, 0, 1, 0}}, + {"inline", KEYWORD_INLINE, {0, 1, 0, 0, 0}}, + {"inout", KEYWORD_INOUT, {0, 0, 0, 0, 1}}, + {"input", KEYWORD_INPUT, {0, 0, 0, 0, 1}}, + {"int", KEYWORD_INT, {1, 1, 1, 1, 0}}, + {"integer", KEYWORD_INTEGER, {0, 0, 0, 0, 1}}, + {"interface", KEYWORD_INTERFACE, {0, 0, 1, 1, 1}}, + {"internal", KEYWORD_INTERNAL, {0, 0, 1, 0, 0}}, + {"local", KEYWORD_LOCAL, {0, 0, 0, 0, 1}}, + {"long", KEYWORD_LONG, {1, 1, 1, 1, 0}}, + {"m_bad_state", KEYWORD_M_BAD_STATE, {0, 0, 0, 0, 1}}, + {"m_bad_trans", KEYWORD_M_BAD_TRANS, {0, 0, 0, 0, 1}}, + {"m_state", KEYWORD_M_STATE, {0, 0, 0, 0, 1}}, + {"m_trans", KEYWORD_M_TRANS, {0, 0, 0, 0, 1}}, + {"mutable", KEYWORD_MUTABLE, {0, 1, 0, 0, 0}}, + {"namespace", KEYWORD_NAMESPACE, {0, 1, 1, 0, 0}}, + {"native", KEYWORD_NATIVE, {0, 0, 0, 1, 0}}, + {"new", KEYWORD_NEW, {0, 1, 1, 1, 0}}, + {"newcov", KEYWORD_NEWCOV, {0, 0, 0, 0, 1}}, + {"operator", KEYWORD_OPERATOR, {0, 1, 1, 0, 0}}, + {"output", KEYWORD_OUTPUT, {0, 0, 0, 0, 1}}, + {"overload", KEYWORD_OVERLOAD, {0, 1, 0, 0, 0}}, + {"override", KEYWORD_OVERRIDE, {0, 0, 1, 0, 0}}, + {"package", KEYWORD_PACKAGE, {0, 0, 0, 1, 0}}, + {"packed", KEYWORD_PACKED, {0, 0, 0, 0, 1}}, + {"port", KEYWORD_PORT, {0, 0, 0, 0, 1}}, + {"private", KEYWORD_PRIVATE, {0, 1, 1, 1, 0}}, + {"program", KEYWORD_PROGRAM, {0, 0, 0, 0, 1}}, + {"protected", KEYWORD_PROTECTED, {0, 1, 1, 1, 1}}, + {"public", KEYWORD_PUBLIC, {0, 1, 1, 1, 1}}, + {"register", KEYWORD_REGISTER, {1, 1, 0, 0, 0}}, + {"return", KEYWORD_RETURN, {1, 1, 1, 1, 0}}, + {"shadow", KEYWORD_SHADOW, {0, 0, 0, 0, 1}}, + {"short", KEYWORD_SHORT, {1, 1, 1, 1, 0}}, + {"signed", KEYWORD_SIGNED, {1, 1, 0, 0, 0}}, + {"state", KEYWORD_STATE, {0, 0, 0, 0, 1}}, + {"static", KEYWORD_STATIC, {1, 1, 1, 1, 1}}, + {"string", KEYWORD_STRING, {0, 0, 1, 0, 1}}, + {"struct", KEYWORD_STRUCT, {1, 1, 1, 0, 0}}, + {"switch", KEYWORD_SWITCH, {1, 1, 1, 1, 0}}, + {"synchronized", KEYWORD_SYNCHRONIZED, {0, 0, 0, 1, 0}}, + {"task", KEYWORD_TASK, {0, 0, 0, 0, 1}}, + {"template", KEYWORD_TEMPLATE, {0, 1, 0, 0, 0}}, + {"this", KEYWORD_THIS, {0, 1, 1, 1, 0}}, + {"throw", KEYWORD_THROW, {0, 1, 1, 1, 0}}, + {"throws", KEYWORD_THROWS, {0, 0, 0, 1, 0}}, + {"trans", KEYWORD_TRANS, {0, 0, 0, 0, 1}}, + {"transition", KEYWORD_TRANSITION, {0, 0, 0, 0, 1}}, + {"transient", KEYWORD_TRANSIENT, {0, 0, 0, 1, 0}}, + {"try", KEYWORD_TRY, {0, 1, 1, 0, 0}}, + {"typedef", KEYWORD_TYPEDEF, {1, 1, 1, 0, 1}}, + {"typename", KEYWORD_TYPENAME, {0, 1, 0, 0, 0}}, + {"uint", KEYWORD_UINT, {0, 0, 1, 0, 0}}, + {"ulong", KEYWORD_ULONG, {0, 0, 1, 0, 0}}, + {"union", KEYWORD_UNION, {1, 1, 0, 0, 0}}, + {"unsigned", KEYWORD_UNSIGNED, {1, 1, 1, 0, 0}}, + {"ushort", KEYWORD_USHORT, {0, 0, 1, 0, 0}}, + {"using", KEYWORD_USING, {0, 1, 1, 0, 0}}, + {"virtual", KEYWORD_VIRTUAL, {0, 1, 1, 0, 1}}, + {"void", KEYWORD_VOID, {1, 1, 1, 1, 1}}, + {"volatile", KEYWORD_VOLATILE, {1, 1, 1, 1, 0}}, + {"wchar_t", KEYWORD_WCHAR_T, {1, 1, 1, 0, 0}}, + {"while", KEYWORD_WHILE, {1, 1, 1, 1, 0}}}; + +/* + * FUNCTION PROTOTYPES + */ +static void createTags(const unsigned int nestLevel, + statementInfo *const parent); + +/* + * FUNCTION DEFINITIONS + */ + +extern boolean includingDefineTags(void) { + return CKinds[CK_DEFINE].enabled; +} + +/* + * Token management + */ + +static void initToken(tokenInfo *const token) { + token->type = TOKEN_NONE; + token->keyword = KEYWORD_NONE; + token->lineNumber = getSourceLineNumber(); + token->filePosition = getInputFilePosition(); + vStringClear(token->name); +} + +static void advanceToken(statementInfo *const st) { + if (st->tokenIndex >= (unsigned int)NumTokens - 1) + st->tokenIndex = 0; + else + ++st->tokenIndex; + initToken(st->token[st->tokenIndex]); +} + +static tokenInfo *prevToken(const statementInfo *const st, unsigned int n) { + unsigned int tokenIndex; + unsigned int num = (unsigned int)NumTokens; + Assert(n < num); + tokenIndex = (st->tokenIndex + num - n) % num; + return st->token[tokenIndex]; +} + +static void setToken(statementInfo *const st, const tokenType type) { + tokenInfo *token; + token = activeToken(st); + initToken(token); + token->type = type; +} + +static void retardToken(statementInfo *const st) { + if (st->tokenIndex == 0) + st->tokenIndex = (unsigned int)NumTokens - 1; + else + --st->tokenIndex; + setToken(st, TOKEN_NONE); +} + +static tokenInfo *newToken(void) { + tokenInfo *const token = xMalloc(1, tokenInfo); + token->name = vStringNew(); + initToken(token); + return token; +} + +static void deleteToken(tokenInfo *const token) { + if (token != NULL) { + vStringDelete(token->name); + eFree(token); + } +} + +static const char *accessString(const accessType access) { + static const char *const names[] = {"?", "local", "private", + "protected", "public", "default"}; + Assert(sizeof(names) / sizeof(names[0]) == ACCESS_COUNT); + Assert((int)access < ACCESS_COUNT); + return names[(int)access]; +} + +static const char *implementationString(const impType imp) { + static const char *const names[] = {"?", "abstract", "virtual", + "pure virtual"}; + Assert(sizeof(names) / sizeof(names[0]) == IMP_COUNT); + Assert((int)imp < IMP_COUNT); + return names[(int)imp]; +} + +/* + * Debugging functions + */ + +#ifdef DEBUG + +#define boolString(c) ((c) ? "TRUE" : "FALSE") + +static const char *tokenString(const tokenType type) { + static const char *const names[] = { + "none", "args", "}", "{", "colon", + "comma", "double colon", "keyword", "name", "package", + "paren-name", "semicolon", "specifier"}; + Assert(sizeof(names) / sizeof(names[0]) == TOKEN_COUNT); + Assert((int)type < TOKEN_COUNT); + return names[(int)type]; +} + +static const char *scopeString(const tagScope scope) { + static const char *const names[] = {"global", "static", "extern", "friend", + "typedef"}; + Assert(sizeof(names) / sizeof(names[0]) == SCOPE_COUNT); + Assert((int)scope < SCOPE_COUNT); + return names[(int)scope]; +} + +static const char *declString(const declType declaration) { + static const char *const names[] = { + "?", "base", "class", "enum", "event", + "function", "ignore", "interface", "namespace", "no mangle", + "package", "program", "struct", "task", "union", + }; + Assert(sizeof(names) / sizeof(names[0]) == DECL_COUNT); + Assert((int)declaration < DECL_COUNT); + return names[(int)declaration]; +} + +static const char *keywordString(const keywordId keyword) { + const size_t count = sizeof(KeywordTable) / sizeof(KeywordTable[0]); + const char *name = "none"; + size_t i; + for (i = 0; i < count; ++i) { + const keywordDesc *p = &KeywordTable[i]; + if (p->id == keyword) { + name = p->name; + break; + } + } + return name; +} + +static void __unused__ pt(tokenInfo *const token) { + if (isType(token, TOKEN_NAME)) + printf("type: %-12s: %-13s line: %lu\n", tokenString(token->type), + vStringValue(token->name), token->lineNumber); + else if (isType(token, TOKEN_KEYWORD)) + printf("type: %-12s: %-13s line: %lu\n", tokenString(token->type), + keywordString(token->keyword), token->lineNumber); + else + printf("type: %-12s line: %lu\n", tokenString(token->type), + token->lineNumber); +} + +static void __unused__ ps(statementInfo *const st) { + unsigned int i; + printf("scope: %s decl: %s gotName: %s gotParenName: %s\n", + scopeString(st->scope), declString(st->declaration), + boolString(st->gotName), boolString(st->gotParenName)); + printf("haveQualifyingName: %s\n", boolString(st->haveQualifyingName)); + printf("access: %s default: %s\n", accessString(st->member.access), + accessString(st->member.accessDefault)); + printf("token : "); + pt(activeToken(st)); + for (i = 1; i < (unsigned int)NumTokens; ++i) { + printf("prev %u : ", i); + pt(prevToken(st, i)); + } + printf("context: "); + pt(st->context); +} + +#endif + +/* + * Statement management + */ + +static boolean isContextualKeyword(const tokenInfo *const token) { + boolean result; + switch (token->keyword) { + case KEYWORD_CLASS: + case KEYWORD_ENUM: + case KEYWORD_INTERFACE: + case KEYWORD_NAMESPACE: + case KEYWORD_STRUCT: + case KEYWORD_UNION: + result = TRUE; + break; + + default: + result = FALSE; + break; + } + return result; +} + +static boolean isContextualStatement(const statementInfo *const st) { + boolean result = FALSE; + if (st != NULL) switch (st->declaration) { + case DECL_CLASS: + case DECL_ENUM: + case DECL_INTERFACE: + case DECL_NAMESPACE: + case DECL_STRUCT: + case DECL_UNION: + result = TRUE; + break; + + default: + result = FALSE; + break; + } + return result; +} + +static boolean isMember(const statementInfo *const st) { + boolean result; + if (isType(st->context, TOKEN_NAME)) + result = TRUE; + else + result = (boolean)(st->parent != NULL && isContextualStatement(st->parent)); + return result; +} + +static void initMemberInfo(statementInfo *const st) { + accessType accessDefault = ACCESS_UNDEFINED; + + if (st->parent != NULL) switch (st->parent->declaration) { + case DECL_ENUM: + accessDefault = + (isLanguage(Lang_java) ? ACCESS_PUBLIC : ACCESS_UNDEFINED); + break; + case DECL_NAMESPACE: + accessDefault = ACCESS_UNDEFINED; + break; + + case DECL_CLASS: + if (isLanguage(Lang_java)) + accessDefault = ACCESS_DEFAULT; + else + accessDefault = ACCESS_PRIVATE; + break; + + case DECL_INTERFACE: + case DECL_STRUCT: + case DECL_UNION: + accessDefault = ACCESS_PUBLIC; + break; + + default: + break; + } + st->member.accessDefault = accessDefault; + st->member.access = accessDefault; +} + +static void reinitStatement(statementInfo *const st, const boolean partial) { + unsigned int i; + + if (!partial) { + st->scope = SCOPE_GLOBAL; + if (isContextualStatement(st->parent)) + st->declaration = DECL_BASE; + else + st->declaration = DECL_NONE; + } + st->gotParenName = FALSE; + st->isPointer = FALSE; + st->inFunction = FALSE; + st->assignment = FALSE; + st->notVariable = FALSE; + st->implementation = IMP_DEFAULT; + st->gotArgs = FALSE; + st->gotName = FALSE; + st->haveQualifyingName = FALSE; + st->tokenIndex = 0; + + if (st->parent != NULL) st->inFunction = st->parent->inFunction; + + for (i = 0; i < (unsigned int)NumTokens; ++i) initToken(st->token[i]); + + initToken(st->context); + + /* Keep the block name, so that a variable following after a comma will + * still have the structure name. + */ + if (!partial) initToken(st->blockName); + + vStringClear(st->parentClasses); + + /* Init member info. + */ + if (!partial) st->member.access = st->member.accessDefault; +} + +static void initStatement(statementInfo *const st, + statementInfo *const parent) { + st->parent = parent; + initMemberInfo(st); + reinitStatement(st, FALSE); +} + +/* + * Tag generation functions + */ +static cKind cTagKind(const tagType type) { + cKind result = CK_UNDEFINED; + switch (type) { + case TAG_CLASS: + result = CK_CLASS; + break; + case TAG_ENUM: + result = CK_ENUMERATION; + break; + case TAG_ENUMERATOR: + result = CK_ENUMERATOR; + break; + case TAG_FUNCTION: + result = CK_FUNCTION; + break; + case TAG_LOCAL: + result = CK_LOCAL; + break; + case TAG_MEMBER: + result = CK_MEMBER; + break; + case TAG_NAMESPACE: + result = CK_NAMESPACE; + break; + case TAG_PROTOTYPE: + result = CK_PROTOTYPE; + break; + case TAG_STRUCT: + result = CK_STRUCT; + break; + case TAG_TYPEDEF: + result = CK_TYPEDEF; + break; + case TAG_UNION: + result = CK_UNION; + break; + case TAG_VARIABLE: + result = CK_VARIABLE; + break; + case TAG_EXTERN_VAR: + result = CK_EXTERN_VARIABLE; + break; + + default: + Assert("Bad C tag type" == NULL); + break; + } + return result; +} + +static csharpKind csharpTagKind(const tagType type) { + csharpKind result = CSK_UNDEFINED; + switch (type) { + case TAG_CLASS: + result = CSK_CLASS; + break; + case TAG_ENUM: + result = CSK_ENUMERATION; + break; + case TAG_ENUMERATOR: + result = CSK_ENUMERATOR; + break; + case TAG_EVENT: + result = CSK_EVENT; + break; + case TAG_FIELD: + result = CSK_FIELD; + break; + case TAG_INTERFACE: + result = CSK_INTERFACE; + break; + case TAG_LOCAL: + result = CSK_LOCAL; + break; + case TAG_METHOD: + result = CSK_METHOD; + break; + case TAG_NAMESPACE: + result = CSK_NAMESPACE; + break; + case TAG_PROPERTY: + result = CSK_PROPERTY; + break; + case TAG_STRUCT: + result = CSK_STRUCT; + break; + case TAG_TYPEDEF: + result = CSK_TYPEDEF; + break; + + default: + Assert("Bad C# tag type" == NULL); + break; + } + return result; +} + +static javaKind javaTagKind(const tagType type) { + javaKind result = JK_UNDEFINED; + switch (type) { + case TAG_CLASS: + result = JK_CLASS; + break; + case TAG_ENUM: + result = JK_ENUM; + break; + case TAG_ENUMERATOR: + result = JK_ENUM_CONSTANT; + break; + case TAG_FIELD: + result = JK_FIELD; + break; + case TAG_INTERFACE: + result = JK_INTERFACE; + break; + case TAG_LOCAL: + result = JK_LOCAL; + break; + case TAG_METHOD: + result = JK_METHOD; + break; + case TAG_PACKAGE: + result = JK_PACKAGE; + break; + + default: + Assert("Bad Java tag type" == NULL); + break; + } + return result; +} + +static veraKind veraTagKind(const tagType type) { + veraKind result = VK_UNDEFINED; + switch (type) { + case TAG_CLASS: + result = VK_CLASS; + break; + case TAG_ENUM: + result = VK_ENUMERATION; + break; + case TAG_ENUMERATOR: + result = VK_ENUMERATOR; + break; + case TAG_FUNCTION: + result = VK_FUNCTION; + break; + case TAG_LOCAL: + result = VK_LOCAL; + break; + case TAG_MEMBER: + result = VK_MEMBER; + break; + case TAG_PROGRAM: + result = VK_PROGRAM; + break; + case TAG_PROTOTYPE: + result = VK_PROTOTYPE; + break; + case TAG_TASK: + result = VK_TASK; + break; + case TAG_TYPEDEF: + result = VK_TYPEDEF; + break; + case TAG_VARIABLE: + result = VK_VARIABLE; + break; + case TAG_EXTERN_VAR: + result = VK_EXTERN_VARIABLE; + break; + + default: + Assert("Bad Vera tag type" == NULL); + break; + } + return result; +} + +static const char *tagName(const tagType type) { + const char *result; + if (isLanguage(Lang_csharp)) + result = CsharpKinds[csharpTagKind(type)].name; + else if (isLanguage(Lang_java)) + result = JavaKinds[javaTagKind(type)].name; + else if (isLanguage(Lang_vera)) + result = VeraKinds[veraTagKind(type)].name; + else + result = CKinds[cTagKind(type)].name; + return result; +} + +static int tagLetter(const tagType type) { + int result; + if (isLanguage(Lang_csharp)) + result = CsharpKinds[csharpTagKind(type)].letter; + else if (isLanguage(Lang_java)) + result = JavaKinds[javaTagKind(type)].letter; + else if (isLanguage(Lang_vera)) + result = VeraKinds[veraTagKind(type)].letter; + else + result = CKinds[cTagKind(type)].letter; + return result; +} + +static boolean includeTag(const tagType type, const boolean isFileScope) { + boolean result; + if (isFileScope && !Option.include.fileScope) + result = FALSE; + else if (isLanguage(Lang_csharp)) + result = CsharpKinds[csharpTagKind(type)].enabled; + else if (isLanguage(Lang_java)) + result = JavaKinds[javaTagKind(type)].enabled; + else if (isLanguage(Lang_vera)) + result = VeraKinds[veraTagKind(type)].enabled; + else + result = CKinds[cTagKind(type)].enabled; + return result; +} + +static tagType declToTagType(const declType declaration) { + tagType type = TAG_UNDEFINED; + + switch (declaration) { + case DECL_CLASS: + type = TAG_CLASS; + break; + case DECL_ENUM: + type = TAG_ENUM; + break; + case DECL_EVENT: + type = TAG_EVENT; + break; + case DECL_FUNCTION: + type = TAG_FUNCTION; + break; + case DECL_INTERFACE: + type = TAG_INTERFACE; + break; + case DECL_NAMESPACE: + type = TAG_NAMESPACE; + break; + case DECL_PROGRAM: + type = TAG_PROGRAM; + break; + case DECL_TASK: + type = TAG_TASK; + break; + case DECL_STRUCT: + type = TAG_STRUCT; + break; + case DECL_UNION: + type = TAG_UNION; + break; + + default: + Assert("Unexpected declaration" == NULL); + break; + } + return type; +} + +static const char *accessField(const statementInfo *const st) { + const char *result = NULL; + if (isLanguage(Lang_cpp) && st->scope == SCOPE_FRIEND) + result = "friend"; + else if (st->member.access != ACCESS_UNDEFINED) + result = accessString(st->member.access); + return result; +} + +static void addContextSeparator(vString *const scope) { + if (isLanguage(Lang_c) || isLanguage(Lang_cpp)) + vStringCatS(scope, "::"); + else if (isLanguage(Lang_java) || isLanguage(Lang_csharp)) + vStringCatS(scope, "."); +} + +static void addOtherFields(tagEntryInfo *const tag, const tagType type, + const statementInfo *const st, vString *const scope, + vString *const typeRef) { + /* For selected tag types, append an extension flag designating the + * parent object in which the tag is defined. + */ + switch (type) { + default: + break; + + case TAG_FUNCTION: + case TAG_METHOD: + case TAG_PROTOTYPE: + if (vStringLength(Signature) > 0) + tag->extensionFields.signature = vStringValue(Signature); + case TAG_CLASS: + case TAG_ENUM: + case TAG_ENUMERATOR: + case TAG_EVENT: + case TAG_FIELD: + case TAG_INTERFACE: + case TAG_MEMBER: + case TAG_NAMESPACE: + case TAG_PROPERTY: + case TAG_STRUCT: + case TAG_TASK: + case TAG_TYPEDEF: + case TAG_UNION: + if (vStringLength(scope) > 0 && + (isMember(st) || st->parent->declaration == DECL_NAMESPACE)) { + if (isType(st->context, TOKEN_NAME)) + tag->extensionFields.scope[0] = tagName(TAG_CLASS); + else + tag->extensionFields.scope[0] = + tagName(declToTagType(parentDecl(st))); + tag->extensionFields.scope[1] = vStringValue(scope); + } + if ((type == TAG_CLASS || type == TAG_INTERFACE || type == TAG_STRUCT) && + vStringLength(st->parentClasses) > 0) { + + tag->extensionFields.inheritance = vStringValue(st->parentClasses); + } + if (st->implementation != IMP_DEFAULT && + (isLanguage(Lang_cpp) || isLanguage(Lang_csharp) || + isLanguage(Lang_java))) { + tag->extensionFields.implementation = + implementationString(st->implementation); + } + if (isMember(st)) { + tag->extensionFields.access = accessField(st); + } + break; + } + + /* Add typename info, type of the tag and name of struct/union/etc. */ + if ((type == TAG_TYPEDEF || type == TAG_VARIABLE || type == TAG_MEMBER) && + isContextualStatement(st)) { + char *p; + + tag->extensionFields.typeRef[0] = tagName(declToTagType(st->declaration)); + p = vStringValue(st->blockName->name); + + /* If there was no {} block get the name from the token before the + * name (current token is ';' or ',', previous token is the name). + */ + if (p == NULL || *p == '\0') { + tokenInfo *const prev2 = prevToken(st, 2); + if (isType(prev2, TOKEN_NAME)) p = vStringValue(prev2->name); + } + + /* Prepend the scope name if there is one. */ + if (vStringLength(scope) > 0) { + vStringCopy(typeRef, scope); + addContextSeparator(typeRef); + vStringCatS(typeRef, p); + p = vStringValue(typeRef); + } + tag->extensionFields.typeRef[1] = p; + } +} + +static void findScopeHierarchy(vString *const string, + const statementInfo *const st) { + vStringClear(string); + if (isType(st->context, TOKEN_NAME)) vStringCopy(string, st->context->name); + if (st->parent != NULL) { + vString *temp = vStringNew(); + const statementInfo *s; + for (s = st->parent; s != NULL; s = s->parent) { + if (isContextualStatement(s) || s->declaration == DECL_NAMESPACE || + s->declaration == DECL_PROGRAM) { + vStringCopy(temp, string); + vStringClear(string); + Assert(isType(s->blockName, TOKEN_NAME)); + if (isType(s->context, TOKEN_NAME) && + vStringLength(s->context->name) > 0) { + vStringCat(string, s->context->name); + addContextSeparator(string); + } + vStringCat(string, s->blockName->name); + if (vStringLength(temp) > 0) addContextSeparator(string); + vStringCat(string, temp); + } + } + vStringDelete(temp); + } +} + +static void makeExtraTagEntry(const tagType type, tagEntryInfo *const e, + vString *const scope) { + if (Option.include.qualifiedTags && scope != NULL && + vStringLength(scope) > 0) { + vString *const scopedName = vStringNew(); + + if (type != TAG_ENUMERATOR) + vStringCopy(scopedName, scope); + else { + /* remove last component (i.e. enumeration name) from scope */ + const char *const sc = vStringValue(scope); + const char *colon = strrchr(sc, ':'); + if (colon != NULL) { + while (*colon == ':' && colon > sc) --colon; + vStringNCopy(scopedName, scope, colon + 1 - sc); + } + } + if (vStringLength(scopedName) > 0) { + addContextSeparator(scopedName); + vStringCatS(scopedName, e->name); + e->name = vStringValue(scopedName); + makeTagEntry(e); + } + vStringDelete(scopedName); + } +} + +static void makeTag(const tokenInfo *const token, const statementInfo *const st, + boolean isFileScope, const tagType type) { + /* Nothing is really of file scope when it appears in a header file. + */ + isFileScope = (boolean)(isFileScope && !isHeaderFile()); + + if (isType(token, TOKEN_NAME) && vStringLength(token->name) > 0 && + includeTag(type, isFileScope)) { + vString *scope = vStringNew(); + /* Use "typeRef" to store the typename from addOtherFields() until + * it's used in makeTagEntry(). + */ + vString *typeRef = vStringNew(); + tagEntryInfo e; + + initTagEntry(&e, vStringValue(token->name)); + + e.lineNumber = token->lineNumber; + e.filePosition = token->filePosition; + e.isFileScope = isFileScope; + e.kindName = tagName(type); + e.kind = tagLetter(type); + + findScopeHierarchy(scope, st); + addOtherFields(&e, type, st, scope, typeRef); + + makeTagEntry(&e); + makeExtraTagEntry(type, &e, scope); + vStringDelete(scope); + vStringDelete(typeRef); + } +} + +static boolean isValidTypeSpecifier(const declType declaration) { + boolean result; + switch (declaration) { + case DECL_BASE: + case DECL_CLASS: + case DECL_ENUM: + case DECL_EVENT: + case DECL_STRUCT: + case DECL_UNION: + result = TRUE; + break; + + default: + result = FALSE; + break; + } + return result; +} + +static void qualifyEnumeratorTag(const statementInfo *const st, + const tokenInfo *const nameToken) { + if (isType(nameToken, TOKEN_NAME)) + makeTag(nameToken, st, TRUE, TAG_ENUMERATOR); +} + +static void qualifyFunctionTag(const statementInfo *const st, + const tokenInfo *const nameToken) { + if (isType(nameToken, TOKEN_NAME)) { + tagType type; + const boolean isFileScope = + (boolean)(st->member.access == ACCESS_PRIVATE || + (!isMember(st) && st->scope == SCOPE_STATIC)); + if (isLanguage(Lang_java) || isLanguage(Lang_csharp)) + type = TAG_METHOD; + else if (isLanguage(Lang_vera) && st->declaration == DECL_TASK) + type = TAG_TASK; + else + type = TAG_FUNCTION; + makeTag(nameToken, st, isFileScope, type); + } +} + +static void qualifyFunctionDeclTag(const statementInfo *const st, + const tokenInfo *const nameToken) { + if (!isType(nameToken, TOKEN_NAME)) + ; + else if (isLanguage(Lang_java) || isLanguage(Lang_csharp)) + qualifyFunctionTag(st, nameToken); + else if (st->scope == SCOPE_TYPEDEF) + makeTag(nameToken, st, TRUE, TAG_TYPEDEF); + else if (isValidTypeSpecifier(st->declaration) && !isLanguage(Lang_csharp)) + makeTag(nameToken, st, TRUE, TAG_PROTOTYPE); +} + +static void qualifyCompoundTag(const statementInfo *const st, + const tokenInfo *const nameToken) { + if (isType(nameToken, TOKEN_NAME)) { + const tagType type = declToTagType(st->declaration); + const boolean fileScoped = + (boolean)(!(isLanguage(Lang_java) || isLanguage(Lang_csharp) || + isLanguage(Lang_vera))); + + if (type != TAG_UNDEFINED) makeTag(nameToken, st, fileScoped, type); + } +} + +static void qualifyBlockTag(statementInfo *const st, + const tokenInfo *const nameToken) { + switch (st->declaration) { + case DECL_CLASS: + case DECL_ENUM: + case DECL_INTERFACE: + case DECL_NAMESPACE: + case DECL_PROGRAM: + case DECL_STRUCT: + case DECL_UNION: + qualifyCompoundTag(st, nameToken); + break; + default: + break; + } +} + +static void qualifyVariableTag(const statementInfo *const st, + const tokenInfo *const nameToken) { + /* We have to watch that we do not interpret a declaration of the + * form "struct tag;" as a variable definition. In such a case, the + * token preceding the name will be a keyword. + */ + if (!isType(nameToken, TOKEN_NAME)) + ; + else if (st->scope == SCOPE_TYPEDEF) + makeTag(nameToken, st, TRUE, TAG_TYPEDEF); + else if (st->declaration == DECL_EVENT) + makeTag(nameToken, st, (boolean)(st->member.access == ACCESS_PRIVATE), + TAG_EVENT); + else if (st->declaration == DECL_PACKAGE) + makeTag(nameToken, st, FALSE, TAG_PACKAGE); + else if (isValidTypeSpecifier(st->declaration)) { + if (st->notVariable) + ; + else if (isMember(st)) { + if (isLanguage(Lang_java) || isLanguage(Lang_csharp)) + makeTag(nameToken, st, (boolean)(st->member.access == ACCESS_PRIVATE), + TAG_FIELD); + else if (st->scope == SCOPE_GLOBAL || st->scope == SCOPE_STATIC) + makeTag(nameToken, st, TRUE, TAG_MEMBER); + } else { + if (st->scope == SCOPE_EXTERN || !st->haveQualifyingName) + makeTag(nameToken, st, FALSE, TAG_EXTERN_VAR); + else if (st->inFunction) + makeTag(nameToken, st, (boolean)(st->scope == SCOPE_STATIC), TAG_LOCAL); + else + makeTag(nameToken, st, (boolean)(st->scope == SCOPE_STATIC), + TAG_VARIABLE); + } + } +} + +/* + * Parsing functions + */ + +static int skipToOneOf(const char *const chars) { + int c; + do + c = cppGetc(); + while (c != EOF && c != '\0' && strchr(chars, c) == NULL); + return c; +} + +/* Skip to the next non-white character. + */ +static int skipToNonWhite(void) { + boolean found = FALSE; + int c; + +#if 0 + do + c = cppGetc (); + while (isspace (c)); +#else + while (1) { + c = cppGetc(); + if (isspace(c)) + found = TRUE; + else + break; + } + if (CollectingSignature && found) vStringPut(Signature, ' '); +#endif + + return c; +} + +/* Skips to the next brace in column 1. This is intended for cases where + * preprocessor constructs result in unbalanced braces. + */ +static void skipToFormattedBraceMatch(void) { + int c, next; + + c = cppGetc(); + next = cppGetc(); + while (c != EOF && (c != '\n' || next != '}')) { + c = next; + next = cppGetc(); + } +} + +/* Skip to the matching character indicated by the pair string. If skipping + * to a matching brace and any brace is found within a different level of a + * #if conditional statement while brace formatting is in effect, we skip to + * the brace matched by its formatting. It is assumed that we have already + * read the character which starts the group (i.e. the first character of + * "pair"). + */ +static void skipToMatch(const char *const pair) { + const boolean braceMatching = (boolean)(strcmp("{}", pair) == 0); + const boolean braceFormatting = (boolean)(isBraceFormat() && braceMatching); + const unsigned int initialLevel = getDirectiveNestLevel(); + const int begin = pair[0], end = pair[1]; + const unsigned long inputLineNumber = getInputLineNumber(); + int matchLevel = 1; + int c = '\0'; + + while (matchLevel > 0 && (c = skipToNonWhite()) != EOF) { + if (CollectingSignature) vStringPut(Signature, c); + if (c == begin) { + ++matchLevel; + if (braceFormatting && getDirectiveNestLevel() != initialLevel) { + skipToFormattedBraceMatch(); + break; + } + } else if (c == end) { + --matchLevel; + if (braceFormatting && getDirectiveNestLevel() != initialLevel) { + skipToFormattedBraceMatch(); + break; + } + } + } + if (c == EOF) { + verbose("%s: failed to find match for '%c' at line %lu\n", + getInputFileName(), begin, inputLineNumber); + if (braceMatching) + longjmp(Exception, (int)ExceptionBraceFormattingError); + else + longjmp(Exception, (int)ExceptionFormattingError); + } +} + +static void skipParens(void) { + const int c = skipToNonWhite(); + + if (c == '(') + skipToMatch("()"); + else + cppUngetc(c); +} + +static void skipBraces(void) { + const int c = skipToNonWhite(); + + if (c == '{') + skipToMatch("{}"); + else + cppUngetc(c); +} + +static keywordId analyzeKeyword(const char *const name) { + const keywordId id = (keywordId)lookupKeyword(name, getSourceLanguage()); + return id; +} + +static void analyzeIdentifier(tokenInfo *const token) { + char *const name = vStringValue(token->name); + const char *replacement = NULL; + boolean parensToo = FALSE; + + if (isLanguage(Lang_java) || !isIgnoreToken(name, &parensToo, &replacement)) { + if (replacement != NULL) + token->keyword = analyzeKeyword(replacement); + else + token->keyword = analyzeKeyword(vStringValue(token->name)); + + if (token->keyword == KEYWORD_NONE) + token->type = TOKEN_NAME; + else + token->type = TOKEN_KEYWORD; + } else { + initToken(token); + if (parensToo) { + int c = skipToNonWhite(); + + if (c == '(') skipToMatch("()"); + } + } +} + +static void readIdentifier(tokenInfo *const token, const int firstChar) { + vString *const name = token->name; + int c = firstChar; + boolean first = TRUE; + + initToken(token); + + /* Bug #1585745: strangely, C++ destructors allow whitespace between + * the ~ and the class name. */ + if (isLanguage(Lang_cpp) && firstChar == '~') { + vStringPut(name, c); + c = skipToNonWhite(); + } + + do { + vStringPut(name, c); + if (CollectingSignature) { + if (!first) vStringPut(Signature, c); + first = FALSE; + } + c = cppGetc(); + } while (isident(c) || ((isLanguage(Lang_java) || isLanguage(Lang_csharp)) && + (isHighChar(c) || c == '.'))); + vStringTerminate(name); + cppUngetc(c); /* unget non-identifier character */ + + analyzeIdentifier(token); +} + +static void readPackageName(tokenInfo *const token, const int firstChar) { + vString *const name = token->name; + int c = firstChar; + + initToken(token); + + while (isident(c) || c == '.') { + vStringPut(name, c); + c = cppGetc(); + } + vStringTerminate(name); + cppUngetc(c); /* unget non-package character */ +} + +static void readPackageOrNamespace(statementInfo *const st, + const declType declaration) { + st->declaration = declaration; + + if (declaration == DECL_NAMESPACE && !isLanguage(Lang_csharp)) { + /* In C++ a namespace is specified one level at a time. */ + return; + } else { + /* In C#, a namespace can also be specified like a Java package name. */ + tokenInfo *const token = activeToken(st); + Assert(isType(token, TOKEN_KEYWORD)); + readPackageName(token, skipToNonWhite()); + token->type = TOKEN_NAME; + st->gotName = TRUE; + st->haveQualifyingName = TRUE; + } +} + +static void processName(statementInfo *const st) { + Assert(isType(activeToken(st), TOKEN_NAME)); + if (st->gotName && st->declaration == DECL_NONE) st->declaration = DECL_BASE; + st->gotName = TRUE; + st->haveQualifyingName = TRUE; +} + +static void readOperator(statementInfo *const st) { + const char *const acceptable = "+-*/%^&|~!=<>,[]"; + const tokenInfo *const prev = prevToken(st, 1); + tokenInfo *const token = activeToken(st); + vString *const name = token->name; + int c = skipToNonWhite(); + + /* When we arrive here, we have the keyword "operator" in 'name'. + */ + if (isType(prev, TOKEN_KEYWORD) && + (prev->keyword == KEYWORD_ENUM || prev->keyword == KEYWORD_STRUCT || + prev->keyword == KEYWORD_UNION)) + ; /* ignore "operator" keyword if preceded by these keywords */ + else if (c == '(') { + /* Verify whether this is a valid function call (i.e. "()") operator. + */ + if (cppGetc() == ')') { + vStringPut(name, ' '); /* always separate operator from keyword */ + c = skipToNonWhite(); + if (c == '(') vStringCatS(name, "()"); + } else { + skipToMatch("()"); + c = cppGetc(); + } + } else if (isident1(c)) { + /* Handle "new" and "delete" operators, and conversion functions + * (per 13.3.1.1.2 [2] of the C++ spec). + */ + boolean whiteSpace = TRUE; /* default causes insertion of space */ + do { + if (isspace(c)) + whiteSpace = TRUE; + else { + if (whiteSpace) { + vStringPut(name, ' '); + whiteSpace = FALSE; + } + vStringPut(name, c); + } + c = cppGetc(); + } while (!isOneOf(c, "(;") && c != EOF); + vStringTerminate(name); + } else if (isOneOf(c, acceptable)) { + vStringPut(name, ' '); /* always separate operator from keyword */ + do { + vStringPut(name, c); + c = cppGetc(); + } while (isOneOf(c, acceptable)); + vStringTerminate(name); + } + + cppUngetc(c); + + token->type = TOKEN_NAME; + token->keyword = KEYWORD_NONE; + processName(st); +} + +static void copyToken(tokenInfo *const dest, const tokenInfo *const src) { + dest->type = src->type; + dest->keyword = src->keyword; + dest->filePosition = src->filePosition; + dest->lineNumber = src->lineNumber; + vStringCopy(dest->name, src->name); +} + +static void setAccess(statementInfo *const st, const accessType access) { + if (isMember(st)) { + if (isLanguage(Lang_cpp)) { + int c = skipToNonWhite(); + + if (c == ':') + reinitStatement(st, FALSE); + else + cppUngetc(c); + + st->member.accessDefault = access; + } + st->member.access = access; + } +} + +static void discardTypeList(tokenInfo *const token) { + int c = skipToNonWhite(); + while (isident1(c)) { + readIdentifier(token, c); + c = skipToNonWhite(); + if (c == '.' || c == ',') c = skipToNonWhite(); + } + cppUngetc(c); +} + +static void addParentClass(statementInfo *const st, tokenInfo *const token) { + if (vStringLength(token->name) > 0 && vStringLength(st->parentClasses) > 0) { + vStringPut(st->parentClasses, ','); + } + vStringCat(st->parentClasses, token->name); +} + +static void readParents(statementInfo *const st, const int qualifier) { + tokenInfo *const token = newToken(); + tokenInfo *const parent = newToken(); + int c; + + do { + c = skipToNonWhite(); + if (isident1(c)) { + readIdentifier(token, c); + if (isType(token, TOKEN_NAME)) + vStringCat(parent->name, token->name); + else { + addParentClass(st, parent); + initToken(parent); + } + } else if (c == qualifier) + vStringPut(parent->name, c); + else if (c == '<') + skipToMatch("<>"); + else if (isType(token, TOKEN_NAME)) { + addParentClass(st, parent); + initToken(parent); + } + } while (c != '{' && c != EOF); + cppUngetc(c); + deleteToken(parent); + deleteToken(token); +} + +static void skipStatement(statementInfo *const st) { + st->declaration = DECL_IGNORE; + skipToOneOf(";"); +} + +static void processInterface(statementInfo *const st) { + st->declaration = DECL_INTERFACE; +} + +static void processToken(tokenInfo *const token, statementInfo *const st) { + switch (token->keyword) /* is it a reserved word? */ + { + default: + break; + + case KEYWORD_NONE: + processName(st); + break; + case KEYWORD_ABSTRACT: + st->implementation = IMP_ABSTRACT; + break; + case KEYWORD_ATTRIBUTE: + skipParens(); + initToken(token); + break; + case KEYWORD_BIND: + st->declaration = DECL_BASE; + break; + case KEYWORD_BIT: + st->declaration = DECL_BASE; + break; + case KEYWORD_CATCH: + skipParens(); + skipBraces(); + break; + case KEYWORD_CHAR: + st->declaration = DECL_BASE; + break; + case KEYWORD_CLASS: + st->declaration = DECL_CLASS; + break; + case KEYWORD_CONST: + st->declaration = DECL_BASE; + break; + case KEYWORD_DOUBLE: + st->declaration = DECL_BASE; + break; + case KEYWORD_ENUM: + st->declaration = DECL_ENUM; + break; + case KEYWORD_EXTENDS: + readParents(st, '.'); + setToken(st, TOKEN_NONE); + break; + case KEYWORD_FLOAT: + st->declaration = DECL_BASE; + break; + case KEYWORD_FUNCTION: + st->declaration = DECL_BASE; + break; + case KEYWORD_FRIEND: + st->scope = SCOPE_FRIEND; + break; + case KEYWORD_GOTO: + skipStatement(st); + break; + case KEYWORD_IMPLEMENTS: + readParents(st, '.'); + setToken(st, TOKEN_NONE); + break; + case KEYWORD_IMPORT: + skipStatement(st); + break; + case KEYWORD_INT: + st->declaration = DECL_BASE; + break; + case KEYWORD_INTEGER: + st->declaration = DECL_BASE; + break; + case KEYWORD_INTERFACE: + processInterface(st); + break; + case KEYWORD_LOCAL: + setAccess(st, ACCESS_LOCAL); + break; + case KEYWORD_LONG: + st->declaration = DECL_BASE; + break; + case KEYWORD_OPERATOR: + readOperator(st); + break; + case KEYWORD_PRIVATE: + setAccess(st, ACCESS_PRIVATE); + break; + case KEYWORD_PROGRAM: + st->declaration = DECL_PROGRAM; + break; + case KEYWORD_PROTECTED: + setAccess(st, ACCESS_PROTECTED); + break; + case KEYWORD_PUBLIC: + setAccess(st, ACCESS_PUBLIC); + break; + case KEYWORD_RETURN: + skipStatement(st); + break; + case KEYWORD_SHORT: + st->declaration = DECL_BASE; + break; + case KEYWORD_SIGNED: + st->declaration = DECL_BASE; + break; + case KEYWORD_STRING: + st->declaration = DECL_BASE; + break; + case KEYWORD_STRUCT: + st->declaration = DECL_STRUCT; + break; + case KEYWORD_TASK: + st->declaration = DECL_TASK; + break; + case KEYWORD_THROWS: + discardTypeList(token); + break; + case KEYWORD_UNION: + st->declaration = DECL_UNION; + break; + case KEYWORD_UNSIGNED: + st->declaration = DECL_BASE; + break; + case KEYWORD_USING: + skipStatement(st); + break; + case KEYWORD_VOID: + st->declaration = DECL_BASE; + break; + case KEYWORD_VOLATILE: + st->declaration = DECL_BASE; + break; + case KEYWORD_VIRTUAL: + st->implementation = IMP_VIRTUAL; + break; + case KEYWORD_WCHAR_T: + st->declaration = DECL_BASE; + break; + + case KEYWORD_NAMESPACE: + readPackageOrNamespace(st, DECL_NAMESPACE); + break; + case KEYWORD_PACKAGE: + readPackageOrNamespace(st, DECL_PACKAGE); + break; + + case KEYWORD_EVENT: + if (isLanguage(Lang_csharp)) st->declaration = DECL_EVENT; + break; + + case KEYWORD_TYPEDEF: + reinitStatement(st, FALSE); + st->scope = SCOPE_TYPEDEF; + break; + + case KEYWORD_EXTERN: + if (!isLanguage(Lang_csharp) || !st->gotName) { + reinitStatement(st, FALSE); + st->scope = SCOPE_EXTERN; + st->declaration = DECL_BASE; + } + break; + + case KEYWORD_STATIC: + if (!(isLanguage(Lang_java) || isLanguage(Lang_csharp))) { + reinitStatement(st, FALSE); + st->scope = SCOPE_STATIC; + st->declaration = DECL_BASE; + } + break; + + case KEYWORD_FOR: + case KEYWORD_FOREACH: + case KEYWORD_IF: + case KEYWORD_SWITCH: + case KEYWORD_WHILE: { + int c = skipToNonWhite(); + if (c == '(') skipToMatch("()"); + break; + } + } +} + +/* + * Parenthesis handling functions + */ + +static void restartStatement(statementInfo *const st) { + tokenInfo *const save = newToken(); + tokenInfo *token = activeToken(st); + + copyToken(save, token); + DebugStatement(if (debug(DEBUG_PARSE)) printf("");) + reinitStatement(st, FALSE); + token = activeToken(st); + copyToken(token, save); + deleteToken(save); + processToken(token, st); +} + +/* Skips over a the mem-initializer-list of a ctor-initializer, defined as: + * + * mem-initializer-list: + * mem-initializer, mem-initializer-list + * + * mem-initializer: + * [::] [nested-name-spec] class-name (...) + * identifier + */ +static void skipMemIntializerList(tokenInfo *const token) { + int c; + + do { + c = skipToNonWhite(); + while (isident1(c) || c == ':') { + if (c != ':') readIdentifier(token, c); + c = skipToNonWhite(); + } + if (c == '<') { + skipToMatch("<>"); + c = skipToNonWhite(); + } + if (c == '(') { + skipToMatch("()"); + c = skipToNonWhite(); + } + } while (c == ','); + cppUngetc(c); +} + +static void skipMacro(statementInfo *const st) { + tokenInfo *const prev2 = prevToken(st, 2); + + if (isType(prev2, TOKEN_NAME)) retardToken(st); + skipToMatch("()"); +} + +/* Skips over characters following the parameter list. This will be either + * non-ANSI style function declarations or C++ stuff. Our choices: + * + * C (K&R): + * int func (); + * int func (one, two) int one; float two; {...} + * C (ANSI): + * int func (int one, float two); + * int func (int one, float two) {...} + * C++: + * int foo (...) [const|volatile] [throw (...)]; + * int foo (...) [const|volatile] [throw (...)] [ctor-initializer] {...} + * int foo (...) [const|volatile] [throw (...)] try [ctor-initializer] {...} + * catch (...) {...} + */ +static boolean skipPostArgumentStuff(statementInfo *const st, + parenInfo *const info) { + tokenInfo *const token = activeToken(st); + unsigned int parameters = info->parameterCount; + unsigned int elementCount = 0; + boolean restart = FALSE; + boolean end = FALSE; + int c = skipToNonWhite(); + + do { + switch (c) { + case ')': + break; + case ':': + skipMemIntializerList(token); + break; /* ctor-initializer */ + case '[': + skipToMatch("[]"); + break; + case '=': + cppUngetc(c); + end = TRUE; + break; + case '{': + cppUngetc(c); + end = TRUE; + break; + case '}': + cppUngetc(c); + end = TRUE; + break; + + case '(': + if (elementCount > 0) ++elementCount; + skipToMatch("()"); + break; + + case ';': + if (parameters == 0 || elementCount < 2) { + cppUngetc(c); + end = TRUE; + } else if (--parameters == 0) + end = TRUE; + break; + + default: + if (isident1(c)) { + readIdentifier(token, c); + switch (token->keyword) { + case KEYWORD_ATTRIBUTE: + skipParens(); + break; + case KEYWORD_THROW: + skipParens(); + break; + case KEYWORD_TRY: + break; + + case KEYWORD_CONST: + case KEYWORD_VOLATILE: + if (vStringLength(Signature) > 0) { + vStringPut(Signature, ' '); + vStringCat(Signature, token->name); + } + break; + + case KEYWORD_CATCH: + case KEYWORD_CLASS: + case KEYWORD_EXPLICIT: + case KEYWORD_EXTERN: + case KEYWORD_FRIEND: + case KEYWORD_INLINE: + case KEYWORD_MUTABLE: + case KEYWORD_NAMESPACE: + case KEYWORD_NEW: + case KEYWORD_NEWCOV: + case KEYWORD_OPERATOR: + case KEYWORD_OVERLOAD: + case KEYWORD_PRIVATE: + case KEYWORD_PROTECTED: + case KEYWORD_PUBLIC: + case KEYWORD_STATIC: + case KEYWORD_TEMPLATE: + case KEYWORD_TYPEDEF: + case KEYWORD_TYPENAME: + case KEYWORD_USING: + case KEYWORD_VIRTUAL: + /* Never allowed within parameter declarations. */ + restart = TRUE; + end = TRUE; + break; + + default: + if (isType(token, TOKEN_NONE)) + ; + else if (info->isKnrParamList && info->parameterCount > 0) + ++elementCount; + else { + /* If we encounter any other identifier immediately + * following an empty parameter list, this is almost + * certainly one of those Microsoft macro "thingies" + * that the automatic source code generation sticks + * in. Terminate the current statement. + */ + restart = TRUE; + end = TRUE; + } + break; + } + } + } + if (!end) { + c = skipToNonWhite(); + if (c == EOF) end = TRUE; + } + } while (!end); + + if (restart) + restartStatement(st); + else + setToken(st, TOKEN_NONE); + + return (boolean)(c != EOF); +} + +static void skipJavaThrows(statementInfo *const st) { + tokenInfo *const token = activeToken(st); + int c = skipToNonWhite(); + + if (isident1(c)) { + readIdentifier(token, c); + if (token->keyword == KEYWORD_THROWS) { + do { + c = skipToNonWhite(); + if (isident1(c)) { + readIdentifier(token, c); + c = skipToNonWhite(); + } + } while (c == '.' || c == ','); + } + } + cppUngetc(c); + setToken(st, TOKEN_NONE); +} + +static void analyzePostParens(statementInfo *const st, parenInfo *const info) { + const unsigned long inputLineNumber = getInputLineNumber(); + int c = skipToNonWhite(); + + cppUngetc(c); + if (isOneOf(c, "{;,=")) + ; + else if (isLanguage(Lang_java)) + skipJavaThrows(st); + else { + if (!skipPostArgumentStuff(st, info)) { + verbose("%s: confusing argument declarations beginning at line %lu\n", + getInputFileName(), inputLineNumber); + longjmp(Exception, (int)ExceptionFormattingError); + } + } +} + +static boolean languageSupportsGenerics(void) { + return (boolean)(isLanguage(Lang_cpp) || isLanguage(Lang_csharp) || + isLanguage(Lang_java)); +} + +static void processAngleBracket(void) { + int c = cppGetc(); + if (c == '>') { + /* already found match for template */ + } else if (languageSupportsGenerics() && c != '<' && c != '=') { + /* this is a template */ + cppUngetc(c); + skipToMatch("<>"); + } else if (c == '<') { + /* skip "<<" or "<<=". */ + c = cppGetc(); + if (c != '=') { + cppUngetc(c); + } + } else { + cppUngetc(c); + } +} + +static void parseJavaAnnotation(statementInfo *const st) { + /* + * @Override + * @Target(ElementType.METHOD) + * @SuppressWarnings(value = "unchecked") + * + * But watch out for "@interface"! + */ + tokenInfo *const token = activeToken(st); + + int c = skipToNonWhite(); + readIdentifier(token, c); + if (token->keyword == KEYWORD_INTERFACE) { + /* Oops. This was actually "@interface" defining a new annotation. */ + processInterface(st); + } else { + /* Bug #1691412: skip any annotation arguments. */ + skipParens(); + } +} + +static int parseParens(statementInfo *const st, parenInfo *const info) { + tokenInfo *const token = activeToken(st); + unsigned int identifierCount = 0; + unsigned int depth = 1; + boolean firstChar = TRUE; + int nextChar = '\0'; + + CollectingSignature = TRUE; + vStringClear(Signature); + vStringPut(Signature, '('); + info->parameterCount = 1; + do { + int c = skipToNonWhite(); + vStringPut(Signature, c); + + switch (c) { + case '&': + case '*': + info->isPointer = TRUE; + info->isKnrParamList = FALSE; + if (identifierCount == 0) info->isParamList = FALSE; + initToken(token); + break; + + case ':': + info->isKnrParamList = FALSE; + break; + + case '.': + info->isNameCandidate = FALSE; + c = cppGetc(); + if (c != '.') { + cppUngetc(c); + info->isKnrParamList = FALSE; + } else { + c = cppGetc(); + if (c != '.') { + cppUngetc(c); + info->isKnrParamList = FALSE; + } else + vStringCatS(Signature, "..."); /* variable arg list */ + } + break; + + case ',': + info->isNameCandidate = FALSE; + if (info->isKnrParamList) { + ++info->parameterCount; + identifierCount = 0; + } + break; + + case '=': + info->isKnrParamList = FALSE; + info->isNameCandidate = FALSE; + if (firstChar) { + info->isParamList = FALSE; + skipMacro(st); + depth = 0; + } + break; + + case '[': + info->isKnrParamList = FALSE; + skipToMatch("[]"); + break; + + case '<': + info->isKnrParamList = FALSE; + processAngleBracket(); + break; + + case ')': + if (firstChar) info->parameterCount = 0; + --depth; + break; + + case '(': + info->isKnrParamList = FALSE; + if (firstChar) { + info->isNameCandidate = FALSE; + cppUngetc(c); + vStringClear(Signature); + skipMacro(st); + depth = 0; + vStringChop(Signature); + } else if (isType(token, TOKEN_PAREN_NAME)) { + c = skipToNonWhite(); + if (c == '*') /* check for function pointer */ + { + skipToMatch("()"); + c = skipToNonWhite(); + if (c == '(') + skipToMatch("()"); + else + cppUngetc(c); + } else { + cppUngetc(c); + cppUngetc('('); + info->nestedArgs = TRUE; + } + } else + ++depth; + break; + + default: + if (c == '@' && isLanguage(Lang_java)) { + parseJavaAnnotation(st); + } else if (isident1(c)) { + if (++identifierCount > 1) info->isKnrParamList = FALSE; + readIdentifier(token, c); + if (isType(token, TOKEN_NAME) && info->isNameCandidate) + token->type = TOKEN_PAREN_NAME; + else if (isType(token, TOKEN_KEYWORD)) { + if (token->keyword != KEYWORD_CONST && + token->keyword != KEYWORD_VOLATILE) { + info->isKnrParamList = FALSE; + info->isNameCandidate = FALSE; + } + } + } else { + info->isParamList = FALSE; + info->isKnrParamList = FALSE; + info->isNameCandidate = FALSE; + info->invalidContents = TRUE; + } + break; + } + firstChar = FALSE; + } while (!info->nestedArgs && depth > 0 && + (info->isKnrParamList || info->isNameCandidate)); + + if (!info->nestedArgs) + while (depth > 0) { + skipToMatch("()"); + --depth; + } + + if (!info->isNameCandidate) initToken(token); + + vStringTerminate(Signature); + if (info->isKnrParamList) vStringClear(Signature); + CollectingSignature = FALSE; + return nextChar; +} + +static void initParenInfo(parenInfo *const info) { + info->isPointer = FALSE; + info->isParamList = TRUE; + info->isKnrParamList = isLanguage(Lang_c); + info->isNameCandidate = TRUE; + info->invalidContents = FALSE; + info->nestedArgs = FALSE; + info->parameterCount = 0; +} + +static void analyzeParens(statementInfo *const st) { + tokenInfo *const prev = prevToken(st, 1); + + if (st->inFunction && !st->assignment) st->notVariable = TRUE; + if (!isType(prev, TOKEN_NONE)) /* in case of ignored enclosing macros */ + { + tokenInfo *const token = activeToken(st); + parenInfo info; + int c; + + initParenInfo(&info); + parseParens(st, &info); + c = skipToNonWhite(); + cppUngetc(c); + if (info.invalidContents) + reinitStatement(st, FALSE); + else if (info.isNameCandidate && isType(token, TOKEN_PAREN_NAME) && + !st->gotParenName && + (!info.isParamList || !st->haveQualifyingName || c == '(' || + (c == '=' && st->implementation != IMP_VIRTUAL) || + (st->declaration == DECL_NONE && isOneOf(c, ",;")))) { + token->type = TOKEN_NAME; + processName(st); + st->gotParenName = TRUE; + if (!(c == '(' && info.nestedArgs)) st->isPointer = info.isPointer; + } else if (!st->gotArgs && info.isParamList) { + st->gotArgs = TRUE; + setToken(st, TOKEN_ARGS); + advanceToken(st); + if (st->scope != SCOPE_TYPEDEF) analyzePostParens(st, &info); + } else + setToken(st, TOKEN_NONE); + } +} + +/* + * Token parsing functions + */ + +static void addContext(statementInfo *const st, const tokenInfo *const token) { + if (isType(token, TOKEN_NAME)) { + if (vStringLength(st->context->name) > 0) { + if (isLanguage(Lang_c) || isLanguage(Lang_cpp)) + vStringCatS(st->context->name, "::"); + else if (isLanguage(Lang_java) || isLanguage(Lang_csharp)) + vStringCatS(st->context->name, "."); + } + vStringCat(st->context->name, token->name); + st->context->type = TOKEN_NAME; + } +} + +static boolean inheritingDeclaration(declType decl) { + /* C# supports inheritance for enums. C++0x will too, but not yet. */ + if (decl == DECL_ENUM) { + return (boolean)(isLanguage(Lang_csharp)); + } + return (boolean)(decl == DECL_CLASS || decl == DECL_STRUCT || + decl == DECL_INTERFACE); +} + +static void processColon(statementInfo *const st) { + int c = (isLanguage(Lang_cpp) ? cppGetc() : skipToNonWhite()); + const boolean doubleColon = (boolean)(c == ':'); + + if (doubleColon) { + setToken(st, TOKEN_DOUBLE_COLON); + st->haveQualifyingName = FALSE; + } else { + cppUngetc(c); + if ((isLanguage(Lang_cpp) || isLanguage(Lang_csharp)) && + inheritingDeclaration(st->declaration)) { + readParents(st, ':'); + } else if (parentDecl(st) == DECL_STRUCT) { + c = skipToOneOf(",;"); + if (c == ',') + setToken(st, TOKEN_COMMA); + else if (c == ';') + setToken(st, TOKEN_SEMICOLON); + } else { + const tokenInfo *const prev = prevToken(st, 1); + const tokenInfo *const prev2 = prevToken(st, 2); + if (prev->keyword == KEYWORD_DEFAULT || prev2->keyword == KEYWORD_CASE || + st->parent != NULL) { + reinitStatement(st, FALSE); + } + } + } +} + +/* Skips over any initializing value which may follow an '=' character in a + * variable definition. + */ +static int skipInitializer(statementInfo *const st) { + boolean done = FALSE; + int c; + + while (!done) { + c = skipToNonWhite(); + + if (c == EOF) + longjmp(Exception, (int)ExceptionFormattingError); + else + switch (c) { + case ',': + case ';': + done = TRUE; + break; + + case '0': + if (st->implementation == IMP_VIRTUAL) + st->implementation = IMP_PURE_VIRTUAL; + break; + + case '[': + skipToMatch("[]"); + break; + case '(': + skipToMatch("()"); + break; + case '{': + skipToMatch("{}"); + break; + case '<': + processAngleBracket(); + break; + + case '}': + if (insideEnumBody(st)) + done = TRUE; + else if (!isBraceFormat()) { + verbose("%s: unexpected closing brace at line %lu\n", + getInputFileName(), getInputLineNumber()); + longjmp(Exception, (int)ExceptionBraceFormattingError); + } + break; + + default: + break; + } + } + return c; +} + +static void processInitializer(statementInfo *const st) { + const boolean inEnumBody = insideEnumBody(st); + int c = cppGetc(); + + if (c != '=') { + cppUngetc(c); + c = skipInitializer(st); + st->assignment = TRUE; + if (c == ';') + setToken(st, TOKEN_SEMICOLON); + else if (c == ',') + setToken(st, TOKEN_COMMA); + else if (c == '}' && inEnumBody) { + cppUngetc(c); + setToken(st, TOKEN_COMMA); + } + if (st->scope == SCOPE_EXTERN) st->scope = SCOPE_GLOBAL; + } +} + +static void parseIdentifier(statementInfo *const st, const int c) { + tokenInfo *const token = activeToken(st); + + readIdentifier(token, c); + if (!isType(token, TOKEN_NONE)) processToken(token, st); +} + +static void parseGeneralToken(statementInfo *const st, const int c) { + const tokenInfo *const prev = prevToken(st, 1); + + if (isident1(c) || (isLanguage(Lang_java) && isHighChar(c))) { + parseIdentifier(st, c); + if (isType(st->context, TOKEN_NAME) && + isType(activeToken(st), TOKEN_NAME) && isType(prev, TOKEN_NAME)) { + initToken(st->context); + } + } else if (c == '.' || c == '-') { + if (!st->assignment) st->notVariable = TRUE; + if (c == '-') { + int c2 = cppGetc(); + if (c2 != '>') cppUngetc(c2); + } + } else if (c == '!' || c == '>') { + int c2 = cppGetc(); + if (c2 != '=') cppUngetc(c2); + } else if (c == '@' && isLanguage(Lang_java)) { + parseJavaAnnotation(st); + } else if (isExternCDecl(st, c)) { + st->declaration = DECL_NOMANGLE; + st->scope = SCOPE_GLOBAL; + } +} + +/* Reads characters from the pre-processor and assembles tokens, setting + * the current statement state. + */ +static void nextToken(statementInfo *const st) { + tokenInfo *token; + do { + int c = skipToNonWhite(); + switch (c) { + case EOF: + longjmp(Exception, (int)ExceptionEOF); + break; + case '(': + analyzeParens(st); + break; + case '<': + processAngleBracket(); + break; + case '*': + st->haveQualifyingName = FALSE; + break; + case ',': + setToken(st, TOKEN_COMMA); + break; + case ':': + processColon(st); + break; + case ';': + setToken(st, TOKEN_SEMICOLON); + break; + case '=': + processInitializer(st); + break; + case '[': + skipToMatch("[]"); + break; + case '{': + setToken(st, TOKEN_BRACE_OPEN); + break; + case '}': + setToken(st, TOKEN_BRACE_CLOSE); + break; + default: + parseGeneralToken(st, c); + break; + } + token = activeToken(st); + } while (isType(token, TOKEN_NONE)); +} + +/* + * Scanning support functions + */ + +static statementInfo *CurrentStatement = NULL; + +static statementInfo *newStatement(statementInfo *const parent) { + statementInfo *const st = xMalloc(1, statementInfo); + unsigned int i; + + for (i = 0; i < (unsigned int)NumTokens; ++i) st->token[i] = newToken(); + + st->context = newToken(); + st->blockName = newToken(); + st->parentClasses = vStringNew(); + + initStatement(st, parent); + CurrentStatement = st; + + return st; +} + +static void deleteStatement(void) { + statementInfo *const st = CurrentStatement; + statementInfo *const parent = st->parent; + unsigned int i; + + for (i = 0; i < (unsigned int)NumTokens; ++i) { + deleteToken(st->token[i]); + st->token[i] = NULL; + } + deleteToken(st->blockName); + st->blockName = NULL; + deleteToken(st->context); + st->context = NULL; + vStringDelete(st->parentClasses); + st->parentClasses = NULL; + eFree(st); + CurrentStatement = parent; +} + +static void deleteAllStatements(void) { + while (CurrentStatement != NULL) deleteStatement(); +} + +static boolean isStatementEnd(const statementInfo *const st) { + const tokenInfo *const token = activeToken(st); + boolean isEnd; + + if (isType(token, TOKEN_SEMICOLON)) + isEnd = TRUE; + else if (isType(token, TOKEN_BRACE_CLOSE)) + /* Java and C# do not require semicolons to end a block. Neither do C++ + * namespaces. All other blocks require a semicolon to terminate them. + */ + isEnd = (boolean)(isLanguage(Lang_java) || isLanguage(Lang_csharp) || + !isContextualStatement(st)); + else + isEnd = FALSE; + + return isEnd; +} + +static void checkStatementEnd(statementInfo *const st) { + const tokenInfo *const token = activeToken(st); + + if (isType(token, TOKEN_COMMA)) + reinitStatement(st, TRUE); + else if (isStatementEnd(st)) { + DebugStatement(if (debug(DEBUG_PARSE)) printf("");) + reinitStatement(st, FALSE); + cppEndStatement(); + } else { + cppBeginStatement(); + advanceToken(st); + } +} + +static void nest(statementInfo *const st, const unsigned int nestLevel) { + switch (st->declaration) { + case DECL_CLASS: + case DECL_ENUM: + case DECL_INTERFACE: + case DECL_NAMESPACE: + case DECL_NOMANGLE: + case DECL_STRUCT: + case DECL_UNION: + createTags(nestLevel, st); + break; + + case DECL_FUNCTION: + case DECL_TASK: + st->inFunction = TRUE; + /* fall through */ + default: + if (includeTag(TAG_LOCAL, FALSE)) + createTags(nestLevel, st); + else + skipToMatch("{}"); + break; + } + advanceToken(st); + setToken(st, TOKEN_BRACE_CLOSE); +} + +static void tagCheck(statementInfo *const st) { + const tokenInfo *const token = activeToken(st); + const tokenInfo *const prev = prevToken(st, 1); + const tokenInfo *const prev2 = prevToken(st, 2); + + switch (token->type) { + case TOKEN_NAME: + if (insideEnumBody(st)) qualifyEnumeratorTag(st, token); + break; +#if 0 + case TOKEN_PACKAGE: + if (st->haveQualifyingName) + makeTag (token, st, FALSE, TAG_PACKAGE); + break; +#endif + case TOKEN_BRACE_OPEN: + if (isType(prev, TOKEN_ARGS)) { + if (st->haveQualifyingName) { + if (!isLanguage(Lang_vera)) st->declaration = DECL_FUNCTION; + if (isType(prev2, TOKEN_NAME)) copyToken(st->blockName, prev2); + qualifyFunctionTag(st, prev2); + } + } else if (isContextualStatement(st) || + st->declaration == DECL_NAMESPACE || + st->declaration == DECL_PROGRAM) { + if (isType(prev, TOKEN_NAME)) + copyToken(st->blockName, prev); + else { + /* For an anonymous struct or union we use a unique ID + * a number, so that the members can be found. + */ + char buf[20]; /* length of "_anon" + digits + null */ + sprintf(buf, "__anon%d", ++AnonymousID); + vStringCopyS(st->blockName->name, buf); + st->blockName->type = TOKEN_NAME; + st->blockName->keyword = KEYWORD_NONE; + } + qualifyBlockTag(st, prev); + } else if (isLanguage(Lang_csharp)) + makeTag(prev, st, FALSE, TAG_PROPERTY); + break; + + case TOKEN_SEMICOLON: + case TOKEN_COMMA: + if (insideEnumBody(st)) + ; + else if (isType(prev, TOKEN_NAME)) { + if (isContextualKeyword(prev2)) + makeTag(prev, st, TRUE, TAG_EXTERN_VAR); + else + qualifyVariableTag(st, prev); + } else if (isType(prev, TOKEN_ARGS) && isType(prev2, TOKEN_NAME)) { + if (st->isPointer) + qualifyVariableTag(st, prev2); + else + qualifyFunctionDeclTag(st, prev2); + } + if (isLanguage(Lang_java) && token->type == TOKEN_SEMICOLON && + insideEnumBody(st)) { + /* In Java, after an initial enum-like part, + * a semicolon introduces a class-like part. + * See Bug #1730485 for the full rationale. */ + st->parent->declaration = DECL_CLASS; + } + break; + + default: + break; + } +} + +/* Parses the current file and decides whether to write out and tags that + * are discovered. + */ +static void createTags(const unsigned int nestLevel, + statementInfo *const parent) { + statementInfo *const st = newStatement(parent); + + DebugStatement(if (nestLevel > 0) + debugParseNest(TRUE, nestLevel);) while (TRUE) { + tokenInfo *token; + + nextToken(st); + token = activeToken(st); + if (isType(token, TOKEN_BRACE_CLOSE)) { + if (nestLevel > 0) + break; + else { + verbose("%s: unexpected closing brace at line %lu\n", + getInputFileName(), getInputLineNumber()); + longjmp(Exception, (int)ExceptionBraceFormattingError); + } + } else if (isType(token, TOKEN_DOUBLE_COLON)) { + addContext(st, prevToken(st, 1)); + advanceToken(st); + } else { + tagCheck(st); + if (isType(token, TOKEN_BRACE_OPEN)) nest(st, nestLevel + 1); + checkStatementEnd(st); + } + } + deleteStatement(); + DebugStatement(if (nestLevel > 0) debugParseNest(FALSE, nestLevel - 1);) +} + +static boolean findCTags(const unsigned int passCount) { + exception_t exception; + boolean retry; + + Assert(passCount < 3); + cppInit((boolean)(passCount > 1), isLanguage(Lang_csharp)); + Signature = vStringNew(); + + exception = (exception_t)setjmp(Exception); + retry = FALSE; + if (exception == ExceptionNone) + createTags(0, NULL); + else { + deleteAllStatements(); + if (exception == ExceptionBraceFormattingError && passCount == 1) { + retry = TRUE; + verbose("%s: retrying file with fallback brace matching algorithm\n", + getInputFileName()); + } + } + vStringDelete(Signature); + cppTerminate(); + return retry; +} + +static void buildKeywordHash(const langType language, unsigned int idx) { + const size_t count = sizeof(KeywordTable) / sizeof(KeywordTable[0]); + size_t i; + for (i = 0; i < count; ++i) { + const keywordDesc *const p = &KeywordTable[i]; + if (p->isValid[idx]) addKeyword(p->name, language, (int)p->id); + } +} + +static void initializeCParser(const langType language) { + Lang_c = language; + buildKeywordHash(language, 0); +} + +static void initializeCppParser(const langType language) { + Lang_cpp = language; + buildKeywordHash(language, 1); +} + +static void initializeCsharpParser(const langType language) { + Lang_csharp = language; + buildKeywordHash(language, 2); +} + +static void initializeJavaParser(const langType language) { + Lang_java = language; + buildKeywordHash(language, 3); +} + +static void initializeVeraParser(const langType language) { + Lang_vera = language; + buildKeywordHash(language, 4); +} + +extern parserDefinition *CParser(void) { + static const char *const extensions[] = {"c", NULL}; + parserDefinition *def = parserNew("C"); + def->kinds = CKinds; + def->kindCount = KIND_COUNT(CKinds); + def->extensions = extensions; + def->parser2 = findCTags; + def->initialize = initializeCParser; + return def; +} + +extern parserDefinition *CppParser(void) { + static const char *const extensions[] = {"c++", "cc", "cp", "cpp", "cxx", "h", + "h++", "hh", "hp", "hpp", "hxx", +#ifndef CASE_INSENSITIVE_FILENAMES + "C", "H", +#endif + NULL}; + parserDefinition *def = parserNew("C++"); + def->kinds = CKinds; + def->kindCount = KIND_COUNT(CKinds); + def->extensions = extensions; + def->parser2 = findCTags; + def->initialize = initializeCppParser; + return def; +} + +extern parserDefinition *CsharpParser(void) { + static const char *const extensions[] = {"cs", NULL}; + parserDefinition *def = parserNew("C#"); + def->kinds = CsharpKinds; + def->kindCount = KIND_COUNT(CsharpKinds); + def->extensions = extensions; + def->parser2 = findCTags; + def->initialize = initializeCsharpParser; + return def; +} + +extern parserDefinition *JavaParser(void) { + static const char *const extensions[] = {"java", NULL}; + parserDefinition *def = parserNew("Java"); + def->kinds = JavaKinds; + def->kindCount = KIND_COUNT(JavaKinds); + def->extensions = extensions; + def->parser2 = findCTags; + def->initialize = initializeJavaParser; + return def; +} + +extern parserDefinition *VeraParser(void) { + static const char *const extensions[] = {"vr", "vri", "vrh", NULL}; + parserDefinition *def = parserNew("Vera"); + def->kinds = VeraKinds; + def->kindCount = KIND_COUNT(VeraKinds); + def->extensions = extensions; + def->parser2 = findCTags; + def->initialize = initializeVeraParser; + return def; +} + +/* vi:set tabstop=4 shiftwidth=4 noexpandtab: */ diff --git a/third_party/ctags/cobol.c b/third_party/ctags/cobol.c new file mode 100644 index 00000000..3ee9b4b7 --- /dev/null +++ b/third_party/ctags/cobol.c @@ -0,0 +1,47 @@ +/* + * $Id: cobol.c 443 2006-05-30 04:37:13Z darren $ + * + * Copyright (c) 2000-2003, Darren Hiebert + * + * This source code is released for free distribution under the terms of the + * GNU General Public License. + * + * This module contains functions for generating tags for COBOL language + * files. + */ +#include "third_party/ctags/general.h" +/* must always come first */ +#include "third_party/ctags/parse.h" + +/* + * FUNCTION DEFINITIONS + */ + +static void installCobolRegex(const langType language) { + addTagRegex( + language, + "^[ \t]*[0-9]+[ \t]+([A-Z0-9][A-Z0-9-]*)[ " + "\t]+(BLANK|OCCURS|IS|JUST|PIC|REDEFINES|RENAMES|SIGN|SYNC|USAGE|VALUE)", + "\\1", "d,data,data items", "i"); + addTagRegex(language, "^[ \t]*[FSR]D[ \t]+([A-Z0-9][A-Z0-9-]*)\\.", "\\1", + "f,file,file descriptions (FD, SD, RD)", "i"); + addTagRegex(language, "^[ \t]*[0-9]+[ \t]+([A-Z0-9][A-Z0-9-]*)\\.", "\\1", + "g,group,group items", "i"); + addTagRegex(language, "^[ \t]*([A-Z0-9][A-Z0-9-]*)\\.", "\\1", + "p,paragraph,paragraphs", "i"); + addTagRegex(language, "^[ \t]*PROGRAM-ID\\.[ \t]+([A-Z0-9][A-Z0-9-]*)\\.", + "\\1", "P,program,program ids", "i"); + addTagRegex(language, "^[ \t]*([A-Z0-9][A-Z0-9-]*)[ \t]+SECTION\\.", "\\1", + "s,section,sections", "i"); +} + +extern parserDefinition* CobolParser() { + static const char* const extensions[] = {"cbl", "cob", "CBL", "COB", NULL}; + parserDefinition* def = parserNew("Cobol"); + def->extensions = extensions; + def->initialize = installCobolRegex; + def->regex = TRUE; + return def; +} + +/* vi:set tabstop=4 shiftwidth=4: */ diff --git a/third_party/ctags/config.h b/third_party/ctags/config.h new file mode 100644 index 00000000..552c69f3 --- /dev/null +++ b/third_party/ctags/config.h @@ -0,0 +1,283 @@ +/* config.h. Generated from config.h.in by configure. */ +/* config.h.in. Generated from configure.ac by autoheader. */ + +/* Define this label if your system uses case-insensitive file names */ +/* #undef CASE_INSENSITIVE_FILENAMES */ + +/* Define this label if you wish to check the regcomp() function at run time + for correct behavior. This function is currently broken on Cygwin. */ +/* #undef CHECK_REGCOMP */ + +/* You can define this label to be a string containing the name of a + site-specific configuration file containing site-wide default options. The + files /etc/ctags.conf and /usr/local/etc/ctags.conf are already checked, so + only define one here if you need a file somewhere else. */ +/* #undef CUSTOM_CONFIGURATION_FILE */ + +/* Define this as desired. + * 1: Original ctags format + * 2: Extended ctags format with extension flags in EX-style comment. + */ +#define DEFAULT_FILE_FORMAT 2 + +/* Define this label to use the system sort utility (which is probably more + * efficient) over the internal sorting algorithm. + */ +#ifndef INTERNAL_SORT +#define EXTERNAL_SORT 1 +#endif + +/* Define to 1 if you have the `chmod' function. */ +/* #undef HAVE_CHMOD */ + +/* Define to 1 if you have the `chsize' function. */ +/* #undef HAVE_CHSIZE */ + +/* Define to 1 if you have the `clock' function. */ +#define HAVE_CLOCK 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_DIRENT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_FCNTL_H 1 + +/* Define to 1 if you have the `fgetpos' function. */ +#define HAVE_FGETPOS 1 + +/* Define to 1 if you have the `findfirst' function. */ +/* #undef HAVE_FINDFIRST */ + +/* Define to 1 if you have the `fnmatch' function. */ +#define HAVE_FNMATCH 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_FNMATCH_H 1 + +/* Define to 1 if you have the `ftruncate' function. */ +/* #undef HAVE_FTRUNCATE */ + +/* Define to 1 if you have the header file. */ +#define HAVE_INTTYPES_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_MEMORY_H 1 + +/* Define to 1 if you have the `mkstemp' function. */ +#define HAVE_MKSTEMP 1 + +/* Define to 1 if you have the `opendir' function. */ +#define HAVE_OPENDIR 1 + +/* Define to 1 if you have the `putenv' function. */ +/* #undef HAVE_PUTENV */ + +/* Define to 1 if you have the `regcomp' function. */ +#define HAVE_REGCOMP 1 + +/* Define to 1 if you have the `remove' function. */ +#define HAVE_REMOVE 1 + +/* Define to 1 if you have the `setenv' function. */ +#define HAVE_SETENV 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_STAT_H */ + +/* Define this macro if the field "st_ino" exists in struct stat in + . */ +#define HAVE_STAT_ST_INO 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDINT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDLIB_H 1 + +/* Define to 1 if you have the `strcasecmp' function. */ +#define HAVE_STRCASECMP 1 + +/* Define to 1 if you have the `strerror' function. */ +#define HAVE_STRERROR 1 + +/* Define to 1 if you have the `stricmp' function. */ +/* #undef HAVE_STRICMP */ + +/* Define to 1 if you have the header file. */ +#define HAVE_STRINGS_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STRING_H 1 + +/* Define to 1 if you have the `strncasecmp' function. */ +#define HAVE_STRNCASECMP 1 + +/* Define to 1 if you have the `strnicmp' function. */ +/* #undef HAVE_STRNICMP */ + +/* Define to 1 if you have the `strstr' function. */ +#define HAVE_STRSTR 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_DIR_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_STAT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_TIMES_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_TYPES_H 1 + +/* Define to 1 if you have the `tempnam' function. */ +/* #undef HAVE_TEMPNAM */ + +/* Define to 1 if you have the `times' function. */ +/* #undef HAVE_TIMES */ + +/* Define to 1 if you have the header file. */ +#define HAVE_TIME_H 1 + +/* Define to 1 if you have the `truncate' function. */ +#define HAVE_TRUNCATE 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_TYPES_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_UNISTD_H 1 + +/* Define to 1 if you have the `_findfirst' function. */ +/* #undef HAVE__FINDFIRST */ + +/* Define as the maximum integer on your system if not defined . */ +/* #undef INT_MAX */ + +/* Define to the appropriate size for tmpnam() if does not define + this. */ +/* #undef L_tmpnam */ + +/* Define this label if you want macro tags (defined lables) to use patterns + in the EX command by default (original ctags behavior is to use line + numbers). */ +/* #undef MACROS_USE_PATTERNS */ + +/* If you receive error or warning messages indicating that you are missing a + prototype for, or a type mismatch using, the following function, define + this label and remake. */ +/* #undef NEED_PROTO_FGETPOS */ + +/* If you receive error or warning messages indicating that you are missing a + prototype for, or a type mismatch using, the following function, define + this label and remake. */ +/* #undef NEED_PROTO_FTRUNCATE */ + +/* If you receive error or warning messages indicating that you are missing a + prototype for, or a type mismatch using, the following function, define + this label and remake. */ +/* #undef NEED_PROTO_GETENV */ + +/* If you receive error or warning messages indicating that you are missing a + prototype for, or a type mismatch using, the following function, define + this label and remake. */ +/* #undef NEED_PROTO_LSTAT */ + +/* If you receive error or warning messages indicating that you are missing a + prototype for, or a type mismatch using, the following function, define + this label and remake. */ +/* #undef NEED_PROTO_MALLOC */ + +/* If you receive error or warning messages indicating that you are missing a + prototype for, or a type mismatch using, the following function, define + this label and remake. */ +/* #undef NEED_PROTO_REMOVE */ + +/* If you receive error or warning messages indicating that you are missing a + prototype for, or a type mismatch using, the following function, define + this label and remake. */ +/* #undef NEED_PROTO_STAT */ + +/* If you receive error or warning messages indicating that you are missing a + prototype for, or a type mismatch using, the following function, define + this label and remake. */ +/* #undef NEED_PROTO_TRUNCATE */ + +/* If you receive error or warning messages indicating that you are missing a + prototype for, or a type mismatch using, the following function, define + this label and remake. */ +/* #undef NEED_PROTO_UNLINK */ + +/* Define this is you have a prototype for putenv() in , but doesn't + declare its argument as "const char *". */ +/* #undef NON_CONST_PUTENV_PROTOTYPE */ + +/* Package name. */ +/* #undef PACKAGE */ + +/* Define to the address where bug reports for this package should be sent. */ +#define PACKAGE_BUGREPORT "" + +/* Define to the full name of this package. */ +#define PACKAGE_NAME "" + +/* Define to the full name and version of this package. */ +#define PACKAGE_STRING "" + +/* Define to the one symbol short name of this package. */ +#define PACKAGE_TARNAME "" + +/* Define to the home page for this package. */ +#define PACKAGE_URL "" + +/* Define to the version of this package. */ +#define PACKAGE_VERSION "" + +/* Define this label if regcomp() is broken. */ +/* #undef REGCOMP_BROKEN */ + +/* Define this value used by fseek() appropriately if (or + on SunOS 4.1.x) does not define them. */ +/* #undef SEEK_SET */ + +/* Define to 1 if you have the ANSI C header files. */ +#define STDC_HEADERS 1 + +/* Define this label if your system supports starting scripts with a line of + the form "#! /bin/sh" to select the interpreter to use for the script. */ +#define SYS_INTERPRETER 1 + +/* If you wish to change the directory in which temporary files are stored, + define this label to the directory desired. */ +#define TMPDIR kTmpPath + +/* Package version. */ +/* #undef VERSION */ + +/* Number of bits in a file offset, on hosts where this is settable. */ +/* #undef _FILE_OFFSET_BITS */ + +/* Define for large files, on AIX-style hosts. */ +/* #undef _LARGE_FILES */ + +/* This corrects the problem of missing prototypes for certain functions in + some GNU installations (e.g. SunOS 4.1.x). */ +/* #undef __USE_FIXED_PROTOTYPES__ */ + +/* Define to the appropriate type if does not define this. */ +/* #undef clock_t */ + +/* Define to empty if `const' does not conform to ANSI C. */ +/* #undef const */ + +/* Define to long if does not define this. */ +/* #undef fpos_t */ + +/* Define to `long int' if does not define. */ +/* #undef off_t */ + +/* Define remove to unlink if you have unlink(), but not remove(). */ +/* #undef remove */ + +/* Define to `unsigned int' if does not define. */ +/* #undef size_t */ diff --git a/third_party/ctags/ctags.h b/third_party/ctags/ctags.h new file mode 100644 index 00000000..685f0220 --- /dev/null +++ b/third_party/ctags/ctags.h @@ -0,0 +1,26 @@ +/* + * $Id: ctags.h 702 2009-03-14 03:52:21Z dhiebert $ + * + * Copyright (c) 1996-2002, Darren Hiebert + * + * This source code is released for free distribution under the terms of the + * GNU General Public License. + * + * Program definitions + */ +#ifndef _CTAGS_H +#define _CTAGS_H + +/* + * MACROS + */ +#ifndef PROGRAM_VERSION +#define PROGRAM_VERSION "5.9~svn20110310" +#endif +#define PROGRAM_NAME "Exuberant Ctags" +#define PROGRAM_URL "http://ctags.sourceforge.net" +#define PROGRAM_COPYRIGHT "Copyright (C) 1996-2009" +#define AUTHOR_NAME "Darren Hiebert" +#define AUTHOR_EMAIL "dhiebert@users.sourceforge.net" + +#endif /* _CTAGS_H */ diff --git a/third_party/ctags/ctags.mk b/third_party/ctags/ctags.mk new file mode 100644 index 00000000..e4ead4cd --- /dev/null +++ b/third_party/ctags/ctags.mk @@ -0,0 +1,61 @@ +#-*-mode:makefile-gmake;indent-tabs-mode:t;tab-width:8;coding:utf-8-*-┐ +#───vi: set et ft=make ts=8 tw=8 fenc=utf-8 :vi───────────────────────┘ + +THIRD_PARTY_CTAGS_FILES := $(wildcard third_party/ctags/*) +THIRD_PARTY_CTAGS_HDRS = $(filter %.h,$(THIRD_PARTY_CTAGS_FILES)) +THIRD_PARTY_CTAGS_SRCS = $(filter %.c,$(THIRD_PARTY_CTAGS_FILES)) + +THIRD_PARTY_CTAGS_BINS = \ + o/$(MODE)/third_party/ctags/ctags.com + +THIRD_PARTY_CTAGS_CHECKS = \ + o/$(MODE)/third_party/ctags/ctags.pkg \ + $(THIRD_PARTY_CTAGS_HDRS:%=o/$(MODE)/%.ok) + +THIRD_PARTY_CTAGS_DIRECTDEPS = \ + LIBC_CALLS \ + LIBC_CALLS_HEFTY \ + LIBC_CONV \ + LIBC_RUNTIME \ + LIBC_MEM \ + LIBC_FMT \ + LIBC_LOG \ + LIBC_TIME \ + LIBC_SYSV \ + LIBC_STR \ + LIBC_STUBS \ + LIBC_STDIO \ + LIBC_NEXGEN32E \ + LIBC_UNICODE \ + THIRD_PARTY_MUSL \ + THIRD_PARTY_REGEX + +THIRD_PARTY_CTAGS_DEPS := \ + $(call uniq,$(foreach x,$(THIRD_PARTY_CTAGS_DIRECTDEPS),$($(x)))) + +o/$(MODE)/third_party/ctags/ctags.srcs.a: \ + third_party/ctags/ \ + $(THIRD_PARTY_CTAGS_SRCS:%=o/$(MODE)/%.zip.o) + +o/$(MODE)/third_party/ctags/ctags.com.dbg: \ + o/$(MODE)/third_party/ctags/ctags.pkg \ + o/$(MODE)/third_party/ctags/ctags.srcs.a \ + $(THIRD_PARTY_CTAGS_DEPS) \ + $(THIRD_PARTY_CTAGS_SRCS:%.c=o/$(MODE)/%.o) \ + $(CRT) \ + $(APE) + @$(APELINK) + +o/$(MODE)/third_party/ctags/ctags.pkg: \ + $(THIRD_PARTY_CTAGS_SRCS:%.c=o/$(MODE)/%.o) \ + o/$(MODE)/third_party/ctags/ctags.srcs.pkg \ + $(foreach x,$(THIRD_PARTY_CTAGS_DIRECTDEPS),$($(x)_A).pkg) + +o/$(MODE)/third_party/ctags/ctags.srcs.pkg: \ + $(THIRD_PARTY_CTAGS_SRCS:%=o/$(MODE)/%.zip.o) \ + $(foreach x,$(THIRD_PARTY_CTAGS_DIRECTDEPS),$($(x)_A).pkg) + +.PHONY: o/$(MODE)/third_party/ctags +o/$(MODE)/third_party/ctags: \ + $(THIRD_PARTY_CTAGS_BINS) \ + $(THIRD_PARTY_CTAGS_CHECKS) diff --git a/third_party/ctags/debug.h b/third_party/ctags/debug.h new file mode 100644 index 00000000..2b13c981 --- /dev/null +++ b/third_party/ctags/debug.h @@ -0,0 +1,53 @@ +#ifndef _DEBUG_H +#define _DEBUG_H +#include "third_party/ctags/general.h" +/* must always come first */ +#include "third_party/ctags/entry.h" + +/* + * Macros + */ + +#ifdef DEBUG +#define debug(level) ((Option.debugLevel & (long)(level)) != 0) +#define DebugStatement(x) x +#define PrintStatus(x) \ + if (debug(DEBUG_STATUS)) printf x; +#define Assert(c) assert(c) +#else +#define DebugStatement(x) +#define PrintStatus(x) +#define Assert(c) +#ifndef NDEBUG +#define NDEBUG +#endif +#endif + +/* + * Data declarations + */ + +/* Defines the debugging levels. + */ +enum eDebugLevels { + DEBUG_READ = 0x01, /* echo raw (filtered) characters */ + DEBUG_PARSE = 0x02, /* echo parsing results */ + DEBUG_STATUS = 0x04, /* echo file status information */ + DEBUG_OPTION = 0x08, /* echo option parsing */ + DEBUG_CPP = 0x10, /* echo characters out of pre-processor */ + DEBUG_RAW = 0x20 /* echo raw (filtered) characters */ +}; + +/* + * Function prototypes + */ +extern void lineBreak(void); +extern void debugPrintf(const enum eDebugLevels level, const char *const format, + ...) __printf__(2, 3); +extern void debugPutc(const int level, const int c); +extern void debugParseNest(const boolean increase, const unsigned int level); +extern void debugCppNest(const boolean begin, const unsigned int level); +extern void debugCppIgnore(const boolean ignore); +extern void debugEntry(const tagEntryInfo *const tag); + +#endif /* _DEBUG_H */ diff --git a/third_party/ctags/dosbatch.c b/third_party/ctags/dosbatch.c new file mode 100644 index 00000000..b446b864 --- /dev/null +++ b/third_party/ctags/dosbatch.c @@ -0,0 +1,35 @@ +/* + * $Id$ + * + * Copyright (c) 2009, David Fishburn + * + * This source code is released for free distribution under the terms of the + * GNU General Public License. + * + * This module contains functions for generating tags for DOS Batch language + * files. + */ +#include "third_party/ctags/general.h" +/* must always come first */ +#include "third_party/ctags/parse.h" + +/* + * FUNCTION DEFINITIONS + */ + +static void installDosBatchRegex(const langType language) { + addTagRegex(language, "^:([A-Za-z_0-9]+)", "\\1", "l,label,labels", NULL); + addTagRegex(language, "set[ \t]+([A-Za-z_0-9]+)[ \t]*=", "\\1", + "v,variable,variables", NULL); +} + +extern parserDefinition* DosBatchParser() { + static const char* const extensions[] = {"bat", "cmd", NULL}; + parserDefinition* const def = parserNew("DosBatch"); + def->extensions = extensions; + def->initialize = installDosBatchRegex; + def->regex = TRUE; + return def; +} + +/* vi:set tabstop=4 shiftwidth=4: */ diff --git a/third_party/ctags/eiffel.c b/third_party/ctags/eiffel.c new file mode 100644 index 00000000..135d214e --- /dev/null +++ b/third_party/ctags/eiffel.c @@ -0,0 +1,1304 @@ +/* + * $Id: eiffel.c 748 2009-11-06 02:44:42Z dhiebert $ + * + * Copyright (c) 1998-2002, Darren Hiebert + * + * This source code is released for free distribution under the terms of the + * GNU General Public License. + * + * This module contains functions for generating tags for Eiffel language + * files. + */ +#include "third_party/ctags/general.h" +/* must always come first */ +#include "libc/calls/calls.h" +#include "libc/conv/conv.h" +#include "libc/runtime/runtime.h" +#include "libc/str/str.h" +#include "third_party/ctags/debug.h" +#include "third_party/ctags/entry.h" +#include "third_party/ctags/keyword.h" +#include "third_party/ctags/options.h" +#include "third_party/ctags/parse.h" +#include "third_party/ctags/read.h" +#include "third_party/ctags/routines.h" +#include "third_party/ctags/vstring.h" + +/* + * MACROS + */ +#define isident(c) (isalnum(c) || (c) == '_') +#define isFreeOperatorChar(c) \ + ((c) == '@' || (c) == '#' || (c) == '|' || (c) == '&') +#define isType(token, t) (boolean)((token)->type == (t)) +#define isKeyword(token, k) (boolean)((token)->keyword == (k)) + +/* + * DATA DECLARATIONS + */ + +typedef enum eException { ExceptionNone, ExceptionEOF } exception_t; + +/* Used to specify type of keyword. + */ +typedef enum eKeywordId { + KEYWORD_NONE = -1, + KEYWORD_alias, + KEYWORD_all, + KEYWORD_and, + KEYWORD_as, + KEYWORD_assign, + KEYWORD_attached, + KEYWORD_check, + KEYWORD_class, + KEYWORD_convert, + KEYWORD_create, + KEYWORD_creation, + KEYWORD_Current, + KEYWORD_debug, + KEYWORD_deferred, + KEYWORD_detachable, + KEYWORD_do, + KEYWORD_else, + KEYWORD_elseif, + KEYWORD_end, + KEYWORD_ensure, + KEYWORD_expanded, + KEYWORD_export, + KEYWORD_external, + KEYWORD_false, + KEYWORD_feature, + KEYWORD_from, + KEYWORD_frozen, + KEYWORD_if, + KEYWORD_implies, + KEYWORD_indexing, + KEYWORD_infix, + KEYWORD_inherit, + KEYWORD_inspect, + KEYWORD_invariant, + KEYWORD_is, + KEYWORD_like, + KEYWORD_local, + KEYWORD_loop, + KEYWORD_not, + KEYWORD_obsolete, + KEYWORD_old, + KEYWORD_once, + KEYWORD_or, + KEYWORD_prefix, + KEYWORD_redefine, + KEYWORD_rename, + KEYWORD_require, + KEYWORD_rescue, + KEYWORD_Result, + KEYWORD_retry, + KEYWORD_select, + KEYWORD_separate, + KEYWORD_strip, + KEYWORD_then, + KEYWORD_true, + KEYWORD_undefine, + KEYWORD_unique, + KEYWORD_until, + KEYWORD_variant, + KEYWORD_when, + KEYWORD_xor +} keywordId; + +/* Used to determine whether keyword is valid for the token language and + * what its ID is. + */ +typedef struct sKeywordDesc { + const char *name; + keywordId id; +} keywordDesc; + +typedef enum eTokenType { + TOKEN_UNDEFINED, + TOKEN_BANG, + TOKEN_CHARACTER, + TOKEN_CLOSE_BRACE, + TOKEN_CLOSE_BRACKET, + TOKEN_CLOSE_PAREN, + TOKEN_COLON, + TOKEN_COMMA, + TOKEN_CONSTRAINT, + TOKEN_DOT, + TOKEN_DOLLAR, + TOKEN_IDENTIFIER, + TOKEN_KEYWORD, + TOKEN_NUMERIC, + TOKEN_OPEN_BRACE, + TOKEN_OPEN_BRACKET, + TOKEN_OPEN_PAREN, + TOKEN_OPERATOR, + TOKEN_OTHER, + TOKEN_QUESTION, + TOKEN_SEMICOLON, + TOKEN_SEPARATOR, + TOKEN_STRING, + TOKEN_TILDE +} tokenType; + +typedef struct sTokenInfo { + tokenType type; + keywordId keyword; + boolean isExported; + vString *string; + vString *className; + vString *featureName; +} tokenInfo; + +/* + * DATA DEFINITIONS + */ + +static langType Lang_eiffel; + +#ifdef TYPE_REFERENCE_TOOL + +static const char *FileName; +static FILE *File; +static int PrintClass; +static int PrintReferences; +static int SelfReferences; +static int Debug; +static stringList *GenericNames; +static stringList *ReferencedTypes; + +#else + +typedef enum { + EKIND_CLASS, + EKIND_FEATURE, + EKIND_LOCAL, + EKIND_QUALIFIED_TAGS +} eiffelKind; + +static kindOption EiffelKinds[] = {{TRUE, 'c', "class", "classes"}, + {TRUE, 'f', "feature", "features"}, + {FALSE, 'l', "local", "local entities"}}; + +#endif + +static jmp_buf Exception; + +static const keywordDesc EiffelKeywordTable[] = { + /* keyword keyword ID */ + {"alias", KEYWORD_alias}, + {"all", KEYWORD_all}, + {"and", KEYWORD_and}, + {"as", KEYWORD_as}, + {"assign", KEYWORD_assign}, + {"attached", KEYWORD_attached}, + {"check", KEYWORD_check}, + {"class", KEYWORD_class}, + {"convert", KEYWORD_convert}, + {"create", KEYWORD_create}, + {"creation", KEYWORD_creation}, + {"current", KEYWORD_Current}, + {"debug", KEYWORD_debug}, + {"deferred", KEYWORD_deferred}, + {"detachable", KEYWORD_detachable}, + {"do", KEYWORD_do}, + {"else", KEYWORD_else}, + {"elseif", KEYWORD_elseif}, + {"end", KEYWORD_end}, + {"ensure", KEYWORD_ensure}, + {"expanded", KEYWORD_expanded}, + {"export", KEYWORD_export}, + {"external", KEYWORD_external}, + {"false", KEYWORD_false}, + {"feature", KEYWORD_feature}, + {"from", KEYWORD_from}, + {"frozen", KEYWORD_frozen}, + {"if", KEYWORD_if}, + {"implies", KEYWORD_implies}, + {"indexing", KEYWORD_indexing}, + {"infix", KEYWORD_infix}, + {"inherit", KEYWORD_inherit}, + {"inspect", KEYWORD_inspect}, + {"invariant", KEYWORD_invariant}, + {"is", KEYWORD_is}, + {"like", KEYWORD_like}, + {"local", KEYWORD_local}, + {"loop", KEYWORD_loop}, + {"not", KEYWORD_not}, + {"obsolete", KEYWORD_obsolete}, + {"old", KEYWORD_old}, + {"once", KEYWORD_once}, + {"or", KEYWORD_or}, + {"prefix", KEYWORD_prefix}, + {"redefine", KEYWORD_redefine}, + {"rename", KEYWORD_rename}, + {"require", KEYWORD_require}, + {"rescue", KEYWORD_rescue}, + {"result", KEYWORD_Result}, + {"retry", KEYWORD_retry}, + {"select", KEYWORD_select}, + {"separate", KEYWORD_separate}, + {"strip", KEYWORD_strip}, + {"then", KEYWORD_then}, + {"true", KEYWORD_true}, + {"undefine", KEYWORD_undefine}, + {"unique", KEYWORD_unique}, + {"until", KEYWORD_until}, + {"variant", KEYWORD_variant}, + {"when", KEYWORD_when}, + {"xor", KEYWORD_xor}}; + +/* + * FUNCTION DEFINITIONS + */ + +static void buildEiffelKeywordHash(void) { + const size_t count = + sizeof(EiffelKeywordTable) / sizeof(EiffelKeywordTable[0]); + size_t i; + for (i = 0; i < count; ++i) { + const keywordDesc *const p = &EiffelKeywordTable[i]; + addKeyword(p->name, Lang_eiffel, (int)p->id); + } +} + +#ifdef TYPE_REFERENCE_TOOL + +static void addGenericName(tokenInfo *const token) { + vStringUpper(token->string); + if (vStringLength(token->string) > 0) + stringListAdd(GenericNames, vStringNewCopy(token->string)); +} + +static boolean isGeneric(tokenInfo *const token) { + return (boolean)stringListHas(GenericNames, vStringValue(token->string)); +} + +static void reportType(tokenInfo *const token) { + vStringUpper(token->string); + if (vStringLength(token->string) > 0 && !isGeneric(token) && + (SelfReferences || strcmp(vStringValue(token->string), + vStringValue(token->className)) != 0) && + !stringListHas(ReferencedTypes, vStringValue(token->string))) { + printf("%s\n", vStringValue(token->string)); + stringListAdd(ReferencedTypes, vStringNewCopy(token->string)); + } +} + +static int fileGetc(void) { + int c = getc(File); + if (c == '\r') { + c = getc(File); + if (c != '\n') { + ungetc(c, File); + c = '\n'; + } + } + if (Debug > 0 && c != EOF) putc(c, errout); + return c; +} + +static int fileUngetc(c) { + return ungetc(c, File); +} + +extern char *readLine(vString *const vLine, FILE *const fp) { + return NULL; +} + +#else + +/* + * Tag generation functions + */ + +static void makeEiffelClassTag(tokenInfo *const token) { + if (EiffelKinds[EKIND_CLASS].enabled) { + const char *const name = vStringValue(token->string); + tagEntryInfo e; + + initTagEntry(&e, name); + + e.kindName = EiffelKinds[EKIND_CLASS].name; + e.kind = EiffelKinds[EKIND_CLASS].letter; + + makeTagEntry(&e); + } + vStringCopy(token->className, token->string); +} + +static void makeEiffelFeatureTag(tokenInfo *const token) { + if (EiffelKinds[EKIND_FEATURE].enabled && + (token->isExported || Option.include.fileScope)) { + const char *const name = vStringValue(token->string); + tagEntryInfo e; + + initTagEntry(&e, name); + + e.isFileScope = (boolean)(!token->isExported); + e.kindName = EiffelKinds[EKIND_FEATURE].name; + e.kind = EiffelKinds[EKIND_FEATURE].letter; + e.extensionFields.scope[0] = EiffelKinds[EKIND_CLASS].name; + e.extensionFields.scope[1] = vStringValue(token->className); + + makeTagEntry(&e); + + if (Option.include.qualifiedTags) { + vString *qualified = vStringNewInit(vStringValue(token->className)); + vStringPut(qualified, '.'); + vStringCat(qualified, token->string); + e.name = vStringValue(qualified); + makeTagEntry(&e); + vStringDelete(qualified); + } + } + vStringCopy(token->featureName, token->string); +} + +static void makeEiffelLocalTag(tokenInfo *const token) { + if (EiffelKinds[EKIND_LOCAL].enabled && Option.include.fileScope) { + const char *const name = vStringValue(token->string); + vString *scope = vStringNew(); + tagEntryInfo e; + + initTagEntry(&e, name); + + e.isFileScope = TRUE; + e.kindName = EiffelKinds[EKIND_LOCAL].name; + e.kind = EiffelKinds[EKIND_LOCAL].letter; + + vStringCopy(scope, token->className); + vStringPut(scope, '.'); + vStringCat(scope, token->featureName); + + e.extensionFields.scope[0] = EiffelKinds[EKIND_FEATURE].name; + e.extensionFields.scope[1] = vStringValue(scope); + + makeTagEntry(&e); + vStringDelete(scope); + } +} + +#endif + +/* + * Parsing functions + */ + +static int skipToCharacter(const int c) { + int d; + + do { + d = fileGetc(); + } while (d != EOF && d != c); + + return d; +} + +/* If a numeric is passed in 'c', this is used as the first digit of the + * numeric being parsed. + */ +static vString *parseInteger(int c) { + vString *string = vStringNew(); + + if (c == '\0') c = fileGetc(); + if (c == '-') { + vStringPut(string, c); + c = fileGetc(); + } else if (!isdigit(c)) + c = fileGetc(); + while (c != EOF && (isdigit(c) || c == '_')) { + vStringPut(string, c); + c = fileGetc(); + } + vStringTerminate(string); + fileUngetc(c); + + return string; +} + +static vString *parseNumeric(int c) { + vString *string = vStringNew(); + vString *integer = parseInteger(c); + vStringCopy(string, integer); + vStringDelete(integer); + + c = fileGetc(); + if (c == '.') { + integer = parseInteger('\0'); + vStringPut(string, c); + vStringCat(string, integer); + vStringDelete(integer); + c = fileGetc(); + } + if (tolower(c) == 'e') { + integer = parseInteger('\0'); + vStringPut(string, c); + vStringCat(string, integer); + vStringDelete(integer); + } else if (!isspace(c)) + fileUngetc(c); + + vStringTerminate(string); + + return string; +} + +static int parseEscapedCharacter(void) { + int d = '\0'; + int c = fileGetc(); + + switch (c) { + case 'A': + d = '@'; + break; + case 'B': + d = '\b'; + break; + case 'C': + d = '^'; + break; + case 'D': + d = '$'; + break; + case 'F': + d = '\f'; + break; + case 'H': + d = '\\'; + break; + case 'L': + d = '~'; + break; + case 'N': + d = '\n'; + break; +#ifdef QDOS + case 'Q': + d = 0x9F; + break; +#else + case 'Q': + d = '`'; + break; +#endif + case 'R': + d = '\r'; + break; + case 'S': + d = '#'; + break; + case 'T': + d = '\t'; + break; + case 'U': + d = '\0'; + break; + case 'V': + d = '|'; + break; + case '%': + d = '%'; + break; + case '\'': + d = '\''; + break; + case '"': + d = '"'; + break; + case '(': + d = '['; + break; + case ')': + d = ']'; + break; + case '<': + d = '{'; + break; + case '>': + d = '}'; + break; + + case '\n': + skipToCharacter('%'); + break; + + case '/': { + vString *string = parseInteger('\0'); + const char *value = vStringValue(string); + const unsigned long ascii = atol(value); + vStringDelete(string); + + c = fileGetc(); + if (c == '/' && ascii < 256) d = ascii; + break; + } + + default: + break; + } + return d; +} + +static int parseCharacter(void) { + int c = fileGetc(); + int result = c; + + if (c == '%') result = parseEscapedCharacter(); + + c = fileGetc(); + if (c != '\'') skipToCharacter('\n'); + + return result; +} + +static void parseString(vString *const string) { + boolean verbatim = FALSE; + boolean align = FALSE; + boolean end = FALSE; + vString *verbatimCloser = vStringNew(); + vString *lastLine = vStringNew(); + int prev = '\0'; + int c; + + while (!end) { + c = fileGetc(); + if (c == EOF) + end = TRUE; + else if (c == '"') { + if (!verbatim) + end = TRUE; + else + end = (boolean)( + strcmp(vStringValue(lastLine), vStringValue(verbatimCloser)) == 0); + } else if (c == '\n') { + if (verbatim) vStringClear(lastLine); + if (prev == '[' /* || prev == '{' */) { + verbatim = TRUE; + vStringClear(verbatimCloser); + vStringClear(lastLine); + if (prev == '{') + vStringPut(verbatimCloser, '}'); + else { + vStringPut(verbatimCloser, ']'); + align = TRUE; + } + vStringNCat(verbatimCloser, string, vStringLength(string) - 1); + vStringClear(string); + } + if (verbatim && align) { + do + c = fileGetc(); + while (isspace(c)); + } + } else if (c == '%') + c = parseEscapedCharacter(); + if (!end) { + vStringPut(string, c); + if (verbatim) { + vStringPut(lastLine, c); + vStringTerminate(lastLine); + } + prev = c; + } + } + vStringTerminate(string); + vStringDelete(lastLine); + vStringDelete(verbatimCloser); +} + +/* Read a C identifier beginning with "firstChar" and places it into "name". + */ +static void parseIdentifier(vString *const string, const int firstChar) { + int c = firstChar; + + do { + vStringPut(string, c); + c = fileGetc(); + } while (isident(c)); + + vStringTerminate(string); + if (!isspace(c)) fileUngetc(c); /* unget non-identifier character */ +} + +static void parseFreeOperator(vString *const string, const int firstChar) { + int c = firstChar; + + do { + vStringPut(string, c); + c = fileGetc(); + } while (c > ' '); + + vStringTerminate(string); + if (!isspace(c)) fileUngetc(c); /* unget non-identifier character */ +} + +static void copyToken(tokenInfo *dst, const tokenInfo *src) { + dst->type = src->type; + dst->keyword = src->keyword; + dst->isExported = src->isExported; + + vStringCopy(dst->string, src->string); + vStringCopy(dst->className, src->className); + vStringCopy(dst->featureName, src->featureName); +} + +static tokenInfo *newToken(void) { + tokenInfo *const token = xMalloc(1, tokenInfo); + + token->type = TOKEN_UNDEFINED; + token->keyword = KEYWORD_NONE; + token->isExported = TRUE; + + token->string = vStringNew(); + token->className = vStringNew(); + token->featureName = vStringNew(); + + return token; +} + +static void deleteToken(tokenInfo *const token) { + vStringDelete(token->string); + vStringDelete(token->className); + vStringDelete(token->featureName); + + eFree(token); +} + +static void readToken(tokenInfo *const token) { + int c; + + token->type = TOKEN_UNDEFINED; + token->keyword = KEYWORD_NONE; + vStringClear(token->string); + +getNextChar: + + do + c = fileGetc(); + while (c == '\t' || c == ' ' || c == '\n'); + + switch (c) { + case EOF: + longjmp(Exception, (int)ExceptionEOF); + break; + case ';': + token->type = TOKEN_SEMICOLON; + break; + case '!': + token->type = TOKEN_BANG; + break; + case '}': + token->type = TOKEN_CLOSE_BRACE; + break; + case ']': + token->type = TOKEN_CLOSE_BRACKET; + break; + case ')': + token->type = TOKEN_CLOSE_PAREN; + break; + case ',': + token->type = TOKEN_COMMA; + break; + case '$': + token->type = TOKEN_DOLLAR; + break; + case '.': + token->type = TOKEN_DOT; + break; + case '{': + token->type = TOKEN_OPEN_BRACE; + break; + case '[': + token->type = TOKEN_OPEN_BRACKET; + break; + case '(': + token->type = TOKEN_OPEN_PAREN; + break; + case '~': + token->type = TOKEN_TILDE; + break; + + case '+': + case '*': + case '^': + case '=': + token->type = TOKEN_OPERATOR; + break; + + case '-': + c = fileGetc(); + if (c == '>') + token->type = TOKEN_CONSTRAINT; + else if (c == '-') /* is this the start of a comment? */ + { + skipToCharacter('\n'); + goto getNextChar; + } else { + if (!isspace(c)) fileUngetc(c); + token->type = TOKEN_OPERATOR; + } + break; + + case '?': + case ':': { + int c2 = fileGetc(); + if (c2 == '=') + token->type = TOKEN_OPERATOR; + else { + if (!isspace(c2)) fileUngetc(c2); + if (c == ':') + token->type = TOKEN_COLON; + else + token->type = TOKEN_QUESTION; + } + break; + } + + case '<': + c = fileGetc(); + if (c != '=' && c != '>' && !isspace(c)) fileUngetc(c); + token->type = TOKEN_OPERATOR; + break; + + case '>': + c = fileGetc(); + if (c != '=' && c != '>' && !isspace(c)) fileUngetc(c); + token->type = TOKEN_OPERATOR; + break; + + case '/': + c = fileGetc(); + if (c != '/' && c != '=' && !isspace(c)) fileUngetc(c); + token->type = TOKEN_OPERATOR; + break; + + case '\\': + c = fileGetc(); + if (c != '\\' && !isspace(c)) fileUngetc(c); + token->type = TOKEN_OPERATOR; + break; + + case '"': + token->type = TOKEN_STRING; + parseString(token->string); + break; + + case '\'': + token->type = TOKEN_CHARACTER; + parseCharacter(); + break; + + default: + if (isalpha(c)) { + parseIdentifier(token->string, c); + token->keyword = analyzeToken(token->string, Lang_eiffel); + if (isKeyword(token, KEYWORD_NONE)) + token->type = TOKEN_IDENTIFIER; + else + token->type = TOKEN_KEYWORD; + } else if (isdigit(c)) { + vString *numeric = parseNumeric(c); + vStringCat(token->string, numeric); + vStringDelete(numeric); + token->type = TOKEN_NUMERIC; + } else if (isFreeOperatorChar(c)) { + parseFreeOperator(token->string, c); + token->type = TOKEN_OPERATOR; + } else { + token->type = TOKEN_UNDEFINED; + Assert(!isType(token, TOKEN_UNDEFINED)); + } + break; + } +} + +/* + * Scanning functions + */ + +static boolean isIdentifierMatch(const tokenInfo *const token, + const char *const name) { + return (boolean)(isType(token, TOKEN_IDENTIFIER) && + strcasecmp(vStringValue(token->string), name) == 0); +} + +static void findToken(tokenInfo *const token, const tokenType type) { + while (!isType(token, type)) readToken(token); +} + +static void findKeyword(tokenInfo *const token, const keywordId keyword) { + while (!isKeyword(token, keyword)) readToken(token); +} + +static boolean parseType(tokenInfo *const token); + +static void parseGeneric(tokenInfo *const token, + boolean declaration __unused__) { + unsigned int depth = 0; +#ifdef TYPE_REFERENCE_TOOL + boolean constraint = FALSE; +#endif + Assert(isType(token, TOKEN_OPEN_BRACKET)); + do { + if (isType(token, TOKEN_OPEN_BRACKET)) { + ++depth; + readToken(token); + } else if (isType(token, TOKEN_CLOSE_BRACKET)) { + --depth; + readToken(token); + } +#ifdef TYPE_REFERENCE_TOOL + else if (declaration) { + boolean advanced = FALSE; + if (depth == 1) { + if (isType(token, TOKEN_CONSTRAINT)) + constraint = TRUE; + else if (isKeyword(token, KEYWORD_create)) + findKeyword(token, KEYWORD_end); + else if (isType(token, TOKEN_IDENTIFIER)) { + if (constraint) + advanced = parseType(token); + else + addGenericName(token); + constraint = FALSE; + } + } else if (isType(token, TOKEN_IDENTIFIER)) + advanced = parseType(token); + if (!advanced) readToken(token); + } +#endif + else + parseType(token); + } while (depth > 0); +} + +static boolean parseType(tokenInfo *const token) { + tokenInfo *const id = newToken(); + copyToken(id, token); + readToken(token); + if (isType(token, TOKEN_COLON)) /* check for "{entity: TYPE}" */ + { + readToken(id); + readToken(token); + } + if (isKeyword(id, KEYWORD_like)) { + if (isType(token, TOKEN_IDENTIFIER) || isKeyword(token, KEYWORD_Current)) + readToken(token); + } else { + if (isKeyword(id, KEYWORD_attached) || isKeyword(id, KEYWORD_detachable) || + isKeyword(id, KEYWORD_expanded)) { + copyToken(id, token); + readToken(token); + } + if (isType(id, TOKEN_IDENTIFIER)) { +#ifdef TYPE_REFERENCE_TOOL + reportType(id); +#endif + if (isType(token, TOKEN_OPEN_BRACKET)) + parseGeneric(token, FALSE); + else if ((strcmp("BIT", vStringValue(id->string)) == 0)) + readToken(token); /* read token after number of bits */ + } + } + deleteToken(id); + return TRUE; +} + +static void parseEntityType(tokenInfo *const token) { + Assert(isType(token, TOKEN_COLON)); + readToken(token); + + if (isType(token, TOKEN_BANG) || isType(token, TOKEN_QUESTION)) + readToken(token); /* skip over '!' or '?' */ + parseType(token); +} + +static void parseLocal(tokenInfo *const token) { + Assert(isKeyword(token, KEYWORD_local)); + readToken(token); + + /* Check keyword first in case local clause is empty + */ + while (!isKeyword(token, KEYWORD_do) && !isKeyword(token, KEYWORD_once)) { +#ifndef TYPE_REFERENCE_TOOL + if (isType(token, TOKEN_IDENTIFIER)) makeEiffelLocalTag(token); +#endif + readToken(token); + if (isType(token, TOKEN_COLON)) parseEntityType(token); + } +} + +static void findFeatureEnd(tokenInfo *const token) { + boolean isFound = isKeyword(token, KEYWORD_is); + if (isFound) readToken(token); + switch (token->keyword) { + case KEYWORD_deferred: + case KEYWORD_do: + case KEYWORD_external: + case KEYWORD_local: + case KEYWORD_obsolete: + case KEYWORD_once: + case KEYWORD_require: { + int depth = 1; + + while (depth > 0) { +#ifdef TYPE_REFERENCE_TOOL + if (isType(token, TOKEN_OPEN_BRACE)) { + readToken(token); + if (isType(token, TOKEN_IDENTIFIER)) parseType(token); + } else if (isType(token, TOKEN_BANG)) { + readToken(token); + if (isType(token, TOKEN_IDENTIFIER)) parseType(token); + if (isType(token, TOKEN_BANG)) readToken(token); + } else +#endif + switch (token->keyword) { + case KEYWORD_check: + case KEYWORD_debug: + case KEYWORD_from: + case KEYWORD_if: + case KEYWORD_inspect: + ++depth; + break; + + case KEYWORD_local: + parseLocal(token); + break; + + case KEYWORD_end: + --depth; + break; + + default: + break; + } + readToken(token); + } + break; + } + + default: + /* is this a manifest constant? */ + if (isFound || isType(token, TOKEN_OPERATOR)) { + if (isType(token, TOKEN_OPERATOR)) readToken(token); + readToken(token); + } + break; + } +} + +static boolean readFeatureName(tokenInfo *const token) { + boolean isFeatureName = FALSE; + + if (isKeyword(token, KEYWORD_frozen)) readToken(token); + if (isType(token, TOKEN_IDENTIFIER)) + isFeatureName = TRUE; + else if (isKeyword(token, KEYWORD_assign)) /* legacy code */ + isFeatureName = TRUE; + else if (isKeyword(token, KEYWORD_infix) || + isKeyword(token, KEYWORD_prefix)) { + readToken(token); + if (isType(token, TOKEN_STRING)) isFeatureName = TRUE; + } + return isFeatureName; +} + +static void parseArguments(tokenInfo *const token) { +#ifndef TYPE_REFERENCE_TOOL + findToken(token, TOKEN_CLOSE_PAREN); + readToken(token); +#else + Assert(isType(token, TOKEN_OPEN_PAREN)); + readToken(token); + do { + if (isType(token, TOKEN_COLON)) + parseEntityType(token); + else + readToken(token); + } while (!isType(token, TOKEN_CLOSE_PAREN)); + readToken(token); +#endif +} + +static boolean parseFeature(tokenInfo *const token) { + boolean found = FALSE; + while (readFeatureName(token)) { + found = TRUE; +#ifndef TYPE_REFERENCE_TOOL + makeEiffelFeatureTag(token); +#endif + readToken(token); + if (isType(token, TOKEN_COMMA)) readToken(token); + } + if (found) { + if (isKeyword(token, KEYWORD_alias)) { + readToken(token); +#ifndef TYPE_REFERENCE_TOOL + if (isType(token, TOKEN_STRING)) makeEiffelFeatureTag(token); +#endif + readToken(token); + } + if (isType(token, TOKEN_OPEN_PAREN)) /* arguments? */ + parseArguments(token); + if (isType(token, TOKEN_COLON)) /* a query? */ + parseEntityType(token); + if (isKeyword(token, KEYWORD_assign)) { + readToken(token); + readToken(token); + } + if (isKeyword(token, KEYWORD_obsolete)) { + readToken(token); + if (isType(token, TOKEN_STRING)) readToken(token); + } + findFeatureEnd(token); + } + return found; +} + +static void parseExport(tokenInfo *const token) { + token->isExported = TRUE; + readToken(token); + if (isType(token, TOKEN_OPEN_BRACE)) { + token->isExported = FALSE; + while (!isType(token, TOKEN_CLOSE_BRACE)) { + if (isType(token, TOKEN_IDENTIFIER)) + token->isExported |= !isIdentifierMatch(token, "NONE"); + readToken(token); + } + readToken(token); + } +} + +static void parseFeatureClauses(tokenInfo *const token) { + Assert(isKeyword(token, KEYWORD_feature)); + do { + if (isKeyword(token, KEYWORD_feature)) parseExport(token); + if (!isKeyword(token, KEYWORD_feature) && + !isKeyword(token, KEYWORD_invariant) && + !isKeyword(token, KEYWORD_indexing)) { + if (!parseFeature(token)) readToken(token); + } + } while (!isKeyword(token, KEYWORD_end) && + !isKeyword(token, KEYWORD_invariant) && + !isKeyword(token, KEYWORD_indexing)); +} + +static void parseRename(tokenInfo *const token) { + Assert(isKeyword(token, KEYWORD_rename)); + do { + readToken(token); + if (readFeatureName(token)) { + readToken(token); + if (isKeyword(token, KEYWORD_as)) { + readToken(token); + if (readFeatureName(token)) { +#ifndef TYPE_REFERENCE_TOOL + makeEiffelFeatureTag(token); /* renamed feature */ +#endif + readToken(token); + } + } + } + } while (isType(token, TOKEN_COMMA)); +} + +static void parseInherit(tokenInfo *const token) { + Assert(isKeyword(token, KEYWORD_inherit)); + readToken(token); + while (isType(token, TOKEN_IDENTIFIER)) { + parseType(token); + if (isType(token, TOKEN_KEYWORD)) { + switch (token->keyword) /* check for feature adaptation */ + { + case KEYWORD_rename: + parseRename(token); + case KEYWORD_export: + case KEYWORD_undefine: + case KEYWORD_redefine: + case KEYWORD_select: + findKeyword(token, KEYWORD_end); + readToken(token); + break; + + case KEYWORD_end: + readToken(token); + break; + + default: + break; + } + } + if (isType(token, TOKEN_SEMICOLON)) readToken(token); + } +} + +static void parseConvert(tokenInfo *const token) { + Assert(isKeyword(token, KEYWORD_convert)); + do { + readToken(token); + if (!isType(token, TOKEN_IDENTIFIER)) + break; + else if (isType(token, TOKEN_OPEN_PAREN)) { + while (!isType(token, TOKEN_CLOSE_PAREN)) readToken(token); + } else if (isType(token, TOKEN_COLON)) { + readToken(token); + if (!isType(token, TOKEN_OPEN_BRACE)) + break; + else + while (!isType(token, TOKEN_CLOSE_BRACE)) readToken(token); + } + } while (isType(token, TOKEN_COMMA)); +} + +static void parseClass(tokenInfo *const token) { + Assert(isKeyword(token, KEYWORD_class)); + readToken(token); + if (isType(token, TOKEN_IDENTIFIER)) { +#ifndef TYPE_REFERENCE_TOOL + makeEiffelClassTag(token); + readToken(token); +#else + vStringCopy(token->className, token->string); + vStringUpper(token->className); + if (PrintClass) puts(vStringValue(token->className)); + if (!PrintReferences) exit(0); + readToken(token); +#endif + } + + do { + if (isType(token, TOKEN_OPEN_BRACKET)) + parseGeneric(token, TRUE); + else if (!isType(token, TOKEN_KEYWORD)) + readToken(token); + else + switch (token->keyword) { + case KEYWORD_inherit: + parseInherit(token); + break; + case KEYWORD_feature: + parseFeatureClauses(token); + break; + case KEYWORD_convert: + parseConvert(token); + break; + default: + readToken(token); + break; + } + } while (!isKeyword(token, KEYWORD_end)); +} + +static void initialize(const langType language) { + Lang_eiffel = language; + buildEiffelKeywordHash(); +} + +static void findEiffelTags(void) { + tokenInfo *const token = newToken(); + exception_t exception; + + exception = (exception_t)(setjmp(Exception)); + while (exception == ExceptionNone) { + findKeyword(token, KEYWORD_class); + parseClass(token); + } + deleteToken(token); +} + +#ifndef TYPE_REFERENCE_TOOL + +extern parserDefinition *EiffelParser(void) { + static const char *const extensions[] = {"e", NULL}; + parserDefinition *def = parserNew("Eiffel"); + def->kinds = EiffelKinds; + def->kindCount = KIND_COUNT(EiffelKinds); + def->extensions = extensions; + def->parser = findEiffelTags; + def->initialize = initialize; + return def; +} + +#else + +static void findReferences(void) { + ReferencedTypes = stringListNew(); + GenericNames = stringListNew(); + initialize(0); + + findEiffelTags(); + + stringListDelete(GenericNames); + GenericNames = NULL; + stringListDelete(ReferencedTypes); + ReferencedTypes = NULL; +} + +static const char *const Usage = + "Prints names of types referenced by an Eiffel language file.\n" + "\n" + "Usage: %s [-cdrs] [file_name | -]\n" + "\n" + "Options:\n" + " -c Print class name of current file (on first line of output).\n" + " -d Enable debug output.\n" + " -r Print types referenced by current file (default unless -c).\n" + " -s Include self-references.\n" + "\n"; + +extern int main(int argc, char **argv) { + int i; + for (i = 1; argv[i] != NULL; ++i) { + const char *const arg = argv[i]; + if (arg[0] == '-') { + int j; + if (arg[1] == '\0') { + File = stdin; + FileName = "stdin"; + } else + for (j = 1; arg[j] != '\0'; ++j) switch (arg[j]) { + case 'c': + PrintClass = 1; + break; + case 'r': + PrintReferences = 1; + break; + case 's': + SelfReferences = 1; + break; + case 'd': + Debug = 1; + break; + default: + fprintf(errout, "%s: unknown option: %c\n", argv[0], arg[1]); + fprintf(errout, Usage, argv[0]); + exit(1); + break; + } + } else if (File != NULL) { + fprintf(errout, Usage, argv[0]); + exit(1); + } else { + FileName = arg; + File = fopen(FileName, "r"); + if (File == NULL) { + perror(argv[0]); + exit(1); + } + } + } + if (!PrintClass) PrintReferences = 1; + if (File == NULL) { + fprintf(errout, Usage, argv[0]); + exit(1); + } else { + findReferences(); + fclose(File); + } + return 0; +} + +#endif + +/* vi:set tabstop=4 shiftwidth=4: */ diff --git a/third_party/ctags/entry.c b/third_party/ctags/entry.c new file mode 100644 index 00000000..4f2d9318 --- /dev/null +++ b/third_party/ctags/entry.c @@ -0,0 +1,736 @@ +/* + * $Id: entry.c 766 2010-09-11 18:59:45Z dhiebert $ + * + * Copyright (c) 1996-2002, Darren Hiebert + * + * This source code is released for free distribution under the terms of the + * GNU General Public License. + * + * This module contains functions for creating tag entries. + */ +#include "third_party/ctags/general.h" +/* must always come first */ +#include "libc/calls/calls.h" +#include "libc/calls/weirdtypes.h" +#include "libc/errno.h" +#include "libc/fmt/fmt.h" +#include "libc/runtime/runtime.h" +#include "libc/str/str.h" +#include "third_party/ctags/ctags.h" +#include "third_party/ctags/debug.h" +#include "third_party/ctags/entry.h" +#include "third_party/ctags/main.h" +#include "third_party/ctags/options.h" +#include "third_party/ctags/read.h" +#include "third_party/ctags/routines.h" +#include "third_party/ctags/sort.h" +#include "third_party/ctags/strlist.h" + +/* + * MACROS + */ +#define PSEUDO_TAG_PREFIX "!_" + +#define includeExtensionFlags() (Option.tagFileFormat > 1) + +/* + * Portability defines + */ +#if !defined(HAVE_TRUNCATE) && !defined(HAVE_FTRUNCATE) && !defined(HAVE_CHSIZE) +#define USE_REPLACEMENT_TRUNCATE +#endif + +/* Hack for rediculous practice of Microsoft Visual C++. + */ +#if defined(WIN32) && defined(_MSC_VER) +#define chsize _chsize +#define open _open +#define close _close +#define O_RDWR _O_RDWR +#endif + +/* + * DATA DEFINITIONS + */ + +tagFile TagFile = { + NULL, /* tag file name */ + NULL, /* tag file directory (absolute) */ + NULL, /* file pointer */ + {0, 0}, /* numTags */ + {0, 0, 0}, /* max */ + {NULL, NULL, 0}, /* etags */ + NULL /* vLine */ +}; + +static boolean TagsToStdout = FALSE; + +/* + * FUNCTION PROTOTYPES + */ +#ifdef NEED_PROTO_TRUNCATE +extern int truncate(const char *path, off_t length); +#endif + +#ifdef NEED_PROTO_FTRUNCATE +extern int ftruncate(int fd, off_t length); +#endif + +/* + * FUNCTION DEFINITIONS + */ + +extern void freeTagFileResources(void) { + if (TagFile.directory != NULL) eFree(TagFile.directory); + vStringDelete(TagFile.vLine); +} + +extern const char *tagFileName(void) { + return TagFile.name; +} + +/* + * Pseudo tag support + */ + +static void rememberMaxLengths(const size_t nameLength, + const size_t lineLength) { + if (nameLength > TagFile.max.tag) TagFile.max.tag = nameLength; + + if (lineLength > TagFile.max.line) TagFile.max.line = lineLength; +} + +static void writePseudoTag(const char *const tagName, + const char *const fileName, + const char *const pattern) { + const int length = fprintf(TagFile.fp, "%s%s\t%s\t/%s/\n", PSEUDO_TAG_PREFIX, + tagName, fileName, pattern); + ++TagFile.numTags.added; + rememberMaxLengths(strlen(tagName), (size_t)length); +} + +static void addPseudoTags(void) { + if (!Option.xref) { + char format[11]; + const char *formatComment = "unknown format"; + + sprintf(format, "%u", Option.tagFileFormat); + + if (Option.tagFileFormat == 1) + formatComment = "original ctags format"; + else if (Option.tagFileFormat == 2) + formatComment = + "extended format; --format=1 will not append ;\" to lines"; + + writePseudoTag("TAG_FILE_FORMAT", format, formatComment); + writePseudoTag("TAG_FILE_SORTED", + Option.sorted == SO_FOLDSORTED + ? "2" + : (Option.sorted == SO_SORTED ? "1" : "0"), + "0=unsorted, 1=sorted, 2=foldcase"); + writePseudoTag("TAG_PROGRAM_AUTHOR", AUTHOR_NAME, AUTHOR_EMAIL); + writePseudoTag("TAG_PROGRAM_NAME", PROGRAM_NAME, ""); + writePseudoTag("TAG_PROGRAM_URL", PROGRAM_URL, "official site"); + writePseudoTag("TAG_PROGRAM_VERSION", PROGRAM_VERSION, ""); + } +} + +static void updateSortedFlag(const char *const line, FILE *const fp, + fpos_t startOfLine) { + const char *const tab = strchr(line, '\t'); + + if (tab != NULL) { + const long boolOffset = tab - line + 1; /* where it should be */ + + if (line[boolOffset] == '0' || line[boolOffset] == '1') { + fpos_t nextLine; + + if (fgetpos(fp, &nextLine) == -1 || fsetpos(fp, &startOfLine) == -1) + error(WARNING, "Failed to update 'sorted' pseudo-tag"); + else { + fpos_t flagLocation; + int c, d; + do + c = fgetc(fp); + while (c != '\t' && c != '\n'); + fgetpos(fp, &flagLocation); + d = fgetc(fp); + if (c == '\t' && (d == '0' || d == '1') && d != (int)Option.sorted) { + fsetpos(fp, &flagLocation); + fputc(Option.sorted == SO_FOLDSORTED + ? '2' + : (Option.sorted == SO_SORTED ? '1' : '0'), + fp); + } + fsetpos(fp, &nextLine); + } + } + } +} + +/* Look through all line beginning with "!_TAG_FILE", and update those which + * require it. + */ +static long unsigned int updatePseudoTags(FILE *const fp) { + enum { maxEntryLength = 20 }; + char entry[maxEntryLength + 1]; + unsigned long linesRead = 0; + fpos_t startOfLine; + size_t entryLength; + const char *line; + + sprintf(entry, "%sTAG_FILE", PSEUDO_TAG_PREFIX); + entryLength = strlen(entry); + Assert(entryLength < maxEntryLength); + + fgetpos(fp, &startOfLine); + line = readLine(TagFile.vLine, fp); + while (line != NULL && line[0] == entry[0]) { + ++linesRead; + if (strncmp(line, entry, entryLength) == 0) { + char tab, classType[16]; + + if (sscanf(line + entryLength, "%15s%c", classType, &tab) == 2 && + tab == '\t') { + if (strcmp(classType, "_SORTED") == 0) + updateSortedFlag(line, fp, startOfLine); + } + fgetpos(fp, &startOfLine); + } + line = readLine(TagFile.vLine, fp); + } + while (line != NULL) /* skip to end of file */ + { + ++linesRead; + line = readLine(TagFile.vLine, fp); + } + return linesRead; +} + +/* + * Tag file management + */ + +static boolean isValidTagAddress(const char *const excmd) { + boolean isValid = FALSE; + + if (strchr("/?", excmd[0]) != NULL) + isValid = TRUE; + else { + char *address = xMalloc(strlen(excmd) + 1, char); + if (sscanf(excmd, "%[^;\n]", address) == 1 && + strspn(address, "0123456789") == strlen(address)) + isValid = TRUE; + eFree(address); + } + return isValid; +} + +static boolean isCtagsLine(const char *const line) { + enum fieldList { TAG, TAB1, SRC_FILE, TAB2, EXCMD, NUM_FIELDS }; + boolean ok = FALSE; /* we assume not unless confirmed */ + const size_t fieldLength = strlen(line) + 1; + char *const fields = xMalloc(NUM_FIELDS * fieldLength, char); + + if (fields == NULL) + error(FATAL, "Cannot analyze tag file"); + else { +#define field(x) (fields + ((size_t)(x)*fieldLength)) + + const int numFields = + sscanf(line, "%[^\t]%[\t]%[^\t]%[\t]%[^\r\n]", field(TAG), field(TAB1), + field(SRC_FILE), field(TAB2), field(EXCMD)); + + /* There must be exactly five fields: two tab fields containing + * exactly one tab each, the tag must not begin with "#", and the + * file name should not end with ";", and the excmd must be + * accceptable. + * + * These conditions will reject tag-looking lines like: + * int a; + * #define LABEL + */ + if (numFields == NUM_FIELDS && strlen(field(TAB1)) == 1 && + strlen(field(TAB2)) == 1 && field(TAG)[0] != '#' && + field(SRC_FILE)[strlen(field(SRC_FILE)) - 1] != ';' && + isValidTagAddress(field(EXCMD))) + ok = TRUE; + + eFree(fields); + } + return ok; +} + +static boolean isEtagsLine(const char *const line) { + boolean result = FALSE; + if (line[0] == '\f') result = (boolean)(line[1] == '\n' || line[1] == '\r'); + return result; +} + +static boolean isTagFile(const char *const filename) { + boolean ok = FALSE; /* we assume not unless confirmed */ + FILE *const fp = fopen(filename, "rb"); + + if (fp == NULL && errno == ENOENT) + ok = TRUE; + else if (fp != NULL) { + const char *line = readLine(TagFile.vLine, fp); + + if (line == NULL) + ok = TRUE; + else + ok = (boolean)(isCtagsLine(line) || isEtagsLine(line)); + fclose(fp); + } + return ok; +} + +extern void copyBytes(FILE *const fromFp, FILE *const toFp, const long size) { + enum { BufferSize = 1000 }; + long toRead, numRead; + char *buffer = xMalloc(BufferSize, char); + long remaining = size; + do { + toRead = (0 < remaining && remaining < BufferSize) ? remaining + : (long)BufferSize; + numRead = fread(buffer, (size_t)1, (size_t)toRead, fromFp); + if (fwrite(buffer, (size_t)1, (size_t)numRead, toFp) < (size_t)numRead) + error(FATAL | PERROR, "cannot complete write"); + if (remaining > 0) remaining -= numRead; + } while (numRead == toRead && remaining != 0); + eFree(buffer); +} + +extern void copyFile(const char *const from, const char *const to, + const long size) { + FILE *const fromFp = fopen(from, "rb"); + if (fromFp == NULL) + error(FATAL | PERROR, "cannot open file to copy"); + else { + FILE *const toFp = fopen(to, "wb"); + if (toFp == NULL) + error(FATAL | PERROR, "cannot open copy destination"); + else { + copyBytes(fromFp, toFp, size); + fclose(toFp); + } + fclose(fromFp); + } +} + +extern void openTagFile(void) { + setDefaultTagFileName(); + TagsToStdout = isDestinationStdout(); + + if (TagFile.vLine == NULL) TagFile.vLine = vStringNew(); + + /* Open the tags file. + */ + if (TagsToStdout) + TagFile.fp = tempFile("w", &TagFile.name); + else { + boolean fileExists; + + setDefaultTagFileName(); + TagFile.name = eStrdup(Option.tagFileName); + fileExists = doesFileExist(TagFile.name); + if (fileExists && !isTagFile(TagFile.name)) + error(FATAL, + "\"%s\" doesn't look like a tag file; I refuse to overwrite it.", + TagFile.name); + + if (Option.etags) { + if (Option.append && fileExists) + TagFile.fp = fopen(TagFile.name, "a+b"); + else + TagFile.fp = fopen(TagFile.name, "w+b"); + } else { + if (Option.append && fileExists) { + TagFile.fp = fopen(TagFile.name, "r+"); + if (TagFile.fp != NULL) { + TagFile.numTags.prev = updatePseudoTags(TagFile.fp); + fclose(TagFile.fp); + TagFile.fp = fopen(TagFile.name, "a+"); + } + } else { + TagFile.fp = fopen(TagFile.name, "w"); + if (TagFile.fp != NULL) addPseudoTags(); + } + } + if (TagFile.fp == NULL) { + error(FATAL | PERROR, "cannot open tag file"); + exit(1); + } + } + if (TagsToStdout) + TagFile.directory = eStrdup(CurrentDirectory); + else + TagFile.directory = absoluteDirname(TagFile.name); +} + +#ifdef USE_REPLACEMENT_TRUNCATE + +/* Replacement for missing library function. + */ +static int replacementTruncate(const char *const name, const long size) { + char *tempName = NULL; + FILE *fp = tempFile("w", &tempName); + fclose(fp); + copyFile(name, tempName, size); + copyFile(tempName, name, WHOLE_FILE); + remove(tempName); + eFree(tempName); + + return 0; +} + +#endif + +static void sortTagFile(void) { + if (TagFile.numTags.added > 0L) { + if (Option.sorted != SO_UNSORTED) { + verbose("sorting tag file\n"); +#ifdef EXTERNAL_SORT + externalSortTags(TagsToStdout); +#else + internalSortTags(TagsToStdout); +#endif + } else if (TagsToStdout) + catFile(tagFileName()); + } + if (TagsToStdout) remove(tagFileName()); /* remove temporary file */ +} + +static void resizeTagFile(const long newSize) { + int result; + +#ifdef USE_REPLACEMENT_TRUNCATE + result = replacementTruncate(TagFile.name, newSize); +#else +#ifdef HAVE_TRUNCATE + result = truncate(TagFile.name, (off_t)newSize); +#else + const int fd = open(TagFile.name, O_RDWR); + + if (fd == -1) + result = -1; + else { +#ifdef HAVE_FTRUNCATE + result = ftruncate(fd, (off_t)newSize); +#else +#ifdef HAVE_CHSIZE + result = chsize(fd, newSize); +#endif +#endif + close(fd); + } +#endif +#endif + if (result == -1) + fprintf(errout, "Cannot shorten tag file: errno = %d\n", errno); +} + +static void writeEtagsIncludes(FILE *const fp) { + if (Option.etagsInclude) { + unsigned int i; + for (i = 0; i < stringListCount(Option.etagsInclude); ++i) { + vString *item = stringListItem(Option.etagsInclude, i); + fprintf(fp, "\f\n%s,include\n", vStringValue(item)); + } + } +} + +extern void closeTagFile(const boolean resize) { + long desiredSize, size; + + if (Option.etags) writeEtagsIncludes(TagFile.fp); + desiredSize = ftell(TagFile.fp); + fseek(TagFile.fp, 0L, SEEK_END); + size = ftell(TagFile.fp); + fclose(TagFile.fp); + if (resize && desiredSize < size) { + DebugStatement(debugPrintf(DEBUG_STATUS, + "shrinking %s from %ld to %ld bytes\n", + TagFile.name, size, desiredSize);) + resizeTagFile(desiredSize); + } + sortTagFile(); + eFree(TagFile.name); + TagFile.name = NULL; +} + +extern void beginEtagsFile(void) { + TagFile.etags.fp = tempFile("w+b", &TagFile.etags.name); + TagFile.etags.byteCount = 0; +} + +extern void endEtagsFile(const char *const name) { + const char *line; + + fprintf(TagFile.fp, "\f\n%s,%ld\n", name, (long)TagFile.etags.byteCount); + if (TagFile.etags.fp != NULL) { + rewind(TagFile.etags.fp); + while ((line = readLine(TagFile.vLine, TagFile.etags.fp)) != NULL) + fputs(line, TagFile.fp); + fclose(TagFile.etags.fp); + remove(TagFile.etags.name); + eFree(TagFile.etags.name); + TagFile.etags.fp = NULL; + TagFile.etags.name = NULL; + } +} + +/* + * Tag entry management + */ + +/* This function copies the current line out to a specified file. It has no + * effect on the fileGetc () function. During copying, any '\' characters + * are doubled and a leading '^' or trailing '$' is also quoted. End of line + * characters (line feed or carriage return) are dropped. + */ +static size_t writeSourceLine(FILE *const fp, const char *const line) { + size_t length = 0; + const char *p; + + /* Write everything up to, but not including, a line end character. + */ + for (p = line; *p != '\0'; ++p) { + const int next = *(p + 1); + const int c = *p; + + if (c == CRETURN || c == NEWLINE) break; + + /* If character is '\', or a terminal '$', then quote it. + */ + if (c == BACKSLASH || c == (Option.backward ? '?' : '/') || + (c == '$' && (next == NEWLINE || next == CRETURN))) { + putc(BACKSLASH, fp); + ++length; + } + putc(c, fp); + ++length; + } + return length; +} + +/* Writes "line", stripping leading and duplicate white space. + */ +static size_t writeCompactSourceLine(FILE *const fp, const char *const line) { + boolean lineStarted = FALSE; + size_t length = 0; + const char *p; + int c; + + /* Write everything up to, but not including, the newline. + */ + for (p = line, c = *p; c != NEWLINE && c != '\0'; c = *++p) { + if (lineStarted || !isspace(c)) /* ignore leading spaces */ + { + lineStarted = TRUE; + if (isspace(c)) { + int next; + + /* Consume repeating white space. + */ + while (next = *(p + 1), isspace(next) && next != NEWLINE) ++p; + c = ' '; /* force space character for any white space */ + } + if (c != CRETURN || *(p + 1) != NEWLINE) { + putc(c, fp); + ++length; + } + } + } + return length; +} + +static int writeXrefEntry(const tagEntryInfo *const tag) { + const char *const line = + readSourceLine(TagFile.vLine, tag->filePosition, NULL); + int length; + + if (Option.tagFileFormat == 1) + length = fprintf(TagFile.fp, "%-16s %4lu %-16s ", tag->name, + tag->lineNumber, tag->sourceFileName); + else + length = fprintf(TagFile.fp, "%-16s %-10s %4lu %-16s ", tag->name, + tag->kindName, tag->lineNumber, tag->sourceFileName); + + length += writeCompactSourceLine(TagFile.fp, line); + putc(NEWLINE, TagFile.fp); + ++length; + + return length; +} + +/* Truncates the text line containing the tag at the character following the + * tag, providing a character which designates the end of the tag. + */ +static void truncateTagLine(char *const line, const char *const token, + const boolean discardNewline) { + char *p = strstr(line, token); + + if (p != NULL) { + p += strlen(token); + if (*p != '\0' && !(*p == '\n' && discardNewline)) + ++p; /* skip past character terminating character */ + *p = '\0'; + } +} + +static int writeEtagsEntry(const tagEntryInfo *const tag) { + int length; + + if (tag->isFileEntry) + length = fprintf(TagFile.etags.fp, "\177%s\001%lu,0\n", tag->name, + tag->lineNumber); + else { + long seekValue; + char *const line = + readSourceLine(TagFile.vLine, tag->filePosition, &seekValue); + + if (tag->truncateLine) + truncateTagLine(line, tag->name, TRUE); + else + line[strlen(line) - 1] = '\0'; + + length = fprintf(TagFile.etags.fp, "%s\177%s\001%lu,%ld\n", line, tag->name, + tag->lineNumber, seekValue); + } + TagFile.etags.byteCount += length; + + return length; +} + +static int addExtensionFields(const tagEntryInfo *const tag) { + const char *const kindKey = Option.extensionFields.kindKey ? "kind:" : ""; + boolean first = TRUE; + const char *separator = ";\""; + const char *const empty = ""; + int length = 0; +/* "sep" returns a value only the first time it is evaluated */ +#define sep (first ? (first = FALSE, separator) : empty) + + if (tag->kindName != NULL && + (Option.extensionFields.kindLong || + (Option.extensionFields.kind && tag->kind == '\0'))) + length += fprintf(TagFile.fp, "%s\t%s%s", sep, kindKey, tag->kindName); + else if (tag->kind != '\0' && + (Option.extensionFields.kind || + (Option.extensionFields.kindLong && tag->kindName == NULL))) + length += fprintf(TagFile.fp, "%s\t%s%c", sep, kindKey, tag->kind); + + if (Option.extensionFields.lineNumber) + length += fprintf(TagFile.fp, "%s\tline:%ld", sep, tag->lineNumber); + + if (Option.extensionFields.language && tag->language != NULL) + length += fprintf(TagFile.fp, "%s\tlanguage:%s", sep, tag->language); + + if (Option.extensionFields.scope && tag->extensionFields.scope[0] != NULL && + tag->extensionFields.scope[1] != NULL) + length += + fprintf(TagFile.fp, "%s\t%s:%s", sep, tag->extensionFields.scope[0], + tag->extensionFields.scope[1]); + + if (Option.extensionFields.typeRef && + tag->extensionFields.typeRef[0] != NULL && + tag->extensionFields.typeRef[1] != NULL) + length += fprintf(TagFile.fp, "%s\ttyperef:%s:%s", sep, + tag->extensionFields.typeRef[0], + tag->extensionFields.typeRef[1]); + + if (Option.extensionFields.fileScope && tag->isFileScope) + length += fprintf(TagFile.fp, "%s\tfile:", sep); + + if (Option.extensionFields.inheritance && + tag->extensionFields.inheritance != NULL) + length += fprintf(TagFile.fp, "%s\tinherits:%s", sep, + tag->extensionFields.inheritance); + + if (Option.extensionFields.access && tag->extensionFields.access != NULL) + length += + fprintf(TagFile.fp, "%s\taccess:%s", sep, tag->extensionFields.access); + + if (Option.extensionFields.implementation && + tag->extensionFields.implementation != NULL) + length += fprintf(TagFile.fp, "%s\timplementation:%s", sep, + tag->extensionFields.implementation); + + if (Option.extensionFields.signature && + tag->extensionFields.signature != NULL) + length += fprintf(TagFile.fp, "%s\tsignature:%s", sep, + tag->extensionFields.signature); + + return length; +#undef sep +} + +static int writePatternEntry(const tagEntryInfo *const tag) { + char *const line = readSourceLine(TagFile.vLine, tag->filePosition, NULL); + const int searchChar = Option.backward ? '?' : '/'; + boolean newlineTerminated; + int length = 0; + + if (line == NULL) error(FATAL, "bad tag in %s", vStringValue(File.name)); + if (tag->truncateLine) truncateTagLine(line, tag->name, FALSE); + newlineTerminated = (boolean)(line[strlen(line) - 1] == '\n'); + + length += fprintf(TagFile.fp, "%c^", searchChar); + length += writeSourceLine(TagFile.fp, line); + length += + fprintf(TagFile.fp, "%s%c", newlineTerminated ? "$" : "", searchChar); + + return length; +} + +static int writeLineNumberEntry(const tagEntryInfo *const tag) { + return fprintf(TagFile.fp, "%lu", tag->lineNumber); +} + +static int writeCtagsEntry(const tagEntryInfo *const tag) { + int length = fprintf(TagFile.fp, "%s\t%s\t", tag->name, tag->sourceFileName); + + if (tag->lineNumberEntry) + length += writeLineNumberEntry(tag); + else + length += writePatternEntry(tag); + + if (includeExtensionFlags()) length += addExtensionFields(tag); + + length += fprintf(TagFile.fp, "\n"); + + return length; +} + +extern void makeTagEntry(const tagEntryInfo *const tag) { + Assert(tag->name != NULL); + if (tag->name[0] == '\0') + error(WARNING, "ignoring null tag in %s", vStringValue(File.name)); + else { + int length = 0; + + DebugStatement(debugEntry(tag);) if (Option.xref) { + if (!tag->isFileEntry) length = writeXrefEntry(tag); + } + else if (Option.etags) length = writeEtagsEntry(tag); + else length = writeCtagsEntry(tag); + + ++TagFile.numTags.added; + rememberMaxLengths(strlen(tag->name), (size_t)length); + DebugStatement(fflush(TagFile.fp);) + } +} + +extern void initTagEntry(tagEntryInfo *const e, const char *const name) { + Assert(File.source.name != NULL); + memset(e, 0, sizeof(tagEntryInfo)); + e->lineNumberEntry = (boolean)(Option.locate == EX_LINENUM); + e->lineNumber = getSourceLineNumber(); + e->language = getSourceLanguageName(); + e->filePosition = getInputFilePosition(); + e->sourceFileName = getSourceFileTagPath(); + e->name = name; +} + +/* vi:set tabstop=4 shiftwidth=4: */ diff --git a/third_party/ctags/entry.h b/third_party/ctags/entry.h new file mode 100644 index 00000000..cde8e1f4 --- /dev/null +++ b/third_party/ctags/entry.h @@ -0,0 +1,93 @@ +#ifndef _ENTRY_H +#define _ENTRY_H +#include "third_party/ctags/general.h" +/* must always come first */ +#include "libc/stdio/stdio.h" +#include "third_party/ctags/vstring.h" + +/* + * MACROS + */ +#define WHOLE_FILE -1L + +/* + * DATA DECLARATIONS + */ + +/* Maintains the state of the tag file. + */ +typedef struct eTagFile { + char *name; + char *directory; + FILE *fp; + struct sNumTags { + unsigned long added, prev; + } numTags; + struct sMax { + size_t line, tag, file; + } max; + struct sEtags { + char *name; + FILE *fp; + size_t byteCount; + } etags; + vString *vLine; +} tagFile; + +typedef struct sTagFields { + unsigned int count; /* number of additional extension flags */ + const char *const *label; /* list of labels for extension flags */ + const char *const *value; /* list of values for extension flags */ +} tagFields; + +/* Information about the current tag candidate. + */ +typedef struct sTagEntryInfo { + boolean lineNumberEntry; /* pattern or line number entry */ + unsigned long lineNumber; /* line number of tag */ + fpos_t filePosition; /* file position of line containing tag */ + const char *language; /* language of source file */ + boolean isFileScope; /* is tag visibile only within source file? */ + boolean isFileEntry; /* is this just an entry for a file name? */ + boolean truncateLine; /* truncate tag line at end of tag name? */ + const char *sourceFileName; /* name of source file */ + const char *name; /* name of the tag */ + const char *kindName; /* kind of tag */ + char kind; /* single character representation of kind */ + struct { + const char *access; + const char *fileScope; + const char *implementation; + const char *inheritance; + const char *scope[2]; /* value and key */ + const char *signature; + + /* type (union/struct/etc.) and name for a variable or typedef. */ + const char *typeRef[2]; /* e.g., "struct" and struct name */ + + } extensionFields; /* list of extension fields*/ +} tagEntryInfo; + +/* + * GLOBAL VARIABLES + */ +extern tagFile TagFile; + +/* + * FUNCTION PROTOTYPES + */ +extern void freeTagFileResources(void); +extern const char *tagFileName(void); +extern void copyBytes(FILE *const fromFp, FILE *const toFp, const long size); +extern void copyFile(const char *const from, const char *const to, + const long size); +extern void openTagFile(void); +extern void closeTagFile(const boolean resize); +extern void beginEtagsFile(void); +extern void endEtagsFile(const char *const name); +extern void makeTagEntry(const tagEntryInfo *const tag); +extern void initTagEntry(tagEntryInfo *const e, const char *const name); + +#endif /* _ENTRY_H */ + +/* vi:set tabstop=4 shiftwidth=4: */ diff --git a/third_party/ctags/erlang.c b/third_party/ctags/erlang.c new file mode 100644 index 00000000..dc83fa95 --- /dev/null +++ b/third_party/ctags/erlang.c @@ -0,0 +1,164 @@ +/* + * $Id: erlang.c 443 2006-05-30 04:37:13Z darren $ + * + * Copyright (c) 2003, Brent Fulgham + * + * This source code is released for free distribution under the terms of the + * GNU General Public License. + * + * This module contains functions for generating tags for Erlang language + * files. Some of the parsing constructs are based on the Emacs 'etags' + * program by Francesco Potori + */ +#include "third_party/ctags/general.h" +/* must always come first */ +#include "libc/str/str.h" +#include "third_party/ctags/entry.h" +#include "third_party/ctags/options.h" +#include "third_party/ctags/read.h" +#include "third_party/ctags/routines.h" +#include "third_party/ctags/vstring.h" + +/* + * DATA DEFINITIONS + */ +typedef enum { K_MACRO, K_FUNCTION, K_MODULE, K_RECORD } erlangKind; + +static kindOption ErlangKinds[] = { + {TRUE, 'd', "macro", "macro definitions"}, + {TRUE, 'f', "function", "functions"}, + {TRUE, 'm', "module", "modules"}, + {TRUE, 'r', "record", "record definitions"}, +}; + +/* + * FUNCTION DEFINITIONS + */ +/* tagEntryInfo and vString should be preinitialized/preallocated but not + * necessary. If successful you will find class name in vString + */ + +static boolean isIdentifierFirstCharacter(int c) { + return (boolean)(isalpha(c)); +} + +static boolean isIdentifierCharacter(int c) { + return (boolean)(isalnum(c) || c == '_' || c == ':'); +} + +static const unsigned char *skipSpace(const unsigned char *cp) { + while (isspace((int)*cp)) ++cp; + return cp; +} + +static const unsigned char *parseIdentifier(const unsigned char *cp, + vString *const identifier) { + vStringClear(identifier); + while (isIdentifierCharacter((int)*cp)) { + vStringPut(identifier, (int)*cp); + ++cp; + } + vStringTerminate(identifier); + return cp; +} + +static void makeMemberTag(vString *const identifier, erlangKind kind, + vString *const module) { + if (ErlangKinds[kind].enabled && vStringLength(identifier) > 0) { + tagEntryInfo tag; + initTagEntry(&tag, vStringValue(identifier)); + tag.kindName = ErlangKinds[kind].name; + tag.kind = ErlangKinds[kind].letter; + + if (module != NULL && vStringLength(module) > 0) { + tag.extensionFields.scope[0] = "module"; + tag.extensionFields.scope[1] = vStringValue(module); + } + makeTagEntry(&tag); + } +} + +static void parseModuleTag(const unsigned char *cp, vString *const module) { + vString *const identifier = vStringNew(); + parseIdentifier(cp, identifier); + makeSimpleTag(identifier, ErlangKinds, K_MODULE); + + /* All further entries go in the new module */ + vStringCopy(module, identifier); + vStringDelete(identifier); +} + +static void parseSimpleTag(const unsigned char *cp, erlangKind kind) { + vString *const identifier = vStringNew(); + parseIdentifier(cp, identifier); + makeSimpleTag(identifier, ErlangKinds, kind); + vStringDelete(identifier); +} + +static void parseFunctionTag(const unsigned char *cp, vString *const module) { + vString *const identifier = vStringNew(); + parseIdentifier(cp, identifier); + makeMemberTag(identifier, K_FUNCTION, module); + vStringDelete(identifier); +} + +/* + * Directives are of the form: + * -module(foo) + * -define(foo, bar) + * -record(graph, {vtab = notable, cyclic = true}). + */ +static void parseDirective(const unsigned char *cp, vString *const module) { + /* + * A directive will be either a record definition or a directive. + * Record definitions are handled separately + */ + vString *const directive = vStringNew(); + const char *const drtv = vStringValue(directive); + cp = parseIdentifier(cp, directive); + cp = skipSpace(cp); + if (*cp == '(') ++cp; + + if (strcmp(drtv, "record") == 0) + parseSimpleTag(cp, K_RECORD); + else if (strcmp(drtv, "define") == 0) + parseSimpleTag(cp, K_MACRO); + else if (strcmp(drtv, "module") == 0) + parseModuleTag(cp, module); + /* Otherwise, it was an import, export, etc. */ + + vStringDelete(directive); +} + +static void findErlangTags(void) { + vString *const module = vStringNew(); + const unsigned char *line; + + while ((line = fileReadLine()) != NULL) { + const unsigned char *cp = line; + + if (*cp == '%') /* skip initial comment */ + continue; + if (*cp == '"') /* strings sometimes start in column one */ + continue; + + if (*cp == '-') { + ++cp; /* Move off of the '-' */ + parseDirective(cp, module); + } else if (isIdentifierFirstCharacter((int)*cp)) + parseFunctionTag(cp, module); + } + vStringDelete(module); +} + +extern parserDefinition *ErlangParser(void) { + static const char *const extensions[] = {"erl", "ERL", "hrl", "HRL", NULL}; + parserDefinition *def = parserNew("Erlang"); + def->kinds = ErlangKinds; + def->kindCount = KIND_COUNT(ErlangKinds); + def->extensions = extensions; + def->parser = findErlangTags; + return def; +} + +/* vi:set tabstop=4 shiftwidth=4: */ diff --git a/third_party/ctags/flex.c b/third_party/ctags/flex.c new file mode 100644 index 00000000..891eed77 --- /dev/null +++ b/third_party/ctags/flex.c @@ -0,0 +1,2105 @@ +/* + * $Id: flex.c 666 2008-05-15 17:47:31Z dfishburn $ + * + * Copyright (c) 2008, David Fishburn + * + * This source code is released for free distribution under the + * terms of the GNU General Public License. + * + * This module contains functions for generating tags for Adobe languages. + * There are a number of different ones, but this will begin with: + * Flex + * MXML files (*.mMacromedia XML) + * ActionScript files (*.as) + * + * Flex 3 language reference + * http://livedocs.adobe.com/flex/3/langref/index.html + */ +#include "third_party/ctags/general.h" +/* must always come first */ +#include "libc/calls/calls.h" +#include "libc/runtime/runtime.h" +#include "libc/str/str.h" +#include "third_party/ctags/debug.h" +#include "third_party/ctags/entry.h" +#include "third_party/ctags/keyword.h" +#include "third_party/ctags/parse.h" +#include "third_party/ctags/read.h" +#include "third_party/ctags/routines.h" +#include "third_party/ctags/vstring.h" + +/* + * MACROS + */ +#define isType(token, t) (boolean)((token)->type == (t)) +#define isKeyword(token, k) (boolean)((token)->keyword == (k)) + +/* + * DATA DECLARATIONS + */ + +typedef enum eException { ExceptionNone, ExceptionEOF } exception_t; + +/* + * Tracks class and function names already created + */ +static stringList *ClassNames; +static stringList *FunctionNames; + +/* Used to specify type of keyword. + */ +typedef enum eKeywordId { + KEYWORD_NONE = -1, + KEYWORD_function, + KEYWORD_capital_function, + KEYWORD_object, + KEYWORD_capital_object, + KEYWORD_prototype, + KEYWORD_var, + KEYWORD_new, + KEYWORD_this, + KEYWORD_for, + KEYWORD_while, + KEYWORD_do, + KEYWORD_if, + KEYWORD_else, + KEYWORD_switch, + KEYWORD_try, + KEYWORD_catch, + KEYWORD_finally, + KEYWORD_public, + KEYWORD_private, + KEYWORD_static, + KEYWORD_class, + KEYWORD_id, + KEYWORD_name, + KEYWORD_script, + KEYWORD_cdata, + KEYWORD_mx, + KEYWORD_fx, + KEYWORD_override +} keywordId; + +/* Used to determine whether keyword is valid for the token language and + * what its ID is. + */ +typedef struct sKeywordDesc { + const char *name; + keywordId id; +} keywordDesc; + +typedef enum eTokenType { + TOKEN_UNDEFINED, + TOKEN_CHARACTER, + TOKEN_CLOSE_PAREN, + TOKEN_SEMICOLON, + TOKEN_COLON, + TOKEN_COMMA, + TOKEN_KEYWORD, + TOKEN_OPEN_PAREN, + TOKEN_OPERATOR, + TOKEN_IDENTIFIER, + TOKEN_STRING, + TOKEN_PERIOD, + TOKEN_OPEN_CURLY, + TOKEN_CLOSE_CURLY, + TOKEN_EQUAL_SIGN, + TOKEN_EXCLAMATION, + TOKEN_FORWARD_SLASH, + TOKEN_OPEN_SQUARE, + TOKEN_CLOSE_SQUARE, + TOKEN_OPEN_MXML, + TOKEN_CLOSE_MXML, + TOKEN_CLOSE_SGML, + TOKEN_LESS_THAN, + TOKEN_GREATER_THAN, + TOKEN_QUESTION_MARK, + TOKEN_OPEN_NAMESPACE +} tokenType; + +typedef struct sTokenInfo { + tokenType type; + keywordId keyword; + vString *string; + vString *scope; + unsigned long lineNumber; + fpos_t filePosition; + int nestLevel; + boolean ignoreTag; + boolean isClass; +} tokenInfo; + +/* + * DATA DEFINITIONS + */ + +static langType Lang_js; + +static jmp_buf Exception; + +typedef enum { + FLEXTAG_FUNCTION, + FLEXTAG_CLASS, + FLEXTAG_METHOD, + FLEXTAG_PROPERTY, + FLEXTAG_VARIABLE, + FLEXTAG_MXTAG, + FLEXTAG_COUNT +} flexKind; + +static kindOption FlexKinds[] = {{TRUE, 'f', "function", "functions"}, + {TRUE, 'c', "class", "classes"}, + {TRUE, 'm', "method", "methods"}, + {TRUE, 'p', "property", "properties"}, + {TRUE, 'v', "variable", "global variables"}, + {TRUE, 'x', "mxtag", "mxtags"}}; + +static const keywordDesc FlexKeywordTable[] = { + /* keyword keyword ID */ + {"function", KEYWORD_function}, + {"Function", KEYWORD_capital_function}, + {"object", KEYWORD_object}, + {"Object", KEYWORD_capital_object}, + {"prototype", KEYWORD_prototype}, + {"var", KEYWORD_var}, + {"new", KEYWORD_new}, + {"this", KEYWORD_this}, + {"for", KEYWORD_for}, + {"while", KEYWORD_while}, + {"do", KEYWORD_do}, + {"if", KEYWORD_if}, + {"else", KEYWORD_else}, + {"switch", KEYWORD_switch}, + {"try", KEYWORD_try}, + {"catch", KEYWORD_catch}, + {"finally", KEYWORD_finally}, + {"public", KEYWORD_public}, + {"private", KEYWORD_private}, + {"static", KEYWORD_static}, + {"class", KEYWORD_class}, + {"id", KEYWORD_id}, + {"name", KEYWORD_name}, + {"script", KEYWORD_script}, + {"cdata", KEYWORD_cdata}, + {"mx", KEYWORD_mx}, + {"fx", KEYWORD_fx}, + {"override", KEYWORD_override}}; + +/* + * FUNCTION DEFINITIONS + */ + +/* Recursive functions */ +static void parseFunction(tokenInfo *const token); +static boolean parseBlock(tokenInfo *const token, tokenInfo *const parent); +static boolean parseLine(tokenInfo *const token); +static boolean parseActionScript(tokenInfo *const token); +static boolean parseMXML(tokenInfo *const token); + +static boolean isIdentChar(const int c) { + return (boolean)(isalpha(c) || isdigit(c) || c == '$' || c == '@' || + c == '_' || c == '#'); +} + +static void buildFlexKeywordHash(void) { + const size_t count = sizeof(FlexKeywordTable) / sizeof(FlexKeywordTable[0]); + size_t i; + for (i = 0; i < count; ++i) { + const keywordDesc *const p = &FlexKeywordTable[i]; + addKeyword(p->name, Lang_js, (int)p->id); + } +} + +static tokenInfo *newToken(void) { + tokenInfo *const token = xMalloc(1, tokenInfo); + + token->type = TOKEN_UNDEFINED; + token->keyword = KEYWORD_NONE; + token->string = vStringNew(); + token->scope = vStringNew(); + token->nestLevel = 0; + token->isClass = FALSE; + token->ignoreTag = FALSE; + token->lineNumber = getSourceLineNumber(); + token->filePosition = getInputFilePosition(); + + return token; +} + +static void deleteToken(tokenInfo *const token) { + vStringDelete(token->string); + vStringDelete(token->scope); + eFree(token); +} + +/* + * Tag generation functions + */ + +static void makeConstTag(tokenInfo *const token, const flexKind kind) { + if (FlexKinds[kind].enabled && !token->ignoreTag) { + const char *const name = vStringValue(token->string); + tagEntryInfo e; + initTagEntry(&e, name); + + e.lineNumber = token->lineNumber; + e.filePosition = token->filePosition; + e.kindName = FlexKinds[kind].name; + e.kind = FlexKinds[kind].letter; + + makeTagEntry(&e); + } +} + +static void makeFlexTag(tokenInfo *const token, flexKind kind) { + vString *fulltag; + + if (FlexKinds[kind].enabled && !token->ignoreTag) { + DebugStatement( + debugPrintf( + DEBUG_PARSE, + "\n makeFlexTag start: token isClass:%d scope:%s name:%s\n", + token->isClass, vStringValue(token->scope), + vStringValue(token->string));); + if (kind == FLEXTAG_FUNCTION && token->isClass) { + kind = FLEXTAG_METHOD; + } + /* + * If a scope has been added to the token, change the token + * string to include the scope when making the tag. + */ + if (vStringLength(token->scope) > 0) { + fulltag = vStringNew(); + vStringCopy(fulltag, token->scope); + vStringCatS(fulltag, "."); + vStringCatS(fulltag, vStringValue(token->string)); + vStringTerminate(fulltag); + vStringCopy(token->string, fulltag); + vStringDelete(fulltag); + } + makeConstTag(token, kind); + } +} + +static void makeClassTag(tokenInfo *const token) { + vString *fulltag; + + if (!token->ignoreTag) { + fulltag = vStringNew(); + if (vStringLength(token->scope) > 0) { + vStringCopy(fulltag, token->scope); + vStringCatS(fulltag, "."); + vStringCatS(fulltag, vStringValue(token->string)); + } else { + vStringCopy(fulltag, token->string); + } + vStringTerminate(fulltag); + if (!stringListHas(ClassNames, vStringValue(fulltag))) { + stringListAdd(ClassNames, vStringNewCopy(fulltag)); + makeFlexTag(token, FLEXTAG_CLASS); + } + vStringDelete(fulltag); + } +} + +static void makeMXTag(tokenInfo *const token) { + vString *fulltag; + + if (!token->ignoreTag) { + fulltag = vStringNew(); + if (vStringLength(token->scope) > 0) { + vStringCopy(fulltag, token->scope); + vStringCatS(fulltag, "."); + vStringCatS(fulltag, vStringValue(token->string)); + } else { + vStringCopy(fulltag, token->string); + } + vStringTerminate(fulltag); + makeFlexTag(token, FLEXTAG_MXTAG); + vStringDelete(fulltag); + } +} + +static void makeFunctionTag(tokenInfo *const token) { + vString *fulltag; + + if (!token->ignoreTag) { + fulltag = vStringNew(); + if (vStringLength(token->scope) > 0) { + vStringCopy(fulltag, token->scope); + vStringCatS(fulltag, "."); + vStringCatS(fulltag, vStringValue(token->string)); + } else { + vStringCopy(fulltag, token->string); + } + vStringTerminate(fulltag); + if (!stringListHas(FunctionNames, vStringValue(fulltag))) { + stringListAdd(FunctionNames, vStringNewCopy(fulltag)); + makeFlexTag(token, FLEXTAG_FUNCTION); + } + vStringDelete(fulltag); + } +} + +/* + * Parsing functions + */ + +static void parseString(vString *const string, const int delimiter) { + boolean end = FALSE; + while (!end) { + int c = fileGetc(); + if (c == EOF) + end = TRUE; + else if (c == '\\') { + c = fileGetc(); /* This maybe a ' or ". */ + vStringPut(string, c); + } else if (c == delimiter) + end = TRUE; + else + vStringPut(string, c); + } + vStringTerminate(string); +} + +/* Read a C identifier beginning with "firstChar" and places it into + * "name". + */ +static void parseIdentifier(vString *const string, const int firstChar) { + int c = firstChar; + Assert(isIdentChar(c)); + do { + vStringPut(string, c); + c = fileGetc(); + } while (isIdentChar(c)); + vStringTerminate(string); + if (!isspace(c)) fileUngetc(c); /* unget non-identifier character */ +} + +static void readToken(tokenInfo *const token) { + int c; + + token->type = TOKEN_UNDEFINED; + token->keyword = KEYWORD_NONE; + vStringClear(token->string); + +getNextChar: + do { + c = fileGetc(); + token->lineNumber = getSourceLineNumber(); + token->filePosition = getInputFilePosition(); + } while (c == '\t' || c == ' ' || c == '\n'); + + switch (c) { + case EOF: + longjmp(Exception, (int)ExceptionEOF); + break; + case '(': + token->type = TOKEN_OPEN_PAREN; + break; + case ')': + token->type = TOKEN_CLOSE_PAREN; + break; + case ';': + token->type = TOKEN_SEMICOLON; + break; + case ',': + token->type = TOKEN_COMMA; + break; + case '.': + token->type = TOKEN_PERIOD; + break; + case ':': + token->type = TOKEN_COLON; + break; + case '{': + token->type = TOKEN_OPEN_CURLY; + break; + case '}': + token->type = TOKEN_CLOSE_CURLY; + break; + case '=': + token->type = TOKEN_EQUAL_SIGN; + break; + case '[': + token->type = TOKEN_OPEN_SQUARE; + break; + case ']': + token->type = TOKEN_CLOSE_SQUARE; + break; + case '?': + token->type = TOKEN_QUESTION_MARK; + break; + + case '\'': + case '"': + token->type = TOKEN_STRING; + parseString(token->string, c); + token->lineNumber = getSourceLineNumber(); + token->filePosition = getInputFilePosition(); + break; + + case '\\': + c = fileGetc(); + if (c != '\\' && c != '"' && !isspace(c)) fileUngetc(c); + token->type = TOKEN_CHARACTER; + token->lineNumber = getSourceLineNumber(); + token->filePosition = getInputFilePosition(); + break; + + case '/': { + int d = fileGetc(); + if ((d != '*') && /* is this the start of a comment? */ + (d != '/') && /* is a one line comment? */ + (d != '>')) /* is this a close XML tag? */ + { + fileUngetc(d); + token->type = TOKEN_FORWARD_SLASH; + token->lineNumber = getSourceLineNumber(); + token->filePosition = getInputFilePosition(); + } else { + if (d == '*') { + do { + fileSkipToCharacter('*'); + c = fileGetc(); + if (c == '/') + break; + else + fileUngetc(c); + } while (c != EOF && c != '\0'); + goto getNextChar; + } else if (d == '/') /* is this the start of a comment? */ + { + fileSkipToCharacter('\n'); + goto getNextChar; + } else if (d == '>') /* is this the start of a comment? */ + { + token->type = TOKEN_CLOSE_SGML; + token->lineNumber = getSourceLineNumber(); + token->filePosition = getInputFilePosition(); + } + } + break; + } + + case '<': { + /* + * An XML comment looks like this + * + */ + int d = fileGetc(); + + if ((d != '!') && /* is this the start of a comment? */ + (d != '/') && /* is this the start of a closing mx tag */ + (d != 'm') && /* is this the start of a mx tag */ + (d != 'f') && /* is this the start of a fx tag */ + (d != 's')) /* is this the start of a spark tag */ + { + fileUngetc(d); + token->type = TOKEN_LESS_THAN; + token->lineNumber = getSourceLineNumber(); + token->filePosition = getInputFilePosition(); + break; + } else { + if (d == '!') { + int e = fileGetc(); + if (e != '-') /* is this the start of a comment? */ + { + fileUngetc(e); + fileUngetc(d); + token->type = TOKEN_LESS_THAN; + token->lineNumber = getSourceLineNumber(); + token->filePosition = getInputFilePosition(); + } else { + if (e == '-') { + int f = fileGetc(); + if (f != '-') /* is this the start of a comment? */ + { + fileUngetc(f); + fileUngetc(e); + fileUngetc(d); + token->type = TOKEN_LESS_THAN; + token->lineNumber = getSourceLineNumber(); + token->filePosition = getInputFilePosition(); + } else { + if (f == '-') { + do { + fileSkipToCharacter('-'); + c = fileGetc(); + if (c == '-') { + d = fileGetc(); + if (d == '>') + break; + else { + fileUngetc(d); + fileUngetc(c); + } + break; + } else + fileUngetc(c); + } while (c != EOF && c != '\0'); + goto getNextChar; + } + } + } + } + } else if (d == 'm' || d == 'f' || d == 's') { + int e = fileGetc(); + if ((d == 'm' || d == 'f') && + e != 'x') /* continuing an mx or fx tag */ + { + fileUngetc(e); + fileUngetc(d); + token->type = TOKEN_LESS_THAN; + token->lineNumber = getSourceLineNumber(); + token->filePosition = getInputFilePosition(); + break; + } else { + if ((d == 'm' || d == 'f') && e == 'x') { + int f = fileGetc(); + if (f != ':') /* start of the tag */ + { + fileUngetc(f); + fileUngetc(e); + fileUngetc(d); + token->type = TOKEN_LESS_THAN; + token->lineNumber = getSourceLineNumber(); + token->filePosition = getInputFilePosition(); + break; + } else { + token->type = TOKEN_OPEN_MXML; + token->lineNumber = getSourceLineNumber(); + token->filePosition = getInputFilePosition(); + break; + } + } + if (d == 's' && e == ':') /* continuing a spark tag */ + { + token->type = TOKEN_OPEN_MXML; + token->lineNumber = getSourceLineNumber(); + token->filePosition = getInputFilePosition(); + break; + } else { + fileUngetc(e); + fileUngetc(d); + token->type = TOKEN_LESS_THAN; + token->lineNumber = getSourceLineNumber(); + token->filePosition = getInputFilePosition(); + break; + } + } + } else if (d == '/') { + int e = fileGetc(); + if (!(e == 'm' || e == 'f' || e == 's')) { + fileUngetc(e); + fileUngetc(d); + token->type = TOKEN_LESS_THAN; + token->lineNumber = getSourceLineNumber(); + token->filePosition = getInputFilePosition(); + break; + } else { + int f = fileGetc(); + if ((e == 'm' || e == 'f') && + f != 'x') /* continuing an mx or fx tag */ + { + fileUngetc(f); + fileUngetc(e); + token->type = TOKEN_LESS_THAN; + token->lineNumber = getSourceLineNumber(); + token->filePosition = getInputFilePosition(); + break; + } else { + if (f == 'x') { + int g = fileGetc(); + if (g != ':') /* is this the start of a comment? */ + { + fileUngetc(g); + fileUngetc(f); + fileUngetc(e); + token->type = TOKEN_LESS_THAN; + token->lineNumber = getSourceLineNumber(); + token->filePosition = getInputFilePosition(); + break; + } else { + token->type = TOKEN_CLOSE_MXML; + token->lineNumber = getSourceLineNumber(); + token->filePosition = getInputFilePosition(); + break; + } + } + if (e == 's' && f == ':') /* continuing a spark tag */ + { + token->type = TOKEN_CLOSE_MXML; + token->lineNumber = getSourceLineNumber(); + token->filePosition = getInputFilePosition(); + break; + } else { + fileUngetc(f); + fileUngetc(e); + token->type = TOKEN_LESS_THAN; + token->lineNumber = getSourceLineNumber(); + token->filePosition = getInputFilePosition(); + break; + } + } + } + } + } + break; + } + + case '>': + token->type = TOKEN_GREATER_THAN; + token->lineNumber = getSourceLineNumber(); + token->filePosition = getInputFilePosition(); + break; + + case '!': + token->type = TOKEN_EXCLAMATION; + /*token->lineNumber = getSourceLineNumber (); + token->filePosition = getInputFilePosition ();*/ + break; + + default: + if (!isIdentChar(c)) + token->type = TOKEN_UNDEFINED; + else { + parseIdentifier(token->string, c); + token->lineNumber = getSourceLineNumber(); + token->filePosition = getInputFilePosition(); + token->keyword = analyzeToken(token->string, Lang_js); + if (isKeyword(token, KEYWORD_NONE)) + token->type = TOKEN_IDENTIFIER; + else + token->type = TOKEN_KEYWORD; + } + break; + } +} + +static void copyToken(tokenInfo *const dest, tokenInfo *const src) { + dest->nestLevel = src->nestLevel; + dest->lineNumber = src->lineNumber; + dest->filePosition = src->filePosition; + dest->type = src->type; + dest->keyword = src->keyword; + dest->isClass = src->isClass; + vStringCopy(dest->string, src->string); + vStringCopy(dest->scope, src->scope); +} + +/* + * Token parsing functions + */ + +static void skipArgumentList(tokenInfo *const token) { + int nest_level = 0; + + /* + * Other databases can have arguments with fully declared + * datatypes: + * ( name varchar(30), text binary(10) ) + * So we must check for nested open and closing parantheses + */ + + if (isType(token, TOKEN_OPEN_PAREN)) /* arguments? */ + { + nest_level++; + while (!(isType(token, TOKEN_CLOSE_PAREN) && (nest_level == 0))) { + readToken(token); + if (isType(token, TOKEN_OPEN_PAREN)) { + nest_level++; + } + if (isType(token, TOKEN_CLOSE_PAREN)) { + if (nest_level > 0) { + nest_level--; + } + } + } + readToken(token); + } +} + +static void skipArrayList(tokenInfo *const token) { + int nest_level = 0; + + /* + * Handle square brackets + * var name[1] + * So we must check for nested open and closing square brackets + */ + + if (isType(token, TOKEN_OPEN_SQUARE)) /* arguments? */ + { + nest_level++; + while (!(isType(token, TOKEN_CLOSE_SQUARE) && (nest_level == 0))) { + readToken(token); + if (isType(token, TOKEN_OPEN_SQUARE)) { + nest_level++; + } + if (isType(token, TOKEN_CLOSE_SQUARE)) { + if (nest_level > 0) { + nest_level--; + } + } + } + readToken(token); + } +} + +static void addContext(tokenInfo *const parent, const tokenInfo *const child) { + if (vStringLength(parent->string) > 0) { + vStringCatS(parent->string, "."); + } + vStringCatS(parent->string, vStringValue(child->string)); + vStringTerminate(parent->string); +} + +static void addToScope(tokenInfo *const token, vString *const extra) { + if (vStringLength(token->scope) > 0) { + vStringCatS(token->scope, "."); + } + vStringCatS(token->scope, vStringValue(extra)); + vStringTerminate(token->scope); +} + +/* + * Scanning functions + */ + +static void findCmdTerm(tokenInfo *const token) { + /* + * Read until we find either a semicolon or closing brace. + * Any nested braces will be handled within. + */ + while ( + !(isType(token, TOKEN_SEMICOLON) || isType(token, TOKEN_CLOSE_CURLY))) { + /* Handle nested blocks */ + if (isType(token, TOKEN_OPEN_CURLY)) { + parseBlock(token, token); + } else if (isType(token, TOKEN_OPEN_PAREN)) { + skipArgumentList(token); + } else { + readToken(token); + } + } +} + +static void parseSwitch(tokenInfo *const token) { + /* + * switch (expression){ + * case value1: + * statement; + * break; + * case value2: + * statement; + * break; + * default : statement; + * } + */ + + readToken(token); + + if (isType(token, TOKEN_OPEN_PAREN)) { + skipArgumentList(token); + } + + if (isType(token, TOKEN_OPEN_CURLY)) { + do { + readToken(token); + } while (!( + isType(token, TOKEN_CLOSE_SGML) || isType(token, TOKEN_CLOSE_MXML) || + isType(token, TOKEN_CLOSE_CURLY) || isType(token, TOKEN_GREATER_THAN))); + } +} + +static void parseLoop(tokenInfo *const token) { + /* + * Handles these statements + * for (x=0; x<3; x++) + * document.write("This text is repeated three times
"); + * + * for (x=0; x<3; x++) + * { + * document.write("This text is repeated three times
"); + * } + * + * while (number<5){ + * document.write(number+"
"); + * number++; + * } + * + * do{ + * document.write(number+"
"); + * number++; + * } + * while (number<5); + */ + + if (isKeyword(token, KEYWORD_for) || isKeyword(token, KEYWORD_while)) { + readToken(token); + + if (isType(token, TOKEN_OPEN_PAREN)) { + /* + * Handle nameless functions, these will only + * be considered methods. + */ + skipArgumentList(token); + } + + if (isType(token, TOKEN_OPEN_CURLY)) { + /* + * This will be either a function or a class. + * We can only determine this by checking the body + * of the function. If we find a "this." we know + * it is a class, otherwise it is a function. + */ + parseBlock(token, token); + } else { + parseLine(token); + } + } else if (isKeyword(token, KEYWORD_do)) { + readToken(token); + + if (isType(token, TOKEN_OPEN_CURLY)) { + /* + * This will be either a function or a class. + * We can only determine this by checking the body + * of the function. If we find a "this." we know + * it is a class, otherwise it is a function. + */ + parseBlock(token, token); + } else { + parseLine(token); + } + + readToken(token); + + if (isKeyword(token, KEYWORD_while)) { + readToken(token); + + if (isType(token, TOKEN_OPEN_PAREN)) { + /* + * Handle nameless functions, these will only + * be considered methods. + */ + skipArgumentList(token); + } + } + } +} + +static boolean parseIf(tokenInfo *const token) { + boolean read_next_token = TRUE; + /* + * If statements have two forms + * if ( ... ) + * one line; + * + * if ( ... ) + * statement; + * else + * statement + * + * if ( ... ) { + * multiple; + * statements; + * } + * + * + * if ( ... ) { + * return elem + * } + * + * This example if correctly written, but the + * else contains only 1 statement without a terminator + * since the function finishes with the closing brace. + * + * function a(flag){ + * if(flag) + * test(1); + * else + * test(2) + * } + * + * TODO: Deal with statements that can optional end + * without a semi-colon. Currently this messes up + * the parsing of blocks. + * Need to somehow detect this has happened, and either + * backup a token, or skip reading the next token if + * that is possible from all code locations. + * + */ + + readToken(token); + + if (isKeyword(token, KEYWORD_if)) { + /* + * Check for an "else if" and consume the "if" + */ + readToken(token); + } + + if (isType(token, TOKEN_OPEN_PAREN)) { + /* + * Handle nameless functions, these will only + * be considered methods. + */ + skipArgumentList(token); + } + + if (isType(token, TOKEN_OPEN_CURLY)) { + /* + * This will be either a function or a class. + * We can only determine this by checking the body + * of the function. If we find a "this." we know + * it is a class, otherwise it is a function. + */ + parseBlock(token, token); + } else { + findCmdTerm(token); + + /* + * The IF could be followed by an ELSE statement. + * This too could have two formats, a curly braced + * multiline section, or another single line. + */ + + if (isType(token, TOKEN_CLOSE_CURLY)) { + /* + * This statement did not have a line terminator. + */ + read_next_token = FALSE; + } else { + readToken(token); + + if (isType(token, TOKEN_CLOSE_CURLY)) { + /* + * This statement did not have a line terminator. + */ + read_next_token = FALSE; + } else { + if (isKeyword(token, KEYWORD_else)) read_next_token = parseIf(token); + } + } + } + return read_next_token; +} + +static void parseFunction(tokenInfo *const token) { + tokenInfo *const name = newToken(); + + /* + * This deals with these formats + * private static function ioErrorHandler( event:IOErrorEvent ):void { + */ + + if (isKeyword(token, KEYWORD_function)) { + readToken(token); + } + + copyToken(name, token); + /* Add scope in case this is an INNER function + addToScope(name, token->scope); + */ + + DebugStatement( + debugPrintf(DEBUG_PARSE, + "\n parseFunction: token isClass:%d scope:%s name:%s\n", + token->isClass, vStringValue(token->scope), + vStringValue(token->string));); + DebugStatement( + debugPrintf(DEBUG_PARSE, + "\n parseFunction: name isClass:%d scope:%s name:%s\n", + name->isClass, vStringValue(name->scope), + vStringValue(name->string));); + + readToken(token); + + if (isType(token, TOKEN_OPEN_PAREN)) skipArgumentList(token); + + if (isType(token, TOKEN_COLON)) { + /* + * function fname ():ReturnType + */ + readToken(token); + readToken(token); + } + + if (isType(token, TOKEN_OPEN_CURLY)) { + DebugStatement( + debugPrintf( + DEBUG_PARSE, + "\n parseFunction end: name isClass:%d scope:%s name:%s\n", + name->isClass, vStringValue(name->scope), + vStringValue(name->string));); + parseBlock(token, name); + DebugStatement( + debugPrintf( + DEBUG_PARSE, + "\n parseFunction end2: token isClass:%d scope:%s name:%s\n", + token->isClass, vStringValue(token->scope), + vStringValue(token->string));); + DebugStatement( + debugPrintf( + DEBUG_PARSE, + "\n parseFunction end2: token isClass:%d scope:%s name:%s\n", + token->isClass, vStringValue(token->scope), + vStringValue(token->string));); + DebugStatement( + debugPrintf( + DEBUG_PARSE, + "\n parseFunction end3: name isClass:%d scope:%s name:%s\n", + name->isClass, vStringValue(name->scope), + vStringValue(name->string));); + makeFunctionTag(name); + } + + findCmdTerm(token); + + deleteToken(name); +} + +static boolean parseBlock(tokenInfo *const token, tokenInfo *const parent) { + boolean read_next_token = TRUE; + vString *saveScope = vStringNew(); + + vStringClear(saveScope); + vStringCopy(saveScope, token->scope); + token->nestLevel++; + DebugStatement( + debugPrintf(DEBUG_PARSE, + "\n parseBlock start: token isClass:%d scope:%s name:%s\n", + token->isClass, vStringValue(token->scope), + vStringValue(token->string));); + /* + * Make this routine a bit more forgiving. + * If called on an open_curly advance it + */ + if (isType(token, TOKEN_OPEN_CURLY) && isKeyword(token, KEYWORD_NONE)) + readToken(token); + + if (!isType(token, TOKEN_CLOSE_CURLY)) { + /* + * Read until we find the closing brace, + * any nested braces will be handled within + */ + do { + if (isType(token, TOKEN_OPEN_CURLY)) { + /* Handle nested blocks */ + parseBlock(token, parent); + } else { + /* + * It is possible for a line to have no terminator + * if the following line is a closing brace. + * parseLine will detect this case and indicate + * whether we should read an additional token. + */ + read_next_token = parseLine(token); + } + + /* + * Always read a new token unless we find a statement without + * a ending terminator + */ + if (read_next_token) readToken(token); + + /* + * If we find a statement without a terminator consider the + * block finished, otherwise the stack will be off by one. + */ + } while (!isType(token, TOKEN_CLOSE_CURLY) && read_next_token); + } + + vStringDelete(saveScope); + token->nestLevel--; + + DebugStatement( + debugPrintf(DEBUG_PARSE, + "\n parseBlock end: token isClass:%d scope:%s name:%s\n", + token->isClass, vStringValue(token->scope), + vStringValue(token->string));); + return FALSE; +} + +static void parseMethods(tokenInfo *const token, tokenInfo *const class) { + tokenInfo *const name = newToken(); + + /* + * This deals with these formats + * validProperty : 2, + * validMethod : function(a,b) {} + * 'validMethod2' : function(a,b) {} + * container.dirtyTab = {'url': false, 'title':false, 'snapshot':false, + *'*': false} + */ + + do { + readToken(token); + if (isType(token, TOKEN_STRING) || isKeyword(token, KEYWORD_NONE)) { + copyToken(name, token); + + readToken(token); + if (isType(token, TOKEN_COLON)) { + readToken(token); + if (isKeyword(token, KEYWORD_function)) { + readToken(token); + if (isType(token, TOKEN_OPEN_PAREN)) { + skipArgumentList(token); + } + + if (isType(token, TOKEN_OPEN_CURLY)) { + addToScope(name, class->string); + makeFlexTag(name, FLEXTAG_METHOD); + parseBlock(token, name); + + /* + * Read to the closing curly, check next + * token, if a comma, we must loop again + */ + readToken(token); + } + } else { + addToScope(name, class->string); + makeFlexTag(name, FLEXTAG_PROPERTY); + + /* + * Read the next token, if a comma + * we must loop again + */ + readToken(token); + } + } + } + } while (isType(token, TOKEN_COMMA)); + + findCmdTerm(token); + + deleteToken(name); +} + +static boolean parseVar(tokenInfo *const token, boolean is_public) { + tokenInfo *const name = newToken(); + tokenInfo *const secondary_name = newToken(); + vString *saveScope = vStringNew(); + boolean is_terminated = TRUE; + + vStringClear(saveScope); + vStringCopy(saveScope, token->scope); + /* + * Variables are defined as: + * private static var lastFaultMessage:Date = new Date( 0 ); + * private static var webRequests:ArrayCollection = new ArrayCollection(); + */ + + if (isKeyword(token, KEYWORD_var)) { + readToken(token); + } + + /* Variable name */ + copyToken(name, token); + readToken(token); + + if (isType(token, TOKEN_COLON)) { + /* + * var vname ():DataType = new Date(); + * var vname ():DataType; + */ + readToken(token); + readToken(token); + } + + while (!isType(token, TOKEN_SEMICOLON)) { + readToken(token); + } + + if (isType(token, TOKEN_SEMICOLON)) { + /* + * Only create variables for global scope + */ + /* if ( token->nestLevel == 0 && is_global ) */ + if (is_public) { + if (isType(token, TOKEN_SEMICOLON)) makeFlexTag(name, FLEXTAG_VARIABLE); + } + } + + vStringCopy(token->scope, saveScope); + deleteToken(name); + deleteToken(secondary_name); + vStringDelete(saveScope); + + return is_terminated; +} + +static boolean parseClass(tokenInfo *const token) { + tokenInfo *const name = newToken(); + vString *saveScope = vStringNew(); + boolean saveIsClass = token->isClass; + + vStringClear(saveScope); + vStringCopy(saveScope, token->scope); + /* + * Variables are defined as: + * private static var lastFaultMessage:Date = new Date( 0 ); + * private static var webRequests:ArrayCollection = new ArrayCollection(); + */ + + if (isKeyword(token, KEYWORD_class)) { + readToken(token); + } + + token->isClass = TRUE; + /* Add class name to scope */ + addToScope(token, token->string); + /* Class name */ + copyToken(name, token); + readToken(token); + + DebugStatement( + debugPrintf(DEBUG_PARSE, + "\n parseClass start: token isClass:%d scope:%s name:%s\n", + token->isClass, vStringValue(token->scope), + vStringValue(token->string));); + if (isType(token, TOKEN_OPEN_CURLY)) { + makeClassTag(name); + parseBlock(token, name); + } + + DebugStatement( + debugPrintf(DEBUG_PARSE, + "\n parseClass end: token isClass:%d scope:%s name:%s\n", + token->isClass, vStringValue(token->scope), + vStringValue(token->string));); + vStringCopy(token->scope, saveScope); + token->isClass = saveIsClass; + deleteToken(name); + vStringDelete(saveScope); + + return TRUE; +} + +static boolean parseStatement(tokenInfo *const token) { + tokenInfo *const name = newToken(); + tokenInfo *const secondary_name = newToken(); + vString *saveScope = vStringNew(); + boolean is_public = FALSE; + boolean is_class = FALSE; + boolean is_terminated = TRUE; + boolean is_global = FALSE; + boolean is_prototype = FALSE; + vString *fulltag; + + vStringClear(saveScope); + vStringCopy(saveScope, token->scope); + DebugStatement( + debugPrintf(DEBUG_PARSE, + "\n parseStatement: token isClass:%d scope:%s name:%s\n", + token->isClass, vStringValue(token->scope), + vStringValue(token->string));); + /* + * Functions can be named or unnamed. + * This deals with these formats: + * Function + * validFunctionOne = function(a,b) {} + * testlib.validFunctionFive = function(a,b) {} + * var innerThree = function(a,b) {} + * var innerFour = (a,b) {} + * var D2 = secondary_fcn_name(a,b) {} + * var D3 = new Function("a", "b", "return a+b;"); + * Class + * testlib.extras.ValidClassOne = function(a,b) { + * this.a = a; + * } + * Class Methods + * testlib.extras.ValidClassOne.prototype = { + * 'validMethodOne' : function(a,b) {}, + * 'validMethodTwo' : function(a,b) {} + * } + * ValidClassTwo = function () + * { + * this.validMethodThree = function() {} + * // unnamed method + * this.validMethodFour = () {} + * } + * Database.prototype.validMethodThree = Database_getTodaysDate; + */ + + if (isKeyword(token, KEYWORD_public)) { + is_public = TRUE; + readToken(token); + } + + if (isKeyword(token, KEYWORD_private)) { + readToken(token); + } + + if (isKeyword(token, KEYWORD_static)) { + readToken(token); + } + + if (isType(token, TOKEN_KEYWORD)) { + switch (token->keyword) { + case KEYWORD_for: + case KEYWORD_while: + case KEYWORD_do: + parseLoop(token); + break; + case KEYWORD_if: + case KEYWORD_else: + case KEYWORD_try: + case KEYWORD_catch: + case KEYWORD_finally: + /* Common semantics */ + is_terminated = parseIf(token); + break; + case KEYWORD_switch: + parseSwitch(token); + break; + case KEYWORD_class: + parseClass(token); + return is_terminated; + break; + case KEYWORD_function: + parseFunction(token); + return is_terminated; + break; + case KEYWORD_var: + parseVar(token, is_public); + return is_terminated; + break; + default: + readToken(token); + break; + } + } + + copyToken(name, token); + + while (!isType(token, TOKEN_CLOSE_CURLY) && !isType(token, TOKEN_SEMICOLON) && + !isType(token, TOKEN_EQUAL_SIGN)) { + /* Potentially the name of the function */ + readToken(token); + if (isType(token, TOKEN_PERIOD)) { + /* + * Cannot be a global variable is it has dot references in the name + */ + is_global = FALSE; + do { + readToken(token); + if (isKeyword(token, KEYWORD_NONE)) { + if (is_class) { + vStringCopy(saveScope, token->scope); + addToScope(token, name->string); + } else + addContext(name, token); + } else if (isKeyword(token, KEYWORD_prototype)) { + /* + * When we reach the "prototype" tag, we infer: + * "BindAgent" is a class + * "build" is a method + * + * function BindAgent( repeatableIdName, newParentIdName ) { + * } + * + * CASE 1 + * Specified function name: "build" + * BindAgent.prototype.build = function( mode ) { + * ignore everything within this function + * } + * + * CASE 2 + * Prototype listing + * ValidClassOne.prototype = { + * 'validMethodOne' : function(a,b) {}, + * 'validMethodTwo' : function(a,b) {} + * } + * + */ + makeClassTag(name); + is_class = TRUE; + is_prototype = TRUE; + + /* + * There should a ".function_name" next. + */ + readToken(token); + if (isType(token, TOKEN_PERIOD)) { + /* + * Handle CASE 1 + */ + readToken(token); + if (isKeyword(token, KEYWORD_NONE)) { + vStringCopy(saveScope, token->scope); + addToScope(token, name->string); + + makeFlexTag(token, FLEXTAG_METHOD); + /* + * We can read until the end of the block / statement. + * We need to correctly parse any nested blocks, but + * we do NOT want to create any tags based on what is + * within the blocks. + */ + token->ignoreTag = TRUE; + /* + * Find to the end of the statement + */ + findCmdTerm(token); + token->ignoreTag = FALSE; + is_terminated = TRUE; + goto cleanUp; + } + } else if (isType(token, TOKEN_EQUAL_SIGN)) { + readToken(token); + if (isType(token, TOKEN_OPEN_CURLY)) { + /* + * Handle CASE 2 + * + * Creates tags for each of these class methods + * ValidClassOne.prototype = { + * 'validMethodOne' : function(a,b) {}, + * 'validMethodTwo' : function(a,b) {} + * } + */ + parseMethods(token, name); + /* + * Find to the end of the statement + */ + findCmdTerm(token); + token->ignoreTag = FALSE; + is_terminated = TRUE; + goto cleanUp; + } + } + } + readToken(token); + } while (isType(token, TOKEN_PERIOD)); + } + + if (isType(token, TOKEN_OPEN_PAREN)) skipArgumentList(token); + + if (isType(token, TOKEN_COLON)) { + /* + * Functions are of this form: + * function fname ():ReturnType { + */ + readToken(token); + readToken(token); + } + + if (isType(token, TOKEN_OPEN_SQUARE)) skipArrayList(token); + } + + if (isType(token, TOKEN_CLOSE_CURLY)) { + /* + * Reaching this section without having + * processed an open curly brace indicates + * the statement is most likely not terminated. + */ + is_terminated = FALSE; + goto cleanUp; + } + + if (isType(token, TOKEN_SEMICOLON)) { + /* + * Only create variables for global scope + */ + if (token->nestLevel == 0 && is_global) { + /* + * Handles this syntax: + * var g_var2; + */ + if (isType(token, TOKEN_SEMICOLON)) makeFlexTag(name, FLEXTAG_VARIABLE); + } + /* + * Statement has ended. + * This deals with calls to functions, like: + * alert(..); + */ + goto cleanUp; + } + + if (isType(token, TOKEN_EQUAL_SIGN)) { + readToken(token); + + if (isKeyword(token, KEYWORD_function)) { + readToken(token); + + if (isKeyword(token, KEYWORD_NONE) && !isType(token, TOKEN_OPEN_PAREN)) { + /* + * Functions of this format: + * var D2A = function theAdd(a, b) + * { + * return a+b; + * } + * Are really two separate defined functions and + * can be referenced in two ways: + * alert( D2A(1,2) ); // produces 3 + * alert( theAdd(1,2) ); // also produces 3 + * So it must have two tags: + * D2A + * theAdd + * Save the reference to the name for later use, once + * we have established this is a valid function we will + * create the secondary reference to it. + */ + copyToken(secondary_name, token); + readToken(token); + } + + if (isType(token, TOKEN_OPEN_PAREN)) skipArgumentList(token); + + if (isType(token, TOKEN_OPEN_CURLY)) { + /* + * This will be either a function or a class. + * We can only determine this by checking the body + * of the function. If we find a "this." we know + * it is a class, otherwise it is a function. + */ + if (token->isClass) { + makeFlexTag(name, FLEXTAG_METHOD); + if (vStringLength(secondary_name->string) > 0) + makeFunctionTag(secondary_name); + parseBlock(token, name); + } else { + parseBlock(token, name); + makeFunctionTag(name); + + if (vStringLength(secondary_name->string) > 0) + makeFunctionTag(secondary_name); + + /* + * Find to the end of the statement + */ + goto cleanUp; + } + } + } else if (isType(token, TOKEN_OPEN_PAREN)) { + /* + * Handle nameless functions + * this.method_name = () {} + */ + skipArgumentList(token); + + if (isType(token, TOKEN_OPEN_CURLY)) { + /* + * Nameless functions are only setup as methods. + */ + makeFlexTag(name, FLEXTAG_METHOD); + parseBlock(token, name); + } + } else if (isType(token, TOKEN_OPEN_CURLY)) { + /* + * Creates tags for each of these class methods + * ValidClassOne.prototype = { + * 'validMethodOne' : function(a,b) {}, + * 'validMethodTwo' : function(a,b) {} + * } + */ + parseMethods(token, name); + if (isType(token, TOKEN_CLOSE_CURLY)) { + /* + * Assume the closing parantheses terminates + * this statements. + */ + is_terminated = TRUE; + } + } else if (isKeyword(token, KEYWORD_new)) { + readToken(token); + if (isKeyword(token, KEYWORD_function) || + isKeyword(token, KEYWORD_capital_function) || + isKeyword(token, KEYWORD_object) || + isKeyword(token, KEYWORD_capital_object)) { + if (isKeyword(token, KEYWORD_object) || + isKeyword(token, KEYWORD_capital_object)) + is_class = TRUE; + + readToken(token); + if (isType(token, TOKEN_OPEN_PAREN)) skipArgumentList(token); + + if (isType(token, TOKEN_SEMICOLON)) { + if (token->nestLevel == 0) { + if (is_class) { + makeClassTag(name); + } else { + makeFunctionTag(name); + } + } + } + } + } else if (isKeyword(token, KEYWORD_NONE)) { + /* + * Only create variables for global scope + */ + if (token->nestLevel == 0 && is_global) { + /* + * A pointer can be created to the function. + * If we recognize the function/class name ignore the variable. + * This format looks identical to a variable definition. + * A variable defined outside of a block is considered + * a global variable: + * var g_var1 = 1; + * var g_var2; + * This is not a global variable: + * var g_var = function; + * This is a global variable: + * var g_var = different_var_name; + */ + fulltag = vStringNew(); + if (vStringLength(token->scope) > 0) { + vStringCopy(fulltag, token->scope); + vStringCatS(fulltag, "."); + vStringCatS(fulltag, vStringValue(token->string)); + } else { + vStringCopy(fulltag, token->string); + } + vStringTerminate(fulltag); + if (!stringListHas(FunctionNames, vStringValue(fulltag)) && + !stringListHas(ClassNames, vStringValue(fulltag))) { + findCmdTerm(token); + if (isType(token, TOKEN_SEMICOLON)) + makeFlexTag(name, FLEXTAG_VARIABLE); + } + vStringDelete(fulltag); + } + } + } + findCmdTerm(token); + + /* + * Statements can be optionally terminated in the case of + * statement prior to a close curly brace as in the + * document.write line below: + * + * function checkForUpdate() { + * if( 1==1 ) { + * document.write("hello from checkForUpdate
") + * } + * return 1; + * } + */ + if (!is_terminated && isType(token, TOKEN_CLOSE_CURLY)) is_terminated = FALSE; + +cleanUp: + vStringCopy(token->scope, saveScope); + deleteToken(name); + deleteToken(secondary_name); + vStringDelete(saveScope); + + return is_terminated; +} + +static boolean parseLine(tokenInfo *const token) { + boolean is_terminated = TRUE; + /* + * Detect the common statements, if, while, for, do, ... + * This is necessary since the last statement within a block "{}" + * can be optionally terminated. + * + * If the statement is not terminated, we need to tell + * the calling routine to prevent reading an additional token + * looking for the end of the statement. + */ + + if (isType(token, TOKEN_KEYWORD)) { + switch (token->keyword) { + case KEYWORD_for: + case KEYWORD_while: + case KEYWORD_do: + parseLoop(token); + break; + case KEYWORD_if: + case KEYWORD_else: + case KEYWORD_try: + case KEYWORD_catch: + case KEYWORD_finally: + /* Common semantics */ + is_terminated = parseIf(token); + break; + case KEYWORD_switch: + parseSwitch(token); + break; + default: + parseStatement(token); + break; + } + } else { + /* + * Special case where single line statements may not be + * SEMICOLON terminated. parseBlock needs to know this + * so that it does not read the next token. + */ + is_terminated = parseStatement(token); + } + return is_terminated; +} + +static boolean parseCDATA(tokenInfo *const token) { + if (isType(token, TOKEN_LESS_THAN)) { + /* + * Handle these tags + * + */ + readToken(token); + if (isType(token, TOKEN_EXCLAMATION)) { + /* + * Not sure why I had to comment these out, but I did. + * readToken (token); + * if (isType (token, TOKEN_OPEN_SQUARE)) + * { + */ + readToken(token); + if (isKeyword(token, KEYWORD_cdata)) { + readToken(token); + if (isType(token, TOKEN_OPEN_SQUARE)) { + parseActionScript(token); + if (isType(token, TOKEN_CLOSE_SQUARE)) { + readToken(token); + if (isType(token, TOKEN_CLOSE_SQUARE)) { + readToken(token); + } + } + } + } + /*} Not sure */ + } + } else { + parseActionScript(token); + } + return TRUE; +} + +static boolean parseNamespace(tokenInfo *const token) { + /* + * If we have found a <, we know it is not a TOKEN_OPEN_MXML + * but it could potentially be a different namespace. + * This means it will also have a closing tag, which will + * mess up the parser if we do not properly recurse + * through these tags. + */ + + if (isType(token, TOKEN_LESS_THAN)) { + readToken(token); + } + + /* + * Check if we have reached a other namespace tag + * + * or + * + * + */ + if (isType(token, TOKEN_IDENTIFIER)) { + readToken(token); + if (isType(token, TOKEN_COLON)) { + readToken(token); + if (!isType(token, TOKEN_IDENTIFIER)) { + return TRUE; + } + } else { + return TRUE; + } + } else { + return TRUE; + } + + /* + * Confirmed we are inside a namespace tag, so + * process it until the close tag. + * + * But also check for new tags, which will either + * be recursive namespaces or MXML tags + */ + do { + if (isType(token, TOKEN_LESS_THAN)) { + parseNamespace(token); + readToken(token); + } + if (isType(token, TOKEN_OPEN_MXML)) { + parseMXML(token); + } else { + readToken(token); + } + } while ( + !(isType(token, TOKEN_CLOSE_SGML) || isType(token, TOKEN_CLOSE_MXML))); + + return TRUE; +} + +static boolean parseMXML(tokenInfo *const token) { + tokenInfo *const name = newToken(); + tokenInfo *const type = newToken(); + boolean inside_attributes = TRUE; + /* + * Detect the common statements, if, while, for, do, ... + * This is necessary since the last statement within a block "{}" + * can be optionally terminated. + * + * If the statement is not terminated, we need to tell + * the calling routine to prevent reading an additional token + * looking for the end of the statement. + */ + + readToken(token); + + if (isKeyword(token, KEYWORD_script)) { + /* + * These tags can be of this form: + * + */ + do { + readToken(token); + } while (!(isType(token, TOKEN_CLOSE_SGML) || + isType(token, TOKEN_CLOSE_MXML) || + isType(token, TOKEN_GREATER_THAN))); + + if (isType(token, TOKEN_CLOSE_MXML)) { + /* + * We have found a tag + * Finish reading the "type" and ">" + */ + readToken(token); + readToken(token); + goto cleanUp; + } + if (isType(token, TOKEN_CLOSE_SGML)) { + /* + * We have found a + */ + goto cleanUp; + } + + /* + * This is a beginning of an embedded script. + * These typically are of this format: + * + * + * + */ + readToken(token); + parseCDATA(token); + + readToken(token); + if (isType(token, TOKEN_CLOSE_MXML)) { + /* + * We have found a tag + * Finish reading the "type" and ">" + */ + readToken(token); + readToken(token); + } + goto cleanUp; + } + + copyToken(type, token); + + readToken(token); + do { + if (isType(token, TOKEN_GREATER_THAN)) { + inside_attributes = FALSE; + } + if (isType(token, TOKEN_LESS_THAN)) { + parseNamespace(token); + readToken(token); + } else if (isType(token, TOKEN_OPEN_MXML)) { + parseMXML(token); + readToken(token); + } else if (inside_attributes && (isKeyword(token, KEYWORD_id) || + isKeyword(token, KEYWORD_name))) { + if (vStringLength(name->string) == 0) { + /* + * If we have already created the tag based on either "name" + * or "id" do not do it again. + */ + readToken(token); + readToken(token); + + copyToken(name, token); + addToScope(name, type->string); + makeMXTag(name); + } else { + readToken(token); + } + } else { + readToken(token); + } + } while ( + !(isType(token, TOKEN_CLOSE_SGML) || isType(token, TOKEN_CLOSE_MXML))); + + if (isType(token, TOKEN_CLOSE_MXML)) { + /* + * We have found a tag + * Finish reading the "type" and ">" + */ + readToken(token); + readToken(token); + } + +cleanUp: + deleteToken(name); + deleteToken(type); + return TRUE; +} + +static boolean parseActionScript(tokenInfo *const token) { + do { + readToken(token); + + if (isType(token, TOKEN_LESS_THAN)) { + /* + * Handle these tags + * + */ + readToken(token); + if (isType(token, TOKEN_EQUAL_SIGN)) { + if (isType(token, TOKEN_OPEN_SQUARE)) { + readToken(token); + if (isKeyword(token, KEYWORD_cdata)) { + readToken(token); + } + } + } + } + if (isType(token, TOKEN_CLOSE_SQUARE)) { + /* + * Handle these tags + * + */ + readToken(token); + if (isType(token, TOKEN_CLOSE_SQUARE)) { + readToken(token); + if (isType(token, TOKEN_GREATER_THAN)) { + return TRUE; + } + } + } else if (isType(token, TOKEN_CLOSE_MXML)) { + /* + * Read the Script> tags + */ + readToken(token); + readToken(token); + return TRUE; + } else if (isType(token, TOKEN_OPEN_MXML)) { + parseMXML(token); + } else { + if (isType(token, TOKEN_KEYWORD)) { + if (isKeyword(token, KEYWORD_private) || + isKeyword(token, KEYWORD_public) || + isKeyword(token, KEYWORD_override)) { + /* + * Methods can be defined as: + * private function f_name + * public override function f_name + * override private function f_name + * Ignore these keywords if present. + */ + readToken(token); + } + if (isKeyword(token, KEYWORD_private) || + isKeyword(token, KEYWORD_public) || + isKeyword(token, KEYWORD_override)) { + /* + * Methods can be defined as: + * private function f_name + * public override function f_name + * override private function f_name + * Ignore these keywords if present. + */ + readToken(token); + } + + switch (token->keyword) { + case KEYWORD_function: + parseFunction(token); + break; + default: + parseLine(token); + break; + } + } else { + parseLine(token); + } + } + } while (TRUE); +} + +static void parseFlexFile(tokenInfo *const token) { + do { + readToken(token); + + if (isType(token, TOKEN_OPEN_MXML)) { + parseMXML(token); + } else if (isType(token, TOKEN_LESS_THAN)) { + readToken(token); + if (isType(token, TOKEN_QUESTION_MARK)) { + /* + * + */ + readToken(token); + while (!isType(token, TOKEN_QUESTION_MARK)) { + readToken(token); + } + readToken(token); + } else if (isKeyword(token, KEYWORD_NONE)) { + /* + * This is a simple XML tag, read until the closing statement + * + * + */ + readToken(token); + while (!isType(token, TOKEN_GREATER_THAN)) { + readToken(token); + } + } + } else { + parseActionScript(token); + } + } while (TRUE); +} + +static void initialize(const langType language) { + Assert(sizeof(FlexKinds) / sizeof(FlexKinds[0]) == FLEXTAG_COUNT); + Lang_js = language; + buildFlexKeywordHash(); +} + +static void findFlexTags(void) { + tokenInfo *const token = newToken(); + exception_t exception; + + ClassNames = stringListNew(); + FunctionNames = stringListNew(); + + exception = (exception_t)(setjmp(Exception)); + while (exception == ExceptionNone) parseFlexFile(token); + + stringListDelete(ClassNames); + stringListDelete(FunctionNames); + ClassNames = NULL; + FunctionNames = NULL; + deleteToken(token); +} + +/* Create parser definition stucture */ +extern parserDefinition *FlexParser(void) { + static const char *const extensions[] = {"as", "mxml", NULL}; + parserDefinition *const def = parserNew("Flex"); + def->extensions = extensions; + /* + * New definitions for parsing instead of regex + */ + def->kinds = FlexKinds; + def->kindCount = KIND_COUNT(FlexKinds); + def->parser = findFlexTags; + def->initialize = initialize; + + return def; +} +/* vi:set tabstop=4 shiftwidth=4 noexpandtab: */ diff --git a/third_party/ctags/fortran.c b/third_party/ctags/fortran.c new file mode 100644 index 00000000..b8a1083d --- /dev/null +++ b/third_party/ctags/fortran.c @@ -0,0 +1,2026 @@ +/* + * $Id: fortran.c 660 2008-04-20 23:30:12Z elliotth $ + * + * Copyright (c) 1998-2003, Darren Hiebert + * + * This source code is released for free distribution under the terms of the + * GNU General Public License. + * + * This module contains functions for generating tags for Fortran language + * files. + */ +#include "third_party/ctags/general.h" +/* must always come first */ +#include "third_party/ctags/debug.h" +#include "third_party/ctags/entry.h" +#include "third_party/ctags/keyword.h" +#include "third_party/ctags/options.h" +#include "third_party/ctags/parse.h" +#include "third_party/ctags/read.h" +#include "third_party/ctags/routines.h" +#include "third_party/ctags/vstring.h" + +/* + * MACROS + */ +#define isident(c) (isalnum(c) || (c) == '_') +#define isBlank(c) (boolean)(c == ' ' || c == '\t') +#define isType(token, t) (boolean)((token)->type == (t)) +#define isKeyword(token, k) (boolean)((token)->keyword == (k)) +#define isSecondaryKeyword(token, k) \ + (boolean)((token)->secondary == NULL ? FALSE \ + : (token)->secondary->keyword == (k)) + +/* + * DATA DECLARATIONS + */ + +typedef enum eException { + ExceptionNone, + ExceptionEOF, + ExceptionFixedFormat, + ExceptionLoop +} exception_t; + +/* Used to designate type of line read in fixed source form. + */ +typedef enum eFortranLineType { + LTYPE_UNDETERMINED, + LTYPE_INVALID, + LTYPE_COMMENT, + LTYPE_CONTINUATION, + LTYPE_EOF, + LTYPE_INITIAL, + LTYPE_SHORT +} lineType; + +/* Used to specify type of keyword. + */ +typedef enum eKeywordId { + KEYWORD_NONE = -1, + KEYWORD_allocatable, + KEYWORD_assignment, + KEYWORD_automatic, + KEYWORD_block, + KEYWORD_byte, + KEYWORD_cexternal, + KEYWORD_cglobal, + KEYWORD_character, + KEYWORD_common, + KEYWORD_complex, + KEYWORD_contains, + KEYWORD_data, + KEYWORD_dimension, + KEYWORD_dllexport, + KEYWORD_dllimport, + KEYWORD_do, + KEYWORD_double, + KEYWORD_elemental, + KEYWORD_end, + KEYWORD_entry, + KEYWORD_equivalence, + KEYWORD_external, + KEYWORD_format, + KEYWORD_function, + KEYWORD_if, + KEYWORD_implicit, + KEYWORD_include, + KEYWORD_inline, + KEYWORD_integer, + KEYWORD_intent, + KEYWORD_interface, + KEYWORD_intrinsic, + KEYWORD_logical, + KEYWORD_map, + KEYWORD_module, + KEYWORD_namelist, + KEYWORD_operator, + KEYWORD_optional, + KEYWORD_parameter, + KEYWORD_pascal, + KEYWORD_pexternal, + KEYWORD_pglobal, + KEYWORD_pointer, + KEYWORD_precision, + KEYWORD_private, + KEYWORD_program, + KEYWORD_public, + KEYWORD_pure, + KEYWORD_real, + KEYWORD_record, + KEYWORD_recursive, + KEYWORD_save, + KEYWORD_select, + KEYWORD_sequence, + KEYWORD_static, + KEYWORD_stdcall, + KEYWORD_structure, + KEYWORD_subroutine, + KEYWORD_target, + KEYWORD_then, + KEYWORD_type, + KEYWORD_union, + KEYWORD_use, + KEYWORD_value, + KEYWORD_virtual, + KEYWORD_volatile, + KEYWORD_where, + KEYWORD_while +} keywordId; + +/* Used to determine whether keyword is valid for the token language and + * what its ID is. + */ +typedef struct sKeywordDesc { + const char *name; + keywordId id; +} keywordDesc; + +typedef enum eTokenType { + TOKEN_UNDEFINED, + TOKEN_COMMA, + TOKEN_DOUBLE_COLON, + TOKEN_IDENTIFIER, + TOKEN_KEYWORD, + TOKEN_LABEL, + TOKEN_NUMERIC, + TOKEN_OPERATOR, + TOKEN_PAREN_CLOSE, + TOKEN_PAREN_OPEN, + TOKEN_PERCENT, + TOKEN_STATEMENT_END, + TOKEN_STRING +} tokenType; + +typedef enum eTagType { + TAG_UNDEFINED = -1, + TAG_BLOCK_DATA, + TAG_COMMON_BLOCK, + TAG_ENTRY_POINT, + TAG_FUNCTION, + TAG_INTERFACE, + TAG_COMPONENT, + TAG_LABEL, + TAG_LOCAL, + TAG_MODULE, + TAG_NAMELIST, + TAG_PROGRAM, + TAG_SUBROUTINE, + TAG_DERIVED_TYPE, + TAG_VARIABLE, + TAG_COUNT /* must be last */ +} tagType; + +typedef struct sTokenInfo { + tokenType type; + keywordId keyword; + tagType tag; + vString *string; + struct sTokenInfo *secondary; + unsigned long lineNumber; + fpos_t filePosition; +} tokenInfo; + +/* + * DATA DEFINITIONS + */ + +static langType Lang_fortran; +static jmp_buf Exception; +static int Ungetc; +static unsigned int Column; +static boolean FreeSourceForm; +static boolean ParsingString; +static tokenInfo *Parent; + +/* indexed by tagType */ +static kindOption FortranKinds[] = { + {TRUE, 'b', "block data", "block data"}, + {TRUE, 'c', "common", "common blocks"}, + {TRUE, 'e', "entry", "entry points"}, + {TRUE, 'f', "function", "functions"}, + {FALSE, 'i', "interface", + "interface contents, generic names, and operators"}, + {TRUE, 'k', "component", "type and structure components"}, + {TRUE, 'l', "label", "labels"}, + {FALSE, 'L', "local", "local, common block, and namelist variables"}, + {TRUE, 'm', "module", "modules"}, + {TRUE, 'n', "namelist", "namelists"}, + {TRUE, 'p', "program", "programs"}, + {TRUE, 's', "subroutine", "subroutines"}, + {TRUE, 't', "type", "derived types and structures"}, + {TRUE, 'v', "variable", "program (global) and module variables"}}; + +/* For efinitions of Fortran 77 with extensions: + * http://www.fortran.com/fortran/F77_std/rjcnf0001.html + * http://scienide.uwaterloo.ca/MIPSpro7/007-2362-004/sgi_html/index.html + * + * For the Compaq Fortran Reference Manual: + * http://h18009.www1.hp.com/fortran/docs/lrm/dflrm.htm + */ + +static const keywordDesc FortranKeywordTable[] = { + /* keyword keyword ID */ + {"allocatable", KEYWORD_allocatable}, + {"assignment", KEYWORD_assignment}, + {"automatic", KEYWORD_automatic}, + {"block", KEYWORD_block}, + {"byte", KEYWORD_byte}, + {"cexternal", KEYWORD_cexternal}, + {"cglobal", KEYWORD_cglobal}, + {"character", KEYWORD_character}, + {"common", KEYWORD_common}, + {"complex", KEYWORD_complex}, + {"contains", KEYWORD_contains}, + {"data", KEYWORD_data}, + {"dimension", KEYWORD_dimension}, + {"dll_export", KEYWORD_dllexport}, + {"dll_import", KEYWORD_dllimport}, + {"do", KEYWORD_do}, + {"double", KEYWORD_double}, + {"elemental", KEYWORD_elemental}, + {"end", KEYWORD_end}, + {"entry", KEYWORD_entry}, + {"equivalence", KEYWORD_equivalence}, + {"external", KEYWORD_external}, + {"format", KEYWORD_format}, + {"function", KEYWORD_function}, + {"if", KEYWORD_if}, + {"implicit", KEYWORD_implicit}, + {"include", KEYWORD_include}, + {"inline", KEYWORD_inline}, + {"integer", KEYWORD_integer}, + {"intent", KEYWORD_intent}, + {"interface", KEYWORD_interface}, + {"intrinsic", KEYWORD_intrinsic}, + {"logical", KEYWORD_logical}, + {"map", KEYWORD_map}, + {"module", KEYWORD_module}, + {"namelist", KEYWORD_namelist}, + {"operator", KEYWORD_operator}, + {"optional", KEYWORD_optional}, + {"parameter", KEYWORD_parameter}, + {"pascal", KEYWORD_pascal}, + {"pexternal", KEYWORD_pexternal}, + {"pglobal", KEYWORD_pglobal}, + {"pointer", KEYWORD_pointer}, + {"precision", KEYWORD_precision}, + {"private", KEYWORD_private}, + {"program", KEYWORD_program}, + {"public", KEYWORD_public}, + {"pure", KEYWORD_pure}, + {"real", KEYWORD_real}, + {"record", KEYWORD_record}, + {"recursive", KEYWORD_recursive}, + {"save", KEYWORD_save}, + {"select", KEYWORD_select}, + {"sequence", KEYWORD_sequence}, + {"static", KEYWORD_static}, + {"stdcall", KEYWORD_stdcall}, + {"structure", KEYWORD_structure}, + {"subroutine", KEYWORD_subroutine}, + {"target", KEYWORD_target}, + {"then", KEYWORD_then}, + {"type", KEYWORD_type}, + {"union", KEYWORD_union}, + {"use", KEYWORD_use}, + {"value", KEYWORD_value}, + {"virtual", KEYWORD_virtual}, + {"volatile", KEYWORD_volatile}, + {"where", KEYWORD_where}, + {"while", KEYWORD_while}}; + +static struct { + unsigned int count; + unsigned int max; + tokenInfo *list; +} Ancestors = {0, 0, NULL}; + +/* + * FUNCTION PROTOTYPES + */ +static void parseStructureStmt(tokenInfo *const token); +static void parseUnionStmt(tokenInfo *const token); +static void parseDerivedTypeDef(tokenInfo *const token); +static void parseFunctionSubprogram(tokenInfo *const token); +static void parseSubroutineSubprogram(tokenInfo *const token); + +/* + * FUNCTION DEFINITIONS + */ + +static void ancestorPush(tokenInfo *const token) { + enum { incrementalIncrease = 10 }; + if (Ancestors.list == NULL) { + Assert(Ancestors.max == 0); + Ancestors.count = 0; + Ancestors.max = incrementalIncrease; + Ancestors.list = xMalloc(Ancestors.max, tokenInfo); + } else if (Ancestors.count == Ancestors.max) { + Ancestors.max += incrementalIncrease; + Ancestors.list = xRealloc(Ancestors.list, Ancestors.max, tokenInfo); + } + Ancestors.list[Ancestors.count] = *token; + Ancestors.list[Ancestors.count].string = vStringNewCopy(token->string); + Ancestors.count++; +} + +static void ancestorPop(void) { + Assert(Ancestors.count > 0); + --Ancestors.count; + vStringDelete(Ancestors.list[Ancestors.count].string); + + Ancestors.list[Ancestors.count].type = TOKEN_UNDEFINED; + Ancestors.list[Ancestors.count].keyword = KEYWORD_NONE; + Ancestors.list[Ancestors.count].secondary = NULL; + Ancestors.list[Ancestors.count].tag = TAG_UNDEFINED; + Ancestors.list[Ancestors.count].string = NULL; + Ancestors.list[Ancestors.count].lineNumber = 0L; +} + +static const tokenInfo *ancestorScope(void) { + tokenInfo *result = NULL; + unsigned int i; + for (i = Ancestors.count; i > 0 && result == NULL; --i) { + tokenInfo *const token = Ancestors.list + i - 1; + if (token->type == TOKEN_IDENTIFIER && token->tag != TAG_UNDEFINED && + token->tag != TAG_INTERFACE) + result = token; + } + return result; +} + +static const tokenInfo *ancestorTop(void) { + Assert(Ancestors.count > 0); + return &Ancestors.list[Ancestors.count - 1]; +} + +#define ancestorCount() (Ancestors.count) + +static void ancestorClear(void) { + while (Ancestors.count > 0) ancestorPop(); + if (Ancestors.list != NULL) eFree(Ancestors.list); + Ancestors.list = NULL; + Ancestors.count = 0; + Ancestors.max = 0; +} + +static boolean insideInterface(void) { + boolean result = FALSE; + unsigned int i; + for (i = 0; i < Ancestors.count && !result; ++i) { + if (Ancestors.list[i].tag == TAG_INTERFACE) result = TRUE; + } + return result; +} + +static void buildFortranKeywordHash(void) { + const size_t count = + sizeof(FortranKeywordTable) / sizeof(FortranKeywordTable[0]); + size_t i; + for (i = 0; i < count; ++i) { + const keywordDesc *const p = &FortranKeywordTable[i]; + addKeyword(p->name, Lang_fortran, (int)p->id); + } +} + +/* + * Tag generation functions + */ + +static tokenInfo *newToken(void) { + tokenInfo *const token = xMalloc(1, tokenInfo); + + token->type = TOKEN_UNDEFINED; + token->keyword = KEYWORD_NONE; + token->tag = TAG_UNDEFINED; + token->string = vStringNew(); + token->secondary = NULL; + token->lineNumber = getSourceLineNumber(); + token->filePosition = getInputFilePosition(); + + return token; +} + +static tokenInfo *newTokenFrom(tokenInfo *const token) { + tokenInfo *result = newToken(); + *result = *token; + result->string = vStringNewCopy(token->string); + token->secondary = NULL; + return result; +} + +static void deleteToken(tokenInfo *const token) { + if (token != NULL) { + vStringDelete(token->string); + deleteToken(token->secondary); + token->secondary = NULL; + eFree(token); + } +} + +static boolean isFileScope(const tagType type) { + return (boolean)(type == TAG_LABEL || type == TAG_LOCAL); +} + +static boolean includeTag(const tagType type) { + boolean include; + Assert(type != TAG_UNDEFINED); + include = FortranKinds[(int)type].enabled; + if (include && isFileScope(type)) include = Option.include.fileScope; + return include; +} + +static void makeFortranTag(tokenInfo *const token, tagType tag) { + token->tag = tag; + if (includeTag(token->tag)) { + const char *const name = vStringValue(token->string); + tagEntryInfo e; + + initTagEntry(&e, name); + + if (token->tag == TAG_COMMON_BLOCK) + e.lineNumberEntry = (boolean)(Option.locate != EX_PATTERN); + + e.lineNumber = token->lineNumber; + e.filePosition = token->filePosition; + e.isFileScope = isFileScope(token->tag); + e.kindName = FortranKinds[token->tag].name; + e.kind = FortranKinds[token->tag].letter; + e.truncateLine = (boolean)(token->tag != TAG_LABEL); + + if (ancestorCount() > 0) { + const tokenInfo *const scope = ancestorScope(); + if (scope != NULL) { + e.extensionFields.scope[0] = FortranKinds[scope->tag].name; + e.extensionFields.scope[1] = vStringValue(scope->string); + } + } + if (!insideInterface() || includeTag(TAG_INTERFACE)) makeTagEntry(&e); + } +} + +/* + * Parsing functions + */ + +static int skipLine(void) { + int c; + + do + c = fileGetc(); + while (c != EOF && c != '\n'); + + return c; +} + +static void makeLabelTag(vString *const label) { + tokenInfo *token = newToken(); + token->type = TOKEN_LABEL; + vStringCopy(token->string, label); + makeFortranTag(token, TAG_LABEL); + deleteToken(token); +} + +static lineType getLineType(void) { + vString *label = vStringNew(); + int column = 0; + lineType type = LTYPE_UNDETERMINED; + + do /* read in first 6 "margin" characters */ + { + int c = fileGetc(); + + /* 3.2.1 Comment_Line. A comment line is any line that contains + * a C or an asterisk in column 1, or contains only blank characters + * in columns 1 through 72. A comment line that contains a C or + * an asterisk in column 1 may contain any character capable of + * representation in the processor in columns 2 through 72. + */ + /* EXCEPTION! Some compilers permit '!' as a commment character here. + * + * Treat # and $ in column 1 as comment to permit preprocessor directives. + * Treat D and d in column 1 as comment for HP debug statements. + */ + if (column == 0 && strchr("*Cc!#$Dd", c) != NULL) + type = LTYPE_COMMENT; + else if (c == '\t') /* EXCEPTION! Some compilers permit a tab here */ + { + column = 8; + type = LTYPE_INITIAL; + } else if (column == 5) { + /* 3.2.2 Initial_Line. An initial line is any line that is not + * a comment line and contains the character blank or the digit 0 + * in column 6. Columns 1 through 5 may contain a statement label + * (3.4), or each of the columns 1 through 5 must contain the + * character blank. + */ + if (c == ' ' || c == '0') type = LTYPE_INITIAL; + + /* 3.2.3 Continuation_Line. A continuation line is any line that + * contains any character of the FORTRAN character set other than + * the character blank or the digit 0 in column 6 and contains + * only blank characters in columns 1 through 5. + */ + else if (vStringLength(label) == 0) + type = LTYPE_CONTINUATION; + else + type = LTYPE_INVALID; + } else if (c == ' ') + ; + else if (c == EOF) + type = LTYPE_EOF; + else if (c == '\n') + type = LTYPE_SHORT; + else if (isdigit(c)) + vStringPut(label, c); + else + type = LTYPE_INVALID; + + ++column; + } while (column < 6 && type == LTYPE_UNDETERMINED); + + Assert(type != LTYPE_UNDETERMINED); + + if (vStringLength(label) > 0) { + vStringTerminate(label); + makeLabelTag(label); + } + vStringDelete(label); + return type; +} + +static int getFixedFormChar(void) { + boolean newline = FALSE; + lineType type; + int c = '\0'; + + if (Column > 0) { +#ifdef STRICT_FIXED_FORM + /* EXCEPTION! Some compilers permit more than 72 characters per line. + */ + if (Column > 71) + c = skipLine(); + else +#endif + { + c = fileGetc(); + ++Column; + } + if (c == '\n') { + newline = TRUE; /* need to check for continuation line */ + Column = 0; + } else if (c == '!' && !ParsingString) { + c = skipLine(); + newline = TRUE; /* need to check for continuation line */ + Column = 0; + } else if (c == '&') /* check for free source form */ + { + const int c2 = fileGetc(); + if (c2 == '\n') + longjmp(Exception, (int)ExceptionFixedFormat); + else + fileUngetc(c2); + } + } + while (Column == 0) { + type = getLineType(); + switch (type) { + case LTYPE_UNDETERMINED: + case LTYPE_INVALID: + longjmp(Exception, (int)ExceptionFixedFormat); + break; + + case LTYPE_SHORT: + break; + case LTYPE_COMMENT: + skipLine(); + break; + + case LTYPE_EOF: + Column = 6; + if (newline) + c = '\n'; + else + c = EOF; + break; + + case LTYPE_INITIAL: + if (newline) { + c = '\n'; + Column = 6; + break; + } + /* fall through to next case */ + case LTYPE_CONTINUATION: + Column = 5; + do { + c = fileGetc(); + ++Column; + } while (isBlank(c)); + if (c == '\n') + Column = 0; + else if (Column > 6) { + fileUngetc(c); + c = ' '; + } + break; + + default: + Assert("Unexpected line type" == NULL); + } + } + return c; +} + +static int skipToNextLine(void) { + int c = skipLine(); + if (c != EOF) c = fileGetc(); + return c; +} + +static int getFreeFormChar(void) { + static boolean newline = TRUE; + boolean advanceLine = FALSE; + int c = fileGetc(); + + /* If the last nonblank, non-comment character of a FORTRAN 90 + * free-format text line is an ampersand then the next non-comment + * line is a continuation line. + */ + if (c == '&') { + do + c = fileGetc(); + while (isspace(c) && c != '\n'); + if (c == '\n') { + newline = TRUE; + advanceLine = TRUE; + } else if (c == '!') + advanceLine = TRUE; + else { + fileUngetc(c); + c = '&'; + } + } else if (newline && (c == '!' || c == '#')) + advanceLine = TRUE; + while (advanceLine) { + while (isspace(c)) c = fileGetc(); + if (c == '!' || (newline && c == '#')) { + c = skipToNextLine(); + newline = TRUE; + continue; + } + if (c == '&') + c = fileGetc(); + else + advanceLine = FALSE; + } + newline = (boolean)(c == '\n'); + return c; +} + +static int getChar(void) { + int c; + + if (Ungetc != '\0') { + c = Ungetc; + Ungetc = '\0'; + } else if (FreeSourceForm) + c = getFreeFormChar(); + else + c = getFixedFormChar(); + return c; +} + +static void ungetChar(const int c) { + Ungetc = c; +} + +/* If a numeric is passed in 'c', this is used as the first digit of the + * numeric being parsed. + */ +static vString *parseInteger(int c) { + vString *string = vStringNew(); + + if (c == '-') { + vStringPut(string, c); + c = getChar(); + } else if (!isdigit(c)) + c = getChar(); + while (c != EOF && isdigit(c)) { + vStringPut(string, c); + c = getChar(); + } + vStringTerminate(string); + + if (c == '_') { + do + c = getChar(); + while (c != EOF && isalpha(c)); + } + ungetChar(c); + + return string; +} + +static vString *parseNumeric(int c) { + vString *string = vStringNew(); + vString *integer = parseInteger(c); + vStringCopy(string, integer); + vStringDelete(integer); + + c = getChar(); + if (c == '.') { + integer = parseInteger('\0'); + vStringPut(string, c); + vStringCat(string, integer); + vStringDelete(integer); + c = getChar(); + } + if (tolower(c) == 'e') { + integer = parseInteger('\0'); + vStringPut(string, c); + vStringCat(string, integer); + vStringDelete(integer); + } else + ungetChar(c); + + vStringTerminate(string); + + return string; +} + +static void parseString(vString *const string, const int delimiter) { + const unsigned long inputLineNumber = getInputLineNumber(); + int c; + ParsingString = TRUE; + c = getChar(); + while (c != delimiter && c != '\n' && c != EOF) { + vStringPut(string, c); + c = getChar(); + } + if (c == '\n' || c == EOF) { + verbose("%s: unterminated character string at line %lu\n", + getInputFileName(), inputLineNumber); + if (c == EOF) + longjmp(Exception, (int)ExceptionEOF); + else if (!FreeSourceForm) + longjmp(Exception, (int)ExceptionFixedFormat); + } + vStringTerminate(string); + ParsingString = FALSE; +} + +/* Read a C identifier beginning with "firstChar" and places it into "name". + */ +static void parseIdentifier(vString *const string, const int firstChar) { + int c = firstChar; + + do { + vStringPut(string, c); + c = getChar(); + } while (isident(c)); + + vStringTerminate(string); + ungetChar(c); /* unget non-identifier character */ +} + +static void checkForLabel(void) { + tokenInfo *token = NULL; + int length; + int c; + + do + c = getChar(); + while (isBlank(c)); + + for (length = 0; isdigit(c) && length < 5; ++length) { + if (token == NULL) { + token = newToken(); + token->type = TOKEN_LABEL; + } + vStringPut(token->string, c); + c = getChar(); + } + if (length > 0 && token != NULL) { + vStringTerminate(token->string); + makeFortranTag(token, TAG_LABEL); + deleteToken(token); + } + ungetChar(c); +} + +static void readIdentifier(tokenInfo *const token, const int c) { + parseIdentifier(token->string, c); + token->keyword = analyzeToken(token->string, Lang_fortran); + if (!isKeyword(token, KEYWORD_NONE)) + token->type = TOKEN_KEYWORD; + else { + token->type = TOKEN_IDENTIFIER; + if (strncmp(vStringValue(token->string), "end", 3) == 0) { + vString *const sub = vStringNewInit(vStringValue(token->string) + 3); + const keywordId kw = analyzeToken(sub, Lang_fortran); + vStringDelete(sub); + if (kw != KEYWORD_NONE) { + token->secondary = newToken(); + token->secondary->type = TOKEN_KEYWORD; + token->secondary->keyword = kw; + token->keyword = KEYWORD_end; + } + } + } +} + +static void readToken(tokenInfo *const token) { + int c; + + deleteToken(token->secondary); + token->type = TOKEN_UNDEFINED; + token->tag = TAG_UNDEFINED; + token->keyword = KEYWORD_NONE; + token->secondary = NULL; + vStringClear(token->string); + +getNextChar: + c = getChar(); + + token->lineNumber = getSourceLineNumber(); + token->filePosition = getInputFilePosition(); + + switch (c) { + case EOF: + longjmp(Exception, (int)ExceptionEOF); + break; + case ' ': + goto getNextChar; + case '\t': + goto getNextChar; + case ',': + token->type = TOKEN_COMMA; + break; + case '(': + token->type = TOKEN_PAREN_OPEN; + break; + case ')': + token->type = TOKEN_PAREN_CLOSE; + break; + case '%': + token->type = TOKEN_PERCENT; + break; + + case '*': + case '/': + case '+': + case '-': + case '=': + case '<': + case '>': { + const char *const operatorChars = "*/+=<>"; + do { + vStringPut(token->string, c); + c = getChar(); + } while (strchr(operatorChars, c) != NULL); + ungetChar(c); + vStringTerminate(token->string); + token->type = TOKEN_OPERATOR; + break; + } + + case '!': + if (FreeSourceForm) { + do + c = getChar(); + while (c != '\n' && c != EOF); + } else { + skipLine(); + Column = 0; + } + /* fall through to newline case */ + case '\n': + token->type = TOKEN_STATEMENT_END; + if (FreeSourceForm) checkForLabel(); + break; + + case '.': + parseIdentifier(token->string, c); + c = getChar(); + if (c == '.') { + vStringPut(token->string, c); + vStringTerminate(token->string); + token->type = TOKEN_OPERATOR; + } else { + ungetChar(c); + token->type = TOKEN_UNDEFINED; + } + break; + + case '"': + case '\'': + parseString(token->string, c); + token->type = TOKEN_STRING; + break; + + case ';': + token->type = TOKEN_STATEMENT_END; + break; + + case ':': + c = getChar(); + if (c == ':') + token->type = TOKEN_DOUBLE_COLON; + else { + ungetChar(c); + token->type = TOKEN_UNDEFINED; + } + break; + + default: + if (isalpha(c)) + readIdentifier(token, c); + else if (isdigit(c)) { + vString *numeric = parseNumeric(c); + vStringCat(token->string, numeric); + vStringDelete(numeric); + token->type = TOKEN_NUMERIC; + } else + token->type = TOKEN_UNDEFINED; + break; + } +} + +static void readSubToken(tokenInfo *const token) { + if (token->secondary == NULL) { + token->secondary = newToken(); + readToken(token->secondary); + } +} + +/* + * Scanning functions + */ + +static void skipToToken(tokenInfo *const token, tokenType type) { + while (!isType(token, type) && !isType(token, TOKEN_STATEMENT_END) && + !(token->secondary != NULL && + isType(token->secondary, TOKEN_STATEMENT_END))) + readToken(token); +} + +static void skipPast(tokenInfo *const token, tokenType type) { + skipToToken(token, type); + if (!isType(token, TOKEN_STATEMENT_END)) readToken(token); +} + +static void skipToNextStatement(tokenInfo *const token) { + do { + skipToToken(token, TOKEN_STATEMENT_END); + readToken(token); + } while (isType(token, TOKEN_STATEMENT_END)); +} + +/* skip over parenthesis enclosed contents starting at next token. + * Token is left at the first token following closing parenthesis. If an + * opening parenthesis is not found, `token' is moved to the end of the + * statement. + */ +static void skipOverParens(tokenInfo *const token) { + int level = 0; + do { + if (isType(token, TOKEN_STATEMENT_END)) + break; + else if (isType(token, TOKEN_PAREN_OPEN)) + ++level; + else if (isType(token, TOKEN_PAREN_CLOSE)) + --level; + readToken(token); + } while (level > 0); +} + +static boolean isTypeSpec(tokenInfo *const token) { + boolean result; + switch (token->keyword) { + case KEYWORD_byte: + case KEYWORD_integer: + case KEYWORD_real: + case KEYWORD_double: + case KEYWORD_complex: + case KEYWORD_character: + case KEYWORD_logical: + case KEYWORD_record: + case KEYWORD_type: + result = TRUE; + break; + default: + result = FALSE; + break; + } + return result; +} + +static boolean isSubprogramPrefix(tokenInfo *const token) { + boolean result; + switch (token->keyword) { + case KEYWORD_elemental: + case KEYWORD_pure: + case KEYWORD_recursive: + case KEYWORD_stdcall: + result = TRUE; + break; + default: + result = FALSE; + break; + } + return result; +} + +/* type-spec + * is INTEGER [kind-selector] + * or REAL [kind-selector] is ( etc. ) + * or DOUBLE PRECISION + * or COMPLEX [kind-selector] + * or CHARACTER [kind-selector] + * or LOGICAL [kind-selector] + * or TYPE ( type-name ) + * + * Note that INTEGER and REAL may be followed by "*N" where "N" is an integer + */ +static void parseTypeSpec(tokenInfo *const token) { + /* parse type-spec, leaving `token' at first token following type-spec */ + Assert(isTypeSpec(token)); + switch (token->keyword) { + case KEYWORD_character: + /* skip char-selector */ + readToken(token); + if (isType(token, TOKEN_OPERATOR) && + strcmp(vStringValue(token->string), "*") == 0) + readToken(token); + if (isType(token, TOKEN_PAREN_OPEN)) + skipOverParens(token); + else if (isType(token, TOKEN_NUMERIC)) + readToken(token); + break; + + case KEYWORD_byte: + case KEYWORD_complex: + case KEYWORD_integer: + case KEYWORD_logical: + case KEYWORD_real: + readToken(token); + if (isType(token, TOKEN_PAREN_OPEN)) + skipOverParens(token); /* skip kind-selector */ + if (isType(token, TOKEN_OPERATOR) && + strcmp(vStringValue(token->string), "*") == 0) { + readToken(token); + readToken(token); + } + break; + + case KEYWORD_double: + readToken(token); + if (isKeyword(token, KEYWORD_complex) || + isKeyword(token, KEYWORD_precision)) + readToken(token); + else + skipToToken(token, TOKEN_STATEMENT_END); + break; + + case KEYWORD_record: + readToken(token); + if (isType(token, TOKEN_OPERATOR) && + strcmp(vStringValue(token->string), "/") == 0) { + readToken(token); /* skip to structure name */ + readToken(token); /* skip to '/' */ + readToken(token); /* skip to variable name */ + } + break; + + case KEYWORD_type: + readToken(token); + if (isType(token, TOKEN_PAREN_OPEN)) + skipOverParens(token); /* skip type-name */ + else + parseDerivedTypeDef(token); + break; + + default: + skipToToken(token, TOKEN_STATEMENT_END); + break; + } +} + +static boolean skipStatementIfKeyword(tokenInfo *const token, + keywordId keyword) { + boolean result = FALSE; + if (isKeyword(token, keyword)) { + result = TRUE; + skipToNextStatement(token); + } + return result; +} + +/* parse a list of qualifying specifiers, leaving `token' at first token + * following list. Examples of such specifiers are: + * [[, attr-spec] ::] + * [[, component-attr-spec-list] ::] + * + * attr-spec + * is PARAMETER + * or access-spec (is PUBLIC or PRIVATE) + * or ALLOCATABLE + * or DIMENSION ( array-spec ) + * or EXTERNAL + * or INTENT ( intent-spec ) + * or INTRINSIC + * or OPTIONAL + * or POINTER + * or SAVE + * or TARGET + * + * component-attr-spec + * is POINTER + * or DIMENSION ( component-array-spec ) + */ +static void parseQualifierSpecList(tokenInfo *const token) { + do { + readToken(token); /* should be an attr-spec */ + switch (token->keyword) { + case KEYWORD_parameter: + case KEYWORD_allocatable: + case KEYWORD_external: + case KEYWORD_intrinsic: + case KEYWORD_optional: + case KEYWORD_private: + case KEYWORD_pointer: + case KEYWORD_public: + case KEYWORD_save: + case KEYWORD_target: + readToken(token); + break; + + case KEYWORD_dimension: + case KEYWORD_intent: + readToken(token); + skipOverParens(token); + break; + + default: + skipToToken(token, TOKEN_STATEMENT_END); + break; + } + } while (isType(token, TOKEN_COMMA)); + if (!isType(token, TOKEN_DOUBLE_COLON)) + skipToToken(token, TOKEN_STATEMENT_END); +} + +static tagType variableTagType(void) { + tagType result = TAG_VARIABLE; + if (ancestorCount() > 0) { + const tokenInfo *const parent = ancestorTop(); + switch (parent->tag) { + case TAG_MODULE: + result = TAG_VARIABLE; + break; + case TAG_DERIVED_TYPE: + result = TAG_COMPONENT; + break; + case TAG_FUNCTION: + result = TAG_LOCAL; + break; + case TAG_SUBROUTINE: + result = TAG_LOCAL; + break; + default: + result = TAG_VARIABLE; + break; + } + } + return result; +} + +static void parseEntityDecl(tokenInfo *const token) { + Assert(isType(token, TOKEN_IDENTIFIER)); + makeFortranTag(token, variableTagType()); + readToken(token); + if (isType(token, TOKEN_PAREN_OPEN)) skipOverParens(token); + if (isType(token, TOKEN_OPERATOR) && + strcmp(vStringValue(token->string), "*") == 0) { + readToken(token); /* read char-length */ + if (isType(token, TOKEN_PAREN_OPEN)) + skipOverParens(token); + else + readToken(token); + } + if (isType(token, TOKEN_OPERATOR)) { + if (strcmp(vStringValue(token->string), "/") == + 0) { /* skip over initializations of structure field */ + readToken(token); + skipPast(token, TOKEN_OPERATOR); + } else if (strcmp(vStringValue(token->string), "=") == 0) { + while (!isType(token, TOKEN_COMMA) && + !isType(token, TOKEN_STATEMENT_END)) { + readToken(token); + if (isType(token, TOKEN_PAREN_OPEN)) skipOverParens(token); + } + } + } + /* token left at either comma or statement end */ +} + +static void parseEntityDeclList(tokenInfo *const token) { + if (isType(token, TOKEN_PERCENT)) + skipToNextStatement(token); + else + while (isType(token, TOKEN_IDENTIFIER) || + (isType(token, TOKEN_KEYWORD) && + !isKeyword(token, KEYWORD_function) && + !isKeyword(token, KEYWORD_subroutine))) { + /* compilers accept keywoeds as identifiers */ + if (isType(token, TOKEN_KEYWORD)) token->type = TOKEN_IDENTIFIER; + parseEntityDecl(token); + if (isType(token, TOKEN_COMMA)) + readToken(token); + else if (isType(token, TOKEN_STATEMENT_END)) { + skipToNextStatement(token); + break; + } + } +} + +/* type-declaration-stmt is + * type-spec [[, attr-spec] ... ::] entity-decl-list + */ +static void parseTypeDeclarationStmt(tokenInfo *const token) { + Assert(isTypeSpec(token)); + parseTypeSpec(token); + if (!isType(token, TOKEN_STATEMENT_END)) /* if not end of derived type... */ + { + if (isType(token, TOKEN_COMMA)) parseQualifierSpecList(token); + if (isType(token, TOKEN_DOUBLE_COLON)) readToken(token); + parseEntityDeclList(token); + } + if (isType(token, TOKEN_STATEMENT_END)) skipToNextStatement(token); +} + +/* namelist-stmt is + * NAMELIST /namelist-group-name/ namelist-group-object-list + * [[,]/[namelist-group-name]/ namelist-block-object-list] + *... + * + * namelist-group-object is + * variable-name + * + * common-stmt is + * COMMON [/[common-block-name]/] common-block-object-list + * [[,]/[common-block-name]/ common-block-object-list] ... + * + * common-block-object is + * variable-name [ ( explicit-shape-spec-list ) ] + */ +static void parseCommonNamelistStmt(tokenInfo *const token, tagType type) { + Assert(isKeyword(token, KEYWORD_common) || + isKeyword(token, KEYWORD_namelist)); + readToken(token); + do { + if (isType(token, TOKEN_OPERATOR) && + strcmp(vStringValue(token->string), "/") == 0) { + readToken(token); + if (isType(token, TOKEN_IDENTIFIER)) { + makeFortranTag(token, type); + readToken(token); + } + skipPast(token, TOKEN_OPERATOR); + } + if (isType(token, TOKEN_IDENTIFIER)) makeFortranTag(token, TAG_LOCAL); + readToken(token); + if (isType(token, TOKEN_PAREN_OPEN)) + skipOverParens(token); /* skip explicit-shape-spec-list */ + if (isType(token, TOKEN_COMMA)) readToken(token); + } while (!isType(token, TOKEN_STATEMENT_END)); + skipToNextStatement(token); +} + +static void parseFieldDefinition(tokenInfo *const token) { + if (isTypeSpec(token)) + parseTypeDeclarationStmt(token); + else if (isKeyword(token, KEYWORD_structure)) + parseStructureStmt(token); + else if (isKeyword(token, KEYWORD_union)) + parseUnionStmt(token); + else + skipToNextStatement(token); +} + +static void parseMap(tokenInfo *const token) { + Assert(isKeyword(token, KEYWORD_map)); + skipToNextStatement(token); + while (!isKeyword(token, KEYWORD_end)) parseFieldDefinition(token); + readSubToken(token); + /* should be at KEYWORD_map token */ + skipToNextStatement(token); +} + +/* UNION + * MAP + * [field-definition] [field-definition] ... + * END MAP + * MAP + * [field-definition] [field-definition] ... + * END MAP + * [MAP + * [field-definition] + * [field-definition] ... + * END MAP] ... + * END UNION + * * + * + * Typed data declarations (variables or arrays) in structure declarations + * have the form of normal Fortran typed data declarations. Data items with + * different types can be freely intermixed within a structure declaration. + * + * Unnamed fields can be declared in a structure by specifying the pseudo + * name %FILL in place of an actual field name. You can use this mechanism to + * generate empty space in a record for purposes such as alignment. + * + * All mapped field declarations that are made within a UNION declaration + * share a common location within the containing structure. When initializing + * the fields within a UNION, the final initialization value assigned + * overlays any value previously assigned to a field definition that shares + * that field. + */ +static void parseUnionStmt(tokenInfo *const token) { + Assert(isKeyword(token, KEYWORD_union)); + skipToNextStatement(token); + while (isKeyword(token, KEYWORD_map)) parseMap(token); + /* should be at KEYWORD_end token */ + readSubToken(token); + /* secondary token should be KEYWORD_end token */ + skipToNextStatement(token); +} + +/* STRUCTURE [/structure-name/] [field-names] + * [field-definition] + * [field-definition] ... + * END STRUCTURE + * + * structure-name + * identifies the structure in a subsequent RECORD statement. + * Substructures can be established within a structure by means of + *either a nested STRUCTURE declaration or a RECORD statement. + * + * field-names + * (for substructure declarations only) one or more names having + *the structure of the substructure being defined. + * + * field-definition + * can be one or more of the following: + * + * Typed data declarations, which can optionally include one or + *more data initialization values. + * + * Substructure declarations (defined by either RECORD + *statements or subsequent STRUCTURE statements). + * + * UNION declarations, which are mapped fields defined by a + *block of statements. The syntax of a UNION declaration is described below. + * + * PARAMETER statements, which do not affect the form of + *the structure. + */ +static void parseStructureStmt(tokenInfo *const token) { + tokenInfo *name; + Assert(isKeyword(token, KEYWORD_structure)); + readToken(token); + if (isType(token, TOKEN_OPERATOR) && + strcmp(vStringValue(token->string), "/") == 0) { /* read structure name */ + readToken(token); + if (isType(token, TOKEN_IDENTIFIER)) + makeFortranTag(token, TAG_DERIVED_TYPE); + name = newTokenFrom(token); + skipPast(token, TOKEN_OPERATOR); + } else { /* fake out anonymous structure */ + name = newToken(); + name->type = TOKEN_IDENTIFIER; + name->tag = TAG_DERIVED_TYPE; + vStringCopyS(name->string, "anonymous"); + } + while (isType(token, TOKEN_IDENTIFIER)) { /* read field names */ + makeFortranTag(token, TAG_COMPONENT); + readToken(token); + if (isType(token, TOKEN_COMMA)) readToken(token); + } + skipToNextStatement(token); + ancestorPush(name); + while (!isKeyword(token, KEYWORD_end)) parseFieldDefinition(token); + readSubToken(token); + /* secondary token should be KEYWORD_structure token */ + skipToNextStatement(token); + ancestorPop(); + deleteToken(name); +} + +/* specification-stmt + * is access-stmt (is access-spec [[::] access-id-list) + * or allocatable-stmt (is ALLOCATABLE [::] array-name etc.) + * or common-stmt (is COMMON [ / [common-block-name] /] etc.) + * or data-stmt (is DATA data-stmt-list [[,] data-stmt-set] ...) + * or dimension-stmt (is DIMENSION [::] array-name etc.) + * or equivalence-stmt (is EQUIVALENCE equivalence-set-list) + * or external-stmt (is EXTERNAL etc.) + * or intent-stmt (is INTENT ( intent-spec ) [::] etc.) + * or instrinsic-stmt (is INTRINSIC etc.) + * or namelist-stmt (is NAMELIST / namelist-group-name / etc.) + * or optional-stmt (is OPTIONAL [::] etc.) + * or pointer-stmt (is POINTER [::] object-name etc.) + * or save-stmt (is SAVE etc.) + * or target-stmt (is TARGET [::] object-name etc.) + * + * access-spec is PUBLIC or PRIVATE + */ +static boolean parseSpecificationStmt(tokenInfo *const token) { + boolean result = TRUE; + switch (token->keyword) { + case KEYWORD_common: + parseCommonNamelistStmt(token, TAG_COMMON_BLOCK); + break; + + case KEYWORD_namelist: + parseCommonNamelistStmt(token, TAG_NAMELIST); + break; + + case KEYWORD_structure: + parseStructureStmt(token); + break; + + case KEYWORD_allocatable: + case KEYWORD_data: + case KEYWORD_dimension: + case KEYWORD_equivalence: + case KEYWORD_external: + case KEYWORD_intent: + case KEYWORD_intrinsic: + case KEYWORD_optional: + case KEYWORD_pointer: + case KEYWORD_private: + case KEYWORD_public: + case KEYWORD_save: + case KEYWORD_target: + skipToNextStatement(token); + break; + + default: + result = FALSE; + break; + } + return result; +} + +/* component-def-stmt is + * type-spec [[, component-attr-spec-list] ::] component-decl-list + * + * component-decl is + * component-name [ ( component-array-spec ) ] [ * char-length ] + */ +static void parseComponentDefStmt(tokenInfo *const token) { + Assert(isTypeSpec(token)); + parseTypeSpec(token); + if (isType(token, TOKEN_COMMA)) parseQualifierSpecList(token); + if (isType(token, TOKEN_DOUBLE_COLON)) readToken(token); + parseEntityDeclList(token); +} + +/* derived-type-def is + * derived-type-stmt is (TYPE [[, access-spec] ::] type-name + * [private-sequence-stmt] ... (is PRIVATE or SEQUENCE) + * component-def-stmt + * [component-def-stmt] ... + * end-type-stmt + */ +static void parseDerivedTypeDef(tokenInfo *const token) { + if (isType(token, TOKEN_COMMA)) parseQualifierSpecList(token); + if (isType(token, TOKEN_DOUBLE_COLON)) readToken(token); + if (isType(token, TOKEN_IDENTIFIER)) makeFortranTag(token, TAG_DERIVED_TYPE); + ancestorPush(token); + skipToNextStatement(token); + if (isKeyword(token, KEYWORD_private) || isKeyword(token, KEYWORD_sequence)) { + skipToNextStatement(token); + } + while (!isKeyword(token, KEYWORD_end)) { + if (isTypeSpec(token)) + parseComponentDefStmt(token); + else + skipToNextStatement(token); + } + readSubToken(token); + /* secondary token should be KEYWORD_type token */ + skipToToken(token, TOKEN_STATEMENT_END); + ancestorPop(); +} + +/* interface-block + * interface-stmt (is INTERFACE [generic-spec]) + * [interface-body] + * [module-procedure-stmt] ... + * end-interface-stmt (is END INTERFACE) + * + * generic-spec + * is generic-name + * or OPERATOR ( defined-operator ) + * or ASSIGNMENT ( = ) + * + * interface-body + * is function-stmt + * [specification-part] + * end-function-stmt + * or subroutine-stmt + * [specification-part] + * end-subroutine-stmt + * + * module-procedure-stmt is + * MODULE PROCEDURE procedure-name-list + */ +static void parseInterfaceBlock(tokenInfo *const token) { + tokenInfo *name = NULL; + Assert(isKeyword(token, KEYWORD_interface)); + readToken(token); + if (isType(token, TOKEN_IDENTIFIER)) { + makeFortranTag(token, TAG_INTERFACE); + name = newTokenFrom(token); + } else if (isKeyword(token, KEYWORD_assignment) || + isKeyword(token, KEYWORD_operator)) { + readToken(token); + if (isType(token, TOKEN_PAREN_OPEN)) readToken(token); + if (isType(token, TOKEN_OPERATOR)) { + makeFortranTag(token, TAG_INTERFACE); + name = newTokenFrom(token); + } + } + if (name == NULL) { + name = newToken(); + name->type = TOKEN_IDENTIFIER; + name->tag = TAG_INTERFACE; + } + ancestorPush(name); + while (!isKeyword(token, KEYWORD_end)) { + switch (token->keyword) { + case KEYWORD_function: + parseFunctionSubprogram(token); + break; + case KEYWORD_subroutine: + parseSubroutineSubprogram(token); + break; + + default: + if (isSubprogramPrefix(token)) + readToken(token); + else if (isTypeSpec(token)) + parseTypeSpec(token); + else + skipToNextStatement(token); + break; + } + } + readSubToken(token); + /* secondary token should be KEYWORD_interface token */ + skipToNextStatement(token); + ancestorPop(); + deleteToken(name); +} + +/* entry-stmt is + * ENTRY entry-name [ ( dummy-arg-list ) ] + */ +static void parseEntryStmt(tokenInfo *const token) { + Assert(isKeyword(token, KEYWORD_entry)); + readToken(token); + if (isType(token, TOKEN_IDENTIFIER)) makeFortranTag(token, TAG_ENTRY_POINT); + skipToNextStatement(token); +} + +/* stmt-function-stmt is + * function-name ([dummy-arg-name-list]) = scalar-expr + */ +static boolean parseStmtFunctionStmt(tokenInfo *const token) { + boolean result = FALSE; + Assert(isType(token, TOKEN_IDENTIFIER)); +#if 0 /* cannot reliably parse this yet */ + makeFortranTag (token, TAG_FUNCTION); +#endif + readToken(token); + if (isType(token, TOKEN_PAREN_OPEN)) { + skipOverParens(token); + result = (boolean)(isType(token, TOKEN_OPERATOR) && + strcmp(vStringValue(token->string), "=") == 0); + } + skipToNextStatement(token); + return result; +} + +static boolean isIgnoredDeclaration(tokenInfo *const token) { + boolean result; + switch (token->keyword) { + case KEYWORD_cexternal: + case KEYWORD_cglobal: + case KEYWORD_dllexport: + case KEYWORD_dllimport: + case KEYWORD_external: + case KEYWORD_format: + case KEYWORD_include: + case KEYWORD_inline: + case KEYWORD_parameter: + case KEYWORD_pascal: + case KEYWORD_pexternal: + case KEYWORD_pglobal: + case KEYWORD_static: + case KEYWORD_value: + case KEYWORD_virtual: + case KEYWORD_volatile: + result = TRUE; + break; + + default: + result = FALSE; + break; + } + return result; +} + +/* declaration-construct + * [derived-type-def] + * [interface-block] + * [type-declaration-stmt] + * [specification-stmt] + * [parameter-stmt] (is PARAMETER ( named-constant-def-list ) + * [format-stmt] (is FORMAT format-specification) + * [entry-stmt] + * [stmt-function-stmt] + */ +static boolean parseDeclarationConstruct(tokenInfo *const token) { + boolean result = TRUE; + switch (token->keyword) { + case KEYWORD_entry: + parseEntryStmt(token); + break; + case KEYWORD_interface: + parseInterfaceBlock(token); + break; + case KEYWORD_stdcall: + readToken(token); + break; + /* derived type handled by parseTypeDeclarationStmt(); */ + + case KEYWORD_automatic: + readToken(token); + if (isTypeSpec(token)) + parseTypeDeclarationStmt(token); + else + skipToNextStatement(token); + result = TRUE; + break; + + default: + if (isIgnoredDeclaration(token)) + skipToNextStatement(token); + else if (isTypeSpec(token)) { + parseTypeDeclarationStmt(token); + result = TRUE; + } else if (isType(token, TOKEN_IDENTIFIER)) + result = parseStmtFunctionStmt(token); + else + result = parseSpecificationStmt(token); + break; + } + return result; +} + +/* implicit-part-stmt + * is [implicit-stmt] (is IMPLICIT etc.) + * or [parameter-stmt] (is PARAMETER etc.) + * or [format-stmt] (is FORMAT etc.) + * or [entry-stmt] (is ENTRY entry-name etc.) + */ +static boolean parseImplicitPartStmt(tokenInfo *const token) { + boolean result = TRUE; + switch (token->keyword) { + case KEYWORD_entry: + parseEntryStmt(token); + break; + + case KEYWORD_implicit: + case KEYWORD_include: + case KEYWORD_parameter: + case KEYWORD_format: + skipToNextStatement(token); + break; + + default: + result = FALSE; + break; + } + return result; +} + +/* specification-part is + * [use-stmt] ... (is USE module-name etc.) + * [implicit-part] (is [implicit-part-stmt] ... [implicit-stmt]) + * [declaration-construct] ... + */ +static boolean parseSpecificationPart(tokenInfo *const token) { + boolean result = FALSE; + while (skipStatementIfKeyword(token, KEYWORD_use)) result = TRUE; + while (parseImplicitPartStmt(token)) result = TRUE; + while (parseDeclarationConstruct(token)) result = TRUE; + return result; +} + +/* block-data is + * block-data-stmt (is BLOCK DATA [block-data-name] + * [specification-part] + * end-block-data-stmt (is END [BLOCK DATA [block-data-name]]) + */ +static void parseBlockData(tokenInfo *const token) { + Assert(isKeyword(token, KEYWORD_block)); + readToken(token); + if (isKeyword(token, KEYWORD_data)) { + readToken(token); + if (isType(token, TOKEN_IDENTIFIER)) makeFortranTag(token, TAG_BLOCK_DATA); + } + ancestorPush(token); + skipToNextStatement(token); + parseSpecificationPart(token); + while (!isKeyword(token, KEYWORD_end)) skipToNextStatement(token); + readSubToken(token); + /* secondary token should be KEYWORD_NONE or KEYWORD_block token */ + skipToNextStatement(token); + ancestorPop(); +} + +/* internal-subprogram-part is + * contains-stmt (is CONTAINS) + * internal-subprogram + * [internal-subprogram] ... + * + * internal-subprogram + * is function-subprogram + * or subroutine-subprogram + */ +static void parseInternalSubprogramPart(tokenInfo *const token) { + boolean done = FALSE; + if (isKeyword(token, KEYWORD_contains)) skipToNextStatement(token); + do { + switch (token->keyword) { + case KEYWORD_function: + parseFunctionSubprogram(token); + break; + case KEYWORD_subroutine: + parseSubroutineSubprogram(token); + break; + case KEYWORD_end: + done = TRUE; + break; + + default: + if (isSubprogramPrefix(token)) + readToken(token); + else if (isTypeSpec(token)) + parseTypeSpec(token); + else + readToken(token); + break; + } + } while (!done); +} + +/* module is + * module-stmt (is MODULE module-name) + * [specification-part] + * [module-subprogram-part] + * end-module-stmt (is END [MODULE [module-name]]) + * + * module-subprogram-part + * contains-stmt (is CONTAINS) + * module-subprogram + * [module-subprogram] ... + * + * module-subprogram + * is function-subprogram + * or subroutine-subprogram + */ +static void parseModule(tokenInfo *const token) { + Assert(isKeyword(token, KEYWORD_module)); + readToken(token); + if (isType(token, TOKEN_IDENTIFIER)) makeFortranTag(token, TAG_MODULE); + ancestorPush(token); + skipToNextStatement(token); + parseSpecificationPart(token); + if (isKeyword(token, KEYWORD_contains)) parseInternalSubprogramPart(token); + while (!isKeyword(token, KEYWORD_end)) skipToNextStatement(token); + readSubToken(token); + /* secondary token should be KEYWORD_NONE or KEYWORD_module token */ + skipToNextStatement(token); + ancestorPop(); +} + +/* execution-part + * executable-construct + * + * executable-contstruct is + * execution-part-construct [execution-part-construct] + * + * execution-part-construct + * is executable-construct + * or format-stmt + * or data-stmt + * or entry-stmt + */ +static boolean parseExecutionPart(tokenInfo *const token) { + boolean result = FALSE; + boolean done = FALSE; + while (!done) { + switch (token->keyword) { + default: + if (isSubprogramPrefix(token)) + readToken(token); + else + skipToNextStatement(token); + result = TRUE; + break; + + case KEYWORD_entry: + parseEntryStmt(token); + result = TRUE; + break; + + case KEYWORD_contains: + case KEYWORD_function: + case KEYWORD_subroutine: + done = TRUE; + break; + + case KEYWORD_end: + readSubToken(token); + if (isSecondaryKeyword(token, KEYWORD_do) || + isSecondaryKeyword(token, KEYWORD_if) || + isSecondaryKeyword(token, KEYWORD_select) || + isSecondaryKeyword(token, KEYWORD_where)) { + skipToNextStatement(token); + result = TRUE; + } else + done = TRUE; + break; + } + } + return result; +} + +static void parseSubprogram(tokenInfo *const token, const tagType tag) { + Assert(isKeyword(token, KEYWORD_program) || + isKeyword(token, KEYWORD_function) || + isKeyword(token, KEYWORD_subroutine)); + readToken(token); + if (isType(token, TOKEN_IDENTIFIER)) makeFortranTag(token, tag); + ancestorPush(token); + skipToNextStatement(token); + parseSpecificationPart(token); + parseExecutionPart(token); + if (isKeyword(token, KEYWORD_contains)) parseInternalSubprogramPart(token); + /* should be at KEYWORD_end token */ + readSubToken(token); + /* secondary token should be one of KEYWORD_NONE, KEYWORD_program, + * KEYWORD_function, KEYWORD_function + */ + skipToNextStatement(token); + ancestorPop(); +} + +/* function-subprogram is + * function-stmt (is [prefix] FUNCTION function-name etc.) + * [specification-part] + * [execution-part] + * [internal-subprogram-part] + * end-function-stmt (is END [FUNCTION [function-name]]) + * + * prefix + * is type-spec [RECURSIVE] + * or [RECURSIVE] type-spec + */ +static void parseFunctionSubprogram(tokenInfo *const token) { + parseSubprogram(token, TAG_FUNCTION); +} + +/* subroutine-subprogram is + * subroutine-stmt (is [RECURSIVE] SUBROUTINE subroutine-name etc.) + * [specification-part] + * [execution-part] + * [internal-subprogram-part] + * end-subroutine-stmt (is END [SUBROUTINE [function-name]]) + */ +static void parseSubroutineSubprogram(tokenInfo *const token) { + parseSubprogram(token, TAG_SUBROUTINE); +} + +/* main-program is + * [program-stmt] (is PROGRAM program-name) + * [specification-part] + * [execution-part] + * [internal-subprogram-part ] + * end-program-stmt + */ +static void parseMainProgram(tokenInfo *const token) { + parseSubprogram(token, TAG_PROGRAM); +} + +/* program-unit + * is main-program + * or external-subprogram (is function-subprogram or subroutine-subprogram) + * or module + * or block-data + */ +static void parseProgramUnit(tokenInfo *const token) { + readToken(token); + do { + if (isType(token, TOKEN_STATEMENT_END)) + readToken(token); + else + switch (token->keyword) { + case KEYWORD_block: + parseBlockData(token); + break; + case KEYWORD_end: + skipToNextStatement(token); + break; + case KEYWORD_function: + parseFunctionSubprogram(token); + break; + case KEYWORD_module: + parseModule(token); + break; + case KEYWORD_program: + parseMainProgram(token); + break; + case KEYWORD_subroutine: + parseSubroutineSubprogram(token); + break; + + default: + if (isSubprogramPrefix(token)) + readToken(token); + else { + boolean one = parseSpecificationPart(token); + boolean two = parseExecutionPart(token); + if (!(one || two)) readToken(token); + } + break; + } + } while (TRUE); +} + +static boolean findFortranTags(const unsigned int passCount) { + tokenInfo *token; + exception_t exception; + boolean retry; + + Assert(passCount < 3); + Parent = newToken(); + token = newToken(); + FreeSourceForm = (boolean)(passCount > 1); + Column = 0; + exception = (exception_t)setjmp(Exception); + if (exception == ExceptionEOF) + retry = FALSE; + else if (exception == ExceptionFixedFormat && !FreeSourceForm) { + verbose("%s: not fixed source form; retry as free source form\n", + getInputFileName()); + retry = TRUE; + } else { + parseProgramUnit(token); + retry = FALSE; + } + ancestorClear(); + deleteToken(token); + deleteToken(Parent); + + return retry; +} + +static void initialize(const langType language) { + Lang_fortran = language; + buildFortranKeywordHash(); +} + +extern parserDefinition *FortranParser(void) { + static const char *const extensions[] = { + "f", "for", "ftn", "f77", "f90", "f95", +#ifndef CASE_INSENSITIVE_FILENAMES + "F", "FOR", "FTN", "F77", "F90", "F95", +#endif + NULL}; + parserDefinition *def = parserNew("Fortran"); + def->kinds = FortranKinds; + def->kindCount = KIND_COUNT(FortranKinds); + def->extensions = extensions; + def->parser2 = findFortranTags; + def->initialize = initialize; + return def; +} + +/* vi:set tabstop=4 shiftwidth=4: */ diff --git a/third_party/ctags/general.h b/third_party/ctags/general.h new file mode 100644 index 00000000..7ce39a98 --- /dev/null +++ b/third_party/ctags/general.h @@ -0,0 +1,90 @@ +#ifndef _GENERAL_H +#define _GENERAL_H +#include "libc/calls/calls.h" +#include "libc/runtime/runtime.h" +#include "libc/str/str.h" +#include "third_party/ctags/config.h" + +/* Define standard error destination + */ +#ifndef errout +#define errout stderr +#endif + +/* Define regex if supported */ +#if (defined(HAVE_REGCOMP) && !defined(REGCOMP_BROKEN)) +#define HAVE_REGEX 1 +#endif + +/* This is a helpful internal feature of later versions (> 2.7) of GCC + * to prevent warnings about unused variables. + */ +#if (__GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 7)) && \ + !defined(__GNUG__) +#define __unused__ +#define __printf__(s, f) __attribute__((__format__(printf, s, f))) +#else +#define __unused__ +#define __printf__(s, f) +#endif + +/* + * Portability macros + */ +#if !defined(HAVE_STRCASECMP) && !defined(strcasecmp) +#ifdef HAVE_STRICMP +#define strcasecmp(s1, s2) stricmp(s1, s2) +#else +#define strcasecmp(s1, s2) struppercmp(s1, s2) +#endif +#endif + +#if !defined(HAVE_STRNCASECMP) && !defined(strncasecmp) +#ifdef HAVE_STRNICMP +#define strncasecmp(s1, s2, n) strnicmp(s1, s2, n) +#else +#define strncasecmp(s1, s2, n) strnuppercmp(s1, s2, n) +#endif +#endif + +/* + * DATA DECLARATIONS + */ + +#undef FALSE +#undef TRUE +#ifdef VAXC +typedef enum { FALSE, TRUE } booleanType; +typedef int boolean; +#else +#ifdef __cplusplus +typedef bool boolean; +#define FALSE false +#define TRUE true +#else +typedef enum { FALSE, TRUE } boolean; +#endif +#endif + +#if !defined(HAVE_FGETPOS) && !defined(fpos_t) +#define fpos_t long +#endif + +/* + * FUNCTION PROTOTYPES + */ + +#if defined(NEED_PROTO_REMOVE) && defined(HAVE_REMOVE) +extern int remove(const char *); +#endif + +#if defined(NEED_PROTO_UNLINK) && !defined(HAVE_REMOVE) +extern void *unlink(const char *); +#endif + +#ifdef NEED_PROTO_GETENV +extern char *getenv(const char *); +#endif + +/* vi:set tabstop=4 shiftwidth=4: */ +#endif /* _GENERAL_H */ diff --git a/third_party/ctags/get.c b/third_party/ctags/get.c new file mode 100644 index 00000000..5796312b --- /dev/null +++ b/third_party/ctags/get.c @@ -0,0 +1,614 @@ +/* + * $Id: get.c 559 2007-06-17 03:30:09Z elliotth $ + * + * Copyright (c) 1996-2002, Darren Hiebert + * + * This source code is released for free distribution under the terms of the + * GNU General Public License. + * + * This module contains the high level source read functions (preprocessor + * directives are handled within this level). + */ +#include "third_party/ctags/general.h" +/* must always come first */ +#include "libc/calls/calls.h" +#include "libc/str/str.h" +#include "third_party/ctags/debug.h" +#include "third_party/ctags/entry.h" +#include "third_party/ctags/get.h" +#include "third_party/ctags/options.h" +#include "third_party/ctags/read.h" +#include "third_party/ctags/vstring.h" + +/* + * MACROS + */ +#define stringMatch(s1, s2) (strcmp(s1, s2) == 0) +#define isspacetab(c) ((c) == SPACE || (c) == TAB) + +/* + * DATA DECLARATIONS + */ +typedef enum { COMMENT_NONE, COMMENT_C, COMMENT_CPLUS } Comment; + +enum eCppLimits { MaxCppNestingLevel = 20, MaxDirectiveName = 10 }; + +/* Defines the one nesting level of a preprocessor conditional. + */ +typedef struct sConditionalInfo { + boolean ignoreAllBranches; /* ignoring parent conditional branch */ + boolean singleBranch; /* choose only one branch */ + boolean branchChosen; /* branch already selected */ + boolean ignoring; /* current ignore state */ +} conditionalInfo; + +enum eState { + DRCTV_NONE, /* no known directive - ignore to end of line */ + DRCTV_DEFINE, /* "#define" encountered */ + DRCTV_HASH, /* initial '#' read; determine directive */ + DRCTV_IF, /* "#if" or "#ifdef" encountered */ + DRCTV_PRAGMA, /* #pragma encountered */ + DRCTV_UNDEF /* "#undef" encountered */ +}; + +/* Defines the current state of the pre-processor. + */ +typedef struct sCppState { + int ungetch, ungetch2; /* ungotten characters, if any */ + boolean resolveRequired; /* must resolve if/else/elif/endif branch */ + boolean hasAtLiteralStrings; /* supports @"c:\" strings */ + struct sDirective { + enum eState state; /* current directive being processed */ + boolean accept; /* is a directive syntactically permitted? */ + vString *name; /* macro name */ + unsigned int nestLevel; /* level 0 is not used */ + conditionalInfo ifdef[MaxCppNestingLevel]; + } directive; +} cppState; + +/* + * DATA DEFINITIONS + */ + +/* Use brace formatting to detect end of block. + */ +static boolean BraceFormat = FALSE; + +static cppState Cpp = { + '\0', + '\0', /* ungetch characters */ + FALSE, /* resolveRequired */ + FALSE, /* hasAtLiteralStrings */ + { + DRCTV_NONE, /* state */ + FALSE, /* accept */ + NULL, /* tag name */ + 0, /* nestLevel */ + {{FALSE, FALSE, FALSE, FALSE}} /* ifdef array */ + } /* directive */ +}; + +/* + * FUNCTION DEFINITIONS + */ + +extern boolean isBraceFormat(void) { + return BraceFormat; +} + +extern unsigned int getDirectiveNestLevel(void) { + return Cpp.directive.nestLevel; +} + +extern void cppInit(const boolean state, const boolean hasAtLiteralStrings) { + BraceFormat = state; + + Cpp.ungetch = '\0'; + Cpp.ungetch2 = '\0'; + Cpp.resolveRequired = FALSE; + Cpp.hasAtLiteralStrings = hasAtLiteralStrings; + + Cpp.directive.state = DRCTV_NONE; + Cpp.directive.accept = TRUE; + Cpp.directive.nestLevel = 0; + + Cpp.directive.ifdef[0].ignoreAllBranches = FALSE; + Cpp.directive.ifdef[0].singleBranch = FALSE; + Cpp.directive.ifdef[0].branchChosen = FALSE; + Cpp.directive.ifdef[0].ignoring = FALSE; + + if (Cpp.directive.name == NULL) + Cpp.directive.name = vStringNew(); + else + vStringClear(Cpp.directive.name); +} + +extern void cppTerminate(void) { + if (Cpp.directive.name != NULL) { + vStringDelete(Cpp.directive.name); + Cpp.directive.name = NULL; + } +} + +extern void cppBeginStatement(void) { + Cpp.resolveRequired = TRUE; +} + +extern void cppEndStatement(void) { + Cpp.resolveRequired = FALSE; +} + +/* + * Scanning functions + * + * This section handles preprocessor directives. It strips out all + * directives and may emit a tag for #define directives. + */ + +/* This puts a character back into the input queue for the source File. + * Up to two characters may be ungotten. + */ +extern void cppUngetc(const int c) { + Assert(Cpp.ungetch2 == '\0'); + Cpp.ungetch2 = Cpp.ungetch; + Cpp.ungetch = c; +} + +/* Reads a directive, whose first character is given by "c", into "name". + */ +static boolean readDirective(int c, char *const name, unsigned int maxLength) { + unsigned int i; + + for (i = 0; i < maxLength - 1; ++i) { + if (i > 0) { + c = fileGetc(); + if (c == EOF || !isalpha(c)) { + fileUngetc(c); + break; + } + } + name[i] = c; + } + name[i] = '\0'; /* null terminate */ + + return (boolean)isspacetab(c); +} + +/* Reads an identifier, whose first character is given by "c", into "tag", + * together with the file location and corresponding line number. + */ +static void readIdentifier(int c, vString *const name) { + vStringClear(name); + do { + vStringPut(name, c); + } while (c = fileGetc(), (c != EOF && isident(c))); + fileUngetc(c); + vStringTerminate(name); +} + +static conditionalInfo *currentConditional(void) { + return &Cpp.directive.ifdef[Cpp.directive.nestLevel]; +} + +static boolean isIgnore(void) { + return Cpp.directive.ifdef[Cpp.directive.nestLevel].ignoring; +} + +static boolean setIgnore(const boolean ignore) { + return Cpp.directive.ifdef[Cpp.directive.nestLevel].ignoring = ignore; +} + +static boolean isIgnoreBranch(void) { + conditionalInfo *const ifdef = currentConditional(); + + /* Force a single branch if an incomplete statement is discovered + * en route. This may have allowed earlier branches containing complete + * statements to be followed, but we must follow no further branches. + */ + if (Cpp.resolveRequired && !BraceFormat) ifdef->singleBranch = TRUE; + + /* We will ignore this branch in the following cases: + * + * 1. We are ignoring all branches (conditional was within an ignored + * branch of the parent conditional) + * 2. A branch has already been chosen and either of: + * a. A statement was incomplete upon entering the conditional + * b. A statement is incomplete upon encountering a branch + */ + return (boolean)(ifdef->ignoreAllBranches || + (ifdef->branchChosen && ifdef->singleBranch)); +} + +static void chooseBranch(void) { + if (!BraceFormat) { + conditionalInfo *const ifdef = currentConditional(); + + ifdef->branchChosen = (boolean)(ifdef->singleBranch || Cpp.resolveRequired); + } +} + +/* Pushes one nesting level for an #if directive, indicating whether or not + * the branch should be ignored and whether a branch has already been chosen. + */ +static boolean pushConditional(const boolean firstBranchChosen) { + const boolean ignoreAllBranches = isIgnore(); /* current ignore */ + boolean ignoreBranch = FALSE; + + if (Cpp.directive.nestLevel < (unsigned int)MaxCppNestingLevel - 1) { + conditionalInfo *ifdef; + + ++Cpp.directive.nestLevel; + ifdef = currentConditional(); + + /* We take a snapshot of whether there is an incomplete statement in + * progress upon encountering the preprocessor conditional. If so, + * then we will flag that only a single branch of the conditional + * should be followed. + */ + ifdef->ignoreAllBranches = ignoreAllBranches; + ifdef->singleBranch = Cpp.resolveRequired; + ifdef->branchChosen = firstBranchChosen; + ifdef->ignoring = + (boolean)(ignoreAllBranches || (!firstBranchChosen && !BraceFormat && + (ifdef->singleBranch || !Option.if0))); + ignoreBranch = ifdef->ignoring; + } + return ignoreBranch; +} + +/* Pops one nesting level for an #endif directive. + */ +static boolean popConditional(void) { + if (Cpp.directive.nestLevel > 0) --Cpp.directive.nestLevel; + + return isIgnore(); +} + +static void makeDefineTag(const char *const name) { + const boolean isFileScope = (boolean)(!isHeaderFile()); + + if (includingDefineTags() && (!isFileScope || Option.include.fileScope)) { + tagEntryInfo e; + initTagEntry(&e, name); + e.lineNumberEntry = (boolean)(Option.locate != EX_PATTERN); + e.isFileScope = isFileScope; + e.truncateLine = TRUE; + e.kindName = "macro"; + e.kind = 'd'; + makeTagEntry(&e); + } +} + +static void directiveDefine(const int c) { + if (isident1(c)) { + readIdentifier(c, Cpp.directive.name); + if (!isIgnore()) makeDefineTag(vStringValue(Cpp.directive.name)); + } + Cpp.directive.state = DRCTV_NONE; +} + +static void directivePragma(int c) { + if (isident1(c)) { + readIdentifier(c, Cpp.directive.name); + if (stringMatch(vStringValue(Cpp.directive.name), "weak")) { + /* generate macro tag for weak name */ + do { + c = fileGetc(); + } while (c == SPACE); + if (isident1(c)) { + readIdentifier(c, Cpp.directive.name); + makeDefineTag(vStringValue(Cpp.directive.name)); + } + } + } + Cpp.directive.state = DRCTV_NONE; +} + +static boolean directiveIf(const int c) { + DebugStatement(const boolean ignore0 = isIgnore();) const boolean ignore = + pushConditional((boolean)(c != '0')); + + Cpp.directive.state = DRCTV_NONE; + DebugStatement(debugCppNest(TRUE, Cpp.directive.nestLevel); + if (ignore != ignore0) debugCppIgnore(ignore);) + + return ignore; +} + +static boolean directiveHash(const int c) { + boolean ignore = FALSE; + char directive[MaxDirectiveName]; + DebugStatement(const boolean ignore0 = isIgnore();) + + readDirective(c, directive, MaxDirectiveName); + if (stringMatch(directive, "define")) + Cpp.directive.state = DRCTV_DEFINE; + else if (stringMatch(directive, "undef")) + Cpp.directive.state = DRCTV_UNDEF; + else if (strncmp(directive, "if", (size_t)2) == 0) + Cpp.directive.state = DRCTV_IF; + else if (stringMatch(directive, "elif") || stringMatch(directive, "else")) { + ignore = setIgnore(isIgnoreBranch()); + if (!ignore && stringMatch(directive, "else")) chooseBranch(); + Cpp.directive.state = DRCTV_NONE; + DebugStatement(if (ignore != ignore0) debugCppIgnore(ignore);) + } else if (stringMatch(directive, "endif")) { + DebugStatement(debugCppNest(FALSE, Cpp.directive.nestLevel);) ignore = + popConditional(); + Cpp.directive.state = DRCTV_NONE; + DebugStatement(if (ignore != ignore0) debugCppIgnore(ignore);) + } else if (stringMatch(directive, "pragma")) + Cpp.directive.state = DRCTV_PRAGMA; + else + Cpp.directive.state = DRCTV_NONE; + + return ignore; +} + +/* Handles a pre-processor directive whose first character is given by "c". + */ +static boolean handleDirective(const int c) { + boolean ignore = isIgnore(); + + switch (Cpp.directive.state) { + case DRCTV_NONE: + ignore = isIgnore(); + break; + case DRCTV_DEFINE: + directiveDefine(c); + break; + case DRCTV_HASH: + ignore = directiveHash(c); + break; + case DRCTV_IF: + ignore = directiveIf(c); + break; + case DRCTV_PRAGMA: + directivePragma(c); + break; + case DRCTV_UNDEF: + directiveDefine(c); + break; + } + return ignore; +} + +/* Called upon reading of a slash ('/') characters, determines whether a + * comment is encountered, and its type. + */ +static Comment isComment(void) { + Comment comment; + const int next = fileGetc(); + + if (next == '*') + comment = COMMENT_C; + else if (next == '/') + comment = COMMENT_CPLUS; + else { + fileUngetc(next); + comment = COMMENT_NONE; + } + return comment; +} + +/* Skips over a C style comment. According to ANSI specification a comment + * is treated as white space, so we perform this substitution. + */ +int skipOverCComment(void) { + int c = fileGetc(); + + while (c != EOF) { + if (c != '*') + c = fileGetc(); + else { + const int next = fileGetc(); + + if (next != '/') + c = next; + else { + c = SPACE; /* replace comment with space */ + break; + } + } + } + return c; +} + +/* Skips over a C++ style comment. + */ +static int skipOverCplusComment(void) { + int c; + + while ((c = fileGetc()) != EOF) { + if (c == BACKSLASH) + fileGetc(); /* throw away next character, too */ + else if (c == NEWLINE) + break; + } + return c; +} + +/* Skips to the end of a string, returning a special character to + * symbolically represent a generic string. + */ +static int skipToEndOfString(boolean ignoreBackslash) { + int c; + + while ((c = fileGetc()) != EOF) { + if (c == BACKSLASH && !ignoreBackslash) + fileGetc(); /* throw away next character, too */ + else if (c == DOUBLE_QUOTE) + break; + } + return STRING_SYMBOL; /* symbolic representation of string */ +} + +/* Skips to the end of the three (possibly four) 'c' sequence, returning a + * special character to symbolically represent a generic character. + * Also detects Vera numbers that include a base specifier (ie. 'b1010). + */ +static int skipToEndOfChar(void) { + int c; + int count = 0, veraBase = '\0'; + + while ((c = fileGetc()) != EOF) { + ++count; + if (c == BACKSLASH) + fileGetc(); /* throw away next character, too */ + else if (c == SINGLE_QUOTE) + break; + else if (c == NEWLINE) { + fileUngetc(c); + break; + } else if (count == 1 && strchr("DHOB", toupper(c)) != NULL) + veraBase = c; + else if (veraBase != '\0' && !isalnum(c)) { + fileUngetc(c); + break; + } + } + return CHAR_SYMBOL; /* symbolic representation of character */ +} + +/* This function returns the next character, stripping out comments, + * C pre-processor directives, and the contents of single and double + * quoted strings. In short, strip anything which places a burden upon + * the tokenizer. + */ +extern int cppGetc(void) { + boolean directive = FALSE; + boolean ignore = FALSE; + int c; + + if (Cpp.ungetch != '\0') { + c = Cpp.ungetch; + Cpp.ungetch = Cpp.ungetch2; + Cpp.ungetch2 = '\0'; + return c; /* return here to avoid re-calling debugPutc () */ + } else + do { + c = fileGetc(); + process: + switch (c) { + case EOF: + ignore = FALSE; + directive = FALSE; + break; + + case TAB: + case SPACE: + break; /* ignore most white space */ + + case NEWLINE: + if (directive && !ignore) directive = FALSE; + Cpp.directive.accept = TRUE; + break; + + case DOUBLE_QUOTE: + Cpp.directive.accept = FALSE; + c = skipToEndOfString(FALSE); + break; + + case '#': + if (Cpp.directive.accept) { + directive = TRUE; + Cpp.directive.state = DRCTV_HASH; + Cpp.directive.accept = FALSE; + } + break; + + case SINGLE_QUOTE: + Cpp.directive.accept = FALSE; + c = skipToEndOfChar(); + break; + + case '/': { + const Comment comment = isComment(); + + if (comment == COMMENT_C) + c = skipOverCComment(); + else if (comment == COMMENT_CPLUS) { + c = skipOverCplusComment(); + if (c == NEWLINE) fileUngetc(c); + } else + Cpp.directive.accept = FALSE; + break; + } + + case BACKSLASH: { + int next = fileGetc(); + + if (next == NEWLINE) + continue; + else if (next == '?') + cppUngetc(next); + else + fileUngetc(next); + break; + } + + case '?': { + int next = fileGetc(); + if (next != '?') + fileUngetc(next); + else { + next = fileGetc(); + switch (next) { + case '(': + c = '['; + break; + case ')': + c = ']'; + break; + case '<': + c = '{'; + break; + case '>': + c = '}'; + break; + case '/': + c = BACKSLASH; + goto process; + case '!': + c = '|'; + break; + case SINGLE_QUOTE: + c = '^'; + break; + case '-': + c = '~'; + break; + case '=': + c = '#'; + goto process; + default: + fileUngetc(next); + cppUngetc('?'); + break; + } + } + } break; + + default: + if (c == '@' && Cpp.hasAtLiteralStrings) { + int next = fileGetc(); + if (next == DOUBLE_QUOTE) { + Cpp.directive.accept = FALSE; + c = skipToEndOfString(TRUE); + break; + } + } + Cpp.directive.accept = FALSE; + if (directive) ignore = handleDirective(c); + break; + } + } while (directive || ignore); + + DebugStatement(debugPutc(DEBUG_CPP, c);) + DebugStatement(if (c == NEWLINE) debugPrintf( + DEBUG_CPP, "%6ld: ", getInputLineNumber() + 1);) + + return c; +} + +/* vi:set tabstop=4 shiftwidth=4: */ diff --git a/third_party/ctags/get.h b/third_party/ctags/get.h new file mode 100644 index 00000000..421c7e81 --- /dev/null +++ b/third_party/ctags/get.h @@ -0,0 +1,50 @@ +/* +* $Id: get.h 525 2007-05-28 01:50:41Z elliotth $ +* +* Copyright (c) 1998-2002, Darren Hiebert +* +* This source code is released for free distribution under the terms of the +* GNU General Public License. +* +* External interface to get.c +*/ +#ifndef _GET_H +#define _GET_H + +/* +* INCLUDE FILES +*/ +#include "third_party/ctags/general.h" /* must always come first */ + +#include "third_party/ctags/ctags.h" /* to define langType */ + +/* +* MACROS +*/ +/* Is the character valid as a character of a C identifier? + * VMS allows '$' in identifiers. + */ +#define isident(c) (isalnum(c) || (c) == '_' || (c) == '$') + +/* Is the character valid as the first character of a C identifier? + * C++ allows '~' in destructors. + * VMS allows '$' in identifiers. + */ +#define isident1(c) (isalpha(c) || (c) == '_' || (c) == '~' || (c) == '$') + +/* +* FUNCTION PROTOTYPES +*/ +extern boolean isBraceFormat (void); +extern unsigned int getDirectiveNestLevel (void); +extern void cppInit (const boolean state, const boolean hasAtLiteralStrings); +extern void cppTerminate (void); +extern void cppBeginStatement (void); +extern void cppEndStatement (void); +extern void cppUngetc (const int c); +extern int cppGetc (void); +extern int skipOverCComment (void); + +#endif /* _GET_H */ + +/* vi:set tabstop=4 shiftwidth=4: */ diff --git a/third_party/ctags/go.c b/third_party/ctags/go.c new file mode 100644 index 00000000..037afda7 --- /dev/null +++ b/third_party/ctags/go.c @@ -0,0 +1,596 @@ +#include "third_party/ctags/general.h" +/* must always come first */ +#include "libc/calls/calls.h" +#include "libc/runtime/runtime.h" +#include "libc/str/str.h" +#include "third_party/ctags/debug.h" +#include "third_party/ctags/entry.h" +#include "third_party/ctags/keyword.h" +#include "third_party/ctags/main.h" +#include "third_party/ctags/options.h" +#include "third_party/ctags/read.h" +#include "third_party/ctags/routines.h" +#include "third_party/ctags/vstring.h" + +/* + * MACROS + */ +#define isType(token, t) (boolean)((token)->type == (t)) +#define isKeyword(token, k) (boolean)((token)->keyword == (k)) + +/* + * DATA DECLARATIONS + */ + +typedef enum eException { ExceptionNone, ExceptionEOF } exception_t; + +typedef enum eKeywordId { + KEYWORD_NONE = -1, + KEYWORD_package, + KEYWORD_import, + KEYWORD_const, + KEYWORD_type, + KEYWORD_var, + KEYWORD_func, + KEYWORD_struct, + KEYWORD_interface, + KEYWORD_map, + KEYWORD_chan +} keywordId; + +/* Used to determine whether keyword is valid for the current language and + * what its ID is. + */ +typedef struct sKeywordDesc { + const char *name; + keywordId id; +} keywordDesc; + +typedef enum eTokenType { + TOKEN_NONE = -1, + TOKEN_CHARACTER, + // Don't need TOKEN_FORWARD_SLASH + TOKEN_FORWARD_SLASH, + TOKEN_KEYWORD, + TOKEN_IDENTIFIER, + TOKEN_STRING, + TOKEN_OPEN_PAREN, + TOKEN_CLOSE_PAREN, + TOKEN_OPEN_CURLY, + TOKEN_CLOSE_CURLY, + TOKEN_OPEN_SQUARE, + TOKEN_CLOSE_SQUARE, + TOKEN_SEMICOLON, + TOKEN_STAR, + TOKEN_LEFT_ARROW, + TOKEN_DOT, + TOKEN_COMMA +} tokenType; + +typedef struct sTokenInfo { + tokenType type; + keywordId keyword; + vString *string; /* the name of the token */ + unsigned long lineNumber; /* line number of tag */ + fpos_t filePosition; /* file position of line containing name */ +} tokenInfo; + +/* + * DATA DEFINITIONS + */ + +static int Lang_go; +static jmp_buf Exception; +static vString *scope; + +typedef enum { + GOTAG_UNDEFINED = -1, + GOTAG_PACKAGE, + GOTAG_FUNCTION, + GOTAG_CONST, + GOTAG_TYPE, + GOTAG_VAR, +} goKind; + +static kindOption GoKinds[] = {{TRUE, 'p', "package", "packages"}, + {TRUE, 'f', "func", "functions"}, + {TRUE, 'c', "const", "constants"}, + {TRUE, 't', "type", "types"}, + {TRUE, 'v', "var", "variables"}}; + +static keywordDesc GoKeywordTable[] = { + {"package", KEYWORD_package}, {"import", KEYWORD_import}, + {"const", KEYWORD_const}, {"type", KEYWORD_type}, + {"var", KEYWORD_var}, {"func", KEYWORD_func}, + {"struct", KEYWORD_struct}, {"interface", KEYWORD_interface}, + {"map", KEYWORD_map}, {"chan", KEYWORD_chan}}; + +/* + * FUNCTION DEFINITIONS + */ + +// XXX UTF-8 +static boolean isIdentChar(const int c) { + return (boolean)(isalpha(c) || isdigit(c) || c == '$' || c == '@' || + c == '_' || c == '#' || c > 128); +} + +static void initialize(const langType language) { + size_t i; + const size_t count = sizeof(GoKeywordTable) / sizeof(GoKeywordTable[0]); + Lang_go = language; + for (i = 0; i < count; ++i) { + const keywordDesc *const p = &GoKeywordTable[i]; + addKeyword(p->name, language, (int)p->id); + } +} + +static tokenInfo *newToken(void) { + tokenInfo *const token = xMalloc(1, tokenInfo); + token->type = TOKEN_NONE; + token->keyword = KEYWORD_NONE; + token->string = vStringNew(); + token->lineNumber = getSourceLineNumber(); + token->filePosition = getInputFilePosition(); + return token; +} + +static void deleteToken(tokenInfo *const token) { + if (token != NULL) { + vStringDelete(token->string); + eFree(token); + } +} + +/* + * Parsing functions + */ + +static void parseString(vString *const string, const int delimiter) { + boolean end = FALSE; + while (!end) { + int c = fileGetc(); + if (c == EOF) + end = TRUE; + else if (c == '\\' && delimiter != '`') { + c = fileGetc(); /* This maybe a ' or ". */ + vStringPut(string, c); + } else if (c == delimiter) + end = TRUE; + else + vStringPut(string, c); + } + vStringTerminate(string); +} + +static void parseIdentifier(vString *const string, const int firstChar) { + int c = firstChar; + // Assert (isIdentChar (c)); + do { + vStringPut(string, c); + c = fileGetc(); + } while (isIdentChar(c)); + vStringTerminate(string); + fileUngetc(c); /* always unget, LF might add a semicolon */ +} + +static void readToken(tokenInfo *const token) { + int c; + static tokenType lastTokenType = TOKEN_NONE; + + token->type = TOKEN_NONE; + token->keyword = KEYWORD_NONE; + vStringClear(token->string); + +getNextChar: + do { + c = fileGetc(); + token->lineNumber = getSourceLineNumber(); + token->filePosition = getInputFilePosition(); + if (c == '\n' && + (lastTokenType == TOKEN_IDENTIFIER || lastTokenType == TOKEN_STRING || + lastTokenType == TOKEN_CLOSE_PAREN || + lastTokenType == TOKEN_CLOSE_CURLY || + lastTokenType == TOKEN_CLOSE_SQUARE)) { + token->type = TOKEN_SEMICOLON; + goto done; + } + } while (c == '\t' || c == ' ' || c == '\r' || c == '\n'); + + switch (c) { + case EOF: + longjmp(Exception, (int)ExceptionEOF); + break; + + case '/': { + boolean hasNewline = FALSE; + int d = fileGetc(); + switch (d) { + case '/': + fileSkipToCharacter('\n'); + /* Line comments start with the + * character sequence // and + * continue through the next + * newline. A line comment acts + * like a newline. */ + fileUngetc('\n'); + goto getNextChar; + case '*': + do { + int d; + do { + d = fileGetc(); + if (d == '\n') { + hasNewline = TRUE; + } + } while (d != EOF && d != '*'); + + c = fileGetc(); + if (c == '/') + break; + else + fileUngetc(c); + } while (c != EOF && c != '\0'); + + fileUngetc(hasNewline ? '\n' : ' '); + goto getNextChar; + default: + token->type = TOKEN_FORWARD_SLASH; + fileUngetc(d); + break; + } + } break; + + case '"': + case '\'': + case '`': + token->type = TOKEN_STRING; + parseString(token->string, c); + token->lineNumber = getSourceLineNumber(); + token->filePosition = getInputFilePosition(); + break; + + case '<': { + int d = fileGetc(); + if (d == '-') { + token->type = TOKEN_LEFT_ARROW; + break; + } else + goto getNextChar; + } + + case '(': + token->type = TOKEN_OPEN_PAREN; + break; + + case ')': + token->type = TOKEN_CLOSE_PAREN; + break; + + case '{': + token->type = TOKEN_OPEN_CURLY; + break; + + case '}': + token->type = TOKEN_CLOSE_CURLY; + break; + + case '[': + token->type = TOKEN_OPEN_SQUARE; + break; + + case ']': + token->type = TOKEN_CLOSE_SQUARE; + break; + + case '*': + token->type = TOKEN_STAR; + break; + + case '.': + token->type = TOKEN_DOT; + break; + + case ',': + token->type = TOKEN_COMMA; + break; + + default: + parseIdentifier(token->string, c); + token->lineNumber = getSourceLineNumber(); + token->filePosition = getInputFilePosition(); + token->keyword = lookupKeyword(vStringValue(token->string), Lang_go); + if (isKeyword(token, KEYWORD_NONE)) + token->type = TOKEN_IDENTIFIER; + else + token->type = TOKEN_KEYWORD; + break; + } + +done: + lastTokenType = token->type; +} + +static void skipToMatched(tokenInfo *const token) { + int nest_level = 0; + tokenType open_token; + tokenType close_token; + + switch (token->type) { + case TOKEN_OPEN_PAREN: + open_token = TOKEN_OPEN_PAREN; + close_token = TOKEN_CLOSE_PAREN; + break; + case TOKEN_OPEN_CURLY: + open_token = TOKEN_OPEN_CURLY; + close_token = TOKEN_CLOSE_CURLY; + break; + case TOKEN_OPEN_SQUARE: + open_token = TOKEN_OPEN_SQUARE; + close_token = TOKEN_CLOSE_SQUARE; + break; + default: + return; + } + + /* + * This routine will skip to a matching closing token. + * It will also handle nested tokens like the (, ) below. + * ( name varchar(30), text binary(10) ) + */ + if (isType(token, open_token)) { + nest_level++; + while (!(isType(token, close_token) && (nest_level == 0))) { + readToken(token); + if (isType(token, open_token)) { + nest_level++; + } + if (isType(token, close_token)) { + if (nest_level > 0) { + nest_level--; + } + } + } + readToken(token); + } +} + +static void skipType(tokenInfo *const token) { +again: + // Type = TypeName | TypeLit | "(" Type ")" . + if (isType(token, TOKEN_OPEN_PAREN)) { + skipToMatched(token); + return; + } + + // TypeName = QualifiedIdent. + // QualifiedIdent = [ PackageName "." ] identifier . + // PackageName = identifier . + if (isType(token, TOKEN_IDENTIFIER)) { + readToken(token); + if (isType(token, TOKEN_DOT)) { + readToken(token); + Assert(isType(token, TOKEN_IDENTIFIER)); + readToken(token); + } + return; + } + + // StructType = "struct" "{" { FieldDecl ";" } "}" + // InterfaceType = "interface" "{" { MethodSpec ";" } "}" . + if (isKeyword(token, KEYWORD_struct) || isKeyword(token, KEYWORD_interface)) { + readToken(token); + Assert(isType(token, TOKEN_OPEN_CURLY)); + skipToMatched(token); + return; + } + + // ArrayType = "[" ArrayLength "]" ElementType . + // SliceType = "[" "]" ElementType . + // ElementType = Type . + if (isType(token, TOKEN_OPEN_SQUARE)) { + skipToMatched(token); + goto again; + } + + // PointerType = "*" BaseType . + // BaseType = Type . + // ChannelType = ( "chan" [ "<-" ] | "<-" "chan" ) ElementType . + if (isType(token, TOKEN_STAR) || isKeyword(token, KEYWORD_chan) || + isType(token, TOKEN_LEFT_ARROW)) { + readToken(token); + goto again; + } + + // MapType = "map" "[" KeyType "]" ElementType . + // KeyType = Type . + if (isKeyword(token, KEYWORD_map)) { + readToken(token); + Assert(isType(token, TOKEN_OPEN_SQUARE)); + skipToMatched(token); + goto again; + } + + // FunctionType = "func" Signature . + // Signature = Parameters [ Result ] . + // Result = Parameters | Type . + // Parameters = "(" [ ParameterList [ "," ] ] ")" . + if (isKeyword(token, KEYWORD_func)) { + readToken(token); + Assert(isType(token, TOKEN_OPEN_PAREN)); + // Parameters + skipToMatched(token); + // Result is parameters or type or nothing. skipType treats anything + // surrounded by parentheses as a type, and does nothing if what + // follows is not a type. + goto again; + } +} + +// Skip to the next semicolon, skipping over matching brackets. +static void skipToTopLevelSemicolon(tokenInfo *const token) { + while (!isType(token, TOKEN_SEMICOLON)) { + readToken(token); + skipToMatched(token); + } +} + +static void makeTag(tokenInfo *const token, const goKind kind) { + const char *const name = vStringValue(token->string); + + tagEntryInfo e; + initTagEntry(&e, name); + + if (!GoKinds[kind].enabled) return; + + e.lineNumber = token->lineNumber; + e.filePosition = token->filePosition; + e.kindName = GoKinds[kind].name; + e.kind = GoKinds[kind].letter; + + makeTagEntry(&e); + + if (scope && Option.include.qualifiedTags) { + vString *qualifiedName = vStringNew(); + vStringCopy(qualifiedName, scope); + vStringCatS(qualifiedName, "."); + vStringCat(qualifiedName, token->string); + e.name = vStringValue(qualifiedName); + makeTagEntry(&e); + vStringDelete(qualifiedName); + } +} + +static void parsePackage(tokenInfo *const token) { + tokenInfo *const name = newToken(); + + readToken(name); + Assert(isType(name, TOKEN_IDENTIFIER)); + makeTag(name, GOTAG_PACKAGE); + if (!scope && Option.include.qualifiedTags) { + scope = vStringNew(); + vStringCopy(scope, name->string); + } + + deleteToken(name); +} + +static void parseFunctionOrMethod(tokenInfo *const token) { + // FunctionDecl = "func" identifier Signature [ Body ] . + // Body = Block. + // + // MethodDecl = "func" Receiver MethodName Signature [ Body ] . + // Receiver = "(" [ identifier ] [ "*" ] BaseTypeName ")" . + // BaseTypeName = identifier . + tokenInfo *const name = newToken(); + + // Skip over receiver. + readToken(name); + if (isType(name, TOKEN_OPEN_PAREN)) skipToMatched(name); + + Assert(isType(name, TOKEN_IDENTIFIER)); + + // Skip over parameters. + readToken(token); + skipToMatched(token); + + // Skip over result. + skipType(token); + + // Skip over function body. + if (isType(token, TOKEN_OPEN_CURLY)) skipToMatched(token); + + makeTag(name, GOTAG_FUNCTION); + + deleteToken(name); +} + +static void parseConstTypeVar(tokenInfo *const token, goKind kind) { + // ConstDecl = "const" ( ConstSpec | "(" { ConstSpec ";" } ")" ) . + // ConstSpec = IdentifierList [ [ Type ] "=" ExpressionList ] . + // IdentifierList = identifier { "," identifier } . + // ExpressionList = Expression { "," Expression } . + // TypeDecl = "type" ( TypeSpec | "(" { TypeSpec ";" } ")" ) . + // TypeSpec = identifier Type . + // VarDecl = "var" ( VarSpec | "(" { VarSpec ";" } ")" ) . + // VarSpec = IdentifierList ( Type [ "=" ExpressionList ] | "=" + // ExpressionList ) . + tokenInfo *const name = newToken(); + boolean usesParens = FALSE; + + readToken(name); + + if (isType(name, TOKEN_OPEN_PAREN)) { + usesParens = TRUE; + readToken(name); + } + +again: + while (1) { + makeTag(name, kind); + readToken(token); + if (!isType(token, TOKEN_COMMA) && !isType(token, TOKEN_CLOSE_PAREN)) break; + readToken(name); + } + + skipType(token); + skipToTopLevelSemicolon(token); + + if (usesParens) { + readToken(name); + if (!isType(name, TOKEN_CLOSE_PAREN)) goto again; + } + + deleteToken(name); +} + +static void parseGoFile(tokenInfo *const token) { + do { + readToken(token); + + if (isType(token, TOKEN_KEYWORD)) { + switch (token->keyword) { + case KEYWORD_package: + parsePackage(token); + break; + case KEYWORD_func: + parseFunctionOrMethod(token); + break; + case KEYWORD_const: + parseConstTypeVar(token, GOTAG_CONST); + break; + case KEYWORD_type: + parseConstTypeVar(token, GOTAG_TYPE); + break; + case KEYWORD_var: + parseConstTypeVar(token, GOTAG_VAR); + break; + default: + break; + } + } + } while (TRUE); +} + +static void findGoTags(void) { + tokenInfo *const token = newToken(); + exception_t exception; + + exception = (exception_t)(setjmp(Exception)); + while (exception == ExceptionNone) parseGoFile(token); + + deleteToken(token); + vStringDelete(scope); + scope = NULL; +} + +extern parserDefinition *GoParser(void) { + static const char *const extensions[] = {"go", NULL}; + parserDefinition *def = parserNew("Go"); + def->kinds = GoKinds; + def->kindCount = KIND_COUNT(GoKinds); + def->extensions = extensions; + def->parser = findGoTags; + def->initialize = initialize; + return def; +} diff --git a/third_party/ctags/html.c b/third_party/ctags/html.c new file mode 100644 index 00000000..8eb4a8c2 --- /dev/null +++ b/third_party/ctags/html.c @@ -0,0 +1,41 @@ +/* + * $Id: html.c 443 2006-05-30 04:37:13Z darren $ + * + * Copyright (c) 2003, Darren Hiebert + * + * This source code is released for free distribution under the terms of the + * GNU General Public License. + * + * This module contains functions for generating tags for HTML language + * files. + */ +#include "third_party/ctags/general.h" +/* must always come first */ +#include "third_party/ctags/parse.h" + +/* + * FUNCTION DEFINITIONS + */ + +static void installHtmlRegex(const langType language) { +#define POSSIBLE_ATTRIBUTES "([ \t]+[a-z]+=\"?[^>\"]*\"?)*" + addTagRegex(language, + "\"]+)\"?" POSSIBLE_ATTRIBUTES "[ \t]*>", + "\\2", "a,anchor,named anchors", "i"); + + addTagRegex(language, "^[ \t]*function[ \t]*([A-Za-z0-9_]+)[ \t]*\\(", "\\1", + "f,function,JavaScript functions", NULL); +} + +/* Create parser definition stucture */ +extern parserDefinition *HtmlParser(void) { + static const char *const extensions[] = {"htm", "html", NULL}; + parserDefinition *const def = parserNew("HTML"); + def->extensions = extensions; + def->initialize = installHtmlRegex; + def->regex = TRUE; + return def; +} + +/* vi:set tabstop=4 shiftwidth=4: */ diff --git a/third_party/ctags/jscript.c b/third_party/ctags/jscript.c new file mode 100644 index 00000000..539a3bd3 --- /dev/null +++ b/third_party/ctags/jscript.c @@ -0,0 +1,1496 @@ +/* + * $Id: jscript.c 763 2010-07-28 14:22:42Z dfishburn $ + * + * Copyright (c) 2003, Darren Hiebert + * + * This source code is released for free distribution under the + * terms of the GNU General Public License. + * + * This module contains functions for generating tags for + * JavaScript language files. + * + * This is a good reference for different forms of the function statement: + * http://www.permadi.com/tutorial/jsFunc/ + * Another good reference: + * http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Guide + */ +#include "third_party/ctags/general.h" +/* must always come first */ +#include "third_party/ctags/debug.h" +#include "third_party/ctags/entry.h" +#include "third_party/ctags/keyword.h" +#include "third_party/ctags/parse.h" +#include "third_party/ctags/read.h" +#include "third_party/ctags/routines.h" +#include "third_party/ctags/vstring.h" + +/* + * MACROS + */ +#define isType(token, t) (boolean)((token)->type == (t)) +#define isKeyword(token, k) (boolean)((token)->keyword == (k)) + +/* + * DATA DECLARATIONS + */ + +typedef enum eException { ExceptionNone, ExceptionEOF } exception_t; + +/* + * Tracks class and function names already created + */ +static stringList *ClassNames; +static stringList *FunctionNames; + +/* Used to specify type of keyword. + */ +typedef enum eKeywordId { + KEYWORD_NONE = -1, + KEYWORD_function, + KEYWORD_capital_function, + KEYWORD_object, + KEYWORD_capital_object, + KEYWORD_prototype, + KEYWORD_var, + KEYWORD_new, + KEYWORD_this, + KEYWORD_for, + KEYWORD_while, + KEYWORD_do, + KEYWORD_if, + KEYWORD_else, + KEYWORD_switch, + KEYWORD_try, + KEYWORD_catch, + KEYWORD_finally +} keywordId; + +/* Used to determine whether keyword is valid for the token language and + * what its ID is. + */ +typedef struct sKeywordDesc { + const char *name; + keywordId id; +} keywordDesc; + +typedef enum eTokenType { + TOKEN_UNDEFINED, + TOKEN_CHARACTER, + TOKEN_CLOSE_PAREN, + TOKEN_SEMICOLON, + TOKEN_COLON, + TOKEN_COMMA, + TOKEN_KEYWORD, + TOKEN_OPEN_PAREN, + TOKEN_OPERATOR, + TOKEN_IDENTIFIER, + TOKEN_STRING, + TOKEN_PERIOD, + TOKEN_OPEN_CURLY, + TOKEN_CLOSE_CURLY, + TOKEN_EQUAL_SIGN, + TOKEN_FORWARD_SLASH, + TOKEN_OPEN_SQUARE, + TOKEN_CLOSE_SQUARE +} tokenType; + +typedef struct sTokenInfo { + tokenType type; + keywordId keyword; + vString *string; + vString *scope; + unsigned long lineNumber; + fpos_t filePosition; + int nestLevel; + boolean ignoreTag; +} tokenInfo; + +/* + * DATA DEFINITIONS + */ + +static langType Lang_js; + +static jmp_buf Exception; + +typedef enum { + JSTAG_FUNCTION, + JSTAG_CLASS, + JSTAG_METHOD, + JSTAG_PROPERTY, + JSTAG_VARIABLE, + JSTAG_COUNT +} jsKind; + +static kindOption JsKinds[] = {{TRUE, 'f', "function", "functions"}, + {TRUE, 'c', "class", "classes"}, + {TRUE, 'm', "method", "methods"}, + {TRUE, 'p', "property", "properties"}, + {TRUE, 'v', "variable", "global variables"}}; + +static const keywordDesc JsKeywordTable[] = { + /* keyword keyword ID */ + {"function", KEYWORD_function}, + {"Function", KEYWORD_capital_function}, + {"object", KEYWORD_object}, + {"Object", KEYWORD_capital_object}, + {"prototype", KEYWORD_prototype}, + {"var", KEYWORD_var}, + {"new", KEYWORD_new}, + {"this", KEYWORD_this}, + {"for", KEYWORD_for}, + {"while", KEYWORD_while}, + {"do", KEYWORD_do}, + {"if", KEYWORD_if}, + {"else", KEYWORD_else}, + {"switch", KEYWORD_switch}, + {"try", KEYWORD_try}, + {"catch", KEYWORD_catch}, + {"finally", KEYWORD_finally}}; + +/* + * FUNCTION DEFINITIONS + */ + +/* Recursive functions */ +static void parseFunction(tokenInfo *const token); +static boolean parseBlock(tokenInfo *const token, tokenInfo *const parent); +static boolean parseLine(tokenInfo *const token, boolean is_inside_class); + +static boolean isIdentChar(const int c) { + return (boolean)(isalpha(c) || isdigit(c) || c == '$' || c == '@' || + c == '_' || c == '#'); +} + +static void buildJsKeywordHash(void) { + const size_t count = sizeof(JsKeywordTable) / sizeof(JsKeywordTable[0]); + size_t i; + for (i = 0; i < count; ++i) { + const keywordDesc *const p = &JsKeywordTable[i]; + addKeyword(p->name, Lang_js, (int)p->id); + } +} + +static tokenInfo *newToken(void) { + tokenInfo *const token = xMalloc(1, tokenInfo); + + token->type = TOKEN_UNDEFINED; + token->keyword = KEYWORD_NONE; + token->string = vStringNew(); + token->scope = vStringNew(); + token->nestLevel = 0; + token->ignoreTag = FALSE; + token->lineNumber = getSourceLineNumber(); + token->filePosition = getInputFilePosition(); + + return token; +} + +static void deleteToken(tokenInfo *const token) { + vStringDelete(token->string); + vStringDelete(token->scope); + eFree(token); +} + +/* + * Tag generation functions + */ + +/* +static void makeConstTag (tokenInfo *const token, const jsKind kind) +{ + if (JsKinds [kind].enabled && ! token->ignoreTag ) + { + const char *const name = vStringValue (token->string); + tagEntryInfo e; + initTagEntry (&e, name); + + e.lineNumber = token->lineNumber; + e.filePosition = token->filePosition; + e.kindName = JsKinds [kind].name; + e.kind = JsKinds [kind].letter; + + makeTagEntry (&e); + } +} + +static void makeJsTag (tokenInfo *const token, const jsKind kind) +{ + vString * fulltag; + + if (JsKinds [kind].enabled && ! token->ignoreTag ) + { + * + * If a scope has been added to the token, change the token + * string to include the scope when making the tag. + * + if ( vStringLength(token->scope) > 0 ) + { + * + fulltag = vStringNew (); + vStringCopy(fulltag, token->scope); + vStringCatS (fulltag, "."); + vStringCatS (fulltag, vStringValue(token->string)); + vStringTerminate(fulltag); + vStringCopy(token->string, fulltag); + vStringDelete (fulltag); + * + jsKind parent_kind = JSTAG_CLASS; + + * + * if we're creating a function (and not a method), + * guess we're inside another function + * + if (kind == JSTAG_FUNCTION) + parent_kind = JSTAG_FUNCTION; + + e.extensionFields.scope[0] = JsKinds [parent_kind].name; + e.extensionFields.scope[1] = vStringValue +(token->scope); + } + * makeConstTag (token, kind); * + makeTagEntry (&e); + } +} +*/ + +static void makeJsTag(tokenInfo *const token, const jsKind kind) { + if (JsKinds[kind].enabled && !token->ignoreTag) { + const char *const name = vStringValue(token->string); + tagEntryInfo e; + initTagEntry(&e, name); + + e.lineNumber = token->lineNumber; + e.filePosition = token->filePosition; + e.kindName = JsKinds[kind].name; + e.kind = JsKinds[kind].letter; + + if (vStringLength(token->scope) > 0) { + jsKind parent_kind = JSTAG_CLASS; + + /* + * If we're creating a function (and not a method), + * guess we're inside another function + */ + if (kind == JSTAG_FUNCTION) parent_kind = JSTAG_FUNCTION; + + e.extensionFields.scope[0] = JsKinds[parent_kind].name; + e.extensionFields.scope[1] = vStringValue(token->scope); + } + + makeTagEntry(&e); + } +} + +static void makeClassTag(tokenInfo *const token) { + vString *fulltag; + + if (!token->ignoreTag) { + fulltag = vStringNew(); + if (vStringLength(token->scope) > 0) { + vStringCopy(fulltag, token->scope); + vStringCatS(fulltag, "."); + vStringCatS(fulltag, vStringValue(token->string)); + } else { + vStringCopy(fulltag, token->string); + } + vStringTerminate(fulltag); + if (!stringListHas(ClassNames, vStringValue(fulltag))) { + stringListAdd(ClassNames, vStringNewCopy(fulltag)); + makeJsTag(token, JSTAG_CLASS); + } + vStringDelete(fulltag); + } +} + +static void makeFunctionTag(tokenInfo *const token) { + vString *fulltag; + + if (!token->ignoreTag) { + fulltag = vStringNew(); + if (vStringLength(token->scope) > 0) { + vStringCopy(fulltag, token->scope); + vStringCatS(fulltag, "."); + vStringCatS(fulltag, vStringValue(token->string)); + } else { + vStringCopy(fulltag, token->string); + } + vStringTerminate(fulltag); + if (!stringListHas(FunctionNames, vStringValue(fulltag))) { + stringListAdd(FunctionNames, vStringNewCopy(fulltag)); + makeJsTag(token, JSTAG_FUNCTION); + } + vStringDelete(fulltag); + } +} + +/* + * Parsing functions + */ + +static void parseString(vString *const string, const int delimiter) { + boolean end = FALSE; + while (!end) { + int c = fileGetc(); + if (c == EOF) + end = TRUE; + else if (c == '\\') { + c = fileGetc(); /* This maybe a ' or ". */ + vStringPut(string, c); + } else if (c == delimiter) + end = TRUE; + else + vStringPut(string, c); + } + vStringTerminate(string); +} + +/* Read a C identifier beginning with "firstChar" and places it into + * "name". + */ +static void parseIdentifier(vString *const string, const int firstChar) { + int c = firstChar; + Assert(isIdentChar(c)); + do { + vStringPut(string, c); + c = fileGetc(); + } while (isIdentChar(c)); + vStringTerminate(string); + if (!isspace(c)) fileUngetc(c); /* unget non-identifier character */ +} + +static void readToken(tokenInfo *const token) { + int c; + + token->type = TOKEN_UNDEFINED; + token->keyword = KEYWORD_NONE; + vStringClear(token->string); + +getNextChar: + do { + c = fileGetc(); + token->lineNumber = getSourceLineNumber(); + token->filePosition = getInputFilePosition(); + } while (c == '\t' || c == ' ' || c == '\n'); + + switch (c) { + case EOF: + longjmp(Exception, (int)ExceptionEOF); + break; + case '(': + token->type = TOKEN_OPEN_PAREN; + break; + case ')': + token->type = TOKEN_CLOSE_PAREN; + break; + case ';': + token->type = TOKEN_SEMICOLON; + break; + case ',': + token->type = TOKEN_COMMA; + break; + case '.': + token->type = TOKEN_PERIOD; + break; + case ':': + token->type = TOKEN_COLON; + break; + case '{': + token->type = TOKEN_OPEN_CURLY; + break; + case '}': + token->type = TOKEN_CLOSE_CURLY; + break; + case '=': + token->type = TOKEN_EQUAL_SIGN; + break; + case '[': + token->type = TOKEN_OPEN_SQUARE; + break; + case ']': + token->type = TOKEN_CLOSE_SQUARE; + break; + + case '\'': + case '"': + token->type = TOKEN_STRING; + parseString(token->string, c); + token->lineNumber = getSourceLineNumber(); + token->filePosition = getInputFilePosition(); + break; + + case '\\': + c = fileGetc(); + if (c != '\\' && c != '"' && !isspace(c)) fileUngetc(c); + token->type = TOKEN_CHARACTER; + token->lineNumber = getSourceLineNumber(); + token->filePosition = getInputFilePosition(); + break; + + case '/': { + int d = fileGetc(); + if ((d != '*') && /* is this the start of a comment? */ + (d != '/')) /* is a one line comment? */ + { + token->type = TOKEN_FORWARD_SLASH; + fileUngetc(d); + } else { + if (d == '*') { + do { + fileSkipToCharacter('*'); + c = fileGetc(); + if (c == '/') + break; + else + fileUngetc(c); + } while (c != EOF && c != '\0'); + goto getNextChar; + } else if (d == '/') /* is this the start of a comment? */ + { + fileSkipToCharacter('\n'); + goto getNextChar; + } + } + break; + } + + default: + if (!isIdentChar(c)) + token->type = TOKEN_UNDEFINED; + else { + parseIdentifier(token->string, c); + token->lineNumber = getSourceLineNumber(); + token->filePosition = getInputFilePosition(); + token->keyword = analyzeToken(token->string, Lang_js); + if (isKeyword(token, KEYWORD_NONE)) + token->type = TOKEN_IDENTIFIER; + else + token->type = TOKEN_KEYWORD; + } + break; + } +} + +static void copyToken(tokenInfo *const dest, tokenInfo *const src) { + dest->nestLevel = src->nestLevel; + dest->lineNumber = src->lineNumber; + dest->filePosition = src->filePosition; + dest->type = src->type; + dest->keyword = src->keyword; + vStringCopy(dest->string, src->string); + vStringCopy(dest->scope, src->scope); +} + +/* + * Token parsing functions + */ + +static void skipArgumentList(tokenInfo *const token) { + int nest_level = 0; + + /* + * Other databases can have arguments with fully declared + * datatypes: + * ( name varchar(30), text binary(10) ) + * So we must check for nested open and closing parantheses + */ + + if (isType(token, TOKEN_OPEN_PAREN)) /* arguments? */ + { + nest_level++; + while (!(isType(token, TOKEN_CLOSE_PAREN) && (nest_level == 0))) { + readToken(token); + if (isType(token, TOKEN_OPEN_PAREN)) { + nest_level++; + } + if (isType(token, TOKEN_CLOSE_PAREN)) { + if (nest_level > 0) { + nest_level--; + } + } + } + readToken(token); + } +} + +static void skipArrayList(tokenInfo *const token) { + int nest_level = 0; + + /* + * Handle square brackets + * var name[1] + * So we must check for nested open and closing square brackets + */ + + if (isType(token, TOKEN_OPEN_SQUARE)) /* arguments? */ + { + nest_level++; + while (!(isType(token, TOKEN_CLOSE_SQUARE) && (nest_level == 0))) { + readToken(token); + if (isType(token, TOKEN_OPEN_SQUARE)) { + nest_level++; + } + if (isType(token, TOKEN_CLOSE_SQUARE)) { + if (nest_level > 0) { + nest_level--; + } + } + } + readToken(token); + } +} + +static void addContext(tokenInfo *const parent, const tokenInfo *const child) { + if (vStringLength(parent->string) > 0) { + vStringCatS(parent->string, "."); + } + vStringCatS(parent->string, vStringValue(child->string)); + vStringTerminate(parent->string); +} + +static void addToScope(tokenInfo *const token, vString *const extra) { + if (vStringLength(token->scope) > 0) { + vStringCatS(token->scope, "."); + } + vStringCatS(token->scope, vStringValue(extra)); + vStringTerminate(token->scope); +} + +/* + * Scanning functions + */ + +static void findCmdTerm(tokenInfo *const token) { + /* + * Read until we find either a semicolon or closing brace. + * Any nested braces will be handled within. + */ + while ( + !(isType(token, TOKEN_SEMICOLON) || isType(token, TOKEN_CLOSE_CURLY))) { + /* Handle nested blocks */ + if (isType(token, TOKEN_OPEN_CURLY)) { + parseBlock(token, token); + } else if (isType(token, TOKEN_OPEN_PAREN)) { + skipArgumentList(token); + } else { + readToken(token); + } + } +} + +static void parseSwitch(tokenInfo *const token) { + /* + * switch (expression){ + * case value1: + * statement; + * break; + * case value2: + * statement; + * break; + * default : statement; + * } + */ + + readToken(token); + + if (isType(token, TOKEN_OPEN_PAREN)) { + /* + * Handle nameless functions, these will only + * be considered methods. + */ + skipArgumentList(token); + } + + if (isType(token, TOKEN_OPEN_CURLY)) { + /* + * This will be either a function or a class. + * We can only determine this by checking the body + * of the function. If we find a "this." we know + * it is a class, otherwise it is a function. + */ + parseBlock(token, token); + } +} + +static void parseLoop(tokenInfo *const token) { + /* + * Handles these statements + * for (x=0; x<3; x++) + * document.write("This text is repeated three times
"); + * + * for (x=0; x<3; x++) + * { + * document.write("This text is repeated three times
"); + * } + * + * while (number<5){ + * document.write(number+"
"); + * number++; + * } + * + * do{ + * document.write(number+"
"); + * number++; + * } + * while (number<5); + */ + + if (isKeyword(token, KEYWORD_for) || isKeyword(token, KEYWORD_while)) { + readToken(token); + + if (isType(token, TOKEN_OPEN_PAREN)) { + /* + * Handle nameless functions, these will only + * be considered methods. + */ + skipArgumentList(token); + } + + if (isType(token, TOKEN_OPEN_CURLY)) { + /* + * This will be either a function or a class. + * We can only determine this by checking the body + * of the function. If we find a "this." we know + * it is a class, otherwise it is a function. + */ + parseBlock(token, token); + } else { + parseLine(token, FALSE); + } + } else if (isKeyword(token, KEYWORD_do)) { + readToken(token); + + if (isType(token, TOKEN_OPEN_CURLY)) { + /* + * This will be either a function or a class. + * We can only determine this by checking the body + * of the function. If we find a "this." we know + * it is a class, otherwise it is a function. + */ + parseBlock(token, token); + } else { + parseLine(token, FALSE); + } + + readToken(token); + + if (isKeyword(token, KEYWORD_while)) { + readToken(token); + + if (isType(token, TOKEN_OPEN_PAREN)) { + /* + * Handle nameless functions, these will only + * be considered methods. + */ + skipArgumentList(token); + } + } + } +} + +static boolean parseIf(tokenInfo *const token) { + boolean read_next_token = TRUE; + /* + * If statements have two forms + * if ( ... ) + * one line; + * + * if ( ... ) + * statement; + * else + * statement + * + * if ( ... ) { + * multiple; + * statements; + * } + * + * + * if ( ... ) { + * return elem + * } + * + * This example if correctly written, but the + * else contains only 1 statement without a terminator + * since the function finishes with the closing brace. + * + * function a(flag){ + * if(flag) + * test(1); + * else + * test(2) + * } + * + * TODO: Deal with statements that can optional end + * without a semi-colon. Currently this messes up + * the parsing of blocks. + * Need to somehow detect this has happened, and either + * backup a token, or skip reading the next token if + * that is possible from all code locations. + * + */ + + readToken(token); + + if (isKeyword(token, KEYWORD_if)) { + /* + * Check for an "else if" and consume the "if" + */ + readToken(token); + } + + if (isType(token, TOKEN_OPEN_PAREN)) { + /* + * Handle nameless functions, these will only + * be considered methods. + */ + skipArgumentList(token); + } + + if (isType(token, TOKEN_OPEN_CURLY)) { + /* + * This will be either a function or a class. + * We can only determine this by checking the body + * of the function. If we find a "this." we know + * it is a class, otherwise it is a function. + */ + parseBlock(token, token); + } else { + findCmdTerm(token); + + /* + * The IF could be followed by an ELSE statement. + * This too could have two formats, a curly braced + * multiline section, or another single line. + */ + + if (isType(token, TOKEN_CLOSE_CURLY)) { + /* + * This statement did not have a line terminator. + */ + read_next_token = FALSE; + } else { + readToken(token); + + if (isType(token, TOKEN_CLOSE_CURLY)) { + /* + * This statement did not have a line terminator. + */ + read_next_token = FALSE; + } else { + if (isKeyword(token, KEYWORD_else)) read_next_token = parseIf(token); + } + } + } + return read_next_token; +} + +static void parseFunction(tokenInfo *const token) { + tokenInfo *const name = newToken(); + boolean is_class = FALSE; + + /* + * This deals with these formats + * function validFunctionTwo(a,b) {} + */ + + readToken(name); + /* Add scope in case this is an INNER function */ + addToScope(name, token->scope); + + readToken(token); + if (isType(token, TOKEN_PERIOD)) { + do { + readToken(token); + if (isKeyword(token, KEYWORD_NONE)) { + addContext(name, token); + readToken(token); + } + } while (isType(token, TOKEN_PERIOD)); + } + + if (isType(token, TOKEN_OPEN_PAREN)) skipArgumentList(token); + + if (isType(token, TOKEN_OPEN_CURLY)) { + is_class = parseBlock(token, name); + if (is_class) + makeClassTag(name); + else + makeFunctionTag(name); + } + + findCmdTerm(token); + + deleteToken(name); +} + +static boolean parseBlock(tokenInfo *const token, tokenInfo *const parent) { + boolean is_class = FALSE; + boolean read_next_token = TRUE; + vString *saveScope = vStringNew(); + + token->nestLevel++; + /* + * Make this routine a bit more forgiving. + * If called on an open_curly advance it + */ + if (isType(token, TOKEN_OPEN_CURLY) && isKeyword(token, KEYWORD_NONE)) + readToken(token); + + if (!isType(token, TOKEN_CLOSE_CURLY)) { + /* + * Read until we find the closing brace, + * any nested braces will be handled within + */ + do { + read_next_token = TRUE; + if (isKeyword(token, KEYWORD_this)) { + /* + * Means we are inside a class and have found + * a class, not a function + */ + is_class = TRUE; + vStringCopy(saveScope, token->scope); + addToScope(token, parent->string); + + /* + * Ignore the remainder of the line + * findCmdTerm(token); + */ + parseLine(token, is_class); + + vStringCopy(token->scope, saveScope); + } else if (isKeyword(token, KEYWORD_var)) { + /* + * Potentially we have found an inner function. + * Set something to indicate the scope + */ + vStringCopy(saveScope, token->scope); + addToScope(token, parent->string); + parseLine(token, is_class); + vStringCopy(token->scope, saveScope); + } else if (isKeyword(token, KEYWORD_function)) { + vStringCopy(saveScope, token->scope); + addToScope(token, parent->string); + parseFunction(token); + vStringCopy(token->scope, saveScope); + } else if (isType(token, TOKEN_OPEN_CURLY)) { + /* Handle nested blocks */ + parseBlock(token, parent); + } else { + /* + * It is possible for a line to have no terminator + * if the following line is a closing brace. + * parseLine will detect this case and indicate + * whether we should read an additional token. + */ + read_next_token = parseLine(token, is_class); + } + + /* + * Always read a new token unless we find a statement without + * a ending terminator + */ + if (read_next_token) readToken(token); + + /* + * If we find a statement without a terminator consider the + * block finished, otherwise the stack will be off by one. + */ + } while (!isType(token, TOKEN_CLOSE_CURLY) && read_next_token); + } + + vStringDelete(saveScope); + token->nestLevel--; + + return is_class; +} + +static boolean parseMethods(tokenInfo *const token, tokenInfo *const class) { + tokenInfo *const name = newToken(); + boolean has_methods = FALSE; + + /* + * This deals with these formats + * validProperty : 2, + * validMethod : function(a,b) {} + * 'validMethod2' : function(a,b) {} + * container.dirtyTab = {'url': false, 'title':false, 'snapshot':false, + *'*': false} + */ + + do { + readToken(token); + if (isType(token, TOKEN_CLOSE_CURLY)) { + /* + * This was most likely a variable declaration of a hash table. + * indicate there were no methods and return. + */ + has_methods = FALSE; + goto cleanUp; + } + + if (isType(token, TOKEN_STRING) || isKeyword(token, KEYWORD_NONE)) { + copyToken(name, token); + + readToken(token); + if (isType(token, TOKEN_COLON)) { + readToken(token); + if (isKeyword(token, KEYWORD_function)) { + readToken(token); + if (isType(token, TOKEN_OPEN_PAREN)) { + skipArgumentList(token); + } + + if (isType(token, TOKEN_OPEN_CURLY)) { + has_methods = TRUE; + addToScope(name, class->string); + makeJsTag(name, JSTAG_METHOD); + parseBlock(token, name); + + /* + * Read to the closing curly, check next + * token, if a comma, we must loop again + */ + readToken(token); + } + } else { + has_methods = TRUE; + addToScope(name, class->string); + makeJsTag(name, JSTAG_PROPERTY); + + /* + * Read the next token, if a comma + * we must loop again + */ + readToken(token); + } + } + } + } while (isType(token, TOKEN_COMMA)); + + findCmdTerm(token); + +cleanUp: + deleteToken(name); + + return has_methods; +} + +static boolean parseStatement(tokenInfo *const token, boolean is_inside_class) { + tokenInfo *const name = newToken(); + tokenInfo *const secondary_name = newToken(); + vString *saveScope = vStringNew(); + boolean is_class = FALSE; + boolean is_terminated = TRUE; + boolean is_global = FALSE; + boolean is_prototype = FALSE; + boolean has_methods = FALSE; + vString *fulltag; + + vStringClear(saveScope); + /* + * Functions can be named or unnamed. + * This deals with these formats: + * Function + * validFunctionOne = function(a,b) {} + * testlib.validFunctionFive = function(a,b) {} + * var innerThree = function(a,b) {} + * var innerFour = (a,b) {} + * var D2 = secondary_fcn_name(a,b) {} + * var D3 = new Function("a", "b", "return a+b;"); + * Class + * testlib.extras.ValidClassOne = function(a,b) { + * this.a = a; + * } + * Class Methods + * testlib.extras.ValidClassOne.prototype = { + * 'validMethodOne' : function(a,b) {}, + * 'validMethodTwo' : function(a,b) {} + * } + * ValidClassTwo = function () + * { + * this.validMethodThree = function() {} + * // unnamed method + * this.validMethodFour = () {} + * } + * Database.prototype.validMethodThree = Database_getTodaysDate; + */ + + if (is_inside_class) is_class = TRUE; + /* + * var can preceed an inner function + */ + if (isKeyword(token, KEYWORD_var)) { + /* + * Only create variables for global scope + */ + if (token->nestLevel == 0) { + is_global = TRUE; + } + readToken(token); + } + + if (isKeyword(token, KEYWORD_this)) { + readToken(token); + if (isType(token, TOKEN_PERIOD)) { + readToken(token); + } + } + + copyToken(name, token); + + while (!isType(token, TOKEN_CLOSE_CURLY) && !isType(token, TOKEN_SEMICOLON) && + !isType(token, TOKEN_EQUAL_SIGN)) { + /* Potentially the name of the function */ + readToken(token); + if (isType(token, TOKEN_PERIOD)) { + /* + * Cannot be a global variable is it has dot references in the name + */ + is_global = FALSE; + do { + readToken(token); + if (isKeyword(token, KEYWORD_NONE)) { + if (is_class) { + vStringCopy(saveScope, token->scope); + addToScope(token, name->string); + } else + addContext(name, token); + } else if (isKeyword(token, KEYWORD_prototype)) { + /* + * When we reach the "prototype" tag, we infer: + * "BindAgent" is a class + * "build" is a method + * + * function BindAgent( repeatableIdName, newParentIdName ) { + * } + * + * CASE 1 + * Specified function name: "build" + * BindAgent.prototype.build = function( mode ) { + * ignore everything within this function + * } + * + * CASE 2 + * Prototype listing + * ValidClassOne.prototype = { + * 'validMethodOne' : function(a,b) {}, + * 'validMethodTwo' : function(a,b) {} + * } + * + */ + makeClassTag(name); + is_class = TRUE; + is_prototype = TRUE; + + /* + * There should a ".function_name" next. + */ + readToken(token); + if (isType(token, TOKEN_PERIOD)) { + /* + * Handle CASE 1 + */ + readToken(token); + if (isKeyword(token, KEYWORD_NONE)) { + vStringCopy(saveScope, token->scope); + addToScope(token, name->string); + + makeJsTag(token, JSTAG_METHOD); + /* + * We can read until the end of the block / statement. + * We need to correctly parse any nested blocks, but + * we do NOT want to create any tags based on what is + * within the blocks. + */ + token->ignoreTag = TRUE; + /* + * Find to the end of the statement + */ + findCmdTerm(token); + token->ignoreTag = FALSE; + is_terminated = TRUE; + goto cleanUp; + } + } else if (isType(token, TOKEN_EQUAL_SIGN)) { + readToken(token); + if (isType(token, TOKEN_OPEN_CURLY)) { + /* + * Handle CASE 2 + * + * Creates tags for each of these class methods + * ValidClassOne.prototype = { + * 'validMethodOne' : function(a,b) {}, + * 'validMethodTwo' : function(a,b) {} + * } + */ + parseMethods(token, name); + /* + * Find to the end of the statement + */ + findCmdTerm(token); + token->ignoreTag = FALSE; + is_terminated = TRUE; + goto cleanUp; + } + } + } + readToken(token); + } while (isType(token, TOKEN_PERIOD)); + } + + if (isType(token, TOKEN_OPEN_PAREN)) skipArgumentList(token); + + if (isType(token, TOKEN_OPEN_SQUARE)) skipArrayList(token); + + /* + if ( isType (token, TOKEN_OPEN_CURLY) ) + { + is_class = parseBlock (token, name); + } + */ + } + + if (isType(token, TOKEN_CLOSE_CURLY)) { + /* + * Reaching this section without having + * processed an open curly brace indicates + * the statement is most likely not terminated. + */ + is_terminated = FALSE; + goto cleanUp; + } + + if (isType(token, TOKEN_SEMICOLON)) { + /* + * Only create variables for global scope + */ + if (token->nestLevel == 0 && is_global) { + /* + * Handles this syntax: + * var g_var2; + */ + if (isType(token, TOKEN_SEMICOLON)) makeJsTag(name, JSTAG_VARIABLE); + } + /* + * Statement has ended. + * This deals with calls to functions, like: + * alert(..); + */ + goto cleanUp; + } + + if (isType(token, TOKEN_EQUAL_SIGN)) { + readToken(token); + + if (isKeyword(token, KEYWORD_function)) { + readToken(token); + + if (isKeyword(token, KEYWORD_NONE) && !isType(token, TOKEN_OPEN_PAREN)) { + /* + * Functions of this format: + * var D2A = function theAdd(a, b) + * { + * return a+b; + * } + * Are really two separate defined functions and + * can be referenced in two ways: + * alert( D2A(1,2) ); // produces 3 + * alert( theAdd(1,2) ); // also produces 3 + * So it must have two tags: + * D2A + * theAdd + * Save the reference to the name for later use, once + * we have established this is a valid function we will + * create the secondary reference to it. + */ + copyToken(secondary_name, token); + readToken(token); + } + + if (isType(token, TOKEN_OPEN_PAREN)) skipArgumentList(token); + + if (isType(token, TOKEN_OPEN_CURLY)) { + /* + * This will be either a function or a class. + * We can only determine this by checking the body + * of the function. If we find a "this." we know + * it is a class, otherwise it is a function. + */ + if (is_inside_class) { + makeJsTag(name, JSTAG_METHOD); + if (vStringLength(secondary_name->string) > 0) + makeFunctionTag(secondary_name); + parseBlock(token, name); + } else { + is_class = parseBlock(token, name); + if (is_class) + makeClassTag(name); + else + makeFunctionTag(name); + + if (vStringLength(secondary_name->string) > 0) + makeFunctionTag(secondary_name); + + /* + * Find to the end of the statement + */ + goto cleanUp; + } + } + } else if (isType(token, TOKEN_OPEN_PAREN)) { + /* + * Handle nameless functions + * this.method_name = () {} + */ + skipArgumentList(token); + + if (isType(token, TOKEN_OPEN_CURLY)) { + /* + * Nameless functions are only setup as methods. + */ + makeJsTag(name, JSTAG_METHOD); + parseBlock(token, name); + } + } else if (isType(token, TOKEN_OPEN_CURLY)) { + /* + * Creates tags for each of these class methods + * ValidClassOne.prototype = { + * 'validMethodOne' : function(a,b) {}, + * 'validMethodTwo' : function(a,b) {} + * } + * Or checks if this is a hash variable. + * var z = {}; + */ + has_methods = parseMethods(token, name); + if (!has_methods) { + /* + * Only create variables for global scope + */ + if (token->nestLevel == 0 && is_global) { + /* + * A pointer can be created to the function. + * If we recognize the function/class name ignore the variable. + * This format looks identical to a variable definition. + * A variable defined outside of a block is considered + * a global variable: + * var g_var1 = 1; + * var g_var2; + * This is not a global variable: + * var g_var = function; + * This is a global variable: + * var g_var = different_var_name; + */ + fulltag = vStringNew(); + if (vStringLength(token->scope) > 0) { + vStringCopy(fulltag, token->scope); + vStringCatS(fulltag, "."); + vStringCatS(fulltag, vStringValue(token->string)); + } else { + vStringCopy(fulltag, token->string); + } + vStringTerminate(fulltag); + if (!stringListHas(FunctionNames, vStringValue(fulltag)) && + !stringListHas(ClassNames, vStringValue(fulltag))) { + readToken(token); + if (!isType(token, TOKEN_SEMICOLON)) findCmdTerm(token); + if (isType(token, TOKEN_SEMICOLON)) makeJsTag(name, JSTAG_VARIABLE); + } + vStringDelete(fulltag); + } + } + if (isType(token, TOKEN_CLOSE_CURLY)) { + /* + * Assume the closing parantheses terminates + * this statements. + */ + is_terminated = TRUE; + } + } else if (isKeyword(token, KEYWORD_new)) { + readToken(token); + if (isKeyword(token, KEYWORD_function) || + isKeyword(token, KEYWORD_capital_function) || + isKeyword(token, KEYWORD_object) || + isKeyword(token, KEYWORD_capital_object)) { + if (isKeyword(token, KEYWORD_object) || + isKeyword(token, KEYWORD_capital_object)) + is_class = TRUE; + + readToken(token); + if (isType(token, TOKEN_OPEN_PAREN)) skipArgumentList(token); + + if (isType(token, TOKEN_SEMICOLON)) { + if (token->nestLevel == 0) { + if (is_class) { + makeClassTag(name); + } else { + makeFunctionTag(name); + } + } + } + } + } else if (isKeyword(token, KEYWORD_NONE)) { + /* + * Only create variables for global scope + */ + if (token->nestLevel == 0 && is_global) { + /* + * A pointer can be created to the function. + * If we recognize the function/class name ignore the variable. + * This format looks identical to a variable definition. + * A variable defined outside of a block is considered + * a global variable: + * var g_var1 = 1; + * var g_var2; + * This is not a global variable: + * var g_var = function; + * This is a global variable: + * var g_var = different_var_name; + */ + fulltag = vStringNew(); + if (vStringLength(token->scope) > 0) { + vStringCopy(fulltag, token->scope); + vStringCatS(fulltag, "."); + vStringCatS(fulltag, vStringValue(token->string)); + } else { + vStringCopy(fulltag, token->string); + } + vStringTerminate(fulltag); + if (!stringListHas(FunctionNames, vStringValue(fulltag)) && + !stringListHas(ClassNames, vStringValue(fulltag))) { + findCmdTerm(token); + if (isType(token, TOKEN_SEMICOLON)) makeJsTag(name, JSTAG_VARIABLE); + } + vStringDelete(fulltag); + } + } + } + findCmdTerm(token); + + /* + * Statements can be optionally terminated in the case of + * statement prior to a close curly brace as in the + * document.write line below: + * + * function checkForUpdate() { + * if( 1==1 ) { + * document.write("hello from checkForUpdate
") + * } + * return 1; + * } + */ + if (!is_terminated && isType(token, TOKEN_CLOSE_CURLY)) is_terminated = FALSE; + +cleanUp: + vStringCopy(token->scope, saveScope); + deleteToken(name); + deleteToken(secondary_name); + vStringDelete(saveScope); + + return is_terminated; +} + +static boolean parseLine(tokenInfo *const token, boolean is_inside_class) { + boolean is_terminated = TRUE; + /* + * Detect the common statements, if, while, for, do, ... + * This is necessary since the last statement within a block "{}" + * can be optionally terminated. + * + * If the statement is not terminated, we need to tell + * the calling routine to prevent reading an additional token + * looking for the end of the statement. + */ + + if (isType(token, TOKEN_KEYWORD)) { + switch (token->keyword) { + case KEYWORD_for: + case KEYWORD_while: + case KEYWORD_do: + parseLoop(token); + break; + case KEYWORD_if: + case KEYWORD_else: + case KEYWORD_try: + case KEYWORD_catch: + case KEYWORD_finally: + /* Common semantics */ + is_terminated = parseIf(token); + break; + case KEYWORD_switch: + parseSwitch(token); + break; + default: + parseStatement(token, is_inside_class); + break; + } + } else { + /* + * Special case where single line statements may not be + * SEMICOLON terminated. parseBlock needs to know this + * so that it does not read the next token. + */ + is_terminated = parseStatement(token, is_inside_class); + } + return is_terminated; +} + +static void parseJsFile(tokenInfo *const token) { + do { + readToken(token); + + if (isType(token, TOKEN_KEYWORD)) { + switch (token->keyword) { + case KEYWORD_function: + parseFunction(token); + break; + default: + parseLine(token, FALSE); + break; + } + } else { + parseLine(token, FALSE); + } + } while (TRUE); +} + +static void initialize(const langType language) { + Assert(sizeof(JsKinds) / sizeof(JsKinds[0]) == JSTAG_COUNT); + Lang_js = language; + buildJsKeywordHash(); +} + +static void findJsTags(void) { + tokenInfo *const token = newToken(); + exception_t exception; + + ClassNames = stringListNew(); + FunctionNames = stringListNew(); + + exception = (exception_t)(setjmp(Exception)); + while (exception == ExceptionNone) parseJsFile(token); + + stringListDelete(ClassNames); + stringListDelete(FunctionNames); + ClassNames = NULL; + FunctionNames = NULL; + deleteToken(token); +} + +/* Create parser definition stucture */ +extern parserDefinition *JavaScriptParser(void) { + static const char *const extensions[] = {"js", NULL}; + parserDefinition *const def = parserNew("JavaScript"); + def->extensions = extensions; + /* + * New definitions for parsing instead of regex + */ + def->kinds = JsKinds; + def->kindCount = KIND_COUNT(JsKinds); + def->parser = findJsTags; + def->initialize = initialize; + + return def; +} +/* vi:set tabstop=4 shiftwidth=4 noexpandtab: */ diff --git a/third_party/ctags/keyword.c b/third_party/ctags/keyword.c new file mode 100644 index 00000000..8db16e0f --- /dev/null +++ b/third_party/ctags/keyword.c @@ -0,0 +1,222 @@ +/* + * $Id: keyword.c 715 2009-07-06 03:31:00Z dhiebert $ + * + * Copyright (c) 1998-2002, Darren Hiebert + * + * This source code is released for free distribution under the terms of the + * GNU General Public License. + * + * Manages a keyword hash. + */ +#include "third_party/ctags/general.h" +/* must always come first */ +#include "third_party/ctags/debug.h" +#include "third_party/ctags/keyword.h" +#include "third_party/ctags/options.h" +#include "third_party/ctags/routines.h" + +/* + * MACROS + */ +#define HASH_EXPONENT 7 /* must be less than 17 */ + +/* + * DATA DECLARATIONS + */ +typedef struct sHashEntry { + struct sHashEntry *next; + const char *string; + langType language; + int value; +} hashEntry; + +/* + * DATA DEFINITIONS + */ +static const unsigned int TableSize = 1 << HASH_EXPONENT; +static hashEntry **HashTable = NULL; + +/* + * FUNCTION DEFINITIONS + */ + +static hashEntry **getHashTable(void) { + static boolean allocated = FALSE; + + if (!allocated) { + unsigned int i; + + HashTable = xMalloc(TableSize, hashEntry *); + + for (i = 0; i < TableSize; ++i) HashTable[i] = NULL; + + allocated = TRUE; + } + return HashTable; +} + +static hashEntry *getHashTableEntry(unsigned long hashedValue) { + hashEntry **const table = getHashTable(); + hashEntry *entry; + + Assert(hashedValue < TableSize); + entry = table[hashedValue]; + + return entry; +} + +static unsigned long hashValue(const char *const string) { + unsigned long value = 0; + const unsigned char *p; + + Assert(string != NULL); + + /* We combine the various words of the multiword key using the method + * described on page 512 of Vol. 3 of "The Art of Computer Programming". + */ + for (p = (const unsigned char *)string; *p != '\0'; ++p) { + value <<= 1; + if (value & 0x00000100L) value = (value & 0x000000ffL) + 1L; + value ^= *p; + } + /* Algorithm from page 509 of Vol. 3 of "The Art of Computer Programming" + * Treats "value" as a 16-bit integer plus 16-bit fraction. + */ + value *= 40503L; /* = 2^16 * 0.6180339887 ("golden ratio") */ + value &= 0x0000ffffL; /* keep fractional part */ + value >>= 16 - HASH_EXPONENT; /* scale up by hash size and move down */ + + return value; +} + +static hashEntry *newEntry(const char *const string, langType language, + int value) { + hashEntry *const entry = xMalloc(1, hashEntry); + + entry->next = NULL; + entry->string = string; + entry->language = language; + entry->value = value; + + return entry; +} + +/* Note that it is assumed that a "value" of zero means an undefined keyword + * and clients of this function should observe this. Also, all keywords added + * should be added in lower case. If we encounter a case-sensitive language + * whose keywords are in upper case, we will need to redesign this. + */ +extern void addKeyword(const char *const string, langType language, int value) { + const unsigned long hashedValue = hashValue(string); + hashEntry *entry = getHashTableEntry(hashedValue); + + if (entry == NULL) { + hashEntry **const table = getHashTable(); + table[hashedValue] = newEntry(string, language, value); + } else { + hashEntry *prev = NULL; + + while (entry != NULL) { + if (language == entry->language && strcmp(string, entry->string) == 0) { + Assert(("Already in table" == NULL)); + } + prev = entry; + entry = entry->next; + } + if (entry == NULL) { + Assert(prev != NULL); + prev->next = newEntry(string, language, value); + } + } +} + +extern int lookupKeyword(const char *const string, langType language) { + const unsigned long hashedValue = hashValue(string); + hashEntry *entry = getHashTableEntry(hashedValue); + int result = -1; + + while (entry != NULL) { + if (language == entry->language && strcmp(string, entry->string) == 0) { + result = entry->value; + break; + } + entry = entry->next; + } + return result; +} + +extern void freeKeywordTable(void) { + if (HashTable != NULL) { + unsigned int i; + + for (i = 0; i < TableSize; ++i) { + hashEntry *entry = HashTable[i]; + + while (entry != NULL) { + hashEntry *next = entry->next; + eFree(entry); + entry = next; + } + } + eFree(HashTable); + } +} + +extern int analyzeToken(vString *const name, langType language) { + vString *keyword = vStringNew(); + int result; + vStringCopyToLower(keyword, name); + result = lookupKeyword(vStringValue(keyword), language); + vStringDelete(keyword); + return result; +} + +#ifdef DEBUG + +static void printEntry(const hashEntry *const entry) { + printf(" %-15s %-7s\n", entry->string, getLanguageName(entry->language)); +} + +static unsigned int printBucket(const unsigned int i) { + hashEntry **const table = getHashTable(); + hashEntry *entry = table[i]; + unsigned int measure = 1; + boolean first = TRUE; + + printf("%2d:", i); + if (entry == NULL) + printf("\n"); + else + while (entry != NULL) { + if (!first) + printf(" "); + else { + printf(" "); + first = FALSE; + } + printEntry(entry); + entry = entry->next; + measure = 2 * measure; + } + return measure - 1; +} + +extern void printKeywordTable(void) { + unsigned long emptyBucketCount = 0; + unsigned long measure = 0; + unsigned int i; + + for (i = 0; i < TableSize; ++i) { + const unsigned int pass = printBucket(i); + + measure += pass; + if (pass == 0) ++emptyBucketCount; + } + + printf("spread measure = %ld\n", measure); + printf("%ld empty buckets\n", emptyBucketCount); +} + +#endif + +/* vi:set tabstop=4 shiftwidth=4: */ diff --git a/third_party/ctags/keyword.h b/third_party/ctags/keyword.h new file mode 100644 index 00000000..337fd7b8 --- /dev/null +++ b/third_party/ctags/keyword.h @@ -0,0 +1,18 @@ +#ifndef _KEYWORD_H +#define _KEYWORD_H +#include "third_party/ctags/general.h" +/* must always come first */ +#include "third_party/ctags/parse.h" + +/* + * FUNCTION PROTOTYPES + */ +extern void addKeyword(const char *const string, langType language, int value); +extern int lookupKeyword(const char *const string, langType language); +extern void freeKeywordTable(void); +#ifdef DEBUG +extern void printKeywordTable(void); +#endif +extern int analyzeToken(vString *const name, langType language); + +#endif /* _KEYWORD_H */ diff --git a/third_party/ctags/lisp.c b/third_party/ctags/lisp.c new file mode 100644 index 00000000..2830ed04 --- /dev/null +++ b/third_party/ctags/lisp.c @@ -0,0 +1,110 @@ +/* + * $Id: lisp.c 717 2009-07-07 03:40:50Z dhiebert $ + * + * Copyright (c) 2000-2002, Darren Hiebert + * + * This source code is released for free distribution under the terms of the + * GNU General Public License. + * + * This module contains functions for generating tags for LISP files. + */ +#include "third_party/ctags/general.h" +/* must always come first */ +#include "third_party/ctags/parse.h" +#include "third_party/ctags/read.h" +#include "third_party/ctags/vstring.h" + +/* + * DATA DEFINITIONS + */ +typedef enum { K_FUNCTION } lispKind; + +static kindOption LispKinds[] = {{TRUE, 'f', "function", "functions"}}; + +/* + * FUNCTION DEFINITIONS + */ + +/* + * lisp tag functions + * look for (def or (DEF, quote or QUOTE + */ +static int L_isdef(const unsigned char *strp) { + return ((strp[1] == 'd' || strp[1] == 'D') && + (strp[2] == 'e' || strp[2] == 'E') && + (strp[3] == 'f' || strp[3] == 'F')); +} + +static int L_isquote(const unsigned char *strp) { + return ((*(++strp) == 'q' || *strp == 'Q') && + (*(++strp) == 'u' || *strp == 'U') && + (*(++strp) == 'o' || *strp == 'O') && + (*(++strp) == 't' || *strp == 'T') && + (*(++strp) == 'e' || *strp == 'E') && isspace(*(++strp))); +} + +static void L_getit(vString *const name, const unsigned char *dbp) { + const unsigned char *p; + + if (*dbp == '\'') /* Skip prefix quote */ + dbp++; + else if (*dbp == '(' && L_isquote(dbp)) /* Skip "(quote " */ + { + dbp += 7; + while (isspace(*dbp)) dbp++; + } + for (p = dbp; *p != '\0' && *p != '(' && !isspace((int)*p) && *p != ')'; p++) + vStringPut(name, *p); + vStringTerminate(name); + + if (vStringLength(name) > 0) makeSimpleTag(name, LispKinds, K_FUNCTION); + vStringClear(name); +} + +/* Algorithm adapted from from GNU etags. + */ +static void findLispTags(void) { + vString *name = vStringNew(); + const unsigned char *p; + + while ((p = fileReadLine()) != NULL) { + if (*p == '(') { + if (L_isdef(p)) { + while (*p != '\0' && !isspace((int)*p)) p++; + while (isspace((int)*p)) p++; + L_getit(name, p); + } else { + /* Check for (foo::defmumble name-defined ... */ + do + p++; + while (*p != '\0' && !isspace((int)*p) && *p != ':' && *p != '(' && + *p != ')'); + if (*p == ':') { + do + p++; + while (*p == ':'); + + if (L_isdef(p - 1)) { + while (*p != '\0' && !isspace((int)*p)) p++; + while (isspace(*p)) p++; + L_getit(name, p); + } + } + } + } + } + vStringDelete(name); +} + +extern parserDefinition *LispParser(void) { + static const char *const extensions[] = {"cl", "clisp", "el", "l", + "lisp", "lsp", NULL}; + parserDefinition *def = parserNew("Lisp"); + def->kinds = LispKinds; + def->kindCount = KIND_COUNT(LispKinds); + def->extensions = extensions; + def->parser = findLispTags; + return def; +} + +/* vi:set tabstop=4 shiftwidth=4: */ diff --git a/third_party/ctags/lregex.c b/third_party/ctags/lregex.c new file mode 100644 index 00000000..a7011749 --- /dev/null +++ b/third_party/ctags/lregex.c @@ -0,0 +1,616 @@ +/* + * $Id: lregex.c 747 2009-11-06 02:33:37Z dhiebert $ + * + * Copyright (c) 2000-2003, Darren Hiebert + * + * This source code is released for free distribution under the terms of the + * GNU General Public License. + * + * This module contains functions for applying regular expression matching. + * + * The code for utlizing the Gnu regex package with regards to processing the + * regex option and checking for regex matches was adapted from routines in + * Gnu etags. + */ +#include "third_party/ctags/general.h" +/* must always come first */ +#include "third_party/ctags/debug.h" +#include "third_party/ctags/entry.h" +#include "third_party/ctags/parse.h" +#include "third_party/ctags/read.h" +#include "third_party/ctags/routines.h" +#include "third_party/regex/regex.h" + +#ifdef HAVE_REGEX + +/* + * MACROS + */ + +/* Back-references \0 through \9 */ +#define BACK_REFERENCE_COUNT 10 + +#if defined(HAVE_REGCOMP) && !defined(REGCOMP_BROKEN) +#define POSIX_REGEX +#endif + +#define REGEX_NAME "Regex" + +/* + * DATA DECLARATIONS + */ +#if defined(POSIX_REGEX) + +struct sKind { + boolean enabled; + char letter; + char* name; + char* description; +}; + +enum pType { PTRN_TAG, PTRN_CALLBACK }; + +typedef struct { + regex_t* pattern; + enum pType type; + union { + struct { + char* name_pattern; + struct sKind kind; + } tag; + struct { + regexCallback function; + } callback; + } u; +} regexPattern; + +#endif + +typedef struct { + regexPattern* patterns; + unsigned int count; +} patternSet; + +/* + * DATA DEFINITIONS + */ + +static boolean regexBroken = FALSE; + +/* Array of pattern sets, indexed by language */ +static patternSet* Sets = NULL; +static int SetUpper = -1; /* upper language index in list */ + +/* + * FUNCTION DEFINITIONS + */ + +static void clearPatternSet(const langType language) { + if (language <= SetUpper) { + patternSet* const set = Sets + language; + unsigned int i; + for (i = 0; i < set->count; ++i) { + regexPattern* p = &set->patterns[i]; +#if defined(POSIX_REGEX) + regfree(p->pattern); +#endif + eFree(p->pattern); + p->pattern = NULL; + + if (p->type == PTRN_TAG) { + eFree(p->u.tag.name_pattern); + p->u.tag.name_pattern = NULL; + eFree(p->u.tag.kind.name); + p->u.tag.kind.name = NULL; + if (p->u.tag.kind.description != NULL) { + eFree(p->u.tag.kind.description); + p->u.tag.kind.description = NULL; + } + } + } + if (set->patterns != NULL) eFree(set->patterns); + set->patterns = NULL; + set->count = 0; + } +} + +/* + * Regex psuedo-parser + */ + +static void makeRegexTag(const vString* const name, + const struct sKind* const kind) { + if (kind->enabled) { + tagEntryInfo e; + Assert(name != NULL && vStringLength(name) > 0); + Assert(kind != NULL); + initTagEntry(&e, vStringValue(name)); + e.kind = kind->letter; + e.kindName = kind->name; + makeTagEntry(&e); + } +} + +/* + * Regex pattern definition + */ + +/* Take a string like "/blah/" and turn it into "blah", making sure + * that the first and last characters are the same, and handling + * quoted separator characters. Actually, stops on the occurrence of + * an unquoted separator. Also turns "\t" into a Tab character. + * Returns pointer to terminating separator. Works in place. Null + * terminates name string. + */ +static char* scanSeparators(char* name) { + char sep = name[0]; + char* copyto = name; + boolean quoted = FALSE; + + for (++name; *name != '\0'; ++name) { + if (quoted) { + if (*name == sep) + *copyto++ = sep; + else if (*name == 't') + *copyto++ = '\t'; + else { + /* Something else is quoted, so preserve the quote. */ + *copyto++ = '\\'; + *copyto++ = *name; + } + quoted = FALSE; + } else if (*name == '\\') + quoted = TRUE; + else if (*name == sep) { + break; + } else + *copyto++ = *name; + } + *copyto = '\0'; + return name; +} + +/* Parse `regexp', in form "/regex/name/[k,Kind/]flags" (where the separator + * character is whatever the first character of `regexp' is), by breaking it + * up into null terminated strings, removing the separators, and expanding + * '\t' into tabs. When complete, `regexp' points to the line matching + * pattern, a pointer to the name matching pattern is written to `name', a + * pointer to the kinds is written to `kinds' (possibly NULL), and a pointer + * to the trailing flags is written to `flags'. If the pattern is not in the + * correct format, a false value is returned. + */ +static boolean parseTagRegex(char* const regexp, char** const name, + char** const kinds, char** const flags) { + boolean result = FALSE; + const int separator = (unsigned char)regexp[0]; + + *name = scanSeparators(regexp); + if (*regexp == '\0') + error(WARNING, "empty regexp"); + else if (**name != separator) + error(WARNING, "%s: incomplete regexp", regexp); + else { + char* const third = scanSeparators(*name); + if (**name == '\0') + error(WARNING, "%s: regexp missing name pattern", regexp); + if ((*name)[strlen(*name) - 1] == '\\') + error(WARNING, "error in name pattern: \"%s\"", *name); + if (*third != separator) + error(WARNING, "%s: regexp missing final separator", regexp); + else { + char* const fourth = scanSeparators(third); + if (*fourth == separator) { + *kinds = third; + scanSeparators(fourth); + *flags = fourth; + } else { + *flags = third; + *kinds = NULL; + } + result = TRUE; + } + } + return result; +} + +static void addCompiledTagPattern(const langType language, + regex_t* const pattern, char* const name, + const char kind, char* const kindName, + char* const description) { + patternSet* set; + regexPattern* ptrn; + if (language > SetUpper) { + int i; + Sets = xRealloc(Sets, (language + 1), patternSet); + for (i = SetUpper + 1; i <= language; ++i) { + Sets[i].patterns = NULL; + Sets[i].count = 0; + } + SetUpper = language; + } + set = Sets + language; + set->patterns = xRealloc(set->patterns, (set->count + 1), regexPattern); + ptrn = &set->patterns[set->count]; + set->count += 1; + + ptrn->pattern = pattern; + ptrn->type = PTRN_TAG; + ptrn->u.tag.name_pattern = name; + ptrn->u.tag.kind.enabled = TRUE; + ptrn->u.tag.kind.letter = kind; + ptrn->u.tag.kind.name = kindName; + ptrn->u.tag.kind.description = description; +} + +static void addCompiledCallbackPattern(const langType language, + regex_t* const pattern, + const regexCallback callback) { + patternSet* set; + regexPattern* ptrn; + if (language > SetUpper) { + int i; + Sets = xRealloc(Sets, (language + 1), patternSet); + for (i = SetUpper + 1; i <= language; ++i) { + Sets[i].patterns = NULL; + Sets[i].count = 0; + } + SetUpper = language; + } + set = Sets + language; + set->patterns = xRealloc(set->patterns, (set->count + 1), regexPattern); + ptrn = &set->patterns[set->count]; + set->count += 1; + + ptrn->pattern = pattern; + ptrn->type = PTRN_CALLBACK; + ptrn->u.callback.function = callback; +} + +#if defined(POSIX_REGEX) + +static regex_t* compileRegex(const char* const regexp, + const char* const flags) { + int cflags = REG_EXTENDED | REG_NEWLINE; + regex_t* result = NULL; + int errcode; + int i; + for (i = 0; flags != NULL && flags[i] != '\0'; ++i) { + switch ((int)flags[i]) { + case 'b': + cflags &= ~REG_EXTENDED; + break; + case 'e': + cflags |= REG_EXTENDED; + break; + case 'i': + cflags |= REG_ICASE; + break; + default: + error(WARNING, "unknown regex flag: '%c'", *flags); + break; + } + } + result = xMalloc(1, regex_t); + errcode = regcomp(result, regexp, cflags); + if (errcode != 0) { + char errmsg[256]; + regerror(errcode, result, errmsg, 256); + error(WARNING, "regcomp %s: %s", regexp, errmsg); + regfree(result); + eFree(result); + result = NULL; + } + return result; +} + +#endif + +static void parseKinds(const char* const kinds, char* const kind, + char** const kindName, char** description) { + *kind = '\0'; + *kindName = NULL; + *description = NULL; + if (kinds == NULL || kinds[0] == '\0') { + *kind = 'r'; + *kindName = eStrdup("regex"); + } else if (kinds[0] != '\0') { + const char* k = kinds; + if (k[0] != ',' && (k[1] == ',' || k[1] == '\0')) + *kind = *k++; + else + *kind = 'r'; + if (*k == ',') ++k; + if (k[0] == '\0') + *kindName = eStrdup("regex"); + else { + const char* const comma = strchr(k, ','); + if (comma == NULL) + *kindName = eStrdup(k); + else { + *kindName = (char*)eMalloc(comma - k + 1); + strncpy(*kindName, k, comma - k); + (*kindName)[comma - k] = '\0'; + k = comma + 1; + if (k[0] != '\0') *description = eStrdup(k); + } + } + } +} + +static void printRegexKind(const regexPattern* pat, unsigned int i, + boolean indent) { + const struct sKind* const kind = &pat[i].u.tag.kind; + const char* const indentation = indent ? " " : ""; + Assert(pat[i].type == PTRN_TAG); + printf("%s%c %s %s\n", indentation, + kind->letter != '\0' ? kind->letter : '?', + kind->description != NULL ? kind->description : kind->name, + kind->enabled ? "" : " [off]"); +} + +static void processLanguageRegex(const langType language, + const char* const parameter) { + if (parameter == NULL || parameter[0] == '\0') + clearPatternSet(language); + else if (parameter[0] != '@') + addLanguageRegex(language, parameter); + else if (!doesFileExist(parameter + 1)) + error(WARNING, "cannot open regex file"); + else { + const char* regexfile = parameter + 1; + FILE* const fp = fopen(regexfile, "r"); + if (fp == NULL) + error(WARNING | PERROR, "%s", regexfile); + else { + vString* const regex = vStringNew(); + while (readLine(regex, fp)) + addLanguageRegex(language, vStringValue(regex)); + fclose(fp); + vStringDelete(regex); + } + } +} + +/* + * Regex pattern matching + */ + +#if defined(POSIX_REGEX) + +static vString* substitute(const char* const in, const char* out, + const int nmatch, const regmatch_t* const pmatch) { + vString* result = vStringNew(); + const char* p; + for (p = out; *p != '\0'; p++) { + if (*p == '\\' && isdigit((int)*++p)) { + const int dig = *p - '0'; + if (0 < dig && dig < nmatch && pmatch[dig].rm_so != -1) { + const int diglen = pmatch[dig].rm_eo - pmatch[dig].rm_so; + vStringNCatS(result, in + pmatch[dig].rm_so, diglen); + } + } else if (*p != '\n' && *p != '\r') + vStringPut(result, *p); + } + vStringTerminate(result); + return result; +} + +static void matchTagPattern(const vString* const line, + const regexPattern* const patbuf, + const regmatch_t* const pmatch) { + vString* const name = + substitute(vStringValue(line), patbuf->u.tag.name_pattern, + BACK_REFERENCE_COUNT, pmatch); + vStringStripLeading(name); + vStringStripTrailing(name); + if (vStringLength(name) > 0) + makeRegexTag(name, &patbuf->u.tag.kind); + else + error(WARNING, "%s:%ld: null expansion of name pattern \"%s\"", + getInputFileName(), getInputLineNumber(), patbuf->u.tag.name_pattern); + vStringDelete(name); +} + +static void matchCallbackPattern(const vString* const line, + const regexPattern* const patbuf, + const regmatch_t* const pmatch) { + regexMatch matches[BACK_REFERENCE_COUNT]; + unsigned int count = 0; + int i; + for (i = 0; i < BACK_REFERENCE_COUNT && pmatch[i].rm_so != -1; ++i) { + matches[i].start = pmatch[i].rm_so; + matches[i].length = pmatch[i].rm_eo - pmatch[i].rm_so; + ++count; + } + patbuf->u.callback.function(vStringValue(line), matches, count); +} + +static boolean matchRegexPattern(const vString* const line, + const regexPattern* const patbuf) { + boolean result = FALSE; + regmatch_t pmatch[BACK_REFERENCE_COUNT]; + const int match = regexec(patbuf->pattern, vStringValue(line), + BACK_REFERENCE_COUNT, pmatch, 0); + if (match == 0) { + result = TRUE; + if (patbuf->type == PTRN_TAG) + matchTagPattern(line, patbuf, pmatch); + else if (patbuf->type == PTRN_CALLBACK) + matchCallbackPattern(line, patbuf, pmatch); + else { + Assert("invalid pattern type" == NULL); + result = FALSE; + } + } + return result; +} + +#endif + +/* PUBLIC INTERFACE */ + +/* Match against all patterns for specified language. Returns true if at least + * on pattern matched. + */ +extern boolean matchRegex(const vString* const line, const langType language) { + boolean result = FALSE; + if (language != LANG_IGNORE && language <= SetUpper && + Sets[language].count > 0) { + const patternSet* const set = Sets + language; + unsigned int i; + for (i = 0; i < set->count; ++i) + if (matchRegexPattern(line, set->patterns + i)) result = TRUE; + } + return result; +} + +extern void findRegexTags(void) { + /* merely read all lines of the file */ + while (fileReadLine() != NULL) + ; +} + +#endif /* HAVE_REGEX */ + +extern void addTagRegex(const langType language __unused__, + const char* const regex __unused__, + const char* const name __unused__, + const char* const kinds __unused__, + const char* const flags __unused__) { +#ifdef HAVE_REGEX + Assert(regex != NULL); + Assert(name != NULL); + if (!regexBroken) { + regex_t* const cp = compileRegex(regex, flags); + if (cp != NULL) { + char kind; + char* kindName; + char* description; + parseKinds(kinds, &kind, &kindName, &description); + addCompiledTagPattern(language, cp, eStrdup(name), kind, kindName, + description); + } + } +#endif +} + +extern void addCallbackRegex(const langType language __unused__, + const char* const regex __unused__, + const char* const flags __unused__, + const regexCallback callback __unused__) { +#ifdef HAVE_REGEX + Assert(regex != NULL); + if (!regexBroken) { + regex_t* const cp = compileRegex(regex, flags); + if (cp != NULL) addCompiledCallbackPattern(language, cp, callback); + } +#endif +} + +extern void addLanguageRegex(const langType language __unused__, + const char* const regex __unused__) { +#ifdef HAVE_REGEX + if (!regexBroken) { + char* const regex_pat = eStrdup(regex); + char *name, *kinds, *flags; + if (parseTagRegex(regex_pat, &name, &kinds, &flags)) { + addTagRegex(language, regex_pat, name, kinds, flags); + eFree(regex_pat); + } + } +#endif +} + +/* + * Regex option parsing + */ + +extern boolean processRegexOption(const char* const option, + const char* const parameter __unused__) { + boolean handled = FALSE; + const char* const dash = strchr(option, '-'); + if (dash != NULL && strncmp(option, "regex", dash - option) == 0) { +#ifdef HAVE_REGEX + langType language; + language = getNamedLanguage(dash + 1); + if (language == LANG_IGNORE) + error(WARNING, "unknown language \"%s\" in --%s option", (dash + 1), + option); + else + processLanguageRegex(language, parameter); +#else + error(WARNING, "regex support not available; required for --%s option", + option); +#endif + handled = TRUE; + } + return handled; +} + +extern void disableRegexKinds(const langType language __unused__) { +#ifdef HAVE_REGEX + if (language <= SetUpper && Sets[language].count > 0) { + patternSet* const set = Sets + language; + unsigned int i; + for (i = 0; i < set->count; ++i) + if (set->patterns[i].type == PTRN_TAG) + set->patterns[i].u.tag.kind.enabled = FALSE; + } +#endif +} + +extern boolean enableRegexKind(const langType language __unused__, + const int kind __unused__, + const boolean mode __unused__) { + boolean result = FALSE; +#ifdef HAVE_REGEX + if (language <= SetUpper && Sets[language].count > 0) { + patternSet* const set = Sets + language; + unsigned int i; + for (i = 0; i < set->count; ++i) + if (set->patterns[i].type == PTRN_TAG && + set->patterns[i].u.tag.kind.letter == kind) { + set->patterns[i].u.tag.kind.enabled = mode; + result = TRUE; + } + } +#endif + return result; +} + +extern void printRegexKinds(const langType language __unused__, + boolean indent __unused__) { +#ifdef HAVE_REGEX + if (language <= SetUpper && Sets[language].count > 0) { + patternSet* const set = Sets + language; + unsigned int i; + for (i = 0; i < set->count; ++i) + if (set->patterns[i].type == PTRN_TAG) + printRegexKind(set->patterns, i, indent); + } +#endif +} + +extern void freeRegexResources(void) { +#ifdef HAVE_REGEX + int i; + for (i = 0; i <= SetUpper; ++i) clearPatternSet(i); + if (Sets != NULL) eFree(Sets); + Sets = NULL; + SetUpper = -1; +#endif +} + +/* Check for broken regcomp() on Cygwin */ +extern void checkRegex(void) { +#if defined(HAVE_REGEX) && defined(CHECK_REGCOMP) + regex_t patbuf; + int errcode; + if (regcomp(&patbuf, "/hello/", 0) != 0) { + error(WARNING, "Disabling broken regex"); + regexBroken = TRUE; + } +#endif +} + +/* vi:set tabstop=4 shiftwidth=4: */ diff --git a/third_party/ctags/lua.c b/third_party/ctags/lua.c new file mode 100644 index 00000000..a7253e0d --- /dev/null +++ b/third_party/ctags/lua.c @@ -0,0 +1,108 @@ +/* + * $Id: lua.c 443 2006-05-30 04:37:13Z darren $ + * + * Copyright (c) 2000-2001, Max Ischenko . + * + * This source code is released for free distribution under the terms of the + * GNU General Public License. + * + * This module contains functions for generating tags for Lua language. + */ +#include "third_party/ctags/general.h" +/* must always come first */ +#include "third_party/ctags/options.h" +#include "third_party/ctags/parse.h" +#include "third_party/ctags/read.h" +#include "third_party/ctags/vstring.h" + +/* + * DATA DEFINITIONS + */ +typedef enum { K_FUNCTION } luaKind; + +static kindOption LuaKinds[] = {{TRUE, 'f', "function", "functions"}}; + +/* + * FUNCTION DEFINITIONS + */ + +/* for debugging purposes */ +static void __unused__ print_string(char *p, char *q) { + for (; p != q; p++) fprintf(errout, "%c", *p); + fprintf(errout, "\n"); +} + +/* + * Helper function. + * Returns 1 if line looks like a line of Lua code. + * + * TODO: Recognize UNIX bang notation. + * (Lua treat first line as a comment if it starts with #!) + * + */ +static boolean is_a_code_line(const unsigned char *line) { + boolean result; + const unsigned char *p = line; + while (isspace((int)*p)) p++; + if (p[0] == '\0') + result = FALSE; + else if (p[0] == '-' && p[1] == '-') + result = FALSE; + else + result = TRUE; + return result; +} + +static void extract_name(const char *begin, const char *end, vString *name) { + if (begin != NULL && end != NULL && begin < end) { + const char *cp; + + while (isspace((int)*begin)) begin++; + while (isspace((int)*end)) end--; + if (begin < end) { + for (cp = begin; cp != end; cp++) vStringPut(name, (int)*cp); + vStringTerminate(name); + + makeSimpleTag(name, LuaKinds, K_FUNCTION); + vStringClear(name); + } + } +} + +static void findLuaTags(void) { + vString *name = vStringNew(); + const unsigned char *line; + + while ((line = fileReadLine()) != NULL) { + const char *p, *q; + + if (!is_a_code_line(line)) continue; + + p = (const char *)strstr((const char *)line, "function"); + if (p == NULL) continue; + + q = strchr((const char *)line, '='); + + if (q == NULL) { + p = p + 9; /* skip the `function' word */ + q = strchr((const char *)p, '('); + extract_name(p, q, name); + } else { + p = (const char *)&line[0]; + extract_name(p, q, name); + } + } + vStringDelete(name); +} + +extern parserDefinition *LuaParser(void) { + static const char *const extensions[] = {"lua", NULL}; + parserDefinition *def = parserNew("Lua"); + def->kinds = LuaKinds; + def->kindCount = KIND_COUNT(LuaKinds); + def->extensions = extensions; + def->parser = findLuaTags; + return def; +} + +/* vi:set tabstop=4 shiftwidth=4: */ diff --git a/third_party/ctags/main.c b/third_party/ctags/main.c new file mode 100644 index 00000000..44516154 --- /dev/null +++ b/third_party/ctags/main.c @@ -0,0 +1,478 @@ +/* + * $Id: main.c 536 2007-06-02 06:09:00Z elliotth $ + * + * Copyright (c) 1996-2003, Darren Hiebert + * + * Author: Darren Hiebert + * http://ctags.sourceforge.net + * + * This source code is released for free distribution under the terms of the + * GNU General Public License. It is provided on an as-is basis and no + * responsibility is accepted for its failure to perform as expected. + * + * This is a reimplementation of the ctags (1) program. It is an attempt to + * provide a fully featured ctags program which is free of the limitations + * which most (all?) others are subject to. + * + * This module contains the start-up code and routines to determine the list + * of files to parsed for tags. + */ +#include "third_party/ctags/general.h" +/* must always come first */ +#include "libc/calls/struct/dirent.h" +#include "libc/calls/weirdtypes.h" +#include "libc/str/str.h" +#include "libc/time/time.h" +#include "third_party/ctags/debug.h" +#include "third_party/ctags/keyword.h" +#include "third_party/ctags/main.h" +#include "third_party/ctags/options.h" +#include "third_party/ctags/read.h" +#include "third_party/ctags/routines.h" + +/* + * MACROS + */ +#define plural(value) (((unsigned long)(value) == 1L) ? "" : "s") + +/* + * DATA DEFINITIONS + */ +static struct { long files, lines, bytes; } Totals = {0, 0, 0}; + +#ifdef AMIGA +#include "third_party/ctags/ctags.h" +static const char *VERsion = "$VER: " PROGRAM_NAME " " PROGRAM_VERSION " " +#ifdef __SASC + __AMIGADATE__ +#else + __DATE__ +#endif + " " AUTHOR_NAME " $"; +#endif + +/* + * FUNCTION PROTOTYPES + */ +static boolean createTagsForEntry(const char *const entryName); + +/* + * FUNCTION DEFINITIONS + */ + +extern void addTotals(const unsigned int files, const long unsigned int lines, + const long unsigned int bytes) { + Totals.files += files; + Totals.lines += lines; + Totals.bytes += bytes; +} + +extern boolean isDestinationStdout(void) { + boolean toStdout = FALSE; + + if (Option.xref || Option.filter || + (Option.tagFileName != NULL && + (strcmp(Option.tagFileName, "-") == 0 +#if defined(VMS) + || strcmp(Option.tagFileName, "sys$output") == 0 +#else + || strcmp(Option.tagFileName, "/dev/stdout") == 0 +#endif + ))) + toStdout = TRUE; + return toStdout; +} + +#if defined(HAVE_OPENDIR) +static boolean recurseUsingOpendir(const char *const dirName) { + boolean resize = FALSE; + DIR *const dir = opendir(dirName); + if (dir == NULL) + error(WARNING | PERROR, "cannot recurse into directory \"%s\"", dirName); + else { + struct dirent *entry; + while ((entry = readdir(dir)) != NULL) { + if (strcmp(entry->d_name, ".") != 0 && strcmp(entry->d_name, "..") != 0) { + vString *filePath; + if (strcmp(dirName, ".") == 0) + filePath = vStringNewInit(entry->d_name); + else + filePath = combinePathAndFile(dirName, entry->d_name); + resize |= createTagsForEntry(vStringValue(filePath)); + vStringDelete(filePath); + } + } + closedir(dir); + } + return resize; +} + +#elif defined(HAVE_FINDFIRST) || defined(HAVE__FINDFIRST) + +static boolean createTagsForWildcardEntry(const char *const pattern, + const size_t dirLength, + const char *const entryName) { + boolean resize = FALSE; + /* we must not recurse into the directories "." or ".." */ + if (strcmp(entryName, ".") != 0 && strcmp(entryName, "..") != 0) { + vString *const filePath = vStringNew(); + vStringNCopyS(filePath, pattern, dirLength); + vStringCatS(filePath, entryName); + resize = createTagsForEntry(vStringValue(filePath)); + vStringDelete(filePath); + } + return resize; +} + +static boolean createTagsForWildcardUsingFindfirst(const char *const pattern) { + boolean resize = FALSE; + const size_t dirLength = baseFilename(pattern) - pattern; +#if defined(HAVE_FINDFIRST) + struct ffblk fileInfo; + int result = findfirst(pattern, &fileInfo, FA_DIREC); + while (result == 0) { + const char *const entry = (const char *)fileInfo.ff_name; + resize |= createTagsForWildcardEntry(pattern, dirLength, entry); + result = findnext(&fileInfo); + } +#elif defined(HAVE__FINDFIRST) + struct _finddata_t fileInfo; + findfirst_t hFile = _findfirst(pattern, &fileInfo); + if (hFile != -1L) { + do { + const char *const entry = (const char *)fileInfo.name; + resize |= createTagsForWildcardEntry(pattern, dirLength, entry); + } while (_findnext(hFile, &fileInfo) == 0); + _findclose(hFile); + } +#endif + return resize; +} + +#elif defined(AMIGA) + +static boolean createTagsForAmigaWildcard(const char *const pattern) { + boolean resize = FALSE; + struct AnchorPath *const anchor = + (struct AnchorPath *)eMalloc((size_t)ANCHOR_SIZE); + LONG result; + + memset(anchor, 0, (size_t)ANCHOR_SIZE); + anchor->ap_Strlen = ANCHOR_BUF_SIZE; + /* Allow '.' for current directory */ +#ifdef APF_DODOT + anchor->ap_Flags = APF_DODOT | APF_DOWILD; +#else + anchor->ap_Flags = APF_DoDot | APF_DoWild; +#endif + result = MatchFirst((UBYTE *)pattern, anchor); + while (result == 0) { + resize |= createTagsForEntry((char *)anchor->ap_Buf); + result = MatchNext(anchor); + } + MatchEnd(anchor); + eFree(anchor); + return resize; +} +#endif + +static boolean recurseIntoDirectory(const char *const dirName) { + boolean resize = FALSE; + if (isRecursiveLink(dirName)) + verbose("ignoring \"%s\" (recursive link)\n", dirName); + else if (!Option.recurse) + verbose("ignoring \"%s\" (directory)\n", dirName); + else { + verbose("RECURSING into directory \"%s\"\n", dirName); +#if defined(HAVE_OPENDIR) + resize = recurseUsingOpendir(dirName); +#elif defined(HAVE_FINDFIRST) || defined(HAVE__FINDFIRST) + { + vString *const pattern = vStringNew(); + vStringCopyS(pattern, dirName); + vStringPut(pattern, OUTPUT_PATH_SEPARATOR); + vStringCatS(pattern, "*.*"); + resize = createTagsForWildcardUsingFindfirst(vStringValue(pattern)); + vStringDelete(pattern); + } +#elif defined(AMIGA) + { + vString *const pattern = vStringNew(); + if (*dirName != '\0' && strcmp(dirName, ".") != 0) { + vStringCopyS(pattern, dirName); + if (dirName[strlen(dirName) - 1] != '/') vStringPut(pattern, '/'); + } + vStringCatS(pattern, "#?"); + resize = createTagsForAmigaWildcard(vStringValue(pattern)); + vStringDelete(pattern); + } +#endif + } + return resize; +} + +static boolean createTagsForEntry(const char *const entryName) { + boolean resize = FALSE; + fileStatus *status = eStat(entryName); + + Assert(entryName != NULL); + if (isExcludedFile(entryName)) + verbose("excluding \"%s\"\n", entryName); + else if (status->isSymbolicLink && !Option.followLinks) + verbose("ignoring \"%s\" (symbolic link)\n", entryName); + else if (!status->exists) + error(WARNING | PERROR, "cannot open source file \"%s\"", entryName); + else if (status->isDirectory) + resize = recurseIntoDirectory(entryName); + else if (!status->isNormalFile) + verbose("ignoring \"%s\" (special file)\n", entryName); + else + resize = parseFile(entryName); + + eStatFree(status); + return resize; +} + +#ifdef MANUAL_GLOBBING + +static boolean createTagsForWildcardArg(const char *const arg) { + boolean resize = FALSE; + vString *const pattern = vStringNewInit(arg); + char *patternS = vStringValue(pattern); + +#if defined(HAVE_FINDFIRST) || defined(HAVE__FINDFIRST) + /* We must transform the "." and ".." forms into something that can + * be expanded by the findfirst/_findfirst functions. + */ + if (Option.recurse && + (strcmp(patternS, ".") == 0 || strcmp(patternS, "..") == 0)) { + vStringPut(pattern, OUTPUT_PATH_SEPARATOR); + vStringCatS(pattern, "*.*"); + } + resize |= createTagsForWildcardUsingFindfirst(patternS); +#endif + vStringDelete(pattern); + return resize; +} + +#endif + +static boolean createTagsForArgs(cookedArgs *const args) { + boolean resize = FALSE; + + /* Generate tags for each argument on the command line. + */ + while (!cArgOff(args)) { + const char *const arg = cArgItem(args); + +#ifdef MANUAL_GLOBBING + resize |= createTagsForWildcardArg(arg); +#else + resize |= createTagsForEntry(arg); +#endif + cArgForth(args); + parseOptions(args); + } + return resize; +} + +/* Read from an opened file a list of file names for which to generate tags. + */ +static boolean createTagsFromFileInput(FILE *const fp, const boolean filter) { + boolean resize = FALSE; + if (fp != NULL) { + cookedArgs *args = cArgNewFromLineFile(fp); + parseOptions(args); + while (!cArgOff(args)) { + resize |= createTagsForEntry(cArgItem(args)); + if (filter) { + if (Option.filterTerminator != NULL) + fputs(Option.filterTerminator, stdout); + fflush(stdout); + } + cArgForth(args); + parseOptions(args); + } + cArgDelete(args); + } + return resize; +} + +/* Read from a named file a list of file names for which to generate tags. + */ +static boolean createTagsFromListFile(const char *const fileName) { + boolean resize; + Assert(fileName != NULL); + if (strcmp(fileName, "-") == 0) + resize = createTagsFromFileInput(stdin, FALSE); + else { + FILE *const fp = fopen(fileName, "r"); + if (fp == NULL) + error(FATAL | PERROR, "cannot open list file \"%s\"", fileName); + resize = createTagsFromFileInput(fp, FALSE); + fclose(fp); + } + return resize; +} + +#if defined(HAVE_CLOCK) +#define CLOCK_AVAILABLE +#ifndef CLOCKS_PER_SEC +#define CLOCKS_PER_SEC 1000000 +#endif +#elif defined(HAVE_TIMES) +#define CLOCK_AVAILABLE +#define CLOCKS_PER_SEC 60 +static clock_t clock(void) { + struct tms buf; + + times(&buf); + return (buf.tms_utime + buf.tms_stime); +} +#else +#define clock() (clock_t)0 +#endif + +static void printTotals(const clock_t *const timeStamps) { + const unsigned long totalTags = TagFile.numTags.added + TagFile.numTags.prev; + + fprintf(errout, "%ld file%s, %ld line%s (%ld kB) scanned", Totals.files, + plural(Totals.files), Totals.lines, plural(Totals.lines), + Totals.bytes / 1024L); +#ifdef CLOCK_AVAILABLE + { + const double interval = + ((double)(timeStamps[1] - timeStamps[0])) / CLOCKS_PER_SEC; + + fprintf(errout, " in %.01f seconds", interval); + if (interval != (double)0.0) + fprintf(errout, " (%lu kB/s)", + (unsigned long)(Totals.bytes / interval) / 1024L); + } +#endif + fputc('\n', errout); + + fprintf(errout, "%lu tag%s added to tag file", TagFile.numTags.added, + plural(TagFile.numTags.added)); + if (Option.append) fprintf(errout, " (now %lu tags)", totalTags); + fputc('\n', errout); + + if (totalTags > 0 && Option.sorted != SO_UNSORTED) { + fprintf(errout, "%lu tag%s sorted", totalTags, plural(totalTags)); +#ifdef CLOCK_AVAILABLE + fprintf(errout, " in %.02f seconds", + ((double)(timeStamps[2] - timeStamps[1])) / CLOCKS_PER_SEC); +#endif + fputc('\n', errout); + } + +#ifdef DEBUG + fprintf(errout, "longest tag line = %lu\n", (unsigned long)TagFile.max.line); +#endif +} + +static boolean etagsInclude(void) { + return (boolean)(Option.etags && Option.etagsInclude != NULL); +} + +static void makeTags(cookedArgs *args) { + clock_t timeStamps[3]; + boolean resize = FALSE; + boolean files = + (boolean)(!cArgOff(args) || Option.fileList != NULL || Option.filter); + + if (!files) { + if (filesRequired()) + error(FATAL, "No files specified. Try \"%s --help\".", + getExecutableName()); + else if (!Option.recurse && !etagsInclude()) + return; + } + +#define timeStamp(n) \ + timeStamps[(n)] = (Option.printTotals ? clock() : (clock_t)0) + if (!Option.filter) openTagFile(); + + timeStamp(0); + + if (!cArgOff(args)) { + verbose("Reading command line arguments\n"); + resize = createTagsForArgs(args); + } + if (Option.fileList != NULL) { + verbose("Reading list file\n"); + resize = (boolean)(createTagsFromListFile(Option.fileList) || resize); + } + if (Option.filter) { + verbose("Reading filter input\n"); + resize = (boolean)(createTagsFromFileInput(stdin, TRUE) || resize); + } + if (!files && Option.recurse) resize = recurseIntoDirectory("."); + + timeStamp(1); + + if (!Option.filter) closeTagFile(resize); + + timeStamp(2); + + if (Option.printTotals) printTotals(timeStamps); +#undef timeStamp +} + +/* + * Start up code + */ + +extern int main(int __unused__ argc, char **argv) { + cookedArgs *args; +#ifdef VMS + extern int getredirection(int *ac, char ***av); + + /* do wildcard expansion and I/O redirection */ + getredirection(&argc, &argv); +#endif + +#ifdef AMIGA + /* This program doesn't work when started from the Workbench */ + if (argc == 0) exit(1); +#endif + +#ifdef __EMX__ + _wildcard(&argc, &argv); /* expand wildcards in argument list */ +#endif + +#if defined(macintosh) && BUILD_MPW_TOOL == 0 + argc = ccommand(&argv); +#endif + + setCurrentDirectory(); + setExecutableName(*argv++); + checkRegex(); + + args = cArgNewFromArgv(argv); + previewFirstOption(args); + testEtagsInvocation(); + initializeParsing(); + initOptions(); + readOptionConfiguration(); + verbose("Reading initial options from command line\n"); + parseOptions(args); + checkOptions(); + makeTags(args); + + /* Clean up. + */ + cArgDelete(args); + freeKeywordTable(); + freeRoutineResources(); + freeSourceFileResources(); + freeTagFileResources(); + freeOptionResources(); + freeParserResources(); + freeRegexResources(); + + exit(0); + return 0; +} + +/* vi:set tabstop=4 shiftwidth=4: */ diff --git a/third_party/ctags/main.h b/third_party/ctags/main.h new file mode 100644 index 00000000..77032e2b --- /dev/null +++ b/third_party/ctags/main.h @@ -0,0 +1,15 @@ +#ifndef _MAIN_H +#define _MAIN_H +#include "third_party/ctags/general.h" +/* must always come first */ +#include "third_party/ctags/vstring.h" + +/* + * FUNCTION PROTOTYPES + */ +extern void addTotals(const unsigned int files, const long unsigned int lines, + const long unsigned int bytes); +extern boolean isDestinationStdout(void); +extern int main(int argc, char **argv); + +#endif /* _MAIN_H */ diff --git a/third_party/ctags/make.c b/third_party/ctags/make.c new file mode 100644 index 00000000..ab658d05 --- /dev/null +++ b/third_party/ctags/make.c @@ -0,0 +1,175 @@ +/* + * $Id: make.c 751 2010-02-27 17:41:57Z elliotth $ + * + * Copyright (c) 2000-2005, Darren Hiebert + * + * This source code is released for free distribution under the terms of the + * GNU General Public License. + * + * This module contains functions for generating tags for makefiles. + */ +#include "third_party/ctags/general.h" +/* must always come first */ +#include "third_party/ctags/options.h" +#include "third_party/ctags/parse.h" +#include "third_party/ctags/read.h" +#include "third_party/ctags/vstring.h" + +/* + * DATA DEFINITIONS + */ +typedef enum { K_MACRO } shKind; + +static kindOption MakeKinds[] = {{TRUE, 'm', "macro", "macros"}}; + +/* + * FUNCTION DEFINITIONS + */ + +static int nextChar(void) { + int c = fileGetc(); + if (c == '\\') { + c = fileGetc(); + if (c == '\n') c = fileGetc(); + } + return c; +} + +static void skipLine(void) { + int c; + do + c = nextChar(); + while (c != EOF && c != '\n'); + if (c == '\n') fileUngetc(c); +} + +static int skipToNonWhite(void) { + int c; + do + c = nextChar(); + while (c != '\n' && isspace(c)); + return c; +} + +static boolean isIdentifier(int c) { + return (boolean)(c != '\0' && (isalnum(c) || strchr(".-_", c) != NULL)); +} + +static void readIdentifier(const int first, vString *const id) { + int c = first; + vStringClear(id); + while (isIdentifier(c)) { + vStringPut(id, c); + c = nextChar(); + } + fileUngetc(c); + vStringTerminate(id); +} + +static void skipToMatch(const char *const pair) { + const int begin = pair[0], end = pair[1]; + const unsigned long inputLineNumber = getInputLineNumber(); + int matchLevel = 1; + int c = '\0'; + + while (matchLevel > 0) { + c = nextChar(); + if (c == begin) + ++matchLevel; + else if (c == end) + --matchLevel; + else if (c == '\n' || c == EOF) + break; + } + if (c == EOF) + verbose("%s: failed to find match for '%c' at line %lu\n", + getInputFileName(), begin, inputLineNumber); +} + +static void findMakeTags(void) { + vString *name = vStringNew(); + boolean newline = TRUE; + boolean in_define = FALSE; + boolean in_rule = FALSE; + boolean variable_possible = TRUE; + int c; + + while ((c = nextChar()) != EOF) { + if (newline) { + if (in_rule) { + if (c == '\t') { + skipLine(); /* skip rule */ + continue; + } else + in_rule = FALSE; + } + variable_possible = (boolean)(!in_rule); + newline = FALSE; + } + if (c == '\n') + newline = TRUE; + else if (isspace(c)) + continue; + else if (c == '#') + skipLine(); + else if (c == '(') + skipToMatch("()"); + else if (c == '{') + skipToMatch("{}"); + else if (c == ':') { + variable_possible = TRUE; + in_rule = TRUE; + } else if (variable_possible && isIdentifier(c)) { + readIdentifier(c, name); + if (strcmp(vStringValue(name), "endef") == 0) + in_define = FALSE; + else if (in_define) + skipLine(); + else if (strcmp(vStringValue(name), "define") == 0 && isIdentifier(c)) { + in_define = TRUE; + c = skipToNonWhite(); + readIdentifier(c, name); + makeSimpleTag(name, MakeKinds, K_MACRO); + skipLine(); + } else { + if (strcmp(vStringValue(name), "export") == 0 && isIdentifier(c)) { + c = skipToNonWhite(); + readIdentifier(c, name); + } + c = skipToNonWhite(); + if (strchr(":?+", c) != NULL) { + boolean append = (boolean)(c == '+'); + if (c == ':') in_rule = TRUE; + c = nextChar(); + if (c != '=') + fileUngetc(c); + else if (append) { + skipLine(); + continue; + } + } + if (c == '=') { + makeSimpleTag(name, MakeKinds, K_MACRO); + in_rule = FALSE; + skipLine(); + } + } + } else + variable_possible = FALSE; + } + vStringDelete(name); +} + +extern parserDefinition *MakefileParser(void) { + static const char *const patterns[] = {"[Mm]akefile", "GNUmakefile", NULL}; + static const char *const extensions[] = {"mak", "mk", NULL}; + parserDefinition *const def = parserNew("Make"); + def->kinds = MakeKinds; + def->kindCount = KIND_COUNT(MakeKinds); + def->patterns = patterns; + def->extensions = extensions; + def->parser = findMakeTags; + return def; +} + +/* vi:set tabstop=4 shiftwidth=4: */ diff --git a/third_party/ctags/matlab.c b/third_party/ctags/matlab.c new file mode 100644 index 00000000..fb1da715 --- /dev/null +++ b/third_party/ctags/matlab.c @@ -0,0 +1,42 @@ +/* + * $Id$ + * + * Copyright (c) 2008, David Fishburn + * + * This source code is released for free distribution under the terms + * of the GNU General Public License. + * + * This module contains functions for generating tags for MATLAB + * language files. + */ +#include "third_party/ctags/general.h" +/* must always come first */ +#include "third_party/ctags/parse.h" + +/* + * FUNCTION DEFINITIONS + */ + +static void installMatLabRegex(const langType language) { + /* function [x,y,z] = asdf */ + addTagRegex(language, "^function[ \t]*\\[.*\\][ \t]*=[ \t]*([a-zA-Z0-9_]+)", + "\\1", "f,function", NULL); + /* function x = asdf */ + addTagRegex(language, + "^function[ \t]*[a-zA-Z0-9_]+[ \t]*=[ \t]*([a-zA-Z0-9_]+)", "\\1", + "f,function", NULL); + /* function asdf */ + addTagRegex(language, "^function[ \t]*([a-zA-Z0-9_]+)[^=]*$", "\\1", + "f,function", NULL); +} + +extern parserDefinition* MatLabParser() { + static const char* const extensions[] = {"m", NULL}; + parserDefinition* const def = parserNew("MatLab"); + def->extensions = extensions; + def->initialize = installMatLabRegex; + def->regex = TRUE; + return def; +} + +/* vi:set tabstop=4 shiftwidth=4: */ diff --git a/third_party/ctags/objc.c b/third_party/ctags/objc.c new file mode 100644 index 00000000..00dc9711 --- /dev/null +++ b/third_party/ctags/objc.c @@ -0,0 +1,1027 @@ +/* + * Copyright (c) 2010, Vincent Berthoux + * + * This source code is released for free distribution under the terms of the + * GNU General Public License. + * + * This module contains functions for generating tags for Objective C + * language files. + */ +#include "third_party/ctags/general.h" +/* must always come first */ +#include "third_party/ctags/entry.h" +#include "third_party/ctags/keyword.h" +#include "third_party/ctags/options.h" +#include "third_party/ctags/read.h" +#include "third_party/ctags/routines.h" +#include "third_party/ctags/vstring.h" + +/* To get rid of unused parameter warning in + * -Wextra */ +#ifdef UNUSED +#elif defined(__GNUC__) +#define UNUSED(x) UNUSED_##x __attribute__((unused)) +#elif defined(__LCLINT__) +#define UNUSED(x) /*@unused@*/ x +#else +#define UNUSED(x) x +#endif + +typedef enum { + K_INTERFACE, + K_IMPLEMENTATION, + K_PROTOCOL, + K_METHOD, + K_CLASSMETHOD, + K_VAR, + K_FIELD, + K_FUNCTION, + K_PROPERTY, + K_TYPEDEF, + K_STRUCT, + K_ENUM, + K_MACRO +} objcKind; + +static kindOption ObjcKinds[] = { + {TRUE, 'i', "interface", "class interface"}, + {TRUE, 'I', "implementation", "class implementation"}, + {TRUE, 'p', "protocol", "Protocol"}, + {TRUE, 'm', "method", "Object's method"}, + {TRUE, 'c', "class", "Class' method"}, + {TRUE, 'v', "var", "Global variable"}, + {TRUE, 'F', "field", "Object field"}, + {TRUE, 'f', "function", "A function"}, + {TRUE, 'p', "property", "A property"}, + {TRUE, 't', "typedef", "A type alias"}, + {TRUE, 's', "struct", "A type structure"}, + {TRUE, 'e', "enum", "An enumeration"}, + {TRUE, 'M', "macro", "A preprocessor macro"}, +}; + +typedef enum { + ObjcTYPEDEF, + ObjcSTRUCT, + ObjcENUM, + ObjcIMPLEMENTATION, + ObjcINTERFACE, + ObjcPROTOCOL, + ObjcENCODE, + ObjcSYNCHRONIZED, + ObjcSELECTOR, + ObjcPROPERTY, + ObjcEND, + ObjcDEFS, + ObjcCLASS, + ObjcPRIVATE, + ObjcPACKAGE, + ObjcPUBLIC, + ObjcPROTECTED, + ObjcSYNTHESIZE, + ObjcDYNAMIC, + ObjcOPTIONAL, + ObjcREQUIRED, + ObjcSTRING, + ObjcIDENTIFIER, + + Tok_COMA, /* ',' */ + Tok_PLUS, /* '+' */ + Tok_MINUS, /* '-' */ + Tok_PARL, /* '(' */ + Tok_PARR, /* ')' */ + Tok_CurlL, /* '{' */ + Tok_CurlR, /* '}' */ + Tok_SQUAREL, /* '[' */ + Tok_SQUARER, /* ']' */ + Tok_semi, /* ';' */ + Tok_dpoint, /* ':' */ + Tok_Sharp, /* '#' */ + Tok_Backslash, /* '\\' */ + Tok_EOL, /* '\r''\n' */ + Tok_any, + + Tok_EOF /* END of file */ +} objcKeyword; + +typedef objcKeyword objcToken; + +typedef struct sOBjcKeywordDesc { + const char *name; + objcKeyword id; +} objcKeywordDesc; + +static const objcKeywordDesc objcKeywordTable[] = { + {"typedef", ObjcTYPEDEF}, + {"struct", ObjcSTRUCT}, + {"enum", ObjcENUM}, + {"@implementation", ObjcIMPLEMENTATION}, + {"@interface", ObjcINTERFACE}, + {"@protocol", ObjcPROTOCOL}, + {"@encode", ObjcENCODE}, + {"@property", ObjcPROPERTY}, + {"@synchronized", ObjcSYNCHRONIZED}, + {"@selector", ObjcSELECTOR}, + {"@end", ObjcEND}, + {"@defs", ObjcDEFS}, + {"@class", ObjcCLASS}, + {"@private", ObjcPRIVATE}, + {"@package", ObjcPACKAGE}, + {"@public", ObjcPUBLIC}, + {"@protected", ObjcPROTECTED}, + {"@synthesize", ObjcSYNTHESIZE}, + {"@dynamic", ObjcDYNAMIC}, + {"@optional", ObjcOPTIONAL}, + {"@required", ObjcREQUIRED}, +}; + +static langType Lang_ObjectiveC; + +/*////////////////////////////////////////////////////////////////// +//// lexingInit */ +typedef struct _lexingState { + vString *name; /* current parsed identifier/operator */ + const unsigned char *cp; /* position in stream */ +} lexingState; + +static void initKeywordHash(void) { + const size_t count = sizeof(objcKeywordTable) / sizeof(objcKeywordDesc); + size_t i; + + for (i = 0; i < count; ++i) { + addKeyword(objcKeywordTable[i].name, Lang_ObjectiveC, + (int)objcKeywordTable[i].id); + } +} + +/*////////////////////////////////////////////////////////////////////// +//// Lexing */ +static boolean isNum(char c) { + return c >= '0' && c <= '9'; +} + +static boolean isLowerAlpha(char c) { + return c >= 'a' && c <= 'z'; +} + +static boolean isUpperAlpha(char c) { + return c >= 'A' && c <= 'Z'; +} + +static boolean isAlpha(char c) { + return isLowerAlpha(c) || isUpperAlpha(c); +} + +static boolean isIdent(char c) { + return isNum(c) || isAlpha(c) || c == '_'; +} + +static boolean isSpace(char c) { + return c == ' ' || c == '\t'; +} + +/* return true if it end with an end of line */ +static void eatWhiteSpace(lexingState *st) { + const unsigned char *cp = st->cp; + while (isSpace(*cp)) cp++; + + st->cp = cp; +} + +static void eatString(lexingState *st) { + boolean lastIsBackSlash = FALSE; + boolean unfinished = TRUE; + const unsigned char *c = st->cp + 1; + + while (unfinished) { + /* end of line should never happen. + * we tolerate it */ + if (c == NULL || c[0] == '\0') + break; + else if (*c == '"' && !lastIsBackSlash) + unfinished = FALSE; + else + lastIsBackSlash = *c == '\\'; + + c++; + } + + st->cp = c; +} + +static void eatComment(lexingState *st) { + boolean unfinished = TRUE; + boolean lastIsStar = FALSE; + const unsigned char *c = st->cp + 2; + + while (unfinished) { + /* we've reached the end of the line.. + * so we have to reload a line... */ + if (c == NULL || *c == '\0') { + st->cp = fileReadLine(); + /* WOOPS... no more input... + * we return, next lexing read + * will be null and ok */ + if (st->cp == NULL) return; + c = st->cp; + } + /* we've reached the end of the comment */ + else if (*c == '/' && lastIsStar) + unfinished = FALSE; + else { + lastIsStar = '*' == *c; + c++; + } + } + + st->cp = c; +} + +static void readIdentifier(lexingState *st) { + const unsigned char *p; + vStringClear(st->name); + + /* first char is a simple letter */ + if (isAlpha(*st->cp) || *st->cp == '_') vStringPut(st->name, (int)*st->cp); + + /* Go till you get identifier chars */ + for (p = st->cp + 1; isIdent(*p); p++) vStringPut(st->name, (int)*p); + + st->cp = p; + + vStringTerminate(st->name); +} + +/* read the @something directives */ +static void readIdentifierObjcDirective(lexingState *st) { + const unsigned char *p; + vStringClear(st->name); + + /* first char is a simple letter */ + if (*st->cp == '@') vStringPut(st->name, (int)*st->cp); + + /* Go till you get identifier chars */ + for (p = st->cp + 1; isIdent(*p); p++) vStringPut(st->name, (int)*p); + + st->cp = p; + + vStringTerminate(st->name); +} + +/* The lexer is in charge of reading the file. + * Some of sub-lexer (like eatComment) also read file. + * lexing is finished when the lexer return Tok_EOF */ +static objcKeyword lex(lexingState *st) { + int retType; + + /* handling data input here */ + while (st->cp == NULL || st->cp[0] == '\0') { + st->cp = fileReadLine(); + if (st->cp == NULL) return Tok_EOF; + + return Tok_EOL; + } + + if (isAlpha(*st->cp)) { + readIdentifier(st); + retType = lookupKeyword(vStringValue(st->name), Lang_ObjectiveC); + + if (retType == -1) /* If it's not a keyword */ + { + return ObjcIDENTIFIER; + } else { + return retType; + } + } else if (*st->cp == '@') { + readIdentifierObjcDirective(st); + retType = lookupKeyword(vStringValue(st->name), Lang_ObjectiveC); + + if (retType == -1) /* If it's not a keyword */ + { + return Tok_any; + } else { + return retType; + } + } else if (isSpace(*st->cp)) { + eatWhiteSpace(st); + return lex(st); + } else + switch (*st->cp) { + case '(': + st->cp++; + return Tok_PARL; + + case '\\': + st->cp++; + return Tok_Backslash; + + case '#': + st->cp++; + return Tok_Sharp; + + case '/': + if (st->cp[1] == '*') /* ergl, a comment */ + { + eatComment(st); + return lex(st); + } else if (st->cp[1] == '/') { + st->cp = NULL; + return lex(st); + } else { + st->cp++; + return Tok_any; + } + break; + + case ')': + st->cp++; + return Tok_PARR; + case '{': + st->cp++; + return Tok_CurlL; + case '}': + st->cp++; + return Tok_CurlR; + case '[': + st->cp++; + return Tok_SQUAREL; + case ']': + st->cp++; + return Tok_SQUARER; + case ',': + st->cp++; + return Tok_COMA; + case ';': + st->cp++; + return Tok_semi; + case ':': + st->cp++; + return Tok_dpoint; + case '"': + eatString(st); + return Tok_any; + case '+': + st->cp++; + return Tok_PLUS; + case '-': + st->cp++; + return Tok_MINUS; + + default: + st->cp++; + break; + } + + /* default return if nothing is recognized, + * shouldn't happen, but at least, it will + * be handled without destroying the parsing. */ + return Tok_any; +} + +/*////////////////////////////////////////////////////////////////////// +//// Parsing */ +typedef void (*parseNext)(vString *const ident, objcToken what); + +/********** Helpers */ +/* This variable hold the 'parser' which is going to + * handle the next token */ +static parseNext toDoNext; + +/* Special variable used by parser eater to + * determine which action to put after their + * job is finished. */ +static parseNext comeAfter; + +/* Used by some parsers detecting certain token + * to revert to previous parser. */ +static parseNext fallback; + +/********** Grammar */ +static void globalScope(vString *const ident, objcToken what); +static void parseMethods(vString *const ident, objcToken what); +static void parseImplemMethods(vString *const ident, objcToken what); +static vString *tempName = NULL; +static vString *parentName = NULL; +static objcKind parentType = K_INTERFACE; + +/* used to prepare tag for OCaml, just in case their is a need to + * add additional information to the tag. */ +static void prepareTag(tagEntryInfo *tag, vString const *name, objcKind kind) { + initTagEntry(tag, vStringValue(name)); + tag->kindName = ObjcKinds[kind].name; + tag->kind = ObjcKinds[kind].letter; + + if (parentName != NULL) { + tag->extensionFields.scope[0] = ObjcKinds[parentType].name; + tag->extensionFields.scope[1] = vStringValue(parentName); + } +} + +void pushEnclosingContext(const vString *parent, objcKind type) { + vStringCopy(parentName, parent); + parentType = type; +} + +void popEnclosingContext() { + vStringClear(parentName); +} + +/* Used to centralise tag creation, and be able to add + * more information to it in the future */ +static void addTag(vString *const ident, int kind) { + tagEntryInfo toCreate; + prepareTag(&toCreate, ident, kind); + makeTagEntry(&toCreate); +} + +static objcToken waitedToken, fallBackToken; + +/* Ignore everything till waitedToken and jump to comeAfter. + * If the "end" keyword is encountered break, doesn't remember + * why though. */ +static void tillToken(vString *const UNUSED(ident), objcToken what) { + if (what == waitedToken) toDoNext = comeAfter; +} + +static void tillTokenOrFallBack(vString *const UNUSED(ident), objcToken what) { + if (what == waitedToken) + toDoNext = comeAfter; + else if (what == fallBackToken) { + toDoNext = fallback; + } +} + +static void ignoreBalanced(vString *const UNUSED(ident), objcToken what) { + static int count = 0; + + switch (what) { + case Tok_PARL: + case Tok_CurlL: + case Tok_SQUAREL: + count++; + break; + + case Tok_PARR: + case Tok_CurlR: + case Tok_SQUARER: + count--; + break; + + default: + /* don't care */ + break; + } + + if (count == 0) toDoNext = comeAfter; +} + +static void parseFields(vString *const ident, objcToken what) { + switch (what) { + case Tok_CurlR: + toDoNext = &parseMethods; + break; + + case Tok_SQUAREL: + case Tok_PARL: + toDoNext = &ignoreBalanced; + comeAfter = &parseFields; + break; + + // we got an identifier, keep track + // of it + case ObjcIDENTIFIER: + vStringCopy(tempName, ident); + break; + + // our last kept identifier must be our + // variable name =) + case Tok_semi: + addTag(tempName, K_FIELD); + vStringClear(tempName); + break; + + default: + /* NOTHING */ + break; + } +} + +objcKind methodKind; + +static vString *fullMethodName; +static vString *prevIdent; + +static void parseMethodsName(vString *const ident, objcToken what) { + switch (what) { + case Tok_PARL: + toDoNext = &tillToken; + comeAfter = &parseMethodsName; + waitedToken = Tok_PARR; + break; + + case Tok_dpoint: + vStringCat(fullMethodName, prevIdent); + vStringCatS(fullMethodName, ":"); + vStringClear(prevIdent); + break; + + case ObjcIDENTIFIER: + vStringCopy(prevIdent, ident); + break; + + case Tok_CurlL: + case Tok_semi: + // method name is not simple + if (vStringLength(fullMethodName) != '\0') { + addTag(fullMethodName, methodKind); + vStringClear(fullMethodName); + } else + addTag(prevIdent, methodKind); + + toDoNext = &parseMethods; + parseImplemMethods(ident, what); + vStringClear(prevIdent); + break; + + default: + break; + } +} + +static void parseMethodsImplemName(vString *const ident, objcToken what) { + switch (what) { + case Tok_PARL: + toDoNext = &tillToken; + comeAfter = &parseMethodsImplemName; + waitedToken = Tok_PARR; + break; + + case Tok_dpoint: + vStringCat(fullMethodName, prevIdent); + vStringCatS(fullMethodName, ":"); + vStringClear(prevIdent); + break; + + case ObjcIDENTIFIER: + vStringCopy(prevIdent, ident); + break; + + case Tok_CurlL: + case Tok_semi: + // method name is not simple + if (vStringLength(fullMethodName) != '\0') { + addTag(fullMethodName, methodKind); + vStringClear(fullMethodName); + } else + addTag(prevIdent, methodKind); + + toDoNext = &parseImplemMethods; + parseImplemMethods(ident, what); + vStringClear(prevIdent); + break; + + default: + break; + } +} + +static void parseImplemMethods(vString *const ident, objcToken what) { + switch (what) { + case Tok_PLUS: /* + */ + toDoNext = &parseMethodsImplemName; + methodKind = K_CLASSMETHOD; + break; + + case Tok_MINUS: /* - */ + toDoNext = &parseMethodsImplemName; + methodKind = K_METHOD; + break; + + case ObjcEND: /* @end */ + popEnclosingContext(); + toDoNext = &globalScope; + break; + + case Tok_CurlL: /* { */ + toDoNext = &ignoreBalanced; + ignoreBalanced(ident, what); + comeAfter = &parseImplemMethods; + break; + + default: + break; + } +} + +static void parseProperty(vString *const ident, objcToken what) { + switch (what) { + case Tok_PARL: + toDoNext = &tillToken; + comeAfter = &parseProperty; + waitedToken = Tok_PARR; + break; + + // we got an identifier, keep track + // of it + case ObjcIDENTIFIER: + vStringCopy(tempName, ident); + break; + + // our last kept identifier must be our + // variable name =) + case Tok_semi: + addTag(tempName, K_PROPERTY); + vStringClear(tempName); + break; + + default: + break; + } +} + +static void parseMethods(vString *const UNUSED(ident), objcToken what) { + switch (what) { + case Tok_PLUS: /* + */ + toDoNext = &parseMethodsName; + methodKind = K_CLASSMETHOD; + break; + + case Tok_MINUS: /* - */ + toDoNext = &parseMethodsName; + methodKind = K_METHOD; + break; + + case ObjcPROPERTY: + toDoNext = &parseProperty; + break; + + case ObjcEND: /* @end */ + popEnclosingContext(); + toDoNext = &globalScope; + break; + + case Tok_CurlL: /* { */ + toDoNext = &parseFields; + break; + + default: + break; + } +} + +static void parseProtocol(vString *const ident, objcToken what) { + if (what == ObjcIDENTIFIER) { + pushEnclosingContext(ident, K_PROTOCOL); + addTag(ident, K_PROTOCOL); + } + toDoNext = &parseMethods; +} + +static void parseImplementation(vString *const ident, objcToken what) { + if (what == ObjcIDENTIFIER) { + addTag(ident, K_IMPLEMENTATION); + pushEnclosingContext(ident, K_IMPLEMENTATION); + } + toDoNext = &parseImplemMethods; +} + +static void parseInterface(vString *const ident, objcToken what) { + if (what == ObjcIDENTIFIER) { + addTag(ident, K_INTERFACE); + pushEnclosingContext(ident, K_INTERFACE); + } + + toDoNext = &parseMethods; +} + +static void parseStructMembers(vString *const ident, objcToken what) { + static parseNext prev = NULL; + + if (prev != NULL) { + comeAfter = prev; + prev = NULL; + } + + switch (what) { + case ObjcIDENTIFIER: + vStringCopy(tempName, ident); + break; + + case Tok_semi: /* ';' */ + addTag(tempName, K_FIELD); + vStringClear(tempName); + break; + + // some types are complex, the only one + // we will loose is the function type. + case Tok_CurlL: /* '{' */ + case Tok_PARL: /* '(' */ + case Tok_SQUAREL: /* '[' */ + toDoNext = &ignoreBalanced; + prev = comeAfter; + comeAfter = &parseStructMembers; + ignoreBalanced(ident, what); + break; + + case Tok_CurlR: + toDoNext = comeAfter; + break; + + default: + /* don't care */ + break; + } +} + +/* Called just after the struct keyword */ +static void parseStruct(vString *const ident, objcToken what) { + static boolean gotName = FALSE; + + switch (what) { + case ObjcIDENTIFIER: + if (!gotName) { + addTag(ident, K_STRUCT); + pushEnclosingContext(ident, K_STRUCT); + gotName = TRUE; + } else { + gotName = FALSE; + popEnclosingContext(); + toDoNext = comeAfter; + comeAfter(ident, what); + } + break; + + case Tok_CurlL: + toDoNext = &parseStructMembers; + break; + + /* maybe it was just a forward declaration + * in which case, we pop the context */ + case Tok_semi: + if (gotName) popEnclosingContext(); + + toDoNext = comeAfter; + comeAfter(ident, what); + break; + + default: + /* we don't care */ + break; + } +} + +/* Parse enumeration members, ignoring potential initialization */ +static void parseEnumFields(vString *const ident, objcToken what) { + static parseNext prev = NULL; + + if (prev != NULL) { + comeAfter = prev; + prev = NULL; + } + + switch (what) { + case ObjcIDENTIFIER: + addTag(ident, K_ENUM); + prev = comeAfter; + waitedToken = Tok_COMA; + /* last item might not have a coma */ + fallBackToken = Tok_CurlR; + fallback = comeAfter; + comeAfter = parseEnumFields; + toDoNext = &tillTokenOrFallBack; + break; + + case Tok_CurlR: + toDoNext = comeAfter; + popEnclosingContext(); + break; + + default: + /* don't care */ + break; + } +} + +/* parse enum ... { ... */ +static void parseEnum(vString *const ident, objcToken what) { + static boolean named = FALSE; + + switch (what) { + case ObjcIDENTIFIER: + if (!named) { + addTag(ident, K_ENUM); + pushEnclosingContext(ident, K_ENUM); + named = TRUE; + } else { + named = FALSE; + popEnclosingContext(); + toDoNext = comeAfter; + comeAfter(ident, what); + } + break; + + case Tok_CurlL: /* '{' */ + toDoNext = &parseEnumFields; + named = FALSE; + break; + + case Tok_semi: /* ';' */ + if (named) popEnclosingContext(); + toDoNext = comeAfter; + comeAfter(ident, what); + break; + + default: + /* don't care */ + break; + } +} + +/* Parse something like + * typedef .... ident ; + * ignoring the defined type but in the case of struct, + * in which case struct are parsed. + */ +static void parseTypedef(vString *const ident, objcToken what) { + switch (what) { + case ObjcSTRUCT: + toDoNext = &parseStruct; + comeAfter = &parseTypedef; + break; + + case ObjcENUM: + toDoNext = &parseEnum; + comeAfter = &parseTypedef; + break; + + case ObjcIDENTIFIER: + vStringCopy(tempName, ident); + break; + + case Tok_semi: /* ';' */ + addTag(tempName, K_TYPEDEF); + vStringClear(tempName); + toDoNext = &globalScope; + break; + + default: + /* we don't care */ + break; + } +} + +static void ignorePreprocStuff(vString *const UNUSED(ident), objcToken what) { + static boolean escaped = FALSE; + + switch (what) { + case Tok_Backslash: + escaped = TRUE; + break; + + case Tok_EOL: + if (escaped) { + escaped = FALSE; + } else { + toDoNext = &globalScope; + } + break; + + default: + escaped = FALSE; + break; + } +} + +static void parseMacroName(vString *const ident, objcToken what) { + if (what == ObjcIDENTIFIER) addTag(ident, K_MACRO); + + toDoNext = &ignorePreprocStuff; +} + +static void parsePreproc(vString *const ident, objcToken what) { + switch (what) { + case ObjcIDENTIFIER: + if (strcmp(vStringValue(ident), "define") == 0) + toDoNext = &parseMacroName; + else + toDoNext = &ignorePreprocStuff; + break; + + default: + toDoNext = &ignorePreprocStuff; + break; + } +} + +/* Handle the "strong" top levels, all 'big' declarations + * happen here */ +static void globalScope(vString *const ident, objcToken what) { + switch (what) { + case Tok_Sharp: + toDoNext = &parsePreproc; + break; + + case ObjcSTRUCT: + toDoNext = &parseStruct; + comeAfter = &globalScope; + break; + + case ObjcIDENTIFIER: + /* we keep track of the identifier if we + * come across a function. */ + vStringCopy(tempName, ident); + break; + + case Tok_PARL: + /* if we find an opening parenthesis it means we + * found a function (or a macro...) */ + addTag(tempName, K_FUNCTION); + vStringClear(tempName); + comeAfter = &globalScope; + toDoNext = &ignoreBalanced; + ignoreBalanced(ident, what); + break; + + case ObjcINTERFACE: + toDoNext = &parseInterface; + break; + + case ObjcIMPLEMENTATION: + toDoNext = &parseImplementation; + break; + + case ObjcPROTOCOL: + toDoNext = &parseProtocol; + break; + + case ObjcTYPEDEF: + toDoNext = parseTypedef; + comeAfter = &globalScope; + break; + + case Tok_CurlL: + comeAfter = &globalScope; + toDoNext = &ignoreBalanced; + ignoreBalanced(ident, what); + break; + + case ObjcEND: + case ObjcPUBLIC: + case ObjcPROTECTED: + case ObjcPRIVATE: + + default: + /* we don't care */ + break; + } +} + +/*//////////////////////////////////////////////////////////////// +//// Deal with the system */ + +static void findObjcTags(void) { + vString *name = vStringNew(); + lexingState st; + objcToken tok; + + parentName = vStringNew(); + tempName = vStringNew(); + fullMethodName = vStringNew(); + prevIdent = vStringNew(); + + st.name = vStringNew(); + st.cp = fileReadLine(); + toDoNext = &globalScope; + tok = lex(&st); + while (tok != Tok_EOF) { + (*toDoNext)(st.name, tok); + tok = lex(&st); + } + + vStringDelete(name); + vStringDelete(parentName); + vStringDelete(tempName); + vStringDelete(fullMethodName); + vStringDelete(prevIdent); + parentName = NULL; + tempName = NULL; + prevIdent = NULL; + fullMethodName = NULL; +} + +static void objcInitialize(const langType language) { + Lang_ObjectiveC = language; + + initKeywordHash(); +} + +extern parserDefinition *ObjcParser(void) { + static const char *const extensions[] = {"m", "h", NULL}; + parserDefinition *def = parserNew("ObjectiveC"); + def->kinds = ObjcKinds; + def->kindCount = KIND_COUNT(ObjcKinds); + def->extensions = extensions; + def->parser = findObjcTags; + def->initialize = objcInitialize; + + return def; +} diff --git a/third_party/ctags/ocaml.c b/third_party/ctags/ocaml.c new file mode 100644 index 00000000..d08d7106 --- /dev/null +++ b/third_party/ctags/ocaml.c @@ -0,0 +1,1713 @@ +/* + * Copyright (c) 2009, Vincent Berthoux + * + * This source code is released for free distribution under the terms of the + * GNU General Public License. + * + * This module contains functions for generating tags for Objective Caml + * language files. + */ +#include "third_party/ctags/general.h" +/* must always come first */ +#include "third_party/ctags/entry.h" +#include "third_party/ctags/keyword.h" +#include "third_party/ctags/options.h" +#include "third_party/ctags/read.h" +#include "third_party/ctags/routines.h" +#include "third_party/ctags/vstring.h" + +/* To get rid of unused parameter warning in + * -Wextra */ +#ifdef UNUSED +#elif defined(__GNUC__) +#define UNUSED(x) UNUSED_##x __attribute__((unused)) +#elif defined(__LCLINT__) +#define UNUSED(x) /*@unused@*/ x +#else +#define UNUSED(x) x +#endif +#define OCAML_MAX_STACK_SIZE 256 + +typedef enum { + K_CLASS, /* Ocaml class, relatively rare */ + K_METHOD, /* class method */ + K_MODULE, /* Ocaml module OR functor */ + K_VAR, + K_TYPE, /* name of an OCaml type */ + K_FUNCTION, + K_CONSTRUCTOR, /* Constructor of a sum type */ + K_RECORDFIELD, + K_EXCEPTION +} ocamlKind; + +static kindOption OcamlKinds[] = { + {TRUE, 'c', "class", "classes"}, + {TRUE, 'm', "method", "Object's method"}, + {TRUE, 'M', "module", "Module or functor"}, + {TRUE, 'v', "var", "Global variable"}, + {TRUE, 't', "type", "Type name"}, + {TRUE, 'f', "function", "A function"}, + {TRUE, 'C', "Constructor", "A constructor"}, + {TRUE, 'r', "Record field", "A 'structure' field"}, + {TRUE, 'e', "Exception", "An exception"}}; + +typedef enum { + OcaKEYWORD_and, + OcaKEYWORD_begin, + OcaKEYWORD_class, + OcaKEYWORD_do, + OcaKEYWORD_done, + OcaKEYWORD_else, + OcaKEYWORD_end, + OcaKEYWORD_exception, + OcaKEYWORD_for, + OcaKEYWORD_functor, + OcaKEYWORD_fun, + OcaKEYWORD_if, + OcaKEYWORD_in, + OcaKEYWORD_let, + OcaKEYWORD_value, + OcaKEYWORD_match, + OcaKEYWORD_method, + OcaKEYWORD_module, + OcaKEYWORD_mutable, + OcaKEYWORD_object, + OcaKEYWORD_of, + OcaKEYWORD_rec, + OcaKEYWORD_sig, + OcaKEYWORD_struct, + OcaKEYWORD_then, + OcaKEYWORD_try, + OcaKEYWORD_type, + OcaKEYWORD_val, + OcaKEYWORD_virtual, + OcaKEYWORD_while, + OcaKEYWORD_with, + + OcaIDENTIFIER, + Tok_PARL, /* '(' */ + Tok_PARR, /* ')' */ + Tok_BRL, /* '[' */ + Tok_BRR, /* ']' */ + Tok_CurlL, /* '{' */ + Tok_CurlR, /* '}' */ + Tok_Prime, /* '\'' */ + Tok_Pipe, /* '|' */ + Tok_EQ, /* '=' */ + Tok_Val, /* string/number/poo */ + Tok_Op, /* any operator recognized by the language */ + Tok_semi, /* ';' */ + Tok_comma, /* ',' */ + Tok_To, /* '->' */ + Tok_Sharp, /* '#' */ + Tok_Backslash, /* '\\' */ + + Tok_EOF /* END of file */ +} ocamlKeyword; + +typedef struct sOcaKeywordDesc { + const char *name; + ocamlKeyword id; +} ocaKeywordDesc; + +typedef ocamlKeyword ocaToken; + +static const ocaKeywordDesc OcamlKeywordTable[] = { + {"and", OcaKEYWORD_and}, + {"begin", OcaKEYWORD_begin}, + {"class", OcaKEYWORD_class}, + {"do", OcaKEYWORD_do}, + {"done", OcaKEYWORD_done}, + {"else", OcaKEYWORD_else}, + {"end", OcaKEYWORD_end}, + {"exception", OcaKEYWORD_exception}, + {"for", OcaKEYWORD_for}, + {"fun", OcaKEYWORD_fun}, + {"function", OcaKEYWORD_fun}, + {"functor", OcaKEYWORD_functor}, + {"in", OcaKEYWORD_in}, + {"let", OcaKEYWORD_let}, + {"match", OcaKEYWORD_match}, + {"method", OcaKEYWORD_method}, + {"module", OcaKEYWORD_module}, + {"mutable", OcaKEYWORD_mutable}, + {"object", OcaKEYWORD_object}, + {"of", OcaKEYWORD_of}, + {"rec", OcaKEYWORD_rec}, + {"sig", OcaKEYWORD_sig}, + {"struct", OcaKEYWORD_struct}, + {"then", OcaKEYWORD_then}, + {"try", OcaKEYWORD_try}, + {"type", OcaKEYWORD_type}, + {"val", OcaKEYWORD_val}, + {"value", OcaKEYWORD_value}, /* just to handle revised syntax */ + {"virtual", OcaKEYWORD_virtual}, + {"while", OcaKEYWORD_while}, + {"with", OcaKEYWORD_with}, + + {"or", Tok_Op}, + {"mod ", Tok_Op}, + {"land ", Tok_Op}, + {"lor ", Tok_Op}, + {"lxor ", Tok_Op}, + {"lsl ", Tok_Op}, + {"lsr ", Tok_Op}, + {"asr", Tok_Op}, + {"->", Tok_To}, + {"true", Tok_Val}, + {"false", Tok_Val}}; + +static langType Lang_Ocaml; + +boolean exportLocalInfo = FALSE; + +/*////////////////////////////////////////////////////////////////// +//// lexingInit */ +typedef struct _lexingState { + vString *name; /* current parsed identifier/operator */ + const unsigned char *cp; /* position in stream */ +} lexingState; + +/* array of the size of all possible value for a char */ +boolean isOperator[1 << (8 * sizeof(char))] = {FALSE}; + +static void initKeywordHash(void) { + const size_t count = sizeof(OcamlKeywordTable) / sizeof(ocaKeywordDesc); + size_t i; + + for (i = 0; i < count; ++i) { + addKeyword(OcamlKeywordTable[i].name, Lang_Ocaml, + (int)OcamlKeywordTable[i].id); + } +} + +/* definition of all the operator in OCaml, + * /!\ certain operator get special treatment + * in regards of their role in OCaml grammar : + * '|' ':' '=' '~' and '?' */ +static void initOperatorTable(void) { + isOperator['!'] = TRUE; + isOperator['$'] = TRUE; + isOperator['%'] = TRUE; + isOperator['&'] = TRUE; + isOperator['*'] = TRUE; + isOperator['+'] = TRUE; + isOperator['-'] = TRUE; + isOperator['.'] = TRUE; + isOperator['/'] = TRUE; + isOperator[':'] = TRUE; + isOperator['<'] = TRUE; + isOperator['='] = TRUE; + isOperator['>'] = TRUE; + isOperator['?'] = TRUE; + isOperator['@'] = TRUE; + isOperator['^'] = TRUE; + isOperator['~'] = TRUE; + isOperator['|'] = TRUE; +} + +/*////////////////////////////////////////////////////////////////////// +//// Lexing */ +static boolean isNum(char c) { + return c >= '0' && c <= '9'; +} +static boolean isLowerAlpha(char c) { + return c >= 'a' && c <= 'z'; +} + +static boolean isUpperAlpha(char c) { + return c >= 'A' && c <= 'Z'; +} + +static boolean isAlpha(char c) { + return isLowerAlpha(c) || isUpperAlpha(c); +} + +static boolean isIdent(char c) { + return isNum(c) || isAlpha(c) || c == '_' || c == '\''; +} + +static boolean isSpace(char c) { + return c == ' ' || c == '\t' || c == '\r' || c == '\n'; +} + +static void eatWhiteSpace(lexingState *st) { + const unsigned char *cp = st->cp; + while (isSpace(*cp)) cp++; + + st->cp = cp; +} + +static void eatString(lexingState *st) { + boolean lastIsBackSlash = FALSE; + boolean unfinished = TRUE; + const unsigned char *c = st->cp + 1; + + while (unfinished) { + /* end of line should never happen. + * we tolerate it */ + if (c == NULL || c[0] == '\0') + break; + else if (*c == '"' && !lastIsBackSlash) + unfinished = FALSE; + else + lastIsBackSlash = *c == '\\'; + + c++; + } + + st->cp = c; +} + +static void eatComment(lexingState *st) { + boolean unfinished = TRUE; + boolean lastIsStar = FALSE; + const unsigned char *c = st->cp + 2; + + while (unfinished) { + /* we've reached the end of the line.. + * so we have to reload a line... */ + if (c == NULL || *c == '\0') { + st->cp = fileReadLine(); + /* WOOPS... no more input... + * we return, next lexing read + * will be null and ok */ + if (st->cp == NULL) return; + c = st->cp; + } + /* we've reached the end of the comment */ + else if (*c == ')' && lastIsStar) + unfinished = FALSE; + /* here we deal with imbricated comment, which + * are allowed in OCaml */ + else if (c[0] == '(' && c[1] == '*') { + st->cp = c; + eatComment(st); + + c = st->cp; + if (c == NULL) return; + + lastIsStar = FALSE; + c++; + } + /* OCaml has a rule which says : + * + * "Comments do not occur inside string or character literals. + * Nested comments are handled correctly." + * + * So if we encounter a string beginning, we must parse it to + * get a good comment nesting (bug ID: 3117537) + */ + else if (*c == '"') { + st->cp = c; + eatString(st); + c = st->cp; + } else { + lastIsStar = '*' == *c; + c++; + } + } + + st->cp = c; +} + +static void readIdentifier(lexingState *st) { + const unsigned char *p; + vStringClear(st->name); + + /* first char is a simple letter */ + if (isAlpha(*st->cp) || *st->cp == '_') vStringPut(st->name, (int)*st->cp); + + /* Go till you get identifier chars */ + for (p = st->cp + 1; isIdent(*p); p++) vStringPut(st->name, (int)*p); + + st->cp = p; + + vStringTerminate(st->name); +} + +static ocamlKeyword eatNumber(lexingState *st) { + while (isNum(*st->cp)) st->cp++; + return Tok_Val; +} + +/* Operator can be defined in OCaml as a function + * so we must be ample enough to parse them normally */ +static ocamlKeyword eatOperator(lexingState *st) { + int count = 0; + const unsigned char *root = st->cp; + + vStringClear(st->name); + + while (isOperator[st->cp[count]]) { + vStringPut(st->name, st->cp[count]); + count++; + } + + vStringTerminate(st->name); + + st->cp += count; + if (count <= 1) { + switch (root[0]) { + case '|': + return Tok_Pipe; + case '=': + return Tok_EQ; + default: + return Tok_Op; + } + } else if (count == 2 && root[0] == '-' && root[1] == '>') + return Tok_To; + else + return Tok_Op; +} + +/* The lexer is in charge of reading the file. + * Some of sub-lexer (like eatComment) also read file. + * lexing is finished when the lexer return Tok_EOF */ +static ocamlKeyword lex(lexingState *st) { + int retType; + /* handling data input here */ + while (st->cp == NULL || st->cp[0] == '\0') { + st->cp = fileReadLine(); + if (st->cp == NULL) return Tok_EOF; + } + + if (isAlpha(*st->cp)) { + readIdentifier(st); + retType = lookupKeyword(vStringValue(st->name), Lang_Ocaml); + + if (retType == -1) /* If it's not a keyword */ + { + return OcaIDENTIFIER; + } else { + return retType; + } + } else if (isNum(*st->cp)) + return eatNumber(st); + else if (isSpace(*st->cp)) { + eatWhiteSpace(st); + return lex(st); + } + /* OCaml permit the definition of our own operators + * so here we check all the consecuting chars which + * are operators to discard them. */ + else if (isOperator[*st->cp]) + return eatOperator(st); + else + switch (*st->cp) { + case '(': + if (st->cp[1] == '*') /* ergl, a comment */ + { + eatComment(st); + return lex(st); + } else { + st->cp++; + return Tok_PARL; + } + + case ')': + st->cp++; + return Tok_PARR; + case '[': + st->cp++; + return Tok_BRL; + case ']': + st->cp++; + return Tok_BRR; + case '{': + st->cp++; + return Tok_CurlL; + case '}': + st->cp++; + return Tok_CurlR; + case '\'': + st->cp++; + return Tok_Prime; + case ',': + st->cp++; + return Tok_comma; + case '=': + st->cp++; + return Tok_EQ; + case ';': + st->cp++; + return Tok_semi; + case '"': + eatString(st); + return Tok_Val; + case '_': + st->cp++; + return Tok_Val; + case '#': + st->cp++; + return Tok_Sharp; + case '\\': + st->cp++; + return Tok_Backslash; + + default: + st->cp++; + break; + } + + /* default return if nothing is recognized, + * shouldn't happen, but at least, it will + * be handled without destroying the parsing. */ + return Tok_Val; +} + +/*////////////////////////////////////////////////////////////////////// +//// Parsing */ +typedef void (*parseNext)(vString *const ident, ocaToken what); + +/********** Helpers */ +/* This variable hold the 'parser' which is going to + * handle the next token */ +static parseNext toDoNext; + +/* Special variable used by parser eater to + * determine which action to put after their + * job is finished. */ +static parseNext comeAfter; + +/* If a token put an end to current delcaration/ + * statement */ +static ocaToken terminatingToken; + +/* Token to be searched by the different + * parser eater. */ +static ocaToken waitedToken; + +/* name of the last class, used for + * context stacking. */ +vString *lastClass; + +vString *voidName; + +typedef enum _sContextKind { ContextStrong, ContextSoft } contextKind; + +typedef enum _sContextType { + ContextType, + ContextModule, + ContextClass, + ContextValue, + ContextFunction, + ContextMethod, + ContextBlock +} contextType; + +typedef struct _sOcamlContext { + contextKind kind; /* well if the context is strong or not */ + contextType type; + parseNext callback; /* what to do when a context is pop'd */ + vString *contextName; /* name, if any, of the surrounding context */ +} ocamlContext; + +/* context stack, can be used to output scope information + * into the tag file. */ +ocamlContext stack[OCAML_MAX_STACK_SIZE]; +/* current position in the tag */ +int stackIndex; + +/* special function, often recalled, so putting it here */ +static void globalScope(vString *const ident, ocaToken what); + +/* Return : index of the last named context if one + * is found, -1 otherwise */ +static int getLastNamedIndex(void) { + int i; + + for (i = stackIndex - 1; i >= 0; --i) { + if (vStringLength(stack[i].contextName) > 0) { + return i; + } + } + + return -1; +} + +static const char *contextDescription(contextType t) { + switch (t) { + case ContextFunction: + return "function"; + case ContextMethod: + return "method"; + case ContextValue: + return "value"; + case ContextModule: + return "Module"; + case ContextType: + return "type"; + case ContextClass: + return "class"; + case ContextBlock: + return "begin/end"; + } + + return NULL; +} + +static char contextTypeSuffix(contextType t) { + switch (t) { + case ContextFunction: + case ContextMethod: + case ContextValue: + case ContextModule: + return '/'; + case ContextType: + return '.'; + case ContextClass: + return '#'; + case ContextBlock: + return ' '; + } + + return '$'; +} + +/* Push a new context, handle null string */ +static void pushContext(contextKind kind, contextType type, parseNext after, + vString const *contextName) { + int parentIndex; + + if (stackIndex >= OCAML_MAX_STACK_SIZE) { + verbose("OCaml Maximum depth reached"); + return; + } + + stack[stackIndex].kind = kind; + stack[stackIndex].type = type; + stack[stackIndex].callback = after; + + parentIndex = getLastNamedIndex(); + if (contextName == NULL) { + vStringClear(stack[stackIndex++].contextName); + return; + } + + if (parentIndex >= 0) { + vStringCopy(stack[stackIndex].contextName, stack[parentIndex].contextName); + vStringPut(stack[stackIndex].contextName, + contextTypeSuffix(stack[parentIndex].type)); + + vStringCat(stack[stackIndex].contextName, contextName); + } else + vStringCopy(stack[stackIndex].contextName, contextName); + + stackIndex++; +} + +static void pushStrongContext(vString *name, contextType type) { + pushContext(ContextStrong, type, &globalScope, name); +} + +static void pushSoftContext(parseNext continuation, vString *name, + contextType type) { + pushContext(ContextSoft, type, continuation, name); +} + +static void pushEmptyContext(parseNext continuation) { + pushContext(ContextSoft, ContextValue, continuation, NULL); +} + +/* unroll the stack until the last named context. + * then discard it. Used to handle the : + * let f x y = ... + * in ... + * where the context is reseted after the in. Context may have + * been really nested before that. */ +static void popLastNamed(void) { + int i = getLastNamedIndex(); + + if (i >= 0) { + stackIndex = i; + toDoNext = stack[i].callback; + vStringClear(stack[i].contextName); + } else { + /* ok, no named context found... + * (should not happen). */ + stackIndex = 0; + toDoNext = &globalScope; + } +} + +/* pop a context without regarding it's content + * (beside handling empty stack case) */ +static void popSoftContext(void) { + if (stackIndex <= 0) { + toDoNext = &globalScope; + } else { + stackIndex--; + toDoNext = stack[stackIndex].callback; + vStringClear(stack[stackIndex].contextName); + } +} + +/* Reset everything until the last global space. + * a strong context can be : + * - module + * - class definition + * - the initial global space + * - a _global_ delcaration (let at global scope or in a module). + * Created to exit quickly deeply nested context */ +static contextType popStrongContext(void) { + int i; + + for (i = stackIndex - 1; i >= 0; --i) { + if (stack[i].kind == ContextStrong) { + stackIndex = i; + toDoNext = stack[i].callback; + vStringClear(stack[i].contextName); + return stack[i].type; + } + } + /* ok, no strong context found... */ + stackIndex = 0; + toDoNext = &globalScope; + return -1; +} + +/* Ignore everything till waitedToken and jump to comeAfter. + * If the "end" keyword is encountered break, doesn't remember + * why though. */ +static void tillToken(vString *const UNUSED(ident), ocaToken what) { + if (what == waitedToken) + toDoNext = comeAfter; + else if (what == OcaKEYWORD_end) { + popStrongContext(); + toDoNext = &globalScope; + } +} + +/* Ignore everything till a waitedToken is seen, but + * take care of balanced parentheses/bracket use */ +static void contextualTillToken(vString *const UNUSED(ident), ocaToken what) { + static int parentheses = 0; + static int bracket = 0; + static int curly = 0; + + switch (what) { + case Tok_PARL: + parentheses--; + break; + case Tok_PARR: + parentheses++; + break; + case Tok_CurlL: + curly--; + break; + case Tok_CurlR: + curly++; + break; + case Tok_BRL: + bracket--; + break; + case Tok_BRR: + bracket++; + break; + + default: /* other token are ignored */ + break; + } + + if (what == waitedToken && parentheses == 0 && bracket == 0 && curly == 0) + toDoNext = comeAfter; + + else if (what == OcaKEYWORD_end) { + popStrongContext(); + toDoNext = &globalScope; + } +} + +/* Wait for waitedToken and jump to comeAfter or let + * the globalScope handle declarations */ +static void tillTokenOrFallback(vString *const ident, ocaToken what) { + if (what == waitedToken) + toDoNext = comeAfter; + else + globalScope(ident, what); +} + +/* ignore token till waitedToken, or give up if find + * terminatingToken. Use globalScope to handle new + * declarations. */ +static void tillTokenOrTerminatingOrFallback(vString *const ident, + ocaToken what) { + if (what == waitedToken) + toDoNext = comeAfter; + else if (what == terminatingToken) + toDoNext = globalScope; + else + globalScope(ident, what); +} + +/* ignore the next token in the stream and jump to the + * given comeAfter state */ +static void ignoreToken(vString *const UNUSED(ident), ocaToken UNUSED(what)) { + toDoNext = comeAfter; +} + +/********** Grammar */ +/* the purpose of each function is detailled near their + * implementation */ + +static void killCurrentState(void) { + + /* Tracking the kind of previous strong + * context, if it doesn't match with a + * really strong entity, repop */ + switch (popStrongContext()) { + + case ContextValue: + popStrongContext(); + break; + case ContextFunction: + popStrongContext(); + break; + case ContextMethod: + popStrongContext(); + break; + + case ContextType: + popStrongContext(); + break; + case ContextBlock: + break; + case ContextModule: + break; + case ContextClass: + break; + default: + /* nothing more */ + break; + } +} + +/* used to prepare tag for OCaml, just in case their is a need to + * add additional information to the tag. */ +static void prepareTag(tagEntryInfo *tag, vString const *name, ocamlKind kind) { + int parentIndex; + + initTagEntry(tag, vStringValue(name)); + tag->kindName = OcamlKinds[kind].name; + tag->kind = OcamlKinds[kind].letter; + + if (kind == K_MODULE) { + tag->lineNumberEntry = TRUE; + tag->lineNumber = 1; + } + parentIndex = getLastNamedIndex(); + if (parentIndex >= 0) { + tag->extensionFields.scope[0] = contextDescription(stack[parentIndex].type); + tag->extensionFields.scope[1] = + vStringValue(stack[parentIndex].contextName); + } +} + +/* Used to centralise tag creation, and be able to add + * more information to it in the future */ +static void addTag(vString *const ident, int kind) { + if (OcamlKinds[kind].enabled && ident != NULL && vStringLength(ident) > 0) { + tagEntryInfo toCreate; + prepareTag(&toCreate, ident, kind); + makeTagEntry(&toCreate); + } +} + +boolean needStrongPoping = FALSE; +static void requestStrongPoping(void) { + needStrongPoping = TRUE; +} + +static void cleanupPreviousParser(void) { + if (needStrongPoping) { + needStrongPoping = FALSE; + popStrongContext(); + } +} + +/* Due to some circular dependencies, the following functions + * must be forward-declared. */ +static void letParam(vString *const ident, ocaToken what); +static void localScope(vString *const ident, ocaToken what); +static void mayRedeclare(vString *const ident, ocaToken what); +static void typeSpecification(vString *const ident, ocaToken what); + +/* + * Parse a record type + * type ident = // parsed previously + * { + * ident1: type1; + * ident2: type2; + * } + */ +static void typeRecord(vString *const ident, ocaToken what) { + switch (what) { + case OcaIDENTIFIER: + addTag(ident, K_RECORDFIELD); + terminatingToken = Tok_CurlR; + waitedToken = Tok_semi; + comeAfter = &typeRecord; + toDoNext = &tillTokenOrTerminatingOrFallback; + break; + + case OcaKEYWORD_mutable: + /* ignore it */ + break; + + case Tok_CurlR: + popStrongContext(); + toDoNext = &globalScope; + break; + + default: /* don't care */ + break; + } +} + +/* handle : + * exception ExceptionName of ... */ +static void exceptionDecl(vString *const ident, ocaToken what) { + if (what == OcaIDENTIFIER) { + addTag(ident, K_EXCEPTION); + } else /* probably ill-formed, give back to global scope */ + { + globalScope(ident, what); + } + toDoNext = &globalScope; +} + +tagEntryInfo tempTag; +vString *tempIdent; + +/* Ensure a constructor is not a type path beginning + * with a module */ +static void constructorValidation(vString *const ident, ocaToken what) { + switch (what) { + case Tok_Op: /* if we got a '.' which is an operator */ + toDoNext = &globalScope; + popStrongContext(); + needStrongPoping = FALSE; + break; + + case OcaKEYWORD_of: /* OK, it must be a constructor :) */ + makeTagEntry(&tempTag); + vStringClear(tempIdent); + toDoNext = &tillTokenOrFallback; + comeAfter = &typeSpecification; + waitedToken = Tok_Pipe; + break; + + case Tok_Pipe: /* OK, it was a constructor :) */ + makeTagEntry(&tempTag); + vStringClear(tempIdent); + toDoNext = &typeSpecification; + break; + + default: /* and mean that we're not facing a module name */ + makeTagEntry(&tempTag); + vStringClear(tempIdent); + toDoNext = &tillTokenOrFallback; + comeAfter = &typeSpecification; + waitedToken = Tok_Pipe; + + /* nothing in the context, discard it */ + popStrongContext(); + + /* to be sure we use this token */ + globalScope(ident, what); + } +} + +/* Parse beginning of type definition + * type 'avar ident = + * or + * type ('var1, 'var2) ident = + */ +static void typeDecl(vString *const ident, ocaToken what) { + switch (what) { + /* parameterized */ + case Tok_Prime: + comeAfter = &typeDecl; + toDoNext = &ignoreToken; + break; + /* LOTS of parameters */ + case Tok_PARL: + comeAfter = &typeDecl; + waitedToken = Tok_PARR; + toDoNext = &tillToken; + break; + + case OcaIDENTIFIER: + addTag(ident, K_TYPE); + pushStrongContext(ident, ContextType); + requestStrongPoping(); + waitedToken = Tok_EQ; + comeAfter = &typeSpecification; + toDoNext = &tillTokenOrFallback; + break; + + default: + globalScope(ident, what); + } +} + +/* Parse type of kind + * type bidule = Ctor1 of ... + * | Ctor2 + * | Ctor3 of ... + * or + * type bidule = | Ctor1 of ... | Ctor2 + * + * when type bidule = { ... } is detected, + * let typeRecord handle it. */ +static void typeSpecification(vString *const ident, ocaToken what) { + switch (what) { + case OcaIDENTIFIER: + if (isUpperAlpha(ident->buffer[0])) { + /* here we handle type aliases of type + * type foo = AnotherModule.bar + * AnotherModule can mistakenly be took + * for a constructor. */ + vStringCopy(tempIdent, ident); + prepareTag(&tempTag, tempIdent, K_CONSTRUCTOR); + toDoNext = &constructorValidation; + } else { + toDoNext = &tillTokenOrFallback; + comeAfter = &typeSpecification; + waitedToken = Tok_Pipe; + } + break; + + case OcaKEYWORD_and: + toDoNext = &typeDecl; + break; + + case Tok_BRL: /* the '[' & ']' are ignored to accommodate */ + case Tok_BRR: /* with the revised syntax */ + case Tok_Pipe: + /* just ignore it */ + break; + + case Tok_CurlL: + toDoNext = &typeRecord; + break; + + default: /* don't care */ + break; + } +} + +static boolean dirtySpecialParam = FALSE; + +/* parse the ~label and ~label:type parameter */ +static void parseLabel(vString *const ident, ocaToken what) { + static int parCount = 0; + + switch (what) { + case OcaIDENTIFIER: + if (!dirtySpecialParam) { + + if (exportLocalInfo) addTag(ident, K_VAR); + + dirtySpecialParam = TRUE; + } + break; + + case Tok_PARL: + parCount++; + break; + + case Tok_PARR: + parCount--; + if (parCount == 0) toDoNext = &letParam; + break; + + case Tok_Op: + if (ident->buffer[0] == ':') { + toDoNext = &ignoreToken; + comeAfter = &letParam; + } else if (parCount == 0 && dirtySpecialParam) { + toDoNext = &letParam; + letParam(ident, what); + } + break; + + default: + if (parCount == 0 && dirtySpecialParam) { + toDoNext = &letParam; + letParam(ident, what); + } + break; + } +} + +/* Optional argument with syntax like this : + * ?(foo = value) */ +static void parseOptionnal(vString *const ident, ocaToken what) { + static int parCount = 0; + + switch (what) { + case OcaIDENTIFIER: + if (!dirtySpecialParam) { + if (exportLocalInfo) addTag(ident, K_VAR); + + dirtySpecialParam = TRUE; + + if (parCount == 0) toDoNext = &letParam; + } + break; + + case Tok_PARL: + parCount++; + break; + + case Tok_PARR: + parCount--; + if (parCount == 0) toDoNext = &letParam; + break; + + default: /* don't care */ + break; + } +} + +/** handle let inside functions (so like it's name + * say : local let */ +static void localLet(vString *const ident, ocaToken what) { + switch (what) { + case Tok_PARL: + /* We ignore this token to be able to parse such + * declarations : + * let (ident : type) = ... + */ + break; + + case OcaKEYWORD_rec: + /* just ignore to be able to parse such declarations: + * let rec ident = ... */ + break; + + case Tok_Op: + /* we are defining a new operator, it's a + * function definition */ + if (exportLocalInfo) addTag(ident, K_FUNCTION); + + pushSoftContext(mayRedeclare, ident, ContextFunction); + toDoNext = &letParam; + break; + + /* Can be a weiiird binding, or an '_' */ + case Tok_Val: + if (exportLocalInfo) addTag(ident, K_VAR); + pushSoftContext(mayRedeclare, ident, ContextValue); + toDoNext = &letParam; + break; + + case OcaIDENTIFIER: + if (exportLocalInfo) addTag(ident, K_VAR); + pushSoftContext(mayRedeclare, ident, ContextValue); + toDoNext = &letParam; + break; + + case OcaKEYWORD_end: + popStrongContext(); + break; + + default: + toDoNext = &localScope; + break; + } +} + +/* parse : + * | pattern pattern -> ... + * or + * pattern apttern apttern -> ... + * we ignore all identifiers declared in the pattern, + * because their scope is likely to be even more limited + * than the let definitions. + * Used after a match ... with, or a function ... or fun ... + * because their syntax is similar. */ +static void matchPattern(vString *const ident, ocaToken what) { + /* keep track of [], as it + * can be used in patterns and can + * mean the end of match expression in + * revised syntax */ + static int braceCount = 0; + + switch (what) { + case Tok_To: + pushEmptyContext(&matchPattern); + toDoNext = &mayRedeclare; + break; + + case Tok_BRL: + braceCount++; + break; + + case OcaKEYWORD_value: + popLastNamed(); + globalScope(ident, what); + break; + + case OcaKEYWORD_in: + popLastNamed(); + break; + + default: + break; + } +} + +/* Used at the beginning of a new scope (begin of a + * definition, parenthesis...) to catch inner let + * definition that may be in. */ +static void mayRedeclare(vString *const ident, ocaToken what) { + switch (what) { + case OcaKEYWORD_value: + // let globalScope handle it + globalScope(ident, what); + break; + + case OcaKEYWORD_let: + case OcaKEYWORD_val: + toDoNext = localLet; + break; + + case OcaKEYWORD_object: + vStringClear(lastClass); + pushContext(ContextStrong, ContextClass, &localScope, NULL /*voidName */); + needStrongPoping = FALSE; + toDoNext = &globalScope; + break; + + case OcaKEYWORD_for: + case OcaKEYWORD_while: + toDoNext = &tillToken; + waitedToken = OcaKEYWORD_do; + comeAfter = &mayRedeclare; + break; + + case OcaKEYWORD_try: + toDoNext = &mayRedeclare; + pushSoftContext(matchPattern, ident, ContextFunction); + break; + + case OcaKEYWORD_fun: + toDoNext = &matchPattern; + break; + + /* Handle the special ;; from the OCaml + * Top level */ + case Tok_semi: + default: + toDoNext = &localScope; + localScope(ident, what); + } +} + +/* parse : + * p1 p2 ... pn = ... + * or + * ?(p1=v) p2 ~p3 ~pn:ja ... = ... */ +static void letParam(vString *const ident, ocaToken what) { + switch (what) { + case Tok_EQ: + toDoNext = &mayRedeclare; + break; + + case OcaIDENTIFIER: + if (exportLocalInfo) addTag(ident, K_VAR); + break; + + case Tok_Op: + switch (ident->buffer[0]) { + case ':': + /*popSoftContext(); */ + /* we got a type signature */ + comeAfter = &mayRedeclare; + toDoNext = &tillTokenOrFallback; + waitedToken = Tok_EQ; + break; + + /* parse something like + * ~varname:type + * or + * ~varname + * or + * ~(varname: long type) */ + case '~': + toDoNext = &parseLabel; + dirtySpecialParam = FALSE; + break; + + /* Optional argument with syntax like this : + * ?(bla = value) + * or + * ?bla */ + case '?': + toDoNext = &parseOptionnal; + dirtySpecialParam = FALSE; + break; + + default: + break; + } + break; + + default: /* don't care */ + break; + } +} + +/* parse object ... + * used to be sure the class definition is not a type + * alias */ +static void classSpecif(vString *const UNUSED(ident), ocaToken what) { + switch (what) { + case OcaKEYWORD_object: + pushStrongContext(lastClass, ContextClass); + toDoNext = &globalScope; + break; + + default: + vStringClear(lastClass); + toDoNext = &globalScope; + } +} + +/* Handle a method ... class declaration. + * nearly a copy/paste of globalLet. */ +static void methodDecl(vString *const ident, ocaToken what) { + + switch (what) { + case Tok_PARL: + /* We ignore this token to be able to parse such + * declarations : + * let (ident : type) = ... */ + break; + + case OcaKEYWORD_mutable: + case OcaKEYWORD_virtual: + case OcaKEYWORD_rec: + /* just ignore to be able to parse such declarations: + * let rec ident = ... */ + break; + + case OcaIDENTIFIER: + addTag(ident, K_METHOD); + /* Normal pushing to get good subs */ + pushStrongContext(ident, ContextMethod); + /*pushSoftContext( globalScope, ident, ContextMethod ); */ + toDoNext = &letParam; + break; + + case OcaKEYWORD_end: + popStrongContext(); + break; + + default: + toDoNext = &globalScope; + break; + } +} + +/* name of the last module, used for + * context stacking. */ +vString *lastModule; + +/* parse + * ... struct (* new global scope *) end + * or + * ... sig (* new global scope *) end + * or + * functor ... -> moduleSpecif + */ +static void moduleSpecif(vString *const ident, ocaToken what) { + + switch (what) { + case OcaKEYWORD_functor: + toDoNext = &contextualTillToken; + waitedToken = Tok_To; + comeAfter = &moduleSpecif; + break; + + case OcaKEYWORD_struct: + case OcaKEYWORD_sig: + pushStrongContext(lastModule, ContextModule); + toDoNext = &globalScope; + break; + + case Tok_PARL: /* ( */ + toDoNext = &contextualTillToken; + comeAfter = &globalScope; + waitedToken = Tok_PARR; + contextualTillToken(ident, what); + break; + + default: + vStringClear(lastModule); + toDoNext = &globalScope; + } +} + +/* parse : + * module name = ... + * then pass the token stream to moduleSpecif */ +static void moduleDecl(vString *const ident, ocaToken what) { + switch (what) { + case OcaKEYWORD_type: + /* just ignore it, name come after */ + break; + + case OcaIDENTIFIER: + addTag(ident, K_MODULE); + vStringCopy(lastModule, ident); + waitedToken = Tok_EQ; + comeAfter = &moduleSpecif; + toDoNext = &contextualTillToken; + break; + + default: /* don't care */ + break; + } +} + +/* parse : + * class name = ... + * or + * class virtual ['a,'b] classname = ... */ +static void classDecl(vString *const ident, ocaToken what) { + switch (what) { + case OcaIDENTIFIER: + addTag(ident, K_CLASS); + vStringCopy(lastClass, ident); + toDoNext = &contextualTillToken; + waitedToken = Tok_EQ; + comeAfter = &classSpecif; + break; + + case Tok_BRL: + toDoNext = &tillToken; + waitedToken = Tok_BRR; + comeAfter = &classDecl; + break; + + default: + break; + } +} + +/* Handle a global + * let ident ... + * or + * let rec ident ... */ +static void globalLet(vString *const ident, ocaToken what) { + switch (what) { + case Tok_PARL: + /* We ignore this token to be able to parse such + * declarations : + * let (ident : type) = ... + */ + break; + + case OcaKEYWORD_mutable: + case OcaKEYWORD_virtual: + case OcaKEYWORD_rec: + /* just ignore to be able to parse such declarations: + * let rec ident = ... */ + break; + + case Tok_Op: + /* we are defining a new operator, it's a + * function definition */ + addTag(ident, K_FUNCTION); + pushStrongContext(ident, ContextFunction); + toDoNext = &letParam; + break; + + case OcaIDENTIFIER: + addTag(ident, K_VAR); + pushStrongContext(ident, ContextValue); + requestStrongPoping(); + toDoNext = &letParam; + break; + + case OcaKEYWORD_end: + popStrongContext(); + break; + + default: + toDoNext = &globalScope; + break; + } +} + +/* Handle the "strong" top levels, all 'big' declarations + * happen here */ +static void globalScope(vString *const UNUSED(ident), ocaToken what) { + /* Do not touch, this is used only by the global scope + * to handle an 'and' */ + static parseNext previousParser = &globalScope; + + switch (what) { + case OcaKEYWORD_and: + cleanupPreviousParser(); + toDoNext = previousParser; + break; + + case OcaKEYWORD_type: + cleanupPreviousParser(); + toDoNext = &typeDecl; + previousParser = &typeDecl; + break; + + case OcaKEYWORD_class: + cleanupPreviousParser(); + toDoNext = &classDecl; + previousParser = &classDecl; + break; + + case OcaKEYWORD_module: + cleanupPreviousParser(); + toDoNext = &moduleDecl; + previousParser = &moduleDecl; + break; + + case OcaKEYWORD_end: + needStrongPoping = FALSE; + killCurrentState(); + /*popStrongContext(); */ + break; + + case OcaKEYWORD_method: + cleanupPreviousParser(); + toDoNext = &methodDecl; + /* and is not allowed in methods */ + break; + + /* val is mixed with let as global + * to be able to handle mli & new syntax */ + case OcaKEYWORD_val: + case OcaKEYWORD_value: + case OcaKEYWORD_let: + cleanupPreviousParser(); + toDoNext = &globalLet; + previousParser = &globalLet; + break; + + case OcaKEYWORD_exception: + cleanupPreviousParser(); + toDoNext = &exceptionDecl; + previousParser = &globalScope; + break; + + /* must be a #line directive, discard the + * whole line. */ + case Tok_Sharp: + /* ignore */ + break; + + default: + /* we don't care */ + break; + } +} + +/* Parse expression. Well ignore it is more the case, + * ignore all tokens except "shocking" keywords */ +static void localScope(vString *const ident, ocaToken what) { + switch (what) { + case Tok_Pipe: + case Tok_PARR: + case Tok_BRR: + case Tok_CurlR: + popSoftContext(); + break; + + /* Everything that `begin` has an `end` + * as end is overloaded and signal many end + * of things, we add an empty strong context to + * avoid problem with the end. + */ + case OcaKEYWORD_begin: + pushContext(ContextStrong, ContextBlock, &mayRedeclare, NULL); + toDoNext = &mayRedeclare; + break; + + case OcaKEYWORD_in: + popLastNamed(); + break; + + /* Ok, we got a '{', which is much likely to create + * a record. We cannot treat it like other [ && (, + * because it may contain the 'with' keyword and screw + * everything else. */ + case Tok_CurlL: + toDoNext = &contextualTillToken; + waitedToken = Tok_CurlR; + comeAfter = &localScope; + contextualTillToken(ident, what); + break; + + /* Yeah imperative feature of OCaml, + * a ';' like in C */ + case Tok_semi: + toDoNext = &mayRedeclare; + break; + + case Tok_PARL: + case Tok_BRL: + pushEmptyContext(&localScope); + toDoNext = &mayRedeclare; + break; + + case OcaKEYWORD_and: + popLastNamed(); + toDoNext = &localLet; + break; + + case OcaKEYWORD_else: + case OcaKEYWORD_then: + popSoftContext(); + pushEmptyContext(&localScope); + toDoNext = &mayRedeclare; + break; + + case OcaKEYWORD_if: + pushEmptyContext(&localScope); + toDoNext = &mayRedeclare; + break; + + case OcaKEYWORD_match: + pushEmptyContext(&localScope); + toDoNext = &mayRedeclare; + break; + + case OcaKEYWORD_with: + popSoftContext(); + toDoNext = &matchPattern; + pushEmptyContext(&matchPattern); + break; + + case OcaKEYWORD_end: + killCurrentState(); + break; + + case OcaKEYWORD_fun: + comeAfter = &mayRedeclare; + toDoNext = &tillToken; + waitedToken = Tok_To; + break; + + case OcaKEYWORD_done: + case OcaKEYWORD_val: + /* doesn't care */ + break; + + default: + requestStrongPoping(); + globalScope(ident, what); + break; + } +} + +/*//////////////////////////////////////////////////////////////// +//// Deal with the system */ +/* in OCaml the file name is the module name used in the language + * with it first letter put in upper case */ +static void computeModuleName(void) { + /* in Ocaml the file name define a module. + * so we define a module =) + */ + const char *filename = getSourceFileName(); + int beginIndex = 0; + int endIndex = strlen(filename) - 1; + vString *moduleName = vStringNew(); + + while (filename[endIndex] != '.' && endIndex > 0) endIndex--; + + /* avoid problem with path in front of filename */ + beginIndex = endIndex; + while (beginIndex > 0) { + if (filename[beginIndex] == '\\' || filename[beginIndex] == '/') { + beginIndex++; + break; + } + + beginIndex--; + } + + vStringNCopyS(moduleName, &filename[beginIndex], endIndex - beginIndex); + vStringTerminate(moduleName); + + if (isLowerAlpha(moduleName->buffer[0])) moduleName->buffer[0] += ('A' - 'a'); + + addTag(moduleName, K_MODULE); + vStringDelete(moduleName); +} + +/* Allocate all string of the context stack */ +static void initStack(void) { + int i; + for (i = 0; i < OCAML_MAX_STACK_SIZE; ++i) + stack[i].contextName = vStringNew(); + stackIndex = 0; +} + +static void clearStack(void) { + int i; + for (i = 0; i < OCAML_MAX_STACK_SIZE; ++i) + vStringDelete(stack[i].contextName); +} + +static void findOcamlTags(void) { + vString *name = vStringNew(); + lexingState st; + ocaToken tok; + + initStack(); + computeModuleName(); + tempIdent = vStringNew(); + lastModule = vStringNew(); + lastClass = vStringNew(); + voidName = vStringNew(); + vStringCopyS(voidName, "_"); + + st.name = vStringNew(); + st.cp = fileReadLine(); + toDoNext = &globalScope; + tok = lex(&st); + while (tok != Tok_EOF) { + (*toDoNext)(st.name, tok); + tok = lex(&st); + } + + vStringDelete(name); + vStringDelete(voidName); + vStringDelete(tempIdent); + vStringDelete(lastModule); + vStringDelete(lastClass); + clearStack(); +} + +static void ocamlInitialize(const langType language) { + Lang_Ocaml = language; + + initOperatorTable(); + initKeywordHash(); +} + +extern parserDefinition *OcamlParser(void) { + static const char *const extensions[] = {"ml", "mli", NULL}; + parserDefinition *def = parserNew("OCaml"); + def->kinds = OcamlKinds; + def->kindCount = KIND_COUNT(OcamlKinds); + def->extensions = extensions; + def->parser = findOcamlTags; + def->initialize = ocamlInitialize; + + return def; +} diff --git a/third_party/ctags/options.c b/third_party/ctags/options.c new file mode 100644 index 00000000..47c4e56a --- /dev/null +++ b/third_party/ctags/options.c @@ -0,0 +1,1669 @@ +/* + * $Id: options.c 576 2007-06-30 04:16:23Z elliotth $ + * + * Copyright (c) 1996-2003, Darren Hiebert + * + * This source code is released for free distribution under the terms of the + * GNU General Public License. + * + * This module contains functions to process command line options. + */ +#include "third_party/ctags/general.h" +/* must always come first */ +#include "third_party/ctags/ctags.h" +#include "third_party/ctags/debug.h" +#include "third_party/ctags/main.h" +#define OPTION_WRITE +#include "libc/fmt/fmt.h" +#include "third_party/ctags/options.h" +#include "third_party/ctags/parse.h" +#include "third_party/ctags/routines.h" + +/* + * MACROS + */ +#define INVOCATION "Usage: %s [options] [file(s)]\n" + +#define CTAGS_ENVIRONMENT "CTAGS" +#define ETAGS_ENVIRONMENT "ETAGS" + +#define CTAGS_FILE "tags" +#define ETAGS_FILE "TAGS" + +#ifndef ETAGS +#define ETAGS "etags" /* name which causes default use of to -e */ +#endif + +/* The following separators are permitted for list options. + */ +#define EXTENSION_SEPARATOR '.' +#define PATTERN_START '(' +#define PATTERN_STOP ')' +#define IGNORE_SEPARATORS ", \t\n" + +#ifndef DEFAULT_FILE_FORMAT +#define DEFAULT_FILE_FORMAT 2 +#endif + +#if defined(HAVE_OPENDIR) || defined(HAVE_FINDFIRST) || \ + defined(HAVE__FINDFIRST) || defined(AMIGA) +#define RECURSE_SUPPORTED +#endif + +#define isCompoundOption(c) (boolean)(strchr("fohiILpDb", (c)) != NULL) + +/* + * Data declarations + */ + +enum eOptionLimits { + MaxHeaderExtensions = 100, /* maximum number of extensions in -h option */ + MaxSupportedTagFormat = 2 +}; + +typedef struct sOptionDescription { + int usedByEtags; + const char *description; +} optionDescription; + +typedef void (*parametricOptionHandler)(const char *const option, + const char *const parameter); + +typedef const struct { + const char *name; /* name of option as specified by user */ + parametricOptionHandler handler; /* routine to handle option */ + boolean initOnly; /* option must be specified before any files */ +} parametricOption; + +typedef const struct { + const char *name; /* name of option as specified by user */ + boolean *pValue; /* pointer to option value */ + boolean initOnly; /* option must be specified before any files */ +} booleanOption; + +/* + * DATA DEFINITIONS + */ + +static boolean NonOptionEncountered; +static stringList *OptionFiles; +static stringList *Excluded; +static boolean FilesRequired = TRUE; +static boolean SkipConfiguration; + +static const char *const HeaderExtensions[] = { + "h", "H", "hh", "hpp", "hxx", "h++", "inc", "def", NULL}; + +optionValues Option = { + { + FALSE, /* --extra=f */ + FALSE, /* --extra=q */ + TRUE, /* --file-scope */ + }, + { + FALSE, /* -fields=a */ + TRUE, /* -fields=f */ + FALSE, /* -fields=m */ + FALSE, /* -fields=i */ + TRUE, /* -fields=k */ + FALSE, /* -fields=z */ + FALSE, /* -fields=K */ + FALSE, /* -fields=l */ + FALSE, /* -fields=n */ + TRUE, /* -fields=s */ + FALSE, /* -fields=S */ + TRUE /* -fields=t */ + }, + NULL, /* -I */ + FALSE, /* -a */ + FALSE, /* -B */ + FALSE, /* -e */ +#ifdef MACROS_USE_PATTERNS + EX_PATTERN, /* -n, --excmd */ +#else + EX_MIX, /* -n, --excmd */ +#endif + FALSE, /* -R */ + SO_SORTED, /* -u, --sort */ + FALSE, /* -V */ + FALSE, /* -x */ + NULL, /* -L */ + NULL, /* -o */ + NULL, /* -h */ + NULL, /* --etags-include */ + DEFAULT_FILE_FORMAT, /* --format */ + FALSE, /* --if0 */ + FALSE, /* --kind-long */ + LANG_AUTO, /* --lang */ + TRUE, /* --links */ + FALSE, /* --filter */ + NULL, /* --filter-terminator */ + FALSE, /* --tag-relative */ + FALSE, /* --totals */ + FALSE, /* --line-directives */ +#ifdef DEBUG + 0, + 0 /* -D, -b */ +#endif +}; + +/* +- Locally used only +*/ + +static optionDescription LongOptionDescription[] = { + {1, " -a Append the tags to an existing tag file."}, +#ifdef DEBUG + {1, " -b "}, + {1, " Set break line."}, +#endif + {0, " -B Use backward searching patterns (?...?)."}, +#ifdef DEBUG + {1, " -D "}, + {1, " Set debug level."}, +#endif + {0, " -e Output tag file for use with Emacs."}, + {1, " -f "}, + {1, " Write tags to specified file. Value of \"-\" writes tags to " + "stdout"}, + {1, " [\"tags\"; or \"TAGS\" when -e supplied]."}, + {0, " -F Use forward searching patterns (/.../) (default)."}, + {1, " -h "}, + {1, + " Specify list of file extensions to be treated as include files."}, + {1, " [\".h.H.hh.hpp.hxx.h++\"]."}, + {1, " -I "}, + {1, + " A list of tokens to be specially handled is read from either the"}, + {1, " command line or the specified file."}, + {1, " -L "}, + {1, " A list of source file names are read from the specified file."}, + {1, " If specified as \"-\", then standard input is read."}, + {0, " -n Equivalent to --excmd=number."}, + {0, " -N Equivalent to --excmd=pattern."}, + {1, " -o Alternative for -f."}, +#ifdef RECURSE_SUPPORTED + {1, " -R Equivalent to --recurse."}, +#else + {1, " -R Not supported on this platform."}, +#endif + {0, " -u Equivalent to --sort=no."}, + {1, " -V Equivalent to --verbose."}, + {1, " -x Print a tabular cross reference file to standard output."}, + {1, " --append=[yes|no]"}, + {1, " Should tags should be appended to existing tag file [no]?"}, + {1, " --etags-include=file"}, + {1, " Include reference to 'file' in Emacs-style tag file (requires " + "-e)."}, + {1, " --exclude=pattern"}, + {1, " Exclude files and directories matching 'pattern'."}, + {0, " --excmd=number|pattern|mix"}, +#ifdef MACROS_USE_PATTERNS + {0, + " Uses the specified type of EX command to locate tags [pattern]."}, +#else + {0, " Uses the specified type of EX command to locate tags [mix]."}, +#endif + {1, " --extra=[+|-]flags"}, + {1, " Include extra tag entries for selected information (flags: " + "\"fq\")."}, + {1, " --fields=[+|-]flags"}, + {1, " Include selected extension fields (flags: \"afmikKlnsStz\") " + "[fks]."}, + {1, " --file-scope=[yes|no]"}, + {1, + " Should tags scoped only for a single file (e.g. \"static\" tags"}, + {1, " be included in the output [yes]?"}, + {1, " --filter=[yes|no]"}, + {1, + " Behave as a filter, reading file names from standard input and"}, + {1, " writing tags to standard output [no]."}, + {1, " --filter-terminator=string"}, + {1, " Specify string to print to stdout following the tags for each " + "file"}, + {1, " parsed when --filter is enabled."}, + {0, " --format=level"}, +#if DEFAULT_FILE_FORMAT == 1 + {0, " Force output of specified tag file format [1]."}, +#else + {0, " Force output of specified tag file format [2]."}, +#endif + {1, " --help"}, + {1, " Print this option summary."}, + {1, " --if0=[yes|no]"}, + {1, + " Should C code within #if 0 conditional branches be parsed [no]?"}, + {1, " ---kinds=[+|-]kinds"}, + {1, " Enable/disable tag kinds for language ."}, + {1, " --langdef=name"}, + {1, " Define a new language to be parsed with regular expressions."}, + {1, " --langmap=map(s)"}, + {1, + " Override default mapping of language to source file extension."}, + {1, " --language-force=language"}, + {1, " Force all files to be interpreted using specified language."}, + {1, " --languages=[+|-]list"}, + {1, " Restrict files scanned for tags to those mapped to langauges"}, + {1, " specified in the comma-separated 'list'. The list can contain " + "any"}, + {1, " built-in or user-defined language [all]."}, + {1, " --license"}, + {1, " Print details of software license."}, + {0, " --line-directives=[yes|no]"}, + {0, " Should #line directives be processed [no]?"}, + {1, " --links=[yes|no]"}, + {1, " Indicate whether symbolic links should be followed [yes]."}, + {1, " --list-kinds=[language|all]"}, + {1, " Output a list of all tag kinds for specified language or all."}, + {1, " --list-languages"}, + {1, " Output list of supported languages."}, + {1, " --list-maps=[language|all]"}, + {1, " Output list of language mappings."}, + {1, " --options=file"}, + {1, " Specify file from which command line options should be read."}, + {1, " --recurse=[yes|no]"}, +#ifdef RECURSE_SUPPORTED + {1, " Recurse into directories supplied on command line [no]."}, +#else + {1, " Not supported on this platform."}, +#endif +#ifdef HAVE_REGEX + {1, " --regex-=/line_pattern/name_pattern/[flags]"}, + {1, " Define regular expression for locating tags in specific " + "language."}, +#endif + {0, " --sort=[yes|no|foldcase]"}, + {0, " Should tags be sorted (optionally ignoring case) [yes]?."}, + {0, " --tag-relative=[yes|no]"}, + {0, " Should paths be relative to location of tag file [no; yes when " + "-e]?"}, + {1, " --totals=[yes|no]"}, + {1, " Print statistics about source and tag files [no]."}, + {1, " --verbose=[yes|no]"}, + {1, + " Enable verbose messages describing actions on each source file."}, + {1, " --version"}, + {1, " Print version identifier to standard output."}, + {1, NULL}}; + +static const char *const License1 = + "This program is free software; you can redistribute it and/or\n" + "modify it under the terms of the GNU General Public License\n" + "as published by the Free Software Foundation; either version 2\n" + "of the License, or (at your option) any later version.\n" + "\n"; +static const char *const License2 = + "This program is distributed in the hope that it will be useful,\n" + "but WITHOUT ANY WARRANTY; without even the implied warranty of\n" + "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" + "GNU General Public License for more details.\n" + "\n" + "You should have received a copy of the GNU General Public License\n" + "along with this program; if not, write to the Free Software\n" + "Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, " + "USA.\n"; + +/* Contains a set of strings describing the set of "features" compiled into + * the code. + */ +static const char *const Features[] = { +#ifdef WIN32 + "win32", +#endif +#ifdef DJGPP + "msdos_32", +#else +#ifdef MSDOS + "msdos_16", +#endif +#endif +#ifdef OS2 + "os2", +#endif +#ifdef AMIGA + "amiga", +#endif +#ifdef VMS + "vms", +#endif +#ifdef HAVE_FNMATCH + "wildcards", +#endif +#ifdef HAVE_REGEX + "regex", +#endif +#ifndef EXTERNAL_SORT + "internal-sort", +#endif +#ifdef CUSTOM_CONFIGURATION_FILE + "custom-conf", +#endif +#if (defined(MSDOS) || defined(WIN32) || defined(OS2)) && \ + defined(UNIX_PATH_SEPARATOR) + "unix-path-separator", +#endif +#ifdef DEBUG + "debug", +#endif + NULL}; + +/* + * FUNCTION PROTOTYPES + */ +static boolean parseFileOptions(const char *const fileName); + +/* + * FUNCTION DEFINITIONS + */ + +extern void verbose(const char *const format, ...) { + if (Option.verbose) { + va_list ap; + va_start(ap, format); + vprintf(format, ap); + va_end(ap); + } +} + +static char *stringCopy(const char *const string) { + char *result = NULL; + if (string != NULL) result = eStrdup(string); + return result; +} + +static void freeString(char **const pString) { + if (*pString != NULL) { + eFree(*pString); + *pString = NULL; + } +} + +extern void freeList(stringList **const pList) { + if (*pList != NULL) { + stringListDelete(*pList); + *pList = NULL; + } +} + +extern void setDefaultTagFileName(void) { + if (Option.tagFileName != NULL) + ; /* accept given name */ + else if (Option.etags) + Option.tagFileName = stringCopy(ETAGS_FILE); + else + Option.tagFileName = stringCopy(CTAGS_FILE); +} + +extern boolean filesRequired(void) { + boolean result = FilesRequired; + if (Option.recurse) result = FALSE; + return result; +} + +extern void checkOptions(void) { + const char *notice; + if (Option.xref) { + notice = "xref output"; + if (Option.include.fileNames) { + error(WARNING, "%s disables file name tags", notice); + Option.include.fileNames = FALSE; + } + } + if (Option.append) { + notice = "append mode is not compatible with"; + if (isDestinationStdout()) error(FATAL, "%s tags to stdout", notice); + } + if (Option.filter) { + notice = "filter mode"; + if (Option.printTotals) { + error(WARNING, "%s disables totals", notice); + Option.printTotals = FALSE; + } + if (Option.tagFileName != NULL) + error(WARNING, "%s ignores output tag file name", notice); + } +} + +static void setEtagsMode(void) { + Option.etags = TRUE; + Option.sorted = SO_UNSORTED; + Option.lineDirectives = FALSE; + Option.tagRelative = TRUE; +} + +extern void testEtagsInvocation(void) { + char *const execName = eStrdup(getExecutableName()); + char *const etags = eStrdup(ETAGS); +#ifdef CASE_INSENSITIVE_FILENAMES + toLowerString(execName); + toLowerString(etags); +#endif + if (strstr(execName, etags) != NULL) { + verbose("Running in etags mode\n"); + setEtagsMode(); + } + eFree(execName); + eFree(etags); +} + +/* + * Cooked argument parsing + */ + +static void parseShortOption(cookedArgs *const args) { + args->simple[0] = *args->shortOptions++; + args->simple[1] = '\0'; + args->item = args->simple; + if (!isCompoundOption(*args->simple)) + args->parameter = ""; + else if (*args->shortOptions == '\0') { + argForth(args->args); + if (argOff(args->args)) + args->parameter = NULL; + else + args->parameter = argItem(args->args); + args->shortOptions = NULL; + } else { + args->parameter = args->shortOptions; + args->shortOptions = NULL; + } +} + +static void parseLongOption(cookedArgs *const args, const char *item) { + const char *const equal = strchr(item, '='); + if (equal == NULL) { + args->item = eStrdup(item); /* FIXME: memory leak. */ + args->parameter = ""; + } else { + const size_t length = equal - item; + args->item = xMalloc(length + 1, char); /* FIXME: memory leak. */ + strncpy(args->item, item, length); + args->item[length] = '\0'; + args->parameter = equal + 1; + } + Assert(args->item != NULL); + Assert(args->parameter != NULL); +} + +static void cArgRead(cookedArgs *const current) { + char *item; + + Assert(current != NULL); + if (!argOff(current->args)) { + item = argItem(current->args); + current->shortOptions = NULL; + Assert(item != NULL); + if (strncmp(item, "--", (size_t)2) == 0) { + current->isOption = TRUE; + current->longOption = TRUE; + parseLongOption(current, item + 2); + Assert(current->item != NULL); + Assert(current->parameter != NULL); + } else if (*item == '-') { + current->isOption = TRUE; + current->longOption = FALSE; + current->shortOptions = item + 1; + parseShortOption(current); + } else { + current->isOption = FALSE; + current->longOption = FALSE; + current->item = item; + current->parameter = NULL; + } + } +} + +extern cookedArgs *cArgNewFromString(const char *string) { + cookedArgs *const result = xMalloc(1, cookedArgs); + memset(result, 0, sizeof(cookedArgs)); + result->args = argNewFromString(string); + cArgRead(result); + return result; +} + +extern cookedArgs *cArgNewFromArgv(char *const *const argv) { + cookedArgs *const result = xMalloc(1, cookedArgs); + memset(result, 0, sizeof(cookedArgs)); + result->args = argNewFromArgv(argv); + cArgRead(result); + return result; +} + +extern cookedArgs *cArgNewFromFile(FILE *const fp) { + cookedArgs *const result = xMalloc(1, cookedArgs); + memset(result, 0, sizeof(cookedArgs)); + result->args = argNewFromFile(fp); + cArgRead(result); + return result; +} + +extern cookedArgs *cArgNewFromLineFile(FILE *const fp) { + cookedArgs *const result = xMalloc(1, cookedArgs); + memset(result, 0, sizeof(cookedArgs)); + result->args = argNewFromLineFile(fp); + cArgRead(result); + return result; +} + +extern void cArgDelete(cookedArgs *const current) { + Assert(current != NULL); + argDelete(current->args); + memset(current, 0, sizeof(cookedArgs)); + eFree(current); +} + +static boolean cArgOptionPending(cookedArgs *const current) { + boolean result = FALSE; + if (current->shortOptions != NULL) + if (*current->shortOptions != '\0') result = TRUE; + return result; +} + +extern boolean cArgOff(cookedArgs *const current) { + Assert(current != NULL); + return (boolean)(argOff(current->args) && !cArgOptionPending(current)); +} + +extern boolean cArgIsOption(cookedArgs *const current) { + Assert(current != NULL); + return current->isOption; +} + +extern const char *cArgItem(cookedArgs *const current) { + Assert(current != NULL); + return current->item; +} + +extern void cArgForth(cookedArgs *const current) { + Assert(current != NULL); + Assert(!cArgOff(current)); + if (cArgOptionPending(current)) + parseShortOption(current); + else { + Assert(!argOff(current->args)); + argForth(current->args); + if (!argOff(current->args)) + cArgRead(current); + else { + current->isOption = FALSE; + current->longOption = FALSE; + current->shortOptions = NULL; + current->item = NULL; + current->parameter = NULL; + } + } +} + +/* + * File extension and language mapping + */ + +static void addExtensionList(stringList *const slist, const char *const elist, + const boolean clear) { + char *const extensionList = eStrdup(elist); + const char *extension = NULL; + boolean first = TRUE; + + if (clear) { + verbose(" clearing\n"); + stringListClear(slist); + } + verbose(" adding: "); + if (elist != NULL && *elist != '\0') { + extension = extensionList; + if (elist[0] == EXTENSION_SEPARATOR) ++extension; + } + while (extension != NULL) { + char *separator = strchr(extension, EXTENSION_SEPARATOR); + if (separator != NULL) *separator = '\0'; + verbose("%s%s", first ? "" : ", ", + *extension == '\0' ? "(NONE)" : extension); + stringListAdd(slist, vStringNewInit(extension)); + first = FALSE; + if (separator == NULL) + extension = NULL; + else + extension = separator + 1; + } + if (Option.verbose) { + printf("\n now: "); + stringListPrint(slist); + putchar('\n'); + } + eFree(extensionList); +} + +static boolean isFalse(const char *parameter) { + return (boolean)( + strcasecmp(parameter, "0") == 0 || strcasecmp(parameter, "n") == 0 || + strcasecmp(parameter, "no") == 0 || strcasecmp(parameter, "off") == 0); +} + +static boolean isTrue(const char *parameter) { + return (boolean)( + strcasecmp(parameter, "1") == 0 || strcasecmp(parameter, "y") == 0 || + strcasecmp(parameter, "yes") == 0 || strcasecmp(parameter, "on") == 0); +} + +/* Determines whether the specified file name is considered to be a header + * file for the purposes of determining whether enclosed tags are global or + * static. + */ +extern boolean isIncludeFile(const char *const fileName) { + boolean result = FALSE; + const char *const extension = fileExtension(fileName); + if (Option.headerExt != NULL) + result = stringListExtensionMatched(Option.headerExt, extension); + return result; +} + +/* + * Specific option processing + */ + +static void processEtagsInclude(const char *const option, + const char *const parameter) { + if (!Option.etags) + error(FATAL, "Etags must be enabled to use \"%s\" option", option); + else { + vString *const file = vStringNewInit(parameter); + if (Option.etagsInclude == NULL) Option.etagsInclude = stringListNew(); + stringListAdd(Option.etagsInclude, file); + FilesRequired = FALSE; + } +} + +static void processExcludeOption(const char *const option __unused__, + const char *const parameter) { + const char *const fileName = parameter + 1; + if (parameter[0] == '\0') + freeList(&Excluded); + else if (parameter[0] == '@') { + stringList *const sl = stringListNewFromFile(fileName); + if (sl == NULL) error(FATAL | PERROR, "cannot open \"%s\"", fileName); + if (Excluded == NULL) + Excluded = sl; + else + stringListCombine(Excluded, sl); + verbose(" adding exclude patterns from %s\n", fileName); + } else { + vString *const item = vStringNewInit(parameter); + if (Excluded == NULL) Excluded = stringListNew(); + stringListAdd(Excluded, item); + verbose(" adding exclude pattern: %s\n", parameter); + } +} + +extern boolean isExcludedFile(const char *const name) { + const char *base = baseFilename(name); + boolean result = FALSE; + if (Excluded != NULL) { + result = stringListFileMatched(Excluded, base); + if (!result && name != base) result = stringListFileMatched(Excluded, name); + } +#ifdef AMIGA + /* not a good solution, but the only one which works often */ + if (!result) result = (boolean)(strcmp(name, TagFile.name) == 0); +#endif + return result; +} + +static void processExcmdOption(const char *const option, + const char *const parameter) { + switch (*parameter) { + case 'm': + Option.locate = EX_MIX; + break; + case 'n': + Option.locate = EX_LINENUM; + break; + case 'p': + Option.locate = EX_PATTERN; + break; + default: + error(FATAL, "Invalid value for \"%s\" option", option); + break; + } +} + +static void processExtraTagsOption(const char *const option, + const char *const parameter) { + struct sInclude *const inc = &Option.include; + const char *p = parameter; + boolean mode = TRUE; + int c; + + if (*p != '+' && *p != '-') { + inc->fileNames = FALSE; + inc->qualifiedTags = FALSE; +#if 0 + inc->fileScope = FALSE; +#endif + } + while ((c = *p++) != '\0') switch (c) { + case '+': + mode = TRUE; + break; + case '-': + mode = FALSE; + break; + + case 'f': + inc->fileNames = mode; + break; + case 'q': + inc->qualifiedTags = mode; + break; +#if 0 + case 'F': inc->fileScope = mode; break; +#endif + + default: + error(WARNING, "Unsupported parameter '%c' for \"%s\" option", c, + option); + break; + } +} + +static void processFieldsOption(const char *const option, + const char *const parameter) { + struct sExtFields *field = &Option.extensionFields; + const char *p = parameter; + boolean mode = TRUE; + int c; + + if (*p != '+' && *p != '-') { + field->access = FALSE; + field->fileScope = FALSE; + field->implementation = FALSE; + field->inheritance = FALSE; + field->kind = FALSE; + field->kindKey = FALSE; + field->kindLong = FALSE; + field->language = FALSE; + field->scope = FALSE; + field->typeRef = FALSE; + } + while ((c = *p++) != '\0') switch (c) { + case '+': + mode = TRUE; + break; + case '-': + mode = FALSE; + break; + + case 'a': + field->access = mode; + break; + case 'f': + field->fileScope = mode; + break; + case 'm': + field->implementation = mode; + break; + case 'i': + field->inheritance = mode; + break; + case 'k': + field->kind = mode; + break; + case 'K': + field->kindLong = mode; + break; + case 'l': + field->language = mode; + break; + case 'n': + field->lineNumber = mode; + break; + case 's': + field->scope = mode; + break; + case 'S': + field->signature = mode; + break; + case 'z': + field->kindKey = mode; + break; + case 't': + field->typeRef = mode; + break; + + default: + error(WARNING, "Unsupported parameter '%c' for \"%s\" option", c, + option); + break; + } +} + +static void processFilterTerminatorOption(const char *const option __unused__, + const char *const parameter) { + freeString(&Option.filterTerminator); + Option.filterTerminator = stringCopy(parameter); +} + +static void processFormatOption(const char *const option, + const char *const parameter) { + unsigned int format; + + if (sscanf(parameter, "%u", &format) < 1) + error(FATAL, "Invalid value for \"%s\" option", option); + else if (format <= (unsigned int)MaxSupportedTagFormat) + Option.tagFileFormat = format; + else + error(FATAL, "Unsupported value for \"%s\" option", option); +} + +static void printInvocationDescription(void) { + printf(INVOCATION, getExecutableName()); +} + +static void printOptionDescriptions(const optionDescription *const optDesc) { + int i; + for (i = 0; optDesc[i].description != NULL; ++i) { + if (!Option.etags || optDesc[i].usedByEtags) puts(optDesc[i].description); + } +} + +static void printFeatureList(void) { + int i; + + for (i = 0; Features[i] != NULL; ++i) { + if (i == 0) printf(" Optional compiled features: "); + printf("%s+%s", (i > 0 ? ", " : ""), Features[i]); +#ifdef CUSTOM_CONFIGURATION_FILE + if (strcmp(Features[i], "custom-conf") == 0) + printf("=%s", CUSTOM_CONFIGURATION_FILE); +#endif + } + if (i > 0) putchar('\n'); +} + +static void printProgramIdentification(void) { + printf("%s %s, %s %s\n", PROGRAM_NAME, PROGRAM_VERSION, PROGRAM_COPYRIGHT, + AUTHOR_NAME); + printf(" Addresses: <%s>, %s\n", AUTHOR_EMAIL, PROGRAM_URL); + printFeatureList(); +} + +static void processHelpOption(const char *const option __unused__, + const char *const parameter __unused__) { + printProgramIdentification(); + putchar('\n'); + printInvocationDescription(); + putchar('\n'); + printOptionDescriptions(LongOptionDescription); + exit(0); +} + +static void processLanguageForceOption(const char *const option, + const char *const parameter) { + langType language; + if (strcasecmp(parameter, "auto") == 0) + language = LANG_AUTO; + else + language = getNamedLanguage(parameter); + + if (strcmp(option, "lang") == 0 || strcmp(option, "language") == 0) + error(WARNING, + "\"--%s\" option is obsolete; use \"--language-force\" instead", + option); + if (language == LANG_IGNORE) + error(FATAL, "Unknown language \"%s\" in \"%s\" option", parameter, option); + else + Option.language = language; +} +static char *skipPastMap(char *p) { + while (*p != EXTENSION_SEPARATOR && *p != PATTERN_START && *p != ',' && + *p != '\0') + ++p; + return p; +} + +/* Parses the mapping beginning at `map', adds it to the language map, and + * returns first character past the map. + */ +static char *addLanguageMap(const langType language, char *map) { + char *p = NULL; + const char first = *map; + if (first == EXTENSION_SEPARATOR) /* extension map */ + { + ++map; + p = skipPastMap(map); + if (*p == '\0') { + verbose(" .%s", map); + addLanguageExtensionMap(language, map); + p = map + strlen(map); + } else { + const char separator = *p; + *p = '\0'; + verbose(" .%s", map); + addLanguageExtensionMap(language, map); + *p = separator; + } + } else if (first == PATTERN_START) /* pattern map */ + { + ++map; + for (p = map; *p != PATTERN_STOP && *p != '\0'; ++p) { + if (*p == '\\' && *(p + 1) == PATTERN_STOP) ++p; + } + if (*p == '\0') + error(FATAL, "Unterminated file name pattern for %s language", + getLanguageName(language)); + else { + *p++ = '\0'; + verbose(" (%s)", map); + addLanguagePatternMap(language, map); + } + } else + error(FATAL, "Badly formed language map for %s language", + getLanguageName(language)); + return p; +} + +static char *processLanguageMap(char *map) { + char *const separator = strchr(map, ':'); + char *result = NULL; + if (separator != NULL) { + langType language; + char *list = separator + 1; + boolean clear = FALSE; + *separator = '\0'; + language = getNamedLanguage(map); + if (language != LANG_IGNORE) { + const char *const deflt = "default"; + char *p; + if (*list == '+') + ++list; + else + clear = TRUE; + for (p = list; *p != ',' && *p != '\0'; ++p) /*no-op*/ + ; + if ((size_t)(p - list) == strlen(deflt) && + strncasecmp(list, deflt, p - list) == 0) { + verbose(" Restoring default %s language map: ", + getLanguageName(language)); + installLanguageMapDefault(language); + list = p; + } else { + if (clear) { + verbose(" Setting %s language map:", getLanguageName(language)); + clearLanguageMap(language); + } else + verbose(" Adding to %s language map:", getLanguageName(language)); + while (list != NULL && *list != '\0' && *list != ',') + list = addLanguageMap(language, list); + verbose("\n"); + } + if (list != NULL && *list == ',') ++list; + result = list; + } + } + return result; +} + +static void processLanguageMapOption(const char *const option, + const char *const parameter) { + char *const maps = eStrdup(parameter); + char *map = maps; + + if (strcmp(parameter, "default") == 0) { + verbose(" Restoring default language maps:\n"); + installLanguageMapDefaults(); + } else + while (map != NULL && *map != '\0') { + char *const next = processLanguageMap(map); + if (next == NULL) + error(WARNING, "Unknown language \"%s\" in \"%s\" option", parameter, + option); + map = next; + } + eFree(maps); +} + +static void processLanguagesOption(const char *const option, + const char *const parameter) { + char *const langs = eStrdup(parameter); + enum { Add, Remove, Replace } mode = Replace; + boolean first = TRUE; + char *lang = langs; + const char *prefix = ""; + verbose(" Enabled languages: "); + while (lang != NULL) { + char *const end = strchr(lang, ','); + if (lang[0] == '+') { + ++lang; + mode = Add; + prefix = "+ "; + } else if (lang[0] == '-') { + ++lang; + mode = Remove; + prefix = "- "; + } + if (mode == Replace) enableLanguages(FALSE); + if (end != NULL) *end = '\0'; + if (lang[0] != '\0') { + if (strcmp(lang, "all") == 0) + enableLanguages((boolean)(mode != Remove)); + else { + const langType language = getNamedLanguage(lang); + if (language == LANG_IGNORE) + error(WARNING, "Unknown language \"%s\" in \"%s\" option", lang, + option); + else + enableLanguage(language, (boolean)(mode != Remove)); + } + verbose("%s%s%s", (first ? "" : ", "), prefix, lang); + prefix = ""; + first = FALSE; + if (mode == Replace) mode = Add; + } + lang = (end != NULL ? end + 1 : NULL); + } + verbose("\n"); + eFree(langs); +} + +static void processLicenseOption(const char *const option __unused__, + const char *const parameter __unused__) { + printProgramIdentification(); + puts(""); + puts(License1); + puts(License2); + exit(0); +} + +static void processListKindsOption(const char *const option, + const char *const parameter) { + if (parameter[0] == '\0' || strcasecmp(parameter, "all") == 0) + printLanguageKinds(LANG_AUTO); + else { + langType language = getNamedLanguage(parameter); + if (language == LANG_IGNORE) + error(FATAL, "Unknown language \"%s\" in \"%s\" option", parameter, + option); + else + printLanguageKinds(language); + } + exit(0); +} + +static void processListMapsOption(const char *const __unused__ option, + const char *const __unused__ parameter) { + if (parameter[0] == '\0' || strcasecmp(parameter, "all") == 0) + printLanguageMaps(LANG_AUTO); + else { + langType language = getNamedLanguage(parameter); + if (language == LANG_IGNORE) + error(FATAL, "Unknown language \"%s\" in \"%s\" option", parameter, + option); + else + printLanguageMaps(language); + } + exit(0); +} + +static void processListLanguagesOption(const char *const option __unused__, + const char *const parameter __unused__) { + printLanguageList(); + exit(0); +} + +static void processOptionFile(const char *const option, + const char *const parameter) { + if (parameter[0] == '\0') + error(WARNING, "no option file supplied for \"%s\"", option); + else if (!parseFileOptions(parameter)) + error(FATAL | PERROR, "cannot open option file \"%s\"", parameter); +} + +static void processSortOption(const char *const option, + const char *const parameter) { + if (isFalse(parameter)) + Option.sorted = SO_UNSORTED; + else if (isTrue(parameter)) + Option.sorted = SO_SORTED; + else if (strcasecmp(parameter, "f") == 0 || + strcasecmp(parameter, "fold") == 0 || + strcasecmp(parameter, "foldcase") == 0) + Option.sorted = SO_FOLDSORTED; + else + error(FATAL, "Invalid value for \"%s\" option", option); +} + +static void installHeaderListDefaults(void) { + Option.headerExt = stringListNewFromArgv(HeaderExtensions); + if (Option.verbose) { + printf(" Setting default header extensions: "); + stringListPrint(Option.headerExt); + putchar('\n'); + } +} + +static void processHeaderListOption(const int option, const char *parameter) { + /* Check to make sure that the user did not enter "ctags -h *.c" + * by testing to see if the list is a filename that exists. + */ + if (doesFileExist(parameter)) error(FATAL, "-%c: Invalid list", option); + if (strcmp(parameter, "default") == 0) + installHeaderListDefaults(); + else { + boolean clear = TRUE; + + if (parameter[0] == '+') { + ++parameter; + clear = FALSE; + } + if (Option.headerExt == NULL) Option.headerExt = stringListNew(); + verbose(" Header Extensions:\n"); + addExtensionList(Option.headerExt, parameter, clear); + } +} + +/* + * Token ignore processing + */ + +/* Determines whether or not "name" should be ignored, per the ignore list. + */ +extern boolean isIgnoreToken(const char *const name, + boolean *const pIgnoreParens, + const char **const replacement) { + boolean result = FALSE; + + if (Option.ignore != NULL) { + const size_t nameLen = strlen(name); + unsigned int i; + + if (pIgnoreParens != NULL) *pIgnoreParens = FALSE; + + for (i = 0; i < stringListCount(Option.ignore); ++i) { + vString *token = stringListItem(Option.ignore, i); + + if (strncmp(vStringValue(token), name, nameLen) == 0) { + const size_t tokenLen = vStringLength(token); + + if (nameLen == tokenLen) { + result = TRUE; + break; + } else if (tokenLen == nameLen + 1 && + vStringChar(token, tokenLen - 1) == '+') { + result = TRUE; + if (pIgnoreParens != NULL) *pIgnoreParens = TRUE; + break; + } else if (vStringChar(token, nameLen) == '=') { + if (replacement != NULL) + *replacement = vStringValue(token) + nameLen + 1; + break; + } + } + } + } + return result; +} + +static void saveIgnoreToken(vString *const ignoreToken) { + if (Option.ignore == NULL) Option.ignore = stringListNew(); + stringListAdd(Option.ignore, ignoreToken); + verbose(" ignore token: %s\n", vStringValue(ignoreToken)); +} + +static void readIgnoreList(const char *const list) { + char *newList = stringCopy(list); + const char *token = strtok(newList, IGNORE_SEPARATORS); + + while (token != NULL) { + vString *const entry = vStringNewInit(token); + + saveIgnoreToken(entry); + token = strtok(NULL, IGNORE_SEPARATORS); + } + eFree(newList); +} + +static void addIgnoreListFromFile(const char *const fileName) { + stringList *tokens = stringListNewFromFile(fileName); + if (tokens == NULL) error(FATAL | PERROR, "cannot open \"%s\"", fileName); + if (Option.ignore == NULL) + Option.ignore = tokens; + else + stringListCombine(Option.ignore, tokens); +} + +static void processIgnoreOption(const char *const list) { + if (strchr("@./\\", list[0]) != NULL) { + const char *fileName = (*list == '@') ? list + 1 : list; + addIgnoreListFromFile(fileName); + } +#if defined(MSDOS) || defined(WIN32) || defined(OS2) + else if (isalpha(list[0]) && list[1] == ':') + addIgnoreListFromFile(list); +#endif + else if (strcmp(list, "-") == 0) { + freeList(&Option.ignore); + verbose(" clearing list\n"); + } else + readIgnoreList(list); +} + +static void processVersionOption(const char *const option __unused__, + const char *const parameter __unused__) { + printProgramIdentification(); + exit(0); +} + +/* + * Option tables + */ + +static parametricOption ParametricOptions[] = { + {"etags-include", processEtagsInclude, FALSE}, + {"exclude", processExcludeOption, FALSE}, + {"excmd", processExcmdOption, FALSE}, + {"extra", processExtraTagsOption, FALSE}, + {"fields", processFieldsOption, FALSE}, + {"filter-terminator", processFilterTerminatorOption, TRUE}, + {"format", processFormatOption, TRUE}, + {"help", processHelpOption, TRUE}, + {"lang", processLanguageForceOption, FALSE}, + {"language", processLanguageForceOption, FALSE}, + {"language-force", processLanguageForceOption, FALSE}, + {"languages", processLanguagesOption, FALSE}, + {"langdef", processLanguageDefineOption, FALSE}, + {"langmap", processLanguageMapOption, FALSE}, + {"license", processLicenseOption, TRUE}, + {"list-kinds", processListKindsOption, TRUE}, + {"list-maps", processListMapsOption, TRUE}, + {"list-languages", processListLanguagesOption, TRUE}, + {"options", processOptionFile, FALSE}, + {"sort", processSortOption, TRUE}, + {"version", processVersionOption, TRUE}, +}; + +static booleanOption BooleanOptions[] = { + {"append", &Option.append, TRUE}, + {"file-scope", &Option.include.fileScope, FALSE}, + {"file-tags", &Option.include.fileNames, FALSE}, + {"filter", &Option.filter, TRUE}, + {"if0", &Option.if0, FALSE}, + {"kind-long", &Option.kindLong, TRUE}, + {"line-directives", &Option.lineDirectives, FALSE}, + {"links", &Option.followLinks, FALSE}, +#ifdef RECURSE_SUPPORTED + {"recurse", &Option.recurse, FALSE}, +#endif + {"tag-relative", &Option.tagRelative, TRUE}, + {"totals", &Option.printTotals, TRUE}, + {"verbose", &Option.verbose, FALSE}, +}; + +/* + * Generic option parsing + */ + +static void checkOptionOrder(const char *const option) { + if (NonOptionEncountered) + error(FATAL, "-%s option may not follow a file name", option); +} + +static boolean processParametricOption(const char *const option, + const char *const parameter) { + const int count = sizeof(ParametricOptions) / sizeof(parametricOption); + boolean found = FALSE; + int i; + + for (i = 0; i < count && !found; ++i) { + parametricOption *const entry = &ParametricOptions[i]; + if (strcmp(option, entry->name) == 0) { + found = TRUE; + if (entry->initOnly) checkOptionOrder(option); + (entry->handler)(option, parameter); + } + } + return found; +} + +static boolean getBooleanOption(const char *const option, + const char *const parameter) { + boolean selection = TRUE; + + if (parameter[0] == '\0') + selection = TRUE; + else if (isFalse(parameter)) + selection = FALSE; + else if (isTrue(parameter)) + selection = TRUE; + else + error(FATAL, "Invalid value for \"%s\" option", option); + + return selection; +} + +static boolean processBooleanOption(const char *const option, + const char *const parameter) { + const int count = sizeof(BooleanOptions) / sizeof(booleanOption); + boolean found = FALSE; + int i; + + for (i = 0; i < count && !found; ++i) { + booleanOption *const entry = &BooleanOptions[i]; + if (strcmp(option, entry->name) == 0) { + found = TRUE; + if (entry->initOnly) checkOptionOrder(option); + *entry->pValue = getBooleanOption(option, parameter); + } + } + return found; +} + +static void processLongOption(const char *const option, + const char *const parameter) { + Assert(parameter != NULL); + if (parameter == NULL && parameter[0] == '\0') + verbose(" Option: --%s\n", option); + else + verbose(" Option: --%s=%s\n", option, parameter); + + if (processBooleanOption(option, parameter)) + ; + else if (processParametricOption(option, parameter)) + ; + else if (processKindOption(option, parameter)) + ; + else if (processRegexOption(option, parameter)) + ; +#ifndef RECURSE_SUPPORTED + else if (strcmp(option, "recurse") == 0) + error(WARNING, "%s option not supported on this host", option); +#endif + else + error(FATAL, "Unknown option: --%s", option); +} + +static void processShortOption(const char *const option, + const char *const parameter) { + if (parameter == NULL || parameter[0] == '\0') + verbose(" Option: -%s\n", option); + else + verbose(" Option: -%s %s\n", option, parameter); + + if (isCompoundOption(*option) && (parameter == NULL || parameter[0] == '\0')) + error(FATAL, "Missing parameter for \"%s\" option", option); + else + switch (*option) { + case '?': + processHelpOption("?", NULL); + exit(0); + break; + case 'a': + checkOptionOrder(option); + Option.append = TRUE; + break; +#ifdef DEBUG + case 'b': + if (atol(parameter) < 0) + error(FATAL, "-%s: Invalid line number", option); + Option.breakLine = atol(parameter); + break; + case 'D': + Option.debugLevel = strtol(parameter, NULL, 0); + if (debug(DEBUG_STATUS)) Option.verbose = TRUE; + break; +#endif + case 'B': + Option.backward = TRUE; + break; + case 'e': + checkOptionOrder(option); + setEtagsMode(); + break; + case 'f': + case 'o': + checkOptionOrder(option); + if (Option.tagFileName != NULL) { + error(WARNING, "-%s option specified more than once, last value used", + option); + freeString(&Option.tagFileName); + } else if (parameter[0] == '-' && parameter[1] != '\0') + error(FATAL, "output file name may not begin with a '-'"); + Option.tagFileName = stringCopy(parameter); + break; + case 'F': + Option.backward = FALSE; + break; + case 'h': + processHeaderListOption(*option, parameter); + break; + case 'I': + processIgnoreOption(parameter); + break; + case 'L': + if (Option.fileList != NULL) { + error(WARNING, "-%s option specified more than once, last value used", + option); + freeString(&Option.fileList); + } + Option.fileList = stringCopy(parameter); + break; + case 'n': + Option.locate = EX_LINENUM; + break; + case 'N': + Option.locate = EX_PATTERN; + break; + case 'R': +#ifdef RECURSE_SUPPORTED + Option.recurse = TRUE; +#else + error(WARNING, "-%s option not supported on this host", option); +#endif + break; + case 'u': + checkOptionOrder(option); + Option.sorted = SO_UNSORTED; + break; + case 'V': + Option.verbose = TRUE; + break; + case 'w': + /* silently ignored */ + break; + case 'x': + checkOptionOrder(option); + Option.xref = TRUE; + break; + default: + error(FATAL, "Unknown option: -%s", option); + break; + } +} + +extern void parseOption(cookedArgs *const args) { + Assert(!cArgOff(args)); + if (args->isOption) { + if (args->longOption) + processLongOption(args->item, args->parameter); + else { + const char *parameter = args->parameter; + while (*parameter == ' ') ++parameter; + processShortOption(args->item, parameter); + } + cArgForth(args); + } +} + +extern void parseOptions(cookedArgs *const args) { + NonOptionEncountered = FALSE; + while (!cArgOff(args) && cArgIsOption(args)) parseOption(args); + if (!cArgOff(args) && !cArgIsOption(args)) NonOptionEncountered = TRUE; +} + +static const char *CheckFile; +static boolean checkSameFile(const char *const fileName) { + return isSameFile(CheckFile, fileName); +} + +static boolean parseFileOptions(const char *const fileName) { + boolean fileFound = FALSE; + const char *const format = "Considering option file %s: %s\n"; + CheckFile = fileName; + if (stringListHasTest(OptionFiles, checkSameFile)) + verbose(format, fileName, "already considered"); + else { + FILE *const fp = fopen(fileName, "r"); + if (fp == NULL) + verbose(format, fileName, "not found"); + else { + cookedArgs *const args = cArgNewFromLineFile(fp); + vString *file = vStringNewInit(fileName); + stringListAdd(OptionFiles, file); + verbose(format, fileName, "reading..."); + parseOptions(args); + if (NonOptionEncountered) + error(WARNING, "Ignoring non-option in %s\n", fileName); + cArgDelete(args); + fclose(fp); + fileFound = TRUE; + } + } + return fileFound; +} + +/* Actions to be taken before reading any other options */ +extern void previewFirstOption(cookedArgs *const args) { + while (cArgIsOption(args)) { + if (strcmp(args->item, "V") == 0 || strcmp(args->item, "verbose") == 0) + parseOption(args); + else if (strcmp(args->item, "options") == 0 && + strcmp(args->parameter, "NONE") == 0) { + fprintf(stderr, "No options will be read from files or environment\n"); + SkipConfiguration = TRUE; + cArgForth(args); + } else + break; + } +} + +static void parseConfigurationFileOptionsInDirectoryWithLeafname( + const char *directory, const char *leafname) { + vString *const pathname = combinePathAndFile(directory, leafname); + parseFileOptions(vStringValue(pathname)); + vStringDelete(pathname); +} + +static void parseConfigurationFileOptionsInDirectory(const char *directory) { + parseConfigurationFileOptionsInDirectoryWithLeafname(directory, ".ctags"); +#ifdef MSDOS_STYLE_PATH + parseConfigurationFileOptionsInDirectoryWithLeafname(directory, "ctags.cnf"); +#endif +} + +static void parseConfigurationFileOptions(void) { + /* We parse .ctags on all systems, and additionally ctags.cnf on DOS. */ + const char *const home = getenv("HOME"); +#ifdef CUSTOM_CONFIGURATION_FILE + parseFileOptions(CUSTOM_CONFIGURATION_FILE); +#endif +#ifdef MSDOS_STYLE_PATH + parseFileOptions("/ctags.cnf"); +#endif + parseFileOptions("/etc/ctags.conf"); + parseFileOptions("/usr/local/etc/ctags.conf"); + if (home != NULL) { + parseConfigurationFileOptionsInDirectory(home); + } else { +#ifdef MSDOS_STYLE_PATH + /* + * Windows users don't usually set HOME. + * The OS sets HOMEDRIVE and HOMEPATH for them. + */ + const char *homeDrive = getenv("HOMEDRIVE"); + const char *homePath = getenv("HOMEPATH"); + if (homeDrive != NULL && homePath != NULL) { + vString *const windowsHome = vStringNew(); + vStringCatS(windowsHome, homeDrive); + vStringCatS(windowsHome, homePath); + parseConfigurationFileOptionsInDirectory(vStringValue(windowsHome)); + vStringDelete(windowsHome); + } +#endif + } + parseConfigurationFileOptionsInDirectory("."); +} + +static void parseEnvironmentOptions(void) { + const char *envOptions = NULL; + const char *var = NULL; + + if (Option.etags) { + var = ETAGS_ENVIRONMENT; + envOptions = getenv(var); + } + if (envOptions == NULL) { + var = CTAGS_ENVIRONMENT; + envOptions = getenv(var); + } + if (envOptions != NULL && envOptions[0] != '\0') { + cookedArgs *const args = cArgNewFromString(envOptions); + verbose("Reading options from $CTAGS\n"); + parseOptions(args); + cArgDelete(args); + if (NonOptionEncountered) + error(WARNING, "Ignoring non-option in %s variable", var); + } +} + +extern void readOptionConfiguration(void) { + if (!SkipConfiguration) { + parseConfigurationFileOptions(); + parseEnvironmentOptions(); + } +} + +/* + * Option initialization + */ + +extern void initOptions(void) { + OptionFiles = stringListNew(); + verbose("Setting option defaults\n"); + installHeaderListDefaults(); + verbose(" Installing default language mappings:\n"); + installLanguageMapDefaults(); + + /* always excluded by default */ + verbose(" Installing default exclude patterns:\n"); + processExcludeOption(NULL, "{arch}"); + processExcludeOption(NULL, ".arch-ids"); + processExcludeOption(NULL, ".arch-inventory"); + processExcludeOption(NULL, "autom4te.cache"); + processExcludeOption(NULL, "BitKeeper"); + processExcludeOption(NULL, ".bzr"); + processExcludeOption(NULL, ".bzrignore"); + processExcludeOption(NULL, "CVS"); + processExcludeOption(NULL, ".cvsignore"); + processExcludeOption(NULL, "_darcs"); + processExcludeOption(NULL, ".deps"); + processExcludeOption(NULL, "EIFGEN"); + processExcludeOption(NULL, ".git"); + processExcludeOption(NULL, ".hg"); + processExcludeOption(NULL, "PENDING"); + processExcludeOption(NULL, "RCS"); + processExcludeOption(NULL, "RESYNC"); + processExcludeOption(NULL, "SCCS"); + processExcludeOption(NULL, ".svn"); +} + +extern void freeOptionResources(void) { + freeString(&Option.tagFileName); + freeString(&Option.fileList); + freeString(&Option.filterTerminator); + + freeList(&Excluded); + freeList(&Option.ignore); + freeList(&Option.headerExt); + freeList(&Option.etagsInclude); + freeList(&OptionFiles); +} + +/* vi:set tabstop=4 shiftwidth=4: */ diff --git a/third_party/ctags/options.h b/third_party/ctags/options.h new file mode 100644 index 00000000..9af483b8 --- /dev/null +++ b/third_party/ctags/options.h @@ -0,0 +1,134 @@ +#ifndef _OPTIONS_H +#define _OPTIONS_H +#include "third_party/ctags/general.h" +/* must always come first */ +#include "third_party/ctags/args.h" +#include "third_party/ctags/parse.h" +#include "third_party/ctags/strlist.h" +#include "third_party/ctags/vstring.h" + +#if defined(OPTION_WRITE) || defined(VAXC) +#define CONST_OPTION +#else +#define CONST_OPTION const +#endif + +/* + * DATA DECLARATIONS + */ + +typedef enum { OPTION_NONE, OPTION_SHORT, OPTION_LONG } optionType; + +typedef struct sCookedArgs { + /* private */ + Arguments* args; + char* shortOptions; + char simple[2]; + boolean isOption; + boolean longOption; + const char* parameter; + /* public */ + char* item; +} cookedArgs; + +typedef enum eLocate { + EX_MIX, /* line numbers for defines, patterns otherwise */ + EX_LINENUM, /* -n only line numbers in tag file */ + EX_PATTERN /* -N only patterns in tag file */ +} exCmd; + +typedef enum sortType { SO_UNSORTED, SO_SORTED, SO_FOLDSORTED } sortType; + +struct sInclude { + boolean fileNames; /* include tags for source file names */ + boolean qualifiedTags; /* include tags for qualified class members */ + boolean fileScope; /* include tags of file scope only */ +}; + +struct sExtFields { /* extension field content control */ + boolean access; + boolean fileScope; + boolean implementation; + boolean inheritance; + boolean kind; + boolean kindKey; + boolean kindLong; + boolean language; + boolean lineNumber; + boolean scope; + boolean signature; + boolean typeRef; +}; + +/* This stores the command line options. + */ +typedef struct sOptionValues { + struct sInclude include; /* --extra extra tag inclusion */ + struct sExtFields extensionFields; /* --fields extension field control */ + stringList* ignore; /* -I name of file containing tokens to ignore */ + boolean append; /* -a append to "tags" file */ + boolean backward; /* -B regexp patterns search backwards */ + boolean etags; /* -e output Emacs style tags file */ + exCmd locate; /* --excmd EX command used to locate tag */ + boolean recurse; /* -R recurse into directories */ + sortType sorted; /* -u,--sort sort tags */ + boolean verbose; /* -V verbose */ + boolean xref; /* -x generate xref output instead */ + char* fileList; /* -L name of file containing names of files */ + char* tagFileName; /* -o name of tags file */ + stringList* headerExt; /* -h header extensions */ + stringList* etagsInclude; /* --etags-include list of TAGS files to include*/ + unsigned int tagFileFormat; /* --format tag file format (level) */ + boolean if0; /* --if0 examine code within "#if 0" branch */ + boolean kindLong; /* --kind-long */ + langType language; /* --lang specified language override */ + boolean followLinks; /* --link follow symbolic links? */ + boolean filter; /* --filter behave as filter: files in, tags out */ + char* filterTerminator; /* --filter-terminator string to output */ + boolean tagRelative; /* --tag-relative file paths relative to tag file */ + boolean printTotals; /* --totals print cumulative statistics */ + boolean lineDirectives; /* --linedirectives process #line directives */ +#ifdef DEBUG + long debugLevel; /* -D debugging output */ + unsigned long breakLine; /* -b source line at which to call lineBreak() */ +#endif +} optionValues; + +/* + * GLOBAL VARIABLES + */ +extern CONST_OPTION optionValues Option; + +/* + * FUNCTION PROTOTYPES + */ +extern void verbose(const char* const format, ...) __printf__(1, 2); +extern void freeList(stringList** const pString); +extern void setDefaultTagFileName(void); +extern void checkOptions(void); +extern boolean filesRequired(void); +extern void testEtagsInvocation(void); + +extern cookedArgs* cArgNewFromString(const char* string); +extern cookedArgs* cArgNewFromArgv(char* const* const argv); +extern cookedArgs* cArgNewFromFile(FILE* const fp); +extern cookedArgs* cArgNewFromLineFile(FILE* const fp); +extern void cArgDelete(cookedArgs* const current); +extern boolean cArgOff(cookedArgs* const current); +extern boolean cArgIsOption(cookedArgs* const current); +extern const char* cArgItem(cookedArgs* const current); +extern void cArgForth(cookedArgs* const current); + +extern boolean isExcludedFile(const char* const name); +extern boolean isIncludeFile(const char* const fileName); +extern boolean isIgnoreToken(const char* const name, + boolean* const pIgnoreParens, + const char** const replacement); +extern void parseOption(cookedArgs* const cargs); +extern void parseOptions(cookedArgs* const cargs); +extern void previewFirstOption(cookedArgs* const cargs); +extern void readOptionConfiguration(void); +extern void initOptions(void); +extern void freeOptionResources(void); + +#endif /* _OPTIONS_H */ diff --git a/third_party/ctags/parse.c b/third_party/ctags/parse.c new file mode 100644 index 00000000..940b7a40 --- /dev/null +++ b/third_party/ctags/parse.c @@ -0,0 +1,574 @@ +/* + * $Id: parse.c 597 2007-07-31 05:35:30Z dhiebert $ + * + * Copyright (c) 1996-2003, Darren Hiebert + * + * This source code is released for free distribution under the terms of the + * GNU General Public License. + * + * This module contains functions for managing source languages and + * dispatching files to the appropriate language parser. + */ +#include "third_party/ctags/general.h" +/* must always come first */ +#include "third_party/ctags/debug.h" +#include "third_party/ctags/entry.h" +#include "third_party/ctags/main.h" +#define OPTION_WRITE +#include "third_party/ctags/options.h" +#include "third_party/ctags/parsers.h" +#include "third_party/ctags/read.h" +#include "third_party/ctags/routines.h" +#include "third_party/ctags/vstring.h" + +/* + * DATA DEFINITIONS + */ +static parserDefinitionFunc* BuiltInParsers[] = {PARSER_LIST}; +static parserDefinition** LanguageTable = NULL; +static unsigned int LanguageCount = 0; + +/* + * FUNCTION DEFINITIONS + */ + +extern void makeSimpleTag(const vString* const name, kindOption* const kinds, + const int kind) { + if (kinds[kind].enabled && name != NULL && vStringLength(name) > 0) { + tagEntryInfo e; + initTagEntry(&e, vStringValue(name)); + + e.kindName = kinds[kind].name; + e.kind = kinds[kind].letter; + + makeTagEntry(&e); + } +} + +/* + * parserDescription mapping management + */ + +extern parserDefinition* parserNew(const char* name) { + parserDefinition* result = xCalloc(1, parserDefinition); + result->name = eStrdup(name); + return result; +} + +extern const char* getLanguageName(const langType language) { + const char* result; + if (language == LANG_IGNORE) + result = "unknown"; + else { + Assert(0 <= language && language < (int)LanguageCount); + result = LanguageTable[language]->name; + } + return result; +} + +extern langType getNamedLanguage(const char* const name) { + langType result = LANG_IGNORE; + unsigned int i; + Assert(name != NULL); + for (i = 0; i < LanguageCount && result == LANG_IGNORE; ++i) { + const parserDefinition* const lang = LanguageTable[i]; + if (lang->name != NULL) + if (strcasecmp(name, lang->name) == 0) result = i; + } + return result; +} + +static langType getExtensionLanguage(const char* const extension) { + langType result = LANG_IGNORE; + unsigned int i; + for (i = 0; i < LanguageCount && result == LANG_IGNORE; ++i) { + stringList* const exts = LanguageTable[i]->currentExtensions; + if (exts != NULL && stringListExtensionMatched(exts, extension)) result = i; + } + return result; +} + +static langType getPatternLanguage(const char* const fileName) { + langType result = LANG_IGNORE; + const char* base = baseFilename(fileName); + unsigned int i; + for (i = 0; i < LanguageCount && result == LANG_IGNORE; ++i) { + stringList* const ptrns = LanguageTable[i]->currentPatterns; + if (ptrns != NULL && stringListFileMatched(ptrns, base)) result = i; + } + return result; +} + +#ifdef SYS_INTERPRETER + +/* The name of the language interpreter, either directly or as the argument + * to "env". + */ +static vString* determineInterpreter(const char* const cmd) { + vString* const interpreter = vStringNew(); + const char* p = cmd; + do { + vStringClear(interpreter); + for (; isspace((int)*p); ++p) + ; /* no-op */ + for (; *p != '\0' && !isspace((int)*p); ++p) + vStringPut(interpreter, (int)*p); + vStringTerminate(interpreter); + } while (strcmp(vStringValue(interpreter), "env") == 0); + return interpreter; +} + +static langType getInterpreterLanguage(const char* const fileName) { + langType result = LANG_IGNORE; + FILE* const fp = fopen(fileName, "r"); + if (fp != NULL) { + vString* const vLine = vStringNew(); + const char* const line = readLine(vLine, fp); + if (line != NULL && line[0] == '#' && line[1] == '!') { + const char* const lastSlash = strrchr(line, '/'); + const char* const cmd = lastSlash != NULL ? lastSlash + 1 : line + 2; + vString* const interpreter = determineInterpreter(cmd); + result = getExtensionLanguage(vStringValue(interpreter)); + if (result == LANG_IGNORE) + result = getNamedLanguage(vStringValue(interpreter)); + vStringDelete(interpreter); + } + vStringDelete(vLine); + fclose(fp); + } + return result; +} + +#endif + +extern langType getFileLanguage(const char* const fileName) { + langType language = Option.language; + if (language == LANG_AUTO) { + language = getExtensionLanguage(fileExtension(fileName)); + if (language == LANG_IGNORE) language = getPatternLanguage(fileName); +#ifdef SYS_INTERPRETER + if (language == LANG_IGNORE) { + fileStatus* status = eStat(fileName); + if (status->isExecutable) language = getInterpreterLanguage(fileName); + } +#endif + } + return language; +} + +extern void printLanguageMap(const langType language) { + boolean first = TRUE; + unsigned int i; + stringList* map = LanguageTable[language]->currentPatterns; + Assert(0 <= language && language < (int)LanguageCount); + for (i = 0; map != NULL && i < stringListCount(map); ++i) { + printf("%s(%s)", (first ? "" : " "), vStringValue(stringListItem(map, i))); + first = FALSE; + } + map = LanguageTable[language]->currentExtensions; + for (i = 0; map != NULL && i < stringListCount(map); ++i) { + printf("%s.%s", (first ? "" : " "), vStringValue(stringListItem(map, i))); + first = FALSE; + } +} + +extern void installLanguageMapDefault(const langType language) { + parserDefinition* lang; + Assert(0 <= language && language < (int)LanguageCount); + lang = LanguageTable[language]; + if (lang->currentPatterns != NULL) stringListDelete(lang->currentPatterns); + if (lang->currentExtensions != NULL) + stringListDelete(lang->currentExtensions); + + if (lang->patterns == NULL) + lang->currentPatterns = stringListNew(); + else { + lang->currentPatterns = stringListNewFromArgv(lang->patterns); + } + if (lang->extensions == NULL) + lang->currentExtensions = stringListNew(); + else { + lang->currentExtensions = stringListNewFromArgv(lang->extensions); + } + if (Option.verbose) printLanguageMap(language); + verbose("\n"); +} + +extern void installLanguageMapDefaults(void) { + unsigned int i; + for (i = 0; i < LanguageCount; ++i) { + verbose(" %s: ", getLanguageName(i)); + installLanguageMapDefault(i); + } +} + +extern void clearLanguageMap(const langType language) { + Assert(0 <= language && language < (int)LanguageCount); + stringListClear(LanguageTable[language]->currentPatterns); + stringListClear(LanguageTable[language]->currentExtensions); +} + +extern void addLanguagePatternMap(const langType language, const char* ptrn) { + vString* const str = vStringNewInit(ptrn); + parserDefinition* lang; + Assert(0 <= language && language < (int)LanguageCount); + lang = LanguageTable[language]; + if (lang->currentPatterns == NULL) lang->currentPatterns = stringListNew(); + stringListAdd(lang->currentPatterns, str); +} + +extern boolean removeLanguageExtensionMap(const char* const extension) { + boolean result = FALSE; + unsigned int i; + for (i = 0; i < LanguageCount && !result; ++i) { + stringList* const exts = LanguageTable[i]->currentExtensions; + if (exts != NULL && stringListRemoveExtension(exts, extension)) { + verbose(" (removed from %s)", getLanguageName(i)); + result = TRUE; + } + } + return result; +} + +extern void addLanguageExtensionMap(const langType language, + const char* extension) { + vString* const str = vStringNewInit(extension); + Assert(0 <= language && language < (int)LanguageCount); + removeLanguageExtensionMap(extension); + stringListAdd(LanguageTable[language]->currentExtensions, str); +} + +extern void enableLanguage(const langType language, const boolean state) { + Assert(0 <= language && language < (int)LanguageCount); + LanguageTable[language]->enabled = state; +} + +extern void enableLanguages(const boolean state) { + unsigned int i; + for (i = 0; i < LanguageCount; ++i) enableLanguage(i, state); +} + +static void initializeParsers(void) { + unsigned int i; + for (i = 0; i < LanguageCount; ++i) + if (LanguageTable[i]->initialize != NULL) + (LanguageTable[i]->initialize)((langType)i); +} + +extern void initializeParsing(void) { + unsigned int builtInCount; + unsigned int i; + + builtInCount = sizeof(BuiltInParsers) / sizeof(BuiltInParsers[0]); + LanguageTable = xMalloc(builtInCount, parserDefinition*); + + verbose("Installing parsers: "); + for (i = 0; i < builtInCount; ++i) { + parserDefinition* const def = (*BuiltInParsers[i])(); + if (def != NULL) { + boolean accepted = FALSE; + if (def->name == NULL || def->name[0] == '\0') + error(FATAL, "parser definition must contain name\n"); + else if (def->regex) { +#ifdef HAVE_REGEX + def->parser = findRegexTags; + accepted = TRUE; +#endif + } else if ((def->parser == NULL) == (def->parser2 == NULL)) + error(FATAL, + "%s parser definition must define one and only one parsing " + "routine\n", + def->name); + else + accepted = TRUE; + if (accepted) { + verbose("%s%s", i > 0 ? ", " : "", def->name); + def->id = LanguageCount++; + LanguageTable[def->id] = def; + } + } + } + verbose("\n"); + enableLanguages(TRUE); + initializeParsers(); +} + +extern void freeParserResources(void) { + unsigned int i; + for (i = 0; i < LanguageCount; ++i) { + parserDefinition* const lang = LanguageTable[i]; + freeList(&lang->currentPatterns); + freeList(&lang->currentExtensions); + eFree(lang->name); + lang->name = NULL; + eFree(lang); + } + if (LanguageTable != NULL) eFree(LanguageTable); + LanguageTable = NULL; + LanguageCount = 0; +} + +/* + * Option parsing + */ + +extern void processLanguageDefineOption( + const char* const option, const char* const parameter __unused__) { +#ifdef HAVE_REGEX + if (parameter[0] == '\0') + error(WARNING, "No language specified for \"%s\" option", option); + else if (getNamedLanguage(parameter) != LANG_IGNORE) + error(WARNING, "Language \"%s\" already defined", parameter); + else { + unsigned int i = LanguageCount++; + parserDefinition* const def = parserNew(parameter); + def->parser = findRegexTags; + def->currentPatterns = stringListNew(); + def->currentExtensions = stringListNew(); + def->regex = TRUE; + def->enabled = TRUE; + def->id = i; + LanguageTable = xRealloc(LanguageTable, i + 1, parserDefinition*); + LanguageTable[i] = def; + } +#else + error(WARNING, "regex support not available; required for --%s option", + option); +#endif +} + +static kindOption* langKindOption(const langType language, const int flag) { + unsigned int i; + kindOption* result = NULL; + const parserDefinition* lang; + Assert(0 <= language && language < (int)LanguageCount); + lang = LanguageTable[language]; + for (i = 0; i < lang->kindCount && result == NULL; ++i) + if (lang->kinds[i].letter == flag) result = &lang->kinds[i]; + return result; +} + +static void disableLanguageKinds(const langType language) { + const parserDefinition* lang; + Assert(0 <= language && language < (int)LanguageCount); + lang = LanguageTable[language]; + if (lang->regex) + disableRegexKinds(language); + else { + unsigned int i; + for (i = 0; i < lang->kindCount; ++i) lang->kinds[i].enabled = FALSE; + } +} + +static boolean enableLanguageKind(const langType language, const int kind, + const boolean mode) { + boolean result = FALSE; + if (LanguageTable[language]->regex) + result = enableRegexKind(language, kind, mode); + else { + kindOption* const opt = langKindOption(language, kind); + if (opt != NULL) { + opt->enabled = mode; + result = TRUE; + } + } + return result; +} + +static void processLangKindOption(const langType language, + const char* const option, + const char* const parameter) { + const char* p = parameter; + boolean mode = TRUE; + int c; + + Assert(0 <= language && language < (int)LanguageCount); + if (*p != '+' && *p != '-') disableLanguageKinds(language); + while ((c = *p++) != '\0') switch (c) { + case '+': + mode = TRUE; + break; + case '-': + mode = FALSE; + break; + default: + if (!enableLanguageKind(language, c, mode)) + error(WARNING, "Unsupported parameter '%c' for --%s option", c, + option); + break; + } +} + +extern boolean processKindOption(const char* const option, + const char* const parameter) { + boolean handled = FALSE; + const char* const dash = strchr(option, '-'); + if (dash != NULL && + (strcmp(dash + 1, "kinds") == 0 || strcmp(dash + 1, "types") == 0)) { + langType language; + vString* langName = vStringNew(); + vStringNCopyS(langName, option, dash - option); + language = getNamedLanguage(vStringValue(langName)); + if (language == LANG_IGNORE) + error(WARNING, "Unknown language \"%s\" in \"%s\" option", + vStringValue(langName), option); + else + processLangKindOption(language, option, parameter); + vStringDelete(langName); + handled = TRUE; + } + return handled; +} + +static void printLanguageKind(const kindOption* const kind, boolean indent) { + const char* const indentation = indent ? " " : ""; + printf("%s%c %s%s\n", indentation, kind->letter, + kind->description != NULL ? kind->description + : (kind->name != NULL ? kind->name : ""), + kind->enabled ? "" : " [off]"); +} + +static void printKinds(langType language, boolean indent) { + const parserDefinition* lang; + Assert(0 <= language && language < (int)LanguageCount); + lang = LanguageTable[language]; + if (lang->kinds != NULL || lang->regex) { + unsigned int i; + for (i = 0; i < lang->kindCount; ++i) + printLanguageKind(lang->kinds + i, indent); + printRegexKinds(language, indent); + } +} + +extern void printLanguageKinds(const langType language) { + if (language == LANG_AUTO) { + unsigned int i; + for (i = 0; i < LanguageCount; ++i) { + const parserDefinition* const lang = LanguageTable[i]; + printf("%s%s\n", lang->name, lang->enabled ? "" : " [disabled]"); + printKinds(i, TRUE); + } + } else + printKinds(language, FALSE); +} + +static void printMaps(const langType language) { + const parserDefinition* lang; + unsigned int i; + Assert(0 <= language && language < (int)LanguageCount); + lang = LanguageTable[language]; + printf("%-8s", lang->name); + if (lang->currentExtensions != NULL) + for (i = 0; i < stringListCount(lang->currentExtensions); ++i) + printf(" *.%s", vStringValue(stringListItem(lang->currentExtensions, i))); + if (lang->currentPatterns != NULL) + for (i = 0; i < stringListCount(lang->currentPatterns); ++i) + printf(" %s", vStringValue(stringListItem(lang->currentPatterns, i))); + putchar('\n'); +} + +extern void printLanguageMaps(const langType language) { + if (language == LANG_AUTO) { + unsigned int i; + for (i = 0; i < LanguageCount; ++i) printMaps(i); + } else + printMaps(language); +} + +static void printLanguage(const langType language) { + const parserDefinition* lang; + Assert(0 <= language && language < (int)LanguageCount); + lang = LanguageTable[language]; + if (lang->kinds != NULL || lang->regex) + printf("%s%s\n", lang->name, lang->enabled ? "" : " [disabled]"); +} + +extern void printLanguageList(void) { + unsigned int i; + for (i = 0; i < LanguageCount; ++i) printLanguage(i); +} + +/* + * File parsing + */ + +static void makeFileTag(const char* const fileName) { + if (Option.include.fileNames) { + tagEntryInfo tag; + initTagEntry(&tag, baseFilename(fileName)); + + tag.isFileEntry = TRUE; + tag.lineNumberEntry = TRUE; + tag.lineNumber = 1; + tag.kindName = "file"; + tag.kind = 'F'; + + makeTagEntry(&tag); + } +} + +static boolean createTagsForFile(const char* const fileName, + const langType language, + const unsigned int passCount) { + boolean retried = FALSE; + Assert(0 <= language && language < (int)LanguageCount); + if (fileOpen(fileName, language)) { + const parserDefinition* const lang = LanguageTable[language]; + if (Option.etags) beginEtagsFile(); + + makeFileTag(fileName); + + if (lang->parser != NULL) + lang->parser(); + else if (lang->parser2 != NULL) + retried = lang->parser2(passCount); + + if (Option.etags) endEtagsFile(getSourceFileTagPath()); + + fileClose(); + } + + return retried; +} + +static boolean createTagsWithFallback(const char* const fileName, + const langType language) { + const unsigned long numTags = TagFile.numTags.added; + fpos_t tagFilePosition; + unsigned int passCount = 0; + boolean tagFileResized = FALSE; + + fgetpos(TagFile.fp, &tagFilePosition); + while (createTagsForFile(fileName, language, ++passCount)) { + /* Restore prior state of tag file. + */ + fsetpos(TagFile.fp, &tagFilePosition); + TagFile.numTags.added = numTags; + tagFileResized = TRUE; + } + return tagFileResized; +} + +extern boolean parseFile(const char* const fileName) { + boolean tagFileResized = FALSE; + langType language = Option.language; + if (Option.language == LANG_AUTO) language = getFileLanguage(fileName); + Assert(language != LANG_AUTO); + if (language == LANG_IGNORE) + verbose("ignoring %s (unknown language)\n", fileName); + else if (!LanguageTable[language]->enabled) + verbose("ignoring %s (language disabled)\n", fileName); + else { + if (Option.filter) openTagFile(); + + tagFileResized = createTagsWithFallback(fileName, language); + + if (Option.filter) closeTagFile(tagFileResized); + addTotals(1, 0L, 0L); + + return tagFileResized; + } + return tagFileResized; +} + +/* vi:set tabstop=4 shiftwidth=4 nowrap: */ diff --git a/third_party/ctags/parse.h b/third_party/ctags/parse.h new file mode 100644 index 00000000..4f8b1374 --- /dev/null +++ b/third_party/ctags/parse.h @@ -0,0 +1,125 @@ +#ifndef _PARSE_H +#define _PARSE_H +#include "third_party/ctags/general.h" +/* must always come first */ +#include "third_party/ctags/parsers.h" +#include "third_party/ctags/strlist.h" + +/* + * MACROS + */ +#define KIND_COUNT(kindTable) (sizeof(kindTable) / sizeof(kindOption)) + +#define LANG_AUTO (-1) +#define LANG_IGNORE (-2) + +/* + * DATA DECLARATIONS + */ +typedef int langType; + +typedef void (*createRegexTag)(const vString* const name); +typedef void (*simpleParser)(void); +typedef boolean (*rescanParser)(const unsigned int passCount); +typedef void (*parserInitialize)(langType language); + +typedef struct sKindOption { + boolean enabled; /* are tags for kind enabled? */ + int letter; /* kind letter */ + const char* name; /* kind name */ + const char* description; /* displayed in --help output */ +} kindOption; + +typedef struct { + /* defined by parser */ + char* name; /* name of language */ + kindOption* kinds; /* tag kinds handled by parser */ + unsigned int kindCount; /* size of `kinds' list */ + const char* const* extensions; /* list of default extensions */ + const char* const* patterns; /* list of default file name patterns */ + parserInitialize initialize; /* initialization routine, if needed */ + simpleParser parser; /* simple parser (common case) */ + rescanParser parser2; /* rescanning parser (unusual case) */ + boolean regex; /* is this a regex parser? */ + + /* used internally */ + unsigned int id; /* id assigned to language */ + boolean enabled; /* currently enabled? */ + stringList* currentPatterns; /* current list of file name patterns */ + stringList* currentExtensions; /* current list of extensions */ +} parserDefinition; + +typedef parserDefinition*(parserDefinitionFunc)(void); + +typedef struct { + size_t start; /* character index in line where match starts */ + size_t length; /* length of match */ +} regexMatch; + +typedef void (*regexCallback)(const char* line, const regexMatch* matches, + unsigned int count); + +/* + * FUNCTION PROTOTYPES + */ + +/* Each parsers' definition function is called. The routine is expected to + * return a structure allocated using parserNew(). This structure must, + * at minimum, set the `parser' field. + */ +extern parserDefinitionFunc PARSER_LIST; + +/* Legacy interface */ +extern boolean includingDefineTags(void); + +/* Language processing and parsing */ +extern void makeSimpleTag(const vString* const name, kindOption* const kinds, + const int kind); +extern parserDefinition* parserNew(const char* name); +extern const char* getLanguageName(const langType language); +extern langType getNamedLanguage(const char* const name); +extern langType getFileLanguage(const char* const fileName); +extern void installLanguageMapDefault(const langType language); +extern void installLanguageMapDefaults(void); +extern void clearLanguageMap(const langType language); +extern boolean removeLanguageExtensionMap(const char* const extension); +extern void addLanguageExtensionMap(const langType language, + const char* extension); +extern void addLanguagePatternMap(const langType language, const char* ptrn); +extern void printLanguageMap(const langType language); +extern void printLanguageMaps(const langType language); +extern void enableLanguages(const boolean state); +extern void enableLanguage(const langType language, const boolean state); +extern void initializeParsing(void); +extern void freeParserResources(void); +extern void processLanguageDefineOption(const char* const option, + const char* const parameter); +extern boolean processKindOption(const char* const option, + const char* const parameter); +extern void printKindOptions(void); +extern void printLanguageKinds(const langType language); +extern void printLanguageList(void); +extern boolean parseFile(const char* const fileName); + +/* Regex interface */ +#ifdef HAVE_REGEX +extern void findRegexTags(void); +extern boolean matchRegex(const vString* const line, const langType language); +#endif +extern boolean processRegexOption(const char* const option, + const char* const parameter); +extern void addLanguageRegex(const langType language, const char* const regex); +extern void addTagRegex(const langType language, const char* const regex, + const char* const name, const char* const kinds, + const char* const flags); +extern void addCallbackRegex(const langType language, const char* const regex, + const char* const flags, + const regexCallback callback); +extern void disableRegexKinds(const langType language); +extern boolean enableRegexKind(const langType language, const int kind, + const boolean mode); +extern void printRegexKinds(const langType language, boolean indent); +extern void freeRegexResources(void); +extern void checkRegex(void); + +#endif /* _PARSE_H */ diff --git a/third_party/ctags/parsers.h b/third_party/ctags/parsers.h new file mode 100644 index 00000000..bfc10f8b --- /dev/null +++ b/third_party/ctags/parsers.h @@ -0,0 +1,15 @@ +#ifndef _PARSERS_H +#define _PARSERS_H + +/* Add the name of any new parser definition function here */ +#define PARSER_LIST \ + AntParser, AsmParser, AspParser, AwkParser, BasicParser, BetaParser, \ + CParser, CppParser, CsharpParser, CobolParser, DosBatchParser, \ + EiffelParser, ErlangParser, FlexParser, FortranParser, GoParser, \ + HtmlParser, JavaParser, JavaScriptParser, LispParser, LuaParser, \ + MakefileParser, MatLabParser, ObjcParser, OcamlParser, PascalParser, \ + PerlParser, PhpParser, PythonParser, RexxParser, RubyParser, \ + SchemeParser, ShParser, SlangParser, SmlParser, SqlParser, TclParser, \ + TexParser, VeraParser, VerilogParser, VhdlParser, VimParser, YaccParser + +#endif /* _PARSERS_H */ diff --git a/third_party/ctags/pascal.c b/third_party/ctags/pascal.c new file mode 100644 index 00000000..3511fbe5 --- /dev/null +++ b/third_party/ctags/pascal.c @@ -0,0 +1,222 @@ +/* + * $Id: pascal.c 536 2007-06-02 06:09:00Z elliotth $ + * + * Copyright (c) 2001-2002, Darren Hiebert + * + * This source code is released for free distribution under the terms of the + * GNU General Public License. + * + * This module contains functions for generating tags for the Pascal language, + * including some extensions for Object Pascal. + */ +#include "third_party/ctags/general.h" +/* must always come first */ +#include "third_party/ctags/entry.h" +#include "third_party/ctags/parse.h" +#include "third_party/ctags/read.h" +#include "third_party/ctags/vstring.h" + +/* + * DATA DEFINITIONS + */ +typedef enum { K_FUNCTION, K_PROCEDURE } pascalKind; + +static kindOption PascalKinds[] = {{TRUE, 'f', "function", "functions"}, + {TRUE, 'p', "procedure", "procedures"}}; + +/* + * FUNCTION DEFINITIONS + */ + +static void createPascalTag(tagEntryInfo* const tag, const vString* const name, + const int kind) { + if (PascalKinds[kind].enabled && name != NULL && vStringLength(name) > 0) { + initTagEntry(tag, vStringValue(name)); + tag->kindName = PascalKinds[kind].name; + tag->kind = PascalKinds[kind].letter; + } else + initTagEntry(tag, NULL); +} + +static void makePascalTag(const tagEntryInfo* const tag) { + if (tag->name != NULL) makeTagEntry(tag); +} + +static const unsigned char* dbp; + +#define starttoken(c) (isalpha((int)c) || (int)c == '_') +#define intoken(c) (isalnum((int)c) || (int)c == '_' || (int)c == '.') +#define endtoken(c) (!intoken(c) && !isdigit((int)c)) + +static boolean tail(const char* cp) { + boolean result = FALSE; + register int len = 0; + + while (*cp != '\0' && tolower((int)*cp) == tolower((int)dbp[len])) + cp++, len++; + if (*cp == '\0' && !intoken(dbp[len])) { + dbp += len; + result = TRUE; + } + return result; +} + +/* Algorithm adapted from from GNU etags. + * Locates tags for procedures & functions. Doesn't do any type- or + * var-definitions. It does look for the keyword "extern" or "forward" + * immediately following the procedure statement; if found, the tag is + * skipped. + */ +static void findPascalTags(void) { + vString* name = vStringNew(); + tagEntryInfo tag; + pascalKind kind = K_FUNCTION; + /* each of these flags is TRUE iff: */ + boolean incomment = FALSE; /* point is inside a comment */ + int comment_char = '\0'; /* type of current comment */ + boolean inquote = FALSE; /* point is inside '..' string */ + boolean get_tagname = FALSE; /* point is after PROCEDURE/FUNCTION + keyword, so next item = potential tag */ + boolean found_tag = FALSE; /* point is after a potential tag */ + boolean inparms = FALSE; /* point is within parameter-list */ + boolean verify_tag = FALSE; + /* point has passed the parm-list, so the next token will determine + * whether this is a FORWARD/EXTERN to be ignored, or whether it is a + * real tag + */ + + dbp = fileReadLine(); + while (dbp != NULL) { + int c = *dbp++; + + if (c == '\0') /* if end of line */ + { + dbp = fileReadLine(); + if (dbp == NULL || *dbp == '\0') continue; + if (!((found_tag && verify_tag) || get_tagname)) c = *dbp++; + /* only if don't need *dbp pointing to the beginning of + * the name of the procedure or function + */ + } + if (incomment) { + if (comment_char == '{' && c == '}') + incomment = FALSE; + else if (comment_char == '(' && c == '*' && *dbp == ')') { + dbp++; + incomment = FALSE; + } + continue; + } else if (inquote) { + if (c == '\'') inquote = FALSE; + continue; + } else + switch (c) { + case '\'': + inquote = TRUE; /* found first quote */ + continue; + case '{': /* found open { comment */ + incomment = TRUE; + comment_char = c; + continue; + case '(': + if (*dbp == '*') /* found open (* comment */ + { + incomment = TRUE; + comment_char = c; + dbp++; + } else if (found_tag) /* found '(' after tag, i.e., parm-list */ + inparms = TRUE; + continue; + case ')': /* end of parms list */ + if (inparms) inparms = FALSE; + continue; + case ';': + if (found_tag && !inparms) /* end of proc or fn stmt */ + { + verify_tag = TRUE; + break; + } + continue; + } + if (found_tag && verify_tag && *dbp != ' ') { + /* check if this is an "extern" declaration */ + if (*dbp == '\0') continue; + if (tolower((int)*dbp == 'e')) { + if (tail("extern")) /* superfluous, really! */ + { + found_tag = FALSE; + verify_tag = FALSE; + } + } else if (tolower((int)*dbp) == 'f') { + if (tail("forward")) /* check for forward reference */ + { + found_tag = FALSE; + verify_tag = FALSE; + } + } + if (found_tag && verify_tag) /* not external proc, so make tag */ + { + found_tag = FALSE; + verify_tag = FALSE; + makePascalTag(&tag); + continue; + } + } + if (get_tagname) /* grab name of proc or fn */ + { + const unsigned char* cp; + + if (*dbp == '\0') continue; + + /* grab block name */ + while (isspace((int)*dbp)) ++dbp; + for (cp = dbp; *cp != '\0' && !endtoken(*cp); cp++) continue; + vStringNCopyS(name, (const char*)dbp, cp - dbp); + createPascalTag(&tag, name, kind); + dbp = cp; /* set dbp to e-o-token */ + get_tagname = FALSE; + found_tag = TRUE; + /* and proceed to check for "extern" */ + } else if (!incomment && !inquote && !found_tag) { + switch (tolower((int)c)) { + case 'c': + if (tail("onstructor")) { + get_tagname = TRUE; + kind = K_PROCEDURE; + } + break; + case 'd': + if (tail("estructor")) { + get_tagname = TRUE; + kind = K_PROCEDURE; + } + break; + case 'p': + if (tail("rocedure")) { + get_tagname = TRUE; + kind = K_PROCEDURE; + } + break; + case 'f': + if (tail("unction")) { + get_tagname = TRUE; + kind = K_FUNCTION; + } + break; + } + } /* while not eof */ + } + vStringDelete(name); +} + +extern parserDefinition* PascalParser(void) { + static const char* const extensions[] = {"p", "pas", NULL}; + parserDefinition* def = parserNew("Pascal"); + def->extensions = extensions; + def->kinds = PascalKinds; + def->kindCount = KIND_COUNT(PascalKinds); + def->parser = findPascalTags; + return def; +} + +/* vi:set tabstop=4 shiftwidth=4: */ diff --git a/third_party/ctags/perl.c b/third_party/ctags/perl.c new file mode 100644 index 00000000..a9fb7ecf --- /dev/null +++ b/third_party/ctags/perl.c @@ -0,0 +1,327 @@ +/* + * $Id: perl.c 601 2007-08-02 04:45:16Z perlguy0 $ + * + * Copyright (c) 2000-2003, Darren Hiebert + * + * This source code is released for free distribution under the terms of the + * GNU General Public License. + * + * This module contains functions for generating tags for PERL language + * files. + */ +#include "third_party/ctags/general.h" +/* must always come first */ +#include "third_party/ctags/entry.h" +#include "third_party/ctags/options.h" +#include "third_party/ctags/read.h" +#include "third_party/ctags/routines.h" +#include "third_party/ctags/vstring.h" + +#define TRACE_PERL_C 0 +#define TRACE \ + if (TRACE_PERL_C) printf("perl.c:%d: ", __LINE__), printf + +/* + * DATA DEFINITIONS + */ +typedef enum { + K_NONE = -1, + K_CONSTANT, + K_FORMAT, + K_LABEL, + K_PACKAGE, + K_SUBROUTINE, + K_SUBROUTINE_DECLARATION +} perlKind; + +static kindOption PerlKinds[] = { + {TRUE, 'c', "constant", "constants"}, + {TRUE, 'f', "format", "formats"}, + {TRUE, 'l', "label", "labels"}, + {TRUE, 'p', "package", "packages"}, + {TRUE, 's', "subroutine", "subroutines"}, + {FALSE, 'd', "subroutine declaration", "subroutine declarations"}, +}; + +/* + * FUNCTION DEFINITIONS + */ + +static boolean isIdentifier1(int c) { + return (boolean)(isalpha(c) || c == '_'); +} + +static boolean isIdentifier(int c) { + return (boolean)(isalnum(c) || c == '_'); +} + +static boolean isPodWord(const char *word) { + boolean result = FALSE; + if (isalpha(*word)) { + const char *const pods[] = {"head1", "head2", "head3", "head4", + "over", "item", "back", "pod", + "begin", "end", "for"}; + const size_t count = sizeof(pods) / sizeof(pods[0]); + const char *white = strpbrk(word, " \t"); + const size_t len = (white != NULL) ? (size_t)(white - word) : strlen(word); + char *const id = (char *)eMalloc(len + 1); + size_t i; + strncpy(id, word, len); + id[len] = '\0'; + for (i = 0; i < count && !result; ++i) { + if (strcmp(id, pods[i]) == 0) result = TRUE; + } + eFree(id); + } + return result; +} + +/* + * Perl subroutine declaration may look like one of the following: + * + * sub abc; + * sub abc :attr; + * sub abc (proto); + * sub abc (proto) :attr; + * + * Note that there may be more than one attribute. Attributes may + * have things in parentheses (they look like arguments). Anything + * inside of those parentheses goes. Prototypes may contain semi-colons. + * The matching end when we encounter (outside of any parentheses) either + * a semi-colon (that'd be a declaration) or an left curly brace + * (definition). + * + * This is pretty complicated parsing (plus we all know that only perl can + * parse Perl), so we are only promising best effort here. + * + * If we can't determine what this is (due to a file ending, for example), + * we will return FALSE. + */ +static boolean isSubroutineDeclaration(const unsigned char *cp) { + boolean attr = FALSE; + int nparens = 0; + + do { + for (; *cp; ++cp) { + SUB_DECL_SWITCH: + switch (*cp) { + case ':': + if (nparens) + break; + else if (TRUE == attr) + return FALSE; /* Invalid attribute name */ + else + attr = TRUE; + break; + case '(': + ++nparens; + break; + case ')': + --nparens; + break; + case ' ': + case '\t': + break; + case ';': + if (!nparens) return TRUE; + case '{': + if (!nparens) return FALSE; + default: + if (attr) { + if (isIdentifier1(*cp)) { + cp++; + while (isIdentifier(*cp)) cp++; + attr = FALSE; + goto SUB_DECL_SWITCH; /* Instead of --cp; */ + } else { + return FALSE; + } + } else if (nparens) { + break; + } else { + return FALSE; + } + } + } + } while (NULL != (cp = fileReadLine())); + + return FALSE; +} + +/* Algorithm adapted from from GNU etags. + * Perl support by Bart Robinson + * Perl sub names: look for /^ [ \t\n]sub [ \t\n]+ [^ \t\n{ (]+/ + */ +static void findPerlTags(void) { + vString *name = vStringNew(); + vString *package = NULL; + boolean skipPodDoc = FALSE; + const unsigned char *line; + + while ((line = fileReadLine()) != NULL) { + boolean spaceRequired = FALSE; + boolean qualified = FALSE; + const unsigned char *cp = line; + perlKind kind = K_NONE; + tagEntryInfo e; + + if (skipPodDoc) { + if (strncmp((const char *)line, "=cut", (size_t)4) == 0) + skipPodDoc = FALSE; + continue; + } else if (line[0] == '=') { + skipPodDoc = isPodWord((const char *)line + 1); + continue; + } else if (strcmp((const char *)line, "__DATA__") == 0) + break; + else if (strcmp((const char *)line, "__END__") == 0) + break; + else if (line[0] == '#') + continue; + + while (isspace(*cp)) cp++; + + if (strncmp((const char *)cp, "sub", (size_t)3) == 0) { + TRACE("this looks like a sub\n"); + cp += 3; + kind = K_SUBROUTINE; + spaceRequired = TRUE; + qualified = TRUE; + } else if (strncmp((const char *)cp, "use", (size_t)3) == 0) { + cp += 3; + if (!isspace(*cp)) continue; + while (*cp && isspace(*cp)) ++cp; + if (strncmp((const char *)cp, "constant", (size_t)8) != 0) continue; + cp += 8; + kind = K_CONSTANT; + spaceRequired = TRUE; + qualified = TRUE; + } else if (strncmp((const char *)cp, "package", (size_t)7) == 0) { + /* This will point to space after 'package' so that a tag + can be made */ + const unsigned char *space = cp += 7; + + if (package == NULL) + package = vStringNew(); + else + vStringClear(package); + while (isspace(*cp)) cp++; + while ((int)*cp != ';' && !isspace((int)*cp)) { + vStringPut(package, (int)*cp); + cp++; + } + vStringCatS(package, "::"); + + cp = space; /* Rewind */ + kind = K_PACKAGE; + spaceRequired = TRUE; + qualified = TRUE; + } else if (strncmp((const char *)cp, "format", (size_t)6) == 0) { + cp += 6; + kind = K_FORMAT; + spaceRequired = TRUE; + qualified = TRUE; + } else { + if (isIdentifier1(*cp)) { + const unsigned char *p = cp; + while (isIdentifier(*p)) ++p; + while (isspace(*p)) ++p; + if ((int)*p == ':' && (int)*(p + 1) != ':') kind = K_LABEL; + } + } + if (kind != K_NONE) { + TRACE("cp0: %s\n", (const char *)cp); + if (spaceRequired && *cp && !isspace(*cp)) continue; + + TRACE("cp1: %s\n", (const char *)cp); + while (isspace(*cp)) cp++; + + while (!*cp || '#' == *cp) { /* Gobble up empty lines + and comments */ + cp = fileReadLine(); + if (!cp) goto END_MAIN_WHILE; + while (isspace(*cp)) cp++; + } + + while (isIdentifier(*cp) || (K_PACKAGE == kind && ':' == *cp)) { + vStringPut(name, (int)*cp); + cp++; + } + + if (K_FORMAT == kind && + vStringLength(name) == 0 && /* cp did not advance */ + '=' == *cp) { + /* format's name is optional. If it's omitted, 'STDOUT' + is assumed. */ + vStringCatS(name, "STDOUT"); + } + + vStringTerminate(name); + TRACE("name: %s\n", name->buffer); + + if (0 == vStringLength(name)) { + vStringClear(name); + continue; + } + + if (K_SUBROUTINE == kind) { + /* + * isSubroutineDeclaration() may consume several lines. So + * we record line positions. + */ + initTagEntry(&e, vStringValue(name)); + + if (TRUE == isSubroutineDeclaration(cp)) { + if (TRUE == PerlKinds[K_SUBROUTINE_DECLARATION].enabled) { + kind = K_SUBROUTINE_DECLARATION; + } else { + vStringClear(name); + continue; + } + } + + e.kind = PerlKinds[kind].letter; + e.kindName = PerlKinds[kind].name; + + makeTagEntry(&e); + + if (Option.include.qualifiedTags && qualified && package != NULL && + vStringLength(package) > 0) { + vString *const qualifiedName = vStringNew(); + vStringCopy(qualifiedName, package); + vStringCat(qualifiedName, name); + e.name = vStringValue(qualifiedName); + makeTagEntry(&e); + vStringDelete(qualifiedName); + } + } else if (vStringLength(name) > 0) { + makeSimpleTag(name, PerlKinds, kind); + if (Option.include.qualifiedTags && qualified && K_PACKAGE != kind && + package != NULL && vStringLength(package) > 0) { + vString *const qualifiedName = vStringNew(); + vStringCopy(qualifiedName, package); + vStringCat(qualifiedName, name); + makeSimpleTag(qualifiedName, PerlKinds, kind); + vStringDelete(qualifiedName); + } + } + vStringClear(name); + } + } + +END_MAIN_WHILE: + vStringDelete(name); + if (package != NULL) vStringDelete(package); +} + +extern parserDefinition *PerlParser(void) { + static const char *const extensions[] = {"pl", "pm", "plx", "perl", NULL}; + parserDefinition *def = parserNew("Perl"); + def->kinds = PerlKinds; + def->kindCount = KIND_COUNT(PerlKinds); + def->extensions = extensions; + def->parser = findPerlTags; + return def; +} + +/* vi:set tabstop=4 shiftwidth=4 noexpandtab: */ diff --git a/third_party/ctags/php.c b/third_party/ctags/php.c new file mode 100644 index 00000000..06ab4094 --- /dev/null +++ b/third_party/ctags/php.c @@ -0,0 +1,240 @@ +/* + * $Id: php.c 734 2009-08-20 23:33:54Z jafl $ + * + * Copyright (c) 2000, Jesus Castagnetto + * + * This source code is released for free distribution under the terms of the + * GNU General Public License. + * + * This module contains functions for generating tags for the PHP web page + * scripting language. Only recognizes functions and classes, not methods or + * variables. + * + * Parsing PHP defines by Pavel Hlousek , Apr 2003. + */ +#include "third_party/ctags/general.h" +/* must always come first */ +#include "third_party/ctags/parse.h" +#include "third_party/ctags/read.h" +#include "third_party/ctags/vstring.h" + +/* + * DATA DEFINITIONS + */ +typedef enum { K_CLASS, K_DEFINE, K_FUNCTION, K_VARIABLE } phpKind; + +#if 0 +static kindOption PhpKinds [] = { + { TRUE, 'c', "class", "classes" }, + { TRUE, 'd', "define", "constant definitions" }, + { TRUE, 'f', "function", "functions" }, + { TRUE, 'v', "variable", "variables" } +}; +#endif + +/* + * FUNCTION DEFINITIONS + */ + +/* JavaScript patterns are duplicated in jscript.c */ + +/* + * Cygwin doesn't support non-ASCII characters in character classes. + * This isn't a good solution to the underlying problem, because we're still + * making assumptions about the character encoding. + * Really, these regular expressions need to concentrate on what marks the + * end of an identifier, and we need something like iconv to take into + * account the user's locale (or an override on the command-line.) + */ +#ifdef __CYGWIN__ +#define ALPHA "[:alpha:]" +#define ALNUM "[:alnum:]" +#else +#define ALPHA "A-Za-z\x7f-\uffff" +#define ALNUM "0-9A-Za-z\x7f-\uffff" +#endif + +static void installPHPRegex(const langType language) { + addTagRegex(language, + "^[ \t]*((final|abstract)[ \t]+)*class[ \t]+([" ALPHA "_][" ALNUM + "_]*)", + "\\3", "c,class,classes", NULL); + addTagRegex(language, "^[ \t]*interface[ \t]+([" ALPHA "_][" ALNUM "_]*)", + "\\1", "i,interface,interfaces", NULL); + addTagRegex(language, + "^[ \t]*define[ \t]*\\([ \t]*['\"]?([" ALPHA "_][" ALNUM "_]*)", + "\\1", "d,define,constant definitions", NULL); + addTagRegex(language, + "^[ \t]*((static|public|protected|private)[ \t]+)*function[ " + "\t]+&?[ \t]*([" ALPHA "_][" ALNUM "_]*)", + "\\3", "f,function,functions", NULL); + addTagRegex(language, + "^[ \t]*(\\$|::\\$|\\$this->)([" ALPHA "_][" ALNUM "_]*)[ \t]*=", + "\\2", "v,variable,variables", NULL); + addTagRegex(language, + "^[ \t]*((var|public|protected|private|static)[ \t]+)+\\$([" ALPHA + "_][" ALNUM "_]*)[ \t]*[=;]", + "\\3", "v,variable,variables", NULL); + + /* function regex is covered by PHP regex */ + addTagRegex(language, + "(^|[ \t])([A-Za-z0-9_]+)[ \t]*[=:][ \t]*function[ \t]*\\(", + "\\2", "j,jsfunction,javascript functions", NULL); + addTagRegex(language, + "(^|[ \t])([A-Za-z0-9_.]+)\\.([A-Za-z0-9_]+)[ \t]*=[ " + "\t]*function[ \t]*\\(", + "\\2.\\3", "j,jsfunction,javascript functions", NULL); + addTagRegex(language, + "(^|[ \t])([A-Za-z0-9_.]+)\\.([A-Za-z0-9_]+)[ \t]*=[ " + "\t]*function[ \t]*\\(", + "\\3", "j,jsfunction,javascript functions", NULL); +} + +/* Create parser definition structure */ +extern parserDefinition* PhpParser(void) { + static const char* const extensions[] = {"php", "php3", "phtml", NULL}; + parserDefinition* def = parserNew("PHP"); + def->extensions = extensions; + def->initialize = installPHPRegex; + def->regex = TRUE; + return def; +} + +#if 0 + +static boolean isLetter(const int c) +{ + return (boolean)(isalpha(c) || (c >= 127 && c <= 255)); +} + +static boolean isVarChar1(const int c) +{ + return (boolean)(isLetter (c) || c == '_'); +} + +static boolean isVarChar(const int c) +{ + return (boolean)(isVarChar1 (c) || isdigit (c)); +} + +static void findPhpTags (void) +{ + vString *name = vStringNew (); + const unsigned char *line; + + while ((line = fileReadLine ()) != NULL) + { + const unsigned char *cp = line; + const char* f; + + while (isspace (*cp)) + cp++; + + if (*(const char*)cp == '$' && isVarChar1 (*(const char*)(cp+1))) + { + cp += 1; + vStringClear (name); + while (isVarChar ((int) *cp)) + { + vStringPut (name, (int) *cp); + ++cp; + } + while (isspace ((int) *cp)) + ++cp; + if (*(const char*) cp == '=') + { + vStringTerminate (name); + makeSimpleTag (name, PhpKinds, K_VARIABLE); + vStringClear (name); + } + } + else if ((f = strstr ((const char*) cp, "function")) != NULL && + (f == (const char*) cp || isspace ((int) f [-1])) && + isspace ((int) f [8])) + { + cp = ((const unsigned char *) f) + 8; + + while (isspace ((int) *cp)) + ++cp; + + if (*cp == '&') /* skip reference character and following whitespace */ + { + cp++; + + while (isspace ((int) *cp)) + ++cp; + } + + vStringClear (name); + while (isalnum ((int) *cp) || *cp == '_') + { + vStringPut (name, (int) *cp); + ++cp; + } + vStringTerminate (name); + makeSimpleTag (name, PhpKinds, K_FUNCTION); + vStringClear (name); + } + else if (strncmp ((const char*) cp, "class", (size_t) 5) == 0 && + isspace ((int) cp [5])) + { + cp += 5; + + while (isspace ((int) *cp)) + ++cp; + vStringClear (name); + while (isalnum ((int) *cp) || *cp == '_') + { + vStringPut (name, (int) *cp); + ++cp; + } + vStringTerminate (name); + makeSimpleTag (name, PhpKinds, K_CLASS); + vStringClear (name); + } + else if (strncmp ((const char*) cp, "define", (size_t) 6) == 0 && + ! isalnum ((int) cp [6])) + { + cp += 6; + + while (isspace ((int) *cp)) + ++cp; + if (*cp != '(') + continue; + ++cp; + + while (isspace ((int) *cp)) + ++cp; + if ((*cp == '\'') || (*cp == '"')) + ++cp; + else if (! ((*cp == '_') || isalnum ((int) *cp))) + continue; + + vStringClear (name); + while (isalnum ((int) *cp) || *cp == '_') + { + vStringPut (name, (int) *cp); + ++cp; + } + vStringTerminate (name); + makeSimpleTag (name, PhpKinds, K_DEFINE); + vStringClear (name); + } + } + vStringDelete (name); +} + +extern parserDefinition* PhpParser (void) +{ + static const char *const extensions [] = { "php", "php3", "phtml", NULL }; + parserDefinition* def = parserNew ("PHP"); + def->kinds = PhpKinds; + def->kindCount = KIND_COUNT (PhpKinds); + def->extensions = extensions; + def->parser = findPhpTags; + return def; +} + +#endif + +/* vi:set tabstop=4 shiftwidth=4: */ diff --git a/third_party/ctags/python.c b/third_party/ctags/python.c new file mode 100644 index 00000000..ac070ada --- /dev/null +++ b/third_party/ctags/python.c @@ -0,0 +1,668 @@ +/* + * $Id: python.c 752 2010-02-27 17:52:46Z elliotth $ + * + * Copyright (c) 2000-2003, Darren Hiebert + * + * This source code is released for free distribution under the terms of the + * GNU General Public License. + * + * This module contains functions for generating tags for Python language + * files. + */ +#include "third_party/ctags/general.h" +/* must always come first */ +#include "libc/mem/mem.h" +#include "third_party/ctags/debug.h" +#include "third_party/ctags/entry.h" +#include "third_party/ctags/main.h" +#include "third_party/ctags/options.h" +#include "third_party/ctags/read.h" +#include "third_party/ctags/routines.h" +#include "third_party/ctags/vstring.h" + +/* + * DATA DECLARATIONS + */ +typedef struct NestingLevel NestingLevel; +typedef struct NestingLevels NestingLevels; + +struct NestingLevel { + int indentation; + vString *name; + int type; +}; + +struct NestingLevels { + NestingLevel *levels; + int n; /* number of levels in use */ + int allocated; +}; + +typedef enum { K_CLASS, K_FUNCTION, K_MEMBER, K_VARIABLE, K_IMPORT } pythonKind; + +/* + * DATA DEFINITIONS + */ +static kindOption PythonKinds[] = {{TRUE, 'c', "class", "classes"}, + {TRUE, 'f', "function", "functions"}, + {TRUE, 'm', "member", "class members"}, + {TRUE, 'v', "variable", "variables"}, + {FALSE, 'i', "namespace", "imports"}}; + +static char const *const singletriple = "'''"; +static char const *const doubletriple = "\"\"\""; + +/* + * FUNCTION DEFINITIONS + */ + +static NestingLevels *nestingLevelsNew(void) { + NestingLevels *nls = xCalloc(1, NestingLevels); + return nls; +} + +static void nestingLevelsFree(NestingLevels *nls) { + int i; + for (i = 0; i < nls->allocated; i++) vStringDelete(nls->levels[i].name); + if (nls->levels) eFree(nls->levels); + eFree(nls); +} + +static void nestingLevelsPush(NestingLevels *nls, const vString *name, + int type) { + NestingLevel *nl = NULL; + + if (nls->n >= nls->allocated) { + nls->allocated++; + nls->levels = xRealloc(nls->levels, nls->allocated, NestingLevel); + nls->levels[nls->n].name = vStringNew(); + } + nl = &nls->levels[nls->n]; + nls->n++; + + vStringCopy(nl->name, name); + nl->type = type; +} + +#if 0 +static NestingLevel *nestingLevelsGetCurrent (NestingLevels *nls) +{ + Assert (nls != NULL); + + if (nls->n < 1) + return NULL; + + return &nls->levels[nls->n - 1]; +} + +static void nestingLevelsPop (NestingLevels *nls) +{ + const NestingLevel *nl = nestingLevelsGetCurrent(nls); + + Assert (nl != NULL); + vStringClear(nl->name); + nls->n--; +} +#endif + +static boolean isIdentifierFirstCharacter(int c) { + return (boolean)(isalpha(c) || c == '_'); +} + +static boolean isIdentifierCharacter(int c) { + return (boolean)(isalnum(c) || c == '_'); +} + +/* Given a string with the contents of a line directly after the "def" keyword, + * extract all relevant information and create a tag. + */ +static void makeFunctionTag(vString *const function, vString *const parent, + int is_class_parent, + const char *arglist __unused__) { + tagEntryInfo tag; + initTagEntry(&tag, vStringValue(function)); + + tag.kindName = "function"; + tag.kind = 'f'; + /* tag.extensionFields.arglist = arglist; */ + + if (vStringLength(parent) > 0) { + if (is_class_parent) { + tag.kindName = "member"; + tag.kind = 'm'; + tag.extensionFields.scope[0] = "class"; + tag.extensionFields.scope[1] = vStringValue(parent); + } else { + tag.extensionFields.scope[0] = "function"; + tag.extensionFields.scope[1] = vStringValue(parent); + } + } + + /* If a function starts with __, we mark it as file scope. + * FIXME: What is the proper way to signal such attributes? + * TODO: What does functions/classes starting with _ and __ mean in python? + */ + if (strncmp(vStringValue(function), "__", 2) == 0 && + strcmp(vStringValue(function), "__init__") != 0) { + tag.extensionFields.access = "private"; + tag.isFileScope = TRUE; + } else { + tag.extensionFields.access = "public"; + } + makeTagEntry(&tag); +} + +/* Given a string with the contents of the line directly after the "class" + * keyword, extract all necessary information and create a tag. + */ +static void makeClassTag(vString *const class, vString *const inheritance, + vString *const parent, int is_class_parent) { + tagEntryInfo tag; + initTagEntry(&tag, vStringValue(class)); + tag.kindName = "class"; + tag.kind = 'c'; + if (vStringLength(parent) > 0) { + if (is_class_parent) { + tag.extensionFields.scope[0] = "class"; + tag.extensionFields.scope[1] = vStringValue(parent); + } else { + tag.extensionFields.scope[0] = "function"; + tag.extensionFields.scope[1] = vStringValue(parent); + } + } + tag.extensionFields.inheritance = vStringValue(inheritance); + makeTagEntry(&tag); +} + +static void makeVariableTag(vString *const var, vString *const parent) { + tagEntryInfo tag; + initTagEntry(&tag, vStringValue(var)); + tag.kindName = "variable"; + tag.kind = 'v'; + if (vStringLength(parent) > 0) { + tag.extensionFields.scope[0] = "class"; + tag.extensionFields.scope[1] = vStringValue(parent); + } + makeTagEntry(&tag); +} + +/* Skip a single or double quoted string. */ +static const char *skipString(const char *cp) { + const char *start = cp; + int escaped = 0; + for (cp++; *cp; cp++) { + if (escaped) + escaped--; + else if (*cp == '\\') + escaped++; + else if (*cp == *start) + return cp + 1; + } + return cp; +} + +/* Skip everything up to an identifier start. */ +static const char *skipEverything(const char *cp) { + for (; *cp; cp++) { + if (*cp == '"' || *cp == '\'' || *cp == '#') { + cp = skipString(cp); + if (!*cp) break; + } + if (isIdentifierFirstCharacter((int)*cp)) return cp; + } + return cp; +} + +/* Skip an identifier. */ +static const char *skipIdentifier(const char *cp) { + while (isIdentifierCharacter((int)*cp)) cp++; + return cp; +} + +static const char *findDefinitionOrClass(const char *cp) { + while (*cp) { + cp = skipEverything(cp); + if (!strncmp(cp, "def", 3) || !strncmp(cp, "class", 5) || + !strncmp(cp, "cdef", 4) || !strncmp(cp, "cpdef", 5)) { + return cp; + } + cp = skipIdentifier(cp); + } + return NULL; +} + +static const char *skipSpace(const char *cp) { + while (isspace((int)*cp)) ++cp; + return cp; +} + +/* Starting at ''cp'', parse an identifier into ''identifier''. */ +static const char *parseIdentifier(const char *cp, vString *const identifier) { + vStringClear(identifier); + while (isIdentifierCharacter((int)*cp)) { + vStringPut(identifier, (int)*cp); + ++cp; + } + vStringTerminate(identifier); + return cp; +} + +static void parseClass(const char *cp, vString *const class, + vString *const parent, int is_class_parent) { + vString *const inheritance = vStringNew(); + vStringClear(inheritance); + cp = parseIdentifier(cp, class); + cp = skipSpace(cp); + if (*cp == '(') { + ++cp; + while (*cp != ')') { + if (*cp == '\0') { + /* Closing parenthesis can be in follow up line. */ + cp = (const char *)fileReadLine(); + if (!cp) break; + vStringPut(inheritance, ' '); + continue; + } + vStringPut(inheritance, *cp); + ++cp; + } + vStringTerminate(inheritance); + } + makeClassTag(class, inheritance, parent, is_class_parent); + vStringDelete(inheritance); +} + +static void parseImports(const char *cp) { + const char *pos; + vString *name, *name_next; + + cp = skipEverything(cp); + + if ((pos = strstr(cp, "import")) == NULL) return; + + cp = pos + 6; + + /* continue only if there is some space between the keyword and the identifier + */ + if (!isspace(*cp)) return; + + cp++; + cp = skipSpace(cp); + + name = vStringNew(); + name_next = vStringNew(); + + cp = skipEverything(cp); + while (*cp) { + cp = parseIdentifier(cp, name); + + cp = skipEverything(cp); + /* we parse the next possible import statement as well to be able to ignore + * 'foo' in 'import foo as bar' */ + parseIdentifier(cp, name_next); + + /* take the current tag only if the next one is not "as" */ + if (strcmp(vStringValue(name_next), "as") != 0 && + strcmp(vStringValue(name), "as") != 0) { + makeSimpleTag(name, PythonKinds, K_IMPORT); + } + } + vStringDelete(name); + vStringDelete(name_next); +} + +/* modified from get.c getArglistFromStr(). + * warning: terminates rest of string past arglist! + * note: does not ignore brackets inside strings! */ +static char *parseArglist(const char *buf) { + char *start, *end; + int level; + if (NULL == buf) return NULL; + if (NULL == (start = strchr(buf, '('))) return NULL; + for (level = 1, end = start + 1; level > 0; ++end) { + if ('\0' == *end) + break; + else if ('(' == *end) + ++level; + else if (')' == *end) + --level; + } + *end = '\0'; + return strdup(start); +} + +static void parseFunction(const char *cp, vString *const def, + vString *const parent, int is_class_parent) { + char *arglist; + + cp = parseIdentifier(cp, def); + arglist = parseArglist(cp); + makeFunctionTag(def, parent, is_class_parent, arglist); + if (arglist != NULL) { + eFree(arglist); + } +} + +/* Get the combined name of a nested symbol. Classes are separated with ".", + * functions with "/". For example this code: + * class MyClass: + * def myFunction: + * def SubFunction: + * class SubClass: + * def Method: + * pass + * Would produce this string: + * MyClass.MyFunction/SubFunction/SubClass.Method + */ +static boolean constructParentString(NestingLevels *nls, int indent, + vString *result) { + int i; + NestingLevel *prev = NULL; + int is_class = FALSE; + vStringClear(result); + for (i = 0; i < nls->n; i++) { + NestingLevel *nl = nls->levels + i; + if (indent <= nl->indentation) break; + if (prev) { + vStringCatS(result, + "."); /* make Geany symbol list grouping work properly */ + /* + if (prev->type == K_CLASS) + vStringCatS(result, "."); + else + vStringCatS(result, "/"); + */ + } + vStringCat(result, nl->name); + is_class = (nl->type == K_CLASS); + prev = nl; + } + return is_class; +} + +/* Check whether parent's indentation level is higher than the current level and + * if so, remove it. + */ +static void checkParent(NestingLevels *nls, int indent, vString *parent) { + int i; + NestingLevel *n; + + for (i = 0; i < nls->n; i++) { + n = nls->levels + i; + /* is there a better way to compare two vStrings? */ + if (strcmp(vStringValue(parent), vStringValue(n->name)) == 0) { + if (n && indent <= n->indentation) { + /* remove this level by clearing its name */ + vStringClear(n->name); + } + break; + } + } +} + +static void addNestingLevel(NestingLevels *nls, int indentation, + const vString *name, boolean is_class) { + int i; + NestingLevel *nl = NULL; + + for (i = 0; i < nls->n; i++) { + nl = nls->levels + i; + if (indentation <= nl->indentation) break; + } + if (i == nls->n) { + nestingLevelsPush(nls, name, 0); + nl = nls->levels + i; + } else { /* reuse existing slot */ + nls->n = i + 1; + vStringCopy(nl->name, name); + } + nl->indentation = indentation; + nl->type = is_class ? K_CLASS : !K_CLASS; +} + +/* Return a pointer to the start of the next triple string, or NULL. Store + * the kind of triple string in "which" if the return is not NULL. + */ +static char const *find_triple_start(char const *string, char const **which) { + char const *cp = string; + + for (; *cp; cp++) { + if (*cp == '"' || *cp == '\'') { + if (strncmp(cp, doubletriple, 3) == 0) { + *which = doubletriple; + return cp; + } + if (strncmp(cp, singletriple, 3) == 0) { + *which = singletriple; + return cp; + } + cp = skipString(cp); + if (!*cp) break; + } + } + return NULL; +} + +/* Find the end of a triple string as pointed to by "which", and update "which" + * with any other triple strings following in the given string. + */ +static void find_triple_end(char const *string, char const **which) { + char const *s = string; + while (1) { + /* Check if the string ends in the same line. */ + s = strstr(s, *which); + if (!s) break; + s += 3; + *which = NULL; + /* If yes, check if another one starts in the same line. */ + s = find_triple_start(s, which); + if (!s) break; + s += 3; + } +} + +static const char *findVariable(const char *line) { + /* Parse global and class variable names (C.x) from assignment statements. + * Object attributes (obj.x) are ignored. + * Assignment to a tuple 'x, y = 2, 3' not supported. + * TODO: ignore duplicate tags from reassignment statements. */ + const char *cp, *sp, *eq, *start; + + cp = strstr(line, "="); + if (!cp) return NULL; + eq = cp + 1; + while (*eq) { + if (*eq == '=') + return NULL; /* ignore '==' operator and 'x=5,y=6)' function lines */ + if (*eq == '(' || *eq == '#') + break; /* allow 'x = func(b=2,y=2,' lines and comments at the end of line + */ + eq++; + } + + /* go backwards to the start of the line, checking we have valid chars */ + start = cp - 1; + while (start >= line && isspace((int)*start)) --start; + while (start >= line && isIdentifierCharacter((int)*start)) --start; + if (!isIdentifierFirstCharacter(*(start + 1))) return NULL; + sp = start; + while (sp >= line && isspace((int)*sp)) --sp; + if ((sp + 1) != line) /* the line isn't a simple variable assignment */ + return NULL; + /* the line is valid, parse the variable name */ + ++start; + return start; +} + +/* Skip type declaration that optionally follows a cdef/cpdef */ +static const char *skipTypeDecl(const char *cp, boolean *is_class) { + const char *lastStart = cp, *ptr = cp; + int loopCount = 0; + ptr = skipSpace(cp); + if (!strncmp("extern", ptr, 6)) { + ptr += 6; + ptr = skipSpace(ptr); + if (!strncmp("from", ptr, 4)) { + return NULL; + } + } + if (!strncmp("class", ptr, 5)) { + ptr += 5; + *is_class = TRUE; + ptr = skipSpace(ptr); + return ptr; + } + /* limit so that we don't pick off "int item=obj()" */ + while (*ptr && loopCount++ < 2) { + while (*ptr && *ptr != '=' && *ptr != '(' && !isspace(*ptr)) ptr++; + if (!*ptr || *ptr == '=') return NULL; + if (*ptr == '(') { + return lastStart; /* if we stopped on a '(' we are done */ + } + ptr = skipSpace(ptr); + lastStart = ptr; + while (*lastStart == '*') lastStart++; /* cdef int *identifier */ + } + return NULL; +} + +static void findPythonTags(void) { + vString *const continuation = vStringNew(); + vString *const name = vStringNew(); + vString *const parent = vStringNew(); + + NestingLevels *const nesting_levels = nestingLevelsNew(); + + const char *line; + int line_skip = 0; + char const *longStringLiteral = NULL; + + while ((line = (const char *)fileReadLine()) != NULL) { + const char *cp = line, *candidate; + char const *longstring; + char const *keyword, *variable; + int indent; + + cp = skipSpace(cp); + + if (*cp == '\0') /* skip blank line */ + continue; + + /* Skip comment if we are not inside a multi-line string. */ + if (*cp == '#' && !longStringLiteral) continue; + + /* Deal with line continuation. */ + if (!line_skip) vStringClear(continuation); + vStringCatS(continuation, line); + vStringStripTrailing(continuation); + if (vStringLast(continuation) == '\\') { + vStringChop(continuation); + vStringCatS(continuation, " "); + line_skip = 1; + continue; + } + cp = line = vStringValue(continuation); + cp = skipSpace(cp); + indent = cp - line; + line_skip = 0; + + checkParent(nesting_levels, indent, parent); + + /* Deal with multiline string ending. */ + if (longStringLiteral) { + find_triple_end(cp, &longStringLiteral); + continue; + } + + /* Deal with multiline string start. */ + longstring = find_triple_start(cp, &longStringLiteral); + if (longstring) { + longstring += 3; + find_triple_end(longstring, &longStringLiteral); + /* We don't parse for any tags in the rest of the line. */ + continue; + } + + /* Deal with def and class keywords. */ + keyword = findDefinitionOrClass(cp); + if (keyword) { + boolean found = FALSE; + boolean is_class = FALSE; + if (!strncmp(keyword, "def ", 4)) { + cp = skipSpace(keyword + 3); + found = TRUE; + } else if (!strncmp(keyword, "class ", 6)) { + cp = skipSpace(keyword + 5); + found = TRUE; + is_class = TRUE; + } else if (!strncmp(keyword, "cdef ", 5)) { + cp = skipSpace(keyword + 4); + candidate = skipTypeDecl(cp, &is_class); + if (candidate) { + found = TRUE; + cp = candidate; + } + + } else if (!strncmp(keyword, "cpdef ", 6)) { + cp = skipSpace(keyword + 5); + candidate = skipTypeDecl(cp, &is_class); + if (candidate) { + found = TRUE; + cp = candidate; + } + } + + if (found) { + boolean is_parent_class; + + is_parent_class = constructParentString(nesting_levels, indent, parent); + + if (is_class) + parseClass(cp, name, parent, is_parent_class); + else + parseFunction(cp, name, parent, is_parent_class); + + addNestingLevel(nesting_levels, indent, name, is_class); + } + } + /* Find global and class variables */ + variable = findVariable(line); + if (variable) { + const char *start = variable; + boolean parent_is_class; + + vStringClear(name); + while (isIdentifierCharacter((int)*start)) { + vStringPut(name, (int)*start); + ++start; + } + vStringTerminate(name); + + parent_is_class = constructParentString(nesting_levels, indent, parent); + /* skip variables in methods */ + if (!parent_is_class && vStringLength(parent) > 0) continue; + + makeVariableTag(name, parent); + } + /* Find and parse imports */ + parseImports(line); + } + /* Clean up all memory we allocated. */ + vStringDelete(parent); + vStringDelete(name); + vStringDelete(continuation); + nestingLevelsFree(nesting_levels); +} + +extern parserDefinition *PythonParser(void) { + static const char *const extensions[] = {"py", "pyx", "pxd", + "pxi", "scons", NULL}; + parserDefinition *def = parserNew("Python"); + def->kinds = PythonKinds; + def->kindCount = KIND_COUNT(PythonKinds); + def->extensions = extensions; + def->parser = findPythonTags; + return def; +} + +/* vi:set tabstop=4 shiftwidth=4: */ diff --git a/third_party/ctags/read.c b/third_party/ctags/read.c new file mode 100644 index 00000000..9fb5549a --- /dev/null +++ b/third_party/ctags/read.c @@ -0,0 +1,473 @@ +/* + * $Id: read.c 769 2010-09-11 21:00:16Z dhiebert $ + * + * Copyright (c) 1996-2002, Darren Hiebert + * + * This source code is released for free distribution under the terms of the + * GNU General Public License. + * + * This module contains low level source and tag file read functions (newline + * conversion for source files are performed at this level). + */ +#include "third_party/ctags/general.h" +/* must always come first */ +#define FILE_WRITE +#include "third_party/ctags/debug.h" +#include "third_party/ctags/entry.h" +#include "third_party/ctags/main.h" +#include "third_party/ctags/options.h" +#include "third_party/ctags/read.h" +#include "third_party/ctags/routines.h" + +/* + * DATA DEFINITIONS + */ +inputFile File; /* globally read through macros */ +static fpos_t StartOfLine; /* holds deferred position of start of line */ + +/* + * FUNCTION DEFINITIONS + */ + +extern void freeSourceFileResources(void) { + if (File.name != NULL) vStringDelete(File.name); + if (File.path != NULL) vStringDelete(File.path); + if (File.source.name != NULL) vStringDelete(File.source.name); + if (File.source.tagPath != NULL) eFree(File.source.tagPath); + if (File.line != NULL) vStringDelete(File.line); +} + +/* + * Source file access functions + */ + +static void setInputFileName(const char *const fileName) { + const char *const head = fileName; + const char *const tail = baseFilename(head); + + if (File.name != NULL) vStringDelete(File.name); + File.name = vStringNewInit(fileName); + + if (File.path != NULL) vStringDelete(File.path); + if (tail == head) + File.path = NULL; + else { + const size_t length = tail - head - 1; + File.path = vStringNew(); + vStringNCopyS(File.path, fileName, length); + } +} + +static void setSourceFileParameters(vString *const fileName) { + if (File.source.name != NULL) vStringDelete(File.source.name); + File.source.name = fileName; + + if (File.source.tagPath != NULL) eFree(File.source.tagPath); + if (!Option.tagRelative || isAbsolutePath(vStringValue(fileName))) + File.source.tagPath = eStrdup(vStringValue(fileName)); + else + File.source.tagPath = + relativeFilename(vStringValue(fileName), TagFile.directory); + + if (vStringLength(fileName) > TagFile.max.file) + TagFile.max.file = vStringLength(fileName); + + File.source.isHeader = isIncludeFile(vStringValue(fileName)); + File.source.language = getFileLanguage(vStringValue(fileName)); +} + +static boolean setSourceFileName(vString *const fileName) { + boolean result = FALSE; + if (getFileLanguage(vStringValue(fileName)) != LANG_IGNORE) { + vString *pathName; + if (isAbsolutePath(vStringValue(fileName)) || File.path == NULL) + pathName = vStringNewCopy(fileName); + else + pathName = + combinePathAndFile(vStringValue(File.path), vStringValue(fileName)); + setSourceFileParameters(pathName); + result = TRUE; + } + return result; +} + +/* + * Line directive parsing + */ + +static int skipWhite(void) { + int c; + do + c = getc(File.fp); + while (c == ' ' || c == '\t'); + return c; +} + +static unsigned long readLineNumber(void) { + unsigned long lNum = 0; + int c = skipWhite(); + while (c != EOF && isdigit(c)) { + lNum = (lNum * 10) + (c - '0'); + c = getc(File.fp); + } + ungetc(c, File.fp); + if (c != ' ' && c != '\t') lNum = 0; + + return lNum; +} + +/* While ANSI only permits lines of the form: + * # line n "filename" + * Earlier compilers generated lines of the form + * # n filename + * GNU C will output lines of the form: + * # n "filename" + * So we need to be fairly flexible in what we accept. + */ +static vString *readFileName(void) { + vString *const fileName = vStringNew(); + boolean quoteDelimited = FALSE; + int c = skipWhite(); + + if (c == '"') { + c = getc(File.fp); /* skip double-quote */ + quoteDelimited = TRUE; + } + while (c != EOF && c != '\n' && + (quoteDelimited ? (c != '"') : (c != ' ' && c != '\t'))) { + vStringPut(fileName, c); + c = getc(File.fp); + } + if (c == '\n') ungetc(c, File.fp); + vStringPut(fileName, '\0'); + + return fileName; +} + +static boolean parseLineDirective(void) { + boolean result = FALSE; + int c = skipWhite(); + DebugStatement(const char *lineStr = "";) + + if (isdigit(c)) { + ungetc(c, File.fp); + result = TRUE; + } + else if (c == 'l' && getc(File.fp) == 'i' && getc(File.fp) == 'n' && + getc(File.fp) == 'e') { + c = getc(File.fp); + if (c == ' ' || c == '\t') { + DebugStatement(lineStr = "line";) result = TRUE; + } + } + if (result) { + const unsigned long lNum = readLineNumber(); + if (lNum == 0) + result = FALSE; + else { + vString *const fileName = readFileName(); + if (vStringLength(fileName) == 0) { + File.source.lineNumber = lNum - 1; /* applies to NEXT line */ + DebugStatement(debugPrintf(DEBUG_RAW, "#%s %ld", lineStr, lNum);) + } else if (setSourceFileName(fileName)) { + File.source.lineNumber = lNum - 1; /* applies to NEXT line */ + DebugStatement(debugPrintf(DEBUG_RAW, "#%s %ld \"%s\"", lineStr, lNum, + vStringValue(fileName));) + } + + if (Option.include.fileNames && vStringLength(fileName) > 0 && + lNum == 1) { + tagEntryInfo tag; + initTagEntry(&tag, baseFilename(vStringValue(fileName))); + + tag.isFileEntry = TRUE; + tag.lineNumberEntry = TRUE; + tag.lineNumber = 1; + tag.kindName = "file"; + tag.kind = 'F'; + + makeTagEntry(&tag); + } + vStringDelete(fileName); + result = TRUE; + } + } + return result; +} + +/* + * Source file I/O operations + */ + +/* This function opens a source file, and resets the line counter. If it + * fails, it will display an error message and leave the File.fp set to NULL. + */ +extern boolean fileOpen(const char *const fileName, const langType language) { +#ifdef VMS + const char *const openMode = "r"; +#else + const char *const openMode = "rb"; +#endif + boolean opened = FALSE; + + /* If another file was already open, then close it. + */ + if (File.fp != NULL) { + fclose(File.fp); /* close any open source file */ + File.fp = NULL; + } + + File.fp = fopen(fileName, openMode); + if (File.fp == NULL) + error(WARNING | PERROR, "cannot open \"%s\"", fileName); + else { + opened = TRUE; + + setInputFileName(fileName); + fgetpos(File.fp, &StartOfLine); + fgetpos(File.fp, &File.filePosition); + File.currentLine = NULL; + File.lineNumber = 0L; + File.eof = FALSE; + File.newLine = TRUE; + + if (File.line != NULL) vStringClear(File.line); + + setSourceFileParameters(vStringNewInit(fileName)); + File.source.lineNumber = 0L; + + verbose("OPENING %s as %s language %sfile\n", fileName, + getLanguageName(language), File.source.isHeader ? "include " : ""); + } + return opened; +} + +extern void fileClose(void) { + if (File.fp != NULL) { + /* The line count of the file is 1 too big, since it is one-based + * and is incremented upon each newline. + */ + if (Option.printTotals) { + fileStatus *status = eStat(vStringValue(File.name)); + addTotals(0, File.lineNumber - 1L, status->size); + } + fclose(File.fp); + File.fp = NULL; + } +} + +extern boolean fileEOF(void) { + return File.eof; +} + +/* Action to take for each encountered source newline. + */ +static void fileNewline(void) { + File.filePosition = StartOfLine; + File.newLine = FALSE; + File.lineNumber++; + File.source.lineNumber++; + DebugStatement(if (Option.breakLine == File.lineNumber) lineBreak();) + DebugStatement(debugPrintf(DEBUG_RAW, "%6ld: ", File.lineNumber);) +} + +/* This function reads a single character from the stream, performing newline + * canonicalization. + */ +static int iFileGetc(void) { + int c; +readnext: + c = getc(File.fp); + + /* If previous character was a newline, then we're starting a line. + */ + if (File.newLine && c != EOF) { + fileNewline(); + if (c == '#' && Option.lineDirectives) { + if (parseLineDirective()) + goto readnext; + else { + fsetpos(File.fp, &StartOfLine); + c = getc(File.fp); + } + } + } + + if (c == EOF) + File.eof = TRUE; + else if (c == NEWLINE) { + File.newLine = TRUE; + fgetpos(File.fp, &StartOfLine); + } else if (c == CRETURN) { + /* Turn line breaks into a canonical form. The three commonly + * used forms if line breaks: LF (UNIX/Mac OS X), CR (Mac OS 9), + * and CR-LF (MS-DOS) are converted into a generic newline. + */ +#ifndef macintosh + const int next = getc(File.fp); /* is CR followed by LF? */ + if (next != NEWLINE) + ungetc(next, File.fp); + else +#endif + { + c = NEWLINE; /* convert CR into newline */ + File.newLine = TRUE; + fgetpos(File.fp, &StartOfLine); + } + } + DebugStatement(debugPutc(DEBUG_RAW, c);) return c; +} + +extern void fileUngetc(int c) { + File.ungetch = c; +} + +static vString *iFileGetLine(void) { + vString *result = NULL; + int c; + if (File.line == NULL) File.line = vStringNew(); + vStringClear(File.line); + do { + c = iFileGetc(); + if (c != EOF) vStringPut(File.line, c); + if (c == '\n' || (c == EOF && vStringLength(File.line) > 0)) { + vStringTerminate(File.line); +#ifdef HAVE_REGEX + if (vStringLength(File.line) > 0) + matchRegex(File.line, File.source.language); +#endif + result = File.line; + break; + } + } while (c != EOF); + Assert(result != NULL || File.eof); + return result; +} + +/* Do not mix use of fileReadLine () and fileGetc () for the same file. + */ +extern int fileGetc(void) { + int c; + + /* If there is an ungotten character, then return it. Don't do any + * other processing on it, though, because we already did that the + * first time it was read through fileGetc (). + */ + if (File.ungetch != '\0') { + c = File.ungetch; + File.ungetch = '\0'; + return c; /* return here to avoid re-calling debugPutc () */ + } + do { + if (File.currentLine != NULL) { + c = *File.currentLine++; + if (c == '\0') File.currentLine = NULL; + } else { + vString *const line = iFileGetLine(); + if (line != NULL) File.currentLine = (unsigned char *)vStringValue(line); + if (File.currentLine == NULL) + c = EOF; + else + c = '\0'; + } + } while (c == '\0'); + DebugStatement(debugPutc(DEBUG_READ, c);) return c; +} + +extern int fileSkipToCharacter(int c) { + int d; + do { + d = fileGetc(); + } while (d != EOF && d != c); + return d; +} + +/* An alternative interface to fileGetc (). Do not mix use of fileReadLine() + * and fileGetc() for the same file. The returned string does not contain + * the terminating newline. A NULL return value means that all lines in the + * file have been read and we are at the end of file. + */ +extern const unsigned char *fileReadLine(void) { + vString *const line = iFileGetLine(); + const unsigned char *result = NULL; + if (line != NULL) { + result = (const unsigned char *)vStringValue(line); + vStringStripNewline(line); + DebugStatement(debugPrintf(DEBUG_READ, "%s\n", result);) + } + return result; +} + +/* + * Source file line reading with automatic buffer sizing + */ +extern char *readLine(vString *const vLine, FILE *const fp) { + char *result = NULL; + + vStringClear(vLine); + if (fp == NULL) /* to free memory allocated to buffer */ + error(FATAL, "NULL file pointer"); + else { + boolean reReadLine; + + /* If reading the line places any character other than a null or a + * newline at the last character position in the buffer (one less + * than the buffer size), then we must resize the buffer and + * reattempt to read the line. + */ + do { + char *const pLastChar = vStringValue(vLine) + vStringSize(vLine) - 2; + fpos_t startOfLine; + + fgetpos(fp, &startOfLine); + reReadLine = FALSE; + *pLastChar = '\0'; + result = fgets(vStringValue(vLine), (int)vStringSize(vLine), fp); + if (result == NULL) { + if (!feof(fp)) error(FATAL | PERROR, "Failure on attempt to read file"); + } else if (*pLastChar != '\0' && *pLastChar != '\n' && + *pLastChar != '\r') { + /* buffer overflow */ + reReadLine = vStringAutoResize(vLine); + if (reReadLine) + fsetpos(fp, &startOfLine); + else + error(FATAL | PERROR, "input line too big; out of memory"); + } else { + char *eol; + vStringSetLength(vLine); + /* canonicalize new line */ + eol = vStringValue(vLine) + vStringLength(vLine) - 1; + if (*eol == '\r') + *eol = '\n'; + else if (*(eol - 1) == '\r' && *eol == '\n') { + *(eol - 1) = '\n'; + *eol = '\0'; + --vLine->length; + } + } + } while (reReadLine); + } + return result; +} + +/* Places into the line buffer the contents of the line referenced by + * "location". + */ +extern char *readSourceLine(vString *const vLine, fpos_t location, + long *const pSeekValue) { + fpos_t orignalPosition; + char *result; + + fgetpos(File.fp, &orignalPosition); + fsetpos(File.fp, &location); + if (pSeekValue != NULL) *pSeekValue = ftell(File.fp); + result = readLine(vLine, File.fp); + if (result == NULL) + error(FATAL, "Unexpected end of file: %s", vStringValue(File.name)); + fsetpos(File.fp, &orignalPosition); + + return result; +} + +/* vi:set tabstop=4 shiftwidth=4: */ diff --git a/third_party/ctags/read.h b/third_party/ctags/read.h new file mode 100644 index 00000000..9046a7bc --- /dev/null +++ b/third_party/ctags/read.h @@ -0,0 +1,98 @@ +#ifndef _READ_H +#define _READ_H +#include "third_party/ctags/general.h" +/* must always come first */ +#include "libc/stdio/stdio.h" +#include "third_party/ctags/parse.h" +#include "third_party/ctags/vstring.h" + +#if defined(FILE_WRITE) || defined(VAXC) +#define CONST_FILE +#else +#define CONST_FILE const +#endif + +/* + * MACROS + */ +#define getInputLineNumber() File.lineNumber +#define getInputFileName() vStringValue(File.source.name) +#define getInputFilePosition() File.filePosition +#define getSourceFileName() vStringValue(File.source.name) +#define getSourceFileTagPath() File.source.tagPath +#define getSourceLanguage() File.source.language +#define getSourceLanguageName() getLanguageName(File.source.language) +#define getSourceLineNumber() File.source.lineNumber +#define isLanguage(lang) (boolean)((lang) == File.source.language) +#define isHeaderFile() File.source.isHeader + +/* + * DATA DECLARATIONS + */ + +enum eCharacters { + /* white space characters */ + SPACE = ' ', + NEWLINE = '\n', + CRETURN = '\r', + FORMFEED = '\f', + TAB = '\t', + VTAB = '\v', + + /* some hard to read characters */ + DOUBLE_QUOTE = '"', + SINGLE_QUOTE = '\'', + BACKSLASH = '\\', + + STRING_SYMBOL = ('S' + 0x80), + CHAR_SYMBOL = ('C' + 0x80) +}; + +/* Maintains the state of the current source file. + */ +typedef struct sInputFile { + vString *name; /* name of input file */ + vString *path; /* path of input file (if any) */ + vString *line; /* last line read from file */ + const unsigned char *currentLine; /* current line being worked on */ + FILE *fp; /* stream used for reading the file */ + unsigned long lineNumber; /* line number in the input file */ + fpos_t filePosition; /* file position of current line */ + int ungetch; /* a single character that was ungotten */ + boolean eof; /* have we reached the end of file? */ + boolean newLine; /* will the next character begin a new line? */ + + /* Contains data pertaining to the original source file in which the tag + * was defined. This may be different from the input file when #line + * directives are processed (i.e. the input file is preprocessor output). + */ + struct sSource { + vString *name; /* name to report for source file */ + char *tagPath; /* path of source file relative to tag file */ + unsigned long lineNumber; /* line number in the source file */ + boolean isHeader; /* is source file a header file? */ + langType language; /* language of source file */ + } source; +} inputFile; + +/* + * GLOBAL VARIABLES + */ +extern CONST_FILE inputFile File; + +/* + * FUNCTION PROTOTYPES + */ +extern void freeSourceFileResources(void); +extern boolean fileOpen(const char *const fileName, const langType language); +extern boolean fileEOF(void); +extern void fileClose(void); +extern int fileGetc(void); +extern int fileSkipToCharacter(int c); +extern void fileUngetc(int c); +extern const unsigned char *fileReadLine(void); +extern char *readLine(vString *const vLine, FILE *const fp); +extern char *readSourceLine(vString *const vLine, fpos_t location, + long *const pSeekValue); + +#endif /* _READ_H */ diff --git a/third_party/ctags/readtags.c b/third_party/ctags/readtags.c new file mode 100644 index 00000000..646a51b5 --- /dev/null +++ b/third_party/ctags/readtags.c @@ -0,0 +1,805 @@ +/* + * $Id: readtags.c 592 2007-07-31 03:30:41Z dhiebert $ + * + * Copyright (c) 1996-2003, Darren Hiebert + * + * This source code is released into the public domain. + * + * This module contains functions for reading tag files. + */ +#include "libc/calls/calls.h" +#include "libc/calls/weirdtypes.h" +#include "libc/conv/conv.h" +#include "libc/errno.h" +#include "libc/log/log.h" +#include "libc/mem/mem.h" +#include "libc/stdio/stdio.h" +#include "libc/str/str.h" +#include "third_party/ctags/readtags.h" + +/* + * MACROS + */ +#define TAB '\t' + +/* + * DATA DECLARATIONS + */ +typedef struct { + size_t size; + char *buffer; +} vstring; + +/* Information about current tag file */ +struct sTagFile { + /* has the file been opened and this structure initialized? */ + short initialized; + /* format of tag file */ + short format; + /* how is the tag file sorted? */ + sortType sortMethod; + /* pointer to file structure */ + FILE *fp; + /* file position of first character of `line' */ + off_t pos; + /* size of tag file in seekable positions */ + off_t size; + /* last line read */ + vstring line; + /* name of tag in last line read */ + vstring name; + /* defines tag search state */ + struct { + /* file position of last match for tag */ + off_t pos; + /* name of tag last searched for */ + char *name; + /* length of name for partial matches */ + size_t nameLength; + /* peforming partial match */ + short partial; + /* ignoring case */ + short ignorecase; + } search; + /* miscellaneous extension fields */ + struct { + /* number of entries in `list' */ + unsigned short max; + /* list of key value pairs */ + tagExtensionField *list; + } fields; + /* buffers to be freed at close */ + struct { + /* name of program author */ + char *author; + /* name of program */ + char *name; + /* URL of distribution */ + char *url; + /* program version */ + char *version; + } program; +}; + +/* + * DATA DEFINITIONS + */ +const char *const EmptyString = ""; +const char *const PseudoTagPrefix = "!_"; + +/* + * FUNCTION DEFINITIONS + */ + +/* + * Compare two strings, ignoring case. + * Return 0 for match, < 0 for smaller, > 0 for bigger + * Make sure case is folded to uppercase in comparison (like for 'sort -f') + * This makes a difference when one of the chars lies between upper and lower + * ie. one of the chars [ \ ] ^ _ ` for ascii. (The '_' in particular !) + */ +static int struppercmp(const char *s1, const char *s2) { + int result; + do { + result = toupper((int)*s1) - toupper((int)*s2); + } while (result == 0 && *s1++ != '\0' && *s2++ != '\0'); + return result; +} + +static int strnuppercmp(const char *s1, const char *s2, size_t n) { + int result; + do { + result = toupper((int)*s1) - toupper((int)*s2); + } while (result == 0 && --n > 0 && *s1++ != '\0' && *s2++ != '\0'); + return result; +} + +static int growString(vstring *s) { + int result = 0; + size_t newLength; + char *newLine; + if (s->size == 0) { + newLength = 128; + newLine = (char *)malloc(newLength); + *newLine = '\0'; + } else { + newLength = 2 * s->size; + newLine = (char *)realloc(s->buffer, newLength); + } + if (newLine == NULL) + perror("string too large"); + else { + s->buffer = newLine; + s->size = newLength; + result = 1; + } + return result; +} + +/* Copy name of tag out of tag line */ +static void copyName(tagFile *const file) { + size_t length; + const char *end = strchr(file->line.buffer, '\t'); + if (end == NULL) { + end = strchr(file->line.buffer, '\n'); + if (end == NULL) end = strchr(file->line.buffer, '\r'); + } + if (end != NULL) + length = end - file->line.buffer; + else + length = strlen(file->line.buffer); + while (length >= file->name.size) growString(&file->name); + strncpy(file->name.buffer, file->line.buffer, length); + file->name.buffer[length] = '\0'; +} + +static int readTagLineRaw(tagFile *const file) { + int result = 1; + int reReadLine; + + /* If reading the line places any character other than a null or a + * newline at the last character position in the buffer (one less than + * the buffer size), then we must resize the buffer and reattempt to read + * the line. + */ + do { + char *const pLastChar = file->line.buffer + file->line.size - 2; + char *line; + + file->pos = ftell(file->fp); + reReadLine = 0; + *pLastChar = '\0'; + line = fgets(file->line.buffer, (int)file->line.size, file->fp); + if (line == NULL) { + /* read error */ + if (!feof(file->fp)) perror("readTagLine"); + result = 0; + } else if (*pLastChar != '\0' && *pLastChar != '\n' && *pLastChar != '\r') { + /* buffer overflow */ + growString(&file->line); + fseek(file->fp, file->pos, SEEK_SET); + reReadLine = 1; + } else { + size_t i = strlen(file->line.buffer); + while (i > 0 && (file->line.buffer[i - 1] == '\n' || + file->line.buffer[i - 1] == '\r')) { + file->line.buffer[i - 1] = '\0'; + --i; + } + } + } while (reReadLine && result); + if (result) copyName(file); + return result; +} + +static int readTagLine(tagFile *const file) { + int result; + do { + result = readTagLineRaw(file); + } while (result && *file->name.buffer == '\0'); + return result; +} + +static tagResult growFields(tagFile *const file) { + tagResult result = TagFailure; + unsigned short newCount = (unsigned short)2 * file->fields.max; + tagExtensionField *newFields = (tagExtensionField *)realloc( + file->fields.list, newCount * sizeof(tagExtensionField)); + if (newFields == NULL) + perror("too many extension fields"); + else { + file->fields.list = newFields; + file->fields.max = newCount; + result = TagSuccess; + } + return result; +} + +static void parseExtensionFields(tagFile *const file, tagEntry *const entry, + char *const string) { + char *p = string; + while (p != NULL && *p != '\0') { + while (*p == TAB) *p++ = '\0'; + if (*p != '\0') { + char *colon; + char *field = p; + p = strchr(p, TAB); + if (p != NULL) *p++ = '\0'; + colon = strchr(field, ':'); + if (colon == NULL) + entry->kind = field; + else { + const char *key = field; + const char *value = colon + 1; + *colon = '\0'; + if (strcmp(key, "kind") == 0) + entry->kind = value; + else if (strcmp(key, "file") == 0) + entry->fileScope = 1; + else if (strcmp(key, "line") == 0) + entry->address.lineNumber = atol(value); + else { + if (entry->fields.count == file->fields.max) growFields(file); + file->fields.list[entry->fields.count].key = key; + file->fields.list[entry->fields.count].value = value; + ++entry->fields.count; + } + } + } + } +} + +static void parseTagLine(tagFile *file, tagEntry *const entry) { + int i; + char *p = file->line.buffer; + char *tab = strchr(p, TAB); + + entry->fields.list = NULL; + entry->fields.count = 0; + entry->kind = NULL; + entry->fileScope = 0; + + entry->name = p; + if (tab != NULL) { + *tab = '\0'; + p = tab + 1; + entry->file = p; + tab = strchr(p, TAB); + if (tab != NULL) { + int fieldsPresent; + *tab = '\0'; + p = tab + 1; + if (*p == '/' || *p == '?') { + /* parse pattern */ + int delimiter = *(unsigned char *)p; + entry->address.lineNumber = 0; + entry->address.pattern = p; + do { + p = strchr(p + 1, delimiter); + } while (p != NULL && *(p - 1) == '\\'); + if (p == NULL) { + /* invalid pattern */ + } else + ++p; + } else if (isdigit((int)*(unsigned char *)p)) { + /* parse line number */ + entry->address.pattern = p; + entry->address.lineNumber = atol(p); + while (isdigit((int)*(unsigned char *)p)) ++p; + } else { + /* invalid pattern */ + } + fieldsPresent = (strncmp(p, ";\"", 2) == 0); + *p = '\0'; + if (fieldsPresent) parseExtensionFields(file, entry, p + 2); + } + } + if (entry->fields.count > 0) entry->fields.list = file->fields.list; + for (i = entry->fields.count; i < file->fields.max; ++i) { + file->fields.list[i].key = NULL; + file->fields.list[i].value = NULL; + } +} + +static char *duplicate(const char *str) { + char *result = NULL; + if (str != NULL) { + result = strdup(str); + if (result == NULL) perror(NULL); + } + return result; +} + +static void readPseudoTags(tagFile *const file, tagFileInfo *const info) { + fpos_t startOfLine; + const size_t prefixLength = strlen(PseudoTagPrefix); + if (info != NULL) { + info->file.format = 1; + info->file.sort = TAG_UNSORTED; + info->program.author = NULL; + info->program.name = NULL; + info->program.url = NULL; + info->program.version = NULL; + } + while (1) { + fgetpos(file->fp, &startOfLine); + if (!readTagLine(file)) break; + if (strncmp(file->line.buffer, PseudoTagPrefix, prefixLength) != 0) + break; + else { + tagEntry entry; + const char *key, *value; + parseTagLine(file, &entry); + key = entry.name + prefixLength; + value = entry.file; + if (strcmp(key, "TAG_FILE_SORTED") == 0) + file->sortMethod = (sortType)atoi(value); + else if (strcmp(key, "TAG_FILE_FORMAT") == 0) + file->format = (short)atoi(value); + else if (strcmp(key, "TAG_PROGRAM_AUTHOR") == 0) + file->program.author = duplicate(value); + else if (strcmp(key, "TAG_PROGRAM_NAME") == 0) + file->program.name = duplicate(value); + else if (strcmp(key, "TAG_PROGRAM_URL") == 0) + file->program.url = duplicate(value); + else if (strcmp(key, "TAG_PROGRAM_VERSION") == 0) + file->program.version = duplicate(value); + if (info != NULL) { + info->file.format = file->format; + info->file.sort = file->sortMethod; + info->program.author = file->program.author; + info->program.name = file->program.name; + info->program.url = file->program.url; + info->program.version = file->program.version; + } + } + } + fsetpos(file->fp, &startOfLine); +} + +static void gotoFirstLogicalTag(tagFile *const file) { + fpos_t startOfLine; + const size_t prefixLength = strlen(PseudoTagPrefix); + rewind(file->fp); + while (1) { + fgetpos(file->fp, &startOfLine); + if (!readTagLine(file)) break; + if (strncmp(file->line.buffer, PseudoTagPrefix, prefixLength) != 0) break; + } + fsetpos(file->fp, &startOfLine); +} + +static tagFile *initialize(const char *const filePath, + tagFileInfo *const info) { + tagFile *result = (tagFile *)calloc((size_t)1, sizeof(tagFile)); + if (result != NULL) { + growString(&result->line); + growString(&result->name); + result->fields.max = 20; + result->fields.list = (tagExtensionField *)calloc( + result->fields.max, sizeof(tagExtensionField)); + result->fp = fopen(filePath, "r"); + if (result->fp == NULL) { + free(result); + result = NULL; + info->status.error_number = errno; + } else { + fseek(result->fp, 0, SEEK_END); + result->size = ftell(result->fp); + rewind(result->fp); + readPseudoTags(result, info); + info->status.opened = 1; + result->initialized = 1; + } + } + return result; +} + +static void terminate(tagFile *const file) { + fclose(file->fp); + + free(file->line.buffer); + free(file->name.buffer); + free(file->fields.list); + + if (file->program.author != NULL) free(file->program.author); + if (file->program.name != NULL) free(file->program.name); + if (file->program.url != NULL) free(file->program.url); + if (file->program.version != NULL) free(file->program.version); + if (file->search.name != NULL) free(file->search.name); + + memset(file, 0, sizeof(tagFile)); + + free(file); +} + +static tagResult readNext(tagFile *const file, tagEntry *const entry) { + tagResult result; + if (file == NULL || !file->initialized) + result = TagFailure; + else if (!readTagLine(file)) + result = TagFailure; + else { + if (entry != NULL) parseTagLine(file, entry); + result = TagSuccess; + } + return result; +} + +static const char *readFieldValue(const tagEntry *const entry, + const char *const key) { + const char *result = NULL; + int i; + if (strcmp(key, "kind") == 0) + result = entry->kind; + else if (strcmp(key, "file") == 0) + result = EmptyString; + else + for (i = 0; i < entry->fields.count && result == NULL; ++i) + if (strcmp(entry->fields.list[i].key, key) == 0) + result = entry->fields.list[i].value; + return result; +} + +static int readTagLineSeek(tagFile *const file, const off_t pos) { + int result = 0; + if (fseek(file->fp, pos, SEEK_SET) == 0) { + result = readTagLine(file); /* read probable partial line */ + if (pos > 0 && result) result = readTagLine(file); /* read complete line */ + } + return result; +} + +static int nameComparison(tagFile *const file) { + int result; + if (file->search.ignorecase) { + if (file->search.partial) + result = strnuppercmp(file->search.name, file->name.buffer, + file->search.nameLength); + else + result = struppercmp(file->search.name, file->name.buffer); + } else { + if (file->search.partial) + result = strncmp(file->search.name, file->name.buffer, + file->search.nameLength); + else + result = strcmp(file->search.name, file->name.buffer); + } + return result; +} + +static void findFirstNonMatchBefore(tagFile *const file) { +#define JUMP_BACK 512 + int more_lines; + int comp; + off_t start = file->pos; + off_t pos = start; + do { + if (pos < (off_t)JUMP_BACK) + pos = 0; + else + pos = pos - JUMP_BACK; + more_lines = readTagLineSeek(file, pos); + comp = nameComparison(file); + } while (more_lines && comp == 0 && pos > 0 && pos < start); +} + +static tagResult findFirstMatchBefore(tagFile *const file) { + tagResult result = TagFailure; + int more_lines; + off_t start = file->pos; + findFirstNonMatchBefore(file); + do { + more_lines = readTagLine(file); + if (nameComparison(file) == 0) result = TagSuccess; + } while (more_lines && result != TagSuccess && file->pos < start); + return result; +} + +static tagResult findBinary(tagFile *const file) { + tagResult result = TagFailure; + off_t lower_limit = 0; + off_t upper_limit = file->size; + off_t last_pos = 0; + off_t pos = upper_limit / 2; + while (result != TagSuccess) { + if (!readTagLineSeek(file, pos)) { + /* in case we fell off end of file */ + result = findFirstMatchBefore(file); + break; + } else if (pos == last_pos) { + /* prevent infinite loop if we backed up to beginning of file */ + break; + } else { + const int comp = nameComparison(file); + last_pos = pos; + if (comp < 0) { + upper_limit = pos; + pos = lower_limit + ((upper_limit - lower_limit) / 2); + } else if (comp > 0) { + lower_limit = pos; + pos = lower_limit + ((upper_limit - lower_limit) / 2); + } else if (pos == 0) + result = TagSuccess; + else + result = findFirstMatchBefore(file); + } + } + return result; +} + +static tagResult findSequential(tagFile *const file) { + tagResult result = TagFailure; + if (file->initialized) { + while (result == TagFailure && readTagLine(file)) { + if (nameComparison(file) == 0) result = TagSuccess; + } + } + return result; +} + +static tagResult find(tagFile *const file, tagEntry *const entry, + const char *const name, const int options) { + tagResult result; + if (file->search.name != NULL) free(file->search.name); + file->search.name = duplicate(name); + file->search.nameLength = strlen(name); + file->search.partial = (options & TAG_PARTIALMATCH) != 0; + file->search.ignorecase = (options & TAG_IGNORECASE) != 0; + fseek(file->fp, 0, SEEK_END); + file->size = ftell(file->fp); + rewind(file->fp); + if ((file->sortMethod == TAG_SORTED && !file->search.ignorecase) || + (file->sortMethod == TAG_FOLDSORTED && file->search.ignorecase)) { +#ifdef DEBUG + printf("\n"); +#endif + result = findBinary(file); + } else { +#ifdef DEBUG + printf("\n"); +#endif + result = findSequential(file); + } + + if (result != TagSuccess) + file->search.pos = file->size; + else { + file->search.pos = file->pos; + if (entry != NULL) parseTagLine(file, entry); + } + return result; +} + +static tagResult findNext(tagFile *const file, tagEntry *const entry) { + tagResult result; + if ((file->sortMethod == TAG_SORTED && !file->search.ignorecase) || + (file->sortMethod == TAG_FOLDSORTED && file->search.ignorecase)) { + result = tagsNext(file, entry); + if (result == TagSuccess && nameComparison(file) != 0) result = TagFailure; + } else { + result = findSequential(file); + if (result == TagSuccess && entry != NULL) parseTagLine(file, entry); + } + return result; +} + +/* + * EXTERNAL INTERFACE + */ + +extern tagFile *tagsOpen(const char *const filePath, tagFileInfo *const info) { + return initialize(filePath, info); +} + +extern tagResult tagsSetSortType(tagFile *const file, const sortType type) { + tagResult result = TagFailure; + if (file != NULL && file->initialized) { + file->sortMethod = type; + result = TagSuccess; + } + return result; +} + +extern tagResult tagsFirst(tagFile *const file, tagEntry *const entry) { + tagResult result = TagFailure; + if (file != NULL && file->initialized) { + gotoFirstLogicalTag(file); + result = readNext(file, entry); + } + return result; +} + +extern tagResult tagsNext(tagFile *const file, tagEntry *const entry) { + tagResult result = TagFailure; + if (file != NULL && file->initialized) result = readNext(file, entry); + return result; +} + +extern const char *tagsField(const tagEntry *const entry, + const char *const key) { + const char *result = NULL; + if (entry != NULL) result = readFieldValue(entry, key); + return result; +} + +extern tagResult tagsFind(tagFile *const file, tagEntry *const entry, + const char *const name, const int options) { + tagResult result = TagFailure; + if (file != NULL && file->initialized) + result = find(file, entry, name, options); + return result; +} + +extern tagResult tagsFindNext(tagFile *const file, tagEntry *const entry) { + tagResult result = TagFailure; + if (file != NULL && file->initialized) result = findNext(file, entry); + return result; +} + +extern tagResult tagsClose(tagFile *const file) { + tagResult result = TagFailure; + if (file != NULL && file->initialized) { + terminate(file); + result = TagSuccess; + } + return result; +} + +/* + * TEST FRAMEWORK + */ + +#ifdef READTAGS_MAIN + +static const char *TagFileName = "tags"; +static const char *ProgramName; +static int extensionFields; +static int SortOverride; +static sortType SortMethod; + +static void printTag(const tagEntry *entry) { + int i; + int first = 1; + const char *separator = ";\""; + const char *const empty = ""; +/* "sep" returns a value only the first time it is evaluated */ +#define sep (first ? (first = 0, separator) : empty) + printf("%s\t%s\t%s", entry->name, entry->file, entry->address.pattern); + if (extensionFields) { + if (entry->kind != NULL && entry->kind[0] != '\0') + printf("%s\tkind:%s", sep, entry->kind); + if (entry->fileScope) printf("%s\tfile:", sep); +#if 0 + if (entry->address.lineNumber > 0) + printf ("%s\tline:%lu", sep, entry->address.lineNumber); +#endif + for (i = 0; i < entry->fields.count; ++i) + printf("%s\t%s:%s", sep, entry->fields.list[i].key, + entry->fields.list[i].value); + } + putchar('\n'); +#undef sep +} + +static void findTag(const char *const name, const int options) { + tagFileInfo info; + tagEntry entry; + tagFile *const file = tagsOpen(TagFileName, &info); + if (file == NULL) { + fprintf(stderr, "%s: cannot open tag file: %s: %s\n", ProgramName, + strerror(info.status.error_number), name); + exit(1); + } else { + if (SortOverride) tagsSetSortType(file, SortMethod); + if (tagsFind(file, &entry, name, options) == TagSuccess) { + do { + printTag(&entry); + } while (tagsFindNext(file, &entry) == TagSuccess); + } + tagsClose(file); + } +} + +static void listTags(void) { + tagFileInfo info; + tagEntry entry; + tagFile *const file = tagsOpen(TagFileName, &info); + if (file == NULL) { + fprintf(stderr, "%s: cannot open tag file: %s: %s\n", ProgramName, + strerror(info.status.error_number), TagFileName); + exit(1); + } else { + while (tagsNext(file, &entry) == TagSuccess) printTag(&entry); + tagsClose(file); + } +} + +const char *const Usage = + "Find tag file entries matching specified names.\n\n" + "Usage: %s [-ilp] [-s[0|1]] [-t file] [name(s)]\n\n" + "Options:\n" + " -e Include extension fields in output.\n" + " -i Perform case-insensitive matching.\n" + " -l List all tags.\n" + " -p Perform partial matching.\n" + " -s[0|1|2] Override sort detection of tag file.\n" + " -t file Use specified tag file (default: \"tags\").\n" + "Note that options are acted upon as encountered, so order is " + "significant.\n"; + +extern int main(int argc, char **argv) { + int options = 0; + int actionSupplied = 0; + int i; + ProgramName = argv[0]; + if (argc == 1) { + fprintf(stderr, Usage, ProgramName); + exit(1); + } + for (i = 1; i < argc; ++i) { + const char *const arg = argv[i]; + if (arg[0] != '-') { + findTag(arg, options); + actionSupplied = 1; + } else { + size_t j; + for (j = 1; arg[j] != '\0'; ++j) { + switch (arg[j]) { + case 'e': + extensionFields = 1; + break; + case 'i': + options |= TAG_IGNORECASE; + break; + case 'p': + options |= TAG_PARTIALMATCH; + break; + case 'l': + listTags(); + actionSupplied = 1; + break; + + case 't': + if (arg[j + 1] != '\0') { + TagFileName = arg + j + 1; + j += strlen(TagFileName); + } else if (i + 1 < argc) + TagFileName = argv[++i]; + else { + fprintf(stderr, Usage, ProgramName); + exit(1); + } + break; + case 's': + SortOverride = 1; + ++j; + if (arg[j] == '\0') + SortMethod = TAG_SORTED; + else if (strchr("012", arg[j]) != NULL) + SortMethod = (sortType)(arg[j] - '0'); + else { + fprintf(stderr, Usage, ProgramName); + exit(1); + } + break; + default: + fprintf(stderr, "%s: unknown option: %c\n", ProgramName, arg[j]); + exit(1); + break; + } + } + } + } + if (!actionSupplied) { + fprintf(stderr, + "%s: no action specified: specify tag name(s) or -l option\n", + ProgramName); + exit(1); + } + return 0; +} + +#endif + +/* vi:set tabstop=4 shiftwidth=4: */ diff --git a/third_party/ctags/readtags.h b/third_party/ctags/readtags.h new file mode 100644 index 00000000..e3864269 --- /dev/null +++ b/third_party/ctags/readtags.h @@ -0,0 +1,224 @@ +#ifndef COSMOPOLITAN_THIRD_PARTY_CTAGS_READTAGS_H_ +#define COSMOPOLITAN_THIRD_PARTY_CTAGS_READTAGS_H_ +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +/* + * MACROS + */ + +/* Options for tagsSetSortType() */ +typedef enum { TAG_UNSORTED, TAG_SORTED, TAG_FOLDSORTED } sortType; + +/* Options for tagsFind() */ +#define TAG_FULLMATCH 0x0 +#define TAG_PARTIALMATCH 0x1 + +#define TAG_OBSERVECASE 0x0 +#define TAG_IGNORECASE 0x2 + +/* + * DATA DECLARATIONS + */ + +typedef enum { TagFailure = 0, TagSuccess = 1 } tagResult; + +struct sTagFile; + +typedef struct sTagFile tagFile; + +/* This structure contains information about the tag file. */ +typedef struct { + + struct { + /* was the tag file successfully opened? */ + int opened; + + /* errno value when 'opened' is false */ + int error_number; + } status; + + /* information about the structure of the tag file */ + struct { + /* format of tag file (1 = original, 2 = extended) */ + short format; + + /* how is the tag file sorted? */ + sortType sort; + } file; + + /* information about the program which created this tag file */ + struct { + /* name of author of generating program (may be null) */ + const char *author; + + /* name of program (may be null) */ + const char *name; + + /* URL of distribution (may be null) */ + const char *url; + + /* program version (may be null) */ + const char *version; + } program; + +} tagFileInfo; + +/* This structure contains information about an extension field for a tag. + * These exist at the end of the tag in the form "key:value"). + */ +typedef struct { + + /* the key of the extension field */ + const char *key; + + /* the value of the extension field (may be an empty string) */ + const char *value; + +} tagExtensionField; + +/* This structure contains information about a specific tag. */ +typedef struct { + + /* name of tag */ + const char *name; + + /* path of source file containing definition of tag */ + const char *file; + + /* address for locating tag in source file */ + struct { + /* pattern for locating source line + * (may be NULL if not present) */ + const char *pattern; + + /* line number in source file of tag definition + * (may be zero if not known) */ + unsigned long lineNumber; + } address; + + /* kind of tag (may by name, character, or NULL if not known) */ + const char *kind; + + /* is tag of file-limited scope? */ + short fileScope; + + /* miscellaneous extension fields */ + struct { + /* number of entries in `list' */ + unsigned short count; + + /* list of key value pairs */ + tagExtensionField *list; + } fields; + +} tagEntry; + +/* + * FUNCTION PROTOTYPES + */ + +/* + * This function must be called before calling other functions in this + * library. It is passed the path to the tag file to read and a (possibly + * null) pointer to a structure which, if not null, will be populated with + * information about the tag file. If successful, the function will return a + * handle which must be supplied to other calls to read information from the + * tag file, and info.status.opened will be set to true. If unsuccessful, + * info.status.opened will be set to false and info.status.error_number will + * be set to the errno value representing the system error preventing the tag + * file from being successfully opened. + */ +extern tagFile *tagsOpen(const char *const filePath, tagFileInfo *const info); + +/* + * This function allows the client to override the normal automatic detection + * of how a tag file is sorted. Permissible values for `type' are + * TAG_UNSORTED, TAG_SORTED, TAG_FOLDSORTED. Tag files in the new extended + * format contain a key indicating whether or not they are sorted. However, + * tag files in the original format do not contain such a key even when + * sorted, preventing this library from taking advantage of fast binary + * lookups. If the client knows that such an unmarked tag file is indeed + * sorted (or not), it can override the automatic detection. Note that + * incorrect lookup results will result if a tag file is marked as sorted when + * it actually is not. The function will return TagSuccess if called on an + * open tag file or TagFailure if not. + */ +extern tagResult tagsSetSortType(tagFile *const file, const sortType type); + +/* + * Reads the first tag in the file, if any. It is passed the handle to an + * opened tag file and a (possibly null) pointer to a structure which, if not + * null, will be populated with information about the first tag file entry. + * The function will return TagSuccess another tag entry is found, or + * TagFailure if not (i.e. it reached end of file). + */ +extern tagResult tagsFirst(tagFile *const file, tagEntry *const entry); + +/* + * Step to the next tag in the file, if any. It is passed the handle to an + * opened tag file and a (possibly null) pointer to a structure which, if not + * null, will be populated with information about the next tag file entry. The + * function will return TagSuccess another tag entry is found, or TagFailure + * if not (i.e. it reached end of file). It will always read the first tag in + * the file immediately after calling tagsOpen(). + */ +extern tagResult tagsNext(tagFile *const file, tagEntry *const entry); + +/* + * Retrieve the value associated with the extension field for a specified key. + * It is passed a pointer to a structure already populated with values by a + * previous call to tagsNext(), tagsFind(), or tagsFindNext(), and a string + * containing the key of the desired extension field. If no such field of the + * specified key exists, the function will return null. + */ +extern const char *tagsField(const tagEntry *const entry, + const char *const key); + +/* + * Find the first tag matching `name'. The structure pointed to by `entry' + * will be populated with information about the tag file entry. If a tag file + * is sorted using the C locale, a binary search algorithm is used to search + * the tag file, resulting in very fast tag lookups, even in huge tag files. + * Various options controlling the matches can be combined by bit-wise or-ing + * certain values together. The available values are: + * + * TAG_PARTIALMATCH + * Tags whose leading characters match `name' will qualify. + * + * TAG_FULLMATCH + * Only tags whose full lengths match `name' will qualify. + * + * TAG_IGNORECASE + * Matching will be performed in a case-insenstive manner. Note that + * this disables binary searches of the tag file. + * + * TAG_OBSERVECASE + * Matching will be performed in a case-senstive manner. Note that + * this enables binary searches of the tag file. + * + * The function will return TagSuccess if a tag matching the name is found, or + * TagFailure if not. + */ +extern tagResult tagsFind(tagFile *const file, tagEntry *const entry, + const char *const name, const int options); + +/* + * Find the next tag matching the name and options supplied to the most recent + * call to tagsFind() for the same tag file. The structure pointed to by + * `entry' will be populated with information about the tag file entry. The + * function will return TagSuccess if another tag matching the name is found, + * or TagFailure if not. + */ +extern tagResult tagsFindNext(tagFile *const file, tagEntry *const entry); + +/* + * Call tagsTerminate() at completion of reading the tag file, which will + * close the file and free any internal memory allocated. The function will + * return TagFailure is no file is currently open, TagSuccess otherwise. + */ +extern tagResult tagsClose(tagFile *const file); + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_THIRD_PARTY_CTAGS_READTAGS_H_ */ diff --git a/third_party/ctags/rexx.c b/third_party/ctags/rexx.c new file mode 100644 index 00000000..6ca52201 --- /dev/null +++ b/third_party/ctags/rexx.c @@ -0,0 +1,34 @@ +/* + * $Id: rexx.c 443 2006-05-30 04:37:13Z darren $ + * + * Copyright (c) 2001-2003, Darren Hiebert + * + * This source code is released for free distribution under the terms of the + * GNU General Public License. + * + * This module contains functions for generating tags for the REXX language + * (http://www.rexxla.org, http://www2.hursley.ibm.com/rexx). + */ +#include "third_party/ctags/general.h" +/* always include first */ +#include "third_party/ctags/parse.h" + +/* + * FUNCTION DEFINITIONS + */ + +static void installRexxRegex(const langType language) { + addTagRegex(language, "^([A-Za-z0-9@#$\\.!?_]+)[ \t]*:", "\\1", + "s,subroutine,subroutines", NULL); +} + +extern parserDefinition* RexxParser(void) { + static const char* const extensions[] = {"cmd", "rexx", "rx", NULL}; + parserDefinition* const def = parserNew("REXX"); + def->extensions = extensions; + def->initialize = installRexxRegex; + def->regex = TRUE; + return def; +} + +/* vi:set tabstop=4 shiftwidth=4: */ diff --git a/third_party/ctags/routines.c b/third_party/ctags/routines.c new file mode 100644 index 00000000..0d92b60b --- /dev/null +++ b/third_party/ctags/routines.c @@ -0,0 +1,756 @@ +/* + * $Id: routines.c 536 2007-06-02 06:09:00Z elliotth $ + * + * Copyright (c) 2002-2003, Darren Hiebert + * + * This source code is released for free distribution under the terms of the + * GNU General Public License. + * + * This module contains a lose assortment of shared functions. + */ +#include "third_party/ctags/general.h" +/* must always come first */ +#include "libc/calls/struct/stat.h" +#include "libc/errno.h" +#include "libc/fmt/fmt.h" +#include "libc/log/log.h" +#include "libc/mem/mem.h" +#include "libc/stdio/temp.h" +#include "third_party/ctags/debug.h" +#include "third_party/ctags/routines.h" + +/* + * MACROS + */ +#ifndef TMPDIR +#define TMPDIR "/tmp" +#endif + +/* File type tests. + */ +#ifndef S_ISREG +#if defined(S_IFREG) && !defined(AMIGA) +#define S_ISREG(mode) ((mode)&S_IFREG) +#else +#define S_ISREG(mode) TRUE /* assume regular file */ +#endif +#endif + +#ifndef S_ISLNK +#ifdef S_IFLNK +#define S_ISLNK(mode) (((mode)&S_IFMT) == S_IFLNK) +#else +#define S_ISLNK(mode) FALSE /* assume no soft links */ +#endif +#endif + +#ifndef S_ISDIR +#ifdef S_IFDIR +#define S_ISDIR(mode) (((mode)&S_IFMT) == S_IFDIR) +#else +#define S_ISDIR(mode) FALSE /* assume no soft links */ +#endif +#endif + +#ifndef S_IFMT +#define S_IFMT 0 +#endif + +#ifndef S_IXUSR +#define S_IXUSR 0 +#endif +#ifndef S_IXGRP +#define S_IXGRP 0 +#endif +#ifndef S_IXOTH +#define S_IXOTH 0 +#endif + +#ifndef S_IRUSR +#define S_IRUSR 0400 +#endif +#ifndef S_IWUSR +#define S_IWUSR 0200 +#endif + +#ifndef S_ISUID +#define S_ISUID 0 +#endif + +/* Hack for rediculous practice of Microsoft Visual C++. + */ +#if defined(WIN32) +#if defined(_MSC_VER) +#define stat _stat +#define getcwd _getcwd +#define currentdrive() (_getdrive() + 'A' - 1) +#define PATH_MAX _MAX_PATH +#elif defined(__BORLANDC__) +#define PATH_MAX MAXPATH +#define currentdrive() (getdisk() + 'A') +#elif defined(DJGPP) +#define currentdrive() (getdisk() + 'A') +#else +#define currentdrive() 'C' +#endif +#endif + +#ifndef PATH_MAX +#define PATH_MAX 256 +#endif + +/* + * Miscellaneous macros + */ +#define selected(var, feature) (((int)(var) & (int)(feature)) == (int)feature) + +/* + * DATA DEFINITIONS + */ +#if defined(MSDOS_STYLE_PATH) +const char *const PathDelimiters = ":/\\"; +#elif defined(VMS) +const char *const PathDelimiters = ":]>"; +#endif + +char *CurrentDirectory; + +static const char *ExecutableProgram; +static const char *ExecutableName; + +/* + * FUNCTION PROTOTYPES + */ +#ifdef NEED_PROTO_STAT +extern int stat(const char *, struct stat *); +#endif +#ifdef NEED_PROTO_LSTAT +extern int lstat(const char *, struct stat *); +#endif +#if defined(MSDOS) || defined(WIN32) || defined(VMS) || defined(__EMX__) || \ + defined(AMIGA) +#define lstat(fn, buf) stat(fn, buf) +#endif + +/* + * FUNCTION DEFINITIONS + */ + +extern void freeRoutineResources(void) { + if (CurrentDirectory != NULL) eFree(CurrentDirectory); +} + +extern void setExecutableName(const char *const path) { + ExecutableProgram = path; + ExecutableName = baseFilename(path); +#ifdef VAXC + { + /* remove filetype from executable name */ + char *p = strrchr(ExecutableName, '.'); + if (p != NULL) *p = '\0'; + } +#endif +} + +extern const char *getExecutableName(void) { + return ExecutableName; +} + +extern const char *getExecutablePath(void) { + return ExecutableProgram; +} + +extern void error(const errorSelection selection, const char *const format, + ...) { + va_list ap; + + va_start(ap, format); + fprintf(errout, "%s: %s", getExecutableName(), + selected(selection, WARNING) ? "Warning: " : ""); + vfprintf(errout, format, ap); + if (selected(selection, PERROR)) +#ifdef HAVE_STRERROR + fprintf(errout, " : %s", strerror(errno)); +#else + perror(" "); +#endif + fputs("\n", errout); + va_end(ap); + if (selected(selection, FATAL)) exit(1); +} + +/* + * Memory allocation functions + */ + +extern void *eMalloc(const size_t size) { + void *buffer = malloc(size); + + if (buffer == NULL) error(FATAL, "out of memory"); + + return buffer; +} + +extern void *eCalloc(const size_t count, const size_t size) { + void *buffer = calloc(count, size); + + if (buffer == NULL) error(FATAL, "out of memory"); + + return buffer; +} + +extern void *eRealloc(void *const ptr, const size_t size) { + void *buffer; + if (ptr == NULL) + buffer = eMalloc(size); + else { + buffer = realloc(ptr, size); + if (buffer == NULL) error(FATAL, "out of memory"); + } + return buffer; +} + +extern void eFree(void *const ptr) { + Assert(ptr != NULL); + free(ptr); +} + +/* + * String manipulation functions + */ + +/* + * Compare two strings, ignoring case. + * Return 0 for match, < 0 for smaller, > 0 for bigger + * Make sure case is folded to uppercase in comparison (like for 'sort -f') + * This makes a difference when one of the chars lies between upper and lower + * ie. one of the chars [ \ ] ^ _ ` for ascii. (The '_' in particular !) + */ +extern int struppercmp(const char *s1, const char *s2) { + int result; + do { + result = toupper((int)*s1) - toupper((int)*s2); + } while (result == 0 && *s1++ != '\0' && *s2++ != '\0'); + return result; +} + +extern int strnuppercmp(const char *s1, const char *s2, size_t n) { + int result; + do { + result = toupper((int)*s1) - toupper((int)*s2); + } while (result == 0 && --n > 0 && *s1++ != '\0' && *s2++ != '\0'); + return result; +} + +#ifndef HAVE_STRSTR +extern char *strstr(const char *str, const char *substr) { + const size_t length = strlen(substr); + const char *match = NULL; + const char *p; + + for (p = str; *p != '\0' && match == NULL; ++p) + if (strncmp(p, substr, length) == 0) match = p; + return (char *)match; +} +#endif + +extern char *eStrdup(const char *str) { + char *result = xMalloc(strlen(str) + 1, char); + strcpy(result, str); + return result; +} + +extern void toLowerString(char *str) { + while (*str != '\0') { + *str = tolower((int)*str); + ++str; + } +} + +extern void toUpperString(char *str) { + while (*str != '\0') { + *str = toupper((int)*str); + ++str; + } +} + +/* Newly allocated string containing lower case conversion of a string. + */ +extern char *newLowerString(const char *str) { + char *const result = xMalloc(strlen(str) + 1, char); + int i = 0; + do + result[i] = tolower((int)str[i]); + while (str[i++] != '\0'); + return result; +} + +/* Newly allocated string containing upper case conversion of a string. + */ +extern char *newUpperString(const char *str) { + char *const result = xMalloc(strlen(str) + 1, char); + int i = 0; + do + result[i] = toupper((int)str[i]); + while (str[i++] != '\0'); + return result; +} + +/* + * File system functions + */ + +extern void setCurrentDirectory(void) { +#ifndef AMIGA + char *buf; +#endif + if (CurrentDirectory == NULL) + CurrentDirectory = xMalloc((size_t)(PATH_MAX + 1), char); +#ifdef AMIGA + strcpy(CurrentDirectory, "."); +#else + buf = getcwd(CurrentDirectory, PATH_MAX); + if (buf == NULL) perror(""); +#endif + if (CurrentDirectory[strlen(CurrentDirectory) - (size_t)1] != + PATH_SEPARATOR) { + sprintf(CurrentDirectory + strlen(CurrentDirectory), "%c", + OUTPUT_PATH_SEPARATOR); + } +} + +#ifdef AMIGA +static boolean isAmigaDirectory(const char *const name) { + boolean result = FALSE; + struct FileInfoBlock *const fib = xMalloc(1, struct FileInfoBlock); + if (fib != NULL) { + const BPTR flock = Lock((UBYTE *)name, (long)ACCESS_READ); + + if (flock != (BPTR)NULL) { + if (Examine(flock, fib)) + result = ((fib->fib_DirEntryType >= 0) ? TRUE : FALSE); + UnLock(flock); + } + eFree(fib); + } + return result; +} +#endif + +/* For caching of stat() calls */ +extern fileStatus *eStat(const char *const fileName) { + struct stat status; + static fileStatus file; + if (file.name == NULL || strcmp(fileName, file.name) != 0) { + eStatFree(&file); + file.name = eStrdup(fileName); + if (lstat(file.name, &status) != 0) + file.exists = FALSE; + else { + file.isSymbolicLink = (boolean)S_ISLNK(status.st_mode); + if (file.isSymbolicLink && stat(file.name, &status) != 0) + file.exists = FALSE; + else { + file.exists = TRUE; +#ifdef AMIGA + file.isDirectory = isAmigaDirectory(file.name); +#else + file.isDirectory = (boolean)S_ISDIR(status.st_mode); +#endif + file.isNormalFile = (boolean)(S_ISREG(status.st_mode)); + file.isExecutable = + (boolean)((status.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) != 0); + file.isSetuid = (boolean)((status.st_mode & S_ISUID) != 0); + file.size = status.st_size; + } + } + } + return &file; +} + +extern void eStatFree(fileStatus *status) { + if (status->name != NULL) { + eFree(status->name); + status->name = NULL; + } +} + +extern boolean doesFileExist(const char *const fileName) { + fileStatus *status = eStat(fileName); + return status->exists; +} + +extern boolean isRecursiveLink(const char *const dirName) { + boolean result = FALSE; + fileStatus *status = eStat(dirName); + if (status->isSymbolicLink) { + char *const path = absoluteFilename(dirName); + while (path[strlen(path) - 1] == PATH_SEPARATOR) + path[strlen(path) - 1] = '\0'; + while (!result && strlen(path) > (size_t)1) { + char *const separator = strrchr(path, PATH_SEPARATOR); + if (separator == NULL) + break; + else if (separator == path) /* backed up to root directory */ + *(separator + 1) = '\0'; + else + *separator = '\0'; + result = isSameFile(path, dirName); + } + eFree(path); + } + return result; +} + +#ifndef HAVE_FGETPOS + +extern int fgetpos(FILE *stream, fpos_t *pos) { + int result = 0; + + *pos = ftell(stream); + if (*pos == -1L) result = -1; + + return result; +} + +extern int fsetpos(FILE *stream, fpos_t const *pos) { + return fseek(stream, *pos, SEEK_SET); +} + +#endif + +/* + * Pathname manipulation (O/S dependent!!!) + */ + +static boolean isPathSeparator(const int c) { + boolean result; +#if defined(MSDOS_STYLE_PATH) || defined(VMS) + result = (boolean)(strchr(PathDelimiters, c) != NULL); +#else + result = (boolean)(c == PATH_SEPARATOR); +#endif + return result; +} + +#if !defined(HAVE_STAT_ST_INO) + +static void canonicalizePath(char *const path __unused__) { +#if defined(MSDOS_STYLE_PATH) + char *p; + for (p = path; *p != '\0'; ++p) + if (isPathSeparator(*p) && *p != ':') *p = PATH_SEPARATOR; +#endif +} + +#endif + +extern boolean isSameFile(const char *const name1, const char *const name2) { + boolean result = FALSE; +#if defined(HAVE_STAT_ST_INO) + struct stat stat1, stat2; + + if (stat(name1, &stat1) == 0 && stat(name2, &stat2) == 0) + result = (boolean)(stat1.st_ino == stat2.st_ino); +#else + { + char *const n1 = absoluteFilename(name1); + char *const n2 = absoluteFilename(name2); + canonicalizePath(n1); + canonicalizePath(n2); +#if defined(CASE_INSENSITIVE_FILENAMES) + result = (boolean)(strcasecmp(n1, n2) == 0); +#else + result = (boolean)(strcmp(n1, n2) == 0); +#endif + free(n1); + free(n2); + } +#endif + return result; +} + +extern const char *baseFilename(const char *const filePath) { +#if defined(MSDOS_STYLE_PATH) || defined(VMS) + const char *tail = NULL; + unsigned int i; + + /* Find whichever of the path delimiters is last. + */ + for (i = 0; i < strlen(PathDelimiters); ++i) { + const char *sep = strrchr(filePath, PathDelimiters[i]); + + if (sep > tail) tail = sep; + } +#else + const char *tail = strrchr(filePath, PATH_SEPARATOR); +#endif + if (tail == NULL) + tail = filePath; + else + ++tail; /* step past last delimiter */ +#ifdef VAXC + { + /* remove version number from filename */ + char *p = strrchr((char *)tail, ';'); + if (p != NULL) *p = '\0'; + } +#endif + + return tail; +} + +extern const char *fileExtension(const char *const fileName) { + const char *extension; + const char *pDelimiter = NULL; + const char *const base = baseFilename(fileName); +#ifdef QDOS + pDelimiter = strrchr(base, '_'); +#endif + if (pDelimiter == NULL) pDelimiter = strrchr(base, '.'); + + if (pDelimiter == NULL) + extension = ""; + else + extension = pDelimiter + 1; /* skip to first char of extension */ + + return extension; +} + +extern boolean isAbsolutePath(const char *const path) { + boolean result = FALSE; +#if defined(MSDOS_STYLE_PATH) + if (isPathSeparator(path[0])) + result = TRUE; + else if (isalpha(path[0]) && path[1] == ':') { + if (isPathSeparator(path[2])) + result = TRUE; + else + /* We don't support non-absolute file names with a drive + * letter, like `d:NAME' (it's too much hassle). + */ + error(FATAL, "%s: relative file names with drive letters not supported", + path); + } +#elif defined(VMS) + result = (boolean)(strchr(path, ':') != NULL); +#else + result = isPathSeparator(path[0]); +#endif + return result; +} + +extern vString *combinePathAndFile(const char *const path, + const char *const file) { + vString *const filePath = vStringNew(); +#ifdef VMS + const char *const directoryId = strstr(file, ".DIR;1"); + + if (directoryId == NULL) { + const char *const versionId = strchr(file, ';'); + + vStringCopyS(filePath, path); + if (versionId == NULL) + vStringCatS(filePath, file); + else + vStringNCatS(filePath, file, versionId - file); + vStringCopyToLower(filePath, filePath); + } else { + /* File really is a directory; append it to the path. + * Gotcha: doesn't work with logical names. + */ + vStringNCopyS(filePath, path, strlen(path) - 1); + vStringPut(filePath, '.'); + vStringNCatS(filePath, file, directoryId - file); + if (strchr(path, '[') != NULL) + vStringPut(filePath, ']'); + else + vStringPut(filePath, '>'); + vStringTerminate(filePath); + } +#else + const int lastChar = path[strlen(path) - 1]; + boolean terminated = isPathSeparator(lastChar); + + vStringCopyS(filePath, path); + if (!terminated) { + vStringPut(filePath, OUTPUT_PATH_SEPARATOR); + vStringTerminate(filePath); + } + vStringCatS(filePath, file); +#endif + + return filePath; +} + +/* Return a newly-allocated string whose contents concatenate those of + * s1, s2, s3. + * Routine adapted from Gnu etags. + */ +static char *concat(const char *s1, const char *s2, const char *s3) { + int len1 = strlen(s1), len2 = strlen(s2), len3 = strlen(s3); + char *result = xMalloc(len1 + len2 + len3 + 1, char); + + strcpy(result, s1); + strcpy(result + len1, s2); + strcpy(result + len1 + len2, s3); + result[len1 + len2 + len3] = '\0'; + + return result; +} + +/* Return a newly allocated string containing the absolute file name of FILE + * given CWD (which should end with a slash). + * Routine adapted from Gnu etags. + */ +extern char *absoluteFilename(const char *file) { + char *slashp, *cp; + char *res = NULL; + if (isAbsolutePath(file)) { +#ifdef MSDOS_STYLE_PATH + if (file[1] == ':') + res = eStrdup(file); + else { + char drive[3]; + sprintf(drive, "%c:", currentdrive()); + res = concat(drive, file, ""); + } +#else + res = eStrdup(file); +#endif + } else + res = concat(CurrentDirectory, file, ""); + + /* Delete the "/dirname/.." and "/." substrings. */ + slashp = strchr(res, PATH_SEPARATOR); + while (slashp != NULL && slashp[0] != '\0') { + if (slashp[1] == '.') { + if (slashp[2] == '.' && + (slashp[3] == PATH_SEPARATOR || slashp[3] == '\0')) { + cp = slashp; + do + cp--; + while (cp >= res && !isAbsolutePath(cp)); + if (cp < res) cp = slashp; /* the absolute name begins with "/.." */ +#ifdef MSDOS_STYLE_PATH + /* Under MSDOS and NT we get `d:/NAME' as absolute file name, + * so the luser could say `d:/../NAME'. We silently treat this + * as `d:/NAME'. + */ + else if (cp[0] != PATH_SEPARATOR) + cp = slashp; +#endif + memmove(cp, slashp + 3, strlen(slashp + 3) + 1); + slashp = cp; + continue; + } else if (slashp[2] == PATH_SEPARATOR || slashp[2] == '\0') { + memmove(slashp, slashp + 2, strlen(slashp + 2) + 1); + continue; + } + } + slashp = strchr(slashp + 1, PATH_SEPARATOR); + } + + if (res[0] == '\0') + return eStrdup("/"); + else { +#ifdef MSDOS_STYLE_PATH + /* Canonicalize drive letter case. */ + if (res[1] == ':' && islower(res[0])) res[0] = toupper(res[0]); +#endif + + return res; + } +} + +/* Return a newly allocated string containing the absolute file name of dir + * where `file' resides given `CurrentDirectory'. + * Routine adapted from Gnu etags. + */ +extern char *absoluteDirname(char *file) { + char *slashp, *res; + char save; + slashp = strrchr(file, PATH_SEPARATOR); + if (slashp == NULL) + res = eStrdup(CurrentDirectory); + else { + save = slashp[1]; + slashp[1] = '\0'; + res = absoluteFilename(file); + slashp[1] = save; + } + return res; +} + +/* Return a newly allocated string containing the file name of FILE relative + * to the absolute directory DIR (which should end with a slash). + * Routine adapted from Gnu etags. + */ +extern char *relativeFilename(const char *file, const char *dir) { + const char *fp, *dp; + char *absdir, *res; + int i; + + /* Find the common root of file and dir (with a trailing slash). */ + absdir = absoluteFilename(file); + fp = absdir; + dp = dir; + while (*fp++ == *dp++) continue; + fp--; + dp--; /* back to the first differing char */ + do { /* look at the equal chars until path sep */ + if (fp == absdir) return absdir; /* first char differs, give up */ + fp--; + dp--; + } while (*fp != PATH_SEPARATOR); + + /* Build a sequence of "../" strings for the resulting relative file name. + */ + i = 0; + while ((dp = strchr(dp + 1, PATH_SEPARATOR)) != NULL) i += 1; + res = xMalloc(3 * i + strlen(fp + 1) + 1, char); + res[0] = '\0'; + while (i-- > 0) strcat(res, "../"); + + /* Add the file name relative to the common root of file and dir. */ + strcat(res, fp + 1); + free(absdir); + + return res; +} + +extern FILE *tempFile(const char *const mode, char **const pName) { + char *name; + FILE *fp; + int fd; +#if defined(HAVE_MKSTEMP) + const char *const pattern = "tags.XXXXXX"; + const char *tmpdir = NULL; + fileStatus *file = eStat(ExecutableProgram); + if (!file->isSetuid) tmpdir = getenv("TMPDIR"); + if (tmpdir == NULL) tmpdir = TMPDIR; + name = xMalloc(strlen(tmpdir) + 1 + strlen(pattern) + 1, char); + sprintf(name, "%s%c%s", tmpdir, OUTPUT_PATH_SEPARATOR, pattern); + fd = mkstemp(name); + eStatFree(file); +#elif defined(HAVE_TEMPNAM) + name = tempnam(TMPDIR, "tags"); + if (name == NULL) + error(FATAL | PERROR, "cannot allocate temporary file name"); + fd = open(name, O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR); +#else + name = xMalloc(L_tmpnam, char); + if (tmpnam(name) != name) + error(FATAL | PERROR, "cannot assign temporary file name"); + fd = open(name, O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR); +#endif + if (fd == -1) error(FATAL | PERROR, "cannot open temporary file"); + fp = fdopen(fd, mode); + if (fp == NULL) error(FATAL | PERROR, "cannot open temporary file"); + DebugStatement(debugPrintf(DEBUG_STATUS, "opened temporary file %s\n", name);) + Assert(*pName == NULL); + *pName = name; + return fp; +} + +/* vi:set tabstop=4 shiftwidth=4: */ diff --git a/third_party/ctags/routines.h b/third_party/ctags/routines.h new file mode 100644 index 00000000..94b90892 --- /dev/null +++ b/third_party/ctags/routines.h @@ -0,0 +1,125 @@ +#ifndef _ROUTINES_H +#define _ROUTINES_H +#include "third_party/ctags/general.h" +/* must always come first */ +#include "libc/stdio/stdio.h" +#include "third_party/ctags/vstring.h" + +/* + * MACROS + */ +#define xMalloc(n, Type) (Type *)eMalloc((size_t)(n) * sizeof(Type)) +#define xCalloc(n, Type) (Type *)eCalloc((size_t)(n), sizeof(Type)) +#define xRealloc(p, n, Type) (Type *)eRealloc((p), (n) * sizeof(Type)) + +/* + * Portability macros + */ +#ifndef PATH_SEPARATOR +#if defined(MSDOS_STYLE_PATH) +#define PATH_SEPARATOR '\\' +#elif defined(QDOS) +#define PATH_SEPARATOR '_' +#else +#define PATH_SEPARATOR '/' +#endif +#endif + +#if defined(MSDOS_STYLE_PATH) && defined(UNIX_PATH_SEPARATOR) +#define OUTPUT_PATH_SEPARATOR '/' +#else +#define OUTPUT_PATH_SEPARATOR PATH_SEPARATOR +#endif + +/* + * DATA DECLARATIONS + */ +#if defined(MSDOS_STYLE_PATH) || defined(VMS) +extern const char *const PathDelimiters; +#endif +extern char *CurrentDirectory; +typedef int errorSelection; +enum eErrorTypes { FATAL = 1, WARNING = 2, PERROR = 4 }; + +typedef struct { + /* Name of file for which status is valid */ + char *name; + + /* Does file exist? If not, members below do not contain valid data. */ + boolean exists; + + /* is file path a symbolic link to another file? */ + boolean isSymbolicLink; + + /* Is file (pointed to) a directory? */ + boolean isDirectory; + + /* Is file (pointed to) a normal file? */ + boolean isNormalFile; + + /* Is file (pointed to) executable? */ + boolean isExecutable; + + /* Is file (pointed to) setuid? */ + boolean isSetuid; + + /* Size of file (pointed to) */ + unsigned long size; +} fileStatus; + +/* + * FUNCTION PROTOTYPES + */ +extern void freeRoutineResources(void); +extern void setExecutableName(const char *const path); +extern const char *getExecutableName(void); +extern const char *getExecutablePath(void); +extern void error(const errorSelection selection, const char *const format, ...) + __printf__(2, 3); + +/* Memory allocation functions */ +#ifdef NEED_PROTO_MALLOC +extern void *malloc(size_t); +extern void *realloc(void *ptr, size_t); +#endif +extern void *eMalloc(const size_t size); +extern void *eCalloc(const size_t count, const size_t size); +extern void *eRealloc(void *const ptr, const size_t size); +extern void eFree(void *const ptr); + +/* String manipulation functions */ +extern int struppercmp(const char *s1, const char *s2); +extern int strnuppercmp(const char *s1, const char *s2, size_t n); +#ifndef HAVE_STRSTR +extern char *strstr(const char *str, const char *substr); +#endif +extern char *eStrdup(const char *str); +extern void toLowerString(char *str); +extern void toUpperString(char *str); +extern char *newLowerString(const char *str); +extern char *newUpperString(const char *str); + +/* File system functions */ +extern void setCurrentDirectory(void); +extern fileStatus *eStat(const char *const fileName); +extern void eStatFree(fileStatus *status); +extern boolean doesFileExist(const char *const fileName); +extern boolean isRecursiveLink(const char *const dirName); +extern boolean isSameFile(const char *const name1, const char *const name2); +#if defined(NEED_PROTO_FGETPOS) +extern int fgetpos(FILE *stream, fpos_t *pos); +extern int fsetpos(FILE *stream, fpos_t *pos); +#endif +extern const char *baseFilename(const char *const filePath); +extern const char *fileExtension(const char *const fileName); +extern boolean isAbsolutePath(const char *const path); +extern vString *combinePathAndFile(const char *const path, + const char *const file); +extern char *absoluteFilename(const char *file); +extern char *absoluteDirname(char *file); +extern char *relativeFilename(const char *file, const char *dir); +extern FILE *tempFile(const char *const mode, char **const pName); + +#endif /* _ROUTINES_H */ + +/* vi:set tabstop=4 shiftwidth=4: */ diff --git a/third_party/ctags/ruby.c b/third_party/ctags/ruby.c new file mode 100644 index 00000000..22e25dca --- /dev/null +++ b/third_party/ctags/ruby.c @@ -0,0 +1,344 @@ +/* + * $Id: ruby.c 571 2007-06-24 23:32:14Z elliotth $ + * + * Copyright (c) 2000-2001, Thaddeus Covert + * Copyright (c) 2002 Matthias Veit + * Copyright (c) 2004 Elliott Hughes + * + * This source code is released for free distribution under the terms of the + * GNU General Public License. + * + * This module contains functions for generating tags for Ruby language + * files. + */ +#include "third_party/ctags/general.h" +/* must always come first */ +#include "third_party/ctags/entry.h" +#include "third_party/ctags/parse.h" +#include "third_party/ctags/read.h" +#include "third_party/ctags/vstring.h" + +/* + * DATA DECLARATIONS + */ +typedef enum { + K_UNDEFINED = -1, + K_CLASS, + K_METHOD, + K_MODULE, + K_SINGLETON +} rubyKind; + +/* + * DATA DEFINITIONS + */ +static kindOption RubyKinds[] = { + {TRUE, 'c', "class", "classes"}, + {TRUE, 'f', "method", "methods"}, + {TRUE, 'm', "module", "modules"}, + {TRUE, 'F', "singleton method", "singleton methods"}}; + +static stringList* nesting = 0; + +/* + * FUNCTION DEFINITIONS + */ + +/* + * Returns a string describing the scope in 'list'. + * We record the current scope as a list of entered scopes. + * Scopes corresponding to 'if' statements and the like are + * represented by empty strings. Scopes corresponding to + * modules and classes are represented by the name of the + * module or class. + */ +static vString* stringListToScope(const stringList* list) { + unsigned int i; + unsigned int chunks_output = 0; + vString* result = vStringNew(); + const unsigned int max = stringListCount(list); + for (i = 0; i < max; ++i) { + vString* chunk = stringListItem(list, i); + if (vStringLength(chunk) > 0) { + vStringCatS(result, (chunks_output++ > 0) ? "." : ""); + vStringCatS(result, vStringValue(chunk)); + } + } + return result; +} + +/* + * Attempts to advance 's' past 'literal'. + * Returns TRUE if it did, FALSE (and leaves 's' where + * it was) otherwise. + */ +static boolean canMatch(const unsigned char** s, const char* literal) { + const int literal_length = strlen(literal); + const unsigned char next_char = *(*s + literal_length); + if (strncmp((const char*)*s, literal, literal_length) != 0) { + return FALSE; + } + /* Additionally check that we're at the end of a token. */ + if (!(next_char == 0 || isspace(next_char) || next_char == '(')) { + return FALSE; + } + *s += literal_length; + return TRUE; +} + +/* + * Attempts to advance 'cp' past a Ruby operator method name. Returns + * TRUE if successful (and copies the name into 'name'), FALSE otherwise. + */ +static boolean parseRubyOperator(vString* name, const unsigned char** cp) { + static const char* RUBY_OPERATORS[] = { + "[]", "[]=", "**", "!", "~", "+@", "-@", "*", "/", "%", + "+", "-", ">>", "<<", "&", "^", "|", "<=", "<", ">", + ">=", "<=>", "==", "===", "!=", "=~", "!~", "`", 0}; + int i; + for (i = 0; RUBY_OPERATORS[i] != 0; ++i) { + if (canMatch(cp, RUBY_OPERATORS[i])) { + vStringCatS(name, RUBY_OPERATORS[i]); + return TRUE; + } + } + return FALSE; +} + +/* + * Emits a tag for the given 'name' of kind 'kind' at the current nesting. + */ +static void emitRubyTag(vString* name, rubyKind kind) { + tagEntryInfo tag; + vString* scope; + + vStringTerminate(name); + scope = stringListToScope(nesting); + + initTagEntry(&tag, vStringValue(name)); + if (vStringLength(scope) > 0) { + tag.extensionFields.scope[0] = "class"; + tag.extensionFields.scope[1] = vStringValue(scope); + } + tag.kindName = RubyKinds[kind].name; + tag.kind = RubyKinds[kind].letter; + makeTagEntry(&tag); + + stringListAdd(nesting, vStringNewCopy(name)); + + vStringClear(name); + vStringDelete(scope); +} + +/* Tests whether 'ch' is a character in 'list'. */ +static boolean charIsIn(char ch, const char* list) { + return (strchr(list, ch) != 0); +} + +/* Advances 'cp' over leading whitespace. */ +static void skipWhitespace(const unsigned char** cp) { + while (isspace(**cp)) { + ++*cp; + } +} + +/* + * Copies the characters forming an identifier from *cp into + * name, leaving *cp pointing to the character after the identifier. + */ +static rubyKind parseIdentifier(const unsigned char** cp, vString* name, + rubyKind kind) { + /* Method names are slightly different to class and variable names. + * A method name may optionally end with a question mark, exclamation + * point or equals sign. These are all part of the name. + * A method name may also contain a period if it's a singleton method. + */ + const char* also_ok = (kind == K_METHOD) ? "_.?!=" : "_"; + + skipWhitespace(cp); + + /* Check for an anonymous (singleton) class such as "class << HTTP". */ + if (kind == K_CLASS && **cp == '<' && *(*cp + 1) == '<') { + return K_UNDEFINED; + } + + /* Check for operators such as "def []=(key, val)". */ + if (kind == K_METHOD || kind == K_SINGLETON) { + if (parseRubyOperator(name, cp)) { + return kind; + } + } + + /* Copy the identifier into 'name'. */ + while (**cp != 0 && (isalnum(**cp) || charIsIn(**cp, also_ok))) { + char last_char = **cp; + + vStringPut(name, last_char); + ++*cp; + + if (kind == K_METHOD) { + /* Recognize singleton methods. */ + if (last_char == '.') { + vStringTerminate(name); + vStringClear(name); + return parseIdentifier(cp, name, K_SINGLETON); + } + + /* Recognize characters which mark the end of a method name. */ + if (charIsIn(last_char, "?!=")) { + break; + } + } + } + return kind; +} + +static void readAndEmitTag(const unsigned char** cp, rubyKind expected_kind) { + if (isspace(**cp)) { + vString* name = vStringNew(); + rubyKind actual_kind = parseIdentifier(cp, name, expected_kind); + + if (actual_kind == K_UNDEFINED || vStringLength(name) == 0) { + /* + * What kind of tags should we create for code like this? + * + * %w(self.clfloor clfloor).each do |name| + * module_eval <<-"end;" + * def #{name}(x, y=1) + * q, r = x.divmod(y) + * q = q.to_i + * return q, r + * end + * end; + * end + * + * Or this? + * + * class << HTTP + * + * For now, we don't create any. + */ + } else { + emitRubyTag(name, actual_kind); + } + vStringDelete(name); + } +} + +static void enterUnnamedScope(void) { + stringListAdd(nesting, vStringNewInit("")); +} + +static void findRubyTags(void) { + const unsigned char* line; + boolean inMultiLineComment = FALSE; + + nesting = stringListNew(); + + /* FIXME: this whole scheme is wrong, because Ruby isn't line-based. + * You could perfectly well write: + * + * def + * method + * puts("hello") + * end + * + * if you wished, and this function would fail to recognize anything. + */ + while ((line = fileReadLine()) != NULL) { + const unsigned char* cp = line; + + if (canMatch(&cp, "=begin")) { + inMultiLineComment = TRUE; + continue; + } + if (canMatch(&cp, "=end")) { + inMultiLineComment = FALSE; + continue; + } + + skipWhitespace(&cp); + + /* Avoid mistakenly starting a scope for modifiers such as + * + * return if + * + * FIXME: this is fooled by code such as + * + * result = if + * + * else + * + * end + * + * FIXME: we're also fooled if someone does something heinous such as + * + * puts("hello") \ + * unless + */ + if (canMatch(&cp, "case") || canMatch(&cp, "for") || canMatch(&cp, "if") || + canMatch(&cp, "unless") || canMatch(&cp, "while")) { + enterUnnamedScope(); + } + + /* + * "module M", "class C" and "def m" should only be at the beginning + * of a line. + */ + if (canMatch(&cp, "module")) { + readAndEmitTag(&cp, K_MODULE); + } else if (canMatch(&cp, "class")) { + readAndEmitTag(&cp, K_CLASS); + } else if (canMatch(&cp, "def")) { + readAndEmitTag(&cp, K_METHOD); + } + + while (*cp != '\0') { + /* FIXME: we don't cope with here documents, + * or regular expression literals, or ... you get the idea. + * Hopefully, the restriction above that insists on seeing + * definitions at the starts of lines should keep us out of + * mischief. + */ + if (inMultiLineComment || isspace(*cp)) { + ++cp; + } else if (*cp == '#') { + /* FIXME: this is wrong, but there *probably* won't be a + * definition after an interpolated string (where # doesn't + * mean 'comment'). + */ + break; + } else if (canMatch(&cp, "begin") || canMatch(&cp, "do")) { + enterUnnamedScope(); + } else if (canMatch(&cp, "end") && stringListCount(nesting) > 0) { + /* Leave the most recent scope. */ + vStringDelete(stringListLast(nesting)); + stringListRemoveLast(nesting); + } else if (*cp == '"') { + /* Skip string literals. + * FIXME: should cope with escapes and interpolation. + */ + do { + ++cp; + } while (*cp != 0 && *cp != '"'); + } else if (*cp != '\0') { + do + ++cp; + while (isalnum(*cp) || *cp == '_'); + } + } + } + stringListDelete(nesting); +} + +extern parserDefinition* RubyParser(void) { + static const char* const extensions[] = {"rb", "ruby", NULL}; + parserDefinition* def = parserNew("Ruby"); + def->kinds = RubyKinds; + def->kindCount = KIND_COUNT(RubyKinds); + def->extensions = extensions; + def->parser = findRubyTags; + return def; +} + +/* vi:set tabstop=4 shiftwidth=4: */ diff --git a/third_party/ctags/scheme.c b/third_party/ctags/scheme.c new file mode 100644 index 00000000..cfa300ea --- /dev/null +++ b/third_party/ctags/scheme.c @@ -0,0 +1,90 @@ +/* + * $Id: scheme.c 443 2006-05-30 04:37:13Z darren $ + * + * Copyright (c) 2000-2002, Darren Hiebert + * + * This source code is released for free distribution under the terms of the + * GNU General Public License. + * + * This module contains functions for generating tags for Scheme language + * files. + */ +#include "third_party/ctags/general.h" +/* must always come first */ +#include "third_party/ctags/parse.h" +#include "third_party/ctags/read.h" +#include "third_party/ctags/vstring.h" + +/* + * DATA DEFINITIONS + */ +typedef enum { + K_FUNCTION, + K_SET, +} schemeKind; + +static kindOption SchemeKinds[] = { + {TRUE, 'f', "function", "functions"}, + {TRUE, 's', "set", "sets"}, +}; + +/* + * FUNCTION DEFINITIONS + */ + +/* Algorithm adapted from from GNU etags. + * Scheme tag functions + * look for (def... xyzzy + * look for (def... (xyzzy + * look for (def ... ((... (xyzzy .... + * look for (set! xyzzy + */ +static void readIdentifier(vString *const name, const unsigned char *cp) { + const unsigned char *p; + vStringClear(name); + /* Go till you get to white space or a syntactic break */ + for (p = cp; *p != '\0' && *p != '(' && *p != ')' && !isspace(*p); p++) + vStringPut(name, (int)*p); + vStringTerminate(name); +} + +static void findSchemeTags(void) { + vString *name = vStringNew(); + const unsigned char *line; + + while ((line = fileReadLine()) != NULL) { + const unsigned char *cp = line; + + if (cp[0] == '(' && (cp[1] == 'D' || cp[1] == 'd') && + (cp[2] == 'E' || cp[2] == 'e') && (cp[3] == 'F' || cp[3] == 'f')) { + while (!isspace(*cp)) cp++; + /* Skip over open parens and white space */ + while (*cp != '\0' && (isspace(*cp) || *cp == '(')) cp++; + readIdentifier(name, cp); + makeSimpleTag(name, SchemeKinds, K_FUNCTION); + } + if (cp[0] == '(' && (cp[1] == 'S' || cp[1] == 's') && + (cp[2] == 'E' || cp[2] == 'e') && (cp[3] == 'T' || cp[3] == 't') && + (cp[4] == '!' || cp[4] == '!') && (isspace(cp[5]))) { + while (*cp != '\0' && !isspace(*cp)) cp++; + /* Skip over white space */ + while (isspace(*cp)) cp++; + readIdentifier(name, cp); + makeSimpleTag(name, SchemeKinds, K_SET); + } + } + vStringDelete(name); +} + +extern parserDefinition *SchemeParser(void) { + static const char *const extensions[] = {"SCM", "SM", "sch", "scheme", + "scm", "sm", NULL}; + parserDefinition *def = parserNew("Scheme"); + def->kinds = SchemeKinds; + def->kindCount = KIND_COUNT(SchemeKinds); + def->extensions = extensions; + def->parser = findSchemeTags; + return def; +} + +/* vi:set tabstop=4 shiftwidth=4: */ diff --git a/third_party/ctags/sh.c b/third_party/ctags/sh.c new file mode 100644 index 00000000..0640b774 --- /dev/null +++ b/third_party/ctags/sh.c @@ -0,0 +1,87 @@ +/* + * $Id: sh.c 443 2006-05-30 04:37:13Z darren $ + * + * Copyright (c) 2000-2002, Darren Hiebert + * + * This source code is released for free distribution under the terms of the + * GNU General Public License. + * + * This module contains functions for generating tags for scripts for the + * Bourne shell (and its derivatives, the Korn and Z shells). + */ +#include "third_party/ctags/general.h" +/* must always come first */ +#include "third_party/ctags/parse.h" +#include "third_party/ctags/read.h" +#include "third_party/ctags/routines.h" +#include "third_party/ctags/vstring.h" + +/* + * DATA DEFINITIONS + */ +typedef enum { K_FUNCTION } shKind; + +static kindOption ShKinds[] = {{TRUE, 'f', "function", "functions"}}; + +/* + * FUNCTION DEFINITIONS + */ + +/* Reject any tag "main" from a file named "configure". These appear in + * here-documents in GNU autoconf scripts and will add a haystack to the + * needle. + */ +static boolean hackReject(const vString* const tagName) { + const char* const scriptName = baseFilename(vStringValue(File.name)); + boolean result = (boolean)(strcmp(scriptName, "configure") == 0 && + strcmp(vStringValue(tagName), "main") == 0); + return result; +} + +static void findShTags(void) { + vString* name = vStringNew(); + const unsigned char* line; + + while ((line = fileReadLine()) != NULL) { + const unsigned char* cp = line; + boolean functionFound = FALSE; + + if (line[0] == '#') continue; + + while (isspace(*cp)) cp++; + if (strncmp((const char*)cp, "function", (size_t)8) == 0 && + isspace((int)cp[8])) { + functionFound = TRUE; + cp += 8; + if (!isspace((int)*cp)) continue; + while (isspace((int)*cp)) ++cp; + } + if (!(isalnum((int)*cp) || *cp == '_')) continue; + while (isalnum((int)*cp) || *cp == '_') { + vStringPut(name, (int)*cp); + ++cp; + } + vStringTerminate(name); + while (isspace((int)*cp)) ++cp; + if (*cp++ == '(') { + while (isspace((int)*cp)) ++cp; + if (*cp == ')' && !hackReject(name)) functionFound = TRUE; + } + if (functionFound) makeSimpleTag(name, ShKinds, K_FUNCTION); + vStringClear(name); + } + vStringDelete(name); +} + +extern parserDefinition* ShParser(void) { + static const char* const extensions[] = {"sh", "SH", "bsh", "bash", + "ksh", "zsh", NULL}; + parserDefinition* def = parserNew("Sh"); + def->kinds = ShKinds; + def->kindCount = KIND_COUNT(ShKinds); + def->extensions = extensions; + def->parser = findShTags; + return def; +} + +/* vi:set tabstop=4 shiftwidth=4: */ diff --git a/third_party/ctags/slang.c b/third_party/ctags/slang.c new file mode 100644 index 00000000..fa168c42 --- /dev/null +++ b/third_party/ctags/slang.c @@ -0,0 +1,41 @@ +/* + * $Id: slang.c 443 2006-05-30 04:37:13Z darren $ + * + * Copyright (c) 2000-2001, Francesc Rocher + * + * Author: Francesc Rocher . + * + * This source code is released for free distribution under the terms of the + * GNU General Public License. + * + * This module contains functions for generating tags for S-Lang files. + */ + +/* + * INCLUDE FILES + */ +#include "third_party/ctags/general.h" /* must always come first */ +#include "third_party/ctags/parse.h" + +/* + * FUNCTION DEFINITIONS + */ +static void installSlangRegex (const langType language) +{ + addTagRegex (language, + "^.*define[ \t]+([A-Z_][A-Z0-9_]*)[^;]*$", + "\\1", "f,function,functions", "i"); + addTagRegex (language, + "^[ \t]*implements[ \t]+\\([ \t]*\"([^\"]*)\"[ \t]*\\)[ \t]*;", + "\\1", "n,namespace,namespaces", NULL); +} + +extern parserDefinition* SlangParser (void) +{ + static const char *const extensions [] = { "sl", NULL }; + parserDefinition* const def = parserNew ("SLang"); + def->extensions = extensions; + def->initialize = installSlangRegex; + def->regex = TRUE; + return def; +} diff --git a/third_party/ctags/sml.c b/third_party/ctags/sml.c new file mode 100644 index 00000000..89e12fb8 --- /dev/null +++ b/third_party/ctags/sml.c @@ -0,0 +1,175 @@ +/* + * $Id: sml.c 536 2007-06-02 06:09:00Z elliotth $ + * + * Copyright (c) 2002, Venkatesh Prasad Ranganath and Darren Hiebert + * + * This source code is released for free distribution under the terms of the + * GNU General Public License. + * + * This module contains functions for generating tags for SML language files. + */ +#include "third_party/ctags/general.h" +/* must always come first */ +#include "third_party/ctags/entry.h" +#include "third_party/ctags/parse.h" +#include "third_party/ctags/read.h" +#include "third_party/ctags/vstring.h" + +/* + * DATA DECLARATIONS + */ +typedef enum { + K_AND = -2, + K_NONE = -1, + K_EXCEPTION, + K_FUNCTION, + K_FUNCTOR, + K_SIGNATURE, + K_STRUCTURE, + K_TYPE, + K_VAL +} smlKind; + +/* + * DATA DEFINITIONS + */ +static kindOption SmlKinds[] = { + {TRUE, 'e', "exception", "exception declarations"}, + {TRUE, 'f', "function", "function definitions"}, + {TRUE, 'c', "functor", "functor definitions"}, + {TRUE, 's', "signature", "signature declarations"}, + {TRUE, 'r', "structure", "structure declarations"}, + {TRUE, 't', "type", "type definitions"}, + {TRUE, 'v', "value", "value bindings"}}; + +static struct { + const char *keyword; + smlKind kind; +} SmlKeywordTypes[] = {{"abstype", K_TYPE}, {"and", K_AND}, + {"datatype", K_TYPE}, {"exception", K_EXCEPTION}, + {"functor", K_FUNCTOR}, {"fun", K_FUNCTION}, + {"signature", K_SIGNATURE}, {"structure", K_STRUCTURE}, + {"type", K_TYPE}, {"val", K_VAL}}; + +static unsigned int CommentLevel = 0; + +/* + * FUNCTION DEFINITIONS + */ + +static void makeSmlTag(smlKind type, vString *name) { + tagEntryInfo tag; + initTagEntry(&tag, vStringValue(name)); + tag.kindName = SmlKinds[type].name; + tag.kind = SmlKinds[type].letter; + makeTagEntry(&tag); +} + +static const unsigned char *skipSpace(const unsigned char *cp) { + while (isspace((int)*cp)) ++cp; + return cp; +} + +static boolean isIdentifier(int c) { + boolean result = FALSE; + /* Consider '_' as an delimiter to aid user in tracking it's usage. */ + const char *const alternateIdentifiers = "!%&$#+-<>=/?@\\~'^|*_"; + if (isalnum(c)) + result = TRUE; + else if (c != '\0' && strchr(alternateIdentifiers, c) != NULL) + result = TRUE; + return result; +} + +static const unsigned char *parseIdentifier(const unsigned char *cp, + vString *const identifier) { + boolean stringLit = FALSE; + vStringClear(identifier); + while (*cp != '\0' && (!isIdentifier((int)*cp) || stringLit)) { + int oneback = *cp; + cp++; + if (oneback == '(' && *cp == '*' && stringLit == FALSE) { + CommentLevel++; + return ++cp; + } + if (*cp == '"' && oneback != '\\') { + stringLit = TRUE; + continue; + } + if (stringLit && *cp == '"' && oneback != '\\') stringLit = FALSE; + } + if (strcmp((const char *)cp, "") == 0 || cp == NULL) return cp; + + while (isIdentifier((int)*cp)) { + vStringPut(identifier, (int)*cp); + cp++; + } + vStringTerminate(identifier); + return cp; +} + +static smlKind findNextIdentifier(const unsigned char **cp) { + smlKind result = K_NONE; + vString *const identifier = vStringNew(); + unsigned int count = sizeof(SmlKeywordTypes) / sizeof(SmlKeywordTypes[0]); + unsigned int i; + *cp = parseIdentifier(*cp, identifier); + for (i = 0; i < count && result == K_NONE; ++i) { + const char *id = vStringValue(identifier); + if (strcmp(id, SmlKeywordTypes[i].keyword) == 0) + result = SmlKeywordTypes[i].kind; + } + vStringDelete(identifier); + return result; +} + +static void findSmlTags(void) { + vString *const identifier = vStringNew(); + const unsigned char *line; + smlKind lastTag = K_NONE; + + while ((line = fileReadLine()) != NULL) { + const unsigned char *cp = skipSpace(line); + do { + smlKind foundTag; + if (CommentLevel != 0) { + cp = (const unsigned char *)strstr((const char *)cp, "*)"); + if (cp == NULL) + continue; + else { + --CommentLevel; + cp += 2; + } + } + foundTag = findNextIdentifier(&cp); + if (foundTag != K_NONE) { + cp = skipSpace(cp); + cp = parseIdentifier(cp, identifier); + if (foundTag == K_AND) + makeSmlTag(lastTag, identifier); + else { + makeSmlTag(foundTag, identifier); + lastTag = foundTag; + } + } + if (strstr((const char *)cp, "(*") != NULL) { + cp += 2; + cp = (const unsigned char *)strstr((const char *)cp, "*)"); + if (cp == NULL) ++CommentLevel; + } + } while (cp != NULL && strcmp((const char *)cp, "") != 0); + } + vStringDelete(identifier); +} + +extern parserDefinition *SmlParser(void) { + static const char *const extensions[] = {"sml", "sig", NULL}; + parserDefinition *def = parserNew("SML"); + def->kinds = SmlKinds; + def->kindCount = KIND_COUNT(SmlKinds); + def->extensions = extensions; + def->parser = findSmlTags; + return def; +} + +/* vi:set tabstop=4 shiftwidth=4: */ diff --git a/third_party/ctags/sort.c b/third_party/ctags/sort.c new file mode 100644 index 00000000..1de1f73f --- /dev/null +++ b/third_party/ctags/sort.c @@ -0,0 +1,196 @@ +/* + * $Id: sort.c 747 2009-11-06 02:33:37Z dhiebert $ + * + * Copyright (c) 1996-2002, Darren Hiebert + * + * This source code is released for free distribution under the terms of the + * GNU General Public License. + * + * This module contains functions to sort the tag entries. + */ +#include "third_party/ctags/general.h" +/* must always come first */ +#include "libc/fmt/fmt.h" +#include "libc/mem/mem.h" +#include "third_party/ctags/debug.h" +#include "third_party/ctags/entry.h" +#include "third_party/ctags/options.h" +#include "third_party/ctags/read.h" +#include "third_party/ctags/routines.h" +#include "third_party/ctags/sort.h" + +/* + * FUNCTION DEFINITIONS + */ + +extern void catFile(const char *const name) { + FILE *const fp = fopen(name, "r"); + + if (fp != NULL) { + int c; + while ((c = getc(fp)) != EOF) putchar(c); + fflush(stdout); + fclose(fp); + } +} + +#ifdef EXTERNAL_SORT + +#ifdef NON_CONST_PUTENV_PROTOTYPE +#define PE_CONST +#else +#define PE_CONST const +#endif + +extern void externalSortTags(const boolean toStdout) { + const char *const sortNormalCommand = "sort -u -o"; + const char *const sortFoldedCommand = "sort -u -f -o"; + const char *sortCommand = + Option.sorted == SO_FOLDSORTED ? sortFoldedCommand : sortNormalCommand; + PE_CONST char *const sortOrder1 = "LC_COLLATE=C"; + PE_CONST char *const sortOrder2 = "LC_ALL=C"; + const size_t length = 4 + strlen(sortOrder1) + strlen(sortOrder2) + + strlen(sortCommand) + (2 * strlen(tagFileName())); + char *const cmd = (char *)malloc(length + 1); + int ret = -1; + + if (cmd != NULL) { + /* Ensure ASCII value sort order. + */ +#ifdef HAVE_SETENV + setenv("LC_COLLATE", "C", 1); + setenv("LC_ALL", "C", 1); + sprintf(cmd, "%s %s %s", sortCommand, tagFileName(), tagFileName()); +#else +#ifdef HAVE_PUTENV + putenv(sortOrder1); + putenv(sortOrder2); + sprintf(cmd, "%s %s %s", sortCommand, tagFileName(), tagFileName()); +#else + sprintf(cmd, "%s %s %s %s %s", sortOrder1, sortOrder2, sortCommand, + tagFileName(), tagFileName()); +#endif +#endif + verbose("system (\"%s\")\n", cmd); + ret = system(cmd); + free(cmd); + } + if (ret != 0) + error(FATAL | PERROR, "cannot sort tag file"); + else if (toStdout) + catFile(tagFileName()); +} + +#else + +/* + * These functions provide a basic internal sort. No great memory + * optimization is performed (e.g. recursive subdivided sorts), + * so have lots of memory if you have large tag files. + */ + +static void failedSort(FILE *const fp, const char *msg) { + const char *const cannotSort = "cannot sort tag file"; + if (fp != NULL) fclose(fp); + if (msg == NULL) + error(FATAL | PERROR, "%s", cannotSort); + else + error(FATAL, "%s: %s", msg, cannotSort); +} + +static int compareTagsFolded(const void *const one, const void *const two) { + const char *const line1 = *(const char *const *)one; + const char *const line2 = *(const char *const *)two; + + return struppercmp(line1, line2); +} + +static int compareTags(const void *const one, const void *const two) { + const char *const line1 = *(const char *const *)one; + const char *const line2 = *(const char *const *)two; + + return strcmp(line1, line2); +} + +static void writeSortedTags(char **const table, const size_t numTags, + const boolean toStdout) { + FILE *fp; + size_t i; + + /* Write the sorted lines back into the tag file. + */ + if (toStdout) + fp = stdout; + else { + fp = fopen(tagFileName(), "w"); + if (fp == NULL) failedSort(fp, NULL); + } + for (i = 0; i < numTags; ++i) { + /* Here we filter out identical tag *lines* (including search + * pattern) if this is not an xref file. + */ + if (i == 0 || Option.xref || strcmp(table[i], table[i - 1]) != 0) + if (fputs(table[i], fp) == EOF) failedSort(fp, NULL); + } + if (toStdout) + fflush(fp); + else + fclose(fp); +} + +extern void internalSortTags(const boolean toStdout) { + vString *vLine = vStringNew(); + FILE *fp = NULL; + const char *line; + size_t i; + int (*cmpFunc)(const void *, const void *); + + /* Allocate a table of line pointers to be sorted. + */ + size_t numTags = TagFile.numTags.added + TagFile.numTags.prev; + const size_t tableSize = numTags * sizeof(char *); + char **const table = (char **)malloc(tableSize); /* line pointers */ + DebugStatement(size_t mallocSize = tableSize;) /* cumulative total */ + + cmpFunc = + Option.sorted == SO_FOLDSORTED ? compareTagsFolded : compareTags; + if (table == NULL) failedSort(fp, "out of memory"); + + /* Open the tag file and place its lines into allocated buffers. + */ + fp = fopen(tagFileName(), "r"); + if (fp == NULL) failedSort(fp, NULL); + for (i = 0; i < numTags && !feof(fp);) { + line = readLine(vLine, fp); + if (line == NULL) { + if (!feof(fp)) failedSort(fp, NULL); + break; + } else if (*line == '\0' || strcmp(line, "\n") == 0) + ; /* ignore blank lines */ + else { + const size_t stringSize = strlen(line) + 1; + + table[i] = (char *)malloc(stringSize); + if (table[i] == NULL) failedSort(fp, "out of memory"); + DebugStatement(mallocSize += stringSize;) strcpy(table[i], line); + ++i; + } + } + numTags = i; + fclose(fp); + vStringDelete(vLine); + + /* Sort the lines. + */ + qsort(table, numTags, sizeof(*table), cmpFunc); + + writeSortedTags(table, numTags, toStdout); + + PrintStatus(("sort memory: %ld bytes\n", (long)mallocSize)); + for (i = 0; i < numTags; ++i) free(table[i]); + free(table); +} + +#endif + +/* vi:set tabstop=4 shiftwidth=4: */ diff --git a/third_party/ctags/sort.h b/third_party/ctags/sort.h new file mode 100644 index 00000000..15e12e03 --- /dev/null +++ b/third_party/ctags/sort.h @@ -0,0 +1,32 @@ +/* +* $Id: sort.h 443 2006-05-30 04:37:13Z darren $ +* +* Copyright (c) 1998-2002, Darren Hiebert +* +* This source code is released for free distribution under the terms of the +* GNU General Public License. +* +* External interface to sort.c +*/ +#ifndef _SORT_H +#define _SORT_H + +/* +* INCLUDE FILES +*/ +#include "third_party/ctags/general.h" /* must always come first */ + +/* +* FUNCTION PROTOTYPES +*/ +extern void catFile (const char *const name); + +#ifdef EXTERNAL_SORT +extern void externalSortTags (const boolean toStdout); +#else +extern void internalSortTags (const boolean toStdout); +#endif + +#endif /* _SORT_H */ + +/* vi:set tabstop=4 shiftwidth=4: */ diff --git a/third_party/ctags/sql.c b/third_party/ctags/sql.c new file mode 100644 index 00000000..da5c8167 --- /dev/null +++ b/third_party/ctags/sql.c @@ -0,0 +1,2174 @@ +/* + * $Id: sql.c 761 2010-06-04 12:40:28Z dfishburn $ + * + * Copyright (c) 2002-2003, Darren Hiebert + * + * This source code is released for free distribution under the + * terms of the GNU General Public License. + * + * This module contains functions for generating tags for PL/SQL language + * files. + */ +#include "third_party/ctags/general.h" +/* must always come first */ +#include "third_party/ctags/debug.h" +#include "third_party/ctags/entry.h" +#include "third_party/ctags/keyword.h" +#include "third_party/ctags/parse.h" +#include "third_party/ctags/read.h" +#include "third_party/ctags/routines.h" +#include "third_party/ctags/vstring.h" + +/* + * On-line "Oracle Database PL/SQL Language Reference": + * http://download.oracle.com/docs/cd/B28359_01/appdev.111/b28370/toc.htm + * + * Sample PL/SQL code is available from: + * http://www.orafaq.com/faqscrpt.htm#GENPLSQL + * + * On-line SQL Anywhere Documentation + * http://www.ianywhere.com/developer/product_manuals/sqlanywhere/index.html + */ + +/* + * MACROS + */ +#define isType(token, t) (boolean)((token)->type == (t)) +#define isKeyword(token, k) (boolean)((token)->keyword == (k)) + +/* + * DATA DECLARATIONS + */ + +typedef enum eException { ExceptionNone, ExceptionEOF } exception_t; + +/* + * Used to specify type of keyword. + */ +typedef enum eKeywordId { + KEYWORD_NONE = -1, + KEYWORD_is, + KEYWORD_begin, + KEYWORD_body, + KEYWORD_cursor, + KEYWORD_declare, + KEYWORD_end, + KEYWORD_function, + KEYWORD_if, + KEYWORD_else, + KEYWORD_elseif, + KEYWORD_endif, + KEYWORD_loop, + KEYWORD_while, + KEYWORD_case, + KEYWORD_for, + KEYWORD_do, + KEYWORD_call, + KEYWORD_package, + KEYWORD_pragma, + KEYWORD_procedure, + KEYWORD_record, + KEYWORD_object, + KEYWORD_ref, + KEYWORD_rem, + KEYWORD_return, + KEYWORD_returns, + KEYWORD_subtype, + KEYWORD_table, + KEYWORD_trigger, + KEYWORD_type, + KEYWORD_index, + KEYWORD_event, + KEYWORD_publication, + KEYWORD_service, + KEYWORD_domain, + KEYWORD_datatype, + KEYWORD_result, + KEYWORD_url, + KEYWORD_internal, + KEYWORD_external, + KEYWORD_when, + KEYWORD_then, + KEYWORD_variable, + KEYWORD_exception, + KEYWORD_at, + KEYWORD_on, + KEYWORD_primary, + KEYWORD_references, + KEYWORD_unique, + KEYWORD_check, + KEYWORD_constraint, + KEYWORD_foreign, + KEYWORD_ml_table, + KEYWORD_ml_table_lang, + KEYWORD_ml_table_dnet, + KEYWORD_ml_table_java, + KEYWORD_ml_table_chk, + KEYWORD_ml_conn, + KEYWORD_ml_conn_lang, + KEYWORD_ml_conn_dnet, + KEYWORD_ml_conn_java, + KEYWORD_ml_conn_chk, + KEYWORD_ml_prop, + KEYWORD_local, + KEYWORD_temporary, + KEYWORD_drop, + KEYWORD_view, + KEYWORD_synonym, + KEYWORD_handler, + KEYWORD_comment, + KEYWORD_create, + KEYWORD_go +} keywordId; + +/* + * Used to determine whether keyword is valid for the token language and + * what its ID is. + */ +typedef struct sKeywordDesc { + const char *name; + keywordId id; +} keywordDesc; + +typedef enum eTokenType { + TOKEN_UNDEFINED, + TOKEN_BLOCK_LABEL_BEGIN, + TOKEN_BLOCK_LABEL_END, + TOKEN_CHARACTER, + TOKEN_CLOSE_PAREN, + TOKEN_COLON, + TOKEN_SEMICOLON, + TOKEN_COMMA, + TOKEN_IDENTIFIER, + TOKEN_KEYWORD, + TOKEN_OPEN_PAREN, + TOKEN_OPERATOR, + TOKEN_OTHER, + TOKEN_STRING, + TOKEN_PERIOD, + TOKEN_OPEN_CURLY, + TOKEN_CLOSE_CURLY, + TOKEN_OPEN_SQUARE, + TOKEN_CLOSE_SQUARE, + TOKEN_TILDE, + TOKEN_FORWARD_SLASH, + TOKEN_EQUAL +} tokenType; + +typedef struct sTokenInfoSQL { + tokenType type; + keywordId keyword; + vString *string; + vString *scope; + int begin_end_nest_lvl; + unsigned long lineNumber; + fpos_t filePosition; +} tokenInfo; + +/* + * DATA DEFINITIONS + */ + +static langType Lang_sql; + +static jmp_buf Exception; + +typedef enum { + SQLTAG_CURSOR, + SQLTAG_PROTOTYPE, + SQLTAG_FUNCTION, + SQLTAG_FIELD, + SQLTAG_LOCAL_VARIABLE, + SQLTAG_BLOCK_LABEL, + SQLTAG_PACKAGE, + SQLTAG_PROCEDURE, + SQLTAG_RECORD, + SQLTAG_SUBTYPE, + SQLTAG_TABLE, + SQLTAG_TRIGGER, + SQLTAG_VARIABLE, + SQLTAG_INDEX, + SQLTAG_EVENT, + SQLTAG_PUBLICATION, + SQLTAG_SERVICE, + SQLTAG_DOMAIN, + SQLTAG_VIEW, + SQLTAG_SYNONYM, + SQLTAG_MLTABLE, + SQLTAG_MLCONN, + SQLTAG_MLPROP, + SQLTAG_COUNT +} sqlKind; + +static kindOption SqlKinds[] = { + {TRUE, 'c', "cursor", "cursors"}, + {FALSE, 'd', "prototype", "prototypes"}, + {TRUE, 'f', "function", "functions"}, + {TRUE, 'F', "field", "record fields"}, + {FALSE, 'l', "local", "local variables"}, + {TRUE, 'L', "label", "block label"}, + {TRUE, 'P', "package", "packages"}, + {TRUE, 'p', "procedure", "procedures"}, + {FALSE, 'r', "record", "records"}, + {TRUE, 's', "subtype", "subtypes"}, + {TRUE, 't', "table", "tables"}, + {TRUE, 'T', "trigger", "triggers"}, + {TRUE, 'v', "variable", "variables"}, + {TRUE, 'i', "index", "indexes"}, + {TRUE, 'e', "event", "events"}, + {TRUE, 'U', "publication", "publications"}, + {TRUE, 'R', "service", "services"}, + {TRUE, 'D', "domain", "domains"}, + {TRUE, 'V', "view", "views"}, + {TRUE, 'n', "synonym", "synonyms"}, + {TRUE, 'x', "mltable", "MobiLink Table Scripts"}, + {TRUE, 'y', "mlconn", "MobiLink Conn Scripts"}, + {TRUE, 'z', "mlprop", "MobiLink Properties "}}; + +static const keywordDesc SqlKeywordTable[] = { + /* keyword keyword ID */ + {"as", KEYWORD_is}, + {"is", KEYWORD_is}, + {"begin", KEYWORD_begin}, + {"body", KEYWORD_body}, + {"cursor", KEYWORD_cursor}, + {"declare", KEYWORD_declare}, + {"end", KEYWORD_end}, + {"function", KEYWORD_function}, + {"if", KEYWORD_if}, + {"else", KEYWORD_else}, + {"elseif", KEYWORD_elseif}, + {"endif", KEYWORD_endif}, + {"loop", KEYWORD_loop}, + {"while", KEYWORD_while}, + {"case", KEYWORD_case}, + {"for", KEYWORD_for}, + {"do", KEYWORD_do}, + {"call", KEYWORD_call}, + {"package", KEYWORD_package}, + {"pragma", KEYWORD_pragma}, + {"procedure", KEYWORD_procedure}, + {"record", KEYWORD_record}, + {"object", KEYWORD_object}, + {"ref", KEYWORD_ref}, + {"rem", KEYWORD_rem}, + {"return", KEYWORD_return}, + {"returns", KEYWORD_returns}, + {"subtype", KEYWORD_subtype}, + {"table", KEYWORD_table}, + {"trigger", KEYWORD_trigger}, + {"type", KEYWORD_type}, + {"index", KEYWORD_index}, + {"event", KEYWORD_event}, + {"publication", KEYWORD_publication}, + {"service", KEYWORD_service}, + {"domain", KEYWORD_domain}, + {"datatype", KEYWORD_datatype}, + {"result", KEYWORD_result}, + {"url", KEYWORD_url}, + {"internal", KEYWORD_internal}, + {"external", KEYWORD_external}, + {"when", KEYWORD_when}, + {"then", KEYWORD_then}, + {"variable", KEYWORD_variable}, + {"exception", KEYWORD_exception}, + {"at", KEYWORD_at}, + {"on", KEYWORD_on}, + {"primary", KEYWORD_primary}, + {"references", KEYWORD_references}, + {"unique", KEYWORD_unique}, + {"check", KEYWORD_check}, + {"constraint", KEYWORD_constraint}, + {"foreign", KEYWORD_foreign}, + {"ml_add_table_script", KEYWORD_ml_table}, + {"ml_add_lang_table_script", KEYWORD_ml_table_lang}, + {"ml_add_dnet_table_script", KEYWORD_ml_table_dnet}, + {"ml_add_java_table_script", KEYWORD_ml_table_java}, + {"ml_add_lang_table_script_chk", KEYWORD_ml_table_chk}, + {"ml_add_connection_script", KEYWORD_ml_conn}, + {"ml_add_lang_connection_script", KEYWORD_ml_conn_lang}, + {"ml_add_dnet_connection_script", KEYWORD_ml_conn_dnet}, + {"ml_add_java_connection_script", KEYWORD_ml_conn_java}, + {"ml_add_lang_conn_script_chk", KEYWORD_ml_conn_chk}, + {"ml_add_property", KEYWORD_ml_prop}, + {"local", KEYWORD_local}, + {"temporary", KEYWORD_temporary}, + {"drop", KEYWORD_drop}, + {"view", KEYWORD_view}, + {"synonym", KEYWORD_synonym}, + {"handler", KEYWORD_handler}, + {"comment", KEYWORD_comment}, + {"create", KEYWORD_create}, + {"go", KEYWORD_go}}; + +/* + * FUNCTION DECLARATIONS + */ + +/* Recursive calls */ +static void parseBlock(tokenInfo *const token, const boolean local); +static void parseDeclare(tokenInfo *const token, const boolean local); +static void parseKeywords(tokenInfo *const token); +static void parseSqlFile(tokenInfo *const token); + +/* + * FUNCTION DEFINITIONS + */ + +static boolean isIdentChar1(const int c) { + /* + * Other databases are less restrictive on the first character of + * an identifier. + * isIdentChar1 is used to identify the first character of an + * identifier, so we are removing some restrictions. + */ + return (boolean)(isalpha(c) || c == '@' || c == '_'); +} + +static boolean isIdentChar(const int c) { + return (boolean)(isalpha(c) || isdigit(c) || c == '$' || c == '@' || + c == '_' || c == '#'); +} + +static boolean isCmdTerm(tokenInfo *const token) { + DebugStatement(debugPrintf(DEBUG_PARSE, + "\n isCmdTerm: token same tt:%d tk:%d\n", + token->type, token->keyword);); + + /* + * Based on the various customer sites I have been at + * the most common command delimiters are + * ; + * ~ + * / + * go + * This routine will check for any of these, more + * can easily be added by modifying readToken and + * either adding the character to: + * enum eTokenType + * enum eTokenType + */ + return (isType(token, TOKEN_SEMICOLON) || isType(token, TOKEN_TILDE) || + isType(token, TOKEN_FORWARD_SLASH) || isKeyword(token, KEYWORD_go)); +} + +static boolean isMatchedEnd(tokenInfo *const token, int nest_lvl) { + boolean terminated = FALSE; + /* + * Since different forms of SQL allow the use of + * BEGIN + * ... + * END + * blocks, some statements may not be terminated using + * the standard delimiters: + * ; + * ~ + * / + * go + * This routine will check to see if we encounter and END + * for the matching nest level of BEGIN ... END statements. + * If we find one, then we can assume, the statement was terminated + * since we have fallen through to the END statement of the BEGIN + * block. + */ + if (nest_lvl > 0 && isKeyword(token, KEYWORD_end)) { + if (token->begin_end_nest_lvl == nest_lvl) terminated = TRUE; + } + + return terminated; +} + +static void buildSqlKeywordHash(void) { + const size_t count = sizeof(SqlKeywordTable) / sizeof(SqlKeywordTable[0]); + size_t i; + for (i = 0; i < count; ++i) { + const keywordDesc *const p = &SqlKeywordTable[i]; + addKeyword(p->name, Lang_sql, (int)p->id); + } +} + +static tokenInfo *newToken(void) { + tokenInfo *const token = xMalloc(1, tokenInfo); + + token->type = TOKEN_UNDEFINED; + token->keyword = KEYWORD_NONE; + token->string = vStringNew(); + token->scope = vStringNew(); + token->begin_end_nest_lvl = 0; + token->lineNumber = getSourceLineNumber(); + token->filePosition = getInputFilePosition(); + + return token; +} + +static void deleteToken(tokenInfo *const token) { + vStringDelete(token->string); + vStringDelete(token->scope); + eFree(token); +} + +/* + * Tag generation functions + */ + +static void makeConstTag(tokenInfo *const token, const sqlKind kind) { + if (SqlKinds[kind].enabled) { + const char *const name = vStringValue(token->string); + tagEntryInfo e; + initTagEntry(&e, name); + + e.lineNumber = token->lineNumber; + e.filePosition = token->filePosition; + e.kindName = SqlKinds[kind].name; + e.kind = SqlKinds[kind].letter; + + makeTagEntry(&e); + } +} + +static void makeSqlTag(tokenInfo *const token, const sqlKind kind) { + vString *fulltag; + + if (SqlKinds[kind].enabled) { + /* + * If a scope has been added to the token, change the token + * string to include the scope when making the tag. + */ + if (vStringLength(token->scope) > 0) { + fulltag = vStringNew(); + vStringCopy(fulltag, token->scope); + vStringCatS(fulltag, "."); + vStringCatS(fulltag, vStringValue(token->string)); + vStringTerminate(fulltag); + vStringCopy(token->string, fulltag); + vStringDelete(fulltag); + } + makeConstTag(token, kind); + } +} + +/* + * Parsing functions + */ + +static void parseString(vString *const string, const int delimiter) { + boolean end = FALSE; + while (!end) { + int c = fileGetc(); + if (c == EOF) end = TRUE; + /* + else if (c == '\\') + { + c = fileGetc(); // This maybe a ' or ". // + vStringPut(string, c); + } + */ + else if (c == delimiter) + end = TRUE; + else + vStringPut(string, c); + } + vStringTerminate(string); +} + +/* Read a C identifier beginning with "firstChar" and places it into + * "name". + */ +static void parseIdentifier(vString *const string, const int firstChar) { + int c = firstChar; + Assert(isIdentChar1(c)); + do { + vStringPut(string, c); + c = fileGetc(); + } while (isIdentChar(c)); + vStringTerminate(string); + if (!isspace(c)) fileUngetc(c); /* unget non-identifier character */ +} + +static void readToken(tokenInfo *const token) { + int c; + + token->type = TOKEN_UNDEFINED; + token->keyword = KEYWORD_NONE; + vStringClear(token->string); + +getNextChar: + do { + c = fileGetc(); + token->lineNumber = getSourceLineNumber(); + token->filePosition = getInputFilePosition(); + /* + * Added " to the list of ignores, not sure what this + * might break but it gets by this issue: + * create table "t1" (...) + * + * Darren, the code passes all my tests for both + * Oracle and SQL Anywhere, but maybe you can tell me + * what this may effect. + */ + } while (c == '\t' || c == ' ' || c == '\n'); + + switch (c) { + case EOF: + longjmp(Exception, (int)ExceptionEOF); + break; + case '(': + token->type = TOKEN_OPEN_PAREN; + break; + case ')': + token->type = TOKEN_CLOSE_PAREN; + break; + case ':': + token->type = TOKEN_COLON; + break; + case ';': + token->type = TOKEN_SEMICOLON; + break; + case '.': + token->type = TOKEN_PERIOD; + break; + case ',': + token->type = TOKEN_COMMA; + break; + case '{': + token->type = TOKEN_OPEN_CURLY; + break; + case '}': + token->type = TOKEN_CLOSE_CURLY; + break; + case '~': + token->type = TOKEN_TILDE; + break; + case '[': + token->type = TOKEN_OPEN_SQUARE; + break; + case ']': + token->type = TOKEN_CLOSE_SQUARE; + break; + case '=': + token->type = TOKEN_EQUAL; + break; + + case '\'': + case '"': + token->type = TOKEN_STRING; + parseString(token->string, c); + token->lineNumber = getSourceLineNumber(); + token->filePosition = getInputFilePosition(); + break; + + case '-': + c = fileGetc(); + if (c == '-') /* -- is this the start of a comment? */ + { + fileSkipToCharacter('\n'); + goto getNextChar; + } else { + if (!isspace(c)) fileUngetc(c); + token->type = TOKEN_OPERATOR; + } + break; + + case '<': + case '>': { + const int initial = c; + int d = fileGetc(); + if (d == initial) { + if (initial == '<') + token->type = TOKEN_BLOCK_LABEL_BEGIN; + else + token->type = TOKEN_BLOCK_LABEL_END; + } else { + fileUngetc(d); + token->type = TOKEN_UNDEFINED; + } + break; + } + + case '\\': + c = fileGetc(); + if (c != '\\' && c != '"' && c != '\'' && !isspace(c)) fileUngetc(c); + token->type = TOKEN_CHARACTER; + token->lineNumber = getSourceLineNumber(); + token->filePosition = getInputFilePosition(); + break; + + case '/': { + int d = fileGetc(); + if ((d != '*') && /* is this the start of a comment? */ + (d != '/')) /* is a one line comment? */ + { + token->type = TOKEN_FORWARD_SLASH; + fileUngetc(d); + } else { + if (d == '*') { + do { + fileSkipToCharacter('*'); + c = fileGetc(); + if (c == '/') + break; + else + fileUngetc(c); + } while (c != EOF && c != '\0'); + goto getNextChar; + } else if (d == '/') /* is this the start of a comment? */ + { + fileSkipToCharacter('\n'); + goto getNextChar; + } + } + break; + } + + default: + if (!isIdentChar1(c)) + token->type = TOKEN_UNDEFINED; + else { + parseIdentifier(token->string, c); + token->lineNumber = getSourceLineNumber(); + token->filePosition = getInputFilePosition(); + token->keyword = analyzeToken(token->string, Lang_sql); + if (isKeyword(token, KEYWORD_rem)) { + vStringClear(token->string); + fileSkipToCharacter('\n'); + goto getNextChar; + } else if (isKeyword(token, KEYWORD_NONE)) + token->type = TOKEN_IDENTIFIER; + else + token->type = TOKEN_KEYWORD; + } + break; + } +} + +/* + * Token parsing functions + */ + +/* + * static void addContext (tokenInfo* const parent, const tokenInfo* const + *child) + * { + * if (vStringLength (parent->string) > 0) + * { + * vStringCatS (parent->string, "."); + * } + * vStringCatS (parent->string, vStringValue(child->string)); + * vStringTerminate(parent->string); + * } + */ + +static void addToScope(tokenInfo *const token, vString *const extra) { + if (vStringLength(token->scope) > 0) { + vStringCatS(token->scope, "."); + } + vStringCatS(token->scope, vStringValue(extra)); + vStringTerminate(token->scope); +} + +/* + * Scanning functions + */ + +static void findToken(tokenInfo *const token, const tokenType type) { + while (!isType(token, type)) { + readToken(token); + } +} + +static void findCmdTerm(tokenInfo *const token, const boolean check_first) { + int begin_end_nest_lvl = token->begin_end_nest_lvl; + + if (check_first) { + if (isCmdTerm(token)) return; + } + do { + readToken(token); + } while (!isCmdTerm(token) && !isMatchedEnd(token, begin_end_nest_lvl)); +} + +static void skipToMatched(tokenInfo *const token) { + int nest_level = 0; + tokenType open_token; + tokenType close_token; + + switch (token->type) { + case TOKEN_OPEN_PAREN: + open_token = TOKEN_OPEN_PAREN; + close_token = TOKEN_CLOSE_PAREN; + break; + case TOKEN_OPEN_CURLY: + open_token = TOKEN_OPEN_CURLY; + close_token = TOKEN_CLOSE_CURLY; + break; + case TOKEN_OPEN_SQUARE: + open_token = TOKEN_OPEN_SQUARE; + close_token = TOKEN_CLOSE_SQUARE; + break; + default: + return; + } + + /* + * This routine will skip to a matching closing token. + * It will also handle nested tokens like the (, ) below. + * ( name varchar(30), text binary(10) ) + */ + + if (isType(token, open_token)) { + nest_level++; + while (!(isType(token, close_token) && (nest_level == 0))) { + readToken(token); + if (isType(token, open_token)) { + nest_level++; + } + if (isType(token, close_token)) { + if (nest_level > 0) { + nest_level--; + } + } + } + readToken(token); + } +} + +static void copyToken(tokenInfo *const dest, tokenInfo *const src) { + dest->lineNumber = src->lineNumber; + dest->filePosition = src->filePosition; + dest->type = src->type; + dest->keyword = src->keyword; + vStringCopy(dest->string, src->string); + vStringCopy(dest->scope, src->scope); +} + +static void skipArgumentList(tokenInfo *const token) { + /* + * Other databases can have arguments with fully declared + * datatypes: + * ( name varchar(30), text binary(10) ) + * So we must check for nested open and closing parantheses + */ + + if (isType(token, TOKEN_OPEN_PAREN)) /* arguments? */ + { + skipToMatched(token); + } +} + +static void parseSubProgram(tokenInfo *const token) { + tokenInfo *const name = newToken(); + vString *saveScope = vStringNew(); + + /* + * This must handle both prototypes and the body of + * the procedures. + * + * Prototype: + * FUNCTION func_name RETURN integer; + * PROCEDURE proc_name( parameters ); + * Procedure + * FUNCTION GET_ML_USERNAME RETURN VARCHAR2 + * IS + * BEGIN + * RETURN v_sync_user_id; + * END GET_ML_USERNAME; + * + * PROCEDURE proc_name( parameters ) + * IS + * BEGIN + * END; + * CREATE PROCEDURE proc_name( parameters ) + * EXTERNAL NAME ... ; + * CREATE PROCEDURE proc_name( parameters ) + * BEGIN + * END; + * + * CREATE FUNCTION f_GetClassName( + * IN @object VARCHAR(128) + * ,IN @code VARCHAR(128) + * ) + * RETURNS VARCHAR(200) + * DETERMINISTIC + * BEGIN + * + * IF( @object = 'user_state' ) THEN + * SET something = something; + * END IF; + * + * RETURN @name; + * END; + * + * Note, a Package adds scope to the items within. + * create or replace package demo_pkg is + * test_var number; + * function test_func return varchar2; + * function more.test_func2 return varchar2; + * end demo_pkg; + * So the tags generated here, contain the package name: + * demo_pkg.test_var + * demo_pkg.test_func + * demo_pkg.more.test_func2 + */ + const sqlKind kind = + isKeyword(token, KEYWORD_function) ? SQLTAG_FUNCTION : SQLTAG_PROCEDURE; + Assert(isKeyword(token, KEYWORD_function) || + isKeyword(token, KEYWORD_procedure)); + + vStringCopy(saveScope, token->scope); + readToken(token); + copyToken(name, token); + readToken(token); + + if (isType(token, TOKEN_PERIOD)) { + /* + * If this is an Oracle package, then the token->scope should + * already be set. If this is the case, also add this value to the + * scope. + * If this is not an Oracle package, chances are the scope should be + * blank and the value just read is the OWNER or CREATOR of the + * function and should not be considered part of the scope. + */ + if (vStringLength(saveScope) > 0) { + addToScope(token, name->string); + } + readToken(token); + copyToken(name, token); + readToken(token); + } + if (isType(token, TOKEN_OPEN_PAREN)) { + /* Reads to the next token after the TOKEN_CLOSE_PAREN */ + skipArgumentList(token); + } + + if (kind == SQLTAG_FUNCTION) { + if (isKeyword(token, KEYWORD_return) || isKeyword(token, KEYWORD_returns)) { + /* Read datatype */ + readToken(token); + /* + * Read token after which could be the + * command terminator if a prototype + * or an open parantheses + */ + readToken(token); + if (isType(token, TOKEN_OPEN_PAREN)) { + /* Reads to the next token after the TOKEN_CLOSE_PAREN */ + skipArgumentList(token); + } + } + } + if (isCmdTerm(token)) { + makeSqlTag(name, SQLTAG_PROTOTYPE); + } else { + while ( + !(isKeyword(token, KEYWORD_is) || isKeyword(token, KEYWORD_begin) || + isKeyword(token, KEYWORD_at) || isKeyword(token, KEYWORD_internal) || + isKeyword(token, KEYWORD_external) || isKeyword(token, KEYWORD_url) || + isType(token, TOKEN_EQUAL) || isCmdTerm(token))) { + if (isKeyword(token, KEYWORD_result)) { + readToken(token); + if (isType(token, TOKEN_OPEN_PAREN)) { + /* Reads to the next token after the TOKEN_CLOSE_PAREN */ + skipArgumentList(token); + } + } else { + readToken(token); + } + } + if (isKeyword(token, KEYWORD_at) || isKeyword(token, KEYWORD_url) || + isKeyword(token, KEYWORD_internal) || + isKeyword(token, KEYWORD_external)) { + addToScope(token, name->string); + if (isType(name, TOKEN_IDENTIFIER) || isType(name, TOKEN_STRING) || + !isKeyword(token, KEYWORD_NONE)) + makeSqlTag(name, kind); + + vStringClear(token->scope); + } + if (isType(token, TOKEN_EQUAL)) readToken(token); + + if (isKeyword(token, KEYWORD_declare)) parseDeclare(token, FALSE); + + if (isKeyword(token, KEYWORD_is) || isKeyword(token, KEYWORD_begin)) { + addToScope(token, name->string); + if (isType(name, TOKEN_IDENTIFIER) || isType(name, TOKEN_STRING) || + !isKeyword(token, KEYWORD_NONE)) + makeSqlTag(name, kind); + + parseBlock(token, TRUE); + vStringClear(token->scope); + } + } + vStringCopy(token->scope, saveScope); + deleteToken(name); + vStringDelete(saveScope); +} + +static void parseRecord(tokenInfo *const token) { + /* + * Make it a bit forgiving, this is called from + * multiple functions, parseTable, parseType + */ + if (!isType(token, TOKEN_OPEN_PAREN)) readToken(token); + + Assert(isType(token, TOKEN_OPEN_PAREN)); + do { + if (isType(token, TOKEN_COMMA) || isType(token, TOKEN_OPEN_PAREN)) + readToken(token); + + /* + * Create table statements can end with various constraints + * which must be excluded from the SQLTAG_FIELD. + * create table t1 ( + * c1 integer, + * c2 char(30), + * c3 numeric(10,5), + * c4 integer, + * constraint whatever, + * primary key(c1), + * foreign key (), + * check () + * ) + */ + if (!(isKeyword(token, KEYWORD_primary) || + isKeyword(token, KEYWORD_references) || + isKeyword(token, KEYWORD_unique) || isKeyword(token, KEYWORD_check) || + isKeyword(token, KEYWORD_constraint) || + isKeyword(token, KEYWORD_foreign))) { + if (isType(token, TOKEN_IDENTIFIER) || isType(token, TOKEN_STRING)) + makeSqlTag(token, SQLTAG_FIELD); + } + + while (!(isType(token, TOKEN_COMMA) || isType(token, TOKEN_CLOSE_PAREN) || + isType(token, TOKEN_OPEN_PAREN))) { + readToken(token); + /* + * A table structure can look like this: + * create table t1 ( + * c1 integer, + * c2 char(30), + * c3 numeric(10,5), + * c4 integer + * ) + * We can't just look for a COMMA or CLOSE_PAREN + * since that will not deal with the numeric(10,5) + * case. So we need to skip the argument list + * when we find an open paren. + */ + if (isType(token, TOKEN_OPEN_PAREN)) { + /* Reads to the next token after the TOKEN_CLOSE_PAREN */ + skipArgumentList(token); + } + } + } while (!isType(token, TOKEN_CLOSE_PAREN)); +} + +static void parseType(tokenInfo *const token) { + tokenInfo *const name = newToken(); + vString *saveScope = vStringNew(); + + vStringCopy(saveScope, token->scope); + /* If a scope has been set, add it to the name */ + addToScope(name, token->scope); + readToken(name); + if (isType(name, TOKEN_IDENTIFIER)) { + readToken(token); + if (isKeyword(token, KEYWORD_is)) { + readToken(token); + addToScope(token, name->string); + switch (token->keyword) { + case KEYWORD_record: + case KEYWORD_object: + makeSqlTag(name, SQLTAG_RECORD); + parseRecord(token); + break; + + case KEYWORD_table: + makeSqlTag(name, SQLTAG_TABLE); + break; + + case KEYWORD_ref: + readToken(token); + if (isKeyword(token, KEYWORD_cursor)) makeSqlTag(name, SQLTAG_CURSOR); + break; + + default: + break; + } + vStringClear(token->scope); + } + } + vStringCopy(token->scope, saveScope); + deleteToken(name); + vStringDelete(saveScope); +} + +static void parseSimple(tokenInfo *const token, const sqlKind kind) { + /* This will simply make the tagname from the first word found */ + readToken(token); + if (isType(token, TOKEN_IDENTIFIER) || isType(token, TOKEN_STRING)) + makeSqlTag(token, kind); +} + +static void parseDeclare(tokenInfo *const token, const boolean local) { + /* + * PL/SQL declares are of this format: + * IS|AS + * [declare] + * CURSOR curname ... + * varname1 datatype; + * varname2 datatype; + * varname3 datatype; + * begin + */ + + if (isKeyword(token, KEYWORD_declare)) readToken(token); + while (!isKeyword(token, KEYWORD_begin) && !isKeyword(token, KEYWORD_end)) { + switch (token->keyword) { + case KEYWORD_cursor: + parseSimple(token, SQLTAG_CURSOR); + break; + case KEYWORD_function: + parseSubProgram(token); + break; + case KEYWORD_procedure: + parseSubProgram(token); + break; + case KEYWORD_subtype: + parseSimple(token, SQLTAG_SUBTYPE); + break; + case KEYWORD_trigger: + parseSimple(token, SQLTAG_TRIGGER); + break; + case KEYWORD_type: + parseType(token); + break; + + default: + if (isType(token, TOKEN_IDENTIFIER)) { + if (local) { + makeSqlTag(token, SQLTAG_LOCAL_VARIABLE); + } else { + makeSqlTag(token, SQLTAG_VARIABLE); + } + } + break; + } + findToken(token, TOKEN_SEMICOLON); + readToken(token); + } +} + +static void parseDeclareANSI(tokenInfo *const token, const boolean local) { + tokenInfo *const type = newToken(); + /* + * ANSI declares are of this format: + * BEGIN + * DECLARE varname1 datatype; + * DECLARE varname2 datatype; + * ... + * + * This differ from PL/SQL where DECLARE preceeds the BEGIN block + * and the DECLARE keyword is not repeated. + */ + while (isKeyword(token, KEYWORD_declare)) { + readToken(token); + readToken(type); + + if (isKeyword(type, KEYWORD_cursor)) + makeSqlTag(token, SQLTAG_CURSOR); + else if (isKeyword(token, KEYWORD_local) && + isKeyword(type, KEYWORD_temporary)) { + /* + * DECLARE LOCAL TEMPORARY TABLE table_name ( + * c1 int, + * c2 int + * ); + */ + readToken(token); + if (isKeyword(token, KEYWORD_table)) { + readToken(token); + if (isType(token, TOKEN_IDENTIFIER) || isType(token, TOKEN_STRING)) { + makeSqlTag(token, SQLTAG_TABLE); + } + } + } else if (isType(token, TOKEN_IDENTIFIER) || isType(token, TOKEN_STRING)) { + if (local) + makeSqlTag(token, SQLTAG_LOCAL_VARIABLE); + else + makeSqlTag(token, SQLTAG_VARIABLE); + } + findToken(token, TOKEN_SEMICOLON); + readToken(token); + } + deleteToken(type); +} + +static void parseLabel(tokenInfo *const token) { + /* + * A label has this format: + * <> + * DECLARE + * v_senator VARCHAR2(100) := 'THURMOND, JESSE'; + * BEGIN + * IF total_contributions (v_senator, 'TOBACCO') > 25000 + * THEN + * <> + * DECLARE + * v_senator VARCHAR2(100) := 'WHATEVERIT, TAKES'; + * BEGIN + * ... + */ + + Assert(isType(token, TOKEN_BLOCK_LABEL_BEGIN)); + readToken(token); + if (isType(token, TOKEN_IDENTIFIER)) { + makeSqlTag(token, SQLTAG_BLOCK_LABEL); + readToken(token); /* read end of label */ + } +} + +static void parseStatements(tokenInfo *const token, + const boolean exit_on_endif) { + boolean isAnsi = TRUE; + boolean stmtTerm = FALSE; + do { + + if (isType(token, TOKEN_BLOCK_LABEL_BEGIN)) + parseLabel(token); + else { + switch (token->keyword) { + case KEYWORD_exception: + /* + * EXCEPTION + * ; + * + * Where an exception handler could be: + * BEGIN + * WHEN OTHERS THEN + * x := x + 3; + * END; + * In this case we need to skip this keyword and + * move on to the next token without reading until + * TOKEN_SEMICOLON; + */ + readToken(token); + continue; + + case KEYWORD_when: + /* + * WHEN statements can be used in exception clauses + * and CASE statements. The CASE statement should skip + * these given below we skip over to an END statement. + * But for an exception clause, we can have: + * EXCEPTION + * WHEN OTHERS THEN + * BEGIN + * x := x + 3; + * END; + * If we skip to the TOKEN_SEMICOLON, we miss the begin + * of a nested BEGIN END block. So read the next token + * after the THEN and restart the LOOP. + */ + while (!isKeyword(token, KEYWORD_then)) readToken(token); + + readToken(token); + continue; + + case KEYWORD_if: + /* + * We do not want to look for a ; since for an empty + * IF block, it would skip over the END. + * IF...THEN + * END IF; + * + * IF...THEN + * ELSE + * END IF; + * + * IF...THEN + * ELSEIF...THEN + * ELSE + * END IF; + * + * or non-ANSI + * IF ... + * BEGIN + * END + */ + while (!isKeyword(token, KEYWORD_then) && + !isKeyword(token, KEYWORD_begin)) { + readToken(token); + } + + if (isKeyword(token, KEYWORD_begin)) { + isAnsi = FALSE; + parseBlock(token, FALSE); + + /* + * Handle the non-Ansi IF blocks. + * parseBlock consumes the END, so if the next + * token in a command terminator (like GO) + * we know we are done with this statement. + */ + if (isCmdTerm(token)) stmtTerm = TRUE; + } else { + readToken(token); + + while (!(isKeyword(token, KEYWORD_end) || + isKeyword(token, KEYWORD_endif))) { + if (isKeyword(token, KEYWORD_else) || + isKeyword(token, KEYWORD_elseif)) + readToken(token); + + parseStatements(token, TRUE); + + if (isCmdTerm(token)) readToken(token); + } + + /* + * parseStatements returns when it finds an END, an IF + * should follow the END for ANSI anyway. + * IF...THEN + * END IF; + */ + if (isKeyword(token, KEYWORD_end)) readToken(token); + + if (isKeyword(token, KEYWORD_if) || + isKeyword(token, KEYWORD_endif)) { + readToken(token); + if (isCmdTerm(token)) stmtTerm = TRUE; + } else { + /* + * Well we need to do something here. + * There are lots of different END statements + * END; + * END CASE; + * ENDIF; + * ENDCASE; + */ + } + } + break; + + case KEYWORD_loop: + case KEYWORD_case: + case KEYWORD_for: + /* + * LOOP... + * END LOOP; + * + * CASE + * WHEN '1' THEN + * END CASE; + * + * FOR loop_name AS cursor_name CURSOR FOR ... + * DO + * END FOR; + */ + if (isKeyword(token, KEYWORD_for)) { + /* loop name */ + readToken(token); + /* AS */ + readToken(token); + + while (!isKeyword(token, KEYWORD_is)) { + /* + * If this is not an AS keyword this is + * not a proper FOR statement and should + * simply be ignored + */ + return; + } + + while (!isKeyword(token, KEYWORD_do)) readToken(token); + } + + readToken(token); + while (!isKeyword(token, KEYWORD_end)) { + /* + if ( isKeyword (token, KEYWORD_else) || + isKeyword (token, KEYWORD_elseif) ) + readToken (token); + */ + + parseStatements(token, FALSE); + + if (isCmdTerm(token)) readToken(token); + } + + if (isKeyword(token, KEYWORD_end)) readToken(token); + + /* + * Typically ended with + * END LOOP [loop name]; + * END CASE + * END FOR [loop name]; + */ + if (isKeyword(token, KEYWORD_loop) || + isKeyword(token, KEYWORD_case) || isKeyword(token, KEYWORD_for)) + readToken(token); + + if (isCmdTerm(token)) stmtTerm = TRUE; + + break; + + case KEYWORD_create: + readToken(token); + parseKeywords(token); + break; + + case KEYWORD_declare: + case KEYWORD_begin: + parseBlock(token, TRUE); + break; + + case KEYWORD_end: + break; + + default: + readToken(token); + break; + } + /* + * Not all statements must end in a semi-colon + * begin + * if current publisher <> 'publish' then + * signal UE_FailStatement + * end if + * end; + * The last statement prior to an end ("signal" above) does + * not need a semi-colon, nor does the end if, since it is + * also the last statement prior to the end of the block. + * + * So we must read to the first semi-colon or an END block + */ + while (!stmtTerm && + !(isKeyword(token, KEYWORD_end) || (isCmdTerm(token)))) { + if (isKeyword(token, KEYWORD_endif) && exit_on_endif) return; + + if (isType(token, TOKEN_COLON)) { + /* + * A : can signal a loop name + * myloop: + * LOOP + * LEAVE myloop; + * END LOOP; + * Unfortunately, labels do not have a + * cmd terminator, therefore we have to check + * if the next token is a keyword and process + * it accordingly. + */ + readToken(token); + if (isKeyword(token, KEYWORD_loop) || + isKeyword(token, KEYWORD_while) || isKeyword(token, KEYWORD_for)) + /* parseStatements (token); */ + return; + } + + readToken(token); + + if (isType(token, TOKEN_OPEN_PAREN) || + isType(token, TOKEN_OPEN_CURLY) || isType(token, TOKEN_OPEN_SQUARE)) + skipToMatched(token); + + /* + * Since we know how to parse various statements + * if we detect them, parse them to completion + */ + if (isType(token, TOKEN_BLOCK_LABEL_BEGIN) || + isKeyword(token, KEYWORD_exception) || + isKeyword(token, KEYWORD_loop) || isKeyword(token, KEYWORD_case) || + isKeyword(token, KEYWORD_for) || isKeyword(token, KEYWORD_begin)) + parseStatements(token, FALSE); + else if (isKeyword(token, KEYWORD_if)) + parseStatements(token, TRUE); + } + } + /* + * We assumed earlier all statements ended with a command terminator. + * See comment above, now, only read if the current token + * is not a command terminator. + */ + if (isCmdTerm(token) && !stmtTerm) stmtTerm = TRUE; + + } while (!isKeyword(token, KEYWORD_end) && + !(exit_on_endif && isKeyword(token, KEYWORD_endif)) && !stmtTerm); +} + +static void parseBlock(tokenInfo *const token, const boolean local) { + if (isType(token, TOKEN_BLOCK_LABEL_BEGIN)) { + parseLabel(token); + readToken(token); + } + if (!isKeyword(token, KEYWORD_begin)) { + readToken(token); + /* + * These are Oracle style declares which generally come + * between an IS/AS and BEGIN block. + */ + parseDeclare(token, local); + } + if (isKeyword(token, KEYWORD_begin)) { + readToken(token); + /* + * Check for ANSI declarations which always follow + * a BEGIN statement. This routine will not advance + * the token if none are found. + */ + parseDeclareANSI(token, local); + token->begin_end_nest_lvl++; + while (!isKeyword(token, KEYWORD_end)) { + parseStatements(token, FALSE); + + if (isCmdTerm(token)) readToken(token); + } + token->begin_end_nest_lvl--; + + /* + * Read the next token (we will assume + * it is the command delimiter) + */ + readToken(token); + + /* + * Check if the END block is terminated + */ + if (!isCmdTerm(token)) { + /* + * Not sure what to do here at the moment. + * I think the routine that calls parseBlock + * must expect the next token has already + * been read since it is possible this + * token is not a command delimiter. + */ + /* findCmdTerm (token, FALSE); */ + } + } +} + +static void parsePackage(tokenInfo *const token) { + /* + * Packages can be specified in a number of ways: + * CREATE OR REPLACE PACKAGE pkg_name AS + * or + * CREATE OR REPLACE PACKAGE owner.pkg_name AS + * or by specifying a package body + * CREATE OR REPLACE PACKAGE BODY pkg_name AS + * CREATE OR REPLACE PACKAGE BODY owner.pkg_name AS + */ + tokenInfo *const name = newToken(); + readToken(name); + if (isKeyword(name, KEYWORD_body)) { + /* + * Ignore the BODY tag since we will process + * the body or prototypes in the same manner + */ + readToken(name); + } + /* Check for owner.pkg_name */ + while (!isKeyword(token, KEYWORD_is)) { + readToken(token); + if (isType(token, TOKEN_PERIOD)) { + readToken(name); + } + } + if (isKeyword(token, KEYWORD_is)) { + if (isType(name, TOKEN_IDENTIFIER) || isType(name, TOKEN_STRING)) + makeSqlTag(name, SQLTAG_PACKAGE); + addToScope(token, name->string); + parseBlock(token, FALSE); + vStringClear(token->scope); + } + findCmdTerm(token, FALSE); + deleteToken(name); +} + +static void parseTable(tokenInfo *const token) { + tokenInfo *const name = newToken(); + + /* + * This deals with these formats: + * create table t1 (c1 int); + * create global tempoary table t2 (c1 int); + * create table "t3" (c1 int); + * create table bob.t4 (c1 int); + * create table bob."t5" (c1 int); + * create table "bob"."t6" (c1 int); + * create table bob."t7" (c1 int); + * Proxy tables use this format: + * create existing table bob."t7" AT '...'; + * SQL Server and Sybase formats + * create table OnlyTable ( + * create table dbo.HasOwner ( + * create table [dbo].[HasOwnerSquare] ( + * create table master.dbo.HasDb ( + * create table master..HasDbNoOwner ( + * create table [master].dbo.[HasDbAndOwnerSquare] ( + * create table [master]..[HasDbNoOwnerSquare] ( + */ + + /* This could be a database, owner or table name */ + readToken(name); + if (isType(name, TOKEN_OPEN_SQUARE)) { + readToken(name); + /* Read close square */ + readToken(token); + } + readToken(token); + if (isType(token, TOKEN_PERIOD)) { + /* + * This could be a owner or table name. + * But this is also a special case since the table can be + * referenced with a blank owner: + * dbname..tablename + */ + readToken(name); + if (isType(name, TOKEN_OPEN_SQUARE)) { + readToken(name); + /* Read close square */ + readToken(token); + } + /* Check if a blank name was provided */ + if (isType(name, TOKEN_PERIOD)) { + readToken(name); + if (isType(name, TOKEN_OPEN_SQUARE)) { + readToken(name); + /* Read close square */ + readToken(token); + } + } + readToken(token); + if (isType(token, TOKEN_PERIOD)) { + /* This can only be the table name */ + readToken(name); + if (isType(name, TOKEN_OPEN_SQUARE)) { + readToken(name); + /* Read close square */ + readToken(token); + } + readToken(token); + } + } + if (isType(token, TOKEN_OPEN_PAREN)) { + if (isType(name, TOKEN_IDENTIFIER) || isType(name, TOKEN_STRING)) { + makeSqlTag(name, SQLTAG_TABLE); + vStringCopy(token->scope, name->string); + parseRecord(token); + vStringClear(token->scope); + } + } else if (isKeyword(token, KEYWORD_at)) { + if (isType(name, TOKEN_IDENTIFIER)) { + makeSqlTag(name, SQLTAG_TABLE); + } + } + findCmdTerm(token, FALSE); + deleteToken(name); +} + +static void parseIndex(tokenInfo *const token) { + tokenInfo *const name = newToken(); + tokenInfo *const owner = newToken(); + + /* + * This deals with these formats + * create index i1 on t1(c1) create index "i2" on t1(c1) + * create virtual unique clustered index "i3" on t1(c1) + * create unique clustered index "i4" on t1(c1) + * create clustered index "i5" on t1(c1) + * create bitmap index "i6" on t1(c1) + */ + + readToken(name); + readToken(token); + if (isType(token, TOKEN_PERIOD)) { + readToken(name); + readToken(token); + } + if (isKeyword(token, KEYWORD_on) && + (isType(name, TOKEN_IDENTIFIER) || isType(name, TOKEN_STRING))) { + readToken(owner); + readToken(token); + if (isType(token, TOKEN_PERIOD)) { + readToken(owner); + readToken(token); + } + addToScope(name, owner->string); + makeSqlTag(name, SQLTAG_INDEX); + } + findCmdTerm(token, FALSE); + deleteToken(name); + deleteToken(owner); +} + +static void parseEvent(tokenInfo *const token) { + tokenInfo *const name = newToken(); + + /* + * This deals with these formats + * create event e1 handler begin end; + * create event "e2" handler begin end; + * create event dba."e3" handler begin end; + * create event "dba"."e4" handler begin end; + */ + + readToken(name); + readToken(token); + if (isType(token, TOKEN_PERIOD)) { + readToken(name); + } + while (!(isKeyword(token, KEYWORD_handler) || + (isType(token, TOKEN_SEMICOLON)))) { + readToken(token); + } + + if (isKeyword(token, KEYWORD_handler) || isType(token, TOKEN_SEMICOLON)) { + makeSqlTag(name, SQLTAG_EVENT); + } + + if (isKeyword(token, KEYWORD_handler)) { + readToken(token); + if (isKeyword(token, KEYWORD_begin)) { + parseBlock(token, TRUE); + } + findCmdTerm(token, TRUE); + } + deleteToken(name); +} + +static void parseTrigger(tokenInfo *const token) { + tokenInfo *const name = newToken(); + tokenInfo *const table = newToken(); + + /* + * This deals with these formats + * create or replace trigger tr1 begin end; + * create trigger "tr2" begin end; + * drop trigger "droptr1"; + * create trigger "tr3" CALL sp_something(); + * create trigger "owner"."tr4" begin end; + * create trigger "tr5" not valid; + * create trigger "tr6" begin end; + */ + + readToken(name); + readToken(token); + if (isType(token, TOKEN_PERIOD)) { + readToken(name); + readToken(token); + } + + while (!isKeyword(token, KEYWORD_on) && !isCmdTerm(token)) { + readToken(token); + } + + /*if (! isType (token, TOKEN_SEMICOLON) ) */ + if (!isCmdTerm(token)) { + readToken(table); + readToken(token); + if (isType(token, TOKEN_PERIOD)) { + readToken(table); + readToken(token); + } + + while (!(isKeyword(token, KEYWORD_begin) || + (isKeyword(token, KEYWORD_call)) || (isCmdTerm(token)))) { + if (isKeyword(token, KEYWORD_declare)) { + addToScope(token, name->string); + parseDeclare(token, TRUE); + vStringClear(token->scope); + } else + readToken(token); + } + + if (isKeyword(token, KEYWORD_begin) || isKeyword(token, KEYWORD_call)) { + addToScope(name, table->string); + makeSqlTag(name, SQLTAG_TRIGGER); + addToScope(token, table->string); + if (isKeyword(token, KEYWORD_begin)) { + parseBlock(token, TRUE); + } + vStringClear(token->scope); + } + } + + findCmdTerm(token, TRUE); + deleteToken(name); + deleteToken(table); +} + +static void parsePublication(tokenInfo *const token) { + tokenInfo *const name = newToken(); + + /* + * This deals with these formats + * create or replace publication pu1 () + * create publication "pu2" () + * create publication dba."pu3" () + * create publication "dba"."pu4" () + */ + + readToken(name); + readToken(token); + if (isType(token, TOKEN_PERIOD)) { + readToken(name); + readToken(token); + } + if (isType(token, TOKEN_OPEN_PAREN)) { + if (isType(name, TOKEN_IDENTIFIER) || isType(name, TOKEN_STRING)) { + makeSqlTag(name, SQLTAG_PUBLICATION); + } + } + findCmdTerm(token, FALSE); + deleteToken(name); +} + +static void parseService(tokenInfo *const token) { + tokenInfo *const name = newToken(); + + /* + * This deals with these formats + * CREATE SERVICE s1 TYPE 'HTML' + * AUTHORIZATION OFF USER DBA AS + * SELECT * + * FROM SYS.SYSTABLE; + * CREATE SERVICE "s2" TYPE 'HTML' + * AUTHORIZATION OFF USER DBA AS + * CALL sp_Something(); + */ + + readToken(name); + readToken(token); + if (isKeyword(token, KEYWORD_type)) { + if (isType(name, TOKEN_IDENTIFIER) || isType(name, TOKEN_STRING)) { + makeSqlTag(name, SQLTAG_SERVICE); + } + } + findCmdTerm(token, FALSE); + deleteToken(name); +} + +static void parseDomain(tokenInfo *const token) { + tokenInfo *const name = newToken(); + + /* + * This deals with these formats + * CREATE DOMAIN|DATATYPE [AS] your_name ...; + */ + + readToken(name); + if (isKeyword(name, KEYWORD_is)) { + readToken(name); + } + readToken(token); + if (isType(name, TOKEN_IDENTIFIER) || isType(name, TOKEN_STRING)) { + makeSqlTag(name, SQLTAG_DOMAIN); + } + findCmdTerm(token, FALSE); + deleteToken(name); +} + +static void parseDrop(tokenInfo *const token) { + /* + * This deals with these formats + * DROP TABLE|PROCEDURE|DOMAIN|DATATYPE name; + * + * Just simply skip over these statements. + * They are often confused with PROCEDURE prototypes + * since the syntax is similar, this effectively deals with + * the issue for all types. + */ + + findCmdTerm(token, FALSE); +} + +static void parseVariable(tokenInfo *const token) { + tokenInfo *const name = newToken(); + + /* + * This deals with these formats + * create variable varname1 integer; + * create variable @varname2 integer; + * create variable "varname3" integer; + * drop variable @varname3; + */ + + readToken(name); + readToken(token); + if ((isType(name, TOKEN_IDENTIFIER) || isType(name, TOKEN_STRING)) && + !isType(token, TOKEN_SEMICOLON)) { + makeSqlTag(name, SQLTAG_VARIABLE); + } + findCmdTerm(token, TRUE); + + deleteToken(name); +} + +static void parseSynonym(tokenInfo *const token) { + tokenInfo *const name = newToken(); + + /* + * This deals with these formats + * create variable varname1 integer; + * create variable @varname2 integer; + * create variable "varname3" integer; + * drop variable @varname3; + */ + + readToken(name); + readToken(token); + if ((isType(name, TOKEN_IDENTIFIER) || isType(name, TOKEN_STRING)) && + isKeyword(token, KEYWORD_for)) { + makeSqlTag(name, SQLTAG_SYNONYM); + } + findCmdTerm(token, TRUE); + + deleteToken(name); +} + +static void parseView(tokenInfo *const token) { + tokenInfo *const name = newToken(); + + /* + * This deals with these formats + * create variable varname1 integer; + * create variable @varname2 integer; + * create variable "varname3" integer; + * drop variable @varname3; + */ + + readToken(name); + readToken(token); + if (isType(token, TOKEN_PERIOD)) { + readToken(name); + readToken(token); + } + if (isType(token, TOKEN_OPEN_PAREN)) { + skipArgumentList(token); + } + + while (!(isKeyword(token, KEYWORD_is) || isType(token, TOKEN_SEMICOLON))) { + readToken(token); + } + + if ((isType(name, TOKEN_IDENTIFIER) || isType(name, TOKEN_STRING)) && + isKeyword(token, KEYWORD_is)) { + makeSqlTag(name, SQLTAG_VIEW); + } + + findCmdTerm(token, TRUE); + + deleteToken(name); +} + +static void parseMLTable(tokenInfo *const token) { + tokenInfo *const version = newToken(); + tokenInfo *const table = newToken(); + tokenInfo *const event = newToken(); + + /* + * This deals with these formats + * call dbo.ml_add_table_script( 'version', 'table_name', 'event', + * 'some SQL statement' + * ); + */ + + readToken(token); + if (isType(token, TOKEN_OPEN_PAREN)) { + readToken(version); + readToken(token); + while (!(isType(token, TOKEN_COMMA) || isType(token, TOKEN_CLOSE_PAREN))) { + readToken(token); + } + + if (isType(token, TOKEN_COMMA)) { + readToken(table); + readToken(token); + while ( + !(isType(token, TOKEN_COMMA) || isType(token, TOKEN_CLOSE_PAREN))) { + readToken(token); + } + + if (isType(token, TOKEN_COMMA)) { + readToken(event); + + if (isType(version, TOKEN_STRING) && isType(table, TOKEN_STRING) && + isType(event, TOKEN_STRING)) { + addToScope(version, table->string); + addToScope(version, event->string); + makeSqlTag(version, SQLTAG_MLTABLE); + } + } + if (!isType(token, TOKEN_CLOSE_PAREN)) + findToken(token, TOKEN_CLOSE_PAREN); + } + } + + findCmdTerm(token, TRUE); + + deleteToken(version); + deleteToken(table); + deleteToken(event); +} + +static void parseMLConn(tokenInfo *const token) { + tokenInfo *const version = newToken(); + tokenInfo *const event = newToken(); + + /* + * This deals with these formats + * call ml_add_connection_script( 'version', 'event', + * 'some SQL statement' + * ); + */ + + readToken(token); + if (isType(token, TOKEN_OPEN_PAREN)) { + readToken(version); + readToken(token); + while (!(isType(token, TOKEN_COMMA) || isType(token, TOKEN_CLOSE_PAREN))) { + readToken(token); + } + + if (isType(token, TOKEN_COMMA)) { + readToken(event); + + if (isType(version, TOKEN_STRING) && isType(event, TOKEN_STRING)) { + addToScope(version, event->string); + makeSqlTag(version, SQLTAG_MLCONN); + } + } + if (!isType(token, TOKEN_CLOSE_PAREN)) findToken(token, TOKEN_CLOSE_PAREN); + } + + findCmdTerm(token, TRUE); + + deleteToken(version); + deleteToken(event); +} + +static void parseMLProp(tokenInfo *const token) { + tokenInfo *const component = newToken(); + tokenInfo *const prop_set_name = newToken(); + tokenInfo *const prop_name = newToken(); + + /* + * This deals with these formats + * ml_add_property ( + * 'comp_name', + * 'prop_set_name', + * 'prop_name', + * 'prop_value' + * ) + */ + + readToken(token); + if (isType(token, TOKEN_OPEN_PAREN)) { + readToken(component); + readToken(token); + while (!(isType(token, TOKEN_COMMA) || isType(token, TOKEN_CLOSE_PAREN))) { + readToken(token); + } + + if (isType(token, TOKEN_COMMA)) { + readToken(prop_set_name); + readToken(token); + while ( + !(isType(token, TOKEN_COMMA) || isType(token, TOKEN_CLOSE_PAREN))) { + readToken(token); + } + + if (isType(token, TOKEN_COMMA)) { + readToken(prop_name); + + if (isType(component, TOKEN_STRING) && + isType(prop_set_name, TOKEN_STRING) && + isType(prop_name, TOKEN_STRING)) { + addToScope(component, prop_set_name->string); + addToScope(component, prop_name->string); + makeSqlTag(component, SQLTAG_MLPROP); + } + } + if (!isType(token, TOKEN_CLOSE_PAREN)) + findToken(token, TOKEN_CLOSE_PAREN); + } + } + + findCmdTerm(token, TRUE); + + deleteToken(component); + deleteToken(prop_set_name); + deleteToken(prop_name); +} + +static void parseComment(tokenInfo *const token) { + /* + * This deals with this statement: + * COMMENT TO PRESERVE FORMAT ON PROCEDURE "DBA"."test" IS + * {create PROCEDURE DBA."test"() + * BEGIN + * signal dave; + * END + * } + * ; + * The comment can contain anything between the CURLY + * braces + * COMMENT ON USER "admin" IS + * 'Administration Group' + * ; + * Or it could be a simple string with no curly braces + */ + while (!isKeyword(token, KEYWORD_is)) { + readToken(token); + } + readToken(token); + if (isType(token, TOKEN_OPEN_CURLY)) { + findToken(token, TOKEN_CLOSE_CURLY); + } + + findCmdTerm(token, TRUE); +} + +static void parseKeywords(tokenInfo *const token) { + switch (token->keyword) { + case KEYWORD_begin: + parseBlock(token, FALSE); + break; + case KEYWORD_comment: + parseComment(token); + break; + case KEYWORD_cursor: + parseSimple(token, SQLTAG_CURSOR); + break; + case KEYWORD_datatype: + parseDomain(token); + break; + case KEYWORD_declare: + parseBlock(token, FALSE); + break; + case KEYWORD_domain: + parseDomain(token); + break; + case KEYWORD_drop: + parseDrop(token); + break; + case KEYWORD_event: + parseEvent(token); + break; + case KEYWORD_function: + parseSubProgram(token); + break; + case KEYWORD_if: + parseStatements(token, FALSE); + break; + case KEYWORD_index: + parseIndex(token); + break; + case KEYWORD_ml_table: + parseMLTable(token); + break; + case KEYWORD_ml_table_lang: + parseMLTable(token); + break; + case KEYWORD_ml_table_dnet: + parseMLTable(token); + break; + case KEYWORD_ml_table_java: + parseMLTable(token); + break; + case KEYWORD_ml_table_chk: + parseMLTable(token); + break; + case KEYWORD_ml_conn: + parseMLConn(token); + break; + case KEYWORD_ml_conn_lang: + parseMLConn(token); + break; + case KEYWORD_ml_conn_dnet: + parseMLConn(token); + break; + case KEYWORD_ml_conn_java: + parseMLConn(token); + break; + case KEYWORD_ml_conn_chk: + parseMLConn(token); + break; + case KEYWORD_ml_prop: + parseMLProp(token); + break; + case KEYWORD_package: + parsePackage(token); + break; + case KEYWORD_procedure: + parseSubProgram(token); + break; + case KEYWORD_publication: + parsePublication(token); + break; + case KEYWORD_service: + parseService(token); + break; + case KEYWORD_subtype: + parseSimple(token, SQLTAG_SUBTYPE); + break; + case KEYWORD_synonym: + parseSynonym(token); + break; + case KEYWORD_table: + parseTable(token); + break; + case KEYWORD_trigger: + parseTrigger(token); + break; + case KEYWORD_type: + parseType(token); + break; + case KEYWORD_variable: + parseVariable(token); + break; + case KEYWORD_view: + parseView(token); + break; + default: + break; + } +} + +static void parseSqlFile(tokenInfo *const token) { + do { + readToken(token); + + if (isType(token, TOKEN_BLOCK_LABEL_BEGIN)) + parseLabel(token); + else + parseKeywords(token); + } while (!isKeyword(token, KEYWORD_end)); +} + +static void initialize(const langType language) { + Assert(sizeof(SqlKinds) / sizeof(SqlKinds[0]) == SQLTAG_COUNT); + Lang_sql = language; + buildSqlKeywordHash(); +} + +static void findSqlTags(void) { + tokenInfo *const token = newToken(); + exception_t exception = (exception_t)(setjmp(Exception)); + + while (exception == ExceptionNone) parseSqlFile(token); + + deleteToken(token); +} + +extern parserDefinition *SqlParser(void) { + static const char *const extensions[] = {"sql", NULL}; + parserDefinition *def = parserNew("SQL"); + def->kinds = SqlKinds; + def->kindCount = KIND_COUNT(SqlKinds); + def->extensions = extensions; + def->parser = findSqlTags; + def->initialize = initialize; + return def; +} + +/* vi:set tabstop=4 shiftwidth=4 noexpandtab: */ diff --git a/third_party/ctags/strlist.c b/third_party/ctags/strlist.c new file mode 100644 index 00000000..e81fde35 --- /dev/null +++ b/third_party/ctags/strlist.c @@ -0,0 +1,237 @@ +/* + * $Id: strlist.c 443 2006-05-30 04:37:13Z darren $ + * + * Copyright (c) 1999-2002, Darren Hiebert + * + * This source code is released for free distribution under the terms of the + * GNU General Public License. + * + * This module contains functions managing resizable string lists. + */ +#include "third_party/ctags/general.h" +/* must always come first */ +#include "third_party/ctags/debug.h" +#include "third_party/ctags/read.h" +#include "third_party/ctags/routines.h" +#include "third_party/ctags/strlist.h" +#include "third_party/musl/fnmatch.h" + +/* + * FUNCTION DEFINITIONS + */ + +extern stringList *stringListNew(void) { + stringList *const result = xMalloc(1, stringList); + result->max = 0; + result->count = 0; + result->list = NULL; + return result; +} + +extern void stringListAdd(stringList *const current, vString *string) { + enum { incrementalIncrease = 10 }; + Assert(current != NULL); + if (current->list == NULL) { + Assert(current->max == 0); + current->count = 0; + current->max = incrementalIncrease; + current->list = xMalloc(current->max, vString *); + } else if (current->count == current->max) { + current->max += incrementalIncrease; + current->list = xRealloc(current->list, current->max, vString *); + } + current->list[current->count++] = string; +} + +extern void stringListRemoveLast(stringList *const current) { + Assert(current != NULL); + Assert(current->count > 0); + --current->count; + current->list[current->count] = NULL; +} + +/* Combine list `from' into `current', deleting `from' */ +extern void stringListCombine(stringList *const current, + stringList *const from) { + unsigned int i; + Assert(current != NULL); + Assert(from != NULL); + for (i = 0; i < from->count; ++i) { + stringListAdd(current, from->list[i]); + from->list[i] = NULL; + } + stringListDelete(from); +} + +extern stringList *stringListNewFromArgv(const char *const *const argv) { + stringList *const result = stringListNew(); + const char *const *p; + Assert(argv != NULL); + for (p = argv; *p != NULL; ++p) stringListAdd(result, vStringNewInit(*p)); + return result; +} + +extern stringList *stringListNewFromFile(const char *const fileName) { + stringList *result = NULL; + FILE *const fp = fopen(fileName, "r"); + if (fp != NULL) { + result = stringListNew(); + while (!feof(fp)) { + vString *const str = vStringNew(); + readLine(str, fp); + vStringStripTrailing(str); + if (vStringLength(str) > 0) + stringListAdd(result, str); + else + vStringDelete(str); + } + } + return result; +} + +extern unsigned int stringListCount(const stringList *const current) { + Assert(current != NULL); + return current->count; +} + +extern vString *stringListItem(const stringList *const current, + const unsigned int indx) { + Assert(current != NULL); + return current->list[indx]; +} + +extern vString *stringListLast(const stringList *const current) { + Assert(current != NULL); + Assert(current->count > 0); + return current->list[current->count - 1]; +} + +extern void stringListClear(stringList *const current) { + unsigned int i; + Assert(current != NULL); + for (i = 0; i < current->count; ++i) { + vStringDelete(current->list[i]); + current->list[i] = NULL; + } + current->count = 0; +} + +extern void stringListDelete(stringList *const current) { + if (current != NULL) { + if (current->list != NULL) { + stringListClear(current); + eFree(current->list); + current->list = NULL; + } + current->max = 0; + current->count = 0; + eFree(current); + } +} + +static boolean compareString(const char *const string, vString *const itm) { + return (boolean)(strcmp(string, vStringValue(itm)) == 0); +} + +static boolean compareStringInsensitive(const char *const string, + vString *const itm) { + return (boolean)(strcasecmp(string, vStringValue(itm)) == 0); +} + +static int stringListIndex(const stringList *const current, + const char *const string, + boolean (*test)(const char *s, vString *const vs)) { + int result = -1; + unsigned int i; + Assert(current != NULL); + Assert(string != NULL); + Assert(test != NULL); + for (i = 0; result == -1 && i < current->count; ++i) + if ((*test)(string, current->list[i])) result = i; + return result; +} + +extern boolean stringListHas(const stringList *const current, + const char *const string) { + boolean result = FALSE; + Assert(current != NULL); + result = stringListIndex(current, string, compareString) != -1; + return result; +} + +extern boolean stringListHasInsensitive(const stringList *const current, + const char *const string) { + boolean result = FALSE; + Assert(current != NULL); + Assert(string != NULL); + result = stringListIndex(current, string, compareStringInsensitive) != -1; + return result; +} + +extern boolean stringListHasTest(const stringList *const current, + boolean (*test)(const char *s)) { + boolean result = FALSE; + unsigned int i; + Assert(current != NULL); + for (i = 0; !result && i < current->count; ++i) + result = (*test)(vStringValue(current->list[i])); + return result; +} + +extern boolean stringListRemoveExtension(stringList *const current, + const char *const extension) { + boolean result = FALSE; + int where; +#ifdef CASE_INSENSITIVE_FILENAMES + where = stringListIndex(current, extension, compareStringInsensitive); +#else + where = stringListIndex(current, extension, compareString); +#endif + if (where != -1) { + memmove(current->list + where, current->list + where + 1, + (current->count - where) * sizeof(*current->list)); + current->list[current->count - 1] = NULL; + --current->count; + result = TRUE; + } + return result; +} + +extern boolean stringListExtensionMatched(const stringList *const current, + const char *const extension) { +#ifdef CASE_INSENSITIVE_FILENAMES + return stringListHasInsensitive(current, extension); +#else + return stringListHas(current, extension); +#endif +} + +static boolean fileNameMatched(const vString *const vpattern, + const char *const fileName) { + const char *const pattern = vStringValue(vpattern); +#if defined(HAVE_FNMATCH) + return (boolean)(fnmatch(pattern, fileName, 0) == 0); +#elif defined(CASE_INSENSITIVE_FILENAMES) + return (boolean)(strcasecmp(pattern, fileName) == 0); +#else + return (boolean)(strcmp(pattern, fileName) == 0); +#endif +} + +extern boolean stringListFileMatched(const stringList *const current, + const char *const fileName) { + boolean result = FALSE; + unsigned int i; + for (i = 0; !result && i < stringListCount(current); ++i) + result = fileNameMatched(stringListItem(current, i), fileName); + return result; +} + +extern void stringListPrint(const stringList *const current) { + unsigned int i; + Assert(current != NULL); + for (i = 0; i < current->count; ++i) + printf("%s%s", (i > 0) ? ", " : "", vStringValue(current->list[i])); +} + +/* vi:set tabstop=4 shiftwidth=4: */ diff --git a/third_party/ctags/strlist.h b/third_party/ctags/strlist.h new file mode 100644 index 00000000..5eb644b3 --- /dev/null +++ b/third_party/ctags/strlist.h @@ -0,0 +1,54 @@ +/* +* $Id: strlist.h 443 2006-05-30 04:37:13Z darren $ +* +* Copyright (c) 1999-2002, Darren Hiebert +* +* This source code is released for free distribution under the terms of the +* GNU General Public License. +* +* Defines external interface to resizable string lists. +*/ +#ifndef _STRLIST_H +#define _STRLIST_H + +/* +* INCLUDE FILES +*/ +#include "third_party/ctags/general.h" /* must always come first */ + +#include "third_party/ctags/vstring.h" + +/* +* DATA DECLARATIONS +*/ +typedef struct sStringList { + unsigned int max; + unsigned int count; + vString **list; +} stringList; + +/* +* FUNCTION PROTOTYPES +*/ +extern stringList *stringListNew (void); +extern void stringListAdd (stringList *const current, vString *string); +extern void stringListRemoveLast (stringList *const current); +extern void stringListCombine (stringList *const current, stringList *const from); +extern stringList* stringListNewFromArgv (const char* const* const list); +extern stringList* stringListNewFromFile (const char* const fileName); +extern void stringListClear (stringList *const current); +extern unsigned int stringListCount (const stringList *const current); +extern vString* stringListItem (const stringList *const current, const unsigned int indx); +extern vString* stringListLast (const stringList *const current); +extern void stringListDelete (stringList *const current); +extern boolean stringListHasInsensitive (const stringList *const current, const char *const string); +extern boolean stringListHas (const stringList *const current, const char *const string); +extern boolean stringListHasTest (const stringList *const current, boolean (*test)(const char *s)); +extern boolean stringListRemoveExtension (stringList* const current, const char* const extension); +extern boolean stringListExtensionMatched (const stringList* const list, const char* const extension); +extern boolean stringListFileMatched (const stringList* const list, const char* const str); +extern void stringListPrint (const stringList *const current); + +#endif /* _STRLIST_H */ + +/* vi:set tabstop=4 shiftwidth=4: */ diff --git a/third_party/ctags/tcl.c b/third_party/ctags/tcl.c new file mode 100644 index 00000000..977f2b32 --- /dev/null +++ b/third_party/ctags/tcl.c @@ -0,0 +1,91 @@ +/* + * $Id: tcl.c 443 2006-05-30 04:37:13Z darren $ + * + * Copyright (c) 2000-2003, Darren Hiebert + * + * This source code is released for free distribution under the terms of the + * GNU General Public License. + * + * This module contains functions for generating tags for TCL scripts. + */ +#include "third_party/ctags/general.h" +/* must always come first */ +#include "third_party/ctags/parse.h" +#include "third_party/ctags/read.h" +#include "third_party/ctags/vstring.h" + +/* + * DATA DEFINITIONS + */ +typedef enum { K_CLASS, K_METHOD, K_PROCEDURE } tclKind; + +static kindOption TclKinds[] = {{TRUE, 'c', "class", "classes"}, + {TRUE, 'm', "method", "methods"}, + {TRUE, 'p', "procedure", "procedures"}}; + +/* + * FUNCTION DEFINITIONS + */ + +static const unsigned char *makeTclTag(const unsigned char *cp, + vString *const name, + const tclKind kind) { + vStringClear(name); + while ((int)*cp != '\0' && !isspace((int)*cp)) { + vStringPut(name, (int)*cp); + ++cp; + } + vStringTerminate(name); + makeSimpleTag(name, TclKinds, kind); + return cp; +} + +static boolean match(const unsigned char *line, const char *word) { + return (boolean)(strncmp((const char *)line, word, strlen(word)) == 0); +} + +static void findTclTags(void) { + vString *name = vStringNew(); + const unsigned char *line; + + while ((line = fileReadLine()) != NULL) { + const unsigned char *cp; + + while (isspace(line[0])) ++line; + + if (line[0] == '\0' || line[0] == '#') continue; + + /* read first word */ + for (cp = line; *cp != '\0' && !isspace((int)*cp); ++cp) + ; + if (!isspace((int)*cp)) continue; + while (isspace((int)*cp)) ++cp; + /* Now `line' points at first word and `cp' points at next word */ + + if (match(line, "proc")) + cp = makeTclTag(cp, name, K_PROCEDURE); + else if (match(line, "class") || match(line, "itcl::class")) + cp = makeTclTag(cp, name, K_CLASS); + else if (match(line, "public") || match(line, "protected") || + match(line, "private")) { + if (match(cp, "method")) { + cp += 6; + while (isspace((int)*cp)) ++cp; + cp = makeTclTag(cp, name, K_METHOD); + } + } + } + vStringDelete(name); +} + +extern parserDefinition *TclParser(void) { + static const char *const extensions[] = {"tcl", "tk", "wish", "itcl", NULL}; + parserDefinition *def = parserNew("Tcl"); + def->kinds = TclKinds; + def->kindCount = KIND_COUNT(TclKinds); + def->extensions = extensions; + def->parser = findTclTags; + return def; +} + +/* vi:set tabstop=4 shiftwidth=4: */ diff --git a/third_party/ctags/tex.c b/third_party/ctags/tex.c new file mode 100644 index 00000000..b2197caa --- /dev/null +++ b/third_party/ctags/tex.c @@ -0,0 +1,476 @@ +/* + * $Id: tex.c 666 2008-05-15 17:47:31Z dfishburn $ + * + * Copyright (c) 2008, David Fishburn + * + * This source code is released for free distribution under the + * terms of the GNU General Public License. + * + * This module contains functions for generating tags for TeX + * language files. + * + * Tex language reference: + * http://en.wikibooks.org/wiki/TeX#The_Structure_of_TeX + */ +#include "third_party/ctags/general.h" +/* must always come first */ +#include "third_party/ctags/debug.h" +#include "third_party/ctags/entry.h" +#include "third_party/ctags/keyword.h" +#include "third_party/ctags/parse.h" +#include "third_party/ctags/read.h" +#include "third_party/ctags/routines.h" +#include "third_party/ctags/vstring.h" + +/* + * MACROS + */ +#define isType(token, t) (boolean)((token)->type == (t)) +#define isKeyword(token, k) (boolean)((token)->keyword == (k)) + +/* + * DATA DECLARATIONS + */ + +typedef enum eException { ExceptionNone, ExceptionEOF } exception_t; + +/* + * Used to specify type of keyword. + */ +typedef enum eKeywordId { + KEYWORD_NONE = -1, + KEYWORD_chapter, + KEYWORD_section, + KEYWORD_subsection, + KEYWORD_subsubsection, + KEYWORD_part, + KEYWORD_paragraph, + KEYWORD_subparagraph, + KEYWORD_include +} keywordId; + +/* Used to determine whether keyword is valid for the token language and + * what its ID is. + */ +typedef struct sKeywordDesc { + const char *name; + keywordId id; +} keywordDesc; + +typedef enum eTokenType { + TOKEN_UNDEFINED, + TOKEN_CHARACTER, + TOKEN_CLOSE_PAREN, + TOKEN_COMMA, + TOKEN_KEYWORD, + TOKEN_OPEN_PAREN, + TOKEN_IDENTIFIER, + TOKEN_STRING, + TOKEN_OPEN_CURLY, + TOKEN_CLOSE_CURLY, + TOKEN_OPEN_SQUARE, + TOKEN_CLOSE_SQUARE, + TOKEN_QUESTION_MARK, + TOKEN_STAR +} tokenType; + +typedef struct sTokenInfo { + tokenType type; + keywordId keyword; + vString *string; + vString *scope; + unsigned long lineNumber; + fpos_t filePosition; +} tokenInfo; + +/* + * DATA DEFINITIONS + */ + +static langType Lang_js; + +static jmp_buf Exception; + +typedef enum { + TEXTAG_CHAPTER, + TEXTAG_SECTION, + TEXTAG_SUBSECTION, + TEXTAG_SUBSUBSECTION, + TEXTAG_PART, + TEXTAG_PARAGRAPH, + TEXTAG_SUBPARAGRAPH, + TEXTAG_INCLUDE, + TEXTAG_COUNT +} texKind; + +static kindOption TexKinds[] = {{TRUE, 'c', "chapter", "chapters"}, + {TRUE, 's', "section", "sections"}, + {TRUE, 'u', "subsection", "subsections"}, + {TRUE, 'b', "subsubsection", "subsubsections"}, + {TRUE, 'p', "part", "parts"}, + {TRUE, 'P', "paragraph", "paragraphs"}, + {TRUE, 'G', "subparagraph", "subparagraphs"}, + {TRUE, 'i', "include", "includes"}}; + +static const keywordDesc TexKeywordTable[] = { + /* keyword keyword ID */ + {"chapter", KEYWORD_chapter}, + {"section", KEYWORD_section}, + {"subsection", KEYWORD_subsection}, + {"subsubsection", KEYWORD_subsubsection}, + {"part", KEYWORD_part}, + {"paragraph", KEYWORD_paragraph}, + {"subparagraph", KEYWORD_subparagraph}, + {"include", KEYWORD_include}}; + +/* + * FUNCTION DEFINITIONS + */ + +static boolean isIdentChar(const int c) { + return (boolean)(isalpha(c) || isdigit(c) || c == '$' || c == '_' || + c == '#' || c == '-' || c == '.'); +} + +static void buildTexKeywordHash(void) { + const size_t count = sizeof(TexKeywordTable) / sizeof(TexKeywordTable[0]); + size_t i; + for (i = 0; i < count; ++i) { + const keywordDesc *const p = &TexKeywordTable[i]; + addKeyword(p->name, Lang_js, (int)p->id); + } +} + +static tokenInfo *newToken(void) { + tokenInfo *const token = xMalloc(1, tokenInfo); + + token->type = TOKEN_UNDEFINED; + token->keyword = KEYWORD_NONE; + token->string = vStringNew(); + token->scope = vStringNew(); + token->lineNumber = getSourceLineNumber(); + token->filePosition = getInputFilePosition(); + + return token; +} + +static void deleteToken(tokenInfo *const token) { + vStringDelete(token->string); + vStringDelete(token->scope); + eFree(token); +} + +/* + * Tag generation functions + */ + +static void makeConstTag(tokenInfo *const token, const texKind kind) { + if (TexKinds[kind].enabled) { + const char *const name = vStringValue(token->string); + tagEntryInfo e; + initTagEntry(&e, name); + + e.lineNumber = token->lineNumber; + e.filePosition = token->filePosition; + e.kindName = TexKinds[kind].name; + e.kind = TexKinds[kind].letter; + + makeTagEntry(&e); + } +} + +static void makeTexTag(tokenInfo *const token, texKind kind) { + vString *fulltag; + + if (TexKinds[kind].enabled) { + /* + * If a scope has been added to the token, change the token + * string to include the scope when making the tag. + */ + if (vStringLength(token->scope) > 0) { + fulltag = vStringNew(); + vStringCopy(fulltag, token->scope); + vStringCatS(fulltag, "."); + vStringCatS(fulltag, vStringValue(token->string)); + vStringTerminate(fulltag); + vStringCopy(token->string, fulltag); + vStringDelete(fulltag); + } + makeConstTag(token, kind); + } +} + +/* + * Parsing functions + */ + +static void parseString(vString *const string, const int delimiter) { + boolean end = FALSE; + while (!end) { + int c = fileGetc(); + if (c == EOF) + end = TRUE; + else if (c == '\\') { + c = fileGetc(); /* This maybe a ' or ". */ + vStringPut(string, c); + } else if (c == delimiter) + end = TRUE; + else + vStringPut(string, c); + } + vStringTerminate(string); +} + +/* + * Read a C identifier beginning with "firstChar" and places it into + * "name". + */ +static void parseIdentifier(vString *const string, const int firstChar) { + int c = firstChar; + Assert(isIdentChar(c)); + do { + vStringPut(string, c); + c = fileGetc(); + } while (isIdentChar(c)); + + vStringTerminate(string); + if (!isspace(c)) fileUngetc(c); /* unget non-identifier character */ +} + +static void readToken(tokenInfo *const token) { + int c; + + token->type = TOKEN_UNDEFINED; + token->keyword = KEYWORD_NONE; + vStringClear(token->string); + +getNextChar: + do { + c = fileGetc(); + token->lineNumber = getSourceLineNumber(); + token->filePosition = getInputFilePosition(); + } while (c == '\t' || c == ' ' || c == '\n'); + + switch (c) { + case EOF: + longjmp(Exception, (int)ExceptionEOF); + break; + case '(': + token->type = TOKEN_OPEN_PAREN; + break; + case ')': + token->type = TOKEN_CLOSE_PAREN; + break; + case ',': + token->type = TOKEN_COMMA; + break; + case '{': + token->type = TOKEN_OPEN_CURLY; + break; + case '}': + token->type = TOKEN_CLOSE_CURLY; + break; + case '[': + token->type = TOKEN_OPEN_SQUARE; + break; + case ']': + token->type = TOKEN_CLOSE_SQUARE; + break; + case '*': + token->type = TOKEN_STAR; + break; + + case '\'': + case '"': + token->type = TOKEN_STRING; + parseString(token->string, c); + token->lineNumber = getSourceLineNumber(); + token->filePosition = getInputFilePosition(); + break; + + case '\\': + /* + * All Tex tags start with a backslash. + * Check if the next character is an alpha character + * else it is not a potential tex tag. + */ + c = fileGetc(); + if (!isalpha(c)) + fileUngetc(c); + else { + parseIdentifier(token->string, c); + token->lineNumber = getSourceLineNumber(); + token->filePosition = getInputFilePosition(); + token->keyword = analyzeToken(token->string, Lang_js); + if (isKeyword(token, KEYWORD_NONE)) + token->type = TOKEN_IDENTIFIER; + else + token->type = TOKEN_KEYWORD; + } + break; + + case '%': + fileSkipToCharacter('\n'); /* % are single line comments */ + goto getNextChar; + break; + + default: + if (!isIdentChar(c)) + token->type = TOKEN_UNDEFINED; + else { + parseIdentifier(token->string, c); + token->lineNumber = getSourceLineNumber(); + token->filePosition = getInputFilePosition(); + token->type = TOKEN_IDENTIFIER; + } + break; + } +} + +static void copyToken(tokenInfo *const dest, tokenInfo *const src) { + dest->lineNumber = src->lineNumber; + dest->filePosition = src->filePosition; + dest->type = src->type; + dest->keyword = src->keyword; + vStringCopy(dest->string, src->string); + vStringCopy(dest->scope, src->scope); +} + +/* + * Scanning functions + */ + +static boolean parseTag(tokenInfo *const token, texKind kind) { + tokenInfo *const name = newToken(); + vString *fullname; + boolean useLongName = TRUE; + + fullname = vStringNew(); + vStringClear(fullname); + + /* + * Tex tags are of these formats: + * \keyword{any number of words} + * \keyword[short desc]{any number of words} + * \keyword*[short desc]{any number of words} + * + * When a keyword is found, loop through all words within + * the curly braces for the tag name. + */ + + if (isType(token, TOKEN_KEYWORD)) { + copyToken(name, token); + readToken(token); + } + + if (isType(token, TOKEN_OPEN_SQUARE)) { + useLongName = FALSE; + + readToken(token); + while (!isType(token, TOKEN_CLOSE_SQUARE)) { + if (isType(token, TOKEN_IDENTIFIER)) { + if (fullname->length > 0) vStringCatS(fullname, " "); + vStringCatS(fullname, vStringValue(token->string)); + } + readToken(token); + } + vStringTerminate(fullname); + vStringCopy(name->string, fullname); + makeTexTag(name, kind); + } + + if (isType(token, TOKEN_STAR)) { + readToken(token); + } + + if (isType(token, TOKEN_OPEN_CURLY)) { + readToken(token); + while (!isType(token, TOKEN_CLOSE_CURLY)) { + /* if (isType (token, TOKEN_IDENTIFIER) && useLongName) */ + if (useLongName) { + if (fullname->length > 0) vStringCatS(fullname, " "); + vStringCatS(fullname, vStringValue(token->string)); + } + readToken(token); + } + if (useLongName) { + vStringTerminate(fullname); + vStringCopy(name->string, fullname); + makeTexTag(name, kind); + } + } + + deleteToken(name); + vStringDelete(fullname); + return TRUE; +} + +static void parseTexFile(tokenInfo *const token) { + do { + readToken(token); + + if (isType(token, TOKEN_KEYWORD)) { + switch (token->keyword) { + case KEYWORD_chapter: + parseTag(token, TEXTAG_CHAPTER); + break; + case KEYWORD_section: + parseTag(token, TEXTAG_SECTION); + break; + case KEYWORD_subsection: + parseTag(token, TEXTAG_SUBSECTION); + break; + case KEYWORD_subsubsection: + parseTag(token, TEXTAG_SUBSUBSECTION); + break; + case KEYWORD_part: + parseTag(token, TEXTAG_PART); + break; + case KEYWORD_paragraph: + parseTag(token, TEXTAG_PARAGRAPH); + break; + case KEYWORD_subparagraph: + parseTag(token, TEXTAG_SUBPARAGRAPH); + break; + case KEYWORD_include: + parseTag(token, TEXTAG_INCLUDE); + break; + default: + break; + } + } + } while (TRUE); +} + +static void initialize(const langType language) { + Assert(sizeof(TexKinds) / sizeof(TexKinds[0]) == TEXTAG_COUNT); + Lang_js = language; + buildTexKeywordHash(); +} + +static void findTexTags(void) { + tokenInfo *const token = newToken(); + exception_t exception; + + exception = (exception_t)(setjmp(Exception)); + while (exception == ExceptionNone) parseTexFile(token); + + deleteToken(token); +} + +/* Create parser definition stucture */ +extern parserDefinition *TexParser(void) { + static const char *const extensions[] = {"tex", NULL}; + parserDefinition *const def = parserNew("Tex"); + def->extensions = extensions; + /* + * New definitions for parsing instead of regex + */ + def->kinds = TexKinds; + def->kindCount = KIND_COUNT(TexKinds); + def->parser = findTexTags; + def->initialize = initialize; + + return def; +} +/* vi:set tabstop=4 shiftwidth=4 noexpandtab: */ diff --git a/third_party/ctags/verilog.c b/third_party/ctags/verilog.c new file mode 100644 index 00000000..131e48d4 --- /dev/null +++ b/third_party/ctags/verilog.c @@ -0,0 +1,293 @@ +/* + * $Id: verilog.c 753 2010-02-27 17:53:32Z elliotth $ + * + * Copyright (c) 2003, Darren Hiebert + * + * This source code is released for free distribution under the terms of the + * GNU General Public License. + * + * This module contains functions for generating tags for the Verilog HDL + * (Hardware Description Language). + * + * Language definition documents: + * http://www.eg.bucknell.edu/~cs320/verilog/verilog-manual.html + * http://www.sutherland-hdl.com/on-line_ref_guide/vlog_ref_top.html + * http://www.verilog.com/VerilogBNF.html + * http://eesun.free.fr/DOC/VERILOG/verilog_manual1.html + */ +#include "third_party/ctags/general.h" +/* must always come first */ +#include "third_party/ctags/debug.h" +#include "third_party/ctags/get.h" +#include "third_party/ctags/keyword.h" +#include "third_party/ctags/parse.h" +#include "third_party/ctags/read.h" +#include "third_party/ctags/vstring.h" + +/* + * DATA DECLARATIONS + */ +typedef enum eException { ExceptionNone, ExceptionEOF } exception_t; + +typedef enum { + K_UNDEFINED = -1, + K_CONSTANT, + K_EVENT, + K_FUNCTION, + K_MODULE, + K_NET, + K_PORT, + K_REGISTER, + K_TASK +} verilogKind; + +typedef struct { + const char *keyword; + verilogKind kind; +} keywordAssoc; + +/* + * DATA DEFINITIONS + */ +static int Ungetc; +static int Lang_verilog; +static jmp_buf Exception; + +static kindOption VerilogKinds[] = { + {TRUE, 'c', "constant", "constants (define, parameter, specparam)"}, + {TRUE, 'e', "event", "events"}, + {TRUE, 'f', "function", "functions"}, + {TRUE, 'm', "module", "modules"}, + {TRUE, 'n', "net", "net data types"}, + {TRUE, 'p', "port", "ports"}, + {TRUE, 'r', "register", "register data types"}, + {TRUE, 't', "task", "tasks"}, +}; + +static keywordAssoc VerilogKeywordTable[] = { + {"`define", K_CONSTANT}, + {"event", K_EVENT}, + {"function", K_FUNCTION}, + {"inout", K_PORT}, + {"input", K_PORT}, + {"integer", K_REGISTER}, + {"module", K_MODULE}, + {"output", K_PORT}, + {"parameter", K_CONSTANT}, + {"real", K_REGISTER}, + {"realtime", K_REGISTER}, + {"reg", K_REGISTER}, + {"specparam", K_CONSTANT}, + {"supply0", K_NET}, + {"supply1", K_NET}, + {"task", K_TASK}, + {"time", K_REGISTER}, + {"tri0", K_NET}, + {"tri1", K_NET}, + {"triand", K_NET}, + {"tri", K_NET}, + {"trior", K_NET}, + {"trireg", K_NET}, + {"wand", K_NET}, + {"wire", K_NET}, + {"wor", K_NET}, +}; + +/* + * FUNCTION DEFINITIONS + */ + +static void initialize(const langType language) { + size_t i; + const size_t count = + sizeof(VerilogKeywordTable) / sizeof(VerilogKeywordTable[0]); + Lang_verilog = language; + for (i = 0; i < count; ++i) { + const keywordAssoc *const p = &VerilogKeywordTable[i]; + addKeyword(p->keyword, language, (int)p->kind); + } +} + +static void vUngetc(int c) { + Assert(Ungetc == '\0'); + Ungetc = c; +} + +static int vGetc(void) { + int c; + if (Ungetc == '\0') + c = fileGetc(); + else { + c = Ungetc; + Ungetc = '\0'; + } + if (c == '/') { + int c2 = fileGetc(); + if (c2 == EOF) + longjmp(Exception, (int)ExceptionEOF); + else if (c2 == '/') /* strip comment until end-of-line */ + { + do + c = fileGetc(); + while (c != '\n' && c != EOF); + } else if (c2 == '*') /* strip block comment */ + { + c = skipOverCComment(); + } else { + fileUngetc(c2); + } + } else if (c == '"') /* strip string contents */ + { + int c2; + do + c2 = fileGetc(); + while (c2 != '"' && c2 != EOF); + c = '@'; + } + if (c == EOF) longjmp(Exception, (int)ExceptionEOF); + return c; +} + +static boolean isIdentifierCharacter(const int c) { + return (boolean)(isalnum(c) || c == '_' || c == '`'); +} + +static int skipWhite(int c) { + while (isspace(c)) c = vGetc(); + return c; +} + +static int skipPastMatch(const char *const pair) { + const int begin = pair[0], end = pair[1]; + int matchLevel = 1; + int c; + do { + c = vGetc(); + if (c == begin) + ++matchLevel; + else if (c == end) + --matchLevel; + } while (matchLevel > 0); + return vGetc(); +} + +static boolean readIdentifier(vString *const name, int c) { + vStringClear(name); + if (isIdentifierCharacter(c)) { + while (isIdentifierCharacter(c)) { + vStringPut(name, c); + c = vGetc(); + } + vUngetc(c); + vStringTerminate(name); + } + return (boolean)(name->length > 0); +} + +static void tagNameList(const verilogKind kind, int c) { + vString *name = vStringNew(); + boolean repeat; + Assert(isIdentifierCharacter(c)); + do { + repeat = FALSE; + if (isIdentifierCharacter(c)) { + readIdentifier(name, c); + makeSimpleTag(name, VerilogKinds, kind); + } else + break; + c = skipWhite(vGetc()); + if (c == '[') c = skipPastMatch("[]"); + c = skipWhite(c); + if (c == '=') { + c = skipWhite(vGetc()); + if (c == '{') + skipPastMatch("{}"); + else { + do + c = vGetc(); + while (c != ',' && c != ';'); + } + } + if (c == ',') { + c = skipWhite(vGetc()); + repeat = TRUE; + } else + repeat = FALSE; + } while (repeat); + vStringDelete(name); + vUngetc(c); +} + +static void findTag(vString *const name) { + const verilogKind kind = + (verilogKind)lookupKeyword(vStringValue(name), Lang_verilog); + if (kind == K_CONSTANT && vStringItem(name, 0) == '`') { + /* Bug #961001: Verilog compiler directives are line-based. */ + int c = skipWhite(vGetc()); + readIdentifier(name, c); + makeSimpleTag(name, VerilogKinds, kind); + /* Skip the rest of the line. */ + do { + c = vGetc(); + } while (c != '\n'); + vUngetc(c); + } else if (kind != K_UNDEFINED) { + int c = skipWhite(vGetc()); + + /* Many keywords can have bit width. + * reg [3:0] net_name; + * inout [(`DBUSWIDTH-1):0] databus; + */ + if (c == '(') c = skipPastMatch("()"); + c = skipWhite(c); + if (c == '[') c = skipPastMatch("[]"); + c = skipWhite(c); + if (c == '#') { + c = vGetc(); + if (c == '(') c = skipPastMatch("()"); + } + c = skipWhite(c); + if (isIdentifierCharacter(c)) tagNameList(kind, c); + } +} + +static void findVerilogTags(void) { + vString *const name = vStringNew(); + volatile boolean newStatement = TRUE; + volatile int c = '\0'; + exception_t exception = (exception_t)setjmp(Exception); + + if (exception == ExceptionNone) + while (c != EOF) { + c = vGetc(); + switch (c) { + case ';': + case '\n': + newStatement = TRUE; + break; + + case ' ': + case '\t': + break; + + default: + if (newStatement && readIdentifier(name, c)) findTag(name); + newStatement = FALSE; + break; + } + } + vStringDelete(name); +} + +extern parserDefinition *VerilogParser(void) { + static const char *const extensions[] = {"v", NULL}; + parserDefinition *def = parserNew("Verilog"); + def->kinds = VerilogKinds; + def->kindCount = KIND_COUNT(VerilogKinds); + def->extensions = extensions; + def->parser = findVerilogTags; + def->initialize = initialize; + return def; +} + +/* vi:set tabstop=4 shiftwidth=4: */ diff --git a/third_party/ctags/vhdl.c b/third_party/ctags/vhdl.c new file mode 100644 index 00000000..a738761c --- /dev/null +++ b/third_party/ctags/vhdl.c @@ -0,0 +1,737 @@ +/* + * $Id: vhdl.c 652 2008-04-18 03:51:47Z elliotth $ + * + * Copyright (c) 2008, Nicolas Vincent + * + * This source code is released for free distribution under the terms of the + * GNU General Public License. + * + * This module contains functions for generating tags for VHDL files. + */ +#include "third_party/ctags/general.h" +/* must always come first */ +#include "third_party/ctags/debug.h" +#include "third_party/ctags/entry.h" +#include "third_party/ctags/keyword.h" +#include "third_party/ctags/parse.h" +#include "third_party/ctags/read.h" +#include "third_party/ctags/routines.h" +#include "third_party/ctags/vstring.h" + +/* + * MACROS + */ +#define isType(token, t) (boolean)((token)->type == (t)) +#define isKeyword(token, k) (boolean)((token)->keyword == (k)) + +/* + * DATA DECLARATIONS + */ +typedef enum eException { ExceptionNone, ExceptionEOF } exception_t; + +/* + * Used to specify type of keyword. + */ +typedef enum eKeywordId { + KEYWORD_NONE = -1, + KEYWORD_ABS, + KEYWORD_ACCESS, + KEYWORD_AFTER, + KEYWORD_ALIAS, + KEYWORD_ALL, + KEYWORD_AND, + KEYWORD_ARCHITECTURE, + KEYWORD_ARRAY, + KEYWORD_ASSERT, + KEYWORD_ATTRIBUTE, + KEYWORD_BEGIN, + KEYWORD_BLOCK, + KEYWORD_BODY, + KEYWORD_BUFFER, + KEYWORD_BUS, + KEYWORD_CASE, + KEYWORD_COMPONENT, + KEYWORD_CONFIGURATION, + KEYWORD_CONSTANT, + KEYWORD_DISCONNECT, + KEYWORD_DOWNTO, + KEYWORD_ELSE, + KEYWORD_ELSIF, + KEYWORD_END, + KEYWORD_ENTITY, + KEYWORD_EXIT, + KEYWORD_FILE, + KEYWORD_FOR, + KEYWORD_FUNCTION, + KEYWORD_GENERATE, + KEYWORD_GENERIC, + KEYWORD_GROUP, + KEYWORD_GUARDED, + KEYWORD_IF, + KEYWORD_IMPURE, + KEYWORD_IN, + KEYWORD_INERTIAL, + KEYWORD_INOUT, + KEYWORD_IS, + KEYWORD_LABEL, + KEYWORD_LIBRARY, + KEYWORD_LINKAGE, + KEYWORD_LITERAL, + KEYWORD_LOOP, + KEYWORD_MAP, + KEYWORD_MOD, + KEYWORD_NAND, + KEYWORD_NEW, + KEYWORD_NEXT, + KEYWORD_NOR, + KEYWORD_NOT, + KEYWORD_NULL, + KEYWORD_OF, + KEYWORD_ON, + KEYWORD_OPEN, + KEYWORD_OR, + KEYWORD_OTHERS, + KEYWORD_OUT, + KEYWORD_PACKAGE, + KEYWORD_PORT, + KEYWORD_POSTPONED, + KEYWORD_PROCEDURE, + KEYWORD_PROCESS, + KEYWORD_PURE, + KEYWORD_RANGE, + KEYWORD_RECORD, + KEYWORD_REGISTER, + KEYWORD_REJECT, + KEYWORD_RETURN, + KEYWORD_ROL, + KEYWORD_ROR, + KEYWORD_SELECT, + KEYWORD_SEVERITY, + KEYWORD_SIGNAL, + KEYWORD_SHARED, + KEYWORD_SLA, + KEYWORD_SLI, + KEYWORD_SRA, + KEYWORD_SRL, + KEYWORD_SUBTYPE, + KEYWORD_THEN, + KEYWORD_TO, + KEYWORD_TRANSPORT, + KEYWORD_TYPE, + KEYWORD_UNAFFECTED, + KEYWORD_UNITS, + KEYWORD_UNTIL, + KEYWORD_USE, + KEYWORD_VARIABLE, + KEYWORD_WAIT, + KEYWORD_WHEN, + KEYWORD_WHILE, + KEYWORD_WITH, + KEYWORD_XNOR, + KEYWORD_XOR +} keywordId; + +/* Used to determine whether keyword is valid for the current language and + * what its ID is. + */ +typedef struct sKeywordDesc { + const char *name; + keywordId id; +} keywordDesc; + +typedef enum eTokenType { + TOKEN_NONE, /* none */ + TOKEN_OPEN_PAREN, /* ( */ + TOKEN_CLOSE_PAREN, /* ) */ + TOKEN_COMMA, /* the comma character */ + TOKEN_IDENTIFIER, + TOKEN_KEYWORD, + TOKEN_PERIOD, /* . */ + TOKEN_OPERATOR, + TOKEN_SEMICOLON, /* the semicolon character */ + TOKEN_STRING +} tokenType; + +typedef struct sTokenInfo { + tokenType type; + keywordId keyword; + vString *string; /* the name of the token */ + vString *scope; + unsigned long lineNumber; /* line number of tag */ + fpos_t filePosition; /* file position of line containing name */ +} tokenInfo; + +/* + * DATA DEFINITIONS + */ +static int Lang_vhdl; +static jmp_buf Exception; + +/* Used to index into the VhdlKinds table. */ +typedef enum { + VHDLTAG_UNDEFINED = -1, + VHDLTAG_CONSTANT, + VHDLTAG_TYPE, + VHDLTAG_SUBTYPE, + VHDLTAG_RECORD, + VHDLTAG_ENTITY, + VHDLTAG_COMPONENT, + VHDLTAG_PROTOTYPE, + VHDLTAG_FUNCTION, + VHDLTAG_PROCEDURE, + VHDLTAG_PACKAGE, + VHDLTAG_LOCAL +} vhdlKind; + +static kindOption VhdlKinds[] = { + {TRUE, 'c', "constant", "constant declarations"}, + {TRUE, 't', "type", "type definitions"}, + {TRUE, 'T', "subtype", "subtype definitions"}, + {TRUE, 'r', "record", "record names"}, + {TRUE, 'e', "entity", "entity declarations"}, + {FALSE, 'C', "component", "component declarations"}, + {FALSE, 'd', "prototype", "prototypes"}, + {TRUE, 'f', "function", "function prototypes and declarations"}, + {TRUE, 'p', "procedure", "procedure prototypes and declarations"}, + {TRUE, 'P', "package", "package definitions"}, + {FALSE, 'l', "local", "local definitions"}}; + +static keywordDesc VhdlKeywordTable[] = { + {"abs", KEYWORD_ABS}, + {"access", KEYWORD_ACCESS}, + {"after", KEYWORD_AFTER}, + {"alias", KEYWORD_ALIAS}, + {"all", KEYWORD_ALL}, + {"and", KEYWORD_AND}, + {"architecture", KEYWORD_ARCHITECTURE}, + {"array", KEYWORD_ARRAY}, + {"assert", KEYWORD_ASSERT}, + {"attribute", KEYWORD_ATTRIBUTE}, + {"begin", KEYWORD_BEGIN}, + {"block", KEYWORD_BLOCK}, + {"body", KEYWORD_BODY}, + {"buffer", KEYWORD_BUFFER}, + {"bus", KEYWORD_BUS}, + {"case", KEYWORD_CASE}, + {"component", KEYWORD_COMPONENT}, + {"configuration", KEYWORD_CONFIGURATION}, + {"constant", KEYWORD_CONSTANT}, + {"disconnect", KEYWORD_DISCONNECT}, + {"downto", KEYWORD_DOWNTO}, + {"else", KEYWORD_ELSE}, + {"elsif", KEYWORD_ELSIF}, + {"end", KEYWORD_END}, + {"entity", KEYWORD_ENTITY}, + {"exit", KEYWORD_EXIT}, + {"file", KEYWORD_FILE}, + {"for", KEYWORD_FOR}, + {"function", KEYWORD_FUNCTION}, + {"generate", KEYWORD_GENERATE}, + {"generic", KEYWORD_GENERIC}, + {"group", KEYWORD_GROUP}, + {"guarded", KEYWORD_GUARDED}, + {"if", KEYWORD_IF}, + {"impure", KEYWORD_IMPURE}, + {"in", KEYWORD_IN}, + {"inertial", KEYWORD_INERTIAL}, + {"inout", KEYWORD_INOUT}, + {"is", KEYWORD_IS}, + {"label", KEYWORD_LABEL}, + {"library", KEYWORD_LIBRARY}, + {"linkage", KEYWORD_LINKAGE}, + {"literal", KEYWORD_LITERAL}, + {"loop", KEYWORD_LOOP}, + {"map", KEYWORD_MAP}, + {"mod", KEYWORD_MOD}, + {"nand", KEYWORD_NAND}, + {"new", KEYWORD_NEW}, + {"next", KEYWORD_NEXT}, + {"nor", KEYWORD_NOR}, + {"not", KEYWORD_NOT}, + {"null", KEYWORD_NULL}, + {"of", KEYWORD_OF}, + {"on", KEYWORD_ON}, + {"open", KEYWORD_OPEN}, + {"or", KEYWORD_OR}, + {"others", KEYWORD_OTHERS}, + {"out", KEYWORD_OUT}, + {"package", KEYWORD_PACKAGE}, + {"port", KEYWORD_PORT}, + {"postponed", KEYWORD_POSTPONED}, + {"procedure", KEYWORD_PROCEDURE}, + {"process", KEYWORD_PROCESS}, + {"pure", KEYWORD_PURE}, + {"range", KEYWORD_RANGE}, + {"record", KEYWORD_RECORD}, + {"register", KEYWORD_REGISTER}, + {"reject", KEYWORD_REJECT}, + {"return", KEYWORD_RETURN}, + {"rol", KEYWORD_ROL}, + {"ror", KEYWORD_ROR}, + {"select", KEYWORD_SELECT}, + {"severity", KEYWORD_SEVERITY}, + {"signal", KEYWORD_SIGNAL}, + {"shared", KEYWORD_SHARED}, + {"sla", KEYWORD_SLA}, + {"sli", KEYWORD_SLI}, + {"sra", KEYWORD_SRA}, + {"srl", KEYWORD_SRL}, + {"subtype", KEYWORD_SUBTYPE}, + {"then", KEYWORD_THEN}, + {"to", KEYWORD_TO}, + {"transport", KEYWORD_TRANSPORT}, + {"type", KEYWORD_TYPE}, + {"unaffected", KEYWORD_UNAFFECTED}, + {"units", KEYWORD_UNITS}, + {"until", KEYWORD_UNTIL}, + {"use", KEYWORD_USE}, + {"variable", KEYWORD_VARIABLE}, + {"wait", KEYWORD_WAIT}, + {"when", KEYWORD_WHEN}, + {"while", KEYWORD_WHILE}, + {"with", KEYWORD_WITH}, + {"xnor", KEYWORD_XNOR}, + {"xor", KEYWORD_XOR}}; + +/* + * FUNCTION DECLARATIONS + */ +static void parseKeywords(tokenInfo *const token, boolean local); + +/* + * FUNCTION DEFINITIONS + */ + +static boolean isIdentChar1(const int c) { + return (boolean)(isalpha(c) || c == '_'); +} + +static boolean isIdentChar(const int c) { + return (boolean)(isalpha(c) || isdigit(c) || c == '_'); +} + +static boolean isIdentifierMatch(const tokenInfo *const token, + const vString *const name) { + return (boolean)( + isType(token, TOKEN_IDENTIFIER) && + strcasecmp(vStringValue(token->string), vStringValue(name)) == 0); + /* XXX this is copy/paste from eiffel.c and slightly modified */ + /* shouldn't we use strNcasecmp ? */ +} + +static boolean isKeywordOrIdent(const tokenInfo *const token, + const keywordId keyword, + const vString *const name) { + return (boolean)(isKeyword(token, keyword) || isIdentifierMatch(token, name)); +} + +static tokenInfo *newToken(void) { + tokenInfo *const token = xMalloc(1, tokenInfo); + token->type = TOKEN_NONE; + token->keyword = KEYWORD_NONE; + token->string = vStringNew(); + token->scope = vStringNew(); + token->lineNumber = getSourceLineNumber(); + token->filePosition = getInputFilePosition(); + return token; +} + +static void deleteToken(tokenInfo *const token) { + if (token != NULL) { + vStringDelete(token->string); + vStringDelete(token->scope); + eFree(token); + } +} + +/* + * Parsing functions + */ + +static void parseString(vString *const string, const int delimiter) { + boolean end = FALSE; + while (!end) { + int c = fileGetc(); + if (c == EOF) + end = TRUE; + else if (c == '\\') { + c = fileGetc(); /* This maybe a ' or ". */ + vStringPut(string, c); + } else if (c == delimiter) + end = TRUE; + else + vStringPut(string, c); + } + vStringTerminate(string); +} + +/* Read a VHDL identifier beginning with "firstChar" and place it into "name". + */ +static void parseIdentifier(vString *const string, const int firstChar) { + int c = firstChar; + Assert(isIdentChar1(c)); + do { + vStringPut(string, c); + c = fileGetc(); + } while (isIdentChar(c)); + vStringTerminate(string); + if (!isspace(c)) fileUngetc(c); /* unget non-identifier character */ +} + +static void readToken(tokenInfo *const token) { + int c; + + token->type = TOKEN_NONE; + token->keyword = KEYWORD_NONE; + vStringClear(token->string); + +getNextChar: + do { + c = fileGetc(); + token->lineNumber = getSourceLineNumber(); + token->filePosition = getInputFilePosition(); + } while (c == '\t' || c == ' ' || c == '\n'); + + switch (c) { + case EOF: + longjmp(Exception, (int)ExceptionEOF); + break; + case '(': + token->type = TOKEN_OPEN_PAREN; + break; + case ')': + token->type = TOKEN_CLOSE_PAREN; + break; + case ';': + token->type = TOKEN_SEMICOLON; + break; + case '.': + token->type = TOKEN_PERIOD; + break; + case ',': + token->type = TOKEN_COMMA; + break; + case '\'': /* only single char are inside simple quotes */ + break; /* or it is for attributes so we don't care */ + case '"': + token->type = TOKEN_STRING; + parseString(token->string, c); + token->lineNumber = getSourceLineNumber(); + token->filePosition = getInputFilePosition(); + break; + case '-': + c = fileGetc(); + if (c == '-') /* start of a comment */ + { + fileSkipToCharacter('\n'); + goto getNextChar; + } else { + if (!isspace(c)) fileUngetc(c); + token->type = TOKEN_OPERATOR; + } + break; + default: + if (!isIdentChar1(c)) + token->type = TOKEN_NONE; + else { + parseIdentifier(token->string, c); + token->lineNumber = getSourceLineNumber(); + token->filePosition = getInputFilePosition(); + token->keyword = analyzeToken(token->string, Lang_vhdl); + if (isKeyword(token, KEYWORD_NONE)) + token->type = TOKEN_IDENTIFIER; + else + token->type = TOKEN_KEYWORD; + } + break; + } +} + +static void skipToKeyword(const keywordId keyword) { + tokenInfo *const token = newToken(); + do { + readToken(token); + } while (!isKeyword(token, keyword)); + deleteToken(token); +} + +static void skipToMatched(tokenInfo *const token) { + int nest_level = 0; + tokenType open_token; + tokenType close_token; + + switch (token->type) { + case TOKEN_OPEN_PAREN: + open_token = TOKEN_OPEN_PAREN; + close_token = TOKEN_CLOSE_PAREN; + break; + default: + return; + } + + /* + * This routine will skip to a matching closing token. + * It will also handle nested tokens like the (, ) below. + * ( name varchar(30), text binary(10) ) + */ + if (isType(token, open_token)) { + nest_level++; + while (!(isType(token, close_token) && (nest_level == 0))) { + readToken(token); + if (isType(token, open_token)) { + nest_level++; + } + if (isType(token, close_token)) { + if (nest_level > 0) { + nest_level--; + } + } + } + readToken(token); + } +} + +static void makeConstTag(tokenInfo *const token, const vhdlKind kind) { + if (VhdlKinds[kind].enabled) { + const char *const name = vStringValue(token->string); + tagEntryInfo e; + initTagEntry(&e, name); + e.lineNumber = token->lineNumber; + e.filePosition = token->filePosition; + e.kindName = VhdlKinds[kind].name; + e.kind = VhdlKinds[kind].letter; + makeTagEntry(&e); + } +} + +static void makeVhdlTag(tokenInfo *const token, const vhdlKind kind) { + if (VhdlKinds[kind].enabled) { + /* + * If a scope has been added to the token, change the token + * string to include the scope when making the tag. + */ + if (vStringLength(token->scope) > 0) { + vString *fulltag = vStringNew(); + vStringCopy(fulltag, token->scope); + vStringCatS(fulltag, "."); + vStringCatS(fulltag, vStringValue(token->string)); + vStringTerminate(fulltag); + vStringCopy(token->string, fulltag); + vStringDelete(fulltag); + } + makeConstTag(token, kind); + } +} + +static void initialize(const langType language) { + size_t i; + const size_t count = sizeof(VhdlKeywordTable) / sizeof(VhdlKeywordTable[0]); + Lang_vhdl = language; + for (i = 0; i < count; ++i) { + const keywordDesc *const p = &VhdlKeywordTable[i]; + addKeyword(p->name, language, (int)p->id); + } +} + +static void parsePackage(tokenInfo *const token) { + tokenInfo *const name = newToken(); + Assert(isKeyword(token, KEYWORD_PACKAGE)); + readToken(token); + if (isKeyword(token, KEYWORD_BODY)) { + readToken(name); + makeVhdlTag(name, VHDLTAG_PACKAGE); + } else if (isType(token, TOKEN_IDENTIFIER)) { + makeVhdlTag(token, VHDLTAG_PACKAGE); + } + deleteToken(name); +} + +static void parseModule(tokenInfo *const token) { + tokenInfo *const name = newToken(); + const vhdlKind kind = + isKeyword(token, KEYWORD_ENTITY) ? VHDLTAG_ENTITY : VHDLTAG_COMPONENT; + Assert(isKeyword(token, KEYWORD_ENTITY) || + isKeyword(token, KEYWORD_COMPONENT)); + readToken(name); + if (kind == VHDLTAG_COMPONENT) { + makeVhdlTag(name, VHDLTAG_COMPONENT); + skipToKeyword(KEYWORD_END); + fileSkipToCharacter(';'); + } else { + readToken(token); + if (isKeyword(token, KEYWORD_IS)) { + makeVhdlTag(name, VHDLTAG_ENTITY); + skipToKeyword(KEYWORD_END); + fileSkipToCharacter(';'); + } + } + deleteToken(name); +} + +static void parseRecord(tokenInfo *const token) { + tokenInfo *const name = newToken(); + Assert(isKeyword(token, KEYWORD_RECORD)); + readToken(name); + do { + readToken(token); /* should be a colon */ + fileSkipToCharacter(';'); + makeVhdlTag(name, VHDLTAG_RECORD); + readToken(name); + } while (!isKeyword(name, KEYWORD_END)); + fileSkipToCharacter(';'); + deleteToken(name); +} + +static void parseTypes(tokenInfo *const token) { + tokenInfo *const name = newToken(); + const vhdlKind kind = + isKeyword(token, KEYWORD_TYPE) ? VHDLTAG_TYPE : VHDLTAG_SUBTYPE; + Assert(isKeyword(token, KEYWORD_TYPE) || isKeyword(token, KEYWORD_SUBTYPE)); + readToken(name); + readToken(token); + if (isKeyword(token, KEYWORD_IS)) { + readToken(token); /* type */ + if (isKeyword(token, KEYWORD_RECORD)) { + makeVhdlTag(name, kind); + /*TODO: make tags of the record's names */ + parseRecord(token); + } else { + makeVhdlTag(name, kind); + } + } + deleteToken(name); +} + +static void parseConstant(boolean local) { + tokenInfo *const name = newToken(); + readToken(name); + if (local) { + makeVhdlTag(name, VHDLTAG_LOCAL); + } else { + makeVhdlTag(name, VHDLTAG_CONSTANT); + } + fileSkipToCharacter(';'); + deleteToken(name); +} + +static void parseSubProgram(tokenInfo *const token) { + tokenInfo *const name = newToken(); + boolean endSubProgram = FALSE; + const vhdlKind kind = + isKeyword(token, KEYWORD_FUNCTION) ? VHDLTAG_FUNCTION : VHDLTAG_PROCEDURE; + Assert(isKeyword(token, KEYWORD_FUNCTION) || + isKeyword(token, KEYWORD_PROCEDURE)); + readToken(name); /* the name of the function or procedure */ + readToken(token); + if (isType(token, TOKEN_OPEN_PAREN)) { + skipToMatched(token); + } + + if (kind == VHDLTAG_FUNCTION) { + if (isKeyword(token, KEYWORD_RETURN)) { + /* Read datatype */ + readToken(token); + while (!isKeyword(token, KEYWORD_IS) && !isType(token, TOKEN_SEMICOLON)) { + readToken(token); + } + } + } + + if (isType(token, TOKEN_SEMICOLON)) { + makeVhdlTag(name, VHDLTAG_PROTOTYPE); + } else if (isKeyword(token, KEYWORD_IS)) { + if (kind == VHDLTAG_FUNCTION) { + makeVhdlTag(name, VHDLTAG_FUNCTION); + do { + readToken(token); + if (isKeyword(token, KEYWORD_END)) { + readToken(token); + endSubProgram = + isKeywordOrIdent(token, KEYWORD_FUNCTION, name->string); + fileSkipToCharacter(';'); + } else { + parseKeywords(token, TRUE); + } + } while (!endSubProgram); + } else { + makeVhdlTag(name, VHDLTAG_PROCEDURE); + do { + readToken(token); + if (isKeyword(token, KEYWORD_END)) { + readToken(token); + endSubProgram = + isKeywordOrIdent(token, KEYWORD_PROCEDURE, name->string); + fileSkipToCharacter(';'); + } else { + parseKeywords(token, TRUE); + } + } while (!endSubProgram); + } + } + deleteToken(name); +} + +/* TODO */ +/* records */ +static void parseKeywords(tokenInfo *const token, boolean local) { + switch (token->keyword) { + case KEYWORD_END: + fileSkipToCharacter(';'); + break; + case KEYWORD_CONSTANT: + parseConstant(local); + break; + case KEYWORD_TYPE: + parseTypes(token); + break; + case KEYWORD_SUBTYPE: + parseTypes(token); + break; + case KEYWORD_ENTITY: + parseModule(token); + break; + case KEYWORD_COMPONENT: + parseModule(token); + break; + case KEYWORD_FUNCTION: + parseSubProgram(token); + break; + case KEYWORD_PROCEDURE: + parseSubProgram(token); + break; + case KEYWORD_PACKAGE: + parsePackage(token); + break; + default: + break; + } +} + +static void parseVhdlFile(tokenInfo *const token) { + do { + readToken(token); + parseKeywords(token, FALSE); + } while (!isKeyword(token, KEYWORD_END)); +} + +static void findVhdlTags(void) { + tokenInfo *const token = newToken(); + exception_t exception = (exception_t)(setjmp(Exception)); + + while (exception == ExceptionNone) parseVhdlFile(token); + + deleteToken(token); +} + +extern parserDefinition *VhdlParser(void) { + static const char *const extensions[] = {"vhdl", "vhd", NULL}; + parserDefinition *def = parserNew("VHDL"); + def->kinds = VhdlKinds; + def->kindCount = KIND_COUNT(VhdlKinds); + def->extensions = extensions; + def->parser = findVhdlTags; + def->initialize = initialize; + return def; +} + +/* vi:set tabstop=4 shiftwidth=4 noet: */ diff --git a/third_party/ctags/vim.c b/third_party/ctags/vim.c new file mode 100644 index 00000000..396ce8d5 --- /dev/null +++ b/third_party/ctags/vim.c @@ -0,0 +1,547 @@ +/* + * $Id: vim.c 762 2010-07-28 11:38:19Z dfishburn $ + * + * Copyright (c) 2000-2003, Darren Hiebert + * + * This source code is released for free distribution under the + * terms of the GNU General Public License. + * + * Thanks are due to Jay Glanville for significant improvements. + * + * This module contains functions for generating tags for user-defined + * functions for the Vim editor. + */ +#include "third_party/ctags/general.h" +/* must always come first */ +#include "third_party/ctags/parse.h" +#include "third_party/ctags/read.h" +#include "third_party/ctags/vstring.h" + +#if 0 +typedef struct sLineInfo { + tokenType type; + keywordId keyword; + vString * string; + vString * scope; + unsigned long lineNumber; + fpos_t filePosition; +} lineInfo; +#endif + +/* + * DATA DEFINITIONS + */ +typedef enum { K_AUGROUP, K_COMMAND, K_FUNCTION, K_MAP, K_VARIABLE } vimKind; + +static kindOption VimKinds[] = { + {TRUE, 'a', "augroup", "autocommand groups"}, + {TRUE, 'c', "command", "user-defined commands"}, + {TRUE, 'f', "function", "function definitions"}, + {TRUE, 'm', "map", "maps"}, + {TRUE, 'v', "variable", "variable definitions"}, +}; + +/* + * DATA DECLARATIONS + */ + +#if 0 +typedef enum eException { + ExceptionNone, ExceptionEOF +} exception_t; +#endif + +/* + * DATA DEFINITIONS + */ + +#if 0 +static jmp_buf Exception; +#endif + +/* + * FUNCTION DEFINITIONS + */ + +/* This function takes a char pointer, tries to find a scope separator in the + * string, and if it does, returns a pointer to the character after the colon, + * and the character defining the scope. + * If a colon is not found, it returns the original pointer. + */ +static const unsigned char *skipPrefix(const unsigned char *name, int *scope) { + const unsigned char *result = name; + int counter; + size_t length; + length = strlen((const char *)name); + if (scope != NULL) *scope = '\0'; + if (length > 3 && name[1] == ':') { + if (scope != NULL) *scope = *name; + result = name + 2; + } else if (length > 5 && + strncasecmp((const char *)name, "", (size_t)5) == 0) { + if (scope != NULL) *scope = *name; + result = name + 5; + } else { + /* + * Vim7 check for dictionaries or autoload function names + */ + counter = 0; + do { + switch (name[counter]) { + case '.': + /* Set the scope to d - Dictionary */ + *scope = 'd'; + break; + case '#': + /* Set the scope to a - autoload */ + *scope = 'a'; + break; + } + ++counter; + } while (isalnum((int)name[counter]) || name[counter] == '_' || + name[counter] == '.' || name[counter] == '#'); + } + return result; +} + +static boolean isMap(const unsigned char *line) { + /* + * There are many different short cuts for specifying a map. + * This routine should capture all the permutations. + */ + if (strncmp((const char *)line, "map", (size_t)3) == 0 || + strncmp((const char *)line, "nm", (size_t)2) == 0 || + strncmp((const char *)line, "nma", (size_t)3) == 0 || + strncmp((const char *)line, "nmap", (size_t)4) == 0 || + strncmp((const char *)line, "vm", (size_t)2) == 0 || + strncmp((const char *)line, "vma", (size_t)3) == 0 || + strncmp((const char *)line, "vmap", (size_t)4) == 0 || + strncmp((const char *)line, "om", (size_t)2) == 0 || + strncmp((const char *)line, "oma", (size_t)3) == 0 || + strncmp((const char *)line, "omap", (size_t)4) == 0 || + strncmp((const char *)line, "im", (size_t)2) == 0 || + strncmp((const char *)line, "ima", (size_t)3) == 0 || + strncmp((const char *)line, "imap", (size_t)4) == 0 || + strncmp((const char *)line, "lm", (size_t)2) == 0 || + strncmp((const char *)line, "lma", (size_t)3) == 0 || + strncmp((const char *)line, "lmap", (size_t)4) == 0 || + strncmp((const char *)line, "cm", (size_t)2) == 0 || + strncmp((const char *)line, "cma", (size_t)3) == 0 || + strncmp((const char *)line, "cmap", (size_t)4) == 0 || + strncmp((const char *)line, "no", (size_t)2) == 0 || + strncmp((const char *)line, "nor", (size_t)3) == 0 || + strncmp((const char *)line, "nore", (size_t)4) == 0 || + strncmp((const char *)line, "norem", (size_t)5) == 0 || + strncmp((const char *)line, "norema", (size_t)6) == 0 || + strncmp((const char *)line, "noremap", (size_t)7) == 0 || + strncmp((const char *)line, "nno", (size_t)3) == 0 || + strncmp((const char *)line, "nnor", (size_t)4) == 0 || + strncmp((const char *)line, "nnore", (size_t)5) == 0 || + strncmp((const char *)line, "nnorem", (size_t)6) == 0 || + strncmp((const char *)line, "nnorema", (size_t)7) == 0 || + strncmp((const char *)line, "nnoremap", (size_t)8) == 0 || + strncmp((const char *)line, "vno", (size_t)3) == 0 || + strncmp((const char *)line, "vnor", (size_t)4) == 0 || + strncmp((const char *)line, "vnore", (size_t)5) == 0 || + strncmp((const char *)line, "vnorem", (size_t)6) == 0 || + strncmp((const char *)line, "vnorema", (size_t)7) == 0 || + strncmp((const char *)line, "vnoremap", (size_t)8) == 0 || + strncmp((const char *)line, "ono", (size_t)3) == 0 || + strncmp((const char *)line, "onor", (size_t)4) == 0 || + strncmp((const char *)line, "onore", (size_t)5) == 0 || + strncmp((const char *)line, "onorem", (size_t)6) == 0 || + strncmp((const char *)line, "onorema", (size_t)7) == 0 || + strncmp((const char *)line, "onoremap", (size_t)8) == 0 || + strncmp((const char *)line, "ino", (size_t)3) == 0 || + strncmp((const char *)line, "inor", (size_t)4) == 0 || + strncmp((const char *)line, "inore", (size_t)5) == 0 || + strncmp((const char *)line, "inorem", (size_t)6) == 0 || + strncmp((const char *)line, "inorema", (size_t)7) == 0 || + strncmp((const char *)line, "inoremap", (size_t)8) == 0 || + strncmp((const char *)line, "lno", (size_t)3) == 0 || + strncmp((const char *)line, "lnor", (size_t)4) == 0 || + strncmp((const char *)line, "lnore", (size_t)5) == 0 || + strncmp((const char *)line, "lnorem", (size_t)6) == 0 || + strncmp((const char *)line, "lnorema", (size_t)7) == 0 || + strncmp((const char *)line, "lnoremap", (size_t)8) == 0 || + strncmp((const char *)line, "cno", (size_t)3) == 0 || + strncmp((const char *)line, "cnor", (size_t)4) == 0 || + strncmp((const char *)line, "cnore", (size_t)5) == 0 || + strncmp((const char *)line, "cnorem", (size_t)6) == 0 || + strncmp((const char *)line, "cnorema", (size_t)7) == 0 || + strncmp((const char *)line, "cnoremap", (size_t)8) == 0) + return TRUE; + + return FALSE; +} + +static const unsigned char *readVimLine(void) { + const unsigned char *line; + + while ((line = fileReadLine()) != NULL) { + while (isspace((int)*line)) ++line; + + if ((int)*line == '"') continue; /* skip comment */ + + break; + } + + return line; +} + +static void parseFunction(const unsigned char *line) { + vString *name = vStringNew(); + /* boolean inFunction = FALSE; */ + int scope; + + const unsigned char *cp = line + 1; + + if ((int)*++cp == 'n' && (int)*++cp == 'c' && (int)*++cp == 't' && + (int)*++cp == 'i' && (int)*++cp == 'o' && (int)*++cp == 'n') + ++cp; + if ((int)*cp == '!') ++cp; + if (isspace((int)*cp)) { + while (*cp && isspace((int)*cp)) ++cp; + + if (*cp) { + cp = skipPrefix(cp, &scope); + if (isupper((int)*cp) || scope == 's' || /* script scope */ + scope == '<' || /* script scope */ + scope == 'd' || /* dictionary */ + scope == 'a') /* autoload */ + { + do { + vStringPut(name, (int)*cp); + ++cp; + } while (isalnum((int)*cp) || *cp == '_' || *cp == '.' || *cp == '#'); + vStringTerminate(name); + makeSimpleTag(name, VimKinds, K_FUNCTION); + vStringClear(name); + } + } + } + + /* TODO - update struct to indicate inside function */ + while ((line = readVimLine()) != NULL) { + /* + * Vim7 added the for/endfo[r] construct, so we must first + * check for an "endfo", before a "endf" + */ + if ((!strncmp((const char *)line, "endfo", (size_t)5) == 0) && + (strncmp((const char *)line, "endf", (size_t)4) == 0)) + break; + /* TODO - call parseVimLine */ + } + vStringDelete(name); +} + +static void parseAutogroup(const unsigned char *line) { + vString *name = vStringNew(); + + /* Found Autocommand Group (augroup) */ + const unsigned char *cp = line + 2; + if ((int)*++cp == 'r' && (int)*++cp == 'o' && (int)*++cp == 'u' && + (int)*++cp == 'p') + ++cp; + if (isspace((int)*cp)) { + while (*cp && isspace((int)*cp)) ++cp; + + if (*cp) { + if (strncasecmp((const char *)cp, "end", (size_t)3) != 0) { + do { + vStringPut(name, (int)*cp); + ++cp; + } while (isalnum((int)*cp) || *cp == '_'); + vStringTerminate(name); + makeSimpleTag(name, VimKinds, K_AUGROUP); + vStringClear(name); + } + } + } + vStringDelete(name); +} + +static boolean parseCommand(const unsigned char *line) { + vString *name = vStringNew(); + boolean cmdProcessed = TRUE; + + /* + * Found a user-defined command + * + * They can have many options preceeded by a dash + * command! -nargs=+ -complete Select :call s:DB_execSql("select " . + * ) The name of the command should be the first word not preceeded by + * a dash + * + */ + const unsigned char *cp = line; + + if ((int)*cp == '\\') { + /* + * We are recursively calling this function is the command + * has been continued on to the next line + * + * Vim statements can be continued onto a newline using a \ + * to indicate the previous line is continuing. + * + * com -nargs=1 -bang -complete=customlist,EditFileComplete + * \ EditFile edit + * + * If the following lines do not have a line continuation + * the command must not be spanning multiple lines and should + * be synatically incorrect. + */ + if ((int)*cp == '\\') ++cp; + + while (*cp && isspace((int)*cp)) ++cp; + } else if ((!strncmp((const char *)line, "comp", (size_t)4) == 0) && + (!strncmp((const char *)line, "comc", (size_t)4) == 0) && + (strncmp((const char *)line, "com", (size_t)3) == 0)) { + cp += 2; + if ((int)*++cp == 'm' && (int)*++cp == 'a' && (int)*++cp == 'n' && + (int)*++cp == 'd') + ++cp; + + if ((int)*cp == '!') ++cp; + + if ((int)*cp != ' ') { + /* + * :command must be followed by a space. If it is not, it is + * not a valid command. + * Treat the line as processed and continue. + */ + cmdProcessed = TRUE; + goto cleanUp; + } + + while (*cp && isspace((int)*cp)) ++cp; + } else { + /* + * We are recursively calling this function. If it does not start + * with "com" or a line continuation character, we have moved off + * the command line and should let the other routines parse this file. + */ + cmdProcessed = FALSE; + goto cleanUp; + } + + /* + * Strip off any spaces and options which are part of the command. + * These should preceed the command name. + */ + do { + if (isspace((int)*cp)) { + ++cp; + } else if (*cp == '-') { + /* + * Read until the next space which separates options or the name + */ + while (*cp && !isspace((int)*cp)) ++cp; + } else + break; + } while (*cp); + + if (!*cp) { + /* + * We have reached the end of the line without finding the command name. + * Read the next line and continue processing it as a command. + */ + line = readVimLine(); + parseCommand(line); + goto cleanUp; + } + + do { + vStringPut(name, (int)*cp); + ++cp; + } while (isalnum((int)*cp) || *cp == '_'); + + vStringTerminate(name); + makeSimpleTag(name, VimKinds, K_COMMAND); + vStringClear(name); + +cleanUp: + vStringDelete(name); + + return cmdProcessed; +} + +static void parseLet(const unsigned char *line) { + vString *name = vStringNew(); + + /* we've found a variable declared outside of a function!! */ + const unsigned char *cp = line + 3; + const unsigned char *np = line; + /* get the name */ + if (isspace((int)*cp)) { + while (*cp && isspace((int)*cp)) ++cp; + + /* + * Ignore lets which set: + * & - local buffer vim settings + * @ - registers + * [ - Lists or Dictionaries + */ + if (!*cp || *cp == '&' || *cp == '@' || *cp == '[') goto cleanUp; + + /* + * Ignore vim variables which are read only + * v: - Vim variables. + */ + np = cp; + ++np; + if ((int)*cp == 'v' && (int)*np == ':') goto cleanUp; + + /* deal with spaces, $, @ and & */ + while (*cp && *cp != '$' && !isalnum((int)*cp)) ++cp; + + if (!*cp) goto cleanUp; + + /* cp = skipPrefix (cp, &scope); */ + do { + if (!*cp) break; + + vStringPut(name, (int)*cp); + ++cp; + } while (isalnum((int)*cp) || *cp == '_' || *cp == '#' || *cp == ':' || + *cp == '$'); + vStringTerminate(name); + makeSimpleTag(name, VimKinds, K_VARIABLE); + vStringClear(name); + } + +cleanUp: + vStringDelete(name); +} + +static boolean parseMap(const unsigned char *line) { + vString *name = vStringNew(); + + const unsigned char *cp = line; + + /* Remove map */ + while (*cp && isalnum((int)*cp)) ++cp; + + if ((int)*cp == '!') ++cp; + + /* + * Maps follow this basic format + * map + * nnoremap :Tlist + * map scdt GetColumnDataType + * inoremap ,,, diwi<pa>pa>kA + * inoremap ( =PreviewFunctionSignature() + * + * The Vim help shows the various special arguments available to a map: + * 1.2 SPECIAL ARGUMENTS *:map-arguments* + * + * + *