Top Level Namespace

Defined Under Namespace

Modules: ImagePack, ImagePackMozjpegSources

Constant Summary collapse

MOZJPEG_CORE_SOURCES =
ImagePackMozjpegSources::TOPLEVEL_C_SOURCES
MOZJPEG_INCLUDE_TEMPLATES =
ImagePackMozjpegSources::TOPLEVEL_INCLUDE_TEMPLATES
NEON_AARCH64_SOURCES =
ImagePackMozjpegSources::NEON_AARCH64_C_SOURCES
X86_64_C_SOURCES =
ImagePackMozjpegSources::X86_64_C_SOURCES
X86_64_ASM_SOURCES =
ImagePackMozjpegSources::X86_64_ASM_SOURCES
MOZJPEG_VERSION =
ImagePackMozjpegSources::VERSION
MOZJPEG_VERSION_NUMBER =
ImagePackMozjpegSources::VERSION_NUMBER
NEON_COMPAT_HEADER =
<<~HEADER
  /*
   * Generated by image_pack extconf.rb (replaces simd/arm/neon-compat.h.in
   * which CMake would otherwise process). All three intrinsic feature flags
   * are unconditionally enabled — every AArch64 toolchain image_pack supports
   * provides them.
   */
  #ifndef IMAGE_PACK_NEON_COMPAT_H
  #define IMAGE_PACK_NEON_COMPAT_H

  #define HAVE_VLD1_S16_X3
  #define HAVE_VLD1_U16_X2
  #define HAVE_VLD1Q_U8_X4

  #if defined(_MSC_VER) && !defined(__clang__)
  #define BUILTIN_CLZ(x)  _CountLeadingZeros(x)
  #define BUILTIN_CLZLL(x) _CountLeadingZeros64(x)
  #define BUILTIN_BSWAP64(x) _byteswap_uint64(x)
  #elif defined(__clang__) || defined(__GNUC__)
  #define BUILTIN_CLZ(x)  __builtin_clz(x)
  #define BUILTIN_CLZLL(x) __builtin_clzll(x)
  #define BUILTIN_BSWAP64(x) __builtin_bswap64(x)
  #else
  #error "Unknown compiler"
  #endif

  #endif /* IMAGE_PACK_NEON_COMPAT_H */
HEADER

Instance Method Summary collapse

Instance Method Details

#configure_vendored_mozjpeg(vendor_dir) ⇒ Object



274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
# File 'ext/image_pack/extconf.rb', line 274

def configure_vendored_mozjpeg(vendor_dir)
  versions = File.read(File.join(vendor_dir, ".vendored"))
  mozjpeg_dir = File.join(vendor_dir, "mozjpeg")

  abort "vendored MozJPEG sources not found in #{mozjpeg_dir}" unless Dir.exist?(mozjpeg_dir)

  required_sources = MOZJPEG_CORE_SOURCES + MOZJPEG_INCLUDE_TEMPLATES
  missing_sources = required_sources.reject { |source| File.exist?(File.join(mozjpeg_dir, source)) }
  abort "vendored MozJPEG sources are incomplete: #{missing_sources.join(', ')}" unless missing_sources.empty?

  arch = detect_simd_arch
  backend = select_simd_backend(mozjpeg_dir, arch)
  with_simd = backend[:kind] != :none
  simd_c_sources = backend.fetch(:c_sources, [])
  simd_asm_sources = backend.fetch(:asm_sources, [])
  scalar_fallback = with_simd ? [] : ["jsimd_none.c"]

  all_c_sources = MOZJPEG_CORE_SOURCES + scalar_fallback + simd_c_sources

  puts "Building with VENDORED MozJPEG from #{mozjpeg_dir}"
  puts "  #{versions.tr("\n", ", ")}"
  puts "  arch=#{arch} backend=#{backend[:kind]}"
  puts "  #{MOZJPEG_CORE_SOURCES.length} core C files + " \
       "#{simd_c_sources.length} SIMD C files + " \
       "#{simd_asm_sources.length} ASM files" \
       "#{with_simd ? '' : ' + jsimd_none.c stub'}"

  build_dir = Dir.pwd
  write_mozjpeg_config_headers!(build_dir, with_simd: with_simd)

  $CPPFLAGS += " -I#{build_dir}"
  $CPPFLAGS += " -I#{mozjpeg_dir}"

  vpath_dirs = [find_image_pack_c_dir, mozjpeg_dir]

  if with_simd
    backend[:extra_includes].each { |inc| $CPPFLAGS += " -I#{inc}" }
    extra_cflags = backend[:extra_cflags].to_s
    $CFLAGS += " #{extra_cflags}" unless extra_cflags.empty?
    simd_c_dirs = simd_c_sources.map { |rel| File.dirname(File.join(mozjpeg_dir, rel)) }.uniq
    vpath_dirs += simd_c_dirs
  end

  $srcs = ["image_pack.c"] + all_c_sources
  $VPATH = vpath_dirs.uniq

  {
    backend:       backend,
    with_simd:     with_simd,
    vpath_dirs:    vpath_dirs.uniq,
    asm_sources:   simd_asm_sources,
    mozjpeg_dir:   mozjpeg_dir,
  }
end

#detect_simd_archObject



72
73
74
75
76
77
78
# File 'ext/image_pack/extconf.rb', line 72

def detect_simd_arch
  case RUBY_PLATFORM
  when /\A(?:arm64|aarch64)/ then :neon_aarch64
  when /\A(?:x86_64|x64)/    then :x86_64
  else                            :none
  end
end

#find_image_pack_c_dirObject



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# File 'ext/image_pack/extconf.rb', line 39

def find_image_pack_c_dir
  candidates = [
    __dir__,
    File.join(__dir__, "..", "..", "..", "..", "ext", "image_pack"),
  ]

  dir = __dir__
  6.times do
    candidates << File.join(dir, "ext", "image_pack")
    dir = File.dirname(dir)
  end

  candidates.find { |path| File.exist?(File.join(path, "image_pack.c")) }
            &.then { |path| File.expand_path(path) } || __dir__
end

#find_nasmObject



80
81
82
83
# File 'ext/image_pack/extconf.rb', line 80

def find_nasm
  presence(ENV["AS_NASM"]) ||
    %w[nasm yasm].find { |bin| system("which #{bin} >/dev/null 2>&1") }
end

#find_vendor_dirObject



21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# File 'ext/image_pack/extconf.rb', line 21

def find_vendor_dir
  candidates = [
    File.join(__dir__, "vendor"),
    File.join(__dir__, "..", "..", "..", "..", "ext", "image_pack", "vendor"),
    File.expand_path("../../ext/image_pack/vendor", __dir__),
    File.join(Dir.pwd, "ext", "image_pack", "vendor"),
  ]

  dir = __dir__
  6.times do
    candidates << File.join(dir, "ext", "image_pack", "vendor")
    dir = File.dirname(dir)
  end

  candidates.find { |path| File.exist?(File.join(path, ".vendored")) }
            &.then { |path| File.expand_path(path) }
end

#inline_keywordObject



64
65
66
# File 'ext/image_pack/extconf.rb', line 64

def inline_keyword
  msvc? ? "__inline" : "inline __attribute__((always_inline))"
end

#msvc?Boolean

Returns:

  • (Boolean)


55
56
57
# File 'ext/image_pack/extconf.rb', line 55

def msvc?
  /mswin|mingw|cygwin/i.match?(RUBY_PLATFORM)
end

#patch_makefile_nasm!(asm_sources, mozjpeg_dir) ⇒ Object



340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
# File 'ext/image_pack/extconf.rb', line 340

def patch_makefile_nasm!(asm_sources, mozjpeg_dir)
  return if asm_sources.empty?

  nasm = find_nasm
  abort "NASM/YASM disappeared between probe and Makefile patch?" unless nasm

  fmt, platform_define, pic_define =
    case RUBY_PLATFORM
    when /darwin/
      ["macho64", "MACHO", nil]
    when /linux/
      ["elf64", "ELF", "PIC"]
    when /mswin|mingw|cygwin/
      ["win64", "WIN64", nil]
    else
      ["elf64", "ELF", "PIC"]
    end

  obj_ext = RbConfig::CONFIG["OBJEXT"] || "o"

  asm_objs = asm_sources.map { |rel| File.basename(rel, ".asm") + ".#{obj_ext}" }
  inc_dirs = [
    File.join(mozjpeg_dir, "simd"),
    File.join(mozjpeg_dir, "simd", "nasm"),
    File.join(mozjpeg_dir, "simd", "x86_64"),
    File.join(mozjpeg_dir, "win"),
  ].select { |d| Dir.exist?(d) }

  nasm_includes = inc_dirs.map { |d| "-I#{d}/" }.join(" ")
  arch_defines = %w[elf64 macho64 win64].include?(fmt) ? ["__x86_64__"] : []
  nasm_defines = ([platform_define, pic_define] + arch_defines).compact.map { |name| "-D#{name}" }.join(" ")

  rules = +""
  asm_sources.each do |rel|
    src = File.join(mozjpeg_dir, rel)
    obj = File.basename(rel, ".asm") + ".#{obj_ext}"
    rules << "#{obj}: #{src}\n"
    rules << "\t#{nasm} -f#{fmt} #{nasm_defines} #{nasm_includes} -o $@ $<\n"
  end

  makefile = File.read("Makefile")
  return if makefile.include?("# vendored mozjpeg nasm")

  asm_obj_line = asm_objs.join(" ")
  makefile.sub!(/^(OBJS\s*=[^\n]*)$/) { "#{Regexp.last_match(1)} #{asm_obj_line}\n# vendored mozjpeg nasm" }
  makefile << "\n# vendored mozjpeg nasm rules\n"
  makefile << rules
  File.write("Makefile", makefile)
  puts "  Patched Makefile with #{asm_sources.length} NASM rules (#{nasm}, -f#{fmt})"
end

#patch_makefile_vpath!(vpath_dirs) ⇒ Object



329
330
331
332
333
334
335
336
337
338
# File 'ext/image_pack/extconf.rb', line 329

def patch_makefile_vpath!(vpath_dirs)
  makefile = File.read("Makefile")
  return if makefile.include?("# vendored mozjpeg vpath")

  vpath_lines = vpath_dirs.map { |dir| "vpath %.c #{dir}" }.join("\n")

  makefile.sub!(/^(VPATH\s*=[^\n]*)$/) { "#{Regexp.last_match(1)}\n# vendored mozjpeg vpath\n#{vpath_lines}" }
  File.write("Makefile", makefile)
  puts "  Patched Makefile with #{vpath_dirs.length} VPATH entries"
end

#presence(string) ⇒ Object



85
86
87
88
89
90
# File 'ext/image_pack/extconf.rb', line 85

def presence(string)
  return nil if string.nil?
  return nil if string.empty?

  string
end

#select_simd_backend(mozjpeg_dir, arch) ⇒ Object



157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
# File 'ext/image_pack/extconf.rb', line 157

def select_simd_backend(mozjpeg_dir, arch)
  case arch
  when :neon_aarch64
    template_in = File.join(mozjpeg_dir, "simd", "arm", "neon-compat.h.in")
    sources_present = NEON_AARCH64_SOURCES.all? { |rel| File.exist?(File.join(mozjpeg_dir, rel)) }
    template_present = File.exist?(template_in)

    if sources_present && template_present
      write_neon_compat_header!(mozjpeg_dir)

      {
        kind:           :neon_aarch64,
        c_sources:      NEON_AARCH64_SOURCES,
        asm_sources:    [],
        extra_includes: [
          File.join(mozjpeg_dir, "simd", "arm"),
          File.join(mozjpeg_dir, "simd", "arm", "aarch64"),
        ],
        extra_cflags:   "-DNEON_INTRINSICS=1"
      }
    else
      missing = NEON_AARCH64_SOURCES.reject { |rel| File.exist?(File.join(mozjpeg_dir, rel)) }
      missing << "simd/arm/neon-compat.h.in" unless template_present
      message = "image_pack: ARM64 detected but NEON sources are incomplete under #{mozjpeg_dir}:
"                 "#{missing.map { |m| "  - #{m}" }.join("
")}"
      if ENV["IMAGE_PACK_REQUIRE_SIMD"] == "1"
        abort "#{message}
image_pack: refusing scalar fallback because IMAGE_PACK_REQUIRE_SIMD=1."
      end
      warn message
      warn "image_pack: re-run `bundle exec rake vendor` after updating script/vendor_libs.rb."
      warn "image_pack: falling back to scalar."
      { kind: :none }
    end

  when :x86_64
    if find_nasm.nil?
      message = "image_pack: x86_64 detected but NASM/YASM not found on PATH."
      if ENV["IMAGE_PACK_REQUIRE_SIMD"] == "1"
        abort "#{message} Install nasm/yasm or unset IMAGE_PACK_REQUIRE_SIMD."
      end
      warn "#{message} Falling back to scalar; install nasm for ~3-4x speedup. Set IMAGE_PACK_REQUIRE_SIMD=1 to fail instead."
      { kind: :none }
    elsif !X86_64_C_SOURCES.all? { |rel| File.exist?(File.join(mozjpeg_dir, rel)) }
      missing = X86_64_C_SOURCES.reject { |rel| File.exist?(File.join(mozjpeg_dir, rel)) }
      message = "image_pack: x86_64 SIMD C sources missing under #{mozjpeg_dir}: #{missing.join(', ')}"
      ENV["IMAGE_PACK_REQUIRE_SIMD"] == "1" ? abort(message) : warn("#{message}. Falling back to scalar.")
      { kind: :none }
    else
      missing_asm = X86_64_ASM_SOURCES.reject { |rel| File.exist?(File.join(mozjpeg_dir, rel)) }
      unless missing_asm.empty?
        message = "image_pack: x86_64 SIMD ASM sources missing under #{mozjpeg_dir}: #{missing_asm.join(', ')}"
        if ENV["IMAGE_PACK_REQUIRE_SIMD"] == "1"
          abort message
        end
        warn "#{message}. Falling back to scalar."
        return { kind: :none }
      end

      {
        kind:           :x86_64_simd,
        c_sources:      X86_64_C_SOURCES,
        asm_sources:    X86_64_ASM_SOURCES,
        extra_includes: [
          File.join(mozjpeg_dir, "simd"),
          File.join(mozjpeg_dir, "simd", "nasm"),
          File.join(mozjpeg_dir, "simd", "x86_64"),
        ],
        extra_cflags:   "",
      }
    end

  else
    { kind: :none }
  end
end

#sizeof_size_tObject



59
60
61
62
# File 'ext/image_pack/extconf.rb', line 59

def sizeof_size_t
  value = RbConfig::CONFIG["SIZEOF_SIZE_T"] || RbConfig::CONFIG["SIZEOF_VOIDP"] || RbConfig::CONFIG["SIZEOF_LONG"]
  value.to_i.positive? ? value.to_i : 8
end

#thread_local_keywordObject



68
69
70
# File 'ext/image_pack/extconf.rb', line 68

def thread_local_keyword
  msvc? ? "__declspec(thread)" : "__thread"
end

#write_mozjpeg_config_headers!(build_dir, with_simd:) ⇒ Object



92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# File 'ext/image_pack/extconf.rb', line 92

def write_mozjpeg_config_headers!(build_dir, with_simd:)
  with_simd_flag = with_simd ? "#define WITH_SIMD 1" : "#undef WITH_SIMD"

  File.write(File.join(build_dir, "jconfig.h"), <<~HEADER)
    /* Generated by image_pack extconf.rb. */
    #define JPEG_LIB_VERSION 62
    #define LIBJPEG_TURBO_VERSION #{MOZJPEG_VERSION}
    #define LIBJPEG_TURBO_VERSION_NUMBER #{MOZJPEG_VERSION_NUMBER}
    #undef C_ARITH_CODING_SUPPORTED
    #undef D_ARITH_CODING_SUPPORTED
    #define MEM_SRCDST_SUPPORTED 1
    #{with_simd_flag}
    #define BITS_IN_JSAMPLE 8
    #undef RIGHT_SHIFT_IS_UNSIGNED
  HEADER

  File.write(File.join(build_dir, "jconfigint.h"), <<~HEADER)
    /* Generated by image_pack extconf.rb. */
    #define BUILD "image_pack"
    #undef inline
    #define INLINE #{inline_keyword}
    #define THREAD_LOCAL #{thread_local_keyword}
    #define PACKAGE_NAME "mozjpeg"
    #define VERSION "#{MOZJPEG_VERSION}"
    #define SIZEOF_SIZE_T #{sizeof_size_t}
    #if defined(__has_attribute)
    #if __has_attribute(fallthrough)
    #define FALLTHROUGH __attribute__((fallthrough));
    #else
    #define FALLTHROUGH
    #endif
    #else
    #define FALLTHROUGH
    #endif
  HEADER

  File.write(File.join(build_dir, "jversion.h"), <<~HEADER)
    /* Generated by image_pack extconf.rb. */
    #if JPEG_LIB_VERSION >= 80
    #define JVERSION "8d  15-Jan-2012"
    #elif JPEG_LIB_VERSION >= 70
    #define JVERSION "7  27-Jun-2009"
    #else
    #define JVERSION "6b  27-Mar-1998"
    #endif

    #define JCOPYRIGHT \
      "Copyright (C) 2009-2023 D. R. Commander\\n" \
      "Copyright (C) 2015, 2020 Google, Inc.\\n" \
      "Copyright (C) 2019-2020 Arm Limited\\n" \
      "Copyright (C) 2015-2016, 2018 Matthieu Darbois\\n" \
      "Copyright (C) 2011-2016 Siarhei Siamashka\\n" \
      "Copyright (C) 2015 Intel Corporation\\n" \
      "Copyright (C) 2013-2014 Linaro Limited\\n" \
      "Copyright (C) 2013-2014 MIPS Technologies, Inc.\\n" \
      "Copyright (C) 2009, 2012 Pierre Ossman for Cendio AB\\n" \
      "Copyright (C) 2009-2011 Nokia Corporation and/or its subsidiary(-ies)\\n" \
      "Copyright (C) 1999-2006 MIYASAKA Masaru\\n" \
      "Copyright (C) 1991-2020 Thomas G. Lane, Guido Vollbeding"

    #define JCOPYRIGHT_SHORT \
      "Copyright (C) 1991-2023 The libjpeg-turbo Project and many others"
  HEADER
end

#write_neon_compat_header!(mozjpeg_dir) ⇒ Object



264
265
266
267
268
269
270
271
272
# File 'ext/image_pack/extconf.rb', line 264

def write_neon_compat_header!(mozjpeg_dir)
  target = File.join(mozjpeg_dir, "simd", "arm", "neon-compat.h")
  return target if File.exist?(target) &&
                   File.read(target) == NEON_COMPAT_HEADER
  FileUtils.mkdir_p(File.dirname(target))
  File.write(target, NEON_COMPAT_HEADER)
  puts "  Generated #{target}"
  target
end