├── .editorconfig ├── .github ├── SECURITY.md ├── SUPPORT.md ├── dependabot.yml ├── stale.yml └── workflows │ ├── ci.yml │ ├── codeql-analysis.yml │ ├── commitlint.yml │ ├── coverage.yml │ ├── lint.yml │ └── pr-extra.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── go.coverage.sh ├── go.mod ├── go.test.sh ├── xor.go ├── xor_amd64.go ├── xor_amd64.s ├── xor_arm64.go ├── xor_arm64.s ├── xor_generic.go ├── xor_ppc64x.go ├── xor_ppc64x.s └── xor_test.go /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org/ 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | end_of_line = lf 10 | 11 | [{*.go, go.mod}] 12 | indent_style = tab 13 | indent_size = 4 14 | 15 | 16 | # Makefiles always use tabs for indentation 17 | [Makefile] 18 | indent_style = tab 19 | -------------------------------------------------------------------------------- /.github/SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | Use [Full Disclosure](https://en.wikipedia.org/wiki/Full_disclosure_(computer_security)) 4 | strategy and open public issue. 5 | 6 | Project is in early development stage so "Coordinated vulnerability disclosure" is redundant. 7 | -------------------------------------------------------------------------------- /.github/SUPPORT.md: -------------------------------------------------------------------------------- 1 | This project is fully opensource and support is done voluntarily 2 | by community, so no SLA is provided. 3 | 4 | **Only** important news, major updates or security issues are posted here: 5 | * [@gotd_news](https://t.me/gotd_news) — gotd news telegram channel 6 | 7 | You can also use following telegram groups: 8 | 9 | * **[@gotd_en](https://t.me/gotd_en) (English)** 10 | * [@gotd_ru](https://t.me/gotd_ru) (Russian) 11 | 12 | Both development and user support is done in [@gotd_en](https://t.me/gotd_en). 13 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: gomod 4 | directory: "/" 5 | schedule: 6 | interval: weekly 7 | - package-ecosystem: github-actions 8 | directory: "/" 9 | schedule: 10 | interval: weekly 11 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 366 3 | 4 | # Number of days of inactivity before a stale issue is closed 5 | daysUntilClose: 30 6 | 7 | # Issues with these labels will never be considered stale 8 | exemptLabels: 9 | - pinned 10 | - security 11 | - bug 12 | - blocked 13 | - protected 14 | - triaged 15 | 16 | # Label to use when marking an issue as stale 17 | staleLabel: stale 18 | 19 | # Comment to post when marking an issue as stale. Set to `false` to disable 20 | markComment: > 21 | This issue has been automatically marked as stale because it has not had 22 | recent activity. It will be closed if no further activity occurs. Thank you 23 | for your contributions. 24 | 25 | # Comment to post when closing a stale issue. Set to `false` to disable 26 | closeComment: false -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | tags: 5 | - v* 6 | branches: 7 | - main 8 | pull_request: 9 | workflow_dispatch: 10 | 11 | jobs: 12 | test: 13 | runs-on: ${{ matrix.runner }} 14 | strategy: 15 | fail-fast: false 16 | matrix: 17 | flags: [""] 18 | arch: 19 | - amd64 20 | runner: 21 | - ubuntu-latest 22 | - macos-latest 23 | - windows-latest 24 | go: 25 | - 1.16 26 | - 1.17 27 | - 1.18 28 | include: 29 | - arch: 386 30 | runner: ubuntu-latest 31 | go: 1.18 32 | - arch: amd64 33 | runner: ubuntu-latest 34 | flags: "-race" 35 | go: 1.18 36 | 37 | steps: 38 | - name: Checkout code 39 | uses: actions/checkout@v4 40 | 41 | - name: Install Go 42 | uses: actions/setup-go@v5 43 | with: 44 | go-version: ${{ matrix.go }} 45 | 46 | - name: Get Go environment 47 | id: go-env 48 | run: | 49 | echo "::set-output name=cache::$(go env GOCACHE)" 50 | echo "::set-output name=modcache::$(go env GOMODCACHE)" 51 | 52 | - name: Set up cache 53 | uses: actions/cache@v4 54 | with: 55 | path: | 56 | ${{ steps.go-env.outputs.cache }} 57 | ${{ steps.go-env.outputs.modcache }} 58 | key: test-${{ runner.os }}-${{ matrix.arch }}-go-${{ matrix.go }}-${{ hashFiles('**/go.sum') }} 59 | restore-keys: | 60 | test-${{ runner.os }}-${{ matrix.arch }}-go-${{ matrix.go }}- 61 | 62 | - name: Run tests 63 | env: 64 | GOARCH: ${{ matrix.arch }} 65 | GOFLAGS: ${{ matrix.flags }} 66 | run: go test --timeout 5m ./... 67 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ main ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ main ] 20 | schedule: 21 | - cron: '45 12 * * 4' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'go' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://git.io/codeql-language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v4 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v3 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 52 | 53 | - name: Install Go 54 | uses: actions/setup-go@v5 55 | with: 56 | go-version: 1.17 57 | 58 | - name: Perform CodeQL Analysis 59 | uses: github/codeql-action/analyze@v3 60 | -------------------------------------------------------------------------------- /.github/workflows/commitlint.yml: -------------------------------------------------------------------------------- 1 | name: Lint Commit Messages 2 | on: [pull_request] 3 | 4 | jobs: 5 | commitlint: 6 | runs-on: ubuntu-latest 7 | if: github.actor != 'dependabot[bot]' 8 | steps: 9 | - uses: actions/checkout@v4 10 | with: 11 | fetch-depth: 0 12 | - uses: wagoid/commitlint-github-action@v6.2.1 13 | -------------------------------------------------------------------------------- /.github/workflows/coverage.yml: -------------------------------------------------------------------------------- 1 | name: Coverage 2 | on: 3 | push: 4 | tags: 5 | - v* 6 | branches: 7 | - main 8 | pull_request: 9 | 10 | jobs: 11 | test: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Install Go 15 | uses: actions/setup-go@v5 16 | with: 17 | go-version: 1.17 18 | 19 | - name: Get Go environment 20 | id: go-env 21 | run: | 22 | echo "::set-output name=cache::$(go env GOCACHE)" 23 | echo "::set-output name=modcache::$(go env GOMODCACHE)" 24 | - name: Set up cache 25 | uses: actions/cache@v4 26 | with: 27 | path: | 28 | ${{ steps.go-env.outputs.cache }} 29 | ${{ steps.go-env.outputs.modcache }} 30 | key: test-${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 31 | restore-keys: | 32 | test-${{ runner.os }}-go- 33 | 34 | - name: Checkout code 35 | uses: actions/checkout@v4 36 | 37 | - name: Download dependencies 38 | run: go mod download && go mod tidy 39 | 40 | - name: Run tests with coverage 41 | run: make coverage 42 | 43 | - name: Upload artifact 44 | uses: actions/upload-artifact@v4 45 | with: 46 | name: coverage 47 | path: profile.out 48 | if-no-files-found: error 49 | retention-days: 1 50 | 51 | - name: Send coverage 52 | uses: codecov/codecov-action@v5.4.3 53 | with: 54 | file: profile.out 55 | 56 | upload: 57 | runs-on: ubuntu-latest 58 | needs: 59 | - test 60 | steps: 61 | - name: Checkout code 62 | uses: actions/checkout@v4 63 | 64 | - name: Download artifact 65 | uses: actions/download-artifact@v4 66 | with: 67 | name: coverage 68 | 69 | - name: Send coverage 70 | uses: codecov/codecov-action@v5.4.3 71 | with: 72 | file: profile.out 73 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | on: 3 | push: 4 | tags: 5 | - v* 6 | branches: 7 | - main 8 | pull_request: 9 | workflow_dispatch: 10 | 11 | jobs: 12 | golangci-lint: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout code 16 | uses: actions/checkout@v4 17 | 18 | - name: Install Go 19 | uses: actions/setup-go@v5 20 | with: 21 | go-version: 1.17 22 | 23 | - name: Lint 24 | uses: golangci/golangci-lint-action@v8.0.0 25 | with: 26 | version: latest 27 | args: --timeout 5m 28 | skip-go-installation: true 29 | 30 | # Check if there are any dirty changes after go mod tidy 31 | check-mod: 32 | runs-on: ubuntu-latest 33 | steps: 34 | - name: Checkout code 35 | uses: actions/checkout@v4 36 | 37 | - name: Install Go 38 | uses: actions/setup-go@v5 39 | with: 40 | go-version: 1.17 41 | 42 | - name: Get Go environment 43 | id: go-env 44 | run: | 45 | echo "::set-output name=cache::$(go env GOCACHE)" 46 | echo "::set-output name=modcache::$(go env GOMODCACHE)" 47 | - name: Set up cache 48 | uses: actions/cache@v4 49 | with: 50 | path: | 51 | ${{ steps.go-env.outputs.cache }} 52 | ${{ steps.go-env.outputs.modcache }} 53 | key: check-mod-${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 54 | restore-keys: | 55 | check-mod-${{ runner.os }}-go- 56 | 57 | - name: Download dependencies 58 | run: go mod download && go mod tidy 59 | 60 | - name: Check git diff 61 | run: git diff --exit-code 62 | 63 | # Check if there are any dirty changes after go generate 64 | check-generate: 65 | runs-on: ubuntu-latest 66 | steps: 67 | - name: Checkout code 68 | uses: actions/checkout@v4 69 | 70 | - name: Install Go 71 | uses: actions/setup-go@v5 72 | with: 73 | go-version: 1.17 74 | 75 | - name: Get Go environment 76 | id: go-env 77 | run: | 78 | echo "::set-output name=cache::$(go env GOCACHE)" 79 | echo "::set-output name=modcache::$(go env GOMODCACHE)" 80 | - name: Set up cache 81 | uses: actions/cache@v4 82 | with: 83 | path: | 84 | ${{ steps.go-env.outputs.cache }} 85 | ${{ steps.go-env.outputs.modcache }} 86 | key: check-generate-${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 87 | restore-keys: | 88 | check-generate-${{ runner.os }}-go- 89 | 90 | - name: Download dependencies 91 | run: go mod download && go mod tidy 92 | 93 | - name: Check git diff 94 | run: git diff --exit-code 95 | -------------------------------------------------------------------------------- /.github/workflows/pr-extra.yml: -------------------------------------------------------------------------------- 1 | name: Extra 2 | on: 3 | push: 4 | tags: 5 | - v* 6 | branches: 7 | - main 8 | pull_request: 9 | workflow_dispatch: 10 | 11 | jobs: 12 | vulns: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout code 16 | uses: actions/checkout@v4 17 | 18 | - name: Install Go 19 | uses: actions/setup-go@v5 20 | with: 21 | go-version: 1.17 22 | 23 | - name: List dependencies 24 | run: go list -json -m all > go.list 25 | 26 | - name: Run nancy 27 | uses: sonatype-nexus-community/nancy-github-action@v1.0.3 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | _bin/* 3 | 4 | *-fuzz.zip 5 | /cmd/gotdecho/gotdecho 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009 The Go Authors. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | @./go.test.sh 3 | .PHONY: test 4 | 5 | coverage: 6 | @./go.coverage.sh 7 | .PHONY: coverage 8 | 9 | test_fast: 10 | go test ./... 11 | 12 | tidy: 13 | go mod tidy 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # xor [![Go Reference](https://img.shields.io/badge/go-pkg-00ADD8)](https://pkg.go.dev/github.com/go-faster/xor#section-documentation) [![codecov](https://img.shields.io/codecov/c/github/go-faster/xor?label=cover)](https://codecov.io/gh/go-faster/xor) [![stable](https://img.shields.io/badge/-stable-brightgreen)](https://go-faster.org/docs/projects/status#stable) 2 | 3 | Package xor implements XOR operations on byte slices. 4 | Extracted from [crypto/cipher](https://golang.org/src/crypto/cipher/xor_generic.go). 5 | ```console 6 | go get github.com/go-faster/xor 7 | ``` 8 | ```go 9 | xor.Bytes(dst, a, b) 10 | ``` 11 | **Ref:** [#30553](https://github.com/golang/go/issues/30553) as rejected proposal to provide XOR in go stdlib 12 | -------------------------------------------------------------------------------- /go.coverage.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | go test -v -coverpkg=./... -coverprofile=profile.out ./... 6 | go tool cover -func profile.out 7 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/go-faster/xor 2 | 3 | go 1.15 4 | -------------------------------------------------------------------------------- /go.test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | # test with -race 6 | go test --timeout 5m -race ./... 7 | -------------------------------------------------------------------------------- /xor.go: -------------------------------------------------------------------------------- 1 | package xor 2 | 3 | // Bytes xors the bytes in a and b. The destination should have enough 4 | // space, otherwise Bytes will panic. Returns the number of bytes xor'd. 5 | func Bytes(dst, a, b []byte) int { 6 | return xorBytes(dst, a, b) 7 | } 8 | -------------------------------------------------------------------------------- /xor_amd64.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package xor 6 | 7 | // xorBytes xors the bytes in a and b. The destination should have enough 8 | // space, otherwise xorBytes will panic. Returns the number of bytes xor'd. 9 | func xorBytes(dst, a, b []byte) int { 10 | n := len(a) 11 | if len(b) < n { 12 | n = len(b) 13 | } 14 | if n == 0 { 15 | return 0 16 | } 17 | _ = dst[n-1] 18 | xorBytesSSE2(&dst[0], &a[0], &b[0], n) // amd64 must have SSE2 19 | return n 20 | } 21 | 22 | //go:noescape 23 | func xorBytesSSE2(dst, a, b *byte, n int) 24 | -------------------------------------------------------------------------------- /xor_amd64.s: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | #include "textflag.h" 6 | 7 | // func xorBytesSSE2(dst, a, b *byte, n int) 8 | TEXT ·xorBytesSSE2(SB), NOSPLIT, $0 9 | MOVQ dst+0(FP), BX 10 | MOVQ a+8(FP), SI 11 | MOVQ b+16(FP), CX 12 | MOVQ n+24(FP), DX 13 | TESTQ $15, DX // AND 15 & len, if not zero jump to not_aligned. 14 | JNZ not_aligned 15 | 16 | aligned: 17 | MOVQ $0, AX // position in slices 18 | 19 | loop16b: 20 | MOVOU (SI)(AX*1), X0 // XOR 16byte forwards. 21 | MOVOU (CX)(AX*1), X1 22 | PXOR X1, X0 23 | MOVOU X0, (BX)(AX*1) 24 | ADDQ $16, AX 25 | CMPQ DX, AX 26 | JNE loop16b 27 | RET 28 | 29 | loop_1b: 30 | SUBQ $1, DX // XOR 1byte backwards. 31 | MOVB (SI)(DX*1), DI 32 | MOVB (CX)(DX*1), AX 33 | XORB AX, DI 34 | MOVB DI, (BX)(DX*1) 35 | TESTQ $7, DX // AND 7 & len, if not zero jump to loop_1b. 36 | JNZ loop_1b 37 | CMPQ DX, $0 // if len is 0, ret. 38 | JE ret 39 | TESTQ $15, DX // AND 15 & len, if zero jump to aligned. 40 | JZ aligned 41 | 42 | not_aligned: 43 | TESTQ $7, DX // AND $7 & len, if not zero jump to loop_1b. 44 | JNE loop_1b 45 | SUBQ $8, DX // XOR 8bytes backwards. 46 | MOVQ (SI)(DX*1), DI 47 | MOVQ (CX)(DX*1), AX 48 | XORQ AX, DI 49 | MOVQ DI, (BX)(DX*1) 50 | CMPQ DX, $16 // if len is greater or equal 16 here, it must be aligned. 51 | JGE aligned 52 | 53 | ret: 54 | RET 55 | -------------------------------------------------------------------------------- /xor_arm64.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package xor 6 | 7 | // xorBytes xors the bytes in a and b. The destination should have enough 8 | // space, otherwise xorBytes will panic. Returns the number of bytes xor'd. 9 | func xorBytes(dst, a, b []byte) int { 10 | n := len(a) 11 | if len(b) < n { 12 | n = len(b) 13 | } 14 | if n == 0 { 15 | return 0 16 | } 17 | // make sure dst has enough space 18 | _ = dst[n-1] 19 | 20 | xorBytesARM64(&dst[0], &a[0], &b[0], n) 21 | return n 22 | } 23 | 24 | func xorWords(dst, a, b []byte) { 25 | xorBytes(dst, a, b) 26 | } 27 | 28 | //go:noescape 29 | func xorBytesARM64(dst, a, b *byte, n int) 30 | -------------------------------------------------------------------------------- /xor_arm64.s: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | #include "textflag.h" 6 | 7 | // func xorBytesARM64(dst, a, b *byte, n int) 8 | TEXT ·xorBytesARM64(SB), NOSPLIT|NOFRAME, $0 9 | MOVD dst+0(FP), R0 10 | MOVD a+8(FP), R1 11 | MOVD b+16(FP), R2 12 | MOVD n+24(FP), R3 13 | CMP $64, R3 14 | BLT tail 15 | loop_64: 16 | VLD1.P 64(R1), [V0.B16, V1.B16, V2.B16, V3.B16] 17 | VLD1.P 64(R2), [V4.B16, V5.B16, V6.B16, V7.B16] 18 | VEOR V0.B16, V4.B16, V4.B16 19 | VEOR V1.B16, V5.B16, V5.B16 20 | VEOR V2.B16, V6.B16, V6.B16 21 | VEOR V3.B16, V7.B16, V7.B16 22 | VST1.P [V4.B16, V5.B16, V6.B16, V7.B16], 64(R0) 23 | SUBS $64, R3 24 | CMP $64, R3 25 | BGE loop_64 26 | tail: 27 | // quick end 28 | CBZ R3, end 29 | TBZ $5, R3, less_than32 30 | VLD1.P 32(R1), [V0.B16, V1.B16] 31 | VLD1.P 32(R2), [V2.B16, V3.B16] 32 | VEOR V0.B16, V2.B16, V2.B16 33 | VEOR V1.B16, V3.B16, V3.B16 34 | VST1.P [V2.B16, V3.B16], 32(R0) 35 | less_than32: 36 | TBZ $4, R3, less_than16 37 | LDP.P 16(R1), (R11, R12) 38 | LDP.P 16(R2), (R13, R14) 39 | EOR R11, R13, R13 40 | EOR R12, R14, R14 41 | STP.P (R13, R14), 16(R0) 42 | less_than16: 43 | TBZ $3, R3, less_than8 44 | MOVD.P 8(R1), R11 45 | MOVD.P 8(R2), R12 46 | EOR R11, R12, R12 47 | MOVD.P R12, 8(R0) 48 | less_than8: 49 | TBZ $2, R3, less_than4 50 | MOVWU.P 4(R1), R13 51 | MOVWU.P 4(R2), R14 52 | EORW R13, R14, R14 53 | MOVWU.P R14, 4(R0) 54 | less_than4: 55 | TBZ $1, R3, less_than2 56 | MOVHU.P 2(R1), R15 57 | MOVHU.P 2(R2), R16 58 | EORW R15, R16, R16 59 | MOVHU.P R16, 2(R0) 60 | less_than2: 61 | TBZ $0, R3, end 62 | MOVBU (R1), R17 63 | MOVBU (R2), R19 64 | EORW R17, R19, R19 65 | MOVBU R19, (R0) 66 | end: 67 | RET 68 | -------------------------------------------------------------------------------- /xor_generic.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:build !amd64 && !ppc64 && !ppc64le && !arm64 6 | // +build !amd64,!ppc64,!ppc64le,!arm64 7 | 8 | package xor 9 | 10 | import ( 11 | "runtime" 12 | "unsafe" 13 | ) 14 | 15 | // xorBytes xors the bytes in a and b. The destination should have enough 16 | // space, otherwise xorBytes will panic. Returns the number of bytes xor'd. 17 | func xorBytes(dst, a, b []byte) int { 18 | n := len(a) 19 | if len(b) < n { 20 | n = len(b) 21 | } 22 | if n == 0 { 23 | return 0 24 | } 25 | 26 | switch { 27 | case supportsUnaligned: 28 | fastXORBytes(dst, a, b, n) 29 | default: 30 | // TODO(hanwen): if (dst, a, b) have common alignment 31 | // we could still try fastXORBytes. It is not clear 32 | // how often this happens, and it's only worth it if 33 | // the block encryption itself is hardware 34 | // accelerated. 35 | safeXORBytes(dst, a, b, n) 36 | } 37 | return n 38 | } 39 | 40 | const wordSize = int(unsafe.Sizeof(uintptr(0))) 41 | const supportsUnaligned = runtime.GOARCH == "386" || runtime.GOARCH == "ppc64" || runtime.GOARCH == "ppc64le" || runtime.GOARCH == "s390x" 42 | 43 | // fastXORBytes xors in bulk. It only works on architectures that 44 | // support unaligned read/writes. 45 | // n needs to be smaller or equal than the length of a and b. 46 | func fastXORBytes(dst, a, b []byte, n int) { 47 | // Assert dst has enough space 48 | _ = dst[n-1] 49 | 50 | w := n / wordSize 51 | if w > 0 { 52 | dw := *(*[]uintptr)(unsafe.Pointer(&dst)) 53 | aw := *(*[]uintptr)(unsafe.Pointer(&a)) 54 | bw := *(*[]uintptr)(unsafe.Pointer(&b)) 55 | for i := 0; i < w; i++ { 56 | dw[i] = aw[i] ^ bw[i] 57 | } 58 | } 59 | 60 | for i := (n - n%wordSize); i < n; i++ { 61 | dst[i] = a[i] ^ b[i] 62 | } 63 | } 64 | 65 | // n needs to be smaller or equal than the length of a and b. 66 | func safeXORBytes(dst, a, b []byte, n int) { 67 | for i := 0; i < n; i++ { 68 | dst[i] = a[i] ^ b[i] 69 | } 70 | } 71 | 72 | // fastXORWords XORs multiples of 4 or 8 bytes (depending on architecture.) 73 | // The arguments are assumed to be of equal length. 74 | func fastXORWords(dst, a, b []byte) { 75 | dw := *(*[]uintptr)(unsafe.Pointer(&dst)) 76 | aw := *(*[]uintptr)(unsafe.Pointer(&a)) 77 | bw := *(*[]uintptr)(unsafe.Pointer(&b)) 78 | n := len(b) / wordSize 79 | for i := 0; i < n; i++ { 80 | dw[i] = aw[i] ^ bw[i] 81 | } 82 | } 83 | 84 | // fastXORWords XORs multiples of 4 or 8 bytes (depending on architecture.) 85 | // The slice arguments a and b are assumed to be of equal length. 86 | func xorWords(dst, a, b []byte) { 87 | if supportsUnaligned { 88 | fastXORWords(dst, a, b) 89 | } else { 90 | safeXORBytes(dst, a, b, len(b)) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /xor_ppc64x.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:build ppc64 || ppc64le 6 | // +build ppc64 ppc64le 7 | 8 | package xor 9 | 10 | // xorBytes xors the bytes in a and b. The destination should have enough 11 | // space, otherwise xorBytes will panic. Returns the number of bytes xor'd. 12 | func xorBytes(dst, a, b []byte) int { 13 | n := len(a) 14 | if len(b) < n { 15 | n = len(b) 16 | } 17 | if n == 0 { 18 | return 0 19 | } 20 | _ = dst[n-1] 21 | xorBytesVSX(&dst[0], &a[0], &b[0], n) 22 | return n 23 | } 24 | 25 | func xorWords(dst, a, b []byte) { 26 | xorBytes(dst, a, b) 27 | } 28 | 29 | //go:noescape 30 | func xorBytesVSX(dst, a, b *byte, n int) 31 | -------------------------------------------------------------------------------- /xor_ppc64x.s: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:build ppc64 || ppc64le 6 | // +build ppc64 ppc64le 7 | 8 | #include "textflag.h" 9 | 10 | // func xorBytesVSX(dst, a, b *byte, n int) 11 | TEXT ·xorBytesVSX(SB), NOSPLIT, $0 12 | MOVD dst+0(FP), R3 // R3 = dst 13 | MOVD a+8(FP), R4 // R4 = a 14 | MOVD b+16(FP), R5 // R5 = b 15 | MOVD n+24(FP), R6 // R6 = n 16 | 17 | CMPU R6, $32, CR7 // Check if n ≥ 32 bytes 18 | MOVD R0, R8 // R8 = index 19 | CMPU R6, $8, CR6 // Check if 8 ≤ n < 32 bytes 20 | BLT CR6, small // Smaller than 8 21 | BLT CR7, xor16 // Case for 16 ≤ n < 32 bytes 22 | 23 | // Case for n ≥ 32 bytes 24 | preloop32: 25 | SRD $5, R6, R7 // Setup loop counter 26 | MOVD R7, CTR 27 | MOVD $16, R10 28 | ANDCC $31, R6, R9 // Check for tailing bytes for later 29 | loop32: 30 | LXVD2X (R4)(R8), VS32 // VS32 = a[i,...,i+15] 31 | LXVD2X (R4)(R10), VS34 32 | LXVD2X (R5)(R8), VS33 // VS33 = b[i,...,i+15] 33 | LXVD2X (R5)(R10), VS35 34 | XXLXOR VS32, VS33, VS32 // VS34 = a[] ^ b[] 35 | XXLXOR VS34, VS35, VS34 36 | STXVD2X VS32, (R3)(R8) // Store to dst 37 | STXVD2X VS34, (R3)(R10) 38 | ADD $32, R8 // Update index 39 | ADD $32, R10 40 | BC 16, 0, loop32 // bdnz loop16 41 | 42 | BEQ CR0, done 43 | 44 | MOVD R9, R6 45 | CMP R6, $8 46 | BLT small 47 | xor16: 48 | CMP R6, $16 49 | BLT xor8 50 | LXVD2X (R4)(R8), VS32 51 | LXVD2X (R5)(R8), VS33 52 | XXLXOR VS32, VS33, VS32 53 | STXVD2X VS32, (R3)(R8) 54 | ADD $16, R8 55 | ADD $-16, R6 56 | CMP R6, $8 57 | BLT small 58 | xor8: 59 | // Case for 8 ≤ n < 16 bytes 60 | MOVD (R4)(R8), R14 // R14 = a[i,...,i+7] 61 | MOVD (R5)(R8), R15 // R15 = b[i,...,i+7] 62 | XOR R14, R15, R16 // R16 = a[] ^ b[] 63 | SUB $8, R6 // n = n - 8 64 | MOVD R16, (R3)(R8) // Store to dst 65 | ADD $8, R8 66 | 67 | // Check if we're finished 68 | CMP R6, R0 69 | BGT small 70 | RET 71 | 72 | // Case for n < 8 bytes and tailing bytes from the 73 | // previous cases. 74 | small: 75 | CMP R6, R0 76 | BEQ done 77 | MOVD R6, CTR // Setup loop counter 78 | 79 | loop: 80 | MOVBZ (R4)(R8), R14 // R14 = a[i] 81 | MOVBZ (R5)(R8), R15 // R15 = b[i] 82 | XOR R14, R15, R16 // R16 = a[i] ^ b[i] 83 | MOVB R16, (R3)(R8) // Store to dst 84 | ADD $1, R8 85 | BC 16, 0, loop // bdnz loop 86 | 87 | done: 88 | RET 89 | -------------------------------------------------------------------------------- /xor_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package xor_test 6 | 7 | import ( 8 | "bytes" 9 | "crypto/rand" 10 | "fmt" 11 | "io" 12 | "testing" 13 | 14 | "github.com/go-faster/xor" 15 | ) 16 | 17 | func TestXOR(t *testing.T) { 18 | for j := 1; j <= 1024; j++ { 19 | if testing.Short() && j > 16 { 20 | break 21 | } 22 | for alignP := 0; alignP < 2; alignP++ { 23 | for alignQ := 0; alignQ < 2; alignQ++ { 24 | for alignD := 0; alignD < 2; alignD++ { 25 | p := make([]byte, j)[alignP:] 26 | q := make([]byte, j)[alignQ:] 27 | d1 := make([]byte, j+alignD)[alignD:] 28 | d2 := make([]byte, j+alignD)[alignD:] 29 | if _, err := io.ReadFull(rand.Reader, p); err != nil { 30 | t.Fatal(err) 31 | } 32 | if _, err := io.ReadFull(rand.Reader, q); err != nil { 33 | t.Fatal(err) 34 | } 35 | xor.Bytes(d1, p, q) 36 | n := min(p, q) 37 | for i := 0; i < n; i++ { 38 | d2[i] = p[i] ^ q[i] 39 | } 40 | if !bytes.Equal(d1, d2) { 41 | t.Logf("p: %#v", p) 42 | t.Logf("q: %#v", q) 43 | t.Logf("expect: %#v", d2) 44 | t.Logf("result: %#v", d1) 45 | t.Fatal("not equal") 46 | } 47 | } 48 | } 49 | } 50 | } 51 | } 52 | 53 | func min(a, b []byte) int { 54 | n := len(a) 55 | if len(b) < n { 56 | n = len(b) 57 | } 58 | return n 59 | } 60 | 61 | func BenchmarkXORBytes(b *testing.B) { 62 | dst := make([]byte, 1<<15) 63 | data0 := make([]byte, 1<<15) 64 | data1 := make([]byte, 1<<15) 65 | sizes := []int64{1 << 3, 1 << 7, 1 << 11, 1 << 15} 66 | for _, size := range sizes { 67 | b.Run(fmt.Sprintf("%dBytes", size), func(b *testing.B) { 68 | s0 := data0[:size] 69 | s1 := data1[:size] 70 | b.SetBytes(int64(size)) 71 | for i := 0; i < b.N; i++ { 72 | xor.Bytes(dst, s0, s1) 73 | } 74 | }) 75 | } 76 | } 77 | --------------------------------------------------------------------------------