Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions base64x_error_native_amd64_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.

//go:build amd64 && !noasm && !appengine
// +build amd64,!noasm,!appengine
//go:build !goexperiment.simd && amd64 && !noasm && !appengine
// +build !goexperiment.simd,amd64,!noasm,!appengine

package base64x

Expand Down
4 changes: 2 additions & 2 deletions base64x_fallback_test.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//go:build !amd64 || noasm || appengine
// +build !amd64 noasm appengine
//go:build goexperiment.simd || !amd64 || noasm || appengine
// +build goexperiment.simd !amd64 noasm appengine

/*
* Copyright 2024 CloudWeGo Authors
Expand Down
4 changes: 2 additions & 2 deletions base64x_native.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//go:build amd64 && !noasm && !appengine
// +build amd64,!noasm,!appengine
//go:build !goexperiment.simd && amd64 && !noasm && !appengine
// +build !goexperiment.simd,amd64,!noasm,!appengine

/*
* Copyright 2024 CloudWeGo Authors
Expand Down
158 changes: 158 additions & 0 deletions base64x_simd_amd64.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
//go:build goexperiment.simd && amd64 && !noasm && !appengine

/*
* Copyright 2024 CloudWeGo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package base64x

import (
"encoding/base64"
"encoding/json"
"simd/archsimd"

"github.com/cloudwego/base64x/internal/simd"
)

func encode(out *[]byte, src []byte, mode int) {
if !archsimd.X86.AVX2() {
encodeStdlib(out, src, mode)
return
}
url := 0
if mode&_MODE_URL != 0 {
url = 1
}
pad := 1
if mode&_MODE_RAW != 0 {
pad = 0
}
enc := encodingForModeSimd(mode)
dst := *out
start := len(dst)
need := enc.EncodedLen(len(src))
dst = dst[:start+need]
simd.B64_encode(src, uint64(len(src)), dst[start:start+need], uint64(need), url, pad)
*out = dst[:start+need]
}

func decode(out *[]byte, src []byte, mode int) (int, error) {
input := src
if mode&_MODE_JSON != 0 {
unquoted, err := unquoteJSONBase64(src)
if err != nil {
return 0, err
}
input = unquoted
}
if !archsimd.X86.AVX2() {
return decodeStdlib(out, input, mode)
}

url := 0
if mode&_MODE_URL != 0 {
url = 1
}
pad := 1
if mode&_MODE_RAW != 0 {
pad = 0
}
enc := encodingForModeSimd(mode)
dst := *out
start := len(dst)
need := enc.DecodedLen(len(input))

// The vector decode loop writes in 32-byte chunks.
// If the destination slice capacity cannot safely hold 32 bytes from start,
// decode into a stack scratch buffer and copy back to avoid out-of-bounds writes.
if cap(dst)-start < 32 || cap(dst)-start < need+8 {
var tmp [256]byte
var buf []byte
allocCap := need + 32
if allocCap <= len(tmp) {
buf = tmp[:allocCap]
} else {
buf = make([]byte, allocCap)
}
var posErr uint64
w := simd.B64_decode(input, uint64(len(input)), buf, uint64(len(buf)), url, pad, &posErr)
if posErr != ^uint64(0) {
return 0, base64.CorruptInputError(posErr)
}
dst = dst[:start+int(w)]
copy(dst[start:], buf[:w])
*out = dst
return int(w), nil
}

dst = dst[:start+need]
var posErr uint64
availCap := uint64(cap(dst) - start)
w := simd.B64_decode(input, uint64(len(input)), dst[start:cap(dst)], availCap, url, pad, &posErr)
if posErr != ^uint64(0) {
return 0, base64.CorruptInputError(posErr)
}
*out = dst[:start+int(w)]
return int(w), nil
}

func encodingForModeSimd(mode int) *base64.Encoding {
switch mode & (_MODE_URL | _MODE_RAW) {
case _MODE_URL:
return base64.URLEncoding
case _MODE_RAW:
return base64.RawStdEncoding
case _MODE_URL | _MODE_RAW:
return base64.RawURLEncoding
default:
return base64.StdEncoding
}
}

func encodeStdlib(out *[]byte, src []byte, mode int) {
enc := encodingForModeSimd(mode)
dst := *out
start := len(dst)
need := enc.EncodedLen(len(src))
dst = dst[:start+need]
enc.Encode(dst[start:], src)
*out = dst
}

func decodeStdlib(out *[]byte, input []byte, mode int) (int, error) {
enc := encodingForModeSimd(mode)
dst := *out
start := len(dst)
buf := dst[start:cap(dst)]
n, err := enc.Decode(buf, input)
if err != nil {
return 0, err
}
*out = dst[:start+n]
return n, nil
}

func unquoteJSONBase64(src []byte) ([]byte, error) {
quoted := make([]byte, 0, len(src)+2)
quoted = append(quoted, '"')
quoted = append(quoted, src...)
quoted = append(quoted, '"')

var decoded string
if err := json.Unmarshal(quoted, &decoded); err != nil {
return nil, err
}
return []byte(decoded), nil
}
4 changes: 2 additions & 2 deletions base64x_subr_native_amd64.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.

//go:build amd64 && !noasm && !appengine
// +build amd64,!noasm,!appengine
//go:build !goexperiment.simd && amd64 && !noasm && !appengine
// +build !goexperiment.simd,amd64,!noasm,!appengine

package base64x

Expand Down
Loading
Loading