ob64 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 110cd68288a2dd3ff785dca2a4dc2bdc0b3d47445fae88d0fef3a3a3db1b0d96
4
- data.tar.gz: a0d837a2ba44079613b99fd862cdf156d3105b69359edbefeae32711a2be402e
3
+ metadata.gz: 93d2eaeaa970d18c2ab92ff9f5bb4621c22135c16faa0f1ef09ac71babe94b95
4
+ data.tar.gz: 7bad60e17ca1b3189634cb2043d58bf1ffa85c701deff0b99886dde4a34082e1
5
5
  SHA512:
6
- metadata.gz: 6a4fa2cabc1fad23055020ed1c49906de65e494f82fbf19966cc0a511cc8e073026dc77c4e0cb9b57d77d67e21f068fdbf79c6dfa2de978381444e1793a4793a
7
- data.tar.gz: 432523720a899411c6a3840fc410bd4e540b7f8211dda82dff2643046959ce52ceec8bc0a8f15a6518bddb913cce69129b52ade76d8dbd33f3a7e980a690c693
6
+ metadata.gz: fc8c44a79e33c420a13c1d4460a2773ed6d60ff0e4eab47ea5ad0a1db4365d85c4e642e442dfa4a2753ef0153f8bde876dca4867005f28c124f4b7251b06e55d
7
+ data.tar.gz: 61b2550bc2035667a86d6bfd87149fcba0bd3faa33d9e29c6f42f89cef8be2b5e5df6a36a35682e2cb9d193ea1252ee7d37bcb1d787d0c62cfa78d1618218144
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env bash
2
+ # Installs Intel SDE if this CPU can't run the avx512 codec, and exports SDE,
3
+ # a command prefix that emulates a CPU that can.
4
+ set -euo pipefail
5
+
6
+ missing=
7
+ for flag in avx512vl avx512vbmi avx512_vbmi2; do
8
+ grep -qw "$flag" /proc/cpuinfo || missing=1
9
+ done
10
+ [ -n "$missing" ] || exit 0
11
+
12
+ url=https://downloadmirror.intel.com/924984/sde-external-10.13.1-2026-07-28-lin.tar.xz
13
+ sha256=94e97d623fec54385686e1e7ba65ebc9941748c05ee451423948334892bf2b50
14
+ dir="$RUNNER_TEMP/sde"
15
+
16
+ curl -fsSL -o "$dir.tar.xz" "$url"
17
+ echo "$sha256 $dir.tar.xz" | sha256sum -c --quiet
18
+ mkdir -p "$dir"
19
+ tar xJf "$dir.tar.xz" -C "$dir" --strip-components=1
20
+ # Ice Lake: AVX512 with VBMI and VBMI2
21
+ echo "SDE=$dir/sde64 -icl --" >>"$GITHUB_ENV"
@@ -36,8 +36,15 @@ jobs:
36
36
  ruby: ["3.3", "3.4", "4.0"]
37
37
  os: [ubuntu-24.04, ubuntu-24.04-arm]
38
38
  cc: [gcc, clang]
39
+ include:
40
+ # Ruby built with AddressSanitizer, x86-64 only: memory errors in the
41
+ # C code fail the specs. ubsan adds UndefinedBehaviorSanitizer.
42
+ - ruby: asan-release
43
+ os: ubuntu-24.04
44
+ cc: clang
45
+ ubsan: true
39
46
  runs-on: ${{ matrix.os }}
40
- timeout-minutes: 15
47
+ timeout-minutes: 30
41
48
  steps:
42
49
  - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
43
50
  with:
@@ -55,14 +62,57 @@ jobs:
55
62
  - name: Compile
56
63
  env:
57
64
  # Same warnings for both compilers; Ruby's defaults are GCC-only.
58
- MAKEFLAGS: 'CC=${{ matrix.cc }} warnflags=-Wall\ -Wextra\ -Wno-unused-parameter\ -Werror'
65
+ MAKEFLAGS: >-
66
+ CC=${{ matrix.cc }}${{ matrix.ubsan && '\ -fsanitize=undefined\ -fno-sanitize-recover=all' || '' }}
67
+ warnflags=-Wall\ -Wextra\ -Wno-unused-parameter\ -Werror
59
68
  run: bundle exec rake compile
60
69
 
70
+ - name: Install Intel SDE if the CPU lacks AVX512
71
+ if: runner.arch == 'X64'
72
+ run: .github/scripts/install-sde
73
+
61
74
  - name: Show codecs
62
- run: bundle exec ruby -Ilib -rob64 -e 'p Ob64.codecs, Ob64.codec'
75
+ run: $SDE bundle exec ruby -Ilib -rob64 -e 'p Ob64.codecs, Ob64.codec'
76
+
77
+ - name: Test
78
+ env:
79
+ # Fails the codec spec if one is missing
80
+ OB64_CODECS: ${{ runner.arch == 'X64' && 'scalar,avx2,avx512' || 'scalar,neon64' }}
81
+ run: $SDE bundle exec rake spec
82
+
83
+ codecs:
84
+ # The C codecs alone under ASan and UBSan, on exact-size buffers.
85
+ strategy:
86
+ fail-fast: false
87
+ matrix:
88
+ os: [ubuntu-24.04, ubuntu-24.04-arm]
89
+ cc: [gcc, clang]
90
+ runs-on: ${{ matrix.os }}
91
+ # Long under SDE, which emulates AVX512 slowly
92
+ timeout-minutes: 90
93
+ steps:
94
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
95
+ with:
96
+ persist-credentials: false
97
+
98
+ - uses: ruby/setup-ruby@7f797815d8adceb1c412e7abefdd1ab4abc47a5f # v1.335.0
99
+ with:
100
+ ruby-version: "3.4"
101
+ bundler-cache: true
102
+
103
+ - name: Install Clang
104
+ if: matrix.cc == 'clang'
105
+ run: command -v clang || { sudo apt-get update && sudo apt-get install -y clang; }
106
+
107
+ - name: Install Intel SDE if the CPU lacks AVX512
108
+ if: runner.arch == 'X64'
109
+ run: .github/scripts/install-sde
63
110
 
64
111
  - name: Test
65
- run: bundle exec rake spec
112
+ env:
113
+ CC: ${{ matrix.cc }}
114
+ OB64_CODECS: ${{ runner.arch == 'X64' && 'scalar,avx2,avx512' || 'scalar,neon64' }}
115
+ run: bundle exec rake spec:c
66
116
 
67
117
  package:
68
118
  runs-on: ubuntu-24.04
data/CHANGELOG.md CHANGED
@@ -1,3 +1,8 @@
1
+ ## [0.8.0] - 2026-10-27
2
+
3
+ - Fix undefined behavior in AVX512 detection.
4
+ - `Ob64::Base64::RFC4648.encode` and `encode_urlsafe` return US-ASCII Strings, like `pack("m0")` (were BINARY).
5
+
1
6
  ## [0.7.0] - 2026-10-26
2
7
 
3
8
  - **Breaking:** Split `Ob64::Base64` into one module per RFC. `Ob64::Base64::RFC4648` has `encode`, `decode`, `encode_urlsafe`, `decode_urlsafe`, `encoded_length_of` and `decoded_length_of` (were `Ob64::Base64.encode`, `decode`, `urlsafe_encode`, `urlsafe_decode`, `encoded_length_of` and `decoded_length_of`).
data/README.md CHANGED
@@ -2,13 +2,56 @@
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/ob64.svg)](http://rubygems.org/gems/ob64)
4
4
  [![CI](https://github.com/jcmfernandes/ob64/actions/workflows/main.yml/badge.svg?branch=master)](https://github.com/jcmfernandes/ob64/actions/workflows/main.yml)
5
- [![Yard Docs](https://img.shields.io/badge/yard-docs-blue.svg)](http://www.rubydoc.info/gems/ob64/0.7.0)
5
+ [![Yard Docs](https://img.shields.io/badge/yard-docs-blue.svg)](http://www.rubydoc.info/gems/ob64/0.8.0)
6
6
 
7
7
  A *fast* Base64 and uuencode encoder and decoder as a Ruby gem.
8
8
 
9
- How fast? Try it yourself, execute:
9
+ How fast? Times faster than Ruby's own `pack` / `unpack1`, per call, measured with `benchmark.rb` on an AMD Ryzen 9 9900X (AVX512) with Ruby 3.4:
10
10
 
11
- $ rake benchmark
11
+ ```mermaid
12
+ %%{init: {"themeVariables": {"xyChart": {"plotColorPalette": "#2a78d6"}}}}%%
13
+ xychart-beta
14
+ title "1 kB input: times faster than Ruby"
15
+ x-axis ["m0 enc", "m0 dec", "url enc", "url dec", "m enc", "m dec", "u enc", "u dec"]
16
+ y-axis "times faster" 0 --> 45
17
+ bar [3.1, 4.8, 11.1, 12.6, 5.2, 3.9, 5.9, 4.2]
18
+ ```
19
+
20
+ ```mermaid
21
+ %%{init: {"themeVariables": {"xyChart": {"plotColorPalette": "#2a78d6"}}}}%%
22
+ xychart-beta
23
+ title "4 kB input: times faster than Ruby"
24
+ x-axis ["m0 enc", "m0 dec", "url enc", "url dec", "m enc", "m dec", "u enc", "u dec"]
25
+ y-axis "times faster" 0 --> 45
26
+ bar [6.3, 8.4, 17.6, 22.9, 8.4, 6.1, 7.5, 5.2]
27
+ ```
28
+
29
+ ```mermaid
30
+ %%{init: {"themeVariables": {"xyChart": {"plotColorPalette": "#2a78d6"}}}}%%
31
+ xychart-beta
32
+ title "16 kB input: times faster than Ruby"
33
+ x-axis ["m0 enc", "m0 dec", "url enc", "url dec", "m enc", "m dec", "u enc", "u dec"]
34
+ y-axis "times faster" 0 --> 45
35
+ bar [11.1, 12.6, 24.3, 32.8, 6.0, 7.0, 7.2, 5.3]
36
+ ```
37
+
38
+ ```mermaid
39
+ %%{init: {"themeVariables": {"xyChart": {"plotColorPalette": "#2a78d6"}}}}%%
40
+ xychart-beta
41
+ title "512 kB input: times faster than Ruby"
42
+ x-axis ["m0 enc", "m0 dec", "url enc", "url dec", "m enc", "m dec", "u enc", "u dec"]
43
+ y-axis "times faster" 0 --> 45
44
+ bar [14.0, 13.8, 39.2, 42.0, 7.7, 7.6, 7.3, 5.3]
45
+ ```
46
+
47
+ - `m0`: RFC 4648 (`Ob64::Base64::RFC4648.encode` / `decode`)
48
+ - `url`: RFC 4648 URL-safe (`encode_urlsafe` / `decode_urlsafe`), against Ruby's `pack("m0")` plus `tr`
49
+ - `m`: RFC 2045 (`Ob64::Base64::RFC2045.encode` / `decode`)
50
+ - `u`: uuencode (`Ob64::UU.encode` / `decode`)
51
+
52
+ Try it yourself:
53
+
54
+ $ bin/rake compile benchmark
12
55
 
13
56
  ## Acknowledgements
14
57
 
@@ -115,7 +158,7 @@ See [{file:CHANGELOG.md}](CHANGELOG.md).
115
158
 
116
159
  ## Development
117
160
 
118
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
161
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests, and `rake spec:c` to check the C codecs for memory errors. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
119
162
 
120
163
  To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
121
164
 
data/Rakefile CHANGED
@@ -14,6 +14,17 @@ Rake::ExtensionTask.new("ob64_ext") do |ext|
14
14
  ext.ext_dir = "ext/ob64"
15
15
  end
16
16
 
17
+ desc "Check the SIMD codecs against scalar in C, under ASan and UBSan; SEED=n repeats a run"
18
+ task "spec:c" do
19
+ mkdir_p "tmp"
20
+ sh "#{ENV.fetch("CC", "cc")} -std=gnu99 -O3 -g -fsanitize=address,undefined -fno-sanitize-recover=all " \
21
+ "-fno-omit-frame-pointer -Wall -Wextra -Wno-unused-parameter -Werror -Iext/ob64 -o tmp/codecs_test " \
22
+ "spec/c/codecs_test.c ext/ob64/pack_base64.c ext/ob64/pack_uuencode.c"
23
+ # SDE: an emulator prefix, see .github/scripts/install-sde. It must run the test
24
+ # itself: under SDE, ASan programs started by another one exit 0 unrun.
25
+ sh "#{ENV["SDE"]} tmp/codecs_test #{ENV["SEED"]}"
26
+ end
27
+
17
28
  desc "Run benchmark"
18
29
  task :benchmark do
19
30
  load "#{__dir__}/benchmark.rb"
data/ext/ob64/ob64_ext.c CHANGED
@@ -1,5 +1,6 @@
1
1
  #include <stdbool.h>
2
2
  #include "ob64.h"
3
+ #include "ruby/encoding.h"
3
4
 
4
5
  VALUE mOb64 = Qnil;
5
6
  VALUE mOb64_Ext = Qnil;
@@ -51,6 +52,8 @@ VALUE Ob64_encode_string(VALUE self, VALUE bin, VALUE urlsafe, VALUE padding)
51
52
 
52
53
  pack_base64_encode(RSTRING_PTR(bin), bin_len, RSTRING_PTR(result), flags);
53
54
  rb_str_set_len(result, __encoded_length_of(bin_len, RTEST(padding)));
55
+ /* US-ASCII, as pack("m0") */
56
+ ENCODING_CODERANGE_SET(result, rb_usascii_encindex(), ENC_CODERANGE_7BIT);
54
57
  RB_GC_GUARD(bin);
55
58
 
56
59
  return result;
@@ -4359,7 +4359,7 @@ pack_base64_xgetbv (uint32_t index)
4359
4359
  }
4360
4360
 
4361
4361
  #ifndef bit_AVX512vl
4362
- #define bit_AVX512vl (1 << 31)
4362
+ #define bit_AVX512vl (1u << 31)
4363
4363
  #endif
4364
4364
  #ifndef bit_AVX512vbmi
4365
4365
  #define bit_AVX512vbmi (1 << 1)
data/lib/ob64/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Ob64
4
- VERSION = "0.7.0"
4
+ VERSION = "0.8.0"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ob64
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - João Fernandes
@@ -19,6 +19,7 @@ extensions:
19
19
  extra_rdoc_files: []
20
20
  files:
21
21
  - ".github/dependabot.yml"
22
+ - ".github/scripts/install-sde"
22
23
  - ".github/workflows/main.yml"
23
24
  - ".gitignore"
24
25
  - ".rspec"
@@ -59,8 +60,8 @@ licenses:
59
60
  metadata:
60
61
  homepage_uri: https://github.com/jcmfernandes/ob64
61
62
  changelog_uri: https://github.com/jcmfernandes/ob64/blob/master/CHANGELOG.md
62
- documentation_uri: https://www.rubydoc.info/gems/ob64/0.7.0
63
- source_code_uri: https://github.com/jcmfernandes/ob64/tree/v0.7.0
63
+ documentation_uri: https://www.rubydoc.info/gems/ob64/0.8.0
64
+ source_code_uri: https://github.com/jcmfernandes/ob64/tree/v0.8.0
64
65
  rdoc_options: []
65
66
  require_paths:
66
67
  - lib