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 00b8bc39..94651402 100755 Binary files a/build/bootstrap/mkdeps.com and b/build/bootstrap/mkdeps.com differ diff --git a/build/compile b/build/compile index a40cf8f5..6989f225 100755 --- a/build/compile +++ b/build/compile @@ -151,9 +151,11 @@ if [ "${PLAT#*clang}" != "${PLAT}" ]; then FIRST=0 continue fi + TRAPV= # clang handles -f{,no-}{trap,wrap}v weird # removes flags clang whines about case "$x" in -gstabs) ;; + -ftrapv) ;; -ffixed-*) ;; -fcall-saved*) ;; -fsignaling-nans) ;; @@ -161,6 +163,7 @@ if [ "${PLAT#*clang}" != "${PLAT}" ]; then -fno-fp-int-builtin-inexact) ;; -Wno-unused-but-set-variable) ;; -Wunsafe-loop-optimizations) ;; + -mdispatch-scheduler) ;; -ftracer) ;; -frounding-math) ;; -fmerge-constants) ;; @@ -189,6 +192,7 @@ if [ "${PLAT#*clang}" != "${PLAT}" ]; then -fschedule-insns) ;; -fno-semantic-interposition) ;; -mno-fentry) ;; + -f*shrink-wrap) ;; -f*schedule-insns2) ;; -fvect-cost-model=*) ;; -fsimd-cost-model=*) ;; @@ -228,6 +232,7 @@ if [ "${PLAT#*clang}" != "${PLAT}" ]; then ;; esac done + set -- "$@" -fno-stack-protector else # removes flags only clang supports FIRST=1 diff --git a/build/config.mk b/build/config.mk index c21e6283..352e74fa 100644 --- a/build/config.mk +++ b/build/config.mk @@ -17,8 +17,11 @@ CONFIG_CCFLAGS += \ $(FTRACE) \ -Og +CONFIG_COPTS += \ + -ftrapv + TARGET_ARCH ?= \ - -msse3 + -march=k8-sse3 RAGELFLAGS ?= -G2 @@ -71,10 +74,7 @@ CONFIG_CPPFLAGS += \ CONFIG_CCFLAGS += \ $(BACKTRACES) \ - -O3 - -#TARGET_ARCH ?= \ - -msse3 + -O2 RAGELFLAGS = -G2 @@ -103,6 +103,12 @@ CONFIG_COPTS += \ $(SECURITY_BLANKETS) \ $(SANITIZER) +CONFIG_COPTS += \ + -ftrapv + +TARGET_ARCH ?= \ + -march=k8-sse3 + OVERRIDE_CCFLAGS += \ -fno-pie @@ -135,7 +141,7 @@ CONFIG_CCFLAGS += \ -fno-align-loops TARGET_ARCH ?= \ - -msse3 + -march=k8-sse3 endif @@ -172,6 +178,8 @@ endif ifeq ($(MODE), ansi) CONFIG_CFLAGS += -std=c11 +#CONFIG_CPPFLAGS += -ansi CONFIG_CXXFLAGS += -std=c++11 +TARGET_ARCH ?= -march=k8-sse3 endif diff --git a/build/definitions.mk b/build/definitions.mk index 60680845..bab54a17 100644 --- a/build/definitions.mk +++ b/build/definitions.mk @@ -45,7 +45,6 @@ # ASFLAGS assembler flags (don't use -Wa, frontend prefix) # TARGET_ARCH microarchitecture flags (e.g. -march=native) -SHELL = /bin/sh DD ?= /bin/dd CP ?= /bin/cp -f RM ?= /bin/rm -f @@ -53,15 +52,14 @@ SED ?= /bin/sed MKDIR ?= /bin/mkdir -p TAGS ?= /usr/bin/ctags # emacs source builds or something breaks it ARFLAGS = rcsD -TAGSFLAGS ?= -e -a --if0=no --langmap=c:.c.h.i --line-directives=yes SILENT ?= 1 -ZFLAGS ?= -4 --rsyncable +ZFLAGS ?= XARGS ?= xargs -P4 -rs8000 NICE ?= build/actuallynice RAGEL ?= ragel DOT ?= dot GZ ?= gzip -CLANG = clang-11 +CLANG = clang-10 FC = gfortran #/opt/cross9f/bin/x86_64-linux-musl-gfortran # see build/compile, etc. which run third_party/gcc/unbundle.sh @@ -108,8 +106,8 @@ FTRACE = \ -pg SANITIZER = \ - -fsanitize=undefined \ -fsanitize=leak \ + -fsanitize=undefined \ -fsanitize=implicit-signed-integer-truncation \ -fsanitize=implicit-integer-sign-change @@ -139,6 +137,7 @@ DEFAULT_OFLAGS = \ -gdescribe-dies DEFAULT_COPTS = \ + -mno-red-zone \ -fno-math-errno \ -fno-trapping-math \ -fno-fp-int-builtin-inexact \ @@ -149,9 +148,13 @@ DEFAULT_COPTS = \ -fstrict-aliasing \ -fstrict-overflow \ -fno-omit-frame-pointer \ - -fno-optimize-sibling-calls \ + -fno-semantic-interposition \ -mno-omit-leaf-frame-pointer +MATHEMATICAL = \ + -O3 \ + -fwrapv + DEFAULT_CPPFLAGS = \ -DIMAGE_BASE_VIRTUAL=$(IMAGE_BASE_VIRTUAL) \ -nostdinc \ @@ -176,15 +179,14 @@ DEFAULT_ASFLAGS = \ --noexecstack DEFAULT_LDFLAGS = \ - -h \ -static \ - --relax \ -nostdlib \ -m elf_x86_64 \ --gc-sections \ --build-id=none \ --cref -Map=$@.map \ --no-dynamic-linker \ + -z max-page-size=0x1000 \ -Ttext-segment=$(IMAGE_BASE_VIRTUAL) ASONLYFLAGS = \ @@ -270,8 +272,8 @@ COMPILE.F.flags = $(cc.flags) $(cpp.flags) $(copt.flags) $(f.flags) COMPILE.i.flags = $(cc.flags) $(copt.flags) $(c.flags) COMPILE.ii.flags = $(cc.flags) $(copt.flags) $(cxx.flags) LINK.flags = $(DEFAULT_LDFLAGS) $(CONFIG_LDFLAGS) $(LDFLAGS) -OBJECTIFY.c.flags = $(OBJECTIFY.S.flags) $(copt.flags) $(c.flags) -OBJECTIFY.cxx.flags = $(OBJECTIFY.S.flags) $(copt.flags) $(cxx.flags) +OBJECTIFY.c.flags = $(OBJECTIFY.S.flags) $(c.flags) +OBJECTIFY.cxx.flags = $(OBJECTIFY.S.flags) $(cxx.flags) OBJECTIFY.s.flags = $(ASONLYFLAGS) $(s.flags) OBJECTIFY.S.flags = $(copt.flags) $(cc.flags) $(o.flags) $(cpp.flags) $(S.flags) OBJECTIFY.f.flags = $(copt.flags) $(cc.flags) $(o.flags) $(copt.flags) $(S.flags) $(f.flags) @@ -339,7 +341,6 @@ OBJECTIFY.ncabi.c = \ -fno-stack-protector \ -fno-instrument-functions \ -fno-optimize-sibling-calls \ - -mpreferred-stack-boundary=3 \ -fno-sanitize=all \ -fcall-saved-rcx \ -fcall-saved-rdx \ @@ -352,10 +353,26 @@ OBJECTIFY.ncabi.c = \ -c \ -xc -BUILD_SRCS = \ - build/definitions.mk \ - build/rules.mk \ - build/compile \ - build/link \ - build/lolsan \ - build/remote +# Initializer ABI +# +# Doesn't clobber RDI and RSI. +OBJECTIFY.initabi.c = \ + $(GCC) \ + $(OBJECTIFY.c.flags) \ + -mno-fentry \ + -fno-stack-protector \ + -fno-instrument-functions \ + -fno-optimize-sibling-calls \ + -fno-sanitize=all \ + -fcall-saved-rdi \ + -fcall-saved-rsi \ + -c + +TAGSFLAGS = \ + -e \ + -a \ + --if0=no \ + --langmap=c:.c.h.i \ + --line-directives=yes \ + --exclude=libc/nt/struct/imagefileheader.h \ + --exclude=libc/nt/struct/filesegmentelement.h diff --git a/build/htags b/build/htags index 8b68d613..d8266fa1 100755 --- a/build/htags +++ b/build/htags @@ -54,4 +54,17 @@ set -- --regex-c='/^[_[:alpha:]][_[:alnum:]]*[ *][ *]*\([_[:alpha:]][_[:alnum:]] # extern int32_t (*const SetEvent)(int64_t hEvent) wincall; set -- --regex-c='/^extern [^(]*(\*const \([^)]*\))(/\1/b' "$@" -exec ${TAGS:-ctags} -e --langmap=c:.c.h "$@" +# ctags doesn't understand forward declarations, e.g. +# struct WorstSoftwareEver; +set -- --regex-c='/^struct.*;$/uehocruehcroue/b' "$@" + +exec ${TAGS:-ctags} \ + -e \ + --langmap=c:.c.h \ + --exclude=libc/nt/struct/imagefileheader.h \ + --exclude=libc/nt/struct/imageseparatedebugheader.h \ + --exclude=libc/nt/struct/importobjectheader.h \ + --exclude=libc/nt/struct/nonpageddebuginfo.h \ + --exclude=libc/nt/struct/ansistring.h \ + --exclude=libc/nt/struct/filesegmentelement.h \ + "$@" diff --git a/build/rollup b/build/rollup new file mode 100755 index 00000000..c9882a7e --- /dev/null +++ b/build/rollup @@ -0,0 +1,6 @@ +#-*-mode:sh;indent-tabs-mode:nil;tab-width:2;coding:utf-8-*-┐ +#───vi: set net ft=sh ts=2 sts=2 fenc=utf-8 :vi─────────────┘ + +for x; do + printf '#include "%s"\n' "$x" +done diff --git a/build/rules.mk b/build/rules.mk index 570df894..07badffb 100644 --- a/build/rules.mk +++ b/build/rules.mk @@ -37,6 +37,7 @@ o/%.greg.o: %.greg.c; @ACTION=OBJECTIFY.greg build/compile $(OBJECTIFY.greg.c) $ o/%.zip.o: o/%; @build/zipobj $(OUTPUT_OPTION) $< o/$(MODE)/%.a:; @$(ARCHIVE) $@ $^ +o/$(MODE)/%: o/$(MODE)/%.dbg; @ACTION=OBJCOPY TARGET=$@ build/do $(OBJCOPY) -SO binary $< $@ o/$(MODE)/%.o: %.s; @TARGET=$@ build/assemble $(OBJECTIFY.s) $(OUTPUT_OPTION) $< o/$(MODE)/%.o: o/$(MODE)/%.s; @TARGET=$@ build/assemble $(OBJECTIFY.s) $(OUTPUT_OPTION) $< o/$(MODE)/%.s: %.S; @ACTION=PREPROCESS build/compile $(PREPROCESS) $(OUTPUT_OPTION) $< @@ -75,15 +76,15 @@ o/$(MODE)/%.runs: o/$(MODE)/%; @ACTION=CHECK.runs TARGET=$< build/runcom $< $(TE o/$(MODE)/%.pkg:; @build/package $(OUTPUT_OPTION) $(addprefix -d,$(filter %.pkg,$^)) $(filter %.o,$^) o/$(MODE)/%.zip.o: %; @build/zipobj $(OUTPUT_OPTION) $< -o/$(MODE)/%-gcc.asm: %.c; @ACTION=COMPILE.c build/compile $(COMPILE.c) $(OUTPUT_OPTION) $< +o/$(MODE)/%-gcc.asm: %.c; @ACTION=OBJECTIFY.c build/compile $(OBJECTIFY.c) -S -g0 $(OUTPUT_OPTION) $< o/$(MODE)/%-clang.asm: CC = $(CLANG) -o/$(MODE)/%-clang.asm: %.c; @ACTION=COMPILE.c build/compile $(COMPILE.c) $(OUTPUT_OPTION) $< || echo / need $(CLANG) >$@ -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* + * + * + *