From 11fd8ba3300a993fa372f0d419f5b5954ab2ebf6 Mon Sep 17 00:00:00 2001 From: Christian Blichmann Date: Thu, 6 Aug 2020 06:53:17 -0700 Subject: [PATCH 01/82] Collect Bazel files into `bzl_library` targets PiperOrigin-RevId: 325221214 Change-Id: Iab03b900e143b9b95bed151097abb59ac1e0f996 --- sandboxed_api/bazel/BUILD | 32 ++++++++++++++++++++++++++++++ sandboxed_api/bazel/embed_data.bzl | 4 ++-- sandboxed_api/bazel/proto.bzl | 2 +- 3 files changed, 35 insertions(+), 3 deletions(-) diff --git a/sandboxed_api/bazel/BUILD b/sandboxed_api/bazel/BUILD index 78acfea..2448930 100644 --- a/sandboxed_api/bazel/BUILD +++ b/sandboxed_api/bazel/BUILD @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +load("@bazel_skylib//:bzl_library.bzl", "bzl_library") + licenses(["notice"]) exports_files([ @@ -19,3 +21,33 @@ exports_files([ "embed_data.bzl", "sapi.bzl", ]) + +bzl_library( + name = "build_defs_bzl", + srcs = ["build_defs.bzl"], + visibility = ["//visibility:private"], +) + +bzl_library( + name = "embed_data_bzl", + srcs = ["embed_data.bzl"], + visibility = ["//visibility:private"], +) + +bzl_library( + name = "proto_bzl", + srcs = ["proto.bzl"], + visibility = ["//visibility:private"], +) + +bzl_library( + name = "repositories_bzl", + srcs = ["repositories.bzl"], + visibility = ["//visibility:private"], +) + +bzl_library( + name = "sapi_deps_bzl", + srcs = ["sapi_deps.bzl"], + visibility = ["//visibility:private"], +) diff --git a/sandboxed_api/bazel/embed_data.bzl b/sandboxed_api/bazel/embed_data.bzl index 85ce1ba..dc12c4f 100644 --- a/sandboxed_api/bazel/embed_data.bzl +++ b/sandboxed_api/bazel/embed_data.bzl @@ -16,8 +16,8 @@ _FILEWRAPPER = "//sandboxed_api/tools/filewrapper" -# TODO(cblichmann): Convert this is to use a "_cc_toolchain" once Bazel #4370 -# is fixed. +# TODO(cblichmann): Convert this to use a "_cc_toolchain" once Bazel #4370 is +# fixed. def _sapi_cc_embed_data_impl(ctx): cc_file_artifact = None h_file_artifact = None diff --git a/sandboxed_api/bazel/proto.bzl b/sandboxed_api/bazel/proto.bzl index 43444c8..c8970d1 100644 --- a/sandboxed_api/bazel/proto.bzl +++ b/sandboxed_api/bazel/proto.bzl @@ -56,7 +56,7 @@ def sapi_proto_library( deps = [], alwayslink = False, **kwargs): - """Generates proto targets in various languages. + """Generates proto library and C++ targets. Args: name: Name for proto_library and base for the cc_proto_library name, name + From b76cb15f26a61783491fa35dabe0cf15d71f8155 Mon Sep 17 00:00:00 2001 From: Christian Blichmann Date: Fri, 7 Aug 2020 00:30:04 -0700 Subject: [PATCH 02/82] Rename accessors, move away from `time_t` API - `GetPid()` -> `pid()` - `GetRpcChannel()` -> `rpc_channel()` - `IsActive()` -> `is_active()` - Suggest `SetWallTimeLimit(time_t)` -> `SetWallTimeLimit(absl::Duration)` In addition, remove the protected zero-argument contructor. PiperOrigin-RevId: 325390292 Change-Id: Iba044ad5ce44e78c4064c0a09faaa4227c4d19a5 --- .../examples/stringop/main_stringop.cc | 6 +-- sandboxed_api/examples/sum/main_sum.cc | 2 +- sandboxed_api/sandbox.cc | 51 ++++++++++--------- sandboxed_api/sandbox.h | 30 +++++++---- sandboxed_api/transaction.cc | 13 ++--- sandboxed_api/transaction.h | 8 ++- 6 files changed, 61 insertions(+), 49 deletions(-) diff --git a/sandboxed_api/examples/stringop/main_stringop.cc b/sandboxed_api/examples/stringop/main_stringop.cc index d83dbf9..9880699 100644 --- a/sandboxed_api/examples/stringop/main_stringop.cc +++ b/sandboxed_api/examples/stringop/main_stringop.cc @@ -113,7 +113,7 @@ TEST(StringopTest, RawStringReversal) { { // Let's call it again with different data as argument, reusing the // existing LenVal object. - EXPECT_THAT(param.ResizeData(sandbox.GetRpcChannel(), 16), IsOk()); + EXPECT_THAT(param.ResizeData(sandbox.rpc_channel(), 16), IsOk()); memcpy(param.GetData() + 10, "ABCDEF", 6); absl::string_view data(reinterpret_cast(param.GetData()), param.GetDataSize()); @@ -135,7 +135,7 @@ TEST(StringopTest, RawStringLength) { StringopApi api(&sandbox); SAPI_ASSERT_OK_AND_ASSIGN(void* target_mem_ptr, api.get_raw_c_string()); SAPI_ASSERT_OK_AND_ASSIGN(uint64_t len, - sandbox.GetRpcChannel()->Strlen(target_mem_ptr)); + sandbox.rpc_channel()->Strlen(target_mem_ptr)); EXPECT_THAT(len, Eq(10)); } @@ -145,7 +145,7 @@ TEST(StringopTest, RawStringReading) { StringopApi api(&sandbox); SAPI_ASSERT_OK_AND_ASSIGN(void* target_mem_ptr, api.get_raw_c_string()); SAPI_ASSERT_OK_AND_ASSIGN(uint64_t len, - sandbox.GetRpcChannel()->Strlen(target_mem_ptr)); + sandbox.rpc_channel()->Strlen(target_mem_ptr)); EXPECT_THAT(len, Eq(10)); SAPI_ASSERT_OK_AND_ASSIGN(std::string data, diff --git a/sandboxed_api/examples/sum/main_sum.cc b/sandboxed_api/examples/sum/main_sum.cc index 886be2c..51e233f 100644 --- a/sandboxed_api/examples/sum/main_sum.cc +++ b/sandboxed_api/examples/sum/main_sum.cc @@ -161,7 +161,7 @@ absl::Status SumTransaction::Main() { LOG(INFO) << "Read from /proc/self/comm = [" << buffer << "]"; // Close test. - SAPI_RETURN_IF_ERROR(fd2.CloseRemoteFd(sandbox()->GetRpcChannel())); + SAPI_RETURN_IF_ERROR(fd2.CloseRemoteFd(sandbox()->rpc_channel())); memset(buffer, 0, sizeof(buffer)); SAPI_RETURN_IF_ERROR(sandbox()->Call("read", &ret, &fd2, buf.PtrBoth(), &size)); LOG(INFO) << "Read from closed /proc/self/comm = [" << buffer << "]"; diff --git a/sandboxed_api/sandbox.cc b/sandboxed_api/sandbox.cc index 79d93b1..b29646b 100644 --- a/sandboxed_api/sandbox.cc +++ b/sandboxed_api/sandbox.cc @@ -100,7 +100,7 @@ void InitDefaultPolicyBuilder(sandbox2::PolicyBuilder* builder) { } void Sandbox::Terminate(bool attempt_graceful_exit) { - if (!IsActive()) { + if (!is_active()) { return; } @@ -129,7 +129,7 @@ static std::string PathToSAPILib(const std::string& lib_path) { absl::Status Sandbox::Init() { // It's already initialized - if (IsActive()) { + if (is_active()) { return absl::OkStatus(); } @@ -156,7 +156,7 @@ absl::Status Sandbox::Init() { } } - std::vector args{lib_path}; + std::vector args = {lib_path}; // Additional arguments, if needed. GetArgs(&args); std::vector envs{}; @@ -214,24 +214,24 @@ absl::Status Sandbox::Init() { return absl::OkStatus(); } -bool Sandbox::IsActive() const { return s2_ && !s2_->IsTerminated(); } +bool Sandbox::is_active() const { return s2_ && !s2_->IsTerminated(); } absl::Status Sandbox::Allocate(v::Var* var, bool automatic_free) { - if (!IsActive()) { + if (!is_active()) { return absl::UnavailableError("Sandbox not active"); } return var->Allocate(GetRpcChannel(), automatic_free); } absl::Status Sandbox::Free(v::Var* var) { - if (!IsActive()) { + if (!is_active()) { return absl::UnavailableError("Sandbox not active"); } return var->Free(GetRpcChannel()); } absl::Status Sandbox::SynchronizePtrBefore(v::Callable* ptr) { - if (!IsActive()) { + if (!is_active()) { return absl::UnavailableError("Sandbox not active"); } if (ptr->GetType() != v::Type::kPointer) { @@ -259,11 +259,11 @@ absl::Status Sandbox::SynchronizePtrBefore(v::Callable* ptr) { VLOG(3) << "Synchronization (TO), ptr " << p << ", Type: " << p->GetSyncType() << " for var: " << p->GetPointedVar()->ToString(); - return p->GetPointedVar()->TransferToSandboxee(GetRpcChannel(), GetPid()); + return p->GetPointedVar()->TransferToSandboxee(GetRpcChannel(), pid()); } absl::Status Sandbox::SynchronizePtrAfter(v::Callable* ptr) const { - if (!IsActive()) { + if (!is_active()) { return absl::UnavailableError("Sandbox not active"); } if (ptr->GetType() != v::Type::kPointer) { @@ -287,12 +287,12 @@ absl::Status Sandbox::SynchronizePtrAfter(v::Callable* ptr) const { p->ToString())); } - return p->GetPointedVar()->TransferFromSandboxee(GetRpcChannel(), GetPid()); + return p->GetPointedVar()->TransferFromSandboxee(GetRpcChannel(), pid()); } absl::Status Sandbox::Call(const std::string& func, v::Callable* ret, std::initializer_list args) { - if (!IsActive()) { + if (!is_active()) { return absl::UnavailableError("Sandbox not active"); } // Send data. @@ -371,29 +371,29 @@ absl::Status Sandbox::Call(const std::string& func, v::Callable* ret, } absl::Status Sandbox::Symbol(const char* symname, void** addr) { - if (!IsActive()) { + if (!is_active()) { return absl::UnavailableError("Sandbox not active"); } return rpc_channel_->Symbol(symname, addr); } absl::Status Sandbox::TransferToSandboxee(v::Var* var) { - if (!IsActive()) { + if (!is_active()) { return absl::UnavailableError("Sandbox not active"); } - return var->TransferToSandboxee(GetRpcChannel(), GetPid()); + return var->TransferToSandboxee(GetRpcChannel(), pid()); } absl::Status Sandbox::TransferFromSandboxee(v::Var* var) { - if (!IsActive()) { + if (!is_active()) { return absl::UnavailableError("Sandbox not active"); } - return var->TransferFromSandboxee(GetRpcChannel(), GetPid()); + return var->TransferFromSandboxee(GetRpcChannel(), pid()); } sapi::StatusOr Sandbox::GetCString(const v::RemotePtr& str, uint64_t max_length) { - if (!IsActive()) { + if (!is_active()) { return absl::UnavailableError("Sandbox not active"); } @@ -436,22 +436,25 @@ const sandbox2::Result& Sandbox::AwaitResult() { return result_; } -absl::Status Sandbox::SetWallTimeLimit(time_t limit) const { - if (!IsActive()) { +absl::Status Sandbox::SetWallTimeLimit(absl::Duration limit) const { + if (!is_active()) { return absl::UnavailableError("Sandbox not active"); } - s2_->SetWallTimeLimit(limit); + s2_->set_walltime_limit(limit); return absl::OkStatus(); } +absl::Status Sandbox::SetWallTimeLimit(time_t limit) const { + return SetWallTimeLimit(absl::Seconds(limit)); +} + void Sandbox::Exit() const { - if (!IsActive()) { + if (!is_active()) { return; } - // Give it 1 second - s2_->SetWallTimeLimit(1); + s2_->set_walltime_limit(absl::Seconds(1)); if (!rpc_channel_->Exit().ok()) { - LOG(WARNING) << "rpc_channel->Exit() failed, killing PID: " << GetPid(); + LOG(WARNING) << "rpc_channel->Exit() failed, killing PID: " << pid(); s2_->Kill(); } } diff --git a/sandboxed_api/sandbox.h b/sandboxed_api/sandbox.h index cb49a45..d8f37ef 100644 --- a/sandboxed_api/sandbox.h +++ b/sandboxed_api/sandbox.h @@ -35,18 +35,21 @@ namespace sapi { // means to communicate with it (make function calls, transfer memory). class Sandbox { public: + explicit Sandbox(const FileToc* embed_lib_toc) + : embed_lib_toc_(embed_lib_toc) {} + Sandbox(const Sandbox&) = delete; Sandbox& operator=(const Sandbox&) = delete; - explicit Sandbox(const FileToc* embed_lib_toc) - : comms_(nullptr), pid_(0), embed_lib_toc_(embed_lib_toc) {} virtual ~Sandbox(); // Initializes a new sandboxing session. absl::Status Init(); - // Is the current sandboxing session alive? - bool IsActive() const; + ABSL_DEPRECATED("Use sapi::Sandbox::is_active() instead") + bool IsActive() const { return is_active(); } + // Returns whether the current sandboxing session is active. + bool is_active() const; // Terminates the current sandboxing session (if it exists). void Terminate(bool attempt_graceful_exit = true); @@ -60,9 +63,13 @@ class Sandbox { // Getters for common fields. sandbox2::Comms* comms() const { return comms_; } + ABSL_DEPRECATED("Use sapi::Sandbox::rpc_channel() instead") RPCChannel* GetRpcChannel() const { return rpc_channel_.get(); } + RPCChannel* rpc_channel() const { return rpc_channel_.get(); } + ABSL_DEPRECATED("Use sapi::Sandbox::pid() instead") int GetPid() const { return pid_; } + int pid() const { return pid_; } // Synchronizes the underlying memory for the pointer before the call. absl::Status SynchronizePtrBefore(v::Callable* ptr); @@ -87,7 +94,7 @@ class Sandbox { // Frees memory in the sandboxee. absl::Status Free(v::Var* var); - // Finds address of a symbol in the sandboxee. + // Finds the address of a symbol in the sandboxee. absl::Status Symbol(const char* symname, void** addr); // Transfers memory (both directions). Status is returned (memory transfer @@ -95,14 +102,17 @@ class Sandbox { absl::Status TransferToSandboxee(v::Var* var); absl::Status TransferFromSandboxee(v::Var* var); - sapi::StatusOr GetCString(const v::RemotePtr& str, - uint64_t max_length = 10 * 1024 * - 1024); + sapi::StatusOr GetCString( + const v::RemotePtr& str, uint64_t max_length = 10ULL << 20 /* 10 MiB*/ + ); // Waits until the sandbox terminated and returns the result. const sandbox2::Result& AwaitResult(); const sandbox2::Result& result() const { return result_; } + absl::Status SetWallTimeLimit(absl::Duration limit) const; + ABSL_DEPRECATED( + "Use sapi::Sandbox::SetWallTimeLimit(absl::Duration) overload instead") absl::Status SetWallTimeLimit(time_t limit) const; protected: @@ -144,11 +154,11 @@ class Sandbox { sandbox2::Result result_; // Comms with the sandboxee. - sandbox2::Comms* comms_; + sandbox2::Comms* comms_ = nullptr; // RPCChannel object. std::unique_ptr rpc_channel_; // The main pid of the sandboxee. - pid_t pid_; + pid_t pid_ = 0; // FileTOC with the embedded library, takes precedence over GetLibPath if // present (not nullptr). diff --git a/sandboxed_api/transaction.cc b/sandboxed_api/transaction.cc index 4ecf9a6..a17c148 100644 --- a/sandboxed_api/transaction.cc +++ b/sandboxed_api/transaction.cc @@ -26,12 +26,10 @@ absl::Status TransactionBase::RunTransactionFunctionInSandbox( // Set the wall-time limit for this transaction run, and clean it up // afterwards, no matter what the result. - SAPI_RETURN_IF_ERROR(sandbox_->SetWallTimeLimit(GetTimeLimit())); + SAPI_RETURN_IF_ERROR(sandbox_->SetWallTimeLimit(absl::Seconds(GetTimeLimit()))); struct TimeCleanup { ~TimeCleanup() { - if (capture->sandbox_->IsActive()) { - capture->sandbox_->SetWallTimeLimit(0).IgnoreError(); - } + capture->sandbox_->SetWallTimeLimit(absl::ZeroDuration()).IgnoreError(); } TransactionBase* capture; } sandbox_cleanup = {this}; @@ -66,8 +64,11 @@ absl::Status TransactionBase::RunTransactionLoop( } TransactionBase::~TransactionBase() { - if (initialized_) { - Finish().IgnoreError(); + if (!initialized_) { + return; + } + if (absl::Status status = Finish(); !status.ok()) { + LOG(ERROR) << "Transaction finalizer returned an error: " << status; } } diff --git a/sandboxed_api/transaction.h b/sandboxed_api/transaction.h index 3ff00c2..b194a80 100644 --- a/sandboxed_api/transaction.h +++ b/sandboxed_api/transaction.h @@ -86,9 +86,7 @@ class TransactionBase { protected: explicit TransactionBase(std::unique_ptr sandbox) - : retry_count_(kDefaultRetryCount), - time_limit_(absl::ToTimeT(absl::UnixEpoch() + kDefaultTimeLimit)), - initialized_(false), + : time_limit_(absl::ToTimeT(absl::UnixEpoch() + kDefaultTimeLimit)), sandbox_(std::move(sandbox)) {} // Runs the main (retrying) transaction loop. @@ -115,14 +113,14 @@ class TransactionBase { virtual absl::Status Finish() { return absl::OkStatus(); } // Number of tries this transaction will be re-executed until it succeeds. - int retry_count_; + int retry_count_ = kDefaultRetryCount; // Time (wall-time) limit for a single Run() call (in seconds). 0 means: no // wall-time limit. time_t time_limit_; // Has Init() finished with success? - bool initialized_; + bool initialized_ = false; // The main sapi::Sandbox object. std::unique_ptr sandbox_; From dc8cf90cb3d5e198dc3391abcc22a295807f0301 Mon Sep 17 00:00:00 2001 From: Bohdan Tyshchenko Date: Mon, 10 Aug 2020 06:29:59 -0700 Subject: [PATCH 03/82] Init guetzli sandbox --- oss-internship-2020/guetzli/BUILD.bazel | 65 +++++ oss-internship-2020/guetzli/WORKSPACE | 79 ++++++ .../guetzli/external/png.BUILD | 33 +++ .../guetzli/external/zlib.BUILD | 36 +++ .../guetzli/guetzli_entry_points.cc | 245 ++++++++++++++++++ .../guetzli/guetzli_entry_points.h | 29 +++ oss-internship-2020/guetzli/guetzli_sandbox.h | 36 +++ .../guetzli/guetzli_sandboxed.cc | 160 ++++++++++++ .../guetzli/guetzli_transaction.cc | 160 ++++++++++++ .../guetzli/guetzli_transaction.h | 216 +++++++++++++++ oss-internship-2020/guetzli/tests/BUILD.bazel | 22 ++ .../guetzli/tests/guetzli_sapi_test.cc | 165 ++++++++++++ .../guetzli/tests/guetzli_transaction_test.cc | 2 + .../guetzli/tests/testdata/bees.png | Bin 0 -> 177424 bytes .../guetzli/tests/testdata/landscape.jpg | Bin 0 -> 14418 bytes 15 files changed, 1248 insertions(+) create mode 100644 oss-internship-2020/guetzli/BUILD.bazel create mode 100644 oss-internship-2020/guetzli/WORKSPACE create mode 100644 oss-internship-2020/guetzli/external/png.BUILD create mode 100644 oss-internship-2020/guetzli/external/zlib.BUILD create mode 100644 oss-internship-2020/guetzli/guetzli_entry_points.cc create mode 100644 oss-internship-2020/guetzli/guetzli_entry_points.h create mode 100644 oss-internship-2020/guetzli/guetzli_sandbox.h create mode 100644 oss-internship-2020/guetzli/guetzli_sandboxed.cc create mode 100644 oss-internship-2020/guetzli/guetzli_transaction.cc create mode 100644 oss-internship-2020/guetzli/guetzli_transaction.h create mode 100644 oss-internship-2020/guetzli/tests/BUILD.bazel create mode 100644 oss-internship-2020/guetzli/tests/guetzli_sapi_test.cc create mode 100644 oss-internship-2020/guetzli/tests/guetzli_transaction_test.cc create mode 100644 oss-internship-2020/guetzli/tests/testdata/bees.png create mode 100644 oss-internship-2020/guetzli/tests/testdata/landscape.jpg diff --git a/oss-internship-2020/guetzli/BUILD.bazel b/oss-internship-2020/guetzli/BUILD.bazel new file mode 100644 index 0000000..345f879 --- /dev/null +++ b/oss-internship-2020/guetzli/BUILD.bazel @@ -0,0 +1,65 @@ +load("@rules_cc//cc:defs.bzl", "cc_proto_library") +load("@rules_proto//proto:defs.bzl", "proto_library") +load( + "@com_google_sandboxed_api//sandboxed_api/bazel:proto.bzl", + "sapi_proto_library", +) +load( + "@com_google_sandboxed_api//sandboxed_api/bazel:sapi.bzl", + "sapi_library", +) + +cc_library( + name = "guetzli_wrapper", + srcs = ["guetzli_entry_points.cc"], + hdrs = ["guetzli_entry_points.h"], + deps = [ + "@guetzli//:guetzli_lib", + "@com_google_sandboxed_api//sandboxed_api:lenval_core", + "@com_google_sandboxed_api//sandboxed_api:vars", + #"@com_google_sandboxed_api//sandboxed_api/sandbox2/util:temp_file", visibility error + "@png_archive//:png" + ], + visibility = ["//visibility:public"] +) + +sapi_library( + name = "guetzli_sapi", + #srcs = ["guetzli_transaction.cc"], // Error when try to place definitions insde .cc file + hdrs = ["guetzli_sandbox.h", "guetzli_transaction.h"], + functions = [ + "ProcessJPEGString", + "ProcessRGBData", + "ButteraugliScoreQuality", + "ReadPng", + "ReadJpegData", + "ReadDataFromFd", + "WriteDataToFd" + ], + input_files = ["guetzli_entry_points.h"], + lib = ":guetzli_wrapper", + lib_name = "Guetzli", + visibility = ["//visibility:public"], + namespace = "guetzli::sandbox" +) + +# cc_library( +# name = "guetzli_sapi_transaction", +# #srcs = ["guetzli_transaction.cc"], +# hdrs = ["guetzli_transaction.h"], +# deps = [ +# ":guetzli_sapi" +# ], +# visibility = ["//visibility:public"] +# ) + +cc_binary( + name="guetzli_sandboxed", + srcs=["guetzli_sandboxed.cc"], + includes = ["."], + visibility= [ "//visibility:public" ], + deps = [ + #":guetzli_sapi_transaction" + ":guetzli_sapi" + ] +) \ No newline at end of file diff --git a/oss-internship-2020/guetzli/WORKSPACE b/oss-internship-2020/guetzli/WORKSPACE new file mode 100644 index 0000000..17887de --- /dev/null +++ b/oss-internship-2020/guetzli/WORKSPACE @@ -0,0 +1,79 @@ +# Copyright 2020 Google LLC +# +# 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. + +workspace(name = "guetzli_sandboxed") + +load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository") +load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe") + +# Include the Sandboxed API dependency if it does not already exist in this +# project. This ensures that this workspace plays well with other external +# dependencies that might use Sandboxed API. +maybe( + git_repository, + name = "com_google_sandboxed_api", + # This example depends on the latest master. In an embedding project, it + # is advisable to pin Sandboxed API to a specific revision instead. + # commit = "ba47adc21d4c9bc316f3c7c32b0faaef952c111e", # 2020-05-15 + branch = "master", + remote = "https://github.com/google/sandboxed-api.git", +) + +# From here on, Sandboxed API files are available. The statements below setup +# transitive dependencies such as Abseil. Like above, those will only be +# included if they don't already exist in the project. +load( + "@com_google_sandboxed_api//sandboxed_api/bazel:sapi_deps.bzl", + "sapi_deps", +) + +sapi_deps() + +# Need to separately setup Protobuf dependencies in order for the build rules +# to work. +load("@com_google_protobuf//:protobuf_deps.bzl", "protobuf_deps") + +protobuf_deps() + +maybe( + git_repository, + name = "guetzli", + remote = "https://github.com/google/guetzli.git", + branch = "master" +) + +maybe( + git_repository, + name = "googletest", + remote = "https://github.com/google/googletest", + tag = "release-1.10.0" +) + +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") + +http_archive( + name = "png_archive", + build_file = "png.BUILD", + sha256 = "a941dc09ca00148fe7aaf4ecdd6a67579c293678ed1e1cf633b5ffc02f4f8cf7", + strip_prefix = "libpng-1.2.57", + url = "http://github.com/glennrp/libpng/archive/v1.2.57.zip", +) + +http_archive( + name = "zlib_archive", + build_file = "zlib.BUILD", + sha256 = "8d7e9f698ce48787b6e1c67e6bff79e487303e66077e25cb9784ac8835978017", + strip_prefix = "zlib-1.2.10", + url = "http://zlib.net/fossils/zlib-1.2.10.tar.gz", +) \ No newline at end of file diff --git a/oss-internship-2020/guetzli/external/png.BUILD b/oss-internship-2020/guetzli/external/png.BUILD new file mode 100644 index 0000000..9ff982b --- /dev/null +++ b/oss-internship-2020/guetzli/external/png.BUILD @@ -0,0 +1,33 @@ +# Description: +# libpng is the official PNG reference library. + +licenses(["notice"]) # BSD/MIT-like license + +cc_library( + name = "png", + srcs = [ + "png.c", + "pngerror.c", + "pngget.c", + "pngmem.c", + "pngpread.c", + "pngread.c", + "pngrio.c", + "pngrtran.c", + "pngrutil.c", + "pngset.c", + "pngtrans.c", + "pngwio.c", + "pngwrite.c", + "pngwtran.c", + "pngwutil.c", + ], + hdrs = [ + "png.h", + "pngconf.h", + ], + includes = ["."], + linkopts = ["-lm"], + visibility = ["//visibility:public"], + deps = ["@zlib_archive//:zlib"], +) diff --git a/oss-internship-2020/guetzli/external/zlib.BUILD b/oss-internship-2020/guetzli/external/zlib.BUILD new file mode 100644 index 0000000..edb77fd --- /dev/null +++ b/oss-internship-2020/guetzli/external/zlib.BUILD @@ -0,0 +1,36 @@ +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) # BSD/MIT-like license (for zlib) + +cc_library( + name = "zlib", + srcs = [ + "adler32.c", + "compress.c", + "crc32.c", + "crc32.h", + "deflate.c", + "deflate.h", + "gzclose.c", + "gzguts.h", + "gzlib.c", + "gzread.c", + "gzwrite.c", + "infback.c", + "inffast.c", + "inffast.h", + "inffixed.h", + "inflate.c", + "inflate.h", + "inftrees.c", + "inftrees.h", + "trees.c", + "trees.h", + "uncompr.c", + "zconf.h", + "zutil.c", + "zutil.h", + ], + hdrs = ["zlib.h"], + includes = ["."], +) diff --git a/oss-internship-2020/guetzli/guetzli_entry_points.cc b/oss-internship-2020/guetzli/guetzli_entry_points.cc new file mode 100644 index 0000000..446150c --- /dev/null +++ b/oss-internship-2020/guetzli/guetzli_entry_points.cc @@ -0,0 +1,245 @@ +#include "guetzli/jpeg_data_reader.h" +#include "guetzli/quality.h" +#include "guetzli_entry_points.h" +#include "png.h" +#include "sandboxed_api/sandbox2/util/fileops.h" + +#include +#include +#include +#include +#include + +namespace { + +inline uint8_t BlendOnBlack(const uint8_t val, const uint8_t alpha) { + return (static_cast(val) * static_cast(alpha) + 128) / 255; +} + +template +void CopyMemoryToLenVal(const T* data, size_t size, + sapi::LenValStruct* out_data) { + free(out_data->data); // Not sure about this + out_data->size = size; + T* new_out = static_cast(malloc(size)); + memcpy(new_out, data, size); + out_data->data = new_out; +} + +} // namespace + +extern "C" bool ProcessJPEGString(const guetzli::Params* params, + int verbose, + sapi::LenValStruct* in_data, + sapi::LenValStruct* out_data) +{ + std::string in_data_temp(static_cast(in_data->data), + in_data->size); + + guetzli::ProcessStats stats; + if (verbose > 0) { + stats.debug_output_file = stderr; + } + + std::string temp_out = ""; + auto result = guetzli::Process(*params, &stats, in_data_temp, &temp_out); + + if (result) { + CopyMemoryToLenVal(temp_out.data(), temp_out.size(), out_data); + } + + return result; +} + +extern "C" bool ProcessRGBData(const guetzli::Params* params, + int verbose, + sapi::LenValStruct* rgb, + int w, int h, + sapi::LenValStruct* out_data) +{ + std::vector in_data_temp; + in_data_temp.reserve(rgb->size); + + auto* rgb_data = static_cast(rgb->data); + std::copy(rgb_data, rgb_data + rgb->size, std::back_inserter(in_data_temp)); + + guetzli::ProcessStats stats; + if (verbose > 0) { + stats.debug_output_file = stderr; + } + + std::string temp_out = ""; + auto result = + guetzli::Process(*params, &stats, in_data_temp, w, h, &temp_out); + + //TODO: Move shared part of the code to another function + if (result) { + CopyMemoryToLenVal(temp_out.data(), temp_out.size(), out_data); + } + + return result; +} + +extern "C" bool ReadPng(sapi::LenValStruct* in_data, + int* xsize, int* ysize, + sapi::LenValStruct* rgb_out) +{ + std::string data(static_cast(in_data->data), in_data->size); + std::vector rgb; + + png_structp png_ptr = + png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); + if (!png_ptr) { + return false; + } + + png_infop info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) { + png_destroy_read_struct(&png_ptr, nullptr, nullptr); + return false; + } + + if (setjmp(png_jmpbuf(png_ptr)) != 0) { + // Ok we are here because of the setjmp. + png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); + return false; + } + + std::istringstream memstream(data, std::ios::in | std::ios::binary); + png_set_read_fn(png_ptr, static_cast(&memstream), [](png_structp png_ptr, png_bytep outBytes, png_size_t byteCountToRead) { + std::istringstream& memstream = *static_cast(png_get_io_ptr(png_ptr)); + + memstream.read(reinterpret_cast(outBytes), byteCountToRead); + + if (memstream.eof()) png_error(png_ptr, "unexpected end of data"); + if (memstream.fail()) png_error(png_ptr, "read from memory error"); + }); + + // The png_transforms flags are as follows: + // packing == convert 1,2,4 bit images, + // strip == 16 -> 8 bits / channel, + // shift == use sBIT dynamics, and + // expand == palettes -> rgb, grayscale -> 8 bit images, tRNS -> alpha. + const unsigned int png_transforms = + PNG_TRANSFORM_PACKING | PNG_TRANSFORM_EXPAND | PNG_TRANSFORM_STRIP_16; + + png_read_png(png_ptr, info_ptr, png_transforms, nullptr); + + png_bytep* row_pointers = png_get_rows(png_ptr, info_ptr); + + *xsize = png_get_image_width(png_ptr, info_ptr); + *ysize = png_get_image_height(png_ptr, info_ptr); + rgb.resize(3 * (*xsize) * (*ysize)); + + const int components = png_get_channels(png_ptr, info_ptr); + switch (components) { + case 1: { + // GRAYSCALE + for (int y = 0; y < *ysize; ++y) { + const uint8_t* row_in = row_pointers[y]; + uint8_t* row_out = &(rgb)[3 * y * (*xsize)]; + for (int x = 0; x < *xsize; ++x) { + const uint8_t gray = row_in[x]; + row_out[3 * x + 0] = gray; + row_out[3 * x + 1] = gray; + row_out[3 * x + 2] = gray; + } + } + break; + } + case 2: { + // GRAYSCALE + ALPHA + for (int y = 0; y < *ysize; ++y) { + const uint8_t* row_in = row_pointers[y]; + uint8_t* row_out = &(rgb)[3 * y * (*xsize)]; + for (int x = 0; x < *xsize; ++x) { + const uint8_t gray = BlendOnBlack(row_in[2 * x], row_in[2 * x + 1]); + row_out[3 * x + 0] = gray; + row_out[3 * x + 1] = gray; + row_out[3 * x + 2] = gray; + } + } + break; + } + case 3: { + // RGB + for (int y = 0; y < *ysize; ++y) { + const uint8_t* row_in = row_pointers[y]; + uint8_t* row_out = &(rgb)[3 * y * (*xsize)]; + memcpy(row_out, row_in, 3 * (*xsize)); + } + break; + } + case 4: { + // RGBA + for (int y = 0; y < *ysize; ++y) { + const uint8_t* row_in = row_pointers[y]; + uint8_t* row_out = &(rgb)[3 * y * (*xsize)]; + for (int x = 0; x < *xsize; ++x) { + const uint8_t alpha = row_in[4 * x + 3]; + row_out[3 * x + 0] = BlendOnBlack(row_in[4 * x + 0], alpha); + row_out[3 * x + 1] = BlendOnBlack(row_in[4 * x + 1], alpha); + row_out[3 * x + 2] = BlendOnBlack(row_in[4 * x + 2], alpha); + } + } + break; + } + default: + png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); + return false; + } + png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); + + CopyMemoryToLenVal(rgb.data(), rgb.size(), rgb_out); + + return true; +} + +extern "C" bool ReadJpegData(sapi::LenValStruct* in_data, + int mode, + int* xsize, int* ysize) +{ + std::string data(static_cast(in_data->data), in_data->size); + guetzli::JPEGData jpg; + + auto result = guetzli::ReadJpeg(data, + static_cast(mode), &jpg); + + if (result) { + *xsize = jpg.width; + *ysize = jpg.height; + } + + return result; +} + +extern "C" double ButteraugliScoreQuality(double quality) { + return guetzli::ButteraugliScoreForQuality(quality); +} + +extern "C" bool ReadDataFromFd(int fd, sapi::LenValStruct* out_data) { + struct stat file_data; + auto status = fstat(fd, &file_data); + + if (status < 0) { + return false; + } + + auto fsize = file_data.st_size; + + std::unique_ptr buf(new char[fsize]); + status = read(fd, buf.get(), fsize); + + if (status < 0) { + return false; + } + + CopyMemoryToLenVal(buf.get(), fsize, out_data); + + return true; +} + +extern "C" bool WriteDataToFd(int fd, sapi::LenValStruct* data) { + return sandbox2::file_util::fileops::WriteToFD(fd, + static_cast(data->data), data->size); +} \ No newline at end of file diff --git a/oss-internship-2020/guetzli/guetzli_entry_points.h b/oss-internship-2020/guetzli/guetzli_entry_points.h new file mode 100644 index 0000000..6fd0f11 --- /dev/null +++ b/oss-internship-2020/guetzli/guetzli_entry_points.h @@ -0,0 +1,29 @@ +#pragma once + +#include "guetzli/processor.h" +#include "sandboxed_api/lenval_core.h" +#include "sandboxed_api/vars.h" + +extern "C" bool ProcessJPEGString(const guetzli::Params* params, + int verbose, + sapi::LenValStruct* in_data, + sapi::LenValStruct* out_data); + +extern "C" bool ProcessRGBData(const guetzli::Params* params, + int verbose, + sapi::LenValStruct* rgb, + int w, int h, + sapi::LenValStruct* out_data); + +extern "C" bool ReadPng(sapi::LenValStruct* in_data, + int* xsize, int* ysize, + sapi::LenValStruct* rgb_out); + +extern "C" bool ReadJpegData(sapi::LenValStruct* in_data, + int mode, int* xsize, int* ysize); + +extern "C" double ButteraugliScoreQuality(double quality); + +extern "C" bool ReadDataFromFd(int fd, sapi::LenValStruct* out_data); + +extern "C" bool WriteDataToFd(int fd, sapi::LenValStruct* data); \ No newline at end of file diff --git a/oss-internship-2020/guetzli/guetzli_sandbox.h b/oss-internship-2020/guetzli/guetzli_sandbox.h new file mode 100644 index 0000000..3fa2b10 --- /dev/null +++ b/oss-internship-2020/guetzli/guetzli_sandbox.h @@ -0,0 +1,36 @@ +#pragma once + +#include +#include + +#include "guetzli_sapi.sapi.h" +#include "sandboxed_api/sandbox2/policy.h" +#include "sandboxed_api/sandbox2/policybuilder.h" +#include "sandboxed_api/util/flag.h" + +namespace guetzli { +namespace sandbox { + +class GuetzliSapiSandbox : public GuetzliSandbox { + public: + std::unique_ptr ModifyPolicy( + sandbox2::PolicyBuilder*) override { + + return sandbox2::PolicyBuilder() + .AllowStaticStartup() + .AllowRead() + .AllowSystemMalloc() + .AllowWrite() + .AllowExit() + .AllowStat() + .AllowSyscalls({ + __NR_futex, + __NR_close, + __NR_recvmsg // Seems like this one needed to work with remote file descriptors + }) + .BuildOrDie(); + } +}; + +} // namespace sandbox +} // namespace guetzli \ No newline at end of file diff --git a/oss-internship-2020/guetzli/guetzli_sandboxed.cc b/oss-internship-2020/guetzli/guetzli_sandboxed.cc new file mode 100644 index 0000000..a62f249 --- /dev/null +++ b/oss-internship-2020/guetzli/guetzli_sandboxed.cc @@ -0,0 +1,160 @@ +#include +#include +#include +#include +#include + +#include + +#include "guetzli_transaction.h" +#include "sandboxed_api/sandbox2/util/fileops.h" +#include "sandboxed_api/util/statusor.h" + +namespace { + +constexpr int kDefaultJPEGQuality = 95; +constexpr int kDefaultMemlimitMB = 6000; // in MB +//constexpr absl::string_view kMktempSuffix = "XXXXXX"; + +// sapi::StatusOr> CreateNamedTempFile( +// absl::string_view prefix) { +// std::string name_template = absl::StrCat(prefix, kMktempSuffix); +// int fd = mkstemp(&name_template[0]); +// if (fd < 0) { +// return absl::UnknownError("Error creating temp file"); +// } +// return std::pair{std::move(name_template), fd}; +// } + +void TerminateHandler() { + fprintf(stderr, "Unhandled exception. Most likely insufficient memory available.\n" + "Make sure that there is 300MB/MPix of memory available.\n"); + exit(1); +} + +void Usage() { + fprintf(stderr, + "Guetzli JPEG compressor. Usage: \n" + "guetzli [flags] input_filename output_filename\n" + "\n" + "Flags:\n" + " --verbose - Print a verbose trace of all attempts to standard output.\n" + " --quality Q - Visual quality to aim for, expressed as a JPEG quality value.\n" + " Default value is %d.\n" + " --memlimit M - Memory limit in MB. Guetzli will fail if unable to stay under\n" + " the limit. Default limit is %d MB.\n" + " --nomemlimit - Do not limit memory usage.\n", kDefaultJPEGQuality, kDefaultMemlimitMB); + exit(1); +} + +} // namespace + +int main(int argc, const char** argv) { + std::set_terminate(TerminateHandler); + + int verbose = 0; + int quality = kDefaultJPEGQuality; + int memlimit_mb = kDefaultMemlimitMB; + + int opt_idx = 1; + for(;opt_idx < argc;opt_idx++) { + if (strnlen(argv[opt_idx], 2) < 2 || argv[opt_idx][0] != '-' || argv[opt_idx][1] != '-') + break; + + if (!strcmp(argv[opt_idx], "--verbose")) { + verbose = 1; + } else if (!strcmp(argv[opt_idx], "--quality")) { + opt_idx++; + if (opt_idx >= argc) + Usage(); + quality = atoi(argv[opt_idx]); + } else if (!strcmp(argv[opt_idx], "--memlimit")) { + opt_idx++; + if (opt_idx >= argc) + Usage(); + memlimit_mb = atoi(argv[opt_idx]); + } else if (!strcmp(argv[opt_idx], "--nomemlimit")) { + memlimit_mb = -1; + } else if (!strcmp(argv[opt_idx], "--")) { + opt_idx++; + break; + } else { + fprintf(stderr, "Unknown commandline flag: %s\n", argv[opt_idx]); + Usage(); + } + } + + if (argc - opt_idx != 2) { + Usage(); + } + + sandbox2::file_util::fileops::FDCloser in_fd_closer( + open(argv[opt_idx], O_RDONLY)); + + if (in_fd_closer.get() < 0) { + fprintf(stderr, "Can't open input file: %s\n", argv[opt_idx]); + return 1; + } + + // auto out_temp_file = CreateNamedTempFile("/tmp/" + std::string(argv[opt_idx + 1])); + // if (!out_temp_file.ok()) { + // fprintf(stderr, "Can't create temporary output file: %s\n", + // argv[opt_idx + 1]); + // return 1; + // } + // sandbox2::file_util::fileops::FDCloser out_fd_closer( + // out_temp_file.value().second); + + // if (unlink(out_temp_file.value().first.c_str()) < 0) { + // fprintf(stderr, "Error unlinking temp out file: %s\n", + // out_temp_file.value().first.c_str()); + // return 1; + // } + + sandbox2::file_util::fileops::FDCloser out_fd_closer( + open(".", O_TMPFILE | O_RDWR, S_IRUSR | S_IWUSR)); + + if (out_fd_closer.get() < 0) { + fprintf(stderr, "Can't create temporary output file: %s\n", argv[opt_idx]); + return 1; + } + + // sandbox2::file_util::fileops::FDCloser out_fd_closer(open(argv[opt_idx + 1], + // O_RDWR | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP)); + + // if (out_fd_closer.get() < 0) { + // fprintf(stderr, "Can't open output file: %s\n", argv[opt_idx + 1]); + // return 1; + // } + + guetzli::sandbox::TransactionParams params = { + in_fd_closer.get(), + out_fd_closer.get(), + verbose, + quality, + memlimit_mb + }; + + guetzli::sandbox::GuetzliTransaction transaction(std::move(params)); + auto result = transaction.Run(); + + if (result.ok()) { + if (access(argv[opt_idx + 1], F_OK) != -1) { + if (remove(argv[opt_idx + 1]) < 0) { + fprintf(stderr, "Error deleting existing output file: %s\n", + argv[opt_idx + 1]); + } + } + + std::stringstream path; + path << "/proc/self/fd/" << out_fd_closer.get(); + linkat(AT_FDCWD, path.str().c_str(), AT_FDCWD, argv[opt_idx + 1], + AT_SYMLINK_FOLLOW); + } + else { + fprintf(stderr, "%s\n", result.ToString().c_str()); // Use cerr instead ? + return 1; + } + + return 0; +} \ No newline at end of file diff --git a/oss-internship-2020/guetzli/guetzli_transaction.cc b/oss-internship-2020/guetzli/guetzli_transaction.cc new file mode 100644 index 0000000..9403783 --- /dev/null +++ b/oss-internship-2020/guetzli/guetzli_transaction.cc @@ -0,0 +1,160 @@ +#include "guetzli_transaction.h" + +#include + +namespace guetzli { +namespace sandbox { + +absl::Status GuetzliTransaction::Init() { + SAPI_RETURN_IF_ERROR(sandbox()->TransferToSandboxee(&in_fd_)); + SAPI_RETURN_IF_ERROR(sandbox()->TransferToSandboxee(&out_fd_)); + + if (in_fd_.GetRemoteFd() < 0) { + return absl::FailedPreconditionError( + "Error receiving remote FD: remote input fd is set to -1"); + } + if (out_fd_.GetRemoteFd() < 0) { + return absl::FailedPreconditionError( + "Error receiving remote FD: remote output fd is set to -1"); + } + + return absl::OkStatus(); +} + + absl::Status GuetzliTransaction::ProcessPng(GuetzliAPi* api, + sapi::v::Struct* params, + sapi::v::LenVal* input, + sapi::v::LenVal* output) const { + sapi::v::Int xsize; + sapi::v::Int ysize; + sapi::v::LenVal rgb_in(0); + + auto read_result = api->ReadPng(input->PtrBefore(), xsize.PtrBoth(), + ysize.PtrBoth(), rgb_in.PtrBoth()); + + if (!read_result.value_or(false)) { + return absl::FailedPreconditionError( + "Error reading PNG data from input file" + ); + } + + double pixels = static_cast(xsize.GetValue()) * ysize.GetValue(); + if (params_.memlimit_mb != -1 + && (pixels * kBytesPerPixel / (1 << 20) > params_.memlimit_mb + || params_.memlimit_mb < kLowestMemusageMB)) { + return absl::FailedPreconditionError( + "Memory limit would be exceeded" + ); + } + + auto result = api->ProcessRGBData(params->PtrBefore(), params_.verbose, + rgb_in.PtrBefore(), xsize.GetValue(), + ysize.GetValue(), output->PtrBoth()); + if (!result.value_or(false)) { + return absl::FailedPreconditionError( + "Guetzli processing failed" + ); + } + + return absl::OkStatus(); + } + + absl::Status GuetzliTransaction::ProcessJpeg(GuetzliApi* api, + sapi::v::Struct* params, + sapi::v::LenVal* input, + sapi::v::LenVal* output) const { + ::sapi::v::Int xsize; + ::sapi::v::Int ysize; + auto read_result = api->ReadJpegData(input->PtrBefore(), 0, xsize.PtrBoth(), + ysize.PtrBoth()); + + if (!read_result.value_or(false)) { + return absl::FailedPreconditionError( + "Error reading JPG data from input file" + ); + } + + double pixels = static_cast(xsize.GetValue()) * ysize.GetValue(); + if (params_.memlimit_mb != -1 + && (pixels * kBytesPerPixel / (1 << 20) > params_.memlimit_mb + || params_.memlimit_mb < kLowestMemusageMB)) { + return absl::FailedPreconditionError( + "Memory limit would be exceeded" + ); + } + + auto result = api->ProcessJPEGString(params->PtrBefore(), params_.verbose, + input->PtrBefore(), output->PtrBoth()); + + if (!result.value_or(false)) { + return absl::FailedPreconditionError( + "Guetzli processing failed" + ); + } + + return absl::OkStatus(); + } + +absl::Status GuetzliTransaction::Main() { + GuetzliApi api(sandbox()); + + sapi::v::LenVal input(0); + sapi::v::LenVal output(0); + sapi::v::Struct params; + + auto read_result = api.ReadDataFromFd(in_fd_.GetRemoteFd(), input.PtrBoth()); + + if (!read_result.value_or(false)) { + return absl::FailedPreconditionError( + "Error reading data inside sandbox" + ); + } + + auto score_quality_result = api.ButteraugliScoreQuality(params_.quality); + + if (!score_quality_result.ok()) { + return absl::FailedPreconditionError( + "Error calculating butteraugli score" + ); + } + + params.mutable_data()->butteraugli_target = score_quality_result.value(); + + static const unsigned char kPNGMagicBytes[] = { + 0x89, 'P', 'N', 'G', '\r', '\n', 0x1a, '\n', + }; + + if (input.GetDataSize() >= 8 && + memcmp(input.GetData(), kPNGMagicBytes, sizeof(kPNGMagicBytes)) == 0) { + auto process_status = ProcessPng(&api, ¶ms, &input, &output); + + if (!process_status.ok()) { + return process_status; + } + } else { + auto process_status = ProcessJpeg(&api, ¶ms, &input, &output); + + if (!process_status.ok()) { + return process_status; + } + } + + auto write_result = api.WriteDataToFd(out_fd_.GetRemoteFd(), + output.PtrBefore()); + + if (!write_result.value_or(false)) { + return absl::FailedPreconditionError( + "Error writing file inside sandbox" + ); + } + + return absl::OkStatus(); +} + +time_t GuetzliTransaction::CalculateTimeLimitFromImageSize( + uint64_t pixels) const { + return (pixels / kMpixPixels + 5) * 60; +} + +} // namespace sandbox +} // namespace guetzli \ No newline at end of file diff --git a/oss-internship-2020/guetzli/guetzli_transaction.h b/oss-internship-2020/guetzli/guetzli_transaction.h new file mode 100644 index 0000000..5d6e83a --- /dev/null +++ b/oss-internship-2020/guetzli/guetzli_transaction.h @@ -0,0 +1,216 @@ +#pragma once + +#include +#include + +#include "guetzli_sandbox.h" +#include "sandboxed_api/transaction.h" +#include "sandboxed_api/vars.h" + +namespace guetzli { +namespace sandbox { + +constexpr int kDefaultTransactionRetryCount = 1; +constexpr uint64_t kMpixPixels = 1'000'000; + +constexpr int kBytesPerPixel = 350; +constexpr int kLowestMemusageMB = 100; // in MB + +struct TransactionParams { + int in_fd; + int out_fd; + int verbose; + int quality; + int memlimit_mb; +}; + +//Add optional time limit/retry count as a constructors arguments +//Use differenet status errors +class GuetzliTransaction : public sapi::Transaction { + public: + GuetzliTransaction(TransactionParams&& params) + : sapi::Transaction(std::make_unique()) + , params_(std::move(params)) + , in_fd_(params_.in_fd) + , out_fd_(params_.out_fd) + { + sapi::Transaction::set_retry_count(kDefaultTransactionRetryCount); + sapi::Transaction::SetTimeLimit(0); + } + + private: + absl::Status Init() override; + absl::Status Main() final; + + absl::Status ProcessPng(GuetzliApi* api, + sapi::v::Struct* params, + sapi::v::LenVal* input, + sapi::v::LenVal* output) const; + + absl::Status ProcessJpeg(GuetzliApi* api, + sapi::v::Struct* params, + sapi::v::LenVal* input, + sapi::v::LenVal* output) const; + + // As guetzli takes roughly 1 minute of CPU per 1 MPix we need to calculate + // approximate time for transaction to complete + time_t CalculateTimeLimitFromImageSize(uint64_t pixels) const; + + const TransactionParams params_; + sapi::v::Fd in_fd_; + sapi::v::Fd out_fd_; +}; + +absl::Status GuetzliTransaction::Init() { + SAPI_RETURN_IF_ERROR(sandbox()->TransferToSandboxee(&in_fd_)); + SAPI_RETURN_IF_ERROR(sandbox()->TransferToSandboxee(&out_fd_)); + + if (in_fd_.GetRemoteFd() < 0) { + return absl::FailedPreconditionError( + "Error receiving remote FD: remote input fd is set to -1"); + } + if (out_fd_.GetRemoteFd() < 0) { + return absl::FailedPreconditionError( + "Error receiving remote FD: remote output fd is set to -1"); + } + + return absl::OkStatus(); +} + + absl::Status GuetzliTransaction::ProcessPng(GuetzliApi* api, + sapi::v::Struct* params, + sapi::v::LenVal* input, + sapi::v::LenVal* output) const { + sapi::v::Int xsize; + sapi::v::Int ysize; + sapi::v::LenVal rgb_in(0); + + auto read_result = api->ReadPng(input->PtrBefore(), xsize.PtrBoth(), + ysize.PtrBoth(), rgb_in.PtrBoth()); + + if (!read_result.value_or(false)) { + return absl::FailedPreconditionError( + "Error reading PNG data from input file" + ); + } + + double pixels = static_cast(xsize.GetValue()) * ysize.GetValue(); + if (params_.memlimit_mb != -1 + && (pixels * kBytesPerPixel / (1 << 20) > params_.memlimit_mb + || params_.memlimit_mb < kLowestMemusageMB)) { + return absl::FailedPreconditionError( + "Memory limit would be exceeded" + ); + } + + auto result = api->ProcessRGBData(params->PtrBefore(), params_.verbose, + rgb_in.PtrBefore(), xsize.GetValue(), + ysize.GetValue(), output->PtrBoth()); + if (!result.value_or(false)) { + return absl::FailedPreconditionError( + "Guetzli processing failed" + ); + } + + return absl::OkStatus(); + } + + absl::Status GuetzliTransaction::ProcessJpeg(GuetzliApi* api, + sapi::v::Struct* params, + sapi::v::LenVal* input, + sapi::v::LenVal* output) const { + sapi::v::Int xsize; + sapi::v::Int ysize; + auto read_result = api->ReadJpegData(input->PtrBefore(), 0, xsize.PtrBoth(), + ysize.PtrBoth()); + + if (!read_result.value_or(false)) { + return absl::FailedPreconditionError( + "Error reading JPG data from input file" + ); + } + + double pixels = static_cast(xsize.GetValue()) * ysize.GetValue(); + if (params_.memlimit_mb != -1 + && (pixels * kBytesPerPixel / (1 << 20) > params_.memlimit_mb + || params_.memlimit_mb < kLowestMemusageMB)) { + return absl::FailedPreconditionError( + "Memory limit would be exceeded" + ); + } + + auto result = api->ProcessJPEGString(params->PtrBefore(), params_.verbose, + input->PtrBefore(), output->PtrBoth()); + + if (!result.value_or(false)) { + return absl::FailedPreconditionError( + "Guetzli processing failed" + ); + } + + return absl::OkStatus(); + } + +absl::Status GuetzliTransaction::Main() { + GuetzliApi api(sandbox()); + + sapi::v::LenVal input(0); + sapi::v::LenVal output(0); + sapi::v::Struct params; + + auto read_result = api.ReadDataFromFd(in_fd_.GetRemoteFd(), input.PtrBoth()); + + if (!read_result.value_or(false)) { + return absl::FailedPreconditionError( + "Error reading data inside sandbox" + ); + } + + auto score_quality_result = api.ButteraugliScoreQuality(params_.quality); + + if (!score_quality_result.ok()) { + return absl::FailedPreconditionError( + "Error calculating butteraugli score" + ); + } + + params.mutable_data()->butteraugli_target = score_quality_result.value(); + + static const unsigned char kPNGMagicBytes[] = { + 0x89, 'P', 'N', 'G', '\r', '\n', 0x1a, '\n', + }; + + if (input.GetDataSize() >= 8 && + memcmp(input.GetData(), kPNGMagicBytes, sizeof(kPNGMagicBytes)) == 0) { + auto process_status = ProcessPng(&api, ¶ms, &input, &output); + + if (!process_status.ok()) { + return process_status; + } + } else { + auto process_status = ProcessJpeg(&api, ¶ms, &input, &output); + + if (!process_status.ok()) { + return process_status; + } + } + + auto write_result = api.WriteDataToFd(out_fd_.GetRemoteFd(), + output.PtrBefore()); + + if (!write_result.value_or(false)) { + return absl::FailedPreconditionError( + "Error writing file inside sandbox" + ); + } + + return absl::OkStatus(); +} + +time_t GuetzliTransaction::CalculateTimeLimitFromImageSize( + uint64_t pixels) const { + return (pixels / kMpixPixels + 5) * 60; +} + +} // namespace sandbox +} // namespace guetzli diff --git a/oss-internship-2020/guetzli/tests/BUILD.bazel b/oss-internship-2020/guetzli/tests/BUILD.bazel new file mode 100644 index 0000000..b7c7517 --- /dev/null +++ b/oss-internship-2020/guetzli/tests/BUILD.bazel @@ -0,0 +1,22 @@ +# cc_test( +# name = "transaction_tests", +# srcs = ["guetzli_transaction_test.cc"], +# visibility=["//visibility:public"], +# includes = ["."], +# deps = [ +# "//:guetzli_sapi", +# "@googletest//:gtest_main" +# ], +# ) + +# cc_test( +# name = "sapi_lib_tests", +# srcs = ["guetzli_sapi_test.cc"], +# visibility=["//visibility:public"], +# includes=[".."], +# deps = [ +# "//:guetzli_sapi", +# "@googletest//:gtest_main" +# ], +# data = glob(["testdata/*"]) +# ) \ No newline at end of file diff --git a/oss-internship-2020/guetzli/tests/guetzli_sapi_test.cc b/oss-internship-2020/guetzli/tests/guetzli_sapi_test.cc new file mode 100644 index 0000000..73e54d6 --- /dev/null +++ b/oss-internship-2020/guetzli/tests/guetzli_sapi_test.cc @@ -0,0 +1,165 @@ +#include "gtest/gtest.h" +#include "guetzli_sandbox.h" +#include "sandboxed_api/sandbox2/util/fileops.h" +#include "sandboxed_api/vars.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace guetzli { +namespace sandbox { +namespace tests { + +namespace { + +constexpr const char* IN_PNG_FILENAME = "bees.png"; +constexpr const char* IN_JPG_FILENAME = "landscape.jpg"; + +constexpr int IN_PNG_FILE_SIZE = 177'424; +constexpr int IN_JPG_FILE_SIZE = 14'418; + +constexpr int DEFAULT_QUALITY_TARGET = 95; + +constexpr const char* RELATIVE_PATH_TO_TESTDATA = + "/guetzli/guetzli-sandboxed/tests/testdata/"; + +std::string GetPathToInputFile(const char* filename) { + return std::string(getenv("TEST_SRCDIR")) + + std::string(RELATIVE_PATH_TO_TESTDATA) + + std::string(filename); +} + +std::string ReadFromFile(const std::string& filename) { + std::ifstream stream(filename, std::ios::binary); + + if (!stream.is_open()) { + return ""; + } + + std::stringstream result; + result << stream.rdbuf(); + return result.str(); +} + +} // namespace + +class GuetzliSapiTest : public ::testing::Test { +protected: + void SetUp() override { + sandbox_ = std::make_unique(); + sandbox_->Init().IgnoreError(); + api_ = std::make_unique(sandbox_.get()); + } + + std::unique_ptr sandbox_; + std::unique_ptr api_; +}; + +TEST_F(GuetzliSapiTest, ReadDataFromFd) { + std::string input_file_path = GetPathToInputFile(IN_PNG_FILENAME); + int fd = open(input_file_path.c_str(), O_RDONLY); + ASSERT_TRUE(fd != -1) << "Error opening input file"; + sapi::v::Fd remote_fd(fd); + auto send_fd_status = sandbox_->TransferToSandboxee(&remote_fd); + ASSERT_TRUE(send_fd_status.ok()) << "Error sending fd to sandboxee"; + ASSERT_TRUE(remote_fd.GetRemoteFd() != -1) << "Error opening remote fd"; + sapi::v::LenVal data(0); + auto read_status = + api_->ReadDataFromFd(remote_fd.GetRemoteFd(), data.PtrBoth()); + ASSERT_TRUE(read_status.value_or(false)) << "Error reading data from fd"; + ASSERT_EQ(data.GetDataSize(), IN_PNG_FILE_SIZE) << "Wrong size of file"; +} + +// TEST_F(GuetzliSapiTest, WriteDataToFd) { + +// } + +TEST_F(GuetzliSapiTest, ReadPng) { + std::string data = ReadFromFile(GetPathToInputFile(IN_PNG_FILENAME)); + ASSERT_EQ(data.size(), IN_PNG_FILE_SIZE) << "Error reading input file"; + sapi::v::LenVal in_data(data.data(), data.size()); + sapi::v::Int xsize, ysize; + sapi::v::LenVal rgb_out(0); + + auto status = api_->ReadPng(in_data.PtrBefore(), xsize.PtrBoth(), + ysize.PtrBoth(), rgb_out.PtrBoth()); + ASSERT_TRUE(status.value_or(false)) << "Error processing png data"; + ASSERT_EQ(xsize.GetValue(), 444) << "Error parsing width"; + ASSERT_EQ(ysize.GetValue(), 258) << "Error parsing height"; +} + +TEST_F(GuetzliSapiTest, ReadJpeg) { + std::string data = ReadFromFile(GetPathToInputFile(IN_JPG_FILENAME)); + ASSERT_EQ(data.size(), IN_JPG_FILE_SIZE) << "Error reading input file"; + sapi::v::LenVal in_data(data.data(), data.size()); + sapi::v::Int xsize, ysize; + + auto status = api_->ReadJpegData(in_data.PtrBefore(), 0, + xsize.PtrBoth(), ysize.PtrBoth()); + ASSERT_TRUE(status.value_or(false)) << "Error processing jpeg data"; + ASSERT_EQ(xsize.GetValue(), 180) << "Error parsing width"; + ASSERT_EQ(ysize.GetValue(), 180) << "Error parsing height"; +} + +// This test can take up to few minutes depending on your hardware +TEST_F(GuetzliSapiTest, ProcessRGB) { + std::string data = ReadFromFile(GetPathToInputFile(IN_PNG_FILENAME)); + ASSERT_EQ(data.size(), IN_PNG_FILE_SIZE) << "Error reading input file"; + sapi::v::LenVal in_data(data.data(), data.size()); + sapi::v::Int xsize, ysize; + sapi::v::LenVal rgb_out(0); + + auto status = api_->ReadPng(in_data.PtrBefore(), xsize.PtrBoth(), + ysize.PtrBoth(), rgb_out.PtrBoth()); + ASSERT_TRUE(status.value_or(false)) << "Error processing png data"; + ASSERT_EQ(xsize.GetValue(), 444) << "Error parsing width"; + ASSERT_EQ(ysize.GetValue(), 258) << "Error parsing height"; + auto quality = + api_->ButteraugliScoreQuality(static_cast(DEFAULT_QUALITY_TARGET)); + ASSERT_TRUE(quality.ok()) << "Error calculating butteraugli quality"; + sapi::v::Struct params; + sapi::v::LenVal out_data(0); + params.mutable_data()->butteraugli_target = quality.value(); + + status = api_->ProcessRGBData(params.PtrBefore(), 0, rgb_out.PtrBefore(), + xsize.GetValue(), ysize.GetValue(), out_data.PtrBoth()); + ASSERT_TRUE(status.value_or(false)) << "Error processing png file"; + ASSERT_EQ(out_data.GetDataSize(), 38'625); + //ADD COMPARSION WITH REFERENCE OUTPUT +} + +// This test can take up to few minutes depending on your hardware +TEST_F(GuetzliSapiTest, ProcessJpeg) { + std::string data = ReadFromFile(GetPathToInputFile(IN_JPG_FILENAME)); + ASSERT_EQ(data.size(), IN_JPG_FILE_SIZE) << "Error reading input file"; + sapi::v::LenVal in_data(data.data(), data.size()); + sapi::v::Int xsize, ysize; + + auto status = api_->ReadJpegData(in_data.PtrBefore(), 0, + xsize.PtrBoth(), ysize.PtrBoth()); + ASSERT_TRUE(status.value_or(false)) << "Error processing jpeg data"; + ASSERT_EQ(xsize.GetValue(), 180) << "Error parsing width"; + ASSERT_EQ(ysize.GetValue(), 180) << "Error parsing height"; + + auto quality = + api_->ButteraugliScoreQuality(static_cast(DEFAULT_QUALITY_TARGET)); + ASSERT_TRUE(quality.ok()) << "Error calculating butteraugli quality"; + sapi::v::Struct params; + params.mutable_data()->butteraugli_target = quality.value(); + sapi::v::LenVal out_data(0); + + status = api_->ProcessJPEGString(params.PtrBefore(), 0, in_data.PtrBefore(), + out_data.PtrBoth()); + ASSERT_TRUE(status.value_or(false)) << "Error processing jpeg file"; + ASSERT_EQ(out_data.GetDataSize(), 10'816); + //ADD COMPARSION WITH REFERENCE OUTPUT +} + +} // namespace tests +} // namespace sandbox +} // namespace guetzli \ No newline at end of file diff --git a/oss-internship-2020/guetzli/tests/guetzli_transaction_test.cc b/oss-internship-2020/guetzli/tests/guetzli_transaction_test.cc new file mode 100644 index 0000000..8471d1e --- /dev/null +++ b/oss-internship-2020/guetzli/tests/guetzli_transaction_test.cc @@ -0,0 +1,2 @@ +#include "gtest/gtest.h" +#include "guetzli_transaction.h" diff --git a/oss-internship-2020/guetzli/tests/testdata/bees.png b/oss-internship-2020/guetzli/tests/testdata/bees.png new file mode 100644 index 0000000000000000000000000000000000000000..11640c7488fb114127f869572d7ffec132e77746 GIT binary patch literal 177424 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%0D2&3f5zx%5uXh+B#w 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

slNF9=bRxAin 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�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 literal 0 HcmV?d00001 From 258dbcd622c21253e336d7669c026b06b38e9185 Mon Sep 17 00:00:00 2001 From: Bohdan Tyshchenko Date: Tue, 11 Aug 2020 15:44:13 -0700 Subject: [PATCH 04/82] Init guetzli sandbox --- oss-internship-2020/guetzli/BUILD.bazel | 65 +++++ oss-internship-2020/guetzli/WORKSPACE | 79 ++++++ .../guetzli/external/png.BUILD | 33 +++ .../guetzli/external/zlib.BUILD | 36 +++ .../guetzli/guetzli_entry_points.cc | 245 ++++++++++++++++++ .../guetzli/guetzli_entry_points.h | 29 +++ oss-internship-2020/guetzli/guetzli_sandbox.h | 36 +++ .../guetzli/guetzli_sandboxed.cc | 160 ++++++++++++ .../guetzli/guetzli_transaction.cc | 160 ++++++++++++ .../guetzli/guetzli_transaction.h | 216 +++++++++++++++ oss-internship-2020/guetzli/tests/BUILD.bazel | 22 ++ .../guetzli/tests/guetzli_sapi_test.cc | 165 ++++++++++++ .../guetzli/tests/guetzli_transaction_test.cc | 2 + .../guetzli/tests/testdata/bees.png | Bin 0 -> 177424 bytes .../guetzli/tests/testdata/landscape.jpg | Bin 0 -> 14418 bytes 15 files changed, 1248 insertions(+) create mode 100644 oss-internship-2020/guetzli/BUILD.bazel create mode 100644 oss-internship-2020/guetzli/WORKSPACE create mode 100644 oss-internship-2020/guetzli/external/png.BUILD create mode 100644 oss-internship-2020/guetzli/external/zlib.BUILD create mode 100644 oss-internship-2020/guetzli/guetzli_entry_points.cc create mode 100644 oss-internship-2020/guetzli/guetzli_entry_points.h create mode 100644 oss-internship-2020/guetzli/guetzli_sandbox.h create mode 100644 oss-internship-2020/guetzli/guetzli_sandboxed.cc create mode 100644 oss-internship-2020/guetzli/guetzli_transaction.cc create mode 100644 oss-internship-2020/guetzli/guetzli_transaction.h create mode 100644 oss-internship-2020/guetzli/tests/BUILD.bazel create mode 100644 oss-internship-2020/guetzli/tests/guetzli_sapi_test.cc create mode 100644 oss-internship-2020/guetzli/tests/guetzli_transaction_test.cc create mode 100644 oss-internship-2020/guetzli/tests/testdata/bees.png create mode 100644 oss-internship-2020/guetzli/tests/testdata/landscape.jpg diff --git a/oss-internship-2020/guetzli/BUILD.bazel b/oss-internship-2020/guetzli/BUILD.bazel new file mode 100644 index 0000000..345f879 --- /dev/null +++ b/oss-internship-2020/guetzli/BUILD.bazel @@ -0,0 +1,65 @@ +load("@rules_cc//cc:defs.bzl", "cc_proto_library") +load("@rules_proto//proto:defs.bzl", "proto_library") +load( + "@com_google_sandboxed_api//sandboxed_api/bazel:proto.bzl", + "sapi_proto_library", +) +load( + "@com_google_sandboxed_api//sandboxed_api/bazel:sapi.bzl", + "sapi_library", +) + +cc_library( + name = "guetzli_wrapper", + srcs = ["guetzli_entry_points.cc"], + hdrs = ["guetzli_entry_points.h"], + deps = [ + "@guetzli//:guetzli_lib", + "@com_google_sandboxed_api//sandboxed_api:lenval_core", + "@com_google_sandboxed_api//sandboxed_api:vars", + #"@com_google_sandboxed_api//sandboxed_api/sandbox2/util:temp_file", visibility error + "@png_archive//:png" + ], + visibility = ["//visibility:public"] +) + +sapi_library( + name = "guetzli_sapi", + #srcs = ["guetzli_transaction.cc"], // Error when try to place definitions insde .cc file + hdrs = ["guetzli_sandbox.h", "guetzli_transaction.h"], + functions = [ + "ProcessJPEGString", + "ProcessRGBData", + "ButteraugliScoreQuality", + "ReadPng", + "ReadJpegData", + "ReadDataFromFd", + "WriteDataToFd" + ], + input_files = ["guetzli_entry_points.h"], + lib = ":guetzli_wrapper", + lib_name = "Guetzli", + visibility = ["//visibility:public"], + namespace = "guetzli::sandbox" +) + +# cc_library( +# name = "guetzli_sapi_transaction", +# #srcs = ["guetzli_transaction.cc"], +# hdrs = ["guetzli_transaction.h"], +# deps = [ +# ":guetzli_sapi" +# ], +# visibility = ["//visibility:public"] +# ) + +cc_binary( + name="guetzli_sandboxed", + srcs=["guetzli_sandboxed.cc"], + includes = ["."], + visibility= [ "//visibility:public" ], + deps = [ + #":guetzli_sapi_transaction" + ":guetzli_sapi" + ] +) \ No newline at end of file diff --git a/oss-internship-2020/guetzli/WORKSPACE b/oss-internship-2020/guetzli/WORKSPACE new file mode 100644 index 0000000..17887de --- /dev/null +++ b/oss-internship-2020/guetzli/WORKSPACE @@ -0,0 +1,79 @@ +# Copyright 2020 Google LLC +# +# 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. + +workspace(name = "guetzli_sandboxed") + +load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository") +load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe") + +# Include the Sandboxed API dependency if it does not already exist in this +# project. This ensures that this workspace plays well with other external +# dependencies that might use Sandboxed API. +maybe( + git_repository, + name = "com_google_sandboxed_api", + # This example depends on the latest master. In an embedding project, it + # is advisable to pin Sandboxed API to a specific revision instead. + # commit = "ba47adc21d4c9bc316f3c7c32b0faaef952c111e", # 2020-05-15 + branch = "master", + remote = "https://github.com/google/sandboxed-api.git", +) + +# From here on, Sandboxed API files are available. The statements below setup +# transitive dependencies such as Abseil. Like above, those will only be +# included if they don't already exist in the project. +load( + "@com_google_sandboxed_api//sandboxed_api/bazel:sapi_deps.bzl", + "sapi_deps", +) + +sapi_deps() + +# Need to separately setup Protobuf dependencies in order for the build rules +# to work. +load("@com_google_protobuf//:protobuf_deps.bzl", "protobuf_deps") + +protobuf_deps() + +maybe( + git_repository, + name = "guetzli", + remote = "https://github.com/google/guetzli.git", + branch = "master" +) + +maybe( + git_repository, + name = "googletest", + remote = "https://github.com/google/googletest", + tag = "release-1.10.0" +) + +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") + +http_archive( + name = "png_archive", + build_file = "png.BUILD", + sha256 = "a941dc09ca00148fe7aaf4ecdd6a67579c293678ed1e1cf633b5ffc02f4f8cf7", + strip_prefix = "libpng-1.2.57", + url = "http://github.com/glennrp/libpng/archive/v1.2.57.zip", +) + +http_archive( + name = "zlib_archive", + build_file = "zlib.BUILD", + sha256 = "8d7e9f698ce48787b6e1c67e6bff79e487303e66077e25cb9784ac8835978017", + strip_prefix = "zlib-1.2.10", + url = "http://zlib.net/fossils/zlib-1.2.10.tar.gz", +) \ No newline at end of file diff --git a/oss-internship-2020/guetzli/external/png.BUILD b/oss-internship-2020/guetzli/external/png.BUILD new file mode 100644 index 0000000..9ff982b --- /dev/null +++ b/oss-internship-2020/guetzli/external/png.BUILD @@ -0,0 +1,33 @@ +# Description: +# libpng is the official PNG reference library. + +licenses(["notice"]) # BSD/MIT-like license + +cc_library( + name = "png", + srcs = [ + "png.c", + "pngerror.c", + "pngget.c", + "pngmem.c", + "pngpread.c", + "pngread.c", + "pngrio.c", + "pngrtran.c", + "pngrutil.c", + "pngset.c", + "pngtrans.c", + "pngwio.c", + "pngwrite.c", + "pngwtran.c", + "pngwutil.c", + ], + hdrs = [ + "png.h", + "pngconf.h", + ], + includes = ["."], + linkopts = ["-lm"], + visibility = ["//visibility:public"], + deps = ["@zlib_archive//:zlib"], +) diff --git a/oss-internship-2020/guetzli/external/zlib.BUILD b/oss-internship-2020/guetzli/external/zlib.BUILD new file mode 100644 index 0000000..edb77fd --- /dev/null +++ b/oss-internship-2020/guetzli/external/zlib.BUILD @@ -0,0 +1,36 @@ +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) # BSD/MIT-like license (for zlib) + +cc_library( + name = "zlib", + srcs = [ + "adler32.c", + "compress.c", + "crc32.c", + "crc32.h", + "deflate.c", + "deflate.h", + "gzclose.c", + "gzguts.h", + "gzlib.c", + "gzread.c", + "gzwrite.c", + "infback.c", + "inffast.c", + "inffast.h", + "inffixed.h", + "inflate.c", + "inflate.h", + "inftrees.c", + "inftrees.h", + "trees.c", + "trees.h", + "uncompr.c", + "zconf.h", + "zutil.c", + "zutil.h", + ], + hdrs = ["zlib.h"], + includes = ["."], +) diff --git a/oss-internship-2020/guetzli/guetzli_entry_points.cc b/oss-internship-2020/guetzli/guetzli_entry_points.cc new file mode 100644 index 0000000..446150c --- /dev/null +++ b/oss-internship-2020/guetzli/guetzli_entry_points.cc @@ -0,0 +1,245 @@ +#include "guetzli/jpeg_data_reader.h" +#include "guetzli/quality.h" +#include "guetzli_entry_points.h" +#include "png.h" +#include "sandboxed_api/sandbox2/util/fileops.h" + +#include +#include +#include +#include +#include + +namespace { + +inline uint8_t BlendOnBlack(const uint8_t val, const uint8_t alpha) { + return (static_cast(val) * static_cast(alpha) + 128) / 255; +} + +template +void CopyMemoryToLenVal(const T* data, size_t size, + sapi::LenValStruct* out_data) { + free(out_data->data); // Not sure about this + out_data->size = size; + T* new_out = static_cast(malloc(size)); + memcpy(new_out, data, size); + out_data->data = new_out; +} + +} // namespace + +extern "C" bool ProcessJPEGString(const guetzli::Params* params, + int verbose, + sapi::LenValStruct* in_data, + sapi::LenValStruct* out_data) +{ + std::string in_data_temp(static_cast(in_data->data), + in_data->size); + + guetzli::ProcessStats stats; + if (verbose > 0) { + stats.debug_output_file = stderr; + } + + std::string temp_out = ""; + auto result = guetzli::Process(*params, &stats, in_data_temp, &temp_out); + + if (result) { + CopyMemoryToLenVal(temp_out.data(), temp_out.size(), out_data); + } + + return result; +} + +extern "C" bool ProcessRGBData(const guetzli::Params* params, + int verbose, + sapi::LenValStruct* rgb, + int w, int h, + sapi::LenValStruct* out_data) +{ + std::vector in_data_temp; + in_data_temp.reserve(rgb->size); + + auto* rgb_data = static_cast(rgb->data); + std::copy(rgb_data, rgb_data + rgb->size, std::back_inserter(in_data_temp)); + + guetzli::ProcessStats stats; + if (verbose > 0) { + stats.debug_output_file = stderr; + } + + std::string temp_out = ""; + auto result = + guetzli::Process(*params, &stats, in_data_temp, w, h, &temp_out); + + //TODO: Move shared part of the code to another function + if (result) { + CopyMemoryToLenVal(temp_out.data(), temp_out.size(), out_data); + } + + return result; +} + +extern "C" bool ReadPng(sapi::LenValStruct* in_data, + int* xsize, int* ysize, + sapi::LenValStruct* rgb_out) +{ + std::string data(static_cast(in_data->data), in_data->size); + std::vector rgb; + + png_structp png_ptr = + png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); + if (!png_ptr) { + return false; + } + + png_infop info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) { + png_destroy_read_struct(&png_ptr, nullptr, nullptr); + return false; + } + + if (setjmp(png_jmpbuf(png_ptr)) != 0) { + // Ok we are here because of the setjmp. + png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); + return false; + } + + std::istringstream memstream(data, std::ios::in | std::ios::binary); + png_set_read_fn(png_ptr, static_cast(&memstream), [](png_structp png_ptr, png_bytep outBytes, png_size_t byteCountToRead) { + std::istringstream& memstream = *static_cast(png_get_io_ptr(png_ptr)); + + memstream.read(reinterpret_cast(outBytes), byteCountToRead); + + if (memstream.eof()) png_error(png_ptr, "unexpected end of data"); + if (memstream.fail()) png_error(png_ptr, "read from memory error"); + }); + + // The png_transforms flags are as follows: + // packing == convert 1,2,4 bit images, + // strip == 16 -> 8 bits / channel, + // shift == use sBIT dynamics, and + // expand == palettes -> rgb, grayscale -> 8 bit images, tRNS -> alpha. + const unsigned int png_transforms = + PNG_TRANSFORM_PACKING | PNG_TRANSFORM_EXPAND | PNG_TRANSFORM_STRIP_16; + + png_read_png(png_ptr, info_ptr, png_transforms, nullptr); + + png_bytep* row_pointers = png_get_rows(png_ptr, info_ptr); + + *xsize = png_get_image_width(png_ptr, info_ptr); + *ysize = png_get_image_height(png_ptr, info_ptr); + rgb.resize(3 * (*xsize) * (*ysize)); + + const int components = png_get_channels(png_ptr, info_ptr); + switch (components) { + case 1: { + // GRAYSCALE + for (int y = 0; y < *ysize; ++y) { + const uint8_t* row_in = row_pointers[y]; + uint8_t* row_out = &(rgb)[3 * y * (*xsize)]; + for (int x = 0; x < *xsize; ++x) { + const uint8_t gray = row_in[x]; + row_out[3 * x + 0] = gray; + row_out[3 * x + 1] = gray; + row_out[3 * x + 2] = gray; + } + } + break; + } + case 2: { + // GRAYSCALE + ALPHA + for (int y = 0; y < *ysize; ++y) { + const uint8_t* row_in = row_pointers[y]; + uint8_t* row_out = &(rgb)[3 * y * (*xsize)]; + for (int x = 0; x < *xsize; ++x) { + const uint8_t gray = BlendOnBlack(row_in[2 * x], row_in[2 * x + 1]); + row_out[3 * x + 0] = gray; + row_out[3 * x + 1] = gray; + row_out[3 * x + 2] = gray; + } + } + break; + } + case 3: { + // RGB + for (int y = 0; y < *ysize; ++y) { + const uint8_t* row_in = row_pointers[y]; + uint8_t* row_out = &(rgb)[3 * y * (*xsize)]; + memcpy(row_out, row_in, 3 * (*xsize)); + } + break; + } + case 4: { + // RGBA + for (int y = 0; y < *ysize; ++y) { + const uint8_t* row_in = row_pointers[y]; + uint8_t* row_out = &(rgb)[3 * y * (*xsize)]; + for (int x = 0; x < *xsize; ++x) { + const uint8_t alpha = row_in[4 * x + 3]; + row_out[3 * x + 0] = BlendOnBlack(row_in[4 * x + 0], alpha); + row_out[3 * x + 1] = BlendOnBlack(row_in[4 * x + 1], alpha); + row_out[3 * x + 2] = BlendOnBlack(row_in[4 * x + 2], alpha); + } + } + break; + } + default: + png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); + return false; + } + png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); + + CopyMemoryToLenVal(rgb.data(), rgb.size(), rgb_out); + + return true; +} + +extern "C" bool ReadJpegData(sapi::LenValStruct* in_data, + int mode, + int* xsize, int* ysize) +{ + std::string data(static_cast(in_data->data), in_data->size); + guetzli::JPEGData jpg; + + auto result = guetzli::ReadJpeg(data, + static_cast(mode), &jpg); + + if (result) { + *xsize = jpg.width; + *ysize = jpg.height; + } + + return result; +} + +extern "C" double ButteraugliScoreQuality(double quality) { + return guetzli::ButteraugliScoreForQuality(quality); +} + +extern "C" bool ReadDataFromFd(int fd, sapi::LenValStruct* out_data) { + struct stat file_data; + auto status = fstat(fd, &file_data); + + if (status < 0) { + return false; + } + + auto fsize = file_data.st_size; + + std::unique_ptr buf(new char[fsize]); + status = read(fd, buf.get(), fsize); + + if (status < 0) { + return false; + } + + CopyMemoryToLenVal(buf.get(), fsize, out_data); + + return true; +} + +extern "C" bool WriteDataToFd(int fd, sapi::LenValStruct* data) { + return sandbox2::file_util::fileops::WriteToFD(fd, + static_cast(data->data), data->size); +} \ No newline at end of file diff --git a/oss-internship-2020/guetzli/guetzli_entry_points.h b/oss-internship-2020/guetzli/guetzli_entry_points.h new file mode 100644 index 0000000..6fd0f11 --- /dev/null +++ b/oss-internship-2020/guetzli/guetzli_entry_points.h @@ -0,0 +1,29 @@ +#pragma once + +#include "guetzli/processor.h" +#include "sandboxed_api/lenval_core.h" +#include "sandboxed_api/vars.h" + +extern "C" bool ProcessJPEGString(const guetzli::Params* params, + int verbose, + sapi::LenValStruct* in_data, + sapi::LenValStruct* out_data); + +extern "C" bool ProcessRGBData(const guetzli::Params* params, + int verbose, + sapi::LenValStruct* rgb, + int w, int h, + sapi::LenValStruct* out_data); + +extern "C" bool ReadPng(sapi::LenValStruct* in_data, + int* xsize, int* ysize, + sapi::LenValStruct* rgb_out); + +extern "C" bool ReadJpegData(sapi::LenValStruct* in_data, + int mode, int* xsize, int* ysize); + +extern "C" double ButteraugliScoreQuality(double quality); + +extern "C" bool ReadDataFromFd(int fd, sapi::LenValStruct* out_data); + +extern "C" bool WriteDataToFd(int fd, sapi::LenValStruct* data); \ No newline at end of file diff --git a/oss-internship-2020/guetzli/guetzli_sandbox.h b/oss-internship-2020/guetzli/guetzli_sandbox.h new file mode 100644 index 0000000..3fa2b10 --- /dev/null +++ b/oss-internship-2020/guetzli/guetzli_sandbox.h @@ -0,0 +1,36 @@ +#pragma once + +#include +#include + +#include "guetzli_sapi.sapi.h" +#include "sandboxed_api/sandbox2/policy.h" +#include "sandboxed_api/sandbox2/policybuilder.h" +#include "sandboxed_api/util/flag.h" + +namespace guetzli { +namespace sandbox { + +class GuetzliSapiSandbox : public GuetzliSandbox { + public: + std::unique_ptr ModifyPolicy( + sandbox2::PolicyBuilder*) override { + + return sandbox2::PolicyBuilder() + .AllowStaticStartup() + .AllowRead() + .AllowSystemMalloc() + .AllowWrite() + .AllowExit() + .AllowStat() + .AllowSyscalls({ + __NR_futex, + __NR_close, + __NR_recvmsg // Seems like this one needed to work with remote file descriptors + }) + .BuildOrDie(); + } +}; + +} // namespace sandbox +} // namespace guetzli \ No newline at end of file diff --git a/oss-internship-2020/guetzli/guetzli_sandboxed.cc b/oss-internship-2020/guetzli/guetzli_sandboxed.cc new file mode 100644 index 0000000..a62f249 --- /dev/null +++ b/oss-internship-2020/guetzli/guetzli_sandboxed.cc @@ -0,0 +1,160 @@ +#include +#include +#include +#include +#include + +#include + +#include "guetzli_transaction.h" +#include "sandboxed_api/sandbox2/util/fileops.h" +#include "sandboxed_api/util/statusor.h" + +namespace { + +constexpr int kDefaultJPEGQuality = 95; +constexpr int kDefaultMemlimitMB = 6000; // in MB +//constexpr absl::string_view kMktempSuffix = "XXXXXX"; + +// sapi::StatusOr> CreateNamedTempFile( +// absl::string_view prefix) { +// std::string name_template = absl::StrCat(prefix, kMktempSuffix); +// int fd = mkstemp(&name_template[0]); +// if (fd < 0) { +// return absl::UnknownError("Error creating temp file"); +// } +// return std::pair{std::move(name_template), fd}; +// } + +void TerminateHandler() { + fprintf(stderr, "Unhandled exception. Most likely insufficient memory available.\n" + "Make sure that there is 300MB/MPix of memory available.\n"); + exit(1); +} + +void Usage() { + fprintf(stderr, + "Guetzli JPEG compressor. Usage: \n" + "guetzli [flags] input_filename output_filename\n" + "\n" + "Flags:\n" + " --verbose - Print a verbose trace of all attempts to standard output.\n" + " --quality Q - Visual quality to aim for, expressed as a JPEG quality value.\n" + " Default value is %d.\n" + " --memlimit M - Memory limit in MB. Guetzli will fail if unable to stay under\n" + " the limit. Default limit is %d MB.\n" + " --nomemlimit - Do not limit memory usage.\n", kDefaultJPEGQuality, kDefaultMemlimitMB); + exit(1); +} + +} // namespace + +int main(int argc, const char** argv) { + std::set_terminate(TerminateHandler); + + int verbose = 0; + int quality = kDefaultJPEGQuality; + int memlimit_mb = kDefaultMemlimitMB; + + int opt_idx = 1; + for(;opt_idx < argc;opt_idx++) { + if (strnlen(argv[opt_idx], 2) < 2 || argv[opt_idx][0] != '-' || argv[opt_idx][1] != '-') + break; + + if (!strcmp(argv[opt_idx], "--verbose")) { + verbose = 1; + } else if (!strcmp(argv[opt_idx], "--quality")) { + opt_idx++; + if (opt_idx >= argc) + Usage(); + quality = atoi(argv[opt_idx]); + } else if (!strcmp(argv[opt_idx], "--memlimit")) { + opt_idx++; + if (opt_idx >= argc) + Usage(); + memlimit_mb = atoi(argv[opt_idx]); + } else if (!strcmp(argv[opt_idx], "--nomemlimit")) { + memlimit_mb = -1; + } else if (!strcmp(argv[opt_idx], "--")) { + opt_idx++; + break; + } else { + fprintf(stderr, "Unknown commandline flag: %s\n", argv[opt_idx]); + Usage(); + } + } + + if (argc - opt_idx != 2) { + Usage(); + } + + sandbox2::file_util::fileops::FDCloser in_fd_closer( + open(argv[opt_idx], O_RDONLY)); + + if (in_fd_closer.get() < 0) { + fprintf(stderr, "Can't open input file: %s\n", argv[opt_idx]); + return 1; + } + + // auto out_temp_file = CreateNamedTempFile("/tmp/" + std::string(argv[opt_idx + 1])); + // if (!out_temp_file.ok()) { + // fprintf(stderr, "Can't create temporary output file: %s\n", + // argv[opt_idx + 1]); + // return 1; + // } + // sandbox2::file_util::fileops::FDCloser out_fd_closer( + // out_temp_file.value().second); + + // if (unlink(out_temp_file.value().first.c_str()) < 0) { + // fprintf(stderr, "Error unlinking temp out file: %s\n", + // out_temp_file.value().first.c_str()); + // return 1; + // } + + sandbox2::file_util::fileops::FDCloser out_fd_closer( + open(".", O_TMPFILE | O_RDWR, S_IRUSR | S_IWUSR)); + + if (out_fd_closer.get() < 0) { + fprintf(stderr, "Can't create temporary output file: %s\n", argv[opt_idx]); + return 1; + } + + // sandbox2::file_util::fileops::FDCloser out_fd_closer(open(argv[opt_idx + 1], + // O_RDWR | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP)); + + // if (out_fd_closer.get() < 0) { + // fprintf(stderr, "Can't open output file: %s\n", argv[opt_idx + 1]); + // return 1; + // } + + guetzli::sandbox::TransactionParams params = { + in_fd_closer.get(), + out_fd_closer.get(), + verbose, + quality, + memlimit_mb + }; + + guetzli::sandbox::GuetzliTransaction transaction(std::move(params)); + auto result = transaction.Run(); + + if (result.ok()) { + if (access(argv[opt_idx + 1], F_OK) != -1) { + if (remove(argv[opt_idx + 1]) < 0) { + fprintf(stderr, "Error deleting existing output file: %s\n", + argv[opt_idx + 1]); + } + } + + std::stringstream path; + path << "/proc/self/fd/" << out_fd_closer.get(); + linkat(AT_FDCWD, path.str().c_str(), AT_FDCWD, argv[opt_idx + 1], + AT_SYMLINK_FOLLOW); + } + else { + fprintf(stderr, "%s\n", result.ToString().c_str()); // Use cerr instead ? + return 1; + } + + return 0; +} \ No newline at end of file diff --git a/oss-internship-2020/guetzli/guetzli_transaction.cc b/oss-internship-2020/guetzli/guetzli_transaction.cc new file mode 100644 index 0000000..9403783 --- /dev/null +++ b/oss-internship-2020/guetzli/guetzli_transaction.cc @@ -0,0 +1,160 @@ +#include "guetzli_transaction.h" + +#include + +namespace guetzli { +namespace sandbox { + +absl::Status GuetzliTransaction::Init() { + SAPI_RETURN_IF_ERROR(sandbox()->TransferToSandboxee(&in_fd_)); + SAPI_RETURN_IF_ERROR(sandbox()->TransferToSandboxee(&out_fd_)); + + if (in_fd_.GetRemoteFd() < 0) { + return absl::FailedPreconditionError( + "Error receiving remote FD: remote input fd is set to -1"); + } + if (out_fd_.GetRemoteFd() < 0) { + return absl::FailedPreconditionError( + "Error receiving remote FD: remote output fd is set to -1"); + } + + return absl::OkStatus(); +} + + absl::Status GuetzliTransaction::ProcessPng(GuetzliAPi* api, + sapi::v::Struct* params, + sapi::v::LenVal* input, + sapi::v::LenVal* output) const { + sapi::v::Int xsize; + sapi::v::Int ysize; + sapi::v::LenVal rgb_in(0); + + auto read_result = api->ReadPng(input->PtrBefore(), xsize.PtrBoth(), + ysize.PtrBoth(), rgb_in.PtrBoth()); + + if (!read_result.value_or(false)) { + return absl::FailedPreconditionError( + "Error reading PNG data from input file" + ); + } + + double pixels = static_cast(xsize.GetValue()) * ysize.GetValue(); + if (params_.memlimit_mb != -1 + && (pixels * kBytesPerPixel / (1 << 20) > params_.memlimit_mb + || params_.memlimit_mb < kLowestMemusageMB)) { + return absl::FailedPreconditionError( + "Memory limit would be exceeded" + ); + } + + auto result = api->ProcessRGBData(params->PtrBefore(), params_.verbose, + rgb_in.PtrBefore(), xsize.GetValue(), + ysize.GetValue(), output->PtrBoth()); + if (!result.value_or(false)) { + return absl::FailedPreconditionError( + "Guetzli processing failed" + ); + } + + return absl::OkStatus(); + } + + absl::Status GuetzliTransaction::ProcessJpeg(GuetzliApi* api, + sapi::v::Struct* params, + sapi::v::LenVal* input, + sapi::v::LenVal* output) const { + ::sapi::v::Int xsize; + ::sapi::v::Int ysize; + auto read_result = api->ReadJpegData(input->PtrBefore(), 0, xsize.PtrBoth(), + ysize.PtrBoth()); + + if (!read_result.value_or(false)) { + return absl::FailedPreconditionError( + "Error reading JPG data from input file" + ); + } + + double pixels = static_cast(xsize.GetValue()) * ysize.GetValue(); + if (params_.memlimit_mb != -1 + && (pixels * kBytesPerPixel / (1 << 20) > params_.memlimit_mb + || params_.memlimit_mb < kLowestMemusageMB)) { + return absl::FailedPreconditionError( + "Memory limit would be exceeded" + ); + } + + auto result = api->ProcessJPEGString(params->PtrBefore(), params_.verbose, + input->PtrBefore(), output->PtrBoth()); + + if (!result.value_or(false)) { + return absl::FailedPreconditionError( + "Guetzli processing failed" + ); + } + + return absl::OkStatus(); + } + +absl::Status GuetzliTransaction::Main() { + GuetzliApi api(sandbox()); + + sapi::v::LenVal input(0); + sapi::v::LenVal output(0); + sapi::v::Struct params; + + auto read_result = api.ReadDataFromFd(in_fd_.GetRemoteFd(), input.PtrBoth()); + + if (!read_result.value_or(false)) { + return absl::FailedPreconditionError( + "Error reading data inside sandbox" + ); + } + + auto score_quality_result = api.ButteraugliScoreQuality(params_.quality); + + if (!score_quality_result.ok()) { + return absl::FailedPreconditionError( + "Error calculating butteraugli score" + ); + } + + params.mutable_data()->butteraugli_target = score_quality_result.value(); + + static const unsigned char kPNGMagicBytes[] = { + 0x89, 'P', 'N', 'G', '\r', '\n', 0x1a, '\n', + }; + + if (input.GetDataSize() >= 8 && + memcmp(input.GetData(), kPNGMagicBytes, sizeof(kPNGMagicBytes)) == 0) { + auto process_status = ProcessPng(&api, ¶ms, &input, &output); + + if (!process_status.ok()) { + return process_status; + } + } else { + auto process_status = ProcessJpeg(&api, ¶ms, &input, &output); + + if (!process_status.ok()) { + return process_status; + } + } + + auto write_result = api.WriteDataToFd(out_fd_.GetRemoteFd(), + output.PtrBefore()); + + if (!write_result.value_or(false)) { + return absl::FailedPreconditionError( + "Error writing file inside sandbox" + ); + } + + return absl::OkStatus(); +} + +time_t GuetzliTransaction::CalculateTimeLimitFromImageSize( + uint64_t pixels) const { + return (pixels / kMpixPixels + 5) * 60; +} + +} // namespace sandbox +} // namespace guetzli \ No newline at end of file diff --git a/oss-internship-2020/guetzli/guetzli_transaction.h b/oss-internship-2020/guetzli/guetzli_transaction.h new file mode 100644 index 0000000..5d6e83a --- /dev/null +++ b/oss-internship-2020/guetzli/guetzli_transaction.h @@ -0,0 +1,216 @@ +#pragma once + +#include +#include + +#include "guetzli_sandbox.h" +#include "sandboxed_api/transaction.h" +#include "sandboxed_api/vars.h" + +namespace guetzli { +namespace sandbox { + +constexpr int kDefaultTransactionRetryCount = 1; +constexpr uint64_t kMpixPixels = 1'000'000; + +constexpr int kBytesPerPixel = 350; +constexpr int kLowestMemusageMB = 100; // in MB + +struct TransactionParams { + int in_fd; + int out_fd; + int verbose; + int quality; + int memlimit_mb; +}; + +//Add optional time limit/retry count as a constructors arguments +//Use differenet status errors +class GuetzliTransaction : public sapi::Transaction { + public: + GuetzliTransaction(TransactionParams&& params) + : sapi::Transaction(std::make_unique()) + , params_(std::move(params)) + , in_fd_(params_.in_fd) + , out_fd_(params_.out_fd) + { + sapi::Transaction::set_retry_count(kDefaultTransactionRetryCount); + sapi::Transaction::SetTimeLimit(0); + } + + private: + absl::Status Init() override; + absl::Status Main() final; + + absl::Status ProcessPng(GuetzliApi* api, + sapi::v::Struct* params, + sapi::v::LenVal* input, + sapi::v::LenVal* output) const; + + absl::Status ProcessJpeg(GuetzliApi* api, + sapi::v::Struct* params, + sapi::v::LenVal* input, + sapi::v::LenVal* output) const; + + // As guetzli takes roughly 1 minute of CPU per 1 MPix we need to calculate + // approximate time for transaction to complete + time_t CalculateTimeLimitFromImageSize(uint64_t pixels) const; + + const TransactionParams params_; + sapi::v::Fd in_fd_; + sapi::v::Fd out_fd_; +}; + +absl::Status GuetzliTransaction::Init() { + SAPI_RETURN_IF_ERROR(sandbox()->TransferToSandboxee(&in_fd_)); + SAPI_RETURN_IF_ERROR(sandbox()->TransferToSandboxee(&out_fd_)); + + if (in_fd_.GetRemoteFd() < 0) { + return absl::FailedPreconditionError( + "Error receiving remote FD: remote input fd is set to -1"); + } + if (out_fd_.GetRemoteFd() < 0) { + return absl::FailedPreconditionError( + "Error receiving remote FD: remote output fd is set to -1"); + } + + return absl::OkStatus(); +} + + absl::Status GuetzliTransaction::ProcessPng(GuetzliApi* api, + sapi::v::Struct* params, + sapi::v::LenVal* input, + sapi::v::LenVal* output) const { + sapi::v::Int xsize; + sapi::v::Int ysize; + sapi::v::LenVal rgb_in(0); + + auto read_result = api->ReadPng(input->PtrBefore(), xsize.PtrBoth(), + ysize.PtrBoth(), rgb_in.PtrBoth()); + + if (!read_result.value_or(false)) { + return absl::FailedPreconditionError( + "Error reading PNG data from input file" + ); + } + + double pixels = static_cast(xsize.GetValue()) * ysize.GetValue(); + if (params_.memlimit_mb != -1 + && (pixels * kBytesPerPixel / (1 << 20) > params_.memlimit_mb + || params_.memlimit_mb < kLowestMemusageMB)) { + return absl::FailedPreconditionError( + "Memory limit would be exceeded" + ); + } + + auto result = api->ProcessRGBData(params->PtrBefore(), params_.verbose, + rgb_in.PtrBefore(), xsize.GetValue(), + ysize.GetValue(), output->PtrBoth()); + if (!result.value_or(false)) { + return absl::FailedPreconditionError( + "Guetzli processing failed" + ); + } + + return absl::OkStatus(); + } + + absl::Status GuetzliTransaction::ProcessJpeg(GuetzliApi* api, + sapi::v::Struct* params, + sapi::v::LenVal* input, + sapi::v::LenVal* output) const { + sapi::v::Int xsize; + sapi::v::Int ysize; + auto read_result = api->ReadJpegData(input->PtrBefore(), 0, xsize.PtrBoth(), + ysize.PtrBoth()); + + if (!read_result.value_or(false)) { + return absl::FailedPreconditionError( + "Error reading JPG data from input file" + ); + } + + double pixels = static_cast(xsize.GetValue()) * ysize.GetValue(); + if (params_.memlimit_mb != -1 + && (pixels * kBytesPerPixel / (1 << 20) > params_.memlimit_mb + || params_.memlimit_mb < kLowestMemusageMB)) { + return absl::FailedPreconditionError( + "Memory limit would be exceeded" + ); + } + + auto result = api->ProcessJPEGString(params->PtrBefore(), params_.verbose, + input->PtrBefore(), output->PtrBoth()); + + if (!result.value_or(false)) { + return absl::FailedPreconditionError( + "Guetzli processing failed" + ); + } + + return absl::OkStatus(); + } + +absl::Status GuetzliTransaction::Main() { + GuetzliApi api(sandbox()); + + sapi::v::LenVal input(0); + sapi::v::LenVal output(0); + sapi::v::Struct params; + + auto read_result = api.ReadDataFromFd(in_fd_.GetRemoteFd(), input.PtrBoth()); + + if (!read_result.value_or(false)) { + return absl::FailedPreconditionError( + "Error reading data inside sandbox" + ); + } + + auto score_quality_result = api.ButteraugliScoreQuality(params_.quality); + + if (!score_quality_result.ok()) { + return absl::FailedPreconditionError( + "Error calculating butteraugli score" + ); + } + + params.mutable_data()->butteraugli_target = score_quality_result.value(); + + static const unsigned char kPNGMagicBytes[] = { + 0x89, 'P', 'N', 'G', '\r', '\n', 0x1a, '\n', + }; + + if (input.GetDataSize() >= 8 && + memcmp(input.GetData(), kPNGMagicBytes, sizeof(kPNGMagicBytes)) == 0) { + auto process_status = ProcessPng(&api, ¶ms, &input, &output); + + if (!process_status.ok()) { + return process_status; + } + } else { + auto process_status = ProcessJpeg(&api, ¶ms, &input, &output); + + if (!process_status.ok()) { + return process_status; + } + } + + auto write_result = api.WriteDataToFd(out_fd_.GetRemoteFd(), + output.PtrBefore()); + + if (!write_result.value_or(false)) { + return absl::FailedPreconditionError( + "Error writing file inside sandbox" + ); + } + + return absl::OkStatus(); +} + +time_t GuetzliTransaction::CalculateTimeLimitFromImageSize( + uint64_t pixels) const { + return (pixels / kMpixPixels + 5) * 60; +} + +} // namespace sandbox +} // namespace guetzli diff --git a/oss-internship-2020/guetzli/tests/BUILD.bazel b/oss-internship-2020/guetzli/tests/BUILD.bazel new file mode 100644 index 0000000..b7c7517 --- /dev/null +++ b/oss-internship-2020/guetzli/tests/BUILD.bazel @@ -0,0 +1,22 @@ +# cc_test( +# name = "transaction_tests", +# srcs = ["guetzli_transaction_test.cc"], +# visibility=["//visibility:public"], +# includes = ["."], +# deps = [ +# "//:guetzli_sapi", +# "@googletest//:gtest_main" +# ], +# ) + +# cc_test( +# name = "sapi_lib_tests", +# srcs = ["guetzli_sapi_test.cc"], +# visibility=["//visibility:public"], +# includes=[".."], +# deps = [ +# "//:guetzli_sapi", +# "@googletest//:gtest_main" +# ], +# data = glob(["testdata/*"]) +# ) \ No newline at end of file diff --git a/oss-internship-2020/guetzli/tests/guetzli_sapi_test.cc b/oss-internship-2020/guetzli/tests/guetzli_sapi_test.cc new file mode 100644 index 0000000..73e54d6 --- /dev/null +++ b/oss-internship-2020/guetzli/tests/guetzli_sapi_test.cc @@ -0,0 +1,165 @@ +#include "gtest/gtest.h" +#include "guetzli_sandbox.h" +#include "sandboxed_api/sandbox2/util/fileops.h" +#include "sandboxed_api/vars.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace guetzli { +namespace sandbox { +namespace tests { + +namespace { + +constexpr const char* IN_PNG_FILENAME = "bees.png"; +constexpr const char* IN_JPG_FILENAME = "landscape.jpg"; + +constexpr int IN_PNG_FILE_SIZE = 177'424; +constexpr int IN_JPG_FILE_SIZE = 14'418; + +constexpr int DEFAULT_QUALITY_TARGET = 95; + +constexpr const char* RELATIVE_PATH_TO_TESTDATA = + "/guetzli/guetzli-sandboxed/tests/testdata/"; + +std::string GetPathToInputFile(const char* filename) { + return std::string(getenv("TEST_SRCDIR")) + + std::string(RELATIVE_PATH_TO_TESTDATA) + + std::string(filename); +} + +std::string ReadFromFile(const std::string& filename) { + std::ifstream stream(filename, std::ios::binary); + + if (!stream.is_open()) { + return ""; + } + + std::stringstream result; + result << stream.rdbuf(); + return result.str(); +} + +} // namespace + +class GuetzliSapiTest : public ::testing::Test { +protected: + void SetUp() override { + sandbox_ = std::make_unique(); + sandbox_->Init().IgnoreError(); + api_ = std::make_unique(sandbox_.get()); + } + + std::unique_ptr sandbox_; + std::unique_ptr api_; +}; + +TEST_F(GuetzliSapiTest, ReadDataFromFd) { + std::string input_file_path = GetPathToInputFile(IN_PNG_FILENAME); + int fd = open(input_file_path.c_str(), O_RDONLY); + ASSERT_TRUE(fd != -1) << "Error opening input file"; + sapi::v::Fd remote_fd(fd); + auto send_fd_status = sandbox_->TransferToSandboxee(&remote_fd); + ASSERT_TRUE(send_fd_status.ok()) << "Error sending fd to sandboxee"; + ASSERT_TRUE(remote_fd.GetRemoteFd() != -1) << "Error opening remote fd"; + sapi::v::LenVal data(0); + auto read_status = + api_->ReadDataFromFd(remote_fd.GetRemoteFd(), data.PtrBoth()); + ASSERT_TRUE(read_status.value_or(false)) << "Error reading data from fd"; + ASSERT_EQ(data.GetDataSize(), IN_PNG_FILE_SIZE) << "Wrong size of file"; +} + +// TEST_F(GuetzliSapiTest, WriteDataToFd) { + +// } + +TEST_F(GuetzliSapiTest, ReadPng) { + std::string data = ReadFromFile(GetPathToInputFile(IN_PNG_FILENAME)); + ASSERT_EQ(data.size(), IN_PNG_FILE_SIZE) << "Error reading input file"; + sapi::v::LenVal in_data(data.data(), data.size()); + sapi::v::Int xsize, ysize; + sapi::v::LenVal rgb_out(0); + + auto status = api_->ReadPng(in_data.PtrBefore(), xsize.PtrBoth(), + ysize.PtrBoth(), rgb_out.PtrBoth()); + ASSERT_TRUE(status.value_or(false)) << "Error processing png data"; + ASSERT_EQ(xsize.GetValue(), 444) << "Error parsing width"; + ASSERT_EQ(ysize.GetValue(), 258) << "Error parsing height"; +} + +TEST_F(GuetzliSapiTest, ReadJpeg) { + std::string data = ReadFromFile(GetPathToInputFile(IN_JPG_FILENAME)); + ASSERT_EQ(data.size(), IN_JPG_FILE_SIZE) << "Error reading input file"; + sapi::v::LenVal in_data(data.data(), data.size()); + sapi::v::Int xsize, ysize; + + auto status = api_->ReadJpegData(in_data.PtrBefore(), 0, + xsize.PtrBoth(), ysize.PtrBoth()); + ASSERT_TRUE(status.value_or(false)) << "Error processing jpeg data"; + ASSERT_EQ(xsize.GetValue(), 180) << "Error parsing width"; + ASSERT_EQ(ysize.GetValue(), 180) << "Error parsing height"; +} + +// This test can take up to few minutes depending on your hardware +TEST_F(GuetzliSapiTest, ProcessRGB) { + std::string data = ReadFromFile(GetPathToInputFile(IN_PNG_FILENAME)); + ASSERT_EQ(data.size(), IN_PNG_FILE_SIZE) << "Error reading input file"; + sapi::v::LenVal in_data(data.data(), data.size()); + sapi::v::Int xsize, ysize; + sapi::v::LenVal rgb_out(0); + + auto status = api_->ReadPng(in_data.PtrBefore(), xsize.PtrBoth(), + ysize.PtrBoth(), rgb_out.PtrBoth()); + ASSERT_TRUE(status.value_or(false)) << "Error processing png data"; + ASSERT_EQ(xsize.GetValue(), 444) << "Error parsing width"; + ASSERT_EQ(ysize.GetValue(), 258) << "Error parsing height"; + auto quality = + api_->ButteraugliScoreQuality(static_cast(DEFAULT_QUALITY_TARGET)); + ASSERT_TRUE(quality.ok()) << "Error calculating butteraugli quality"; + sapi::v::Struct params; + sapi::v::LenVal out_data(0); + params.mutable_data()->butteraugli_target = quality.value(); + + status = api_->ProcessRGBData(params.PtrBefore(), 0, rgb_out.PtrBefore(), + xsize.GetValue(), ysize.GetValue(), out_data.PtrBoth()); + ASSERT_TRUE(status.value_or(false)) << "Error processing png file"; + ASSERT_EQ(out_data.GetDataSize(), 38'625); + //ADD COMPARSION WITH REFERENCE OUTPUT +} + +// This test can take up to few minutes depending on your hardware +TEST_F(GuetzliSapiTest, ProcessJpeg) { + std::string data = ReadFromFile(GetPathToInputFile(IN_JPG_FILENAME)); + ASSERT_EQ(data.size(), IN_JPG_FILE_SIZE) << "Error reading input file"; + sapi::v::LenVal in_data(data.data(), data.size()); + sapi::v::Int xsize, ysize; + + auto status = api_->ReadJpegData(in_data.PtrBefore(), 0, + xsize.PtrBoth(), ysize.PtrBoth()); + ASSERT_TRUE(status.value_or(false)) << "Error processing jpeg data"; + ASSERT_EQ(xsize.GetValue(), 180) << "Error parsing width"; + ASSERT_EQ(ysize.GetValue(), 180) << "Error parsing height"; + + auto quality = + api_->ButteraugliScoreQuality(static_cast(DEFAULT_QUALITY_TARGET)); + ASSERT_TRUE(quality.ok()) << "Error calculating butteraugli quality"; + sapi::v::Struct params; + params.mutable_data()->butteraugli_target = quality.value(); + sapi::v::LenVal out_data(0); + + status = api_->ProcessJPEGString(params.PtrBefore(), 0, in_data.PtrBefore(), + out_data.PtrBoth()); + ASSERT_TRUE(status.value_or(false)) << "Error processing jpeg file"; + ASSERT_EQ(out_data.GetDataSize(), 10'816); + //ADD COMPARSION WITH REFERENCE OUTPUT +} + +} // namespace tests +} // namespace sandbox +} // namespace guetzli \ No newline at end of file diff --git a/oss-internship-2020/guetzli/tests/guetzli_transaction_test.cc b/oss-internship-2020/guetzli/tests/guetzli_transaction_test.cc new file mode 100644 index 0000000..8471d1e --- /dev/null +++ b/oss-internship-2020/guetzli/tests/guetzli_transaction_test.cc @@ -0,0 +1,2 @@ +#include "gtest/gtest.h" +#include "guetzli_transaction.h" diff --git a/oss-internship-2020/guetzli/tests/testdata/bees.png b/oss-internship-2020/guetzli/tests/testdata/bees.png new file mode 100644 index 0000000000000000000000000000000000000000..11640c7488fb114127f869572d7ffec132e77746 GIT binary patch literal 177424 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%0D2&3f5zx%5uXh+B#w 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

slNF9=bRxAin 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�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 literal 0 HcmV?d00001 From d41a3d6d16bccb057aa78ffc68a9a65219c9ce7e Mon Sep 17 00:00:00 2001 From: Bohdan Tyshchenko Date: Tue, 11 Aug 2020 15:48:48 -0700 Subject: [PATCH 05/82] First version of guetzli sandbox --- oss-internship-2020/guetzli/.bazelrc | 2 + oss-internship-2020/guetzli/BUILD.bazel | 31 +- oss-internship-2020/guetzli/WORKSPACE | 31 +- .../guetzli/external/guetzli.BUILD | 18 + .../guetzli/guetzli_entry_points.cc | 237 +- .../guetzli/guetzli_entry_points.h | 31 +- .../guetzli/guetzli_sandboxed.cc | 44 +- .../guetzli/guetzli_transaction.cc | 180 +- .../guetzli/guetzli_transaction.h | 190 +- .../guetzli/third_party/butteraugli/BUILD | 24 + .../guetzli/third_party/butteraugli/LICENSE | 201 ++ .../guetzli/third_party/butteraugli/README.md | 68 + .../guetzli/third_party/butteraugli/WORKSPACE | 25 + .../butteraugli/butteraugli/Makefile | 7 + .../butteraugli/butteraugli/butteraugli.cc | 1994 +++++++++++++++++ .../butteraugli/butteraugli/butteraugli.h | 619 +++++ .../butteraugli/butteraugli_main.cc | 457 ++++ .../third_party/butteraugli/jpeg.BUILD | 89 + .../guetzli/third_party/butteraugli/png.BUILD | 33 + .../third_party/butteraugli/zlib.BUILD | 36 + 20 files changed, 3835 insertions(+), 482 deletions(-) create mode 100644 oss-internship-2020/guetzli/.bazelrc create mode 100644 oss-internship-2020/guetzli/external/guetzli.BUILD create mode 100755 oss-internship-2020/guetzli/third_party/butteraugli/BUILD create mode 100755 oss-internship-2020/guetzli/third_party/butteraugli/LICENSE create mode 100755 oss-internship-2020/guetzli/third_party/butteraugli/README.md create mode 100755 oss-internship-2020/guetzli/third_party/butteraugli/WORKSPACE create mode 100755 oss-internship-2020/guetzli/third_party/butteraugli/butteraugli/Makefile create mode 100755 oss-internship-2020/guetzli/third_party/butteraugli/butteraugli/butteraugli.cc create mode 100755 oss-internship-2020/guetzli/third_party/butteraugli/butteraugli/butteraugli.h create mode 100755 oss-internship-2020/guetzli/third_party/butteraugli/butteraugli/butteraugli_main.cc create mode 100755 oss-internship-2020/guetzli/third_party/butteraugli/jpeg.BUILD create mode 100755 oss-internship-2020/guetzli/third_party/butteraugli/png.BUILD create mode 100755 oss-internship-2020/guetzli/third_party/butteraugli/zlib.BUILD diff --git a/oss-internship-2020/guetzli/.bazelrc b/oss-internship-2020/guetzli/.bazelrc new file mode 100644 index 0000000..a68e070 --- /dev/null +++ b/oss-internship-2020/guetzli/.bazelrc @@ -0,0 +1,2 @@ +# Build in C++17 mode without a custom CROSSTOOL +build --cxxopt=-std=c++17 diff --git a/oss-internship-2020/guetzli/BUILD.bazel b/oss-internship-2020/guetzli/BUILD.bazel index 345f879..6e3332a 100644 --- a/oss-internship-2020/guetzli/BUILD.bazel +++ b/oss-internship-2020/guetzli/BUILD.bazel @@ -1,9 +1,3 @@ -load("@rules_cc//cc:defs.bzl", "cc_proto_library") -load("@rules_proto//proto:defs.bzl", "proto_library") -load( - "@com_google_sandboxed_api//sandboxed_api/bazel:proto.bzl", - "sapi_proto_library", -) load( "@com_google_sandboxed_api//sandboxed_api/bazel:sapi.bzl", "sapi_library", @@ -17,23 +11,17 @@ cc_library( "@guetzli//:guetzli_lib", "@com_google_sandboxed_api//sandboxed_api:lenval_core", "@com_google_sandboxed_api//sandboxed_api:vars", - #"@com_google_sandboxed_api//sandboxed_api/sandbox2/util:temp_file", visibility error "@png_archive//:png" ], - visibility = ["//visibility:public"] ) sapi_library( name = "guetzli_sapi", - #srcs = ["guetzli_transaction.cc"], // Error when try to place definitions insde .cc file + srcs = ["guetzli_transaction.cc"], hdrs = ["guetzli_sandbox.h", "guetzli_transaction.h"], functions = [ - "ProcessJPEGString", - "ProcessRGBData", - "ButteraugliScoreQuality", - "ReadPng", - "ReadJpegData", - "ReadDataFromFd", + "ProcessJpeg", + "ProcessRgb", "WriteDataToFd" ], input_files = ["guetzli_entry_points.h"], @@ -43,23 +31,10 @@ sapi_library( namespace = "guetzli::sandbox" ) -# cc_library( -# name = "guetzli_sapi_transaction", -# #srcs = ["guetzli_transaction.cc"], -# hdrs = ["guetzli_transaction.h"], -# deps = [ -# ":guetzli_sapi" -# ], -# visibility = ["//visibility:public"] -# ) - cc_binary( name="guetzli_sandboxed", srcs=["guetzli_sandboxed.cc"], - includes = ["."], - visibility= [ "//visibility:public" ], deps = [ - #":guetzli_sapi_transaction" ":guetzli_sapi" ] ) \ No newline at end of file diff --git a/oss-internship-2020/guetzli/WORKSPACE b/oss-internship-2020/guetzli/WORKSPACE index 17887de..f7ec64c 100644 --- a/oss-internship-2020/guetzli/WORKSPACE +++ b/oss-internship-2020/guetzli/WORKSPACE @@ -15,6 +15,7 @@ workspace(name = "guetzli_sandboxed") load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository") +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe") # Include the Sandboxed API dependency if it does not already exist in this @@ -46,22 +47,19 @@ load("@com_google_protobuf//:protobuf_deps.bzl", "protobuf_deps") protobuf_deps() -maybe( - git_repository, +local_repository( + name = "butteraugli", + path = "third_party/butteraugli/" +) + +http_archive( name = "guetzli", - remote = "https://github.com/google/guetzli.git", - branch = "master" + build_file = "guetzli.BUILD", + sha256 = "39632357e49db83d9560bf0de560ad833352f36d23b109b0e995b01a37bddb57", + strip_prefix = "guetzli-master", + url = "https://github.com/google/guetzli/archive/master.zip" ) -maybe( - git_repository, - name = "googletest", - remote = "https://github.com/google/googletest", - tag = "release-1.10.0" -) - -load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") - http_archive( name = "png_archive", build_file = "png.BUILD", @@ -76,4 +74,11 @@ http_archive( sha256 = "8d7e9f698ce48787b6e1c67e6bff79e487303e66077e25cb9784ac8835978017", strip_prefix = "zlib-1.2.10", url = "http://zlib.net/fossils/zlib-1.2.10.tar.gz", +) + +maybe( + git_repository, + name = "googletest", + remote = "https://github.com/google/googletest", + tag = "release-1.10.0" ) \ No newline at end of file diff --git a/oss-internship-2020/guetzli/external/guetzli.BUILD b/oss-internship-2020/guetzli/external/guetzli.BUILD new file mode 100644 index 0000000..dec29a1 --- /dev/null +++ b/oss-internship-2020/guetzli/external/guetzli.BUILD @@ -0,0 +1,18 @@ +package(default_visibility = ["//visibility:public"]) + +cc_library( + name = "guetzli_lib", + srcs = glob( + [ + "guetzli/*.h", + "guetzli/*.cc", + "guetzli/*.inc", + ], + exclude = ["guetzli/guetzli.cc"], + ), + copts = [ "-Wno-sign-compare" ], + visibility= [ "//visibility:public" ], + deps = [ + "@butteraugli//:butteraugli_lib", + ], +) \ No newline at end of file diff --git a/oss-internship-2020/guetzli/guetzli_entry_points.cc b/oss-internship-2020/guetzli/guetzli_entry_points.cc index 446150c..a815673 100644 --- a/oss-internship-2020/guetzli/guetzli_entry_points.cc +++ b/oss-internship-2020/guetzli/guetzli_entry_points.cc @@ -3,18 +3,25 @@ #include "guetzli_entry_points.h" #include "png.h" #include "sandboxed_api/sandbox2/util/fileops.h" +#include "sandboxed_api/util/statusor.h" #include #include +#include #include #include #include namespace { -inline uint8_t BlendOnBlack(const uint8_t val, const uint8_t alpha) { - return (static_cast(val) * static_cast(alpha) + 128) / 255; -} +constexpr int kBytesPerPixel = 350; +constexpr int kLowestMemusageMB = 100; // in MB + +struct GuetzliInitData { + std::string in_data; + guetzli::Params params; + guetzli::ProcessStats stats; +}; template void CopyMemoryToLenVal(const T* data, size_t size, @@ -26,68 +33,63 @@ void CopyMemoryToLenVal(const T* data, size_t size, out_data->data = new_out; } -} // namespace - -extern "C" bool ProcessJPEGString(const guetzli::Params* params, - int verbose, - sapi::LenValStruct* in_data, - sapi::LenValStruct* out_data) -{ - std::string in_data_temp(static_cast(in_data->data), - in_data->size); - - guetzli::ProcessStats stats; - if (verbose > 0) { - stats.debug_output_file = stderr; - } - - std::string temp_out = ""; - auto result = guetzli::Process(*params, &stats, in_data_temp, &temp_out); - - if (result) { - CopyMemoryToLenVal(temp_out.data(), temp_out.size(), out_data); - } - - return result; -} - -extern "C" bool ProcessRGBData(const guetzli::Params* params, - int verbose, - sapi::LenValStruct* rgb, - int w, int h, - sapi::LenValStruct* out_data) -{ - std::vector in_data_temp; - in_data_temp.reserve(rgb->size); - - auto* rgb_data = static_cast(rgb->data); - std::copy(rgb_data, rgb_data + rgb->size, std::back_inserter(in_data_temp)); - - guetzli::ProcessStats stats; - if (verbose > 0) { - stats.debug_output_file = stderr; - } - - std::string temp_out = ""; - auto result = - guetzli::Process(*params, &stats, in_data_temp, w, h, &temp_out); - - //TODO: Move shared part of the code to another function - if (result) { - CopyMemoryToLenVal(temp_out.data(), temp_out.size(), out_data); - } - - return result; -} - -extern "C" bool ReadPng(sapi::LenValStruct* in_data, - int* xsize, int* ysize, - sapi::LenValStruct* rgb_out) -{ - std::string data(static_cast(in_data->data), in_data->size); - std::vector rgb; +sapi::StatusOr ReadFromFd(int fd) { + struct stat file_data; + auto status = fstat(fd, &file_data); - png_structp png_ptr = + if (status < 0) { + return absl::FailedPreconditionError( + "Error reading input from fd" + ); + } + + auto fsize = file_data.st_size; + + std::unique_ptr buf(new char[fsize]); + status = read(fd, buf.get(), fsize); + + if (status < 0) { + lseek(fd, 0, SEEK_SET); + return absl::FailedPreconditionError( + "Error reading input from fd" + ); + } + + return std::string(buf.get(), fsize); +} + +sapi::StatusOr PrepareDataForProcessing( + const ProcessingParams* processing_params) { + auto input_status = ReadFromFd(processing_params->remote_fd); + + if (!input_status.ok()) { + return input_status.status(); + } + + guetzli::Params guetzli_params; + guetzli_params.butteraugli_target = static_cast( + guetzli::ButteraugliScoreForQuality(processing_params->quality)); + + guetzli::ProcessStats stats; + + if (processing_params->verbose) { + stats.debug_output_file = stderr; + } + + return GuetzliInitData{ + std::move(input_status.value()), + guetzli_params, + stats + }; +} + +inline uint8_t BlendOnBlack(const uint8_t val, const uint8_t alpha) { + return (static_cast(val) * static_cast(alpha) + 128) / 255; +} + +bool ReadPNG(const std::string& data, int* xsize, int* ysize, + std::vector* rgb) { + png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); if (!png_ptr) { return false; @@ -129,7 +131,7 @@ extern "C" bool ReadPng(sapi::LenValStruct* in_data, *xsize = png_get_image_width(png_ptr, info_ptr); *ysize = png_get_image_height(png_ptr, info_ptr); - rgb.resize(3 * (*xsize) * (*ysize)); + rgb->resize(3 * (*xsize) * (*ysize)); const int components = png_get_channels(png_ptr, info_ptr); switch (components) { @@ -137,7 +139,7 @@ extern "C" bool ReadPng(sapi::LenValStruct* in_data, // GRAYSCALE for (int y = 0; y < *ysize; ++y) { const uint8_t* row_in = row_pointers[y]; - uint8_t* row_out = &(rgb)[3 * y * (*xsize)]; + uint8_t* row_out = &(*rgb)[3 * y * (*xsize)]; for (int x = 0; x < *xsize; ++x) { const uint8_t gray = row_in[x]; row_out[3 * x + 0] = gray; @@ -151,7 +153,7 @@ extern "C" bool ReadPng(sapi::LenValStruct* in_data, // GRAYSCALE + ALPHA for (int y = 0; y < *ysize; ++y) { const uint8_t* row_in = row_pointers[y]; - uint8_t* row_out = &(rgb)[3 * y * (*xsize)]; + uint8_t* row_out = &(*rgb)[3 * y * (*xsize)]; for (int x = 0; x < *xsize; ++x) { const uint8_t gray = BlendOnBlack(row_in[2 * x], row_in[2 * x + 1]); row_out[3 * x + 0] = gray; @@ -165,7 +167,7 @@ extern "C" bool ReadPng(sapi::LenValStruct* in_data, // RGB for (int y = 0; y < *ysize; ++y) { const uint8_t* row_in = row_pointers[y]; - uint8_t* row_out = &(rgb)[3 * y * (*xsize)]; + uint8_t* row_out = &(*rgb)[3 * y * (*xsize)]; memcpy(row_out, row_in, 3 * (*xsize)); } break; @@ -174,7 +176,7 @@ extern "C" bool ReadPng(sapi::LenValStruct* in_data, // RGBA for (int y = 0; y < *ysize; ++y) { const uint8_t* row_in = row_pointers[y]; - uint8_t* row_out = &(rgb)[3 * y * (*xsize)]; + uint8_t* row_out = &(*rgb)[3 * y * (*xsize)]; for (int x = 0; x < *xsize; ++x) { const uint8_t alpha = row_in[4 * x + 3]; row_out[3 * x + 0] = BlendOnBlack(row_in[4 * x + 0], alpha); @@ -189,53 +191,84 @@ extern "C" bool ReadPng(sapi::LenValStruct* in_data, return false; } png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); - - CopyMemoryToLenVal(rgb.data(), rgb.size(), rgb_out); - return true; } -extern "C" bool ReadJpegData(sapi::LenValStruct* in_data, - int mode, - int* xsize, int* ysize) -{ - std::string data(static_cast(in_data->data), in_data->size); - guetzli::JPEGData jpg; - - auto result = guetzli::ReadJpeg(data, - static_cast(mode), &jpg); - - if (result) { - *xsize = jpg.width; - *ysize = jpg.height; - } - - return result; +bool CheckMemoryLimitExceeded(int memlimit_mb, int xsize, int ysize) { + double pixels = static_cast(xsize) * ysize; + return memlimit_mb != -1 + && (pixels * kBytesPerPixel / (1 << 20) > memlimit_mb + || memlimit_mb < kLowestMemusageMB); } -extern "C" double ButteraugliScoreQuality(double quality) { - return guetzli::ButteraugliScoreForQuality(quality); +} // namespace + +extern "C" bool ProcessJpeg(const ProcessingParams* processing_params, + sapi::LenValStruct* output) { + auto processing_data_status = PrepareDataForProcessing(processing_params); + + if (!processing_data_status.status().ok()) { + fprintf(stderr, "%s\n", processing_data_status.status().ToString().c_str()); + return false; + } + + guetzli::JPEGData jpg_header; + if (!guetzli::ReadJpeg(processing_data_status.value().in_data, + guetzli::JPEG_READ_HEADER, &jpg_header)) { + fprintf(stderr, "Error reading JPG data from input file\n"); + return false; + } + + if (CheckMemoryLimitExceeded(processing_params->memlimit_mb, + jpg_header.width, jpg_header.height)) { + fprintf(stderr, "Memory limit would be exceeded.\n"); + return false; + } + + std::string out_data; + if (!guetzli::Process(processing_data_status.value().params, + &processing_data_status.value().stats, + processing_data_status.value().in_data, + &out_data)) { + fprintf(stderr, "Guezli processing failed\n"); + return false; + } + + CopyMemoryToLenVal(out_data.data(), out_data.size(), output); + return true; } -extern "C" bool ReadDataFromFd(int fd, sapi::LenValStruct* out_data) { - struct stat file_data; - auto status = fstat(fd, &file_data); - - if (status < 0) { +extern "C" bool ProcessRgb(const ProcessingParams* processing_params, + sapi::LenValStruct* output) { + auto processing_data_status = PrepareDataForProcessing(processing_params); + + if (!processing_data_status.status().ok()) { + fprintf(stderr, "%s\n", processing_data_status.status().ToString().c_str()); return false; } - - auto fsize = file_data.st_size; - std::unique_ptr buf(new char[fsize]); - status = read(fd, buf.get(), fsize); + int xsize, ysize; + std::vector rgb; - if (status < 0) { + if (!ReadPNG(processing_data_status.value().in_data, &xsize, &ysize, &rgb)) { + fprintf(stderr, "Error reading PNG data from input file\n"); return false; } - - CopyMemoryToLenVal(buf.get(), fsize, out_data); + if (CheckMemoryLimitExceeded(processing_params->memlimit_mb, xsize, ysize)) { + fprintf(stderr, "Memory limit would be exceeded.\n"); + return false; + } + + std::string out_data; + if (!guetzli::Process(processing_data_status.value().params, + &processing_data_status.value().stats, + rgb, xsize, ysize, &out_data)) { + fprintf(stderr, "Guetzli processing failed\n"); + return false; + } + + CopyMemoryToLenVal(out_data.data(), out_data.size(), output); return true; } diff --git a/oss-internship-2020/guetzli/guetzli_entry_points.h b/oss-internship-2020/guetzli/guetzli_entry_points.h index 6fd0f11..90b0d97 100644 --- a/oss-internship-2020/guetzli/guetzli_entry_points.h +++ b/oss-internship-2020/guetzli/guetzli_entry_points.h @@ -4,26 +4,15 @@ #include "sandboxed_api/lenval_core.h" #include "sandboxed_api/vars.h" -extern "C" bool ProcessJPEGString(const guetzli::Params* params, - int verbose, - sapi::LenValStruct* in_data, - sapi::LenValStruct* out_data); - -extern "C" bool ProcessRGBData(const guetzli::Params* params, - int verbose, - sapi::LenValStruct* rgb, - int w, int h, - sapi::LenValStruct* out_data); - -extern "C" bool ReadPng(sapi::LenValStruct* in_data, - int* xsize, int* ysize, - sapi::LenValStruct* rgb_out); - -extern "C" bool ReadJpegData(sapi::LenValStruct* in_data, - int mode, int* xsize, int* ysize); - -extern "C" double ButteraugliScoreQuality(double quality); - -extern "C" bool ReadDataFromFd(int fd, sapi::LenValStruct* out_data); +struct ProcessingParams { + int remote_fd = -1; + int verbose = 0; + int quality = 0; + int memlimit_mb = 0; +}; +extern "C" bool ProcessJpeg(const ProcessingParams* processing_params, + sapi::LenValStruct* output); +extern "C" bool ProcessRgb(const ProcessingParams* processing_params, + sapi::LenValStruct* output); extern "C" bool WriteDataToFd(int fd, sapi::LenValStruct* data); \ No newline at end of file diff --git a/oss-internship-2020/guetzli/guetzli_sandboxed.cc b/oss-internship-2020/guetzli/guetzli_sandboxed.cc index a62f249..3393b8f 100644 --- a/oss-internship-2020/guetzli/guetzli_sandboxed.cc +++ b/oss-internship-2020/guetzli/guetzli_sandboxed.cc @@ -14,17 +14,6 @@ namespace { constexpr int kDefaultJPEGQuality = 95; constexpr int kDefaultMemlimitMB = 6000; // in MB -//constexpr absl::string_view kMktempSuffix = "XXXXXX"; - -// sapi::StatusOr> CreateNamedTempFile( -// absl::string_view prefix) { -// std::string name_template = absl::StrCat(prefix, kMktempSuffix); -// int fd = mkstemp(&name_template[0]); -// if (fd < 0) { -// return absl::UnknownError("Error creating temp file"); -// } -// return std::pair{std::move(name_template), fd}; -// } void TerminateHandler() { fprintf(stderr, "Unhandled exception. Most likely insufficient memory available.\n" @@ -96,21 +85,6 @@ int main(int argc, const char** argv) { return 1; } - // auto out_temp_file = CreateNamedTempFile("/tmp/" + std::string(argv[opt_idx + 1])); - // if (!out_temp_file.ok()) { - // fprintf(stderr, "Can't create temporary output file: %s\n", - // argv[opt_idx + 1]); - // return 1; - // } - // sandbox2::file_util::fileops::FDCloser out_fd_closer( - // out_temp_file.value().second); - - // if (unlink(out_temp_file.value().first.c_str()) < 0) { - // fprintf(stderr, "Error unlinking temp out file: %s\n", - // out_temp_file.value().first.c_str()); - // return 1; - // } - sandbox2::file_util::fileops::FDCloser out_fd_closer( open(".", O_TMPFILE | O_RDWR, S_IRUSR | S_IWUSR)); @@ -119,14 +93,6 @@ int main(int argc, const char** argv) { return 1; } - // sandbox2::file_util::fileops::FDCloser out_fd_closer(open(argv[opt_idx + 1], - // O_RDWR | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP)); - - // if (out_fd_closer.get() < 0) { - // fprintf(stderr, "Can't open output file: %s\n", argv[opt_idx + 1]); - // return 1; - // } - guetzli::sandbox::TransactionParams params = { in_fd_closer.get(), out_fd_closer.get(), @@ -143,13 +109,19 @@ int main(int argc, const char** argv) { if (remove(argv[opt_idx + 1]) < 0) { fprintf(stderr, "Error deleting existing output file: %s\n", argv[opt_idx + 1]); + return 1; } } std::stringstream path; path << "/proc/self/fd/" << out_fd_closer.get(); - linkat(AT_FDCWD, path.str().c_str(), AT_FDCWD, argv[opt_idx + 1], - AT_SYMLINK_FOLLOW); + + if (linkat(AT_FDCWD, path.str().c_str(), AT_FDCWD, argv[opt_idx + 1], + AT_SYMLINK_FOLLOW) < 0) { + fprintf(stderr, "Error linking %s\n", + argv[opt_idx + 1]); + return 1; + } } else { fprintf(stderr, "%s\n", result.ToString().c_str()); // Use cerr instead ? diff --git a/oss-internship-2020/guetzli/guetzli_transaction.cc b/oss-internship-2020/guetzli/guetzli_transaction.cc index 9403783..0f7e1bd 100644 --- a/oss-internship-2020/guetzli/guetzli_transaction.cc +++ b/oss-internship-2020/guetzli/guetzli_transaction.cc @@ -1,11 +1,38 @@ #include "guetzli_transaction.h" #include +#include namespace guetzli { namespace sandbox { absl::Status GuetzliTransaction::Init() { + // Close remote fd if transaction is repeated + if (in_fd_.GetRemoteFd() != -1) { + SAPI_RETURN_IF_ERROR(in_fd_.CloseRemoteFd(sandbox()->GetRpcChannel())); + } + if (out_fd_.GetRemoteFd() != -1) { + SAPI_RETURN_IF_ERROR(out_fd_.CloseRemoteFd(sandbox()->GetRpcChannel())); + } + + // Reposition back to the beginning of file + if (lseek(in_fd_.GetValue(), 0, SEEK_CUR) != 0) { + if (lseek(in_fd_.GetValue(), 0, SEEK_SET) != 0) { + return absl::FailedPreconditionError( + "Error returnig cursor to the beginning" + ); + } + } + + // Choosing between jpg and png modes + sapi::StatusOr image_type = GetImageTypeFromFd(in_fd_.GetValue()); + + if (!image_type.ok()) { + return image_type.status(); + } + + image_type_ = image_type.value(); + SAPI_RETURN_IF_ERROR(sandbox()->TransferToSandboxee(&in_fd_)); SAPI_RETURN_IF_ERROR(sandbox()->TransferToSandboxee(&out_fd_)); @@ -18,125 +45,36 @@ absl::Status GuetzliTransaction::Init() { "Error receiving remote FD: remote output fd is set to -1"); } + in_fd_.OwnLocalFd(false); // FDCloser will close local fd + out_fd_.OwnLocalFd(false); // FDCloser will close local fd + return absl::OkStatus(); } - absl::Status GuetzliTransaction::ProcessPng(GuetzliAPi* api, - sapi::v::Struct* params, - sapi::v::LenVal* input, - sapi::v::LenVal* output) const { - sapi::v::Int xsize; - sapi::v::Int ysize; - sapi::v::LenVal rgb_in(0); - - auto read_result = api->ReadPng(input->PtrBefore(), xsize.PtrBoth(), - ysize.PtrBoth(), rgb_in.PtrBoth()); - - if (!read_result.value_or(false)) { - return absl::FailedPreconditionError( - "Error reading PNG data from input file" - ); - } - - double pixels = static_cast(xsize.GetValue()) * ysize.GetValue(); - if (params_.memlimit_mb != -1 - && (pixels * kBytesPerPixel / (1 << 20) > params_.memlimit_mb - || params_.memlimit_mb < kLowestMemusageMB)) { - return absl::FailedPreconditionError( - "Memory limit would be exceeded" - ); - } - - auto result = api->ProcessRGBData(params->PtrBefore(), params_.verbose, - rgb_in.PtrBefore(), xsize.GetValue(), - ysize.GetValue(), output->PtrBoth()); - if (!result.value_or(false)) { - return absl::FailedPreconditionError( - "Guetzli processing failed" - ); - } - - return absl::OkStatus(); - } - - absl::Status GuetzliTransaction::ProcessJpeg(GuetzliApi* api, - sapi::v::Struct* params, - sapi::v::LenVal* input, - sapi::v::LenVal* output) const { - ::sapi::v::Int xsize; - ::sapi::v::Int ysize; - auto read_result = api->ReadJpegData(input->PtrBefore(), 0, xsize.PtrBoth(), - ysize.PtrBoth()); - - if (!read_result.value_or(false)) { - return absl::FailedPreconditionError( - "Error reading JPG data from input file" - ); - } - - double pixels = static_cast(xsize.GetValue()) * ysize.GetValue(); - if (params_.memlimit_mb != -1 - && (pixels * kBytesPerPixel / (1 << 20) > params_.memlimit_mb - || params_.memlimit_mb < kLowestMemusageMB)) { - return absl::FailedPreconditionError( - "Memory limit would be exceeded" - ); - } - - auto result = api->ProcessJPEGString(params->PtrBefore(), params_.verbose, - input->PtrBefore(), output->PtrBoth()); - - if (!result.value_or(false)) { - return absl::FailedPreconditionError( - "Guetzli processing failed" - ); - } - - return absl::OkStatus(); - } - absl::Status GuetzliTransaction::Main() { GuetzliApi api(sandbox()); - - sapi::v::LenVal input(0); sapi::v::LenVal output(0); - sapi::v::Struct params; - - auto read_result = api.ReadDataFromFd(in_fd_.GetRemoteFd(), input.PtrBoth()); - if (!read_result.value_or(false)) { - return absl::FailedPreconditionError( - "Error reading data inside sandbox" - ); - } - - auto score_quality_result = api.ButteraugliScoreQuality(params_.quality); - - if (!score_quality_result.ok()) { - return absl::FailedPreconditionError( - "Error calculating butteraugli score" - ); - } - - params.mutable_data()->butteraugli_target = score_quality_result.value(); - - static const unsigned char kPNGMagicBytes[] = { - 0x89, 'P', 'N', 'G', '\r', '\n', 0x1a, '\n', + sapi::v::Struct processing_params; + *processing_params.mutable_data() = {in_fd_.GetRemoteFd(), + params_.verbose, + params_.quality, + params_.memlimit_mb }; + + auto result_status = image_type_ == ImageType::JPEG ? + api.ProcessJpeg(processing_params.PtrBefore(), output.PtrBoth()) : + api.ProcessRgb(processing_params.PtrBefore(), output.PtrBoth()); - if (input.GetDataSize() >= 8 && - memcmp(input.GetData(), kPNGMagicBytes, sizeof(kPNGMagicBytes)) == 0) { - auto process_status = ProcessPng(&api, ¶ms, &input, &output); - - if (!process_status.ok()) { - return process_status; - } - } else { - auto process_status = ProcessJpeg(&api, ¶ms, &input, &output); - - if (!process_status.ok()) { - return process_status; - } + if (!result_status.value_or(false)) { + std::stringstream error_stream; + error_stream << "Error processing " + << (image_type_ == ImageType::JPEG ? "jpeg" : "rgb") << " data" + << std::endl; + + return absl::FailedPreconditionError( + error_stream.str() + ); } auto write_result = api.WriteDataToFd(out_fd_.GetRemoteFd(), @@ -156,5 +94,27 @@ time_t GuetzliTransaction::CalculateTimeLimitFromImageSize( return (pixels / kMpixPixels + 5) * 60; } +sapi::StatusOr GuetzliTransaction::GetImageTypeFromFd(int fd) const { + static const unsigned char kPNGMagicBytes[] = { + 0x89, 'P', 'N', 'G', '\r', '\n', 0x1a, '\n', + }; + char read_buf[8]; + + if (read(fd, read_buf, 8) != 8) { + return absl::FailedPreconditionError( + "Error determining type of the input file" + ); + } + + if (lseek(fd, 0, SEEK_SET) != 0) { + return absl::FailedPreconditionError( + "Error returnig cursor to the beginning" + ); + } + + return memcmp(read_buf, kPNGMagicBytes, sizeof(kPNGMagicBytes)) == 0 ? + ImageType::PNG : ImageType::JPEG; +} + } // namespace sandbox } // namespace guetzli \ No newline at end of file diff --git a/oss-internship-2020/guetzli/guetzli_transaction.h b/oss-internship-2020/guetzli/guetzli_transaction.h index 5d6e83a..8dbd7ec 100644 --- a/oss-internship-2020/guetzli/guetzli_transaction.h +++ b/oss-internship-2020/guetzli/guetzli_transaction.h @@ -10,22 +10,24 @@ namespace guetzli { namespace sandbox { -constexpr int kDefaultTransactionRetryCount = 1; +constexpr int kDefaultTransactionRetryCount = 0; constexpr uint64_t kMpixPixels = 1'000'000; -constexpr int kBytesPerPixel = 350; -constexpr int kLowestMemusageMB = 100; // in MB - -struct TransactionParams { - int in_fd; - int out_fd; - int verbose; - int quality; - int memlimit_mb; +enum class ImageType { + JPEG, + PNG }; -//Add optional time limit/retry count as a constructors arguments -//Use differenet status errors +struct TransactionParams { + int in_fd = -1; + int out_fd = -1; + int verbose = 0; + int quality = 0; + int memlimit_mb = 0; +}; + +// Instance of this transaction shouldn't be reused +// Create a new one for each processing operation class GuetzliTransaction : public sapi::Transaction { public: GuetzliTransaction(TransactionParams&& params) @@ -34,7 +36,9 @@ class GuetzliTransaction : public sapi::Transaction { , in_fd_(params_.in_fd) , out_fd_(params_.out_fd) { + //TODO: Add retry count as a parameter sapi::Transaction::set_retry_count(kDefaultTransactionRetryCount); + //TODO: Try to use sandbox().set_wall_limit instead of infinite time limit sapi::Transaction::SetTimeLimit(0); } @@ -42,15 +46,7 @@ class GuetzliTransaction : public sapi::Transaction { absl::Status Init() override; absl::Status Main() final; - absl::Status ProcessPng(GuetzliApi* api, - sapi::v::Struct* params, - sapi::v::LenVal* input, - sapi::v::LenVal* output) const; - - absl::Status ProcessJpeg(GuetzliApi* api, - sapi::v::Struct* params, - sapi::v::LenVal* input, - sapi::v::LenVal* output) const; + sapi::StatusOr GetImageTypeFromFd(int fd) const; // As guetzli takes roughly 1 minute of CPU per 1 MPix we need to calculate // approximate time for transaction to complete @@ -59,158 +55,8 @@ class GuetzliTransaction : public sapi::Transaction { const TransactionParams params_; sapi::v::Fd in_fd_; sapi::v::Fd out_fd_; + ImageType image_type_ = ImageType::JPEG; }; -absl::Status GuetzliTransaction::Init() { - SAPI_RETURN_IF_ERROR(sandbox()->TransferToSandboxee(&in_fd_)); - SAPI_RETURN_IF_ERROR(sandbox()->TransferToSandboxee(&out_fd_)); - - if (in_fd_.GetRemoteFd() < 0) { - return absl::FailedPreconditionError( - "Error receiving remote FD: remote input fd is set to -1"); - } - if (out_fd_.GetRemoteFd() < 0) { - return absl::FailedPreconditionError( - "Error receiving remote FD: remote output fd is set to -1"); - } - - return absl::OkStatus(); -} - - absl::Status GuetzliTransaction::ProcessPng(GuetzliApi* api, - sapi::v::Struct* params, - sapi::v::LenVal* input, - sapi::v::LenVal* output) const { - sapi::v::Int xsize; - sapi::v::Int ysize; - sapi::v::LenVal rgb_in(0); - - auto read_result = api->ReadPng(input->PtrBefore(), xsize.PtrBoth(), - ysize.PtrBoth(), rgb_in.PtrBoth()); - - if (!read_result.value_or(false)) { - return absl::FailedPreconditionError( - "Error reading PNG data from input file" - ); - } - - double pixels = static_cast(xsize.GetValue()) * ysize.GetValue(); - if (params_.memlimit_mb != -1 - && (pixels * kBytesPerPixel / (1 << 20) > params_.memlimit_mb - || params_.memlimit_mb < kLowestMemusageMB)) { - return absl::FailedPreconditionError( - "Memory limit would be exceeded" - ); - } - - auto result = api->ProcessRGBData(params->PtrBefore(), params_.verbose, - rgb_in.PtrBefore(), xsize.GetValue(), - ysize.GetValue(), output->PtrBoth()); - if (!result.value_or(false)) { - return absl::FailedPreconditionError( - "Guetzli processing failed" - ); - } - - return absl::OkStatus(); - } - - absl::Status GuetzliTransaction::ProcessJpeg(GuetzliApi* api, - sapi::v::Struct* params, - sapi::v::LenVal* input, - sapi::v::LenVal* output) const { - sapi::v::Int xsize; - sapi::v::Int ysize; - auto read_result = api->ReadJpegData(input->PtrBefore(), 0, xsize.PtrBoth(), - ysize.PtrBoth()); - - if (!read_result.value_or(false)) { - return absl::FailedPreconditionError( - "Error reading JPG data from input file" - ); - } - - double pixels = static_cast(xsize.GetValue()) * ysize.GetValue(); - if (params_.memlimit_mb != -1 - && (pixels * kBytesPerPixel / (1 << 20) > params_.memlimit_mb - || params_.memlimit_mb < kLowestMemusageMB)) { - return absl::FailedPreconditionError( - "Memory limit would be exceeded" - ); - } - - auto result = api->ProcessJPEGString(params->PtrBefore(), params_.verbose, - input->PtrBefore(), output->PtrBoth()); - - if (!result.value_or(false)) { - return absl::FailedPreconditionError( - "Guetzli processing failed" - ); - } - - return absl::OkStatus(); - } - -absl::Status GuetzliTransaction::Main() { - GuetzliApi api(sandbox()); - - sapi::v::LenVal input(0); - sapi::v::LenVal output(0); - sapi::v::Struct params; - - auto read_result = api.ReadDataFromFd(in_fd_.GetRemoteFd(), input.PtrBoth()); - - if (!read_result.value_or(false)) { - return absl::FailedPreconditionError( - "Error reading data inside sandbox" - ); - } - - auto score_quality_result = api.ButteraugliScoreQuality(params_.quality); - - if (!score_quality_result.ok()) { - return absl::FailedPreconditionError( - "Error calculating butteraugli score" - ); - } - - params.mutable_data()->butteraugli_target = score_quality_result.value(); - - static const unsigned char kPNGMagicBytes[] = { - 0x89, 'P', 'N', 'G', '\r', '\n', 0x1a, '\n', - }; - - if (input.GetDataSize() >= 8 && - memcmp(input.GetData(), kPNGMagicBytes, sizeof(kPNGMagicBytes)) == 0) { - auto process_status = ProcessPng(&api, ¶ms, &input, &output); - - if (!process_status.ok()) { - return process_status; - } - } else { - auto process_status = ProcessJpeg(&api, ¶ms, &input, &output); - - if (!process_status.ok()) { - return process_status; - } - } - - auto write_result = api.WriteDataToFd(out_fd_.GetRemoteFd(), - output.PtrBefore()); - - if (!write_result.value_or(false)) { - return absl::FailedPreconditionError( - "Error writing file inside sandbox" - ); - } - - return absl::OkStatus(); -} - -time_t GuetzliTransaction::CalculateTimeLimitFromImageSize( - uint64_t pixels) const { - return (pixels / kMpixPixels + 5) * 60; -} - } // namespace sandbox } // namespace guetzli diff --git a/oss-internship-2020/guetzli/third_party/butteraugli/BUILD b/oss-internship-2020/guetzli/third_party/butteraugli/BUILD new file mode 100755 index 0000000..f553c0b --- /dev/null +++ b/oss-internship-2020/guetzli/third_party/butteraugli/BUILD @@ -0,0 +1,24 @@ +cc_library( + name = "butteraugli_lib", + srcs = [ + "butteraugli/butteraugli.cc", + "butteraugli/butteraugli.h", + ], + hdrs = [ + "butteraugli/butteraugli.h", + ], + copts = ["-Wno-sign-compare"], + visibility = ["//visibility:public"], +) + +cc_binary( + name = "butteraugli", + srcs = ["butteraugli/butteraugli_main.cc"], + copts = ["-Wno-sign-compare"], + visibility = ["//visibility:public"], + deps = [ + ":butteraugli_lib", + "@jpeg_archive//:jpeg", + "@png_archive//:png", + ], +) \ No newline at end of file diff --git a/oss-internship-2020/guetzli/third_party/butteraugli/LICENSE b/oss-internship-2020/guetzli/third_party/butteraugli/LICENSE new file mode 100755 index 0000000..261eeb9 --- /dev/null +++ b/oss-internship-2020/guetzli/third_party/butteraugli/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/oss-internship-2020/guetzli/third_party/butteraugli/README.md b/oss-internship-2020/guetzli/third_party/butteraugli/README.md new file mode 100755 index 0000000..4623442 --- /dev/null +++ b/oss-internship-2020/guetzli/third_party/butteraugli/README.md @@ -0,0 +1,68 @@ +# butteraugli + +> A tool for measuring perceived differences between images + +## Introduction + +Butteraugli is a project that estimates the psychovisual similarity of two +images. It gives a score for the images that is reliable in the domain of barely +noticeable differences. Butteraugli not only gives a scalar score, but also +computes a spatial map of the level of differences. + +One of the main motivations for this project is the statistical differences in +location and density of different color receptors, particularly the low density +of blue cones in the fovea. Another motivation comes from more accurate modeling +of ganglion cells, particularly the frequency space inhibition. + +## Use + +Butteraugli can work as a quality metric for lossy image and video compression. +On our small test corpus butteraugli performs better than our implementations of +the reference methods, psnrhsv-m, ssim, and our yuv-color-space variant of ssim. +One possible use is to define the quality level setting used in a jpeg +compressor, or to compare two or more compression methods at the same level of +psychovisual differences. + +Butteraugli is intended to be a research tool more than a practical tool for +choosing compression formats. We don't know how well butteraugli performs with +major deformations -- we have mostly tuned it within a small range of quality, +roughly corresponding to jpeg qualities 90 to 95. + +## Interface + +Only a C++ interface is provided. The interface takes two images and outputs a +map together with a scalar value defining the difference. The scalar value can +be compared to two reference values that divide the value space into three +experience classes: 'great', 'acceptable' and 'not acceptable'. + +## Build instructions + +Install [Bazel](http://bazel.build) by following the +[instructions](https://www.bazel.build/docs/install.html). Run `bazel build -c opt +//:butteraugli` in the directory that contains this README file to build the +[command-line utility](#cmdline-tool). If you want to use Butteraugli as a +library, depend on the `//:butteraugli_lib` target. + +Alternatively, you can use the Makefile provided in the `butteraugli` directory, +after ensuring that [libpng](http://www.libpng.org/) and +[libjpeg](http://ijg.org/) are installed. On some systems you might need to also +install corresponding `-dev` packages. + +The code is portable and also compiles on Windows after defining +`_CRT_SECURE_NO_WARNINGS` in the project settings. + +## Command-line utility {#cmdline-tool} + +Butteraugli, apart from the library, comes bundled with a comparison tool. The +comparison tool supports PNG and JPG images as inputs. To compare images, run: + +``` +butteraugli image1.{png|jpg} image2.{png|jpg} +``` + +The tool can also produce a heatmap of differences between images. The heatmap +will be output as a PNM image. To produce one, run: + +``` +butteraugli image1.{png|jpg} image2.{png|jpg} heatmap.pnm +``` diff --git a/oss-internship-2020/guetzli/third_party/butteraugli/WORKSPACE b/oss-internship-2020/guetzli/third_party/butteraugli/WORKSPACE new file mode 100755 index 0000000..4d6ed65 --- /dev/null +++ b/oss-internship-2020/guetzli/third_party/butteraugli/WORKSPACE @@ -0,0 +1,25 @@ +workspace(name = "butteraugli") + +new_http_archive( + name = "png_archive", + url = "http://github.com/glennrp/libpng/archive/v1.2.57.zip", + sha256 = "a941dc09ca00148fe7aaf4ecdd6a67579c293678ed1e1cf633b5ffc02f4f8cf7", + strip_prefix = "libpng-1.2.57", + build_file = "png.BUILD", +) + +new_http_archive( + name = "zlib_archive", + url = "http://zlib.net/fossils/zlib-1.2.10.tar.gz", + sha256 = "8d7e9f698ce48787b6e1c67e6bff79e487303e66077e25cb9784ac8835978017", + strip_prefix = "zlib-1.2.10", + build_file = "zlib.BUILD", +) + +new_http_archive( + name = "jpeg_archive", + url = "http://www.ijg.org/files/jpegsrc.v9b.tar.gz", + sha256 = "240fd398da741669bf3c90366f58452ea59041cacc741a489b99f2f6a0bad052", + strip_prefix = "jpeg-9b", + build_file = "jpeg.BUILD", +) diff --git a/oss-internship-2020/guetzli/third_party/butteraugli/butteraugli/Makefile b/oss-internship-2020/guetzli/third_party/butteraugli/butteraugli/Makefile new file mode 100755 index 0000000..76b3a9b --- /dev/null +++ b/oss-internship-2020/guetzli/third_party/butteraugli/butteraugli/Makefile @@ -0,0 +1,7 @@ +LDLIBS += -lpng -ljpeg +CXXFLAGS += -std=c++11 -I.. +LINK.o = $(LINK.cc) + +all: butteraugli.o butteraugli_main.o butteraugli + +butteraugli: butteraugli.o butteraugli_main.o diff --git a/oss-internship-2020/guetzli/third_party/butteraugli/butteraugli/butteraugli.cc b/oss-internship-2020/guetzli/third_party/butteraugli/butteraugli/butteraugli.cc new file mode 100755 index 0000000..77c91cc --- /dev/null +++ b/oss-internship-2020/guetzli/third_party/butteraugli/butteraugli/butteraugli.cc @@ -0,0 +1,1994 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// 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. +// +// Author: Jyrki Alakuijala (jyrki.alakuijala@gmail.com) +// +// The physical architecture of butteraugli is based on the following naming +// convention: +// * Opsin - dynamics of the photosensitive chemicals in the retina +// with their immediate electrical processing +// * Xyb - hybrid opponent/trichromatic color space +// x is roughly red-subtract-green. +// y is yellow. +// b is blue. +// Xyb values are computed from Opsin mixing, not directly from rgb. +// * Mask - for visual masking +// * Hf - color modeling for spatially high-frequency features +// * Lf - color modeling for spatially low-frequency features +// * Diffmap - to cluster and build an image of error between the images +// * Blur - to hold the smoothing code + +#include "butteraugli/butteraugli.h" + +#include +#include +#include +#include +#include + +#include +#include + + +// Restricted pointers speed up Convolution(); MSVC uses a different keyword. +#ifdef _MSC_VER +#define __restrict__ __restrict +#endif + +#ifndef PROFILER_ENABLED +#define PROFILER_ENABLED 0 +#endif +#if PROFILER_ENABLED +#else +#define PROFILER_FUNC +#define PROFILER_ZONE(name) +#endif + +namespace butteraugli { + +void *CacheAligned::Allocate(const size_t bytes) { + char *const allocated = static_cast(malloc(bytes + kCacheLineSize)); + if (allocated == nullptr) { + return nullptr; + } + const uintptr_t misalignment = + reinterpret_cast(allocated) & (kCacheLineSize - 1); + // malloc is at least kPointerSize aligned, so we can store the "allocated" + // pointer immediately before the aligned memory. + assert(misalignment % kPointerSize == 0); + char *const aligned = allocated + kCacheLineSize - misalignment; + memcpy(aligned - kPointerSize, &allocated, kPointerSize); + return BUTTERAUGLI_ASSUME_ALIGNED(aligned, 64); +} + +void CacheAligned::Free(void *aligned_pointer) { + if (aligned_pointer == nullptr) { + return; + } + char *const aligned = static_cast(aligned_pointer); + assert(reinterpret_cast(aligned) % kCacheLineSize == 0); + char *allocated; + memcpy(&allocated, aligned - kPointerSize, kPointerSize); + assert(allocated <= aligned - kPointerSize); + assert(allocated >= aligned - kCacheLineSize); + free(allocated); +} + +static inline bool IsNan(const float x) { + uint32_t bits; + memcpy(&bits, &x, sizeof(bits)); + const uint32_t bitmask_exp = 0x7F800000; + return (bits & bitmask_exp) == bitmask_exp && (bits & 0x7FFFFF); +} + +static inline bool IsNan(const double x) { + uint64_t bits; + memcpy(&bits, &x, sizeof(bits)); + return (0x7ff0000000000001ULL <= bits && bits <= 0x7fffffffffffffffULL) || + (0xfff0000000000001ULL <= bits && bits <= 0xffffffffffffffffULL); +} + +static inline void CheckImage(const ImageF &image, const char *name) { + for (size_t y = 0; y < image.ysize(); ++y) { + const float * const BUTTERAUGLI_RESTRICT row = image.Row(y); + for (size_t x = 0; x < image.xsize(); ++x) { + if (IsNan(row[x])) { + printf("Image %s @ %lu,%lu (of %lu,%lu)\n", name, x, y, image.xsize(), + image.ysize()); + exit(1); + } + } + } +} + +#if BUTTERAUGLI_ENABLE_CHECKS + +#define CHECK_NAN(x, str) \ + do { \ + if (IsNan(x)) { \ + printf("%d: %s\n", __LINE__, str); \ + abort(); \ + } \ + } while (0) + +#define CHECK_IMAGE(image, name) CheckImage(image, name) + +#else + +#define CHECK_NAN(x, str) +#define CHECK_IMAGE(image, name) + +#endif + + +// Purpose of kInternalGoodQualityThreshold: +// Normalize 'ok' image degradation to 1.0 across different versions of +// butteraugli. +static const double kInternalGoodQualityThreshold = 20.35; +static const double kGlobalScale = 1.0 / kInternalGoodQualityThreshold; + +inline float DotProduct(const float u[3], const float v[3]) { + return u[0] * v[0] + u[1] * v[1] + u[2] * v[2]; +} + +std::vector ComputeKernel(float sigma) { + const float m = 2.25; // Accuracy increases when m is increased. + const float scaler = -1.0 / (2 * sigma * sigma); + const int diff = std::max(1, m * fabs(sigma)); + std::vector kernel(2 * diff + 1); + for (int i = -diff; i <= diff; ++i) { + kernel[i + diff] = exp(scaler * i * i); + } + return kernel; +} + +void ConvolveBorderColumn( + const ImageF& in, + const std::vector& kernel, + const float weight_no_border, + const float border_ratio, + const size_t x, + float* const BUTTERAUGLI_RESTRICT row_out) { + const int offset = kernel.size() / 2; + int minx = x < offset ? 0 : x - offset; + int maxx = std::min(in.xsize() - 1, x + offset); + float weight = 0.0f; + for (int j = minx; j <= maxx; ++j) { + weight += kernel[j - x + offset]; + } + // Interpolate linearly between the no-border scaling and border scaling. + weight = (1.0f - border_ratio) * weight + border_ratio * weight_no_border; + float scale = 1.0f / weight; + for (size_t y = 0; y < in.ysize(); ++y) { + const float* const BUTTERAUGLI_RESTRICT row_in = in.Row(y); + float sum = 0.0f; + for (int j = minx; j <= maxx; ++j) { + sum += row_in[j] * kernel[j - x + offset]; + } + row_out[y] = sum * scale; + } +} + +// Computes a horizontal convolution and transposes the result. +ImageF Convolution(const ImageF& in, + const std::vector& kernel, + const float border_ratio) { + ImageF out(in.ysize(), in.xsize()); + const int len = kernel.size(); + const int offset = kernel.size() / 2; + float weight_no_border = 0.0f; + for (int j = 0; j < len; ++j) { + weight_no_border += kernel[j]; + } + float scale_no_border = 1.0f / weight_no_border; + const int border1 = in.xsize() <= offset ? in.xsize() : offset; + const int border2 = in.xsize() - offset; + std::vector scaled_kernel = kernel; + for (int i = 0; i < scaled_kernel.size(); ++i) { + scaled_kernel[i] *= scale_no_border; + } + // left border + for (int x = 0; x < border1; ++x) { + ConvolveBorderColumn(in, kernel, weight_no_border, border_ratio, x, + out.Row(x)); + } + // middle + for (size_t y = 0; y < in.ysize(); ++y) { + const float* const BUTTERAUGLI_RESTRICT row_in = in.Row(y); + for (int x = border1; x < border2; ++x) { + const int d = x - offset; + float* const BUTTERAUGLI_RESTRICT row_out = out.Row(x); + float sum = 0.0f; + for (int j = 0; j < len; ++j) { + sum += row_in[d + j] * scaled_kernel[j]; + } + row_out[y] = sum; + } + } + // right border + for (int x = border2; x < in.xsize(); ++x) { + ConvolveBorderColumn(in, kernel, weight_no_border, border_ratio, x, + out.Row(x)); + } + return out; +} + +// A blur somewhat similar to a 2D Gaussian blur. +// See: https://en.wikipedia.org/wiki/Gaussian_blur +ImageF Blur(const ImageF& in, float sigma, float border_ratio) { + std::vector kernel = ComputeKernel(sigma); + return Convolution(Convolution(in, kernel, border_ratio), + kernel, border_ratio); +} + +// Clamping linear interpolator. +inline double InterpolateClampNegative(const double *array, + int size, double ix) { + if (ix < 0) { + ix = 0; + } + int baseix = static_cast(ix); + double res; + if (baseix >= size - 1) { + res = array[size - 1]; + } else { + double mix = ix - baseix; + int nextix = baseix + 1; + res = array[baseix] + mix * (array[nextix] - array[baseix]); + } + return res; +} + +double GammaMinArg() { + double out0, out1, out2; + OpsinAbsorbance(0.0, 0.0, 0.0, &out0, &out1, &out2); + return std::min(out0, std::min(out1, out2)); +} + +double GammaMaxArg() { + double out0, out1, out2; + OpsinAbsorbance(255.0, 255.0, 255.0, &out0, &out1, &out2); + return std::max(out0, std::max(out1, out2)); +} + +double SimpleGamma(double v) { + static const double kGamma = 0.372322653176; + static const double limit = 37.8000499603; + double bright = v - limit; + if (bright >= 0) { + static const double mul = 0.0950819040934; + v -= bright * mul; + } + { + static const double limit2 = 74.6154406429; + double bright2 = v - limit2; + if (bright2 >= 0) { + static const double mul = 0.01; + v -= bright2 * mul; + } + } + { + static const double limit2 = 82.8505938033; + double bright2 = v - limit2; + if (bright2 >= 0) { + static const double mul = 0.0316722592629; + v -= bright2 * mul; + } + } + { + static const double limit2 = 92.8505938033; + double bright2 = v - limit2; + if (bright2 >= 0) { + static const double mul = 0.221249885752; + v -= bright2 * mul; + } + } + { + static const double limit2 = 102.8505938033; + double bright2 = v - limit2; + if (bright2 >= 0) { + static const double mul = 0.0402547853939; + v -= bright2 * mul; + } + } + { + static const double limit2 = 112.8505938033; + double bright2 = v - limit2; + if (bright2 >= 0) { + static const double mul = 0.021471798711500003; + v -= bright2 * mul; + } + } + static const double offset = 0.106544447664; + static const double scale = 10.7950943969; + double retval = scale * (offset + pow(v, kGamma)); + return retval; +} + +static inline double Gamma(double v) { + //return SimpleGamma(v); + return GammaPolynomial(v); +} + +std::vector OpsinDynamicsImage(const std::vector& rgb) { + PROFILER_FUNC; + std::vector xyb(3); + std::vector blurred(3); + const double kSigma = 1.2; + for (int i = 0; i < 3; ++i) { + xyb[i] = ImageF(rgb[i].xsize(), rgb[i].ysize()); + blurred[i] = Blur(rgb[i], kSigma, 0.0f); + } + for (size_t y = 0; y < rgb[0].ysize(); ++y) { + const float* const BUTTERAUGLI_RESTRICT row_r = rgb[0].Row(y); + const float* const BUTTERAUGLI_RESTRICT row_g = rgb[1].Row(y); + const float* const BUTTERAUGLI_RESTRICT row_b = rgb[2].Row(y); + const float* const BUTTERAUGLI_RESTRICT row_blurred_r = blurred[0].Row(y); + const float* const BUTTERAUGLI_RESTRICT row_blurred_g = blurred[1].Row(y); + const float* const BUTTERAUGLI_RESTRICT row_blurred_b = blurred[2].Row(y); + float* const BUTTERAUGLI_RESTRICT row_out_x = xyb[0].Row(y); + float* const BUTTERAUGLI_RESTRICT row_out_y = xyb[1].Row(y); + float* const BUTTERAUGLI_RESTRICT row_out_b = xyb[2].Row(y); + for (size_t x = 0; x < rgb[0].xsize(); ++x) { + float sensitivity[3]; + { + // Calculate sensitivity based on the smoothed image gamma derivative. + float pre_mixed0, pre_mixed1, pre_mixed2; + OpsinAbsorbance(row_blurred_r[x], row_blurred_g[x], row_blurred_b[x], + &pre_mixed0, &pre_mixed1, &pre_mixed2); + // TODO: use new polynomial to compute Gamma(x)/x derivative. + sensitivity[0] = Gamma(pre_mixed0) / pre_mixed0; + sensitivity[1] = Gamma(pre_mixed1) / pre_mixed1; + sensitivity[2] = Gamma(pre_mixed2) / pre_mixed2; + } + float cur_mixed0, cur_mixed1, cur_mixed2; + OpsinAbsorbance(row_r[x], row_g[x], row_b[x], + &cur_mixed0, &cur_mixed1, &cur_mixed2); + cur_mixed0 *= sensitivity[0]; + cur_mixed1 *= sensitivity[1]; + cur_mixed2 *= sensitivity[2]; + RgbToXyb(cur_mixed0, cur_mixed1, cur_mixed2, + &row_out_x[x], &row_out_y[x], &row_out_b[x]); + } + } + return xyb; +} + +// Make area around zero less important (remove it). +static BUTTERAUGLI_INLINE float RemoveRangeAroundZero(float w, float x) { + return x > w ? x - w : x < -w ? x + w : 0.0f; +} + +// Make area around zero more important (2x it until the limit). +static BUTTERAUGLI_INLINE float AmplifyRangeAroundZero(float w, float x) { + return x > w ? x + w : x < -w ? x - w : 2.0f * x; +} + +// XybLowFreqToVals converts from low-frequency XYB space to the 'vals' space. +// Vals space can be converted to L2-norm space (Euclidean and normalized) +// through visual masking. +template +BUTTERAUGLI_INLINE void XybLowFreqToVals(const V &x, const V &y, const V &b_arg, + V *BUTTERAUGLI_RESTRICT valx, + V *BUTTERAUGLI_RESTRICT valy, + V *BUTTERAUGLI_RESTRICT valb) { + static const double xmuli = 5.57547552483; + static const double ymuli = 1.20828034498; + static const double bmuli = 6.08319517575; + static const double y_to_b_muli = -0.628811683685; + + const V xmul(xmuli); + const V ymul(ymuli); + const V bmul(bmuli); + const V y_to_b_mul(y_to_b_muli); + const V b = b_arg + y_to_b_mul * y; + *valb = b * bmul; + *valx = x * xmul; + *valy = y * ymul; +} + +static ImageF SuppressInBrightAreas(size_t xsize, size_t ysize, + double mul, double mul2, double reg, + const ImageF& hf, + const ImageF& brightness) { + ImageF inew(xsize, ysize); + for (size_t y = 0; y < ysize; ++y) { + const float* const rowhf = hf.Row(y); + const float* const rowbr = brightness.Row(y); + float* const rownew = inew.Row(y); + for (size_t x = 0; x < xsize; ++x) { + float v = rowhf[x]; + float scaler = mul * reg / (reg + rowbr[x]); + rownew[x] = scaler * v; + } + } + return inew; +} + + +static float SuppressHfInBrightAreas(float hf, float brightness, + float mul, float reg) { + float scaler = mul * reg / (reg + brightness); + return scaler * hf; +} + +static float SuppressUhfInBrightAreas(float hf, float brightness, + float mul, float reg) { + float scaler = mul * reg / (reg + brightness); + return scaler * hf; +} + +static float MaximumClamp(float v, float maxval) { + static const double kMul = 0.688059627878; + if (v >= maxval) { + v -= maxval; + v *= kMul; + v += maxval; + } else if (v < -maxval) { + v += maxval; + v *= kMul; + v -= maxval; + } + return v; +} + +static ImageF MaximumClamping(size_t xsize, size_t ysize, const ImageF& ix, + double yw) { + static const double kMul = 0.688059627878; + ImageF inew(xsize, ysize); + for (size_t y = 0; y < ysize; ++y) { + const float* const rowx = ix.Row(y); + float* const rownew = inew.Row(y); + for (size_t x = 0; x < xsize; ++x) { + double v = rowx[x]; + if (v >= yw) { + v -= yw; + v *= kMul; + v += yw; + } else if (v < -yw) { + v += yw; + v *= kMul; + v -= yw; + } + rownew[x] = v; + } + } + return inew; +} + +static ImageF SuppressXByY(size_t xsize, size_t ysize, + const ImageF& ix, const ImageF& iy, + const double yw) { + static const double s = 0.745954517135; + ImageF inew(xsize, ysize); + for (size_t y = 0; y < ysize; ++y) { + const float* const rowx = ix.Row(y); + const float* const rowy = iy.Row(y); + float* const rownew = inew.Row(y); + for (size_t x = 0; x < xsize; ++x) { + const double xval = rowx[x]; + const double yval = rowy[x]; + const double scaler = s + (yw * (1.0 - s)) / (yw + yval * yval); + rownew[x] = scaler * xval; + } + } + return inew; +} + +static void SeparateFrequencies( + size_t xsize, size_t ysize, + const std::vector& xyb, + PsychoImage &ps) { + PROFILER_FUNC; + ps.lf.resize(3); // XYB + ps.mf.resize(3); // XYB + ps.hf.resize(2); // XY + ps.uhf.resize(2); // XY + // Extract lf ... + static const double kSigmaLf = 7.46953768697; + static const double kSigmaHf = 3.734768843485; + static const double kSigmaUhf = 1.8673844217425; + // At borders we move some more of the energy to the high frequency + // parts, because there can be unfortunate continuations in tiling + // background color etc. So we want to represent the borders with + // some more accuracy. + static double border_lf = -0.00457628248637; + static double border_mf = -0.271277366628; + static double border_hf = 0.147068973249; + for (int i = 0; i < 3; ++i) { + ps.lf[i] = Blur(xyb[i], kSigmaLf, border_lf); + // ... and keep everything else in mf. + ps.mf[i] = ImageF(xsize, ysize); + for (size_t y = 0; y < ysize; ++y) { + for (size_t x = 0; x < xsize; ++x) { + ps.mf[i].Row(y)[x] = xyb[i].Row(y)[x] - ps.lf[i].Row(y)[x]; + } + } + if (i == 2) { + ps.mf[i] = Blur(ps.mf[i], kSigmaHf, border_mf); + break; + } + // Divide mf into mf and hf. + ps.hf[i] = ImageF(xsize, ysize); + for (size_t y = 0; y < ysize; ++y) { + float* BUTTERAUGLI_RESTRICT const row_mf = ps.mf[i].Row(y); + float* BUTTERAUGLI_RESTRICT const row_hf = ps.hf[i].Row(y); + for (size_t x = 0; x < xsize; ++x) { + row_hf[x] = row_mf[x]; + } + } + ps.mf[i] = Blur(ps.mf[i], kSigmaHf, border_mf); + static const double w0 = 0.120079806822; + static const double w1 = 0.03430529365; + if (i == 0) { + for (size_t y = 0; y < ysize; ++y) { + float* BUTTERAUGLI_RESTRICT const row_mf = ps.mf[0].Row(y); + float* BUTTERAUGLI_RESTRICT const row_hf = ps.hf[0].Row(y); + for (size_t x = 0; x < xsize; ++x) { + row_hf[x] -= row_mf[x]; + row_mf[x] = RemoveRangeAroundZero(w0, row_mf[x]); + } + } + } else { + for (size_t y = 0; y < ysize; ++y) { + float* BUTTERAUGLI_RESTRICT const row_mf = ps.mf[1].Row(y); + float* BUTTERAUGLI_RESTRICT const row_hf = ps.hf[1].Row(y); + for (size_t x = 0; x < xsize; ++x) { + row_hf[x] -= row_mf[x]; + row_mf[x] = AmplifyRangeAroundZero(w1, row_mf[x]); + } + } + } + } + // Suppress red-green by intensity change in the high freq channels. + static const double suppress = 2.96534974403; + ps.hf[0] = SuppressXByY(xsize, ysize, ps.hf[0], ps.hf[1], suppress); + + for (int i = 0; i < 2; ++i) { + // Divide hf into hf and uhf. + ps.uhf[i] = ImageF(xsize, ysize); + for (size_t y = 0; y < ysize; ++y) { + float* BUTTERAUGLI_RESTRICT const row_uhf = ps.uhf[i].Row(y); + float* BUTTERAUGLI_RESTRICT const row_hf = ps.hf[i].Row(y); + for (size_t x = 0; x < xsize; ++x) { + row_uhf[x] = row_hf[x]; + } + } + ps.hf[i] = Blur(ps.hf[i], kSigmaUhf, border_hf); + static const double kRemoveHfRange = 0.0287615200377; + static const double kMaxclampHf = 78.8223237675; + static const double kMaxclampUhf = 5.8907152736; + static const float kMulSuppressHf = 1.10684769012; + static const float kMulRegHf = 0.478741530298; + static const float kRegHf = 2000 * kMulRegHf; + static const float kMulSuppressUhf = 1.76905001176; + static const float kMulRegUhf = 0.310148420674; + static const float kRegUhf = 2000 * kMulRegUhf; + + if (i == 0) { + for (size_t y = 0; y < ysize; ++y) { + float* BUTTERAUGLI_RESTRICT const row_uhf = ps.uhf[0].Row(y); + float* BUTTERAUGLI_RESTRICT const row_hf = ps.hf[0].Row(y); + for (size_t x = 0; x < xsize; ++x) { + row_uhf[x] -= row_hf[x]; + row_hf[x] = RemoveRangeAroundZero(kRemoveHfRange, row_hf[x]); + } + } + } else { + for (size_t y = 0; y < ysize; ++y) { + float* BUTTERAUGLI_RESTRICT const row_uhf = ps.uhf[1].Row(y); + float* BUTTERAUGLI_RESTRICT const row_hf = ps.hf[1].Row(y); + float* BUTTERAUGLI_RESTRICT const row_lf = ps.lf[1].Row(y); + for (size_t x = 0; x < xsize; ++x) { + row_uhf[x] -= row_hf[x]; + row_hf[x] = MaximumClamp(row_hf[x], kMaxclampHf); + row_uhf[x] = MaximumClamp(row_uhf[x], kMaxclampUhf); + row_uhf[x] = SuppressUhfInBrightAreas(row_uhf[x], row_lf[x], + kMulSuppressUhf, kRegUhf); + row_hf[x] = SuppressHfInBrightAreas(row_hf[x], row_lf[x], + kMulSuppressHf, kRegHf); + + } + } + } + } + // Modify range around zero code only concerns the high frequency + // planes and only the X and Y channels. + // Convert low freq xyb to vals space so that we can do a simple squared sum + // diff on the low frequencies later. + for (size_t y = 0; y < ysize; ++y) { + float* BUTTERAUGLI_RESTRICT const row_x = ps.lf[0].Row(y); + float* BUTTERAUGLI_RESTRICT const row_y = ps.lf[1].Row(y); + float* BUTTERAUGLI_RESTRICT const row_b = ps.lf[2].Row(y); + for (size_t x = 0; x < xsize; ++x) { + float valx, valy, valb; + XybLowFreqToVals(row_x[x], row_y[x], row_b[x], &valx, &valy, &valb); + row_x[x] = valx; + row_y[x] = valy; + row_b[x] = valb; + } + } +} + +static void SameNoiseLevels(const ImageF& i0, const ImageF& i1, + const double kSigma, + const double w, + const double maxclamp, + ImageF* BUTTERAUGLI_RESTRICT diffmap) { + ImageF blurred(i0.xsize(), i0.ysize()); + for (size_t y = 0; y < i0.ysize(); ++y) { + const float* BUTTERAUGLI_RESTRICT const row0 = i0.Row(y); + const float* BUTTERAUGLI_RESTRICT const row1 = i1.Row(y); + float* BUTTERAUGLI_RESTRICT const to = blurred.Row(y); + for (size_t x = 0; x < i0.xsize(); ++x) { + double v0 = fabs(row0[x]); + double v1 = fabs(row1[x]); + if (v0 > maxclamp) v0 = maxclamp; + if (v1 > maxclamp) v1 = maxclamp; + to[x] = v0 - v1; + } + + } + blurred = Blur(blurred, kSigma, 0.0); + for (size_t y = 0; y < i0.ysize(); ++y) { + const float* BUTTERAUGLI_RESTRICT const row = blurred.Row(y); + float* BUTTERAUGLI_RESTRICT const row_diff = diffmap->Row(y); + for (size_t x = 0; x < i0.xsize(); ++x) { + double diff = row[x]; + row_diff[x] += w * diff * diff; + } + } +} + +static void L2Diff(const ImageF& i0, const ImageF& i1, const double w, + ImageF* BUTTERAUGLI_RESTRICT diffmap) { + if (w == 0) { + return; + } + for (size_t y = 0; y < i0.ysize(); ++y) { + const float* BUTTERAUGLI_RESTRICT const row0 = i0.Row(y); + const float* BUTTERAUGLI_RESTRICT const row1 = i1.Row(y); + float* BUTTERAUGLI_RESTRICT const row_diff = diffmap->Row(y); + for (size_t x = 0; x < i0.xsize(); ++x) { + double diff = row0[x] - row1[x]; + row_diff[x] += w * diff * diff; + } + } +} + +// i0 is the original image. +// i1 is the deformed copy. +static void L2DiffAsymmetric(const ImageF& i0, const ImageF& i1, + double w_0gt1, + double w_0lt1, + ImageF* BUTTERAUGLI_RESTRICT diffmap) { + if (w_0gt1 == 0 && w_0lt1 == 0) { + return; + } + w_0gt1 *= 0.8; + w_0lt1 *= 0.8; + for (size_t y = 0; y < i0.ysize(); ++y) { + const float* BUTTERAUGLI_RESTRICT const row0 = i0.Row(y); + const float* BUTTERAUGLI_RESTRICT const row1 = i1.Row(y); + float* BUTTERAUGLI_RESTRICT const row_diff = diffmap->Row(y); + for (size_t x = 0; x < i0.xsize(); ++x) { + // Primary symmetric quadratic objective. + double diff = row0[x] - row1[x]; + row_diff[x] += w_0gt1 * diff * diff; + + // Secondary half-open quadratic objectives. + const double fabs0 = fabs(row0[x]); + const double too_small = 0.4 * fabs0; + const double too_big = 1.0 * fabs0; + + if (row0[x] < 0) { + if (row1[x] > -too_small) { + double v = row1[x] + too_small; + row_diff[x] += w_0lt1 * v * v; + } else if (row1[x] < -too_big) { + double v = -row1[x] - too_big; + row_diff[x] += w_0lt1 * v * v; + } + } else { + if (row1[x] < too_small) { + double v = too_small - row1[x]; + row_diff[x] += w_0lt1 * v * v; + } else if (row1[x] > too_big) { + double v = row1[x] - too_big; + row_diff[x] += w_0lt1 * v * v; + } + } + } + } +} + +// Making a cluster of local errors to be more impactful than +// just a single error. +ImageF CalculateDiffmap(const ImageF& diffmap_in) { + PROFILER_FUNC; + // Take square root. + ImageF diffmap(diffmap_in.xsize(), diffmap_in.ysize()); + static const float kInitialSlope = 100.0f; + for (size_t y = 0; y < diffmap.ysize(); ++y) { + const float* const BUTTERAUGLI_RESTRICT row_in = diffmap_in.Row(y); + float* const BUTTERAUGLI_RESTRICT row_out = diffmap.Row(y); + for (size_t x = 0; x < diffmap.xsize(); ++x) { + const float orig_val = row_in[x]; + // TODO(b/29974893): Until that is fixed do not call sqrt on very small + // numbers. + row_out[x] = (orig_val < (1.0f / (kInitialSlope * kInitialSlope)) + ? kInitialSlope * orig_val + : std::sqrt(orig_val)); + } + } + { + static const double kSigma = 1.72547472444; + static const double mul1 = 0.458794906198; + static const float scale = 1.0f / (1.0f + mul1); + static const double border_ratio = 1.0; // 2.01209066992; + ImageF blurred = Blur(diffmap, kSigma, border_ratio); + for (int y = 0; y < diffmap.ysize(); ++y) { + const float* const BUTTERAUGLI_RESTRICT row_blurred = blurred.Row(y); + float* const BUTTERAUGLI_RESTRICT row = diffmap.Row(y); + for (int x = 0; x < diffmap.xsize(); ++x) { + row[x] += mul1 * row_blurred[x]; + row[x] *= scale; + } + } + } + return diffmap; +} + +void MaskPsychoImage(const PsychoImage& pi0, const PsychoImage& pi1, + const size_t xsize, const size_t ysize, + std::vector* BUTTERAUGLI_RESTRICT mask, + std::vector* BUTTERAUGLI_RESTRICT mask_dc) { + std::vector mask_xyb0 = CreatePlanes(xsize, ysize, 3); + std::vector mask_xyb1 = CreatePlanes(xsize, ysize, 3); + static const double muls[4] = { + 0, + 1.64178305129, + 0.831081703362, + 3.23680933546, + }; + for (int i = 0; i < 2; ++i) { + double a = muls[2 * i]; + double b = muls[2 * i + 1]; + for (size_t y = 0; y < ysize; ++y) { + const float* const BUTTERAUGLI_RESTRICT row_hf0 = pi0.hf[i].Row(y); + const float* const BUTTERAUGLI_RESTRICT row_hf1 = pi1.hf[i].Row(y); + const float* const BUTTERAUGLI_RESTRICT row_uhf0 = pi0.uhf[i].Row(y); + const float* const BUTTERAUGLI_RESTRICT row_uhf1 = pi1.uhf[i].Row(y); + float* const BUTTERAUGLI_RESTRICT row0 = mask_xyb0[i].Row(y); + float* const BUTTERAUGLI_RESTRICT row1 = mask_xyb1[i].Row(y); + for (size_t x = 0; x < xsize; ++x) { + row0[x] = a * row_uhf0[x] + b * row_hf0[x]; + row1[x] = a * row_uhf1[x] + b * row_hf1[x]; + } + } + } + Mask(mask_xyb0, mask_xyb1, mask, mask_dc); +} + +ButteraugliComparator::ButteraugliComparator(const std::vector& rgb0) + : xsize_(rgb0[0].xsize()), + ysize_(rgb0[0].ysize()), + num_pixels_(xsize_ * ysize_) { + if (xsize_ < 8 || ysize_ < 8) return; + std::vector xyb0 = OpsinDynamicsImage(rgb0); + SeparateFrequencies(xsize_, ysize_, xyb0, pi0_); +} + +void ButteraugliComparator::Mask( + std::vector* BUTTERAUGLI_RESTRICT mask, + std::vector* BUTTERAUGLI_RESTRICT mask_dc) const { + MaskPsychoImage(pi0_, pi0_, xsize_, ysize_, mask, mask_dc); +} + +void ButteraugliComparator::Diffmap(const std::vector& rgb1, + ImageF &result) const { + PROFILER_FUNC; + if (xsize_ < 8 || ysize_ < 8) return; + DiffmapOpsinDynamicsImage(OpsinDynamicsImage(rgb1), result); +} + +void ButteraugliComparator::DiffmapOpsinDynamicsImage( + const std::vector& xyb1, + ImageF &result) const { + PROFILER_FUNC; + if (xsize_ < 8 || ysize_ < 8) return; + PsychoImage pi1; + SeparateFrequencies(xsize_, ysize_, xyb1, pi1); + result = ImageF(xsize_, ysize_); + DiffmapPsychoImage(pi1, result); +} + +void ButteraugliComparator::DiffmapPsychoImage(const PsychoImage& pi1, + ImageF& result) const { + PROFILER_FUNC; + const float hf_asymmetry_ = 0.8f; + if (xsize_ < 8 || ysize_ < 8) { + return; + } + std::vector block_diff_dc(3); + std::vector block_diff_ac(3); + for (int c = 0; c < 3; ++c) { + block_diff_dc[c] = ImageF(xsize_, ysize_, 0.0); + block_diff_ac[c] = ImageF(xsize_, ysize_, 0.0); + } + + static const double wUhfMalta = 5.1409625726; + static const double norm1Uhf = 58.5001247061; + MaltaDiffMap(pi0_.uhf[1], pi1.uhf[1], + wUhfMalta * hf_asymmetry_, + wUhfMalta / hf_asymmetry_, + norm1Uhf, + &block_diff_ac[1]); + + static const double wUhfMaltaX = 4.91743441556; + static const double norm1UhfX = 687196.39002; + MaltaDiffMap(pi0_.uhf[0], pi1.uhf[0], + wUhfMaltaX * hf_asymmetry_, + wUhfMaltaX / hf_asymmetry_, + norm1UhfX, + &block_diff_ac[0]); + + static const double wHfMalta = 153.671655716; + static const double norm1Hf = 83150785.9592; + MaltaDiffMapLF(pi0_.hf[1], pi1.hf[1], + wHfMalta * sqrt(hf_asymmetry_), + wHfMalta / sqrt(hf_asymmetry_), + norm1Hf, + &block_diff_ac[1]); + + static const double wHfMaltaX = 668.358918152; + static const double norm1HfX = 0.882954368025; + MaltaDiffMapLF(pi0_.hf[0], pi1.hf[0], + wHfMaltaX * sqrt(hf_asymmetry_), + wHfMaltaX / sqrt(hf_asymmetry_), + norm1HfX, + &block_diff_ac[0]); + + static const double wMfMalta = 6841.81248144; + static const double norm1Mf = 0.0135134962487; + MaltaDiffMapLF(pi0_.mf[1], pi1.mf[1], wMfMalta, wMfMalta, norm1Mf, + &block_diff_ac[1]); + + static const double wMfMaltaX = 813.901703816; + static const double norm1MfX = 16792.9322251; + MaltaDiffMapLF(pi0_.mf[0], pi1.mf[0], wMfMaltaX, wMfMaltaX, norm1MfX, + &block_diff_ac[0]); + + static const double wmul[9] = { + 0, + 32.4449876135, + 0, + 0, + 0, + 0, + 1.01370836411, + 0, + 1.74566011615, + }; + + static const double maxclamp = 85.7047444518; + static const double kSigmaHfX = 10.6666499623; + static const double w = 884.809801415; + SameNoiseLevels(pi0_.hf[1], pi1.hf[1], kSigmaHfX, w, maxclamp, + &block_diff_ac[1]); + + for (int c = 0; c < 3; ++c) { + if (c < 2) { + L2DiffAsymmetric(pi0_.hf[c], pi1.hf[c], + wmul[c] * hf_asymmetry_, + wmul[c] / hf_asymmetry_, + &block_diff_ac[c]); + } + L2Diff(pi0_.mf[c], pi1.mf[c], wmul[3 + c], &block_diff_ac[c]); + L2Diff(pi0_.lf[c], pi1.lf[c], wmul[6 + c], &block_diff_dc[c]); + } + + std::vector mask_xyb; + std::vector mask_xyb_dc; + MaskPsychoImage(pi0_, pi1, xsize_, ysize_, &mask_xyb, &mask_xyb_dc); + + result = CalculateDiffmap( + CombineChannels(mask_xyb, mask_xyb_dc, block_diff_dc, block_diff_ac)); +} + +// Allows PaddedMaltaUnit to call either function via overloading. +struct MaltaTagLF {}; +struct MaltaTag {}; + +static float MaltaUnit(MaltaTagLF, const float* BUTTERAUGLI_RESTRICT d, + const int xs) { + const int xs3 = 3 * xs; + float retval = 0; + { + // x grows, y constant + float sum = + d[-4] + + d[-2] + + d[0] + + d[2] + + d[4]; + retval += sum * sum; + } + { + // y grows, x constant + float sum = + d[-xs3 - xs] + + d[-xs - xs] + + d[0] + + d[xs + xs] + + d[xs3 + xs]; + retval += sum * sum; + } + { + // both grow + float sum = + d[-xs3 - 3] + + d[-xs - xs - 2] + + d[0] + + d[xs + xs + 2] + + d[xs3 + 3]; + retval += sum * sum; + } + { + // y grows, x shrinks + float sum = + d[-xs3 + 3] + + d[-xs - xs + 2] + + d[0] + + d[xs + xs - 2] + + d[xs3 - 3]; + retval += sum * sum; + } + { + // y grows -4 to 4, x shrinks 1 -> -1 + float sum = + d[-xs3 - xs + 1] + + d[-xs - xs + 1] + + d[0] + + d[xs + xs - 1] + + d[xs3 + xs - 1]; + retval += sum * sum; + } + { + // y grows -4 to 4, x grows -1 -> 1 + float sum = + d[-xs3 - xs - 1] + + d[-xs - xs - 1] + + d[0] + + d[xs + xs + 1] + + d[xs3 + xs + 1]; + retval += sum * sum; + } + { + // x grows -4 to 4, y grows -1 to 1 + float sum = + d[-4 - xs] + + d[-2 - xs] + + d[0] + + d[2 + xs] + + d[4 + xs]; + retval += sum * sum; + } + { + // x grows -4 to 4, y shrinks 1 to -1 + float sum = + d[-4 + xs] + + d[-2 + xs] + + d[0] + + d[2 - xs] + + d[4 - xs]; + retval += sum * sum; + } + { + /* 0_________ + 1__*______ + 2___*_____ + 3_________ + 4____0____ + 5_________ + 6_____*___ + 7______*__ + 8_________ */ + float sum = + d[-xs3 - 2] + + d[-xs - xs - 1] + + d[0] + + d[xs + xs + 1] + + d[xs3 + 2]; + retval += sum * sum; + } + { + /* 0_________ + 1______*__ + 2_____*___ + 3_________ + 4____0____ + 5_________ + 6___*_____ + 7__*______ + 8_________ */ + float sum = + d[-xs3 + 2] + + d[-xs - xs + 1] + + d[0] + + d[xs + xs - 1] + + d[xs3 - 2]; + retval += sum * sum; + } + { + /* 0_________ + 1_________ + 2_*_______ + 3__*______ + 4____0____ + 5______*__ + 6_______*_ + 7_________ + 8_________ */ + float sum = + d[-xs - xs - 3] + + d[-xs - 2] + + d[0] + + d[xs + 2] + + d[xs + xs + 3]; + retval += sum * sum; + } + { + /* 0_________ + 1_________ + 2_______*_ + 3______*__ + 4____0____ + 5__*______ + 6_*_______ + 7_________ + 8_________ */ + float sum = + d[-xs - xs + 3] + + d[-xs + 2] + + d[0] + + d[xs - 2] + + d[xs + xs - 3]; + retval += sum * sum; + } + { + /* 0_________ + 1_________ + 2________* + 3______*__ + 4____0____ + 5__*______ + 6*________ + 7_________ + 8_________ */ + + float sum = + d[xs + xs - 4] + + d[xs - 2] + + d[0] + + d[-xs + 2] + + d[-xs - xs + 4]; + retval += sum * sum; + } + { + /* 0_________ + 1_________ + 2*________ + 3__*______ + 4____0____ + 5______*__ + 6________* + 7_________ + 8_________ */ + float sum = + d[-xs - xs - 4] + + d[-xs - 2] + + d[0] + + d[xs + 2] + + d[xs + xs + 4]; + retval += sum * sum; + } + { + /* 0__*______ + 1_________ + 2___*_____ + 3_________ + 4____0____ + 5_________ + 6_____*___ + 7_________ + 8______*__ */ + float sum = + d[-xs3 - xs - 2] + + d[-xs - xs - 1] + + d[0] + + d[xs + xs + 1] + + d[xs3 + xs + 2]; + retval += sum * sum; + } + { + /* 0______*__ + 1_________ + 2_____*___ + 3_________ + 4____0____ + 5_________ + 6___*_____ + 7_________ + 8__*______ */ + float sum = + d[-xs3 - xs + 2] + + d[-xs - xs + 1] + + d[0] + + d[xs + xs - 1] + + d[xs3 + xs - 2]; + retval += sum * sum; + } + return retval; +} + +static float MaltaUnit(MaltaTag, const float* BUTTERAUGLI_RESTRICT d, + const int xs) { + const int xs3 = 3 * xs; + float retval = 0; + { + // x grows, y constant + float sum = + d[-4] + + d[-3] + + d[-2] + + d[-1] + + d[0] + + d[1] + + d[2] + + d[3] + + d[4]; + retval += sum * sum; + } + { + // y grows, x constant + float sum = + d[-xs3 - xs] + + d[-xs3] + + d[-xs - xs] + + d[-xs] + + d[0] + + d[xs] + + d[xs + xs] + + d[xs3] + + d[xs3 + xs]; + retval += sum * sum; + } + { + // both grow + float sum = + d[-xs3 - 3] + + d[-xs - xs - 2] + + d[-xs - 1] + + d[0] + + d[xs + 1] + + d[xs + xs + 2] + + d[xs3 + 3]; + retval += sum * sum; + } + { + // y grows, x shrinks + float sum = + d[-xs3 + 3] + + d[-xs - xs + 2] + + d[-xs + 1] + + d[0] + + d[xs - 1] + + d[xs + xs - 2] + + d[xs3 - 3]; + retval += sum * sum; + } + { + // y grows -4 to 4, x shrinks 1 -> -1 + float sum = + d[-xs3 - xs + 1] + + d[-xs3 + 1] + + d[-xs - xs + 1] + + d[-xs] + + d[0] + + d[xs] + + d[xs + xs - 1] + + d[xs3 - 1] + + d[xs3 + xs - 1]; + retval += sum * sum; + } + { + // y grows -4 to 4, x grows -1 -> 1 + float sum = + d[-xs3 - xs - 1] + + d[-xs3 - 1] + + d[-xs - xs - 1] + + d[-xs] + + d[0] + + d[xs] + + d[xs + xs + 1] + + d[xs3 + 1] + + d[xs3 + xs + 1]; + retval += sum * sum; + } + { + // x grows -4 to 4, y grows -1 to 1 + float sum = + d[-4 - xs] + + d[-3 - xs] + + d[-2 - xs] + + d[-1] + + d[0] + + d[1] + + d[2 + xs] + + d[3 + xs] + + d[4 + xs]; + retval += sum * sum; + } + { + // x grows -4 to 4, y shrinks 1 to -1 + float sum = + d[-4 + xs] + + d[-3 + xs] + + d[-2 + xs] + + d[-1] + + d[0] + + d[1] + + d[2 - xs] + + d[3 - xs] + + d[4 - xs]; + retval += sum * sum; + } + { + /* 0_________ + 1__*______ + 2___*_____ + 3___*_____ + 4____0____ + 5_____*___ + 6_____*___ + 7______*__ + 8_________ */ + float sum = + d[-xs3 - 2] + + d[-xs - xs - 1] + + d[-xs - 1] + + d[0] + + d[xs + 1] + + d[xs + xs + 1] + + d[xs3 + 2]; + retval += sum * sum; + } + { + /* 0_________ + 1______*__ + 2_____*___ + 3_____*___ + 4____0____ + 5___*_____ + 6___*_____ + 7__*______ + 8_________ */ + float sum = + d[-xs3 + 2] + + d[-xs - xs + 1] + + d[-xs + 1] + + d[0] + + d[xs - 1] + + d[xs + xs - 1] + + d[xs3 - 2]; + retval += sum * sum; + } + { + /* 0_________ + 1_________ + 2_*_______ + 3__**_____ + 4____0____ + 5_____**__ + 6_______*_ + 7_________ + 8_________ */ + float sum = + d[-xs - xs - 3] + + d[-xs - 2] + + d[-xs - 1] + + d[0] + + d[xs + 1] + + d[xs + 2] + + d[xs + xs + 3]; + retval += sum * sum; + } + { + /* 0_________ + 1_________ + 2_______*_ + 3_____**__ + 4____0____ + 5__**_____ + 6_*_______ + 7_________ + 8_________ */ + float sum = + d[-xs - xs + 3] + + d[-xs + 2] + + d[-xs + 1] + + d[0] + + d[xs - 1] + + d[xs - 2] + + d[xs + xs - 3]; + retval += sum * sum; + } + { + /* 0_________ + 1_________ + 2_________ + 3______**_ + 4____0*___ + 5__**_____ + 6**_______ + 7_________ + 8_________ */ + + float sum = + d[xs + xs - 4] + + d[xs + xs - 3] + + d[xs - 2] + + d[xs - 1] + + d[0] + + d[1] + + d[-xs + 2] + + d[-xs + 3]; + retval += sum * sum; + } + { + /* 0_________ + 1_________ + 2**_______ + 3__**_____ + 4____0*___ + 5______**_ + 6_________ + 7_________ + 8_________ */ + float sum = + d[-xs - xs - 4] + + d[-xs - xs - 3] + + d[-xs - 2] + + d[-xs - 1] + + d[0] + + d[1] + + d[xs + 2] + + d[xs + 3]; + retval += sum * sum; + } + { + /* 0__*______ + 1__*______ + 2___*_____ + 3___*_____ + 4____0____ + 5____*____ + 6_____*___ + 7_____*___ + 8_________ */ + float sum = + d[-xs3 - xs - 2] + + d[-xs3 - 2] + + d[-xs - xs - 1] + + d[-xs - 1] + + d[0] + + d[xs] + + d[xs + xs + 1] + + d[xs3 + 1]; + retval += sum * sum; + } + { + /* 0______*__ + 1______*__ + 2_____*___ + 3_____*___ + 4____0____ + 5____*____ + 6___*_____ + 7___*_____ + 8_________ */ + float sum = + d[-xs3 - xs + 2] + + d[-xs3 + 2] + + d[-xs - xs + 1] + + d[-xs + 1] + + d[0] + + d[xs] + + d[xs + xs - 1] + + d[xs3 - 1]; + retval += sum * sum; + } + return retval; +} + +// Returns MaltaUnit. "fastMode" avoids bounds-checks when x0 and y0 are known +// to be far enough from the image borders. +template +static BUTTERAUGLI_INLINE float PaddedMaltaUnit( + float* const BUTTERAUGLI_RESTRICT diffs, const size_t x0, const size_t y0, + const size_t xsize_, const size_t ysize_) { + int ix0 = y0 * xsize_ + x0; + const float* BUTTERAUGLI_RESTRICT d = &diffs[ix0]; + if (fastMode || + (x0 >= 4 && y0 >= 4 && x0 < (xsize_ - 4) && y0 < (ysize_ - 4))) { + return MaltaUnit(Tag(), d, xsize_); + } + + float borderimage[9 * 9]; + for (int dy = 0; dy < 9; ++dy) { + int y = y0 + dy - 4; + if (y < 0 || y >= ysize_) { + for (int dx = 0; dx < 9; ++dx) { + borderimage[dy * 9 + dx] = 0.0f; + } + } else { + for (int dx = 0; dx < 9; ++dx) { + int x = x0 + dx - 4; + if (x < 0 || x >= xsize_) { + borderimage[dy * 9 + dx] = 0.0f; + } else { + borderimage[dy * 9 + dx] = diffs[y * xsize_ + x]; + } + } + } + } + return MaltaUnit(Tag(), &borderimage[4 * 9 + 4], 9); +} + +template +static void MaltaDiffMapImpl(const ImageF& lum0, const ImageF& lum1, + const size_t xsize_, const size_t ysize_, + const double w_0gt1, + const double w_0lt1, + double norm1, + const double len, const double mulli, + ImageF* block_diff_ac) { + const float kWeight0 = 0.5; + const float kWeight1 = 0.33; + + const double w_pre0gt1 = mulli * sqrt(kWeight0 * w_0gt1) / (len * 2 + 1); + const double w_pre0lt1 = mulli * sqrt(kWeight1 * w_0lt1) / (len * 2 + 1); + const float norm2_0gt1 = w_pre0gt1 * norm1; + const float norm2_0lt1 = w_pre0lt1 * norm1; + + std::vector diffs(ysize_ * xsize_); + for (size_t y = 0, ix = 0; y < ysize_; ++y) { + const float* BUTTERAUGLI_RESTRICT const row0 = lum0.Row(y); + const float* BUTTERAUGLI_RESTRICT const row1 = lum1.Row(y); + for (size_t x = 0; x < xsize_; ++x, ++ix) { + const float absval = 0.5 * std::abs(row0[x]) + 0.5 * std::abs(row1[x]); + const float diff = row0[x] - row1[x]; + const float scaler = norm2_0gt1 / (static_cast(norm1) + absval); + + // Primary symmetric quadratic objective. + diffs[ix] = scaler * diff; + + const float scaler2 = norm2_0lt1 / (static_cast(norm1) + absval); + const double fabs0 = fabs(row0[x]); + + // Secondary half-open quadratic objectives. + const double too_small = 0.55 * fabs0; + const double too_big = 1.05 * fabs0; + + if (row0[x] < 0) { + if (row1[x] > -too_small) { + double impact = scaler2 * (row1[x] + too_small); + if (diff < 0) { + diffs[ix] -= impact; + } else { + diffs[ix] += impact; + } + } else if (row1[x] < -too_big) { + double impact = scaler2 * (-row1[x] - too_big); + if (diff < 0) { + diffs[ix] -= impact; + } else { + diffs[ix] += impact; + } + } + } else { + if (row1[x] < too_small) { + double impact = scaler2 * (too_small - row1[x]); + if (diff < 0) { + diffs[ix] -= impact; + } else { + diffs[ix] += impact; + } + } else if (row1[x] > too_big) { + double impact = scaler2 * (row1[x] - too_big); + if (diff < 0) { + diffs[ix] -= impact; + } else { + diffs[ix] += impact; + } + } + } + } + } + + size_t y0 = 0; + // Top + for (; y0 < 4; ++y0) { + float* const BUTTERAUGLI_RESTRICT row_diff = block_diff_ac->Row(y0); + for (size_t x0 = 0; x0 < xsize_; ++x0) { + row_diff[x0] += + PaddedMaltaUnit(&diffs[0], x0, y0, xsize_, ysize_); + } + } + + // Middle + for (; y0 < ysize_ - 4; ++y0) { + float* const BUTTERAUGLI_RESTRICT row_diff = block_diff_ac->Row(y0); + size_t x0 = 0; + for (; x0 < 4; ++x0) { + row_diff[x0] += + PaddedMaltaUnit(&diffs[0], x0, y0, xsize_, ysize_); + } + for (; x0 < xsize_ - 4; ++x0) { + row_diff[x0] += + PaddedMaltaUnit(&diffs[0], x0, y0, xsize_, ysize_); + } + + for (; x0 < xsize_; ++x0) { + row_diff[x0] += + PaddedMaltaUnit(&diffs[0], x0, y0, xsize_, ysize_); + } + } + + // Bottom + for (; y0 < ysize_; ++y0) { + float* const BUTTERAUGLI_RESTRICT row_diff = block_diff_ac->Row(y0); + for (size_t x0 = 0; x0 < xsize_; ++x0) { + row_diff[x0] += + PaddedMaltaUnit(&diffs[0], x0, y0, xsize_, ysize_); + } + } +} + +void ButteraugliComparator::MaltaDiffMap( + const ImageF& lum0, const ImageF& lum1, + const double w_0gt1, + const double w_0lt1, + const double norm1, ImageF* BUTTERAUGLI_RESTRICT block_diff_ac) const { + PROFILER_FUNC; + const double len = 3.75; + static const double mulli = 0.354191303559; + MaltaDiffMapImpl(lum0, lum1, xsize_, ysize_, w_0gt1, w_0lt1, + norm1, len, + mulli, block_diff_ac); +} + +void ButteraugliComparator::MaltaDiffMapLF( + const ImageF& lum0, const ImageF& lum1, + const double w_0gt1, + const double w_0lt1, + const double norm1, ImageF* BUTTERAUGLI_RESTRICT block_diff_ac) const { + PROFILER_FUNC; + const double len = 3.75; + static const double mulli = 0.405371989604; + MaltaDiffMapImpl(lum0, lum1, xsize_, ysize_, + w_0gt1, w_0lt1, + norm1, len, + mulli, block_diff_ac); +} + +ImageF ButteraugliComparator::CombineChannels( + const std::vector& mask_xyb, + const std::vector& mask_xyb_dc, + const std::vector& block_diff_dc, + const std::vector& block_diff_ac) const { + PROFILER_FUNC; + ImageF result(xsize_, ysize_); + for (size_t y = 0; y < ysize_; ++y) { + float* const BUTTERAUGLI_RESTRICT row_out = result.Row(y); + for (size_t x = 0; x < xsize_; ++x) { + float mask[3]; + float dc_mask[3]; + float diff_dc[3]; + float diff_ac[3]; + for (int i = 0; i < 3; ++i) { + mask[i] = mask_xyb[i].Row(y)[x]; + dc_mask[i] = mask_xyb_dc[i].Row(y)[x]; + diff_dc[i] = block_diff_dc[i].Row(y)[x]; + diff_ac[i] = block_diff_ac[i].Row(y)[x]; + } + row_out[x] = (DotProduct(diff_dc, dc_mask) + DotProduct(diff_ac, mask)); + } + } + return result; +} + +double ButteraugliScoreFromDiffmap(const ImageF& diffmap) { + PROFILER_FUNC; + float retval = 0.0f; + for (size_t y = 0; y < diffmap.ysize(); ++y) { + const float * const BUTTERAUGLI_RESTRICT row = diffmap.Row(y); + for (size_t x = 0; x < diffmap.xsize(); ++x) { + retval = std::max(retval, row[x]); + } + } + return retval; +} + +#include + +// ===== Functions used by Mask only ===== +static std::array MakeMask( + double extmul, double extoff, + double mul, double offset, + double scaler) { + std::array lut; + for (int i = 0; i < lut.size(); ++i) { + const double c = mul / ((0.01 * scaler * i) + offset); + lut[i] = kGlobalScale * (1.0 + extmul * (c + extoff)); + if (lut[i] < 1e-5) { + lut[i] = 1e-5; + } + assert(lut[i] >= 0.0); + lut[i] *= lut[i]; + } + return lut; +} + +double MaskX(double delta) { + static const double extmul = 2.59885507073; + static const double extoff = 3.08805636789; + static const double offset = 0.315424196682; + static const double scaler = 16.2770141832; + static const double mul = 5.62939030582; + static const std::array lut = + MakeMask(extmul, extoff, mul, offset, scaler); + return InterpolateClampNegative(lut.data(), lut.size(), delta); +} + +double MaskY(double delta) { + static const double extmul = 0.9613705131; + static const double extoff = -0.581933100068; + static const double offset = 1.00846207765; + static const double scaler = 2.2342321176; + static const double mul = 6.64307621174; + static const std::array lut = + MakeMask(extmul, extoff, mul, offset, scaler); + return InterpolateClampNegative(lut.data(), lut.size(), delta); +} + +double MaskDcX(double delta) { + static const double extmul = 10.0470705878; + static const double extoff = 3.18472654033; + static const double offset = 0.0551512255218; + static const double scaler = 70.0; + static const double mul = 0.373092999662; + static const std::array lut = + MakeMask(extmul, extoff, mul, offset, scaler); + return InterpolateClampNegative(lut.data(), lut.size(), delta); +} + +double MaskDcY(double delta) { + static const double extmul = 0.0115640939227; + static const double extoff = 45.9483175519; + static const double offset = 0.0142290066313; + static const double scaler = 5.0; + static const double mul = 2.52611324247; + static const std::array lut = + MakeMask(extmul, extoff, mul, offset, scaler); + return InterpolateClampNegative(lut.data(), lut.size(), delta); +} + +ImageF DiffPrecompute(const ImageF& xyb0, const ImageF& xyb1) { + PROFILER_FUNC; + const size_t xsize = xyb0.xsize(); + const size_t ysize = xyb0.ysize(); + ImageF result(xsize, ysize); + size_t x2, y2; + for (size_t y = 0; y < ysize; ++y) { + if (y + 1 < ysize) { + y2 = y + 1; + } else if (y > 0) { + y2 = y - 1; + } else { + y2 = y; + } + const float* const BUTTERAUGLI_RESTRICT row0_in = xyb0.Row(y); + const float* const BUTTERAUGLI_RESTRICT row1_in = xyb1.Row(y); + const float* const BUTTERAUGLI_RESTRICT row0_in2 = xyb0.Row(y2); + const float* const BUTTERAUGLI_RESTRICT row1_in2 = xyb1.Row(y2); + float* const BUTTERAUGLI_RESTRICT row_out = result.Row(y); + for (size_t x = 0; x < xsize; ++x) { + if (x + 1 < xsize) { + x2 = x + 1; + } else if (x > 0) { + x2 = x - 1; + } else { + x2 = x; + } + double sup0 = (fabs(row0_in[x] - row0_in[x2]) + + fabs(row0_in[x] - row0_in2[x])); + double sup1 = (fabs(row1_in[x] - row1_in[x2]) + + fabs(row1_in[x] - row1_in2[x])); + static const double mul0 = 0.918416534734; + row_out[x] = mul0 * std::min(sup0, sup1); + static const double cutoff = 55.0184555849; + if (row_out[x] >= cutoff) { + row_out[x] = cutoff; + } + } + } + return result; +} + +void Mask(const std::vector& xyb0, + const std::vector& xyb1, + std::vector* BUTTERAUGLI_RESTRICT mask, + std::vector* BUTTERAUGLI_RESTRICT mask_dc) { + PROFILER_FUNC; + const size_t xsize = xyb0[0].xsize(); + const size_t ysize = xyb0[0].ysize(); + mask->resize(3); + *mask_dc = CreatePlanes(xsize, ysize, 3); + double muls[2] = { + 0.207017089891, + 0.267138152891, + }; + double normalizer = { + 1.0 / (muls[0] + muls[1]), + }; + static const double r0 = 2.3770330432; + static const double r1 = 9.04353323561; + static const double r2 = 9.24456601467; + static const double border_ratio = -0.0724948220913; + + { + // X component + ImageF diff = DiffPrecompute(xyb0[0], xyb1[0]); + ImageF blurred = Blur(diff, r2, border_ratio); + (*mask)[0] = ImageF(xsize, ysize); + for (size_t y = 0; y < ysize; ++y) { + for (size_t x = 0; x < xsize; ++x) { + (*mask)[0].Row(y)[x] = blurred.Row(y)[x]; + } + } + } + { + // Y component + (*mask)[1] = ImageF(xsize, ysize); + ImageF diff = DiffPrecompute(xyb0[1], xyb1[1]); + ImageF blurred1 = Blur(diff, r0, border_ratio); + ImageF blurred2 = Blur(diff, r1, border_ratio); + for (size_t y = 0; y < ysize; ++y) { + for (size_t x = 0; x < xsize; ++x) { + const double val = normalizer * ( + muls[0] * blurred1.Row(y)[x] + + muls[1] * blurred2.Row(y)[x]); + (*mask)[1].Row(y)[x] = val; + } + } + } + // B component + (*mask)[2] = ImageF(xsize, ysize); + static const double mul[2] = { + 16.6963293877, + 2.1364621982, + }; + static const double w00 = 36.4671237619; + static const double w11 = 2.1887170895; + static const double w_ytob_hf = std::max( + 0.086624184478, + 0.0); + static const double w_ytob_lf = 21.6804277046; + static const double p1_to_p0 = 0.0513061271723; + + for (size_t y = 0; y < ysize; ++y) { + for (size_t x = 0; x < xsize; ++x) { + const double s0 = (*mask)[0].Row(y)[x]; + const double s1 = (*mask)[1].Row(y)[x]; + const double p1 = mul[1] * w11 * s1; + const double p0 = mul[0] * w00 * s0 + p1_to_p0 * p1; + + (*mask)[0].Row(y)[x] = MaskX(p0); + (*mask)[1].Row(y)[x] = MaskY(p1); + (*mask)[2].Row(y)[x] = w_ytob_hf * MaskY(p1); + (*mask_dc)[0].Row(y)[x] = MaskDcX(p0); + (*mask_dc)[1].Row(y)[x] = MaskDcY(p1); + (*mask_dc)[2].Row(y)[x] = w_ytob_lf * MaskDcY(p1); + } + } +} + +void ButteraugliDiffmap(const std::vector &rgb0_image, + const std::vector &rgb1_image, + ImageF &result_image) { + const size_t xsize = rgb0_image[0].xsize(); + const size_t ysize = rgb0_image[0].ysize(); + static const int kMax = 8; + if (xsize < kMax || ysize < kMax) { + // Butteraugli values for small (where xsize or ysize is smaller + // than 8 pixels) images are non-sensical, but most likely it is + // less disruptive to try to compute something than just give up. + // Temporarily extend the borders of the image to fit 8 x 8 size. + int xborder = xsize < kMax ? (kMax - xsize) / 2 : 0; + int yborder = ysize < kMax ? (kMax - ysize) / 2 : 0; + size_t xscaled = std::max(kMax, xsize); + size_t yscaled = std::max(kMax, ysize); + std::vector scaled0 = CreatePlanes(xscaled, yscaled, 3); + std::vector scaled1 = CreatePlanes(xscaled, yscaled, 3); + for (int i = 0; i < 3; ++i) { + for (int y = 0; y < yscaled; ++y) { + for (int x = 0; x < xscaled; ++x) { + size_t x2 = std::min(xsize - 1, std::max(0, x - xborder)); + size_t y2 = std::min(ysize - 1, std::max(0, y - yborder)); + scaled0[i].Row(y)[x] = rgb0_image[i].Row(y2)[x2]; + scaled1[i].Row(y)[x] = rgb1_image[i].Row(y2)[x2]; + } + } + } + ImageF diffmap_scaled; + ButteraugliDiffmap(scaled0, scaled1, diffmap_scaled); + result_image = ImageF(xsize, ysize); + for (int y = 0; y < ysize; ++y) { + for (int x = 0; x < xsize; ++x) { + result_image.Row(y)[x] = diffmap_scaled.Row(y + yborder)[x + xborder]; + } + } + return; + } + ButteraugliComparator butteraugli(rgb0_image); + butteraugli.Diffmap(rgb1_image, result_image); +} + +bool ButteraugliInterface(const std::vector &rgb0, + const std::vector &rgb1, + ImageF &diffmap, + double &diffvalue) { + const size_t xsize = rgb0[0].xsize(); + const size_t ysize = rgb0[0].ysize(); + if (xsize < 1 || ysize < 1) { + return false; // No image. + } + for (int i = 1; i < 3; i++) { + if (rgb0[i].xsize() != xsize || rgb0[i].ysize() != ysize || + rgb1[i].xsize() != xsize || rgb1[i].ysize() != ysize) { + return false; // Image planes must have same dimensions. + } + } + ButteraugliDiffmap(rgb0, rgb1, diffmap); + diffvalue = ButteraugliScoreFromDiffmap(diffmap); + return true; +} + +bool ButteraugliAdaptiveQuantization(size_t xsize, size_t ysize, + const std::vector > &rgb, std::vector &quant) { + if (xsize < 16 || ysize < 16) { + return false; // Butteraugli is undefined for small images. + } + size_t size = xsize * ysize; + + std::vector rgb_planes = PlanesFromPacked(xsize, ysize, rgb); + std::vector scale_xyb; + std::vector scale_xyb_dc; + Mask(rgb_planes, rgb_planes, &scale_xyb, &scale_xyb_dc); + quant.reserve(size); + + // Mask gives us values in 3 color channels, but for now we take only + // the intensity channel. + for (size_t y = 0; y < ysize; ++y) { + for (size_t x = 0; x < xsize; ++x) { + quant.push_back(scale_xyb[1].Row(y)[x]); + } + } + return true; +} + +double ButteraugliFuzzyClass(double score) { + static const double fuzzy_width_up = 6.07887388532; + static const double fuzzy_width_down = 5.50793514384; + static const double m0 = 2.0; + static const double scaler = 0.840253347958; + double val; + if (score < 1.0) { + // val in [scaler .. 2.0] + val = m0 / (1.0 + exp((score - 1.0) * fuzzy_width_down)); + val -= 1.0; // from [1 .. 2] to [0 .. 1] + val *= 2.0 - scaler; // from [0 .. 1] to [0 .. 2.0 - scaler] + val += scaler; // from [0 .. 2.0 - scaler] to [scaler .. 2.0] + } else { + // val in [0 .. scaler] + val = m0 / (1.0 + exp((score - 1.0) * fuzzy_width_up)); + val *= scaler; + } + return val; +} + +double ButteraugliFuzzyInverse(double seek) { + double pos = 0; + for (double range = 1.0; range >= 1e-10; range *= 0.5) { + double cur = ButteraugliFuzzyClass(pos); + if (cur < seek) { + pos -= range; + } else { + pos += range; + } + } + return pos; +} + +namespace { + +void ScoreToRgb(double score, double good_threshold, double bad_threshold, + uint8_t rgb[3]) { + double heatmap[12][3] = { + {0, 0, 0}, + {0, 0, 1}, + {0, 1, 1}, + {0, 1, 0}, // Good level + {1, 1, 0}, + {1, 0, 0}, // Bad level + {1, 0, 1}, + {0.5, 0.5, 1.0}, + {1.0, 0.5, 0.5}, // Pastel colors for the very bad quality range. + {1.0, 1.0, 0.5}, + { + 1, 1, 1, + }, + { + 1, 1, 1, + }, + }; + if (score < good_threshold) { + score = (score / good_threshold) * 0.3; + } else if (score < bad_threshold) { + score = 0.3 + + (score - good_threshold) / (bad_threshold - good_threshold) * 0.15; + } else { + score = 0.45 + (score - bad_threshold) / (bad_threshold * 12) * 0.5; + } + static const int kTableSize = sizeof(heatmap) / sizeof(heatmap[0]); + score = std::min(std::max(score * (kTableSize - 1), 0.0), + kTableSize - 2); + int ix = static_cast(score); + double mix = score - ix; + for (int i = 0; i < 3; ++i) { + double v = mix * heatmap[ix + 1][i] + (1 - mix) * heatmap[ix][i]; + rgb[i] = static_cast(255 * pow(v, 0.5) + 0.5); + } +} + +} // namespace + +void CreateHeatMapImage(const std::vector& distmap, + double good_threshold, double bad_threshold, + size_t xsize, size_t ysize, + std::vector* heatmap) { + heatmap->resize(3 * xsize * ysize); + for (size_t y = 0; y < ysize; ++y) { + for (size_t x = 0; x < xsize; ++x) { + int px = xsize * y + x; + double d = distmap[px]; + uint8_t* rgb = &(*heatmap)[3 * px]; + ScoreToRgb(d, good_threshold, bad_threshold, rgb); + } + } +} + +} // namespace butteraugli diff --git a/oss-internship-2020/guetzli/third_party/butteraugli/butteraugli/butteraugli.h b/oss-internship-2020/guetzli/third_party/butteraugli/butteraugli/butteraugli.h new file mode 100755 index 0000000..2f5d938 --- /dev/null +++ b/oss-internship-2020/guetzli/third_party/butteraugli/butteraugli/butteraugli.h @@ -0,0 +1,619 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// 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. +// +// Disclaimer: This is not an official Google product. +// +// Author: Jyrki Alakuijala (jyrki.alakuijala@gmail.com) + +#ifndef BUTTERAUGLI_BUTTERAUGLI_H_ +#define BUTTERAUGLI_BUTTERAUGLI_H_ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define BUTTERAUGLI_ENABLE_CHECKS 0 + +// This is the main interface to butteraugli image similarity +// analysis function. + +namespace butteraugli { + +template +class Image; + +using Image8 = Image; +using ImageF = Image; + +// ButteraugliInterface defines the public interface for butteraugli. +// +// It calculates the difference between rgb0 and rgb1. +// +// rgb0 and rgb1 contain the images. rgb0[c][px] and rgb1[c][px] contains +// the red image for c == 0, green for c == 1, blue for c == 2. Location index +// px is calculated as y * xsize + x. +// +// Value of pixels of images rgb0 and rgb1 need to be represented as raw +// intensity. Most image formats store gamma corrected intensity in pixel +// values. This gamma correction has to be removed, by applying the following +// function: +// butteraugli_val = 255.0 * pow(png_val / 255.0, gamma); +// A typical value of gamma is 2.2. It is usually stored in the image header. +// Take care not to confuse that value with its inverse. The gamma value should +// be always greater than one. +// Butteraugli does not work as intended if the caller does not perform +// gamma correction. +// +// diffmap will contain an image of the size xsize * ysize, containing +// localized differences for values px (indexed with the px the same as rgb0 +// and rgb1). diffvalue will give a global score of similarity. +// +// A diffvalue smaller than kButteraugliGood indicates that images can be +// observed as the same image. +// diffvalue larger than kButteraugliBad indicates that a difference between +// the images can be observed. +// A diffvalue between kButteraugliGood and kButteraugliBad indicates that +// a subtle difference can be observed between the images. +// +// Returns true on success. + +bool ButteraugliInterface(const std::vector &rgb0, + const std::vector &rgb1, + ImageF &diffmap, + double &diffvalue); + +const double kButteraugliQuantLow = 0.26; +const double kButteraugliQuantHigh = 1.454; + +// Converts the butteraugli score into fuzzy class values that are continuous +// at the class boundary. The class boundary location is based on human +// raters, but the slope is arbitrary. Particularly, it does not reflect +// the expectation value of probabilities of the human raters. It is just +// expected that a smoother class boundary will allow for higher-level +// optimization algorithms to work faster. +// +// Returns 2.0 for a perfect match, and 1.0 for 'ok', 0.0 for bad. Because the +// scoring is fuzzy, a butteraugli score of 0.96 would return a class of +// around 1.9. +double ButteraugliFuzzyClass(double score); + +// Input values should be in range 0 (bad) to 2 (good). Use +// kButteraugliNormalization as normalization. +double ButteraugliFuzzyInverse(double seek); + +// Returns a map which can be used for adaptive quantization. Values can +// typically range from kButteraugliQuantLow to kButteraugliQuantHigh. Low +// values require coarse quantization (e.g. near random noise), high values +// require fine quantization (e.g. in smooth bright areas). +bool ButteraugliAdaptiveQuantization(size_t xsize, size_t ysize, + const std::vector > &rgb, std::vector &quant); + +// Implementation details, don't use anything below or your code will +// break in the future. + +#ifdef _MSC_VER +#define BUTTERAUGLI_RESTRICT __restrict +#else +#define BUTTERAUGLI_RESTRICT __restrict__ +#endif + +#ifdef _MSC_VER +#define BUTTERAUGLI_INLINE __forceinline +#else +#define BUTTERAUGLI_INLINE inline +#endif + +#ifdef __clang__ +// Early versions of Clang did not support __builtin_assume_aligned. +#define BUTTERAUGLI_HAS_ASSUME_ALIGNED __has_builtin(__builtin_assume_aligned) +#elif defined(__GNUC__) +#define BUTTERAUGLI_HAS_ASSUME_ALIGNED 1 +#else +#define BUTTERAUGLI_HAS_ASSUME_ALIGNED 0 +#endif + +// Returns a void* pointer which the compiler then assumes is N-byte aligned. +// Example: float* PIK_RESTRICT aligned = (float*)PIK_ASSUME_ALIGNED(in, 32); +// +// The assignment semantics are required by GCC/Clang. ICC provides an in-place +// __assume_aligned, whereas MSVC's __assume appears unsuitable. +#if BUTTERAUGLI_HAS_ASSUME_ALIGNED +#define BUTTERAUGLI_ASSUME_ALIGNED(ptr, align) __builtin_assume_aligned((ptr), (align)) +#else +#define BUTTERAUGLI_ASSUME_ALIGNED(ptr, align) (ptr) +#endif // BUTTERAUGLI_HAS_ASSUME_ALIGNED + +// Functions that depend on the cache line size. +class CacheAligned { + public: + static constexpr size_t kPointerSize = sizeof(void *); + static constexpr size_t kCacheLineSize = 64; + + // The aligned-return annotation is only allowed on function declarations. + static void *Allocate(const size_t bytes); + static void Free(void *aligned_pointer); +}; + +template +using CacheAlignedUniquePtrT = std::unique_ptr; + +using CacheAlignedUniquePtr = CacheAlignedUniquePtrT; + +template +static inline CacheAlignedUniquePtrT Allocate(const size_t entries) { + return CacheAlignedUniquePtrT( + static_cast( + CacheAligned::Allocate(entries * sizeof(T))), + CacheAligned::Free); +} + +// Returns the smallest integer not less than "amount" that is divisible by +// "multiple", which must be a power of two. +template +static inline size_t Align(const size_t amount) { + static_assert(multiple != 0 && ((multiple & (multiple - 1)) == 0), + "Align<> argument must be a power of two"); + return (amount + multiple - 1) & ~(multiple - 1); +} + +// Single channel, contiguous (cache-aligned) rows separated by padding. +// T must be POD. +// +// Rationale: vectorization benefits from aligned operands - unaligned loads and +// especially stores are expensive when the address crosses cache line +// boundaries. Introducing padding after each row ensures the start of a row is +// aligned, and that row loops can process entire vectors (writes to the padding +// are allowed and ignored). +// +// We prefer a planar representation, where channels are stored as separate +// 2D arrays, because that simplifies vectorization (repeating the same +// operation on multiple adjacent components) without the complexity of a +// hybrid layout (8 R, 8 G, 8 B, ...). In particular, clients can easily iterate +// over all components in a row and Image requires no knowledge of the pixel +// format beyond the component type "T". The downside is that we duplicate the +// xsize/ysize members for each channel. +// +// This image layout could also be achieved with a vector and a row accessor +// function, but a class wrapper with support for "deleter" allows wrapping +// existing memory allocated by clients without copying the pixels. It also +// provides convenient accessors for xsize/ysize, which shortens function +// argument lists. Supports move-construction so it can be stored in containers. +template +class Image { + // Returns cache-aligned row stride, being careful to avoid 2K aliasing. + static size_t BytesPerRow(const size_t xsize) { + // Allow reading one extra AVX-2 vector on the right margin. + const size_t row_size = xsize * sizeof(T) + 32; + const size_t align = CacheAligned::kCacheLineSize; + size_t bytes_per_row = (row_size + align - 1) & ~(align - 1); + // During the lengthy window before writes are committed to memory, CPUs + // guard against read after write hazards by checking the address, but + // only the lower 11 bits. We avoid a false dependency between writes to + // consecutive rows by ensuring their sizes are not multiples of 2 KiB. + if (bytes_per_row % 2048 == 0) { + bytes_per_row += align; + } + return bytes_per_row; + } + + public: + using T = ComponentType; + + Image() : xsize_(0), ysize_(0), bytes_per_row_(0), + bytes_(static_cast(nullptr), Ignore) {} + + Image(const size_t xsize, const size_t ysize) + : xsize_(xsize), + ysize_(ysize), + bytes_per_row_(BytesPerRow(xsize)), + bytes_(Allocate(bytes_per_row_ * ysize)) {} + + Image(const size_t xsize, const size_t ysize, T val) + : xsize_(xsize), + ysize_(ysize), + bytes_per_row_(BytesPerRow(xsize)), + bytes_(Allocate(bytes_per_row_ * ysize)) { + for (size_t y = 0; y < ysize_; ++y) { + T* const BUTTERAUGLI_RESTRICT row = Row(y); + for (int x = 0; x < xsize_; ++x) { + row[x] = val; + } + } + } + + Image(const size_t xsize, const size_t ysize, + uint8_t * const BUTTERAUGLI_RESTRICT bytes, + const size_t bytes_per_row) + : xsize_(xsize), + ysize_(ysize), + bytes_per_row_(bytes_per_row), + bytes_(bytes, Ignore) {} + + // Move constructor (required for returning Image from function) + Image(Image &&other) + : xsize_(other.xsize_), + ysize_(other.ysize_), + bytes_per_row_(other.bytes_per_row_), + bytes_(std::move(other.bytes_)) {} + + // Move assignment (required for std::vector) + Image &operator=(Image &&other) { + xsize_ = other.xsize_; + ysize_ = other.ysize_; + bytes_per_row_ = other.bytes_per_row_; + bytes_ = std::move(other.bytes_); + return *this; + } + + void Swap(Image &other) { + std::swap(xsize_, other.xsize_); + std::swap(ysize_, other.ysize_); + std::swap(bytes_per_row_, other.bytes_per_row_); + std::swap(bytes_, other.bytes_); + } + + // How many pixels. + size_t xsize() const { return xsize_; } + size_t ysize() const { return ysize_; } + + T *const BUTTERAUGLI_RESTRICT Row(const size_t y) { +#ifdef BUTTERAUGLI_ENABLE_CHECKS + if (y >= ysize_) { + printf("Row %zu out of bounds (ysize=%zu)\n", y, ysize_); + abort(); + } +#endif + void *row = bytes_.get() + y * bytes_per_row_; + return reinterpret_cast(BUTTERAUGLI_ASSUME_ALIGNED(row, 64)); + } + + const T *const BUTTERAUGLI_RESTRICT Row(const size_t y) const { +#ifdef BUTTERAUGLI_ENABLE_CHECKS + if (y >= ysize_) { + printf("Const row %zu out of bounds (ysize=%zu)\n", y, ysize_); + abort(); + } +#endif + void *row = bytes_.get() + y * bytes_per_row_; + return reinterpret_cast(BUTTERAUGLI_ASSUME_ALIGNED(row, 64)); + } + + // Raw access to byte contents, for interfacing with other libraries. + // Unsigned char instead of char to avoid surprises (sign extension). + uint8_t * const BUTTERAUGLI_RESTRICT bytes() { return bytes_.get(); } + const uint8_t * const BUTTERAUGLI_RESTRICT bytes() const { + return bytes_.get(); + } + size_t bytes_per_row() const { return bytes_per_row_; } + + // Returns number of pixels (some of which are padding) per row. Useful for + // computing other rows via pointer arithmetic. + intptr_t PixelsPerRow() const { + static_assert(CacheAligned::kCacheLineSize % sizeof(T) == 0, + "Padding must be divisible by the pixel size."); + return static_cast(bytes_per_row_ / sizeof(T)); + } + + private: + // Deleter used when bytes are not owned. + static void Ignore(void *ptr) {} + + // (Members are non-const to enable assignment during move-assignment.) + size_t xsize_; // original intended pixels, not including any padding. + size_t ysize_; + size_t bytes_per_row_; // [bytes] including padding. + CacheAlignedUniquePtr bytes_; +}; + +// Returns newly allocated planes of the given dimensions. +template +static inline std::vector> CreatePlanes(const size_t xsize, + const size_t ysize, + const size_t num_planes) { + std::vector> planes; + planes.reserve(num_planes); + for (size_t i = 0; i < num_planes; ++i) { + planes.emplace_back(xsize, ysize); + } + return planes; +} + +// Returns a new image with the same dimensions and pixel values. +template +static inline Image CopyPixels(const Image &other) { + Image copy(other.xsize(), other.ysize()); + const void *BUTTERAUGLI_RESTRICT from = other.bytes(); + void *BUTTERAUGLI_RESTRICT to = copy.bytes(); + memcpy(to, from, other.ysize() * other.bytes_per_row()); + return copy; +} + +// Returns new planes with the same dimensions and pixel values. +template +static inline std::vector> CopyPlanes( + const std::vector> &planes) { + std::vector> copy; + copy.reserve(planes.size()); + for (const Image &plane : planes) { + copy.push_back(CopyPixels(plane)); + } + return copy; +} + +// Compacts a padded image into a preallocated packed vector. +template +static inline void CopyToPacked(const Image &from, std::vector *to) { + const size_t xsize = from.xsize(); + const size_t ysize = from.ysize(); +#if BUTTERAUGLI_ENABLE_CHECKS + if (to->size() < xsize * ysize) { + printf("%zu x %zu exceeds %zu capacity\n", xsize, ysize, to->size()); + abort(); + } +#endif + for (size_t y = 0; y < ysize; ++y) { + const float* const BUTTERAUGLI_RESTRICT row_from = from.Row(y); + float* const BUTTERAUGLI_RESTRICT row_to = to->data() + y * xsize; + memcpy(row_to, row_from, xsize * sizeof(T)); + } +} + +// Expands a packed vector into a preallocated padded image. +template +static inline void CopyFromPacked(const std::vector &from, Image *to) { + const size_t xsize = to->xsize(); + const size_t ysize = to->ysize(); + assert(from.size() == xsize * ysize); + for (size_t y = 0; y < ysize; ++y) { + const float* const BUTTERAUGLI_RESTRICT row_from = + from.data() + y * xsize; + float* const BUTTERAUGLI_RESTRICT row_to = to->Row(y); + memcpy(row_to, row_from, xsize * sizeof(T)); + } +} + +template +static inline std::vector> PlanesFromPacked( + const size_t xsize, const size_t ysize, + const std::vector> &packed) { + std::vector> planes; + planes.reserve(packed.size()); + for (const std::vector &p : packed) { + planes.push_back(Image(xsize, ysize)); + CopyFromPacked(p, &planes.back()); + } + return planes; +} + +template +static inline std::vector> PackedFromPlanes( + const std::vector> &planes) { + assert(!planes.empty()); + const size_t num_pixels = planes[0].xsize() * planes[0].ysize(); + std::vector> packed; + packed.reserve(planes.size()); + for (const Image &image : planes) { + packed.push_back(std::vector(num_pixels)); + CopyToPacked(image, &packed.back()); + } + return packed; +} + +struct PsychoImage { + std::vector uhf; + std::vector hf; + std::vector mf; + std::vector lf; +}; + +class ButteraugliComparator { + public: + ButteraugliComparator(const std::vector& rgb0); + + // Computes the butteraugli map between the original image given in the + // constructor and the distorted image give here. + void Diffmap(const std::vector& rgb1, ImageF& result) const; + + // Same as above, but OpsinDynamicsImage() was already applied. + void DiffmapOpsinDynamicsImage(const std::vector& xyb1, + ImageF& result) const; + + // Same as above, but the frequency decomposition was already applied. + void DiffmapPsychoImage(const PsychoImage& ps1, ImageF &result) const; + + void Mask(std::vector* BUTTERAUGLI_RESTRICT mask, + std::vector* BUTTERAUGLI_RESTRICT mask_dc) const; + + private: + void MaltaDiffMapLF(const ImageF& y0, + const ImageF& y1, + double w_0gt1, + double w_0lt1, + double normalization, + ImageF* BUTTERAUGLI_RESTRICT block_diff_ac) const; + + void MaltaDiffMap(const ImageF& y0, + const ImageF& y1, + double w_0gt1, + double w_0lt1, + double normalization, + ImageF* BUTTERAUGLI_RESTRICT block_diff_ac) const; + + ImageF CombineChannels(const std::vector& scale_xyb, + const std::vector& scale_xyb_dc, + const std::vector& block_diff_dc, + const std::vector& block_diff_ac) const; + + const size_t xsize_; + const size_t ysize_; + const size_t num_pixels_; + PsychoImage pi0_; +}; + +void ButteraugliDiffmap(const std::vector &rgb0, + const std::vector &rgb1, + ImageF &diffmap); + +double ButteraugliScoreFromDiffmap(const ImageF& distmap); + +// Generate rgb-representation of the distance between two images. +void CreateHeatMapImage(const std::vector &distmap, + double good_threshold, double bad_threshold, + size_t xsize, size_t ysize, + std::vector *heatmap); + +// Compute values of local frequency and dc masking based on the activity +// in the two images. +void Mask(const std::vector& xyb0, + const std::vector& xyb1, + std::vector* BUTTERAUGLI_RESTRICT mask, + std::vector* BUTTERAUGLI_RESTRICT mask_dc); + +template +BUTTERAUGLI_INLINE void RgbToXyb(const V &r, const V &g, const V &b, + V *BUTTERAUGLI_RESTRICT valx, + V *BUTTERAUGLI_RESTRICT valy, + V *BUTTERAUGLI_RESTRICT valb) { + *valx = r - g; + *valy = r + g; + *valb = b; +} + +template +BUTTERAUGLI_INLINE void OpsinAbsorbance(const V &in0, const V &in1, + const V &in2, + V *BUTTERAUGLI_RESTRICT out0, + V *BUTTERAUGLI_RESTRICT out1, + V *BUTTERAUGLI_RESTRICT out2) { + // https://en.wikipedia.org/wiki/Photopsin absorbance modeling. + static const double mixi0 = 0.254462330846; + static const double mixi1 = 0.488238255095; + static const double mixi2 = 0.0635278003854; + static const double mixi3 = 1.01681026909; + static const double mixi4 = 0.195214015766; + static const double mixi5 = 0.568019861857; + static const double mixi6 = 0.0860755536007; + static const double mixi7 = 1.1510118369; + static const double mixi8 = 0.07374607900105684; + static const double mixi9 = 0.06142425304154509; + static const double mixi10 = 0.24416850520714256; + static const double mixi11 = 1.20481945273; + + const V mix0(mixi0); + const V mix1(mixi1); + const V mix2(mixi2); + const V mix3(mixi3); + const V mix4(mixi4); + const V mix5(mixi5); + const V mix6(mixi6); + const V mix7(mixi7); + const V mix8(mixi8); + const V mix9(mixi9); + const V mix10(mixi10); + const V mix11(mixi11); + + *out0 = mix0 * in0 + mix1 * in1 + mix2 * in2 + mix3; + *out1 = mix4 * in0 + mix5 * in1 + mix6 * in2 + mix7; + *out2 = mix8 * in0 + mix9 * in1 + mix10 * in2 + mix11; +} + +std::vector OpsinDynamicsImage(const std::vector& rgb); + +ImageF Blur(const ImageF& in, float sigma, float border_ratio); + +double SimpleGamma(double v); + +double GammaMinArg(); +double GammaMaxArg(); + +// Polynomial evaluation via Clenshaw's scheme (similar to Horner's). +// Template enables compile-time unrolling of the recursion, but must reside +// outside of a class due to the specialization. +template +static inline void ClenshawRecursion(const double x, const double *coefficients, + double *b1, double *b2) { + const double x_b1 = x * (*b1); + const double t = (x_b1 + x_b1) - (*b2) + coefficients[INDEX]; + *b2 = *b1; + *b1 = t; + + ClenshawRecursion(x, coefficients, b1, b2); +} + +// Base case +template <> +inline void ClenshawRecursion<0>(const double x, const double *coefficients, + double *b1, double *b2) { + const double x_b1 = x * (*b1); + // The final iteration differs - no 2 * x_b1 here. + *b1 = x_b1 - (*b2) + coefficients[0]; +} + +// Rational polynomial := dividing two polynomial evaluations. These are easier +// to find than minimax polynomials. +struct RationalPolynomial { + template + static double EvaluatePolynomial(const double x, + const double (&coefficients)[N]) { + double b1 = 0.0; + double b2 = 0.0; + ClenshawRecursion(x, coefficients, &b1, &b2); + return b1; + } + + // Evaluates the polynomial at x (in [min_value, max_value]). + inline double operator()(const double x) const { + // First normalize to [0, 1]. + const double x01 = (x - min_value) / (max_value - min_value); + // And then to [-1, 1] domain of Chebyshev polynomials. + const double xc = 2.0 * x01 - 1.0; + + const double yp = EvaluatePolynomial(xc, p); + const double yq = EvaluatePolynomial(xc, q); + if (yq == 0.0) return 0.0; + return static_cast(yp / yq); + } + + // Domain of the polynomials; they are undefined elsewhere. + double min_value; + double max_value; + + // Coefficients of T_n (Chebyshev polynomials of the first kind). + // Degree 5/5 is a compromise between accuracy (0.1%) and numerical stability. + double p[5 + 1]; + double q[5 + 1]; +}; + +static inline double GammaPolynomial(double value) { + static const RationalPolynomial r = { + 0.971783, 590.188894, + { + 98.7821300963361, 164.273222212631, 92.948112871376, + 33.8165311212688, 6.91626704983562, 0.556380877028234 + }, + { + 1, 1.64339473427892, 0.89392405219969, 0.298947051776379, + 0.0507146002577288, 0.00226495093949756 + }}; + return r(value); +} + +} // namespace butteraugli + +#endif // BUTTERAUGLI_BUTTERAUGLI_H_ diff --git a/oss-internship-2020/guetzli/third_party/butteraugli/butteraugli/butteraugli_main.cc b/oss-internship-2020/guetzli/third_party/butteraugli/butteraugli/butteraugli_main.cc new file mode 100755 index 0000000..f38af1d --- /dev/null +++ b/oss-internship-2020/guetzli/third_party/butteraugli/butteraugli/butteraugli_main.cc @@ -0,0 +1,457 @@ +#include +#include +#include +#include +#include "butteraugli/butteraugli.h" + +extern "C" { +#include "png.h" +#include "jpeglib.h" +} + +namespace butteraugli { +namespace { + +// "rgb": cleared and filled with same-sized image planes (one per channel); +// either RGB, or RGBA if the PNG contains an alpha channel. +bool ReadPNG(FILE* f, std::vector* rgb) { + png_structp png_ptr = + png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (!png_ptr) { + return false; + } + + png_infop info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) { + png_destroy_read_struct(&png_ptr, NULL, NULL); + return false; + } + + if (setjmp(png_jmpbuf(png_ptr)) != 0) { + // Ok we are here because of the setjmp. + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + return false; + } + + rewind(f); + png_init_io(png_ptr, f); + + // The png_transforms flags are as follows: + // packing == convert 1,2,4 bit images, + // strip == 16 -> 8 bits / channel, + // shift == use sBIT dynamics, and + // expand == palettes -> rgb, grayscale -> 8 bit images, tRNS -> alpha. + const unsigned int png_transforms = + PNG_TRANSFORM_PACKING | PNG_TRANSFORM_EXPAND | PNG_TRANSFORM_STRIP_16; + + png_read_png(png_ptr, info_ptr, png_transforms, NULL); + + png_bytep* row_pointers = png_get_rows(png_ptr, info_ptr); + + const int xsize = png_get_image_width(png_ptr, info_ptr); + const int ysize = png_get_image_height(png_ptr, info_ptr); + const int components = png_get_channels(png_ptr, info_ptr); + + *rgb = CreatePlanes(xsize, ysize, 3); + + switch (components) { + case 1: { + // GRAYSCALE + for (int y = 0; y < ysize; ++y) { + const uint8_t* const BUTTERAUGLI_RESTRICT row = row_pointers[y]; + uint8_t* const BUTTERAUGLI_RESTRICT row0 = (*rgb)[0].Row(y); + uint8_t* const BUTTERAUGLI_RESTRICT row1 = (*rgb)[1].Row(y); + uint8_t* const BUTTERAUGLI_RESTRICT row2 = (*rgb)[2].Row(y); + + for (int x = 0; x < xsize; ++x) { + const uint8_t gray = row[x]; + row0[x] = row1[x] = row2[x] = gray; + } + } + break; + } + case 2: { + // GRAYSCALE_ALPHA + rgb->push_back(Image8(xsize, ysize)); + for (int y = 0; y < ysize; ++y) { + const uint8_t* const BUTTERAUGLI_RESTRICT row = row_pointers[y]; + uint8_t* const BUTTERAUGLI_RESTRICT row0 = (*rgb)[0].Row(y); + uint8_t* const BUTTERAUGLI_RESTRICT row1 = (*rgb)[1].Row(y); + uint8_t* const BUTTERAUGLI_RESTRICT row2 = (*rgb)[2].Row(y); + uint8_t* const BUTTERAUGLI_RESTRICT row3 = (*rgb)[3].Row(y); + + for (int x = 0; x < xsize; ++x) { + const uint8_t gray = row[2 * x + 0]; + const uint8_t alpha = row[2 * x + 1]; + row0[x] = gray; + row1[x] = gray; + row2[x] = gray; + row3[x] = alpha; + } + } + break; + } + case 3: { + // RGB + for (int y = 0; y < ysize; ++y) { + const uint8_t* const BUTTERAUGLI_RESTRICT row = row_pointers[y]; + uint8_t* const BUTTERAUGLI_RESTRICT row0 = (*rgb)[0].Row(y); + uint8_t* const BUTTERAUGLI_RESTRICT row1 = (*rgb)[1].Row(y); + uint8_t* const BUTTERAUGLI_RESTRICT row2 = (*rgb)[2].Row(y); + + for (int x = 0; x < xsize; ++x) { + row0[x] = row[3 * x + 0]; + row1[x] = row[3 * x + 1]; + row2[x] = row[3 * x + 2]; + } + } + break; + } + case 4: { + // RGBA + rgb->push_back(Image8(xsize, ysize)); + for (int y = 0; y < ysize; ++y) { + const uint8_t* const BUTTERAUGLI_RESTRICT row = row_pointers[y]; + uint8_t* const BUTTERAUGLI_RESTRICT row0 = (*rgb)[0].Row(y); + uint8_t* const BUTTERAUGLI_RESTRICT row1 = (*rgb)[1].Row(y); + uint8_t* const BUTTERAUGLI_RESTRICT row2 = (*rgb)[2].Row(y); + uint8_t* const BUTTERAUGLI_RESTRICT row3 = (*rgb)[3].Row(y); + + for (int x = 0; x < xsize; ++x) { + row0[x] = row[4 * x + 0]; + row1[x] = row[4 * x + 1]; + row2[x] = row[4 * x + 2]; + row3[x] = row[4 * x + 3]; + } + } + break; + } + default: + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + return false; + } + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + return true; +} + +const double* NewSrgbToLinearTable() { + double* table = new double[256]; + for (int i = 0; i < 256; ++i) { + const double srgb = i / 255.0; + table[i] = + 255.0 * (srgb <= 0.04045 ? srgb / 12.92 + : std::pow((srgb + 0.055) / 1.055, 2.4)); + } + return table; +} + +void jpeg_catch_error(j_common_ptr cinfo) { + (*cinfo->err->output_message) (cinfo); + jmp_buf* jpeg_jmpbuf = (jmp_buf*) cinfo->client_data; + jpeg_destroy(cinfo); + longjmp(*jpeg_jmpbuf, 1); +} + +// "rgb": cleared and filled with same-sized image planes (one per channel); +// either RGB, or RGBA if the PNG contains an alpha channel. +bool ReadJPEG(FILE* f, std::vector* rgb) { + rewind(f); + + struct jpeg_decompress_struct cinfo; + struct jpeg_error_mgr jerr; + cinfo.err = jpeg_std_error(&jerr); + jmp_buf jpeg_jmpbuf; + cinfo.client_data = &jpeg_jmpbuf; + jerr.error_exit = jpeg_catch_error; + if (setjmp(jpeg_jmpbuf)) { + return false; + } + + jpeg_create_decompress(&cinfo); + + jpeg_stdio_src(&cinfo, f); + jpeg_read_header(&cinfo, TRUE); + jpeg_start_decompress(&cinfo); + + int row_stride = cinfo.output_width * cinfo.output_components; + JSAMPARRAY buffer = (*cinfo.mem->alloc_sarray) + ((j_common_ptr) &cinfo, JPOOL_IMAGE, row_stride, 1); + + const size_t xsize = cinfo.output_width; + const size_t ysize = cinfo.output_height; + + *rgb = CreatePlanes(xsize, ysize, 3); + + switch (cinfo.out_color_space) { + case JCS_GRAYSCALE: + while (cinfo.output_scanline < cinfo.output_height) { + jpeg_read_scanlines(&cinfo, buffer, 1); + + const uint8_t* const BUTTERAUGLI_RESTRICT row = buffer[0]; + uint8_t* const BUTTERAUGLI_RESTRICT row0 = + (*rgb)[0].Row(cinfo.output_scanline - 1); + uint8_t* const BUTTERAUGLI_RESTRICT row1 = + (*rgb)[1].Row(cinfo.output_scanline - 1); + uint8_t* const BUTTERAUGLI_RESTRICT row2 = + (*rgb)[2].Row(cinfo.output_scanline - 1); + + for (int x = 0; x < xsize; x++) { + const uint8_t gray = row[x]; + row0[x] = row1[x] = row2[x] = gray; + } + } + break; + + case JCS_RGB: + while (cinfo.output_scanline < cinfo.output_height) { + jpeg_read_scanlines(&cinfo, buffer, 1); + + const uint8_t* const BUTTERAUGLI_RESTRICT row = buffer[0]; + uint8_t* const BUTTERAUGLI_RESTRICT row0 = + (*rgb)[0].Row(cinfo.output_scanline - 1); + uint8_t* const BUTTERAUGLI_RESTRICT row1 = + (*rgb)[1].Row(cinfo.output_scanline - 1); + uint8_t* const BUTTERAUGLI_RESTRICT row2 = + (*rgb)[2].Row(cinfo.output_scanline - 1); + + for (int x = 0; x < xsize; x++) { + row0[x] = row[3 * x + 0]; + row1[x] = row[3 * x + 1]; + row2[x] = row[3 * x + 2]; + } + } + break; + + default: + jpeg_destroy_decompress(&cinfo); + return false; + } + + jpeg_finish_decompress(&cinfo); + jpeg_destroy_decompress(&cinfo); + return true; +} + +// Translate R, G, B channels from sRGB to linear space. If an alpha channel +// is present, overlay the image over a black or white background. Overlaying +// is done in the sRGB space; while technically incorrect, this is aligned with +// many other software (web browsers, WebP near lossless). +void FromSrgbToLinear(const std::vector& rgb, + std::vector& linear, int background) { + const size_t xsize = rgb[0].xsize(); + const size_t ysize = rgb[0].ysize(); + static const double* const kSrgbToLinearTable = NewSrgbToLinearTable(); + + if (rgb.size() == 3) { // RGB + for (int c = 0; c < 3; c++) { + linear.push_back(ImageF(xsize, ysize)); + for (int y = 0; y < ysize; ++y) { + const uint8_t* const BUTTERAUGLI_RESTRICT row_rgb = rgb[c].Row(y); + float* const BUTTERAUGLI_RESTRICT row_linear = linear[c].Row(y); + for (size_t x = 0; x < xsize; x++) { + const int value = row_rgb[x]; + row_linear[x] = kSrgbToLinearTable[value]; + } + } + } + } else { // RGBA + for (int c = 0; c < 3; c++) { + linear.push_back(ImageF(xsize, ysize)); + for (int y = 0; y < ysize; ++y) { + const uint8_t* const BUTTERAUGLI_RESTRICT row_rgb = rgb[c].Row(y); + float* const BUTTERAUGLI_RESTRICT row_linear = linear[c].Row(y); + const uint8_t* const BUTTERAUGLI_RESTRICT row_alpha = rgb[3].Row(y); + for (size_t x = 0; x < xsize; x++) { + int value; + if (row_alpha[x] == 255) { + value = row_rgb[x]; + } else if (row_alpha[x] == 0) { + value = background; + } else { + const int fg_weight = row_alpha[x]; + const int bg_weight = 255 - fg_weight; + value = + (row_rgb[x] * fg_weight + background * bg_weight + 127) / 255; + } + row_linear[x] = kSrgbToLinearTable[value]; + } + } + } + } +} + +std::vector ReadImageOrDie(const char* filename) { + std::vector rgb; + FILE* f = fopen(filename, "rb"); + if (!f) { + fprintf(stderr, "Cannot open %s\n", filename); + exit(1); + } + unsigned char magic[2]; + if (fread(magic, 1, 2, f) != 2) { + fprintf(stderr, "Cannot read from %s\n", filename); + exit(1); + } + if (magic[0] == 0xFF && magic[1] == 0xD8) { + if (!ReadJPEG(f, &rgb)) { + fprintf(stderr, "File %s is a malformed JPEG.\n", filename); + exit(1); + } + } else { + if (!ReadPNG(f, &rgb)) { + fprintf(stderr, "File %s is neither a valid JPEG nor a valid PNG.\n", + filename); + exit(1); + } + } + fclose(f); + return rgb; +} + +static void ScoreToRgb(double score, double good_threshold, + double bad_threshold, uint8_t rgb[3]) { + double heatmap[12][3] = { + { 0, 0, 0 }, + { 0, 0, 1 }, + { 0, 1, 1 }, + { 0, 1, 0 }, // Good level + { 1, 1, 0 }, + { 1, 0, 0 }, // Bad level + { 1, 0, 1 }, + { 0.5, 0.5, 1.0 }, + { 1.0, 0.5, 0.5 }, // Pastel colors for the very bad quality range. + { 1.0, 1.0, 0.5 }, + { 1, 1, 1, }, + { 1, 1, 1, }, + }; + if (score < good_threshold) { + score = (score / good_threshold) * 0.3; + } else if (score < bad_threshold) { + score = 0.3 + (score - good_threshold) / + (bad_threshold - good_threshold) * 0.15; + } else { + score = 0.45 + (score - bad_threshold) / + (bad_threshold * 12) * 0.5; + } + static const int kTableSize = sizeof(heatmap) / sizeof(heatmap[0]); + score = std::min(std::max( + score * (kTableSize - 1), 0.0), kTableSize - 2); + int ix = static_cast(score); + double mix = score - ix; + for (int i = 0; i < 3; ++i) { + double v = mix * heatmap[ix + 1][i] + (1 - mix) * heatmap[ix][i]; + rgb[i] = static_cast(255 * pow(v, 0.5) + 0.5); + } +} + +void CreateHeatMapImage(const ImageF& distmap, double good_threshold, + double bad_threshold, size_t xsize, size_t ysize, + std::vector* heatmap) { + heatmap->resize(3 * xsize * ysize); + for (size_t y = 0; y < ysize; ++y) { + for (size_t x = 0; x < xsize; ++x) { + int px = xsize * y + x; + double d = distmap.Row(y)[x]; + uint8_t* rgb = &(*heatmap)[3 * px]; + ScoreToRgb(d, good_threshold, bad_threshold, rgb); + } + } +} + +// main() function, within butteraugli namespace for convenience. +int Run(int argc, char* argv[]) { + if (argc != 3 && argc != 4) { + fprintf(stderr, + "Usage: %s {image1.(png|jpg|jpeg)} {image2.(png|jpg|jpeg)} " + "[heatmap.ppm]\n", + argv[0]); + return 1; + } + + std::vector rgb1 = ReadImageOrDie(argv[1]); + std::vector rgb2 = ReadImageOrDie(argv[2]); + + if (rgb1.size() != rgb2.size()) { + fprintf(stderr, "Different number of channels: %lu vs %lu\n", rgb1.size(), + rgb2.size()); + exit(1); + } + + for (size_t c = 0; c < rgb1.size(); ++c) { + if (rgb1[c].xsize() != rgb2[c].xsize() || + rgb1[c].ysize() != rgb2[c].ysize()) { + fprintf( + stderr, "The images are not equal in size: (%lu,%lu) vs (%lu,%lu)\n", + rgb1[c].xsize(), rgb2[c].xsize(), rgb1[c].ysize(), rgb2[c].ysize()); + return 1; + } + } + + // TODO: Figure out if it is a good idea to fetch the gamma from the image + // instead of applying sRGB conversion. + std::vector linear1, linear2; + // Overlay the image over a black background. + FromSrgbToLinear(rgb1, linear1, 0); + FromSrgbToLinear(rgb2, linear2, 0); + ImageF diff_map, diff_map_on_white; + double diff_value; + if (!butteraugli::ButteraugliInterface(linear1, linear2, diff_map, + diff_value)) { + fprintf(stderr, "Butteraugli comparison failed\n"); + return 1; + } + ImageF* diff_map_ptr = &diff_map; + if (rgb1.size() == 4 || rgb2.size() == 4) { + // If the alpha channel is present, overlay the image over a white + // background as well. + FromSrgbToLinear(rgb1, linear1, 255); + FromSrgbToLinear(rgb2, linear2, 255); + double diff_value_on_white; + if (!butteraugli::ButteraugliInterface(linear1, linear2, diff_map_on_white, + diff_value_on_white)) { + fprintf(stderr, "Butteraugli comparison failed\n"); + return 1; + } + if (diff_value_on_white > diff_value) { + diff_value = diff_value_on_white; + diff_map_ptr = &diff_map_on_white; + } + } + printf("%lf\n", diff_value); + + if (argc == 4) { + const double good_quality = ::butteraugli::ButteraugliFuzzyInverse(1.5); + const double bad_quality = ::butteraugli::ButteraugliFuzzyInverse(0.5); + std::vector rgb; + CreateHeatMapImage(*diff_map_ptr, good_quality, bad_quality, + rgb1[0].xsize(), rgb2[0].ysize(), &rgb); + FILE* const fmap = fopen(argv[3], "wb"); + if (fmap == NULL) { + fprintf(stderr, "Cannot open %s\n", argv[3]); + perror("fopen"); + return 1; + } + bool ok = true; + if (fprintf(fmap, "P6\n%lu %lu\n255\n", + rgb1[0].xsize(), rgb1[0].ysize()) < 0){ + perror("fprintf"); + ok = false; + } + if (ok && fwrite(rgb.data(), 1, rgb.size(), fmap) != rgb.size()) { + perror("fwrite"); + ok = false; + } + if (fclose(fmap) != 0) { + perror("fclose"); + ok = false; + } + if (!ok) return 1; + } + + return 0; +} + +} // namespace +} // namespace butteraugli + +int main(int argc, char** argv) { return butteraugli::Run(argc, argv); } diff --git a/oss-internship-2020/guetzli/third_party/butteraugli/jpeg.BUILD b/oss-internship-2020/guetzli/third_party/butteraugli/jpeg.BUILD new file mode 100755 index 0000000..92c9ddc --- /dev/null +++ b/oss-internship-2020/guetzli/third_party/butteraugli/jpeg.BUILD @@ -0,0 +1,89 @@ +# Description: +# The Independent JPEG Group's JPEG runtime library. + +licenses(["notice"]) # custom notice-style license, see LICENSE + +cc_library( + name = "jpeg", + srcs = [ + "cderror.h", + "cdjpeg.h", + "jaricom.c", + "jcapimin.c", + "jcapistd.c", + "jcarith.c", + "jccoefct.c", + "jccolor.c", + "jcdctmgr.c", + "jchuff.c", + "jcinit.c", + "jcmainct.c", + "jcmarker.c", + "jcmaster.c", + "jcomapi.c", + "jconfig.h", + "jcparam.c", + "jcprepct.c", + "jcsample.c", + "jctrans.c", + "jdapimin.c", + "jdapistd.c", + "jdarith.c", + "jdatadst.c", + "jdatasrc.c", + "jdcoefct.c", + "jdcolor.c", + "jdct.h", + "jddctmgr.c", + "jdhuff.c", + "jdinput.c", + "jdmainct.c", + "jdmarker.c", + "jdmaster.c", + "jdmerge.c", + "jdpostct.c", + "jdsample.c", + "jdtrans.c", + "jerror.c", + "jfdctflt.c", + "jfdctfst.c", + "jfdctint.c", + "jidctflt.c", + "jidctfst.c", + "jidctint.c", + "jinclude.h", + "jmemmgr.c", + "jmemnobs.c", + "jmemsys.h", + "jmorecfg.h", + "jquant1.c", + "jquant2.c", + "jutils.c", + "jversion.h", + "transupp.h", + ], + hdrs = [ + "jerror.h", + "jpegint.h", + "jpeglib.h", + ], + includes = ["."], + visibility = ["//visibility:public"], +) + +genrule( + name = "configure", + outs = ["jconfig.h"], + cmd = "cat <$@\n" + + "#define HAVE_PROTOTYPES 1\n" + + "#define HAVE_UNSIGNED_CHAR 1\n" + + "#define HAVE_UNSIGNED_SHORT 1\n" + + "#define HAVE_STDDEF_H 1\n" + + "#define HAVE_STDLIB_H 1\n" + + "#ifdef WIN32\n" + + "#define INLINE __inline\n" + + "#else\n" + + "#define INLINE __inline__\n" + + "#endif\n" + + "EOF\n", +) diff --git a/oss-internship-2020/guetzli/third_party/butteraugli/png.BUILD b/oss-internship-2020/guetzli/third_party/butteraugli/png.BUILD new file mode 100755 index 0000000..9ff982b --- /dev/null +++ b/oss-internship-2020/guetzli/third_party/butteraugli/png.BUILD @@ -0,0 +1,33 @@ +# Description: +# libpng is the official PNG reference library. + +licenses(["notice"]) # BSD/MIT-like license + +cc_library( + name = "png", + srcs = [ + "png.c", + "pngerror.c", + "pngget.c", + "pngmem.c", + "pngpread.c", + "pngread.c", + "pngrio.c", + "pngrtran.c", + "pngrutil.c", + "pngset.c", + "pngtrans.c", + "pngwio.c", + "pngwrite.c", + "pngwtran.c", + "pngwutil.c", + ], + hdrs = [ + "png.h", + "pngconf.h", + ], + includes = ["."], + linkopts = ["-lm"], + visibility = ["//visibility:public"], + deps = ["@zlib_archive//:zlib"], +) diff --git a/oss-internship-2020/guetzli/third_party/butteraugli/zlib.BUILD b/oss-internship-2020/guetzli/third_party/butteraugli/zlib.BUILD new file mode 100755 index 0000000..edb77fd --- /dev/null +++ b/oss-internship-2020/guetzli/third_party/butteraugli/zlib.BUILD @@ -0,0 +1,36 @@ +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) # BSD/MIT-like license (for zlib) + +cc_library( + name = "zlib", + srcs = [ + "adler32.c", + "compress.c", + "crc32.c", + "crc32.h", + "deflate.c", + "deflate.h", + "gzclose.c", + "gzguts.h", + "gzlib.c", + "gzread.c", + "gzwrite.c", + "infback.c", + "inffast.c", + "inffast.h", + "inffixed.h", + "inflate.c", + "inflate.h", + "inftrees.c", + "inftrees.h", + "trees.c", + "trees.h", + "uncompr.c", + "zconf.h", + "zutil.c", + "zutil.h", + ], + hdrs = ["zlib.h"], + includes = ["."], +) From 6f7110dd4fe54d68827e371ca7c72cac90265347 Mon Sep 17 00:00:00 2001 From: Bohdan Tyshchenko Date: Wed, 12 Aug 2020 03:45:08 -0700 Subject: [PATCH 06/82] Added tests for sandboxed library and transaction --- oss-internship-2020/guetzli/tests/BUILD.bazel | 43 ++--- .../guetzli/tests/guetzli_sapi_test.cc | 168 ++++++++---------- .../guetzli/tests/guetzli_transaction_test.cc | 129 ++++++++++++++ .../guetzli/tests/testdata/bees_reference.jpg | Bin 0 -> 38625 bytes .../testdata/{landscape.jpg => nature.jpg} | Bin .../tests/testdata/nature_reference.jpg | Bin 0 -> 10816 bytes 6 files changed, 223 insertions(+), 117 deletions(-) create mode 100644 oss-internship-2020/guetzli/tests/testdata/bees_reference.jpg rename oss-internship-2020/guetzli/tests/testdata/{landscape.jpg => nature.jpg} (100%) create mode 100644 oss-internship-2020/guetzli/tests/testdata/nature_reference.jpg diff --git a/oss-internship-2020/guetzli/tests/BUILD.bazel b/oss-internship-2020/guetzli/tests/BUILD.bazel index b7c7517..fa8b4ae 100644 --- a/oss-internship-2020/guetzli/tests/BUILD.bazel +++ b/oss-internship-2020/guetzli/tests/BUILD.bazel @@ -1,22 +1,23 @@ -# cc_test( -# name = "transaction_tests", -# srcs = ["guetzli_transaction_test.cc"], -# visibility=["//visibility:public"], -# includes = ["."], -# deps = [ -# "//:guetzli_sapi", -# "@googletest//:gtest_main" -# ], -# ) +cc_test( + name = "transaction_tests", + srcs = ["guetzli_transaction_test.cc"], + visibility=["//visibility:public"], + deps = [ + "//:guetzli_sapi", + "@googletest//:gtest_main" + ], + size = "large", + data = glob(["testdata/*"]) +) -# cc_test( -# name = "sapi_lib_tests", -# srcs = ["guetzli_sapi_test.cc"], -# visibility=["//visibility:public"], -# includes=[".."], -# deps = [ -# "//:guetzli_sapi", -# "@googletest//:gtest_main" -# ], -# data = glob(["testdata/*"]) -# ) \ No newline at end of file +cc_test( + name = "sapi_lib_tests", + srcs = ["guetzli_sapi_test.cc"], + visibility=["//visibility:public"], + deps = [ + "//:guetzli_sapi", + "@googletest//:gtest_main" + ], + size = "large", + data = glob(["testdata/*"]) +) \ No newline at end of file diff --git a/oss-internship-2020/guetzli/tests/guetzli_sapi_test.cc b/oss-internship-2020/guetzli/tests/guetzli_sapi_test.cc index 73e54d6..2de4cb3 100644 --- a/oss-internship-2020/guetzli/tests/guetzli_sapi_test.cc +++ b/oss-internship-2020/guetzli/tests/guetzli_sapi_test.cc @@ -10,6 +10,7 @@ #include #include #include +#include namespace guetzli { namespace sandbox { @@ -18,15 +19,18 @@ namespace tests { namespace { constexpr const char* IN_PNG_FILENAME = "bees.png"; -constexpr const char* IN_JPG_FILENAME = "landscape.jpg"; +constexpr const char* IN_JPG_FILENAME = "nature.jpg"; +constexpr const char* PNG_REFERENCE_FILENAME = "bees_reference.jpg"; +constexpr const char* JPG_REFERENCE_FILENAME = "nature_reference.jpg"; -constexpr int IN_PNG_FILE_SIZE = 177'424; -constexpr int IN_JPG_FILE_SIZE = 14'418; +constexpr int PNG_EXPECTED_SIZE = 38'625; +constexpr int JPG_EXPECTED_SIZE = 10'816; constexpr int DEFAULT_QUALITY_TARGET = 95; +constexpr int DEFAULT_MEMLIMIT_MB = 6000; constexpr const char* RELATIVE_PATH_TO_TESTDATA = - "/guetzli/guetzli-sandboxed/tests/testdata/"; + "/guetzli_sandboxed/tests/testdata/"; std::string GetPathToInputFile(const char* filename) { return std::string(getenv("TEST_SRCDIR")) @@ -46,6 +50,18 @@ std::string ReadFromFile(const std::string& filename) { return result.str(); } +template +bool CompareBytesInLenValAndContainer(const sapi::v::LenVal& lenval, + const Container& container) { + return std::equal( + lenval.GetData(), lenval.GetData() + lenval.GetDataSize(), + container.begin(), + [](const uint8_t lhs, const auto rhs) { + return lhs == static_cast(rhs); + } + ); +} + } // namespace class GuetzliSapiTest : public ::testing::Test { @@ -60,106 +76,66 @@ protected: std::unique_ptr api_; }; -TEST_F(GuetzliSapiTest, ReadDataFromFd) { - std::string input_file_path = GetPathToInputFile(IN_PNG_FILENAME); - int fd = open(input_file_path.c_str(), O_RDONLY); - ASSERT_TRUE(fd != -1) << "Error opening input file"; - sapi::v::Fd remote_fd(fd); - auto send_fd_status = sandbox_->TransferToSandboxee(&remote_fd); - ASSERT_TRUE(send_fd_status.ok()) << "Error sending fd to sandboxee"; - ASSERT_TRUE(remote_fd.GetRemoteFd() != -1) << "Error opening remote fd"; - sapi::v::LenVal data(0); - auto read_status = - api_->ReadDataFromFd(remote_fd.GetRemoteFd(), data.PtrBoth()); - ASSERT_TRUE(read_status.value_or(false)) << "Error reading data from fd"; - ASSERT_EQ(data.GetDataSize(), IN_PNG_FILE_SIZE) << "Wrong size of file"; -} - -// TEST_F(GuetzliSapiTest, WriteDataToFd) { - -// } - -TEST_F(GuetzliSapiTest, ReadPng) { - std::string data = ReadFromFile(GetPathToInputFile(IN_PNG_FILENAME)); - ASSERT_EQ(data.size(), IN_PNG_FILE_SIZE) << "Error reading input file"; - sapi::v::LenVal in_data(data.data(), data.size()); - sapi::v::Int xsize, ysize; - sapi::v::LenVal rgb_out(0); - - auto status = api_->ReadPng(in_data.PtrBefore(), xsize.PtrBoth(), - ysize.PtrBoth(), rgb_out.PtrBoth()); - ASSERT_TRUE(status.value_or(false)) << "Error processing png data"; - ASSERT_EQ(xsize.GetValue(), 444) << "Error parsing width"; - ASSERT_EQ(ysize.GetValue(), 258) << "Error parsing height"; -} - -TEST_F(GuetzliSapiTest, ReadJpeg) { - std::string data = ReadFromFile(GetPathToInputFile(IN_JPG_FILENAME)); - ASSERT_EQ(data.size(), IN_JPG_FILE_SIZE) << "Error reading input file"; - sapi::v::LenVal in_data(data.data(), data.size()); - sapi::v::Int xsize, ysize; - - auto status = api_->ReadJpegData(in_data.PtrBefore(), 0, - xsize.PtrBoth(), ysize.PtrBoth()); - ASSERT_TRUE(status.value_or(false)) << "Error processing jpeg data"; - ASSERT_EQ(xsize.GetValue(), 180) << "Error parsing width"; - ASSERT_EQ(ysize.GetValue(), 180) << "Error parsing height"; -} - // This test can take up to few minutes depending on your hardware TEST_F(GuetzliSapiTest, ProcessRGB) { - std::string data = ReadFromFile(GetPathToInputFile(IN_PNG_FILENAME)); - ASSERT_EQ(data.size(), IN_PNG_FILE_SIZE) << "Error reading input file"; - sapi::v::LenVal in_data(data.data(), data.size()); - sapi::v::Int xsize, ysize; - sapi::v::LenVal rgb_out(0); - - auto status = api_->ReadPng(in_data.PtrBefore(), xsize.PtrBoth(), - ysize.PtrBoth(), rgb_out.PtrBoth()); - ASSERT_TRUE(status.value_or(false)) << "Error processing png data"; - ASSERT_EQ(xsize.GetValue(), 444) << "Error parsing width"; - ASSERT_EQ(ysize.GetValue(), 258) << "Error parsing height"; - auto quality = - api_->ButteraugliScoreQuality(static_cast(DEFAULT_QUALITY_TARGET)); - ASSERT_TRUE(quality.ok()) << "Error calculating butteraugli quality"; - sapi::v::Struct params; - sapi::v::LenVal out_data(0); - params.mutable_data()->butteraugli_target = quality.value(); - - status = api_->ProcessRGBData(params.PtrBefore(), 0, rgb_out.PtrBefore(), - xsize.GetValue(), ysize.GetValue(), out_data.PtrBoth()); - ASSERT_TRUE(status.value_or(false)) << "Error processing png file"; - ASSERT_EQ(out_data.GetDataSize(), 38'625); - //ADD COMPARSION WITH REFERENCE OUTPUT + sapi::v::Fd in_fd(open(GetPathToInputFile(IN_PNG_FILENAME).c_str(), + O_RDONLY)); + ASSERT_TRUE(in_fd.GetValue() != -1) << "Error opening input file"; + ASSERT_EQ(api_->sandbox()->TransferToSandboxee(&in_fd), absl::OkStatus()) + << "Error transfering fd to sandbox"; + ASSERT_TRUE(in_fd.GetRemoteFd() != -1) << "Error opening remote fd"; + sapi::v::Struct processing_params; + *processing_params.mutable_data() = {in_fd.GetRemoteFd(), + 0, + DEFAULT_QUALITY_TARGET, + DEFAULT_MEMLIMIT_MB + }; + sapi::v::LenVal output(0); + auto processing_result = api_->ProcessRgb(processing_params.PtrBefore(), + output.PtrBoth()); + ASSERT_TRUE(processing_result.value_or(false)) << "Error processing rgb data"; + ASSERT_EQ(output.GetDataSize(), PNG_EXPECTED_SIZE) + << "Incorrect result data size"; + std::string reference_data = + ReadFromFile(GetPathToInputFile(PNG_REFERENCE_FILENAME)); + ASSERT_EQ(output.GetDataSize(), reference_data.size()) + << "Incorrect result data size"; + ASSERT_TRUE(CompareBytesInLenValAndContainer(output, reference_data)) + << "Processed data doesn't match reference output"; } // This test can take up to few minutes depending on your hardware TEST_F(GuetzliSapiTest, ProcessJpeg) { - std::string data = ReadFromFile(GetPathToInputFile(IN_JPG_FILENAME)); - ASSERT_EQ(data.size(), IN_JPG_FILE_SIZE) << "Error reading input file"; - sapi::v::LenVal in_data(data.data(), data.size()); - sapi::v::Int xsize, ysize; - - auto status = api_->ReadJpegData(in_data.PtrBefore(), 0, - xsize.PtrBoth(), ysize.PtrBoth()); - ASSERT_TRUE(status.value_or(false)) << "Error processing jpeg data"; - ASSERT_EQ(xsize.GetValue(), 180) << "Error parsing width"; - ASSERT_EQ(ysize.GetValue(), 180) << "Error parsing height"; - - auto quality = - api_->ButteraugliScoreQuality(static_cast(DEFAULT_QUALITY_TARGET)); - ASSERT_TRUE(quality.ok()) << "Error calculating butteraugli quality"; - sapi::v::Struct params; - params.mutable_data()->butteraugli_target = quality.value(); - sapi::v::LenVal out_data(0); - - status = api_->ProcessJPEGString(params.PtrBefore(), 0, in_data.PtrBefore(), - out_data.PtrBoth()); - ASSERT_TRUE(status.value_or(false)) << "Error processing jpeg file"; - ASSERT_EQ(out_data.GetDataSize(), 10'816); - //ADD COMPARSION WITH REFERENCE OUTPUT + sapi::v::Fd in_fd(open(GetPathToInputFile(IN_JPG_FILENAME).c_str(), + O_RDONLY)); + ASSERT_TRUE(in_fd.GetValue() != -1) << "Error opening input file"; + ASSERT_EQ(api_->sandbox()->TransferToSandboxee(&in_fd), absl::OkStatus()) + << "Error transfering fd to sandbox"; + ASSERT_TRUE(in_fd.GetRemoteFd() != -1) << "Error opening remote fd"; + sapi::v::Struct processing_params; + *processing_params.mutable_data() = {in_fd.GetRemoteFd(), + 0, + DEFAULT_QUALITY_TARGET, + DEFAULT_MEMLIMIT_MB + }; + sapi::v::LenVal output(0); + auto processing_result = api_->ProcessJpeg(processing_params.PtrBefore(), + output.PtrBoth()); + ASSERT_TRUE(processing_result.value_or(false)) << "Error processing jpg data"; + ASSERT_EQ(output.GetDataSize(), JPG_EXPECTED_SIZE) + << "Incorrect result data size"; + std::string reference_data = + ReadFromFile(GetPathToInputFile(JPG_REFERENCE_FILENAME)); + ASSERT_EQ(output.GetDataSize(), reference_data.size()) + << "Incorrect result data size"; + ASSERT_TRUE(CompareBytesInLenValAndContainer(output, reference_data)) + << "Processed data doesn't match reference output"; } +// TEST_F(GuetzliSapiTest, WriteDataToFd) { +// sapi::v::Fd fd(open(".", O_TMPFILE | O_RDWR, S_IRUSR | S_IWUSR)); +// } + } // namespace tests } // namespace sandbox } // namespace guetzli \ No newline at end of file diff --git a/oss-internship-2020/guetzli/tests/guetzli_transaction_test.cc b/oss-internship-2020/guetzli/tests/guetzli_transaction_test.cc index 8471d1e..b9e3a22 100644 --- a/oss-internship-2020/guetzli/tests/guetzli_transaction_test.cc +++ b/oss-internship-2020/guetzli/tests/guetzli_transaction_test.cc @@ -1,2 +1,131 @@ #include "gtest/gtest.h" #include "guetzli_transaction.h" +#include "sandboxed_api/sandbox2/util/fileops.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace guetzli { +namespace sandbox { +namespace tests { + +namespace { + +constexpr const char* IN_PNG_FILENAME = "bees.png"; +constexpr const char* IN_JPG_FILENAME = "nature.jpg"; +constexpr const char* PNG_REFERENCE_FILENAME = "bees_reference.jpg"; +constexpr const char* JPG_REFERENCE_FILENAME = "nature_reference.jpg"; + +constexpr int PNG_EXPECTED_SIZE = 38'625; +constexpr int JPG_EXPECTED_SIZE = 10'816; + +constexpr int DEFAULT_QUALITY_TARGET = 95; +constexpr int DEFAULT_MEMLIMIT_MB = 6000; + +constexpr const char* RELATIVE_PATH_TO_TESTDATA = + "/guetzli_sandboxed/tests/testdata/"; + +std::string GetPathToInputFile(const char* filename) { + return std::string(getenv("TEST_SRCDIR")) + + std::string(RELATIVE_PATH_TO_TESTDATA) + + std::string(filename); +} + +std::string ReadFromFile(const std::string& filename) { + std::ifstream stream(filename, std::ios::binary); + + if (!stream.is_open()) { + return ""; + } + + std::stringstream result; + result << stream.rdbuf(); + return result.str(); +} + +} // namespace + +TEST(GuetzliTransactionTest, TestTransactionJpg) { + sandbox2::file_util::fileops::FDCloser in_fd_closer( + open(GetPathToInputFile(IN_JPG_FILENAME).c_str(), O_RDONLY)); + ASSERT_TRUE(in_fd_closer.get() != -1) << "Error opening input jpg file"; + sandbox2::file_util::fileops::FDCloser out_fd_closer( + open(".", O_TMPFILE | O_RDWR, S_IRUSR | S_IWUSR)); + ASSERT_TRUE(out_fd_closer.get() != -1) << "Error creating temp output file"; + TransactionParams params = { + in_fd_closer.get(), + out_fd_closer.get(), + 0, + DEFAULT_QUALITY_TARGET, + DEFAULT_MEMLIMIT_MB + }; + { + GuetzliTransaction transaction(std::move(params)); + auto result = transaction.Run(); + + ASSERT_TRUE(result.ok()) << result.ToString(); + } + ASSERT_TRUE(fcntl(out_fd_closer.get(), F_GETFD) != -1 || errno != EBADF) + << "Local output fd closed"; + auto reference_data = ReadFromFile(GetPathToInputFile(JPG_REFERENCE_FILENAME)); + auto output_size = lseek(out_fd_closer.get(), 0, SEEK_END); + ASSERT_EQ(reference_data.size(), output_size) + << "Different sizes of reference and returned data"; + ASSERT_EQ(lseek(out_fd_closer.get(), 0, SEEK_SET), 0) + << "Error repositioning out file"; + + std::unique_ptr buf(new char[output_size]); + auto status = read(out_fd_closer.get(), buf.get(), output_size); + ASSERT_EQ(status, output_size) << "Error reading data from temp output file"; + + ASSERT_TRUE( + std::equal(buf.get(), buf.get() + output_size, reference_data.begin())) + << "Returned data doesn't match reference"; +} + +TEST(GuetzliTransactionTest, TestTransactionPng) { + sandbox2::file_util::fileops::FDCloser in_fd_closer( + open(GetPathToInputFile(IN_PNG_FILENAME).c_str(), O_RDONLY)); + ASSERT_TRUE(in_fd_closer.get() != -1) << "Error opening input png file"; + sandbox2::file_util::fileops::FDCloser out_fd_closer( + open(".", O_TMPFILE | O_RDWR, S_IRUSR | S_IWUSR)); + ASSERT_TRUE(out_fd_closer.get() != -1) << "Error creating temp output file"; + TransactionParams params = { + in_fd_closer.get(), + out_fd_closer.get(), + 0, + DEFAULT_QUALITY_TARGET, + DEFAULT_MEMLIMIT_MB + }; + { + GuetzliTransaction transaction(std::move(params)); + auto result = transaction.Run(); + + ASSERT_TRUE(result.ok()) << result.ToString(); + } + ASSERT_TRUE(fcntl(out_fd_closer.get(), F_GETFD) != -1 || errno != EBADF) + << "Local output fd closed"; + auto reference_data = ReadFromFile(GetPathToInputFile(PNG_REFERENCE_FILENAME)); + auto output_size = lseek(out_fd_closer.get(), 0, SEEK_END); + ASSERT_EQ(reference_data.size(), output_size) + << "Different sizes of reference and returned data"; + ASSERT_EQ(lseek(out_fd_closer.get(), 0, SEEK_SET), 0) + << "Error repositioning out file"; + + std::unique_ptr buf(new char[output_size]); + auto status = read(out_fd_closer.get(), buf.get(), output_size); + ASSERT_EQ(status, output_size) << "Error reading data from temp output file"; + + ASSERT_TRUE( + std::equal(buf.get(), buf.get() + output_size, reference_data.begin())) + << "Returned data doesn't match refernce"; +} + +} // namespace tests +} // namespace sandbox +} // namespace guetzli \ No newline at end of file diff --git a/oss-internship-2020/guetzli/tests/testdata/bees_reference.jpg b/oss-internship-2020/guetzli/tests/testdata/bees_reference.jpg new file mode 100644 index 0000000000000000000000000000000000000000..24700673fecdef2b805ee1a2529631fd923b5621 GIT binary patch literal 38625 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 XWVO%er7V6NB4LbF?W=O^%+LSXDy^Ji literal 0 HcmV?d00001 diff --git a/oss-internship-2020/guetzli/tests/testdata/landscape.jpg b/oss-internship-2020/guetzli/tests/testdata/nature.jpg similarity index 100% rename from oss-internship-2020/guetzli/tests/testdata/landscape.jpg rename to oss-internship-2020/guetzli/tests/testdata/nature.jpg diff --git a/oss-internship-2020/guetzli/tests/testdata/nature_reference.jpg b/oss-internship-2020/guetzli/tests/testdata/nature_reference.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b4f1c42fa4459d2bee59df5f6ccc50019062781c GIT binary patch literal 10816 zcmbWcRZts_6D=I9NP*&3pb&y2NO5-vZo$39o#NgW_u^KhxCNKs?(Tu2#oY@ODfIsS z^L;b-{hocNPnU!bC)W4yvd!6E>l{FlDMz{GxqhW)>m|0)0q z5e6}k`xPOBw5B-;J&#*3myA|YLH#8rDM$)t(KolvYv$@6avhXhNG7YDU)S3|zr#2C zZv}vh_P<$B0RRcWZ}!X=W$!=3FFRzQB6-kb^XCgV%i(fbwEaii&zCXZejLqv+(nWL z+NYg~*?AW*%|eBo2st6mtXP#@P)LPB5Bwks=HM@UO>rLib()vb!55KHZ@a?69;=LY)QbZED6MHooB|Cn3?}km~%wq2m z*Cl+pkt4D({!Ce>3QOZiB%6pq@q&0ld7l;W`>b^YTLDH$5B)AqGrXVlL~Y4*4jokV z#5sf#$Xa>zm?ERIz&BvOe#;uWv-{Hbnyc_c)e`Qqw{qV}0Ll-RG+L-wnI}#g%nU2G zH8>;3NraBH>a`_&mKRP&J6o$(L;Wt=q!ywx2#UnaPaShj1yJZ#ms0&cMVT*9@5>teU(q*JK*4PpL9X9WyZ)6L*-#;v?e(G^Skkot)7BFIo_vLuny`g>pY<3KG=UJ zH*#++U7!V(D%DHZM&P%FTgCYCRCwmywh%5DUY$JNn_pS2M8D^Pen7bwc)EN@-r*t9 z&UN_*5S57yw~Y;VKfa#Qy6!_KraW1?^;AQJC|GgjCb=(FW7Q{drxfp|sEsIr29yed z6Q|s=^KuLe6X;Des>@EXcCq%$s0y8QM?j!>_{iGiT(*KY)i_T53j8T5u#SnnJf-#}Ax9 z<;A_t(4z7ERo!VX50AjItQNJeje&RhT>|}?Ia4kXM}|CP(fnYfPt@!Xg;?!fe=B~X zC%2;Rd}VmZMa1DB28DWJ(5h2Iv>9x^akRIsng4~tIwu-u9fK@B9r8+x?tZHayzI7bYcWs`@Vd3u#F) zPI&h5Q#g8dT0NA5Qtw>~1f!{yaQE#RNR|xD6wQBQo6}PZvQW#)noKFEIw*Rhhj>)( zed-viQ6Z4--g8odyKINOEuf*F*+QWLo1VUxKcDtH^~oQDPpkpkt*-RUg4K?KYR6qbpgpDbO!`N6gEUe~B(s~z8czqwar%oTW_ICdy;azgy?4NZm z!>&&dVf`J}%xx_^LMx8AFxEuDn(e%zD=!nxrc5N?K}&Hng6NcdO6ffoo9b zQ_5>GDQ_FYqqTNv>*%7$4Z4Gybo>KqD`;_A%$KCmHbg_&IfiTHyu4fPIPnCp>+ygN zvzKCq&8aTh4RiN>e{iH;L1odHW;zBnCKZ0dN+jP(MXFhe90h`491kd{pkTr7=?FQ$ zv}U0Dagp|Qs4T`{?wsd4`Ez@1gz;l>&0pZHjR962N`-BJ%T+u2+>lTFT8O6(*zm0X47&2ys9`amZ zRb0r%B^nxv|CZI!Hz}&9#NlqE>%usBBae^`6F%upq9e0skUj7B#GeMU7zQh-EL`PIHltk0*S>}_V()6uGt*8;y%_wn zT%^5jy>!aFU)9*Sw3ReQchNPELu$69{?0#@BSoRCQlQr(zP=(p32d9Eme!(J7Wyo>kvW*bu@Cs2_kPLt`nnBvP z-*-_g-Le6>j<%+bL6#}^SP}D1!u3uZk(~(+CAXv(TJAqHW&Qz@YNB^2k*FCh_;JgD zM*<-X#57}fO#ZhK;`bORqxg^9I@m5lVYYvZ>@do$!PY`8!t-r>=XZ_d4bkALDhJgJ zlV-0r`ku@>+uAScyfq3d9{Y|j@%u;XvDNdY}tv+xH`7zv+B3O57y48uVtx;2biRo?zL=d~ti%GT)F=VsSQ;yaTI)t5hge%Oy zE~Lv=bq&W#f}H`e!L>MV0PdT;Yr+gkE^&2~w4dV9HR&Z;T#jOzl-2}gXSMh+{{c)- z`J;rxq~N9&zj}9S7YD($R$lK``4Xk4Du##JP+S`+$ zH+JdEs6xg>cVdQ%({EF%mo&R*+5dX%9{>f-oLCB~(xoJ+L6MuoDOiUPx$KsFBeAud z5bN9ZGNX{?612gMzcVrYjM*ZE?=~7s71crB$m6d58q{3>b+`DlghB!u-)Du zG%kD871-q1P3re_!sVXG>r&LR{dE|8~hNx{a%Ha5f9AF^eWS9 z`$Gd85S!kC!MlsrX{jr~70o)CuyMv?U+xE|k+}ze-4u;;+C8c54W^(TwY{VkWjZza z?#5(lyk!w$ePus`4c`7|Y<~8kdQfzymB`PjAZZ@xn0q?TCo)!6*~T}8xfCgE!Pd>K(nV3`;nj}2w@c33% zu}rfXvoo9ZD6`+B$Scb+YZ=%q+YH86gLwNK2d>jK7ud;u4KU~wd>1W>8=OeIV^-Aa?I5z)rD zJuX@eUo`^fGuc5ri){n?W8&3a9s}ST4J;(ec`I=BWPg_#^jI%V-{tkz*notv#YeWSZ?csQ^ z?6tu;z1k*mI89Xcv74|Ori3=^(g_?n+rx=fdhb;jyi? z^OVRcXM;n00g$DXkI)-re`vv;d1qtK0I|jb;ac8dK`hD|mMv?m!jPiEc(SNPGE{lA z#O=TRq~dxQ*X~EnpGn_Ak3CUjh48hsf|8a`8LFSoGS zpLEy><=!jjc-+(J1PP~c+)35aS}9#zxw}_`pNvbzG;WBW12yUPckvS z?DvH$e(ptWf`#qut7e^?tc4w2lEblLz_0P>Cdd!_kK(R0IZ(GV;x}JY)pn%<@&lG_ z57QMpx?Gk&9*%qe4)U%T#&?|kB)~$~CNRqJk#wiWCZXqEu;&kbzX3XuP{_J>EqZ!% zBfPmOt8)%BC?Hc=BZ<#OPZ&<%JFz9V>o}U}>0zcK!d38x`|I9$lqEGLrTXZ3?9qGa zBO3O8e&;gHP2%dC&-5E!0ondnbqv;dgKalAVdQwl+bzjjf2i8&`%gldKUMHmkt!FV z_ApD+k>#XAZ(WvHrra49Hwf1xyUe8yKTSbjA7;{gv0+TuYJ#cne~laDU|jwYXNh0vI6g|n@W9ntLjqxWXsN)VBA}~WQKk>pX#fqvX5r_q#dgTrF_&dz(0WU zjal$HRlDjq^Y=(R;?hUv_}`XEtc)mT?n6#oVSei~Iu|tNvQvcm$mVWF;R)rw#fIVscLniORT+AY6Jni^+ zzqc!Ck1D5(jX~d=4fqc?UeOhM#7o@1cJbL6ZwfZF9Gg%sDYXOkeo+7HN$N1-vno9& zggv>*0&EgSt>yK3L!t47Cjz@MH_aZ!n_{G?_JRPI7b>EaWsf?)pz@l)AU##uEnWo4_A{RV2(L^&E8LPW$Ah89b&(&}?RED8DniPE39YSB9ymi{sm zNpqK&XkJT0NPu-#;bB6td+*m#X|>+hYtRc(Gcz)HBTyJADm|AJao&DVDSs?fA5utF zT!O018`+Xe=FPH6spE-kl-x*Wu)t4G2O;epM;LnR&YvFohvTE!s)7$|652Fg6ZJ@9 zLo%6c&3{DCmG6}akJgTk!cj=|d{#Q>$ME{6u!;iI^p>cvKES#!; z7Mo~KEMAfBOhXzOPQ6{|>q_ctV^NBmZJN1$#PtybCbJr({aCrfh>tIK@&Cm(S`PFC!%T)R#Y0r{-VSnIlB&Fg%;9AS$7{E&1A-S zxF~k&385Gr`|tHqaiaFct=U8E7X4MvQP*3EE_O2@W^LeQGD>**VCn2HQ9Qu{5yrdf6INR}YP9#iUhZkfQtAoUpuK4{yf%D)w(MGMM zu^f>#pKUY+_79mx8$$gdjd<1>^S3!bt4J55JicH~Qb_%@4+MJ-E&Y{*9veTb?{#W# zlDO7#fS1L8Iw#=;GlBS&vCduZuV#;>BZP}pN@=~CJwAUHZ+`E{t?EJW=_dViCi$@G zwB3RD5lfQ8wT#{JQK@aA1I~LwiF=EDzGlE(s@Cui_C>D*eJbK2vg)|gMCV(NT&B`2 zR2^Xs!Q_=!coVL9EV%0kn3k}s>&{EsEwoz~9}x38%X!v3*?L4bOF5ynOBVkgmut~R zpVp5Bhu)ylE-=!nkpAMur9w97PXlL@wMhKa4lRAu*Z``EC#hfY~jf+iY z{&0qow*Z&F17(L%1YWh~tLXT>Gv#+sBzh-R(@zca-@tmRU zxRn(N`s{5d!>q8z$FJJSW0>WQ3we>6-a>*We=en?YF+ZNv(JZDp9u$(Q1s7l*Bx>g zCF*g2T)o^UOEf=f*oNR{J?EF{IihP^-ijlN(pWRGO;ap8a9vt<@V3 z2l7JJEk3TA2+dHUfD{)f7SR6cSjEs}FFF~tep!)2JUQaUQ|MyK$A5|AjFQ{p9fZmd zlu@Al=yn#v{%E@s&OW0#=XG?Fn44pGMb)aQ8B=$mYcot9oS>T&q~t?-8i94G*B;}J zuH|?lz1$SW=qDj*Z=h&Wn5-PP$Ne+?x5_lFLQ8=OA?ceY)I7+T+2+Z%qQpl#L##ljM1B4gAMLVoYV8DzH9^&8g(I*D z3wDJm8c*Rnlpks;KKO5hi@8!7MlNvm-<5Emsp_QqnIx!SlX z)U!rs4zQYR%Sf@vWqd=7;O$y!q%OW0Ty?}E(hS8~eEnx}U6tj5e`{yK9%y)k8MCd!l^++Abd(J0?)CWm>T$9Vuom zCv0OQUhD{(SCv^I5Ab=E4CCA84LGFH?=Xr``IL5~GOzl&Qc>AXahhJ)!zZ*3aIqZ= znVj9_omV9P8d-Tq4lWbY@u~F<9MSvo-4x5}<0LxL?a22OhO_u0l6#qo^?P zyQR8>1W46raS&m}tI7@433Z(xiuwYZVrW$yQno$%R(7PC%I@uA^ADie-AkKY4rOj(a(w&~W#4XOw!S>&^46vBz%K`z0E$+9zrxfcZ5e5L-+3F3jizOt%{|XqNdnC zzzE^~xCFGSUFrA?-Yl=Xu7dq>TCa*o>F{$Il31h1rQCaOdPzOhf3~Z|YkM~Z9p?-p zTfbx()$aK&c_-lCFi&Q#SjUE6{JULODXmW zmx6ncLc?Z?w=z{O`|hE6(kXcOQKn6LjDf`;bLXqrkxlz^rh!K9MmfVw#KFvEc6=Tz;^5DbZ^tZ%AD9P~v z*_F|;!(xV#gqQom5GfLiN}#>8o*Xdy6+8B8zEqc|xUyX}ipkr`11_RXjW4dz zuScLu7~rxsvzV7rR3G9a`8n#`3*rCsWNny2eheco`8KPOlXnf6zv2Dm@J}*}uQ@{T zq!(&g18A7>pm6{FWVRN@D8<4cJfg($3$)l7n=B#~tvY=m@LpAuB%#7gZrCZ6M02={ zV#ngU)3mEu>iWjT?-;={y~N~zG{dylZ`noIsdWqBkix(gk%@c!$MfSo(6zJmHys_E zOEjHX(>LxeefrADoPA`c5eIr!(*3Z9vqyn`OpB0L0R-^O62rYTU=`9B&D#AX6C2j@r7>IST-a(*KHnZk!?02t*_Bg8Tz)ahw{!pe-d^ z7Rq?90^#~irCpxp!1cjo65Yyclex46%5SR<3C{kvCObUq9m5{9kgXU;9vCb~fLCva z6Zc(w5<{##jaK9ny9XIY4XjGmU)DkRFGYE28Y7wC+?3PYWiSH*e({rS4@RbPe<@2G zUTuF=wq@}C(;phfOP9UQ2LJF-n3#48l`FD+8~&~=CANl{fPua>~CEX5r#2rqHyH6mo%&eP8S6pSgXrbHNviX9OZ49*{?QPG1EC;>- zseGh?PBxB?CA$^HtjA-aT&Qv>QmVOpo;H zFWtM*49~a?^E5GqBSuL3XNgP}varl{Qm$>(F^k>zV=#YD$JMmbe}F9ODvvK-2U`&H zVmsdE9`THr%rBQ>wyg&C5~zf3ThvD3W}i=G?mF32K9S(~;9d=W(fI75AgZqfV-(b& zld|Nb7X(t2zsJeuZcpW&U=VcMdf?c&`0Lj-5*beB zPAQNXtu8U#jr$YPS~f1xN*$rH4w>vz)0)a1VsSua#F7mScY}kN__T#AST%4rQ;Aqr9z#8Bdza_{^0K&pOCKZBNN7imO zU+Y*k*T_+H+^YB1v(s2H)d+F9885z?mfXDf?f58C6>K~s($xeUq#{dPMP2?5_A4uI z$-B@|haC_RZL^G$8OY-#mFQ5WLM56&yjHREsK$YUfWzVG&B9?E1m$<44VeM{E%5KUpB8Gik!> z)nk+CvS*G(O{N{`2V;#DR5rKRg}v=wUZYWjaF4bbZ)xo9h3%(NC4K*9(Er2}(L0Pj zqFn7U(7!6hZ{c$_QXpKvxekwi$!mB zz4s_B^!G0!iL^Z5P*>O~JWa?I!2>HSIFZ4jbUJaZ#~kl}so0*9Vzs&*S%plLyONEm6mfL2soF1W&<|z}b zarAuUIQt9)p*m(yqfC^37F`)j%xkrac2)D}4R#&=k1z^URC{$1>T0Q_1H>NV@RR-r zupM*}mGn>ED!m-|?QbH0m#mHBfnyaK*9i@chE7tN&wV#P`3HEQmKrNl*u~^ZRC3Gx zpuH8G#x1uDD)LGEwaXJ8W*yb60`i%!dH3a=>J?2<)+x|?yM!&{jen%YXTyaq%ZVC= zLB(S30__d0*izjySw8tJINUX$!6R6ikA#|)3r4A|eZa2CMXAXGY@3H^ z7SiL(&=k!KN(Sxe2#OL~JN^Td*yUvW*fG~Xf4SRzwrvwORq(i-N}^L2z$TT|_YTkI ziab24n7s+YWIlScx)pPmAn`VxqFqx~VzPjqos~D}gzP#EKQ{7frRUS-Y@cOfleGVW zzwh=O_|$R0{T&o7<2jTS2^eh%`pcb&ABvJ=l|6q=m)0M?4@-(o=Eg!sn{oyohx8Tj39e<`v0c- z_FrEOlp~}wABkljB3W-$RnK-pNfnv5PdG53tSjw|wrdyGe>RWUntEe;hIL6r`~CqQ z7k1*XdWT#Un{$_S;28oKtwx1R)NxB1(&-7vkY<9`2s<_zw^(60AlHo=0MnM9NpYXCrRM&r;|# zm#W=TZvT7qB)xlo8OkV>a{D#b!{!xbUjt!j#py|?G_EF%*2&2eJ-Zf?hEghA!;X63 zn6`ZUupmHf! zJ84Q29(h)k{opKf`k+o{viYK!$4yfCML z4M?-zoo9pFm-{M_x)L@WMImkJ{d)G7GUE! z8Q_xhwi4HI*Rn!ob7+>5sd<+?Cm91UWTF!iQ(wX#n$eJz(>B|EcFi8PkP9Ny|B-Ez zI64%C`kFd+8<*$g8yPy4>+;ZNI2NoxG8oszJe1PR0J@FNEGStJu9aOZ@e7I9@t-GN zOD`Fw6?^jvs(o-LnO#huVyi({Op^`MIL*sr;7LI!!~M1&P+x=l44)JV*%(yX^1*8F zXBY|rHh~u6--nG_bl^*)2NW}O@+xLZo`WWs4gqUB5B~tP6bPKL4<_g9I#*l4J<(5F jl;qmYi+>0s Date: Wed, 12 Aug 2020 11:24:30 +0000 Subject: [PATCH 07/82] initial openjpeg commit --- .gitmodules | 3 + oss-internship-2020/openjpeg/.gitignore | 1 + oss-internship-2020/openjpeg/CMakeLists.txt | 56 +++ oss-internship-2020/openjpeg/README.md | 7 + .../openjpeg/examples/CMakeLists.txt | 33 ++ .../openjpeg/examples/convert_helper.cc | 350 ++++++++++++++++++ .../openjpeg/examples/convert_helper.h | 8 + .../openjpeg/examples/decompress_example.cc | 163 ++++++++ oss-internship-2020/openjpeg/openjpeg | 1 + 9 files changed, 622 insertions(+) create mode 100644 .gitmodules create mode 100644 oss-internship-2020/openjpeg/.gitignore create mode 100644 oss-internship-2020/openjpeg/CMakeLists.txt create mode 100644 oss-internship-2020/openjpeg/README.md create mode 100644 oss-internship-2020/openjpeg/examples/CMakeLists.txt create mode 100644 oss-internship-2020/openjpeg/examples/convert_helper.cc create mode 100644 oss-internship-2020/openjpeg/examples/convert_helper.h create mode 100644 oss-internship-2020/openjpeg/examples/decompress_example.cc create mode 160000 oss-internship-2020/openjpeg/openjpeg diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..5de1e29 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "oss-internship-2020/openjpeg/openjpeg"] + path = oss-internship-2020/openjpeg/openjpeg + url = https://github.com/uclouvain/openjpeg.git diff --git a/oss-internship-2020/openjpeg/.gitignore b/oss-internship-2020/openjpeg/.gitignore new file mode 100644 index 0000000..d163863 --- /dev/null +++ b/oss-internship-2020/openjpeg/.gitignore @@ -0,0 +1 @@ +build/ \ No newline at end of file diff --git a/oss-internship-2020/openjpeg/CMakeLists.txt b/oss-internship-2020/openjpeg/CMakeLists.txt new file mode 100644 index 0000000..fdc7a9a --- /dev/null +++ b/oss-internship-2020/openjpeg/CMakeLists.txt @@ -0,0 +1,56 @@ +# Copyright 2020 Google LLC +# +# 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. + +cmake_minimum_required(VERSION 3.10) + +project(openjpeg-sapi C CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED True) + +# To override lib option -- else SAPI won't work +SET(BUILD_SHARED_LIBS OFF CACHE BOOL "Build OpenJPEG shared library and link executables against it." ) +add_subdirectory(openjpeg) + +set(SAPI_ROOT "" CACHE PATH "Path to the Sandboxed API source tree") +set(SAPI_ENABLE_EXAMPLES OFF CACHE BOOL "") +set(SAPI_ENABLE_TESTS OFF CACHE BOOL "") +set(EXECUTABLE_OUTPUT_PATH "" CACHE PATH "" FORCE) +add_subdirectory("${SAPI_ROOT}" + "${CMAKE_BINARY_DIR}/sandboxed-api-build" + EXCLUDE_FROM_ALL) + +add_sapi_library(openjp2_sapi + FUNCTIONS opj_stream_destroy + opj_stream_create_default_file_stream + opj_create_decompress + opj_image_destroy + opj_setup_decoder + opj_destroy_codec + opj_read_header + opj_decode + opj_set_default_decoder_parameters + opj_end_decompress + + INPUTS ${CMAKE_CURRENT_SOURCE_DIR}/openjpeg/src/lib/openjp2/openjpeg.h + LIBRARY openjp2 + LIBRARY_NAME Openjp2 + NAMESPACE "" +) + +target_include_directories(openjp2_sapi INTERFACE + "${PROJECT_BINARY_DIR}" +) + +add_subdirectory(examples) diff --git a/oss-internship-2020/openjpeg/README.md b/oss-internship-2020/openjpeg/README.md new file mode 100644 index 0000000..8e2090f --- /dev/null +++ b/oss-internship-2020/openjpeg/README.md @@ -0,0 +1,7 @@ +## OpenJPEG + +This library provides sandboxed version of the [OpenJPEG](https://github.com/uclouvain/openjpeg) library. + +### Examples + +The examples are sandboxed and simplified version of the main tools provided by the OpenJPEG librabry, namely (for now) `opj_decompress` from [here](https://github.com/uclouvain/openjpeg/blob/master/src/bin/jp2/opj_decompress.c). \ No newline at end of file diff --git a/oss-internship-2020/openjpeg/examples/CMakeLists.txt b/oss-internship-2020/openjpeg/examples/CMakeLists.txt new file mode 100644 index 0000000..b988086 --- /dev/null +++ b/oss-internship-2020/openjpeg/examples/CMakeLists.txt @@ -0,0 +1,33 @@ +# Copyright 2020 Google LLC +# +# 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. + +add_library(convert_helper STATIC + convert_helper.h + convert_helper.cc +) + +add_executable(decompress_sandboxed + decompress_example.cc +) + +target_link_libraries(decompress_sandboxed PRIVATE + convert_helper + openjp2_sapi + sapi::sapi +) + +target_link_libraries(convert_helper PRIVATE + openjp2_sapi + sapi::sapi +) diff --git a/oss-internship-2020/openjpeg/examples/convert_helper.cc b/oss-internship-2020/openjpeg/examples/convert_helper.cc new file mode 100644 index 0000000..92a0714 --- /dev/null +++ b/oss-internship-2020/openjpeg/examples/convert_helper.cc @@ -0,0 +1,350 @@ + +// The copyright in this software is being made available under the 2-clauses +// BSD License, included below. This software may be subject to other third +// party and contributor rights, including patent rights, and no such rights +// are granted under this license. +// * +// Copyright (c) 2002-2014, Universite catholique de Louvain (UCL), Belgium +// Copyright (c) 2002-2014, Professor Benoit Macq +// Copyright (c) 2003-2014, Antonin Descampe +// Copyright (c) 2003-2009, Francois-Olivier Devaux +// Copyright (c) 2005, Herve Drolon, FreeImage Team +// Copyright (c) 2002-2003, Yannick Verschueren +// Copyright (c) 2001-2003, David Janssens +// Copyright (c) 2011-2012, Centre National d'Etudes Spatiales (CNES), France +// Copyright (c) 2012, CS Systemes d'Information, France +// * +// All rights reserved. +// * +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// * +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS `AS IS' +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + + +// copies of a few library tools + +#include "convert_helper.h" + +const char* opj_version(void) +{ + return "2.3.1"; +} + +static int are_comps_similar(opj_image_t * image) +{ + unsigned int i; + for (i = 1; i < image->numcomps; i++) { + if (image->comps[0].dx != image->comps[i].dx || + image->comps[0].dy != image->comps[i].dy || + (i <= 2 && + (image->comps[0].prec != image->comps[i].prec || + image->comps[0].sgnd != image->comps[i].sgnd))) { + return OPJ_FALSE; + } + } + return OPJ_TRUE; +} + +int imagetopnm(opj_image_t * image, const char *outfile, int force_split) +{ + int *red, *green, *blue, *alpha; + int wr, hr, max; + int i; + unsigned int compno, ncomp; + int adjustR, adjustG, adjustB, adjustA; + int fails, two, want_gray, has_alpha, triple; + int prec, v; + FILE *fdest = NULL; + const char *tmp = outfile; + char *destname; + + alpha = NULL; + + if ((prec = (int)image->comps[0].prec) > 16) { + fprintf(stderr, "%s:%d:imagetopnm\n\tprecision %d is larger than 16" + "\n\t: refused.\n", __FILE__, __LINE__, prec); + return 1; + } + + two = has_alpha = 0; + fails = 1; + ncomp = image->numcomps; + + while (*tmp) { + ++tmp; + } + tmp -= 2; + want_gray = (*tmp == 'g' || *tmp == 'G'); + ncomp = image->numcomps; + + if (want_gray) { + ncomp = 1; + } + + if ((force_split == 0) && ncomp >= 2 && + are_comps_similar(image)) { + fdest = fopen(outfile, "wb"); + + if (!fdest) { + fprintf(stderr, "ERROR -> failed to open %s for writing\n", outfile); + return fails; + } + two = (prec > 8); + triple = (ncomp > 2); + wr = (int)image->comps[0].w; + hr = (int)image->comps[0].h; + max = (1 << prec) - 1; + has_alpha = (ncomp == 4 || ncomp == 2); + + red = image->comps[0].data; + if (red == NULL) { + fprintf(stderr, + "imagetopnm: planes[%d] == NULL.\n", 0); + fprintf(stderr, "\tAborting\n"); + fclose(fdest); + return fails; + } + + if (triple) { + green = image->comps[1].data; + blue = image->comps[2].data; + for (i = 1; i <= 2; i++) { + if (image->comps[i].data == NULL) { + fprintf(stderr, + "imagetopnm: planes[%d] == NULL.\n", i); + fprintf(stderr, "\tAborting\n"); + fclose(fdest); + return fails; + } + } + } else { + green = blue = NULL; + } + + if (has_alpha) { + const char *tt = (triple ? "RGB_ALPHA" : "GRAYSCALE_ALPHA"); + + fprintf(fdest, "P7\n# OpenJPEG-%s\nWIDTH %d\nHEIGHT %d\nDEPTH %u\n" + "MAXVAL %d\nTUPLTYPE %s\nENDHDR\n", opj_version(), + wr, hr, ncomp, max, tt); + alpha = image->comps[ncomp - 1].data; + adjustA = (image->comps[ncomp - 1].sgnd ? + 1 << (image->comps[ncomp - 1].prec - 1) : 0); + } else { + fprintf(fdest, "P6\n# OpenJPEG-%s\n%d %d\n%d\n", + opj_version(), wr, hr, max); + adjustA = 0; + } + adjustR = (image->comps[0].sgnd ? 1 << (image->comps[0].prec - 1) : 0); + + if (triple) { + adjustG = (image->comps[1].sgnd ? 1 << (image->comps[1].prec - 1) : 0); + adjustB = (image->comps[2].sgnd ? 1 << (image->comps[2].prec - 1) : 0); + } else { + adjustG = adjustB = 0; + } + + for (i = 0; i < wr * hr; ++i) { + if (two) { + v = *red + adjustR; + ++red; + if (v > 65535) { + v = 65535; + } else if (v < 0) { + v = 0; + } + + /* netpbm: */ + fprintf(fdest, "%c%c", (unsigned char)(v >> 8), (unsigned char)v); + + if (triple) { + v = *green + adjustG; + ++green; + if (v > 65535) { + v = 65535; + } else if (v < 0) { + v = 0; + } + + /* netpbm: */ + fprintf(fdest, "%c%c", (unsigned char)(v >> 8), (unsigned char)v); + + v = *blue + adjustB; + ++blue; + if (v > 65535) { + v = 65535; + } else if (v < 0) { + v = 0; + } + + /* netpbm: */ + fprintf(fdest, "%c%c", (unsigned char)(v >> 8), (unsigned char)v); + + }/* if(triple) */ + + if (has_alpha) { + v = *alpha + adjustA; + ++alpha; + if (v > 65535) { + v = 65535; + } else if (v < 0) { + v = 0; + } + + /* netpbm: */ + fprintf(fdest, "%c%c", (unsigned char)(v >> 8), (unsigned char)v); + } + continue; + + } /* if(two) */ + + /* prec <= 8: */ + v = *red++; + if (v > 255) { + v = 255; + } else if (v < 0) { + v = 0; + } + + fprintf(fdest, "%c", (unsigned char)v); + if (triple) { + v = *green++; + if (v > 255) { + v = 255; + } else if (v < 0) { + v = 0; + } + + fprintf(fdest, "%c", (unsigned char)v); + v = *blue++; + if (v > 255) { + v = 255; + } else if (v < 0) { + v = 0; + } + + fprintf(fdest, "%c", (unsigned char)v); + } + if (has_alpha) { + v = *alpha++; + if (v > 255) { + v = 255; + } else if (v < 0) { + v = 0; + } + + fprintf(fdest, "%c", (unsigned char)v); + } + } /* for(i */ + + fclose(fdest); + return 0; + } + + /* YUV or MONO: */ + + if (image->numcomps > ncomp) { + fprintf(stderr, "WARNING -> [PGM file] Only the first component\n"); + fprintf(stderr, " is written to the file\n"); + } + destname = (char*)malloc(strlen(outfile) + 8); + if (destname == NULL) { + fprintf(stderr, "imagetopnm: memory out\n"); + return 1; + } + for (compno = 0; compno < ncomp; compno++) { + if (ncomp > 1) { + /*sprintf(destname, "%d.%s", compno, outfile);*/ + const size_t olen = strlen(outfile); + const size_t dotpos = olen - 4; + + strncpy(destname, outfile, dotpos); + sprintf(destname + dotpos, "_%u.pgm", compno); + } else { + sprintf(destname, "%s", outfile); + } + + fdest = fopen(destname, "wb"); + if (!fdest) { + fprintf(stderr, "ERROR -> failed to open %s for writing\n", destname); + free(destname); + return 1; + } + wr = (int)image->comps[compno].w; + hr = (int)image->comps[compno].h; + prec = (int)image->comps[compno].prec; + max = (1 << prec) - 1; + + fprintf(fdest, "P5\n#OpenJPEG-%s\n%d %d\n%d\n", + opj_version(), wr, hr, max); + + red = image->comps[compno].data; + + if (!red) { + fclose(fdest); + continue; + } + + adjustR = + (image->comps[compno].sgnd ? 1 << (image->comps[compno].prec - 1) : 0); + + if (prec > 8) { + for (i = 0; i < wr * hr; i++) { + v = *red + adjustR; + ++red; + if (v > 65535) { + v = 65535; + } else if (v < 0) { + v = 0; + } + + /* netpbm: */ + fprintf(fdest, "%c%c", (unsigned char)(v >> 8), (unsigned char)v); + + if (has_alpha) { + v = *alpha++; + if (v > 65535) { + v = 65535; + } else if (v < 0) { + v = 0; + } + + /* netpbm: */ + fprintf(fdest, "%c%c", (unsigned char)(v >> 8), (unsigned char)v); + } + }/* for(i */ + } else { /* prec <= 8 */ + for (i = 0; i < wr * hr; ++i) { + v = *red + adjustR; + ++red; + if (v > 255) { + v = 255; + } else if (v < 0) { + v = 0; + } + fprintf(fdest, "%c", (unsigned char)v); + } + } + fclose(fdest); + } /* for (compno */ + free(destname); + + return 0; +}/* imagetopnm() */ \ No newline at end of file diff --git a/oss-internship-2020/openjpeg/examples/convert_helper.h b/oss-internship-2020/openjpeg/examples/convert_helper.h new file mode 100644 index 0000000..3a13844 --- /dev/null +++ b/oss-internship-2020/openjpeg/examples/convert_helper.h @@ -0,0 +1,8 @@ +#include "openjp2_sapi.sapi.h" + +#define OPJ_TRUE 1 +#define OPJ_FALSE 0 + +const char* opj_version(void); +int imagetopnm(opj_image_t * image, const char *outfile, int force_split); +static int are_comps_similar(opj_image_t * image); \ No newline at end of file diff --git a/oss-internship-2020/openjpeg/examples/decompress_example.cc b/oss-internship-2020/openjpeg/examples/decompress_example.cc new file mode 100644 index 0000000..8189ccf --- /dev/null +++ b/oss-internship-2020/openjpeg/examples/decompress_example.cc @@ -0,0 +1,163 @@ +// Copyright 2020 Google LLC +// +// 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. + + +// Perform decompression from *.jp2 to *.pnm format + + +#include +#include + +#include +#include +#include + +#include "convert_helper.h" + +#include "openjp2_sapi.sapi.h" +#include "sandboxed_api/util/flag.h" + +class Parameters : public sapi::v::Struct {}; +class Opj_image_t : public sapi::v::Struct {}; + +class Openjp2SapiSandbox : public Openjp2Sandbox { + public: + Openjp2SapiSandbox(const std::string& in_file) + : in_file_(in_file) {} + + std::unique_ptr ModifyPolicy( + sandbox2::PolicyBuilder*) override { + return sandbox2::PolicyBuilder() + .AllowStaticStartup() + .AllowOpen() + .AllowRead() + .AllowWrite() + .AllowStat() + .AllowSystemMalloc() + .AllowExit() + .AllowSyscalls({ + __NR_futex, + __NR_close, + __NR_lseek, + }) + .AddFile(in_file_) + .BuildOrDie(); + } + + private: + std::string in_file_; +}; + + +int main(int argc, char* argv[]) { + gflags::ParseCommandLineFlags(&argc, &argv, true); + + if (argc != 3) { + std::cerr << "usage: " + << basename(argv[0]) + << " absolute/path/to/INPUT.jp2" + << " absolute/path/to/OUTPUT.pnm\n"; + return EXIT_FAILURE; + } + + std::string in_file(argv[1]); + + Openjp2SapiSandbox sandbox(in_file); + absl::Status status = sandbox.Init(); + assert(status.ok()); + + Openjp2Api api(&sandbox); + sapi::v::ConstCStr in_file_v(in_file.c_str()); + + sapi::StatusOr stream_status + = api.opj_stream_create_default_file_stream(in_file_v.PtrBefore(), 1); + assert(stream_status.ok()); + void* stream_status_value = stream_status.value(); + sapi::v::RemotePtr stream_pointer(stream_status_value); + + sapi::StatusOr codec_status + = api.opj_create_decompress(OPJ_CODEC_JP2); + assert(codec_status.ok()); + void* codec_status_value = codec_status.value(); + sapi::v::RemotePtr codec_pointer(codec_status_value); + + Parameters parameters; + status = api.opj_set_default_decoder_parameters(parameters.PtrBoth()); + assert(status.ok()); + + sapi::StatusOr bool_status + = api.opj_setup_decoder(&codec_pointer, parameters.PtrBefore()); + assert(bool_status.ok()); + assert(bool_status.value()); + + sapi::v::GenericPtr image_pointer; + bool_status = api.opj_read_header(&stream_pointer, + &codec_pointer, + image_pointer.PtrAfter()); + assert(bool_status.ok()); + assert(bool_status.value()); + + Opj_image_t image; + image.SetRemote((void*)image_pointer.GetValue()); + assert(sandbox.TransferFromSandboxee(&image).ok()); + + bool_status = api.opj_decode(&codec_pointer, + &stream_pointer, + (sapi::v::Ptr*)&image_pointer); + assert(bool_status.ok()); + assert(bool_status.value()); + + bool_status = api.opj_end_decompress(&codec_pointer, &stream_pointer); + assert(bool_status.ok()); + assert(bool_status.value()); + + status = api.opj_stream_destroy(&stream_pointer); + assert(status.ok()); + + int components = image.data().numcomps; + + sapi::v::Array image_components(components); + image_components.SetRemote(image.data().comps); + assert(sandbox.TransferFromSandboxee(&image_components).ok()); + + image.mutable_data()->comps + = (opj_image_comp_t*)image_components.GetLocal(); + + int width = (int)image.data().comps[0].w; + int height = (int)image.data().comps[0].h; + + int data[components][width * height]; + sapi::v::Array image_components_data(width * height); + + for (int i = 0; i < components; i++) { + image_components_data.SetRemote(image.data().comps[i].data); + assert(sandbox.TransferFromSandboxee(&image_components_data).ok()); + for (int j = 0; j < width * height; j++) { + data[i][j] = image_components_data[j]; + } + image_components[i].data = data[i]; + } + + int error = imagetopnm((opj_image_t*)image.GetLocal(), argv[2], 0); + assert(error == 0); + + status = api.opj_destroy_codec(&codec_pointer); + assert(status.ok()); + + sapi::v::RemotePtr remote_image_pointer(image.GetRemote()); + status = api.opj_image_destroy(&remote_image_pointer); + assert(status.ok()); + + return EXIT_SUCCESS; +} diff --git a/oss-internship-2020/openjpeg/openjpeg b/oss-internship-2020/openjpeg/openjpeg new file mode 160000 index 0000000..cbee789 --- /dev/null +++ b/oss-internship-2020/openjpeg/openjpeg @@ -0,0 +1 @@ +Subproject commit cbee7891a0ee664dd83ca09553d2e30da716a883 From d0f638de4cc6d21f78f372a2bac4fbac9086f973 Mon Sep 17 00:00:00 2001 From: Katarzyna Miernikiewicz Date: Wed, 12 Aug 2020 11:26:55 +0000 Subject: [PATCH 08/82] fixed typo --- oss-internship-2020/openjpeg/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oss-internship-2020/openjpeg/README.md b/oss-internship-2020/openjpeg/README.md index 8e2090f..a87b062 100644 --- a/oss-internship-2020/openjpeg/README.md +++ b/oss-internship-2020/openjpeg/README.md @@ -4,4 +4,4 @@ This library provides sandboxed version of the [OpenJPEG](https://github.com/ucl ### Examples -The examples are sandboxed and simplified version of the main tools provided by the OpenJPEG librabry, namely (for now) `opj_decompress` from [here](https://github.com/uclouvain/openjpeg/blob/master/src/bin/jp2/opj_decompress.c). \ No newline at end of file +The examples are sandboxed and simplified version of the main tools provided by the OpenJPEG library, namely (for now) `opj_decompress` from [here](https://github.com/uclouvain/openjpeg/blob/master/src/bin/jp2/opj_decompress.c). \ No newline at end of file From 89ef35d4c3f99e0a7c2c2b253919c55245108b38 Mon Sep 17 00:00:00 2001 From: Katarzyna Miernikiewicz Date: Wed, 12 Aug 2020 11:37:59 +0000 Subject: [PATCH 09/82] Openjpeg decompress sandboxed --- oss-internship-2020/openjpeg/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/oss-internship-2020/openjpeg/README.md b/oss-internship-2020/openjpeg/README.md index a87b062..b2b64de 100644 --- a/oss-internship-2020/openjpeg/README.md +++ b/oss-internship-2020/openjpeg/README.md @@ -1,7 +1,7 @@ -## OpenJPEG +# OpenJPEG This library provides sandboxed version of the [OpenJPEG](https://github.com/uclouvain/openjpeg) library. -### Examples +## Examples The examples are sandboxed and simplified version of the main tools provided by the OpenJPEG library, namely (for now) `opj_decompress` from [here](https://github.com/uclouvain/openjpeg/blob/master/src/bin/jp2/opj_decompress.c). \ No newline at end of file From 57fa728e4bbe73e8ce6ff9711fc892f734802b92 Mon Sep 17 00:00:00 2001 From: Katarzyna Miernikiewicz Date: Wed, 12 Aug 2020 11:48:40 +0000 Subject: [PATCH 10/82] fixed typos --- oss-internship-2020/openjpeg/.gitignore | 2 +- oss-internship-2020/openjpeg/README.md | 2 +- oss-internship-2020/openjpeg/examples/convert_helper.cc | 2 +- oss-internship-2020/openjpeg/examples/convert_helper.h | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/oss-internship-2020/openjpeg/.gitignore b/oss-internship-2020/openjpeg/.gitignore index d163863..567609b 100644 --- a/oss-internship-2020/openjpeg/.gitignore +++ b/oss-internship-2020/openjpeg/.gitignore @@ -1 +1 @@ -build/ \ No newline at end of file +build/ diff --git a/oss-internship-2020/openjpeg/README.md b/oss-internship-2020/openjpeg/README.md index b2b64de..11bc7af 100644 --- a/oss-internship-2020/openjpeg/README.md +++ b/oss-internship-2020/openjpeg/README.md @@ -4,4 +4,4 @@ This library provides sandboxed version of the [OpenJPEG](https://github.com/ucl ## Examples -The examples are sandboxed and simplified version of the main tools provided by the OpenJPEG library, namely (for now) `opj_decompress` from [here](https://github.com/uclouvain/openjpeg/blob/master/src/bin/jp2/opj_decompress.c). \ No newline at end of file +The examples are sandboxed and simplified version of the main tools provided by the OpenJPEG library, namely (for now) `opj_decompress` from [here](https://github.com/uclouvain/openjpeg/blob/master/src/bin/jp2/opj_decompress.c). diff --git a/oss-internship-2020/openjpeg/examples/convert_helper.cc b/oss-internship-2020/openjpeg/examples/convert_helper.cc index 92a0714..488d616 100644 --- a/oss-internship-2020/openjpeg/examples/convert_helper.cc +++ b/oss-internship-2020/openjpeg/examples/convert_helper.cc @@ -347,4 +347,4 @@ int imagetopnm(opj_image_t * image, const char *outfile, int force_split) free(destname); return 0; -}/* imagetopnm() */ \ No newline at end of file +}/* imagetopnm() */ diff --git a/oss-internship-2020/openjpeg/examples/convert_helper.h b/oss-internship-2020/openjpeg/examples/convert_helper.h index 3a13844..3c21b32 100644 --- a/oss-internship-2020/openjpeg/examples/convert_helper.h +++ b/oss-internship-2020/openjpeg/examples/convert_helper.h @@ -5,4 +5,4 @@ const char* opj_version(void); int imagetopnm(opj_image_t * image, const char *outfile, int force_split); -static int are_comps_similar(opj_image_t * image); \ No newline at end of file +static int are_comps_similar(opj_image_t * image); From 785de90c807024b468218c4573d0727c175bbf7c Mon Sep 17 00:00:00 2001 From: Katarzyna Miernikiewicz Date: Wed, 12 Aug 2020 11:50:11 +0000 Subject: [PATCH 11/82] Openjpeg sandboxed --- oss-internship-2020/openjpeg/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/oss-internship-2020/openjpeg/README.md b/oss-internship-2020/openjpeg/README.md index 11bc7af..50df415 100644 --- a/oss-internship-2020/openjpeg/README.md +++ b/oss-internship-2020/openjpeg/README.md @@ -2,6 +2,7 @@ This library provides sandboxed version of the [OpenJPEG](https://github.com/uclouvain/openjpeg) library. + ## Examples The examples are sandboxed and simplified version of the main tools provided by the OpenJPEG library, namely (for now) `opj_decompress` from [here](https://github.com/uclouvain/openjpeg/blob/master/src/bin/jp2/opj_decompress.c). From 73e6a27b141cd2e643f097f1e9079c8a7b4ce90b Mon Sep 17 00:00:00 2001 From: Katarzyna Miernikiewicz Date: Wed, 12 Aug 2020 11:59:31 +0000 Subject: [PATCH 12/82] fixed typo --- .../openjpeg/examples/convert_helper.cc | 39 ------------------- 1 file changed, 39 deletions(-) diff --git a/oss-internship-2020/openjpeg/examples/convert_helper.cc b/oss-internship-2020/openjpeg/examples/convert_helper.cc index 488d616..eda1c3a 100644 --- a/oss-internship-2020/openjpeg/examples/convert_helper.cc +++ b/oss-internship-2020/openjpeg/examples/convert_helper.cc @@ -1,43 +1,4 @@ -// The copyright in this software is being made available under the 2-clauses -// BSD License, included below. This software may be subject to other third -// party and contributor rights, including patent rights, and no such rights -// are granted under this license. -// * -// Copyright (c) 2002-2014, Universite catholique de Louvain (UCL), Belgium -// Copyright (c) 2002-2014, Professor Benoit Macq -// Copyright (c) 2003-2014, Antonin Descampe -// Copyright (c) 2003-2009, Francois-Olivier Devaux -// Copyright (c) 2005, Herve Drolon, FreeImage Team -// Copyright (c) 2002-2003, Yannick Verschueren -// Copyright (c) 2001-2003, David Janssens -// Copyright (c) 2011-2012, Centre National d'Etudes Spatiales (CNES), France -// Copyright (c) 2012, CS Systemes d'Information, France -// * -// All rights reserved. -// * -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions -// are met: -// 1. Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// * -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS `AS IS' -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - - // copies of a few library tools #include "convert_helper.h" From 47fd491e20d6b8accd6586c53a5ff975ecb57489 Mon Sep 17 00:00:00 2001 From: Bohdan Tyshchenko Date: Wed, 12 Aug 2020 14:09:40 -0700 Subject: [PATCH 13/82] Codestyle update --- oss-internship-2020/guetzli/BUILD.bazel | 14 ++++ oss-internship-2020/guetzli/README.md | 0 .../guetzli/guetzli_entry_points.cc | 36 +++++--- .../guetzli/guetzli_entry_points.h | 21 ++++- oss-internship-2020/guetzli/guetzli_sandbox.h | 26 +++++- .../guetzli/guetzli_sandboxed.cc | 25 ++++-- .../guetzli/guetzli_transaction.cc | 28 +++++-- .../guetzli/guetzli_transaction.h | 38 ++++++--- oss-internship-2020/guetzli/tests/BUILD.bazel | 14 ++++ .../guetzli/tests/guetzli_sapi_test.cc | 84 +++++++++++-------- .../guetzli/tests/guetzli_transaction_test.cc | 68 +++++++++------ 11 files changed, 253 insertions(+), 101 deletions(-) create mode 100644 oss-internship-2020/guetzli/README.md diff --git a/oss-internship-2020/guetzli/BUILD.bazel b/oss-internship-2020/guetzli/BUILD.bazel index 6e3332a..fe5bfb8 100644 --- a/oss-internship-2020/guetzli/BUILD.bazel +++ b/oss-internship-2020/guetzli/BUILD.bazel @@ -1,3 +1,17 @@ +# Copyright 2020 Google LLC +# +# 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. + load( "@com_google_sandboxed_api//sandboxed_api/bazel:sapi.bzl", "sapi_library", diff --git a/oss-internship-2020/guetzli/README.md b/oss-internship-2020/guetzli/README.md new file mode 100644 index 0000000..e69de29 diff --git a/oss-internship-2020/guetzli/guetzli_entry_points.cc b/oss-internship-2020/guetzli/guetzli_entry_points.cc index a815673..ec0743b 100644 --- a/oss-internship-2020/guetzli/guetzli_entry_points.cc +++ b/oss-internship-2020/guetzli/guetzli_entry_points.cc @@ -1,3 +1,25 @@ +// Copyright 2020 Google LLC +// +// 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. + +#include + +#include +#include +#include +#include +#include + #include "guetzli/jpeg_data_reader.h" #include "guetzli/quality.h" #include "guetzli_entry_points.h" @@ -5,17 +27,10 @@ #include "sandboxed_api/sandbox2/util/fileops.h" #include "sandboxed_api/util/statusor.h" -#include -#include -#include -#include -#include -#include - namespace { constexpr int kBytesPerPixel = 350; -constexpr int kLowestMemusageMB = 100; // in MB +constexpr int kLowestMemusageMB = 100; struct GuetzliInitData { std::string in_data; @@ -26,7 +41,7 @@ struct GuetzliInitData { template void CopyMemoryToLenVal(const T* data, size_t size, sapi::LenValStruct* out_data) { - free(out_data->data); // Not sure about this + free(out_data->data); // Not sure about this out_data->size = size; T* new_out = static_cast(malloc(size)); memcpy(new_out, data, size); @@ -49,7 +64,6 @@ sapi::StatusOr ReadFromFd(int fd) { status = read(fd, buf.get(), fsize); if (status < 0) { - lseek(fd, 0, SEEK_SET); return absl::FailedPreconditionError( "Error reading input from fd" ); @@ -275,4 +289,4 @@ extern "C" bool ProcessRgb(const ProcessingParams* processing_params, extern "C" bool WriteDataToFd(int fd, sapi::LenValStruct* data) { return sandbox2::file_util::fileops::WriteToFD(fd, static_cast(data->data), data->size); -} \ No newline at end of file +} diff --git a/oss-internship-2020/guetzli/guetzli_entry_points.h b/oss-internship-2020/guetzli/guetzli_entry_points.h index 90b0d97..b7fd2c5 100644 --- a/oss-internship-2020/guetzli/guetzli_entry_points.h +++ b/oss-internship-2020/guetzli/guetzli_entry_points.h @@ -1,4 +1,19 @@ -#pragma once +// Copyright 2020 Google LLC +// +// 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. + +#ifndef GUETZLI_SANDBOXED_GUETZLI_ENTRY_POINTS_H_ +#define GUETZLI_SANDBOXED_GUETZLI_ENTRY_POINTS_H_ #include "guetzli/processor.h" #include "sandboxed_api/lenval_core.h" @@ -15,4 +30,6 @@ extern "C" bool ProcessJpeg(const ProcessingParams* processing_params, sapi::LenValStruct* output); extern "C" bool ProcessRgb(const ProcessingParams* processing_params, sapi::LenValStruct* output); -extern "C" bool WriteDataToFd(int fd, sapi::LenValStruct* data); \ No newline at end of file +extern "C" bool WriteDataToFd(int fd, sapi::LenValStruct* data); + +#endif // GUETZLI_SANDBOXED_GUETZLI_ENTRY_POINTS_H_ diff --git a/oss-internship-2020/guetzli/guetzli_sandbox.h b/oss-internship-2020/guetzli/guetzli_sandbox.h index 3fa2b10..e55875a 100644 --- a/oss-internship-2020/guetzli/guetzli_sandbox.h +++ b/oss-internship-2020/guetzli/guetzli_sandbox.h @@ -1,13 +1,29 @@ -#pragma once +// Copyright 2020 Google LLC +// +// 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. + +#ifndef GUETZLI_SANDBOXED_GUETZLI_SANDBOX_H_ +#define GUETZLI_SANDBOXED_GUETZLI_SANDBOX_H_ #include #include -#include "guetzli_sapi.sapi.h" #include "sandboxed_api/sandbox2/policy.h" #include "sandboxed_api/sandbox2/policybuilder.h" #include "sandboxed_api/util/flag.h" +#include "guetzli_sapi.sapi.h" + namespace guetzli { namespace sandbox { @@ -32,5 +48,7 @@ class GuetzliSapiSandbox : public GuetzliSandbox { } }; -} // namespace sandbox -} // namespace guetzli \ No newline at end of file +} // namespace sandbox +} // namespace guetzli + +#endif // GUETZLI_SANDBOXED_GUETZLI_SANDBOX_H_ diff --git a/oss-internship-2020/guetzli/guetzli_sandboxed.cc b/oss-internship-2020/guetzli/guetzli_sandboxed.cc index 3393b8f..cb6543c 100644 --- a/oss-internship-2020/guetzli/guetzli_sandboxed.cc +++ b/oss-internship-2020/guetzli/guetzli_sandboxed.cc @@ -1,19 +1,34 @@ +// Copyright 2020 Google LLC +// +// 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. + +#include #include #include #include #include -#include #include -#include "guetzli_transaction.h" #include "sandboxed_api/sandbox2/util/fileops.h" #include "sandboxed_api/util/statusor.h" +#include "guetzli_transaction.h" + namespace { constexpr int kDefaultJPEGQuality = 95; -constexpr int kDefaultMemlimitMB = 6000; // in MB +constexpr int kDefaultMemlimitMB = 6000; void TerminateHandler() { fprintf(stderr, "Unhandled exception. Most likely insufficient memory available.\n" @@ -124,9 +139,9 @@ int main(int argc, const char** argv) { } } else { - fprintf(stderr, "%s\n", result.ToString().c_str()); // Use cerr instead ? + fprintf(stderr, "%s\n", result.ToString().c_str()); // Use cerr instead ? return 1; } return 0; -} \ No newline at end of file +} diff --git a/oss-internship-2020/guetzli/guetzli_transaction.cc b/oss-internship-2020/guetzli/guetzli_transaction.cc index 0f7e1bd..2613bdf 100644 --- a/oss-internship-2020/guetzli/guetzli_transaction.cc +++ b/oss-internship-2020/guetzli/guetzli_transaction.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// 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. + #include "guetzli_transaction.h" #include @@ -45,8 +59,8 @@ absl::Status GuetzliTransaction::Init() { "Error receiving remote FD: remote output fd is set to -1"); } - in_fd_.OwnLocalFd(false); // FDCloser will close local fd - out_fd_.OwnLocalFd(false); // FDCloser will close local fd + in_fd_.OwnLocalFd(false); // FDCloser will close local fd + out_fd_.OwnLocalFd(false); // FDCloser will close local fd return absl::OkStatus(); } @@ -62,14 +76,14 @@ absl::Status GuetzliTransaction::Main() { params_.memlimit_mb }; - auto result_status = image_type_ == ImageType::JPEG ? + auto result_status = image_type_ == ImageType::kJpeg ? api.ProcessJpeg(processing_params.PtrBefore(), output.PtrBoth()) : api.ProcessRgb(processing_params.PtrBefore(), output.PtrBoth()); if (!result_status.value_or(false)) { std::stringstream error_stream; error_stream << "Error processing " - << (image_type_ == ImageType::JPEG ? "jpeg" : "rgb") << " data" + << (image_type_ == ImageType::kJpeg ? "jpeg" : "rgb") << " data" << std::endl; return absl::FailedPreconditionError( @@ -113,8 +127,8 @@ sapi::StatusOr GuetzliTransaction::GetImageTypeFromFd(int fd) const { } return memcmp(read_buf, kPNGMagicBytes, sizeof(kPNGMagicBytes)) == 0 ? - ImageType::PNG : ImageType::JPEG; + ImageType::kPng : ImageType::kJpeg; } -} // namespace sandbox -} // namespace guetzli \ No newline at end of file +} // namespace sandbox +} // namespace guetzli diff --git a/oss-internship-2020/guetzli/guetzli_transaction.h b/oss-internship-2020/guetzli/guetzli_transaction.h index 8dbd7ec..2db681d 100644 --- a/oss-internship-2020/guetzli/guetzli_transaction.h +++ b/oss-internship-2020/guetzli/guetzli_transaction.h @@ -1,21 +1,34 @@ -#pragma once +// Copyright 2020 Google LLC +// +// 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. + +#ifndef GUETZLI_SANDBOXED_GUETZLI_TRANSACTION_H_ +#define GUETZLI_SANDBOXED_GUETZLI_TRANSACTION_H_ #include #include -#include "guetzli_sandbox.h" #include "sandboxed_api/transaction.h" #include "sandboxed_api/vars.h" +#include "guetzli_sandbox.h" + namespace guetzli { namespace sandbox { -constexpr int kDefaultTransactionRetryCount = 0; -constexpr uint64_t kMpixPixels = 1'000'000; - enum class ImageType { - JPEG, - PNG + kJpeg, + kPng }; struct TransactionParams { @@ -55,8 +68,13 @@ class GuetzliTransaction : public sapi::Transaction { const TransactionParams params_; sapi::v::Fd in_fd_; sapi::v::Fd out_fd_; - ImageType image_type_ = ImageType::JPEG; + ImageType image_type_ = ImageType::kJpeg; + + static const int kDefaultTransactionRetryCount = 0; + static const uint64_t kMpixPixels = 1'000'000; }; -} // namespace sandbox -} // namespace guetzli +} // namespace sandbox +} // namespace guetzli + +#endif // GUETZLI_SANDBOXED_GUETZLI_TRANSACTION_H_ diff --git a/oss-internship-2020/guetzli/tests/BUILD.bazel b/oss-internship-2020/guetzli/tests/BUILD.bazel index fa8b4ae..34f3f76 100644 --- a/oss-internship-2020/guetzli/tests/BUILD.bazel +++ b/oss-internship-2020/guetzli/tests/BUILD.bazel @@ -1,3 +1,17 @@ +# Copyright 2020 Google LLC +# +# 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. + cc_test( name = "transaction_tests", srcs = ["guetzli_transaction_test.cc"], diff --git a/oss-internship-2020/guetzli/tests/guetzli_sapi_test.cc b/oss-internship-2020/guetzli/tests/guetzli_sapi_test.cc index 2de4cb3..71439d7 100644 --- a/oss-internship-2020/guetzli/tests/guetzli_sapi_test.cc +++ b/oss-internship-2020/guetzli/tests/guetzli_sapi_test.cc @@ -1,16 +1,32 @@ +// Copyright 2020 Google LLC +// +// 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. + +#include +#include +#include +#include + +#include +#include +#include +#include + #include "gtest/gtest.h" -#include "guetzli_sandbox.h" #include "sandboxed_api/sandbox2/util/fileops.h" #include "sandboxed_api/vars.h" -#include -#include -#include -#include -#include -#include -#include -#include +#include "guetzli_sandbox.h" namespace guetzli { namespace sandbox { @@ -18,23 +34,23 @@ namespace tests { namespace { -constexpr const char* IN_PNG_FILENAME = "bees.png"; -constexpr const char* IN_JPG_FILENAME = "nature.jpg"; -constexpr const char* PNG_REFERENCE_FILENAME = "bees_reference.jpg"; -constexpr const char* JPG_REFERENCE_FILENAME = "nature_reference.jpg"; +constexpr const char* kInPngFilename = "bees.png"; +constexpr const char* kInJpegFilename = "nature.jpg"; +constexpr const char* kPngReferenceFilename = "bees_reference.jpg"; +constexpr const char* kJpegReferenceFIlename = "nature_reference.jpg"; -constexpr int PNG_EXPECTED_SIZE = 38'625; -constexpr int JPG_EXPECTED_SIZE = 10'816; +constexpr int kPngExpectedSize = 38'625; +constexpr int kJpegExpectedSize = 10'816; -constexpr int DEFAULT_QUALITY_TARGET = 95; -constexpr int DEFAULT_MEMLIMIT_MB = 6000; +constexpr int kDefaultQualityTarget = 95; +constexpr int kDefaultMemlimitMb = 6000; -constexpr const char* RELATIVE_PATH_TO_TESTDATA = +constexpr const char* kRelativePathToTestdata = "/guetzli_sandboxed/tests/testdata/"; std::string GetPathToInputFile(const char* filename) { return std::string(getenv("TEST_SRCDIR")) - + std::string(RELATIVE_PATH_TO_TESTDATA) + + std::string(kRelativePathToTestdata) + std::string(filename); } @@ -78,7 +94,7 @@ protected: // This test can take up to few minutes depending on your hardware TEST_F(GuetzliSapiTest, ProcessRGB) { - sapi::v::Fd in_fd(open(GetPathToInputFile(IN_PNG_FILENAME).c_str(), + sapi::v::Fd in_fd(open(GetPathToInputFile(kInPngFilename).c_str(), O_RDONLY)); ASSERT_TRUE(in_fd.GetValue() != -1) << "Error opening input file"; ASSERT_EQ(api_->sandbox()->TransferToSandboxee(&in_fd), absl::OkStatus()) @@ -87,17 +103,17 @@ TEST_F(GuetzliSapiTest, ProcessRGB) { sapi::v::Struct processing_params; *processing_params.mutable_data() = {in_fd.GetRemoteFd(), 0, - DEFAULT_QUALITY_TARGET, - DEFAULT_MEMLIMIT_MB + kDefaultQualityTarget, + kDefaultMemlimitMb }; sapi::v::LenVal output(0); auto processing_result = api_->ProcessRgb(processing_params.PtrBefore(), output.PtrBoth()); ASSERT_TRUE(processing_result.value_or(false)) << "Error processing rgb data"; - ASSERT_EQ(output.GetDataSize(), PNG_EXPECTED_SIZE) + ASSERT_EQ(output.GetDataSize(), kPngExpectedSize) << "Incorrect result data size"; std::string reference_data = - ReadFromFile(GetPathToInputFile(PNG_REFERENCE_FILENAME)); + ReadFromFile(GetPathToInputFile(kPngReferenceFilename)); ASSERT_EQ(output.GetDataSize(), reference_data.size()) << "Incorrect result data size"; ASSERT_TRUE(CompareBytesInLenValAndContainer(output, reference_data)) @@ -106,7 +122,7 @@ TEST_F(GuetzliSapiTest, ProcessRGB) { // This test can take up to few minutes depending on your hardware TEST_F(GuetzliSapiTest, ProcessJpeg) { - sapi::v::Fd in_fd(open(GetPathToInputFile(IN_JPG_FILENAME).c_str(), + sapi::v::Fd in_fd(open(GetPathToInputFile(kInJpegFilename).c_str(), O_RDONLY)); ASSERT_TRUE(in_fd.GetValue() != -1) << "Error opening input file"; ASSERT_EQ(api_->sandbox()->TransferToSandboxee(&in_fd), absl::OkStatus()) @@ -115,27 +131,23 @@ TEST_F(GuetzliSapiTest, ProcessJpeg) { sapi::v::Struct processing_params; *processing_params.mutable_data() = {in_fd.GetRemoteFd(), 0, - DEFAULT_QUALITY_TARGET, - DEFAULT_MEMLIMIT_MB + kDefaultQualityTarget, + kDefaultMemlimitMb }; sapi::v::LenVal output(0); auto processing_result = api_->ProcessJpeg(processing_params.PtrBefore(), output.PtrBoth()); ASSERT_TRUE(processing_result.value_or(false)) << "Error processing jpg data"; - ASSERT_EQ(output.GetDataSize(), JPG_EXPECTED_SIZE) + ASSERT_EQ(output.GetDataSize(), kJpegExpectedSize) << "Incorrect result data size"; std::string reference_data = - ReadFromFile(GetPathToInputFile(JPG_REFERENCE_FILENAME)); + ReadFromFile(GetPathToInputFile(kJpegReferenceFIlename)); ASSERT_EQ(output.GetDataSize(), reference_data.size()) << "Incorrect result data size"; ASSERT_TRUE(CompareBytesInLenValAndContainer(output, reference_data)) << "Processed data doesn't match reference output"; } -// TEST_F(GuetzliSapiTest, WriteDataToFd) { -// sapi::v::Fd fd(open(".", O_TMPFILE | O_RDWR, S_IRUSR | S_IWUSR)); -// } - -} // namespace tests -} // namespace sandbox -} // namespace guetzli \ No newline at end of file +} // namespace tests +} // namespace sandbox +} // namespace guetzli diff --git a/oss-internship-2020/guetzli/tests/guetzli_transaction_test.cc b/oss-internship-2020/guetzli/tests/guetzli_transaction_test.cc index b9e3a22..da76dde 100644 --- a/oss-internship-2020/guetzli/tests/guetzli_transaction_test.cc +++ b/oss-internship-2020/guetzli/tests/guetzli_transaction_test.cc @@ -1,14 +1,30 @@ -#include "gtest/gtest.h" -#include "guetzli_transaction.h" -#include "sandboxed_api/sandbox2/util/fileops.h" +// Copyright 2020 Google LLC +// +// 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. #include #include #include #include + #include -#include #include +#include + +#include "gtest/gtest.h" +#include "sandboxed_api/sandbox2/util/fileops.h" + +#include "guetzli_transaction.h" namespace guetzli { namespace sandbox { @@ -16,23 +32,23 @@ namespace tests { namespace { -constexpr const char* IN_PNG_FILENAME = "bees.png"; -constexpr const char* IN_JPG_FILENAME = "nature.jpg"; -constexpr const char* PNG_REFERENCE_FILENAME = "bees_reference.jpg"; -constexpr const char* JPG_REFERENCE_FILENAME = "nature_reference.jpg"; +constexpr const char* kInPngFilename = "bees.png"; +constexpr const char* kInJpegFilename = "nature.jpg"; +constexpr const char* kPngReferenceFilename = "bees_reference.jpg"; +constexpr const char* kJpegReferenceFIlename = "nature_reference.jpg"; -constexpr int PNG_EXPECTED_SIZE = 38'625; -constexpr int JPG_EXPECTED_SIZE = 10'816; +constexpr int kPngExpectedSize = 38'625; +constexpr int kJpegExpectedSize = 10'816; -constexpr int DEFAULT_QUALITY_TARGET = 95; -constexpr int DEFAULT_MEMLIMIT_MB = 6000; +constexpr int kDefaultQualityTarget = 95; +constexpr int kDefaultMemlimitMb = 6000; -constexpr const char* RELATIVE_PATH_TO_TESTDATA = +constexpr const char* kRelativePathToTestdata = "/guetzli_sandboxed/tests/testdata/"; std::string GetPathToInputFile(const char* filename) { return std::string(getenv("TEST_SRCDIR")) - + std::string(RELATIVE_PATH_TO_TESTDATA) + + std::string(kRelativePathToTestdata) + std::string(filename); } @@ -48,11 +64,11 @@ std::string ReadFromFile(const std::string& filename) { return result.str(); } -} // namespace +} // namespace TEST(GuetzliTransactionTest, TestTransactionJpg) { sandbox2::file_util::fileops::FDCloser in_fd_closer( - open(GetPathToInputFile(IN_JPG_FILENAME).c_str(), O_RDONLY)); + open(GetPathToInputFile(kInJpegFilename).c_str(), O_RDONLY)); ASSERT_TRUE(in_fd_closer.get() != -1) << "Error opening input jpg file"; sandbox2::file_util::fileops::FDCloser out_fd_closer( open(".", O_TMPFILE | O_RDWR, S_IRUSR | S_IWUSR)); @@ -61,8 +77,8 @@ TEST(GuetzliTransactionTest, TestTransactionJpg) { in_fd_closer.get(), out_fd_closer.get(), 0, - DEFAULT_QUALITY_TARGET, - DEFAULT_MEMLIMIT_MB + kDefaultQualityTarget, + kDefaultMemlimitMb }; { GuetzliTransaction transaction(std::move(params)); @@ -72,7 +88,7 @@ TEST(GuetzliTransactionTest, TestTransactionJpg) { } ASSERT_TRUE(fcntl(out_fd_closer.get(), F_GETFD) != -1 || errno != EBADF) << "Local output fd closed"; - auto reference_data = ReadFromFile(GetPathToInputFile(JPG_REFERENCE_FILENAME)); + auto reference_data = ReadFromFile(GetPathToInputFile(kJpegReferenceFIlename)); auto output_size = lseek(out_fd_closer.get(), 0, SEEK_END); ASSERT_EQ(reference_data.size(), output_size) << "Different sizes of reference and returned data"; @@ -90,7 +106,7 @@ TEST(GuetzliTransactionTest, TestTransactionJpg) { TEST(GuetzliTransactionTest, TestTransactionPng) { sandbox2::file_util::fileops::FDCloser in_fd_closer( - open(GetPathToInputFile(IN_PNG_FILENAME).c_str(), O_RDONLY)); + open(GetPathToInputFile(kInPngFilename).c_str(), O_RDONLY)); ASSERT_TRUE(in_fd_closer.get() != -1) << "Error opening input png file"; sandbox2::file_util::fileops::FDCloser out_fd_closer( open(".", O_TMPFILE | O_RDWR, S_IRUSR | S_IWUSR)); @@ -99,8 +115,8 @@ TEST(GuetzliTransactionTest, TestTransactionPng) { in_fd_closer.get(), out_fd_closer.get(), 0, - DEFAULT_QUALITY_TARGET, - DEFAULT_MEMLIMIT_MB + kDefaultQualityTarget, + kDefaultMemlimitMb }; { GuetzliTransaction transaction(std::move(params)); @@ -110,7 +126,7 @@ TEST(GuetzliTransactionTest, TestTransactionPng) { } ASSERT_TRUE(fcntl(out_fd_closer.get(), F_GETFD) != -1 || errno != EBADF) << "Local output fd closed"; - auto reference_data = ReadFromFile(GetPathToInputFile(PNG_REFERENCE_FILENAME)); + auto reference_data = ReadFromFile(GetPathToInputFile(kPngReferenceFilename)); auto output_size = lseek(out_fd_closer.get(), 0, SEEK_END); ASSERT_EQ(reference_data.size(), output_size) << "Different sizes of reference and returned data"; @@ -126,6 +142,6 @@ TEST(GuetzliTransactionTest, TestTransactionPng) { << "Returned data doesn't match refernce"; } -} // namespace tests -} // namespace sandbox -} // namespace guetzli \ No newline at end of file +} // namespace tests +} // namespace sandbox +} // namespace guetzli From 4cc2d40642d0d90eb62a544e9347629df681ec74 Mon Sep 17 00:00:00 2001 From: Doina Chiroiu Date: Thu, 13 Aug 2020 10:22:33 +0000 Subject: [PATCH 14/82] Simple version of sandbox From 5598690320af1dec58a3c7ad30fe3a6ff4fb9bd8 Mon Sep 17 00:00:00 2001 From: Katarzyna Miernikiewicz Date: Fri, 14 Aug 2020 10:38:27 +0000 Subject: [PATCH 15/82] added comments; added Google formatting style --- .../openjpeg/examples/CMakeLists.txt | 2 + .../openjpeg/examples/convert_helper.cc | 589 +++++++++--------- .../openjpeg/examples/convert_helper.h | 7 +- .../openjpeg/examples/decompress_example.cc | 172 +++-- 4 files changed, 401 insertions(+), 369 deletions(-) diff --git a/oss-internship-2020/openjpeg/examples/CMakeLists.txt b/oss-internship-2020/openjpeg/examples/CMakeLists.txt index b988086..f85e392 100644 --- a/oss-internship-2020/openjpeg/examples/CMakeLists.txt +++ b/oss-internship-2020/openjpeg/examples/CMakeLists.txt @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +# copy of one of the converting functions +# put here to omit the library's complex add_library(convert_helper STATIC convert_helper.h convert_helper.cc diff --git a/oss-internship-2020/openjpeg/examples/convert_helper.cc b/oss-internship-2020/openjpeg/examples/convert_helper.cc index eda1c3a..6763d4b 100644 --- a/oss-internship-2020/openjpeg/examples/convert_helper.cc +++ b/oss-internship-2020/openjpeg/examples/convert_helper.cc @@ -1,311 +1,342 @@ +/* + * The copyright in this software is being made available under the 2-clauses + * BSD License, included below. This software may be subject to other third + * party and contributor rights, including patent rights, and no such rights + * are granted under this license. + * + * Copyright (c) 2002-2014, Universite catholique de Louvain (UCL), Belgium + * Copyright (c) 2002-2014, Professor Benoit Macq + * Copyright (c) 2001-2003, David Janssens + * Copyright (c) 2002-2003, Yannick Verschueren + * Copyright (c) 2003-2007, Francois-Olivier Devaux + * Copyright (c) 2003-2014, Antonin Descampe + * Copyright (c) 2005, Herve Drolon, FreeImage Team + * Copyright (c) 2006-2007, Parvatha Elangovan + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS `AS IS' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ // copies of a few library tools #include "convert_helper.h" -const char* opj_version(void) -{ - return "2.3.1"; +const char *opj_version(void) { return "2.3.1"; } + +static int are_comps_similar(opj_image_t *image) { + unsigned int i; + for (i = 1; i < image->numcomps; i++) { + if (image->comps[0].dx != image->comps[i].dx || + image->comps[0].dy != image->comps[i].dy || + (i <= 2 && (image->comps[0].prec != image->comps[i].prec || + image->comps[0].sgnd != image->comps[i].sgnd))) { + return OPJ_FALSE; + } + } + return OPJ_TRUE; } -static int are_comps_similar(opj_image_t * image) -{ - unsigned int i; - for (i = 1; i < image->numcomps; i++) { - if (image->comps[0].dx != image->comps[i].dx || - image->comps[0].dy != image->comps[i].dy || - (i <= 2 && - (image->comps[0].prec != image->comps[i].prec || - image->comps[0].sgnd != image->comps[i].sgnd))) { - return OPJ_FALSE; +int imagetopnm(opj_image_t *image, const char *outfile, int force_split) { + int *red, *green, *blue, *alpha; + int wr, hr, max; + int i; + unsigned int compno, ncomp; + int adjustR, adjustG, adjustB, adjustA; + int fails, two, want_gray, has_alpha, triple; + int prec, v; + FILE *fdest = NULL; + const char *tmp = outfile; + char *destname; + + alpha = NULL; + + if ((prec = (int)image->comps[0].prec) > 16) { + fprintf(stderr, + "%s:%d:imagetopnm\n\tprecision %d is larger than 16" + "\n\t: refused.\n", + __FILE__, __LINE__, prec); + return 1; + } + + two = has_alpha = 0; + fails = 1; + ncomp = image->numcomps; + + while (*tmp) { + ++tmp; + } + tmp -= 2; + want_gray = (*tmp == 'g' || *tmp == 'G'); + ncomp = image->numcomps; + + if (want_gray) { + ncomp = 1; + } + + if ((force_split == 0) && ncomp >= 2 && are_comps_similar(image)) { + fdest = fopen(outfile, "wb"); + + if (!fdest) { + fprintf(stderr, "ERROR -> failed to open %s for writing\n", outfile); + return fails; + } + two = (prec > 8); + triple = (ncomp > 2); + wr = (int)image->comps[0].w; + hr = (int)image->comps[0].h; + max = (1 << prec) - 1; + has_alpha = (ncomp == 4 || ncomp == 2); + + red = image->comps[0].data; + if (red == NULL) { + fprintf(stderr, "imagetopnm: planes[%d] == NULL.\n", 0); + fprintf(stderr, "\tAborting\n"); + fclose(fdest); + return fails; + } + + if (triple) { + green = image->comps[1].data; + blue = image->comps[2].data; + for (i = 1; i <= 2; i++) { + if (image->comps[i].data == NULL) { + fprintf(stderr, "imagetopnm: planes[%d] == NULL.\n", i); + fprintf(stderr, "\tAborting\n"); + fclose(fdest); + return fails; } - } - return OPJ_TRUE; -} - -int imagetopnm(opj_image_t * image, const char *outfile, int force_split) -{ - int *red, *green, *blue, *alpha; - int wr, hr, max; - int i; - unsigned int compno, ncomp; - int adjustR, adjustG, adjustB, adjustA; - int fails, two, want_gray, has_alpha, triple; - int prec, v; - FILE *fdest = NULL; - const char *tmp = outfile; - char *destname; - - alpha = NULL; - - if ((prec = (int)image->comps[0].prec) > 16) { - fprintf(stderr, "%s:%d:imagetopnm\n\tprecision %d is larger than 16" - "\n\t: refused.\n", __FILE__, __LINE__, prec); - return 1; + } + } else { + green = blue = NULL; } - two = has_alpha = 0; - fails = 1; - ncomp = image->numcomps; + if (has_alpha) { + const char *tt = (triple ? "RGB_ALPHA" : "GRAYSCALE_ALPHA"); - while (*tmp) { - ++tmp; + fprintf(fdest, + "P7\n# OpenJPEG-%s\nWIDTH %d\nHEIGHT %d\nDEPTH %u\n" + "MAXVAL %d\nTUPLTYPE %s\nENDHDR\n", + opj_version(), wr, hr, ncomp, max, tt); + alpha = image->comps[ncomp - 1].data; + adjustA = (image->comps[ncomp - 1].sgnd + ? 1 << (image->comps[ncomp - 1].prec - 1) + : 0); + } else { + fprintf(fdest, "P6\n# OpenJPEG-%s\n%d %d\n%d\n", opj_version(), wr, hr, + max); + adjustA = 0; } - tmp -= 2; - want_gray = (*tmp == 'g' || *tmp == 'G'); - ncomp = image->numcomps; + adjustR = (image->comps[0].sgnd ? 1 << (image->comps[0].prec - 1) : 0); - if (want_gray) { - ncomp = 1; + if (triple) { + adjustG = (image->comps[1].sgnd ? 1 << (image->comps[1].prec - 1) : 0); + adjustB = (image->comps[2].sgnd ? 1 << (image->comps[2].prec - 1) : 0); + } else { + adjustG = adjustB = 0; } - if ((force_split == 0) && ncomp >= 2 && - are_comps_similar(image)) { - fdest = fopen(outfile, "wb"); - - if (!fdest) { - fprintf(stderr, "ERROR -> failed to open %s for writing\n", outfile); - return fails; + for (i = 0; i < wr * hr; ++i) { + if (two) { + v = *red + adjustR; + ++red; + if (v > 65535) { + v = 65535; + } else if (v < 0) { + v = 0; } - two = (prec > 8); - triple = (ncomp > 2); - wr = (int)image->comps[0].w; - hr = (int)image->comps[0].h; - max = (1 << prec) - 1; - has_alpha = (ncomp == 4 || ncomp == 2); - red = image->comps[0].data; - if (red == NULL) { - fprintf(stderr, - "imagetopnm: planes[%d] == NULL.\n", 0); - fprintf(stderr, "\tAborting\n"); - fclose(fdest); - return fails; - } + /* netpbm: */ + fprintf(fdest, "%c%c", (unsigned char)(v >> 8), (unsigned char)v); if (triple) { - green = image->comps[1].data; - blue = image->comps[2].data; - for (i = 1; i <= 2; i++) { - if (image->comps[i].data == NULL) { - fprintf(stderr, - "imagetopnm: planes[%d] == NULL.\n", i); - fprintf(stderr, "\tAborting\n"); - fclose(fdest); - return fails; - } - } - } else { - green = blue = NULL; - } + v = *green + adjustG; + ++green; + if (v > 65535) { + v = 65535; + } else if (v < 0) { + v = 0; + } + + /* netpbm: */ + fprintf(fdest, "%c%c", (unsigned char)(v >> 8), (unsigned char)v); + + v = *blue + adjustB; + ++blue; + if (v > 65535) { + v = 65535; + } else if (v < 0) { + v = 0; + } + + /* netpbm: */ + fprintf(fdest, "%c%c", (unsigned char)(v >> 8), (unsigned char)v); + + } /* if(triple) */ if (has_alpha) { - const char *tt = (triple ? "RGB_ALPHA" : "GRAYSCALE_ALPHA"); + v = *alpha + adjustA; + ++alpha; + if (v > 65535) { + v = 65535; + } else if (v < 0) { + v = 0; + } - fprintf(fdest, "P7\n# OpenJPEG-%s\nWIDTH %d\nHEIGHT %d\nDEPTH %u\n" - "MAXVAL %d\nTUPLTYPE %s\nENDHDR\n", opj_version(), - wr, hr, ncomp, max, tt); - alpha = image->comps[ncomp - 1].data; - adjustA = (image->comps[ncomp - 1].sgnd ? - 1 << (image->comps[ncomp - 1].prec - 1) : 0); - } else { - fprintf(fdest, "P6\n# OpenJPEG-%s\n%d %d\n%d\n", - opj_version(), wr, hr, max); - adjustA = 0; + /* netpbm: */ + fprintf(fdest, "%c%c", (unsigned char)(v >> 8), (unsigned char)v); } - adjustR = (image->comps[0].sgnd ? 1 << (image->comps[0].prec - 1) : 0); + continue; - if (triple) { - adjustG = (image->comps[1].sgnd ? 1 << (image->comps[1].prec - 1) : 0); - adjustB = (image->comps[2].sgnd ? 1 << (image->comps[2].prec - 1) : 0); - } else { - adjustG = adjustB = 0; + } /* if(two) */ + + /* prec <= 8: */ + v = *red++; + if (v > 255) { + v = 255; + } else if (v < 0) { + v = 0; + } + + fprintf(fdest, "%c", (unsigned char)v); + if (triple) { + v = *green++; + if (v > 255) { + v = 255; + } else if (v < 0) { + v = 0; } - for (i = 0; i < wr * hr; ++i) { - if (two) { - v = *red + adjustR; - ++red; - if (v > 65535) { - v = 65535; - } else if (v < 0) { - v = 0; - } - - /* netpbm: */ - fprintf(fdest, "%c%c", (unsigned char)(v >> 8), (unsigned char)v); - - if (triple) { - v = *green + adjustG; - ++green; - if (v > 65535) { - v = 65535; - } else if (v < 0) { - v = 0; - } - - /* netpbm: */ - fprintf(fdest, "%c%c", (unsigned char)(v >> 8), (unsigned char)v); - - v = *blue + adjustB; - ++blue; - if (v > 65535) { - v = 65535; - } else if (v < 0) { - v = 0; - } - - /* netpbm: */ - fprintf(fdest, "%c%c", (unsigned char)(v >> 8), (unsigned char)v); - - }/* if(triple) */ - - if (has_alpha) { - v = *alpha + adjustA; - ++alpha; - if (v > 65535) { - v = 65535; - } else if (v < 0) { - v = 0; - } - - /* netpbm: */ - fprintf(fdest, "%c%c", (unsigned char)(v >> 8), (unsigned char)v); - } - continue; - - } /* if(two) */ - - /* prec <= 8: */ - v = *red++; - if (v > 255) { - v = 255; - } else if (v < 0) { - v = 0; - } - - fprintf(fdest, "%c", (unsigned char)v); - if (triple) { - v = *green++; - if (v > 255) { - v = 255; - } else if (v < 0) { - v = 0; - } - - fprintf(fdest, "%c", (unsigned char)v); - v = *blue++; - if (v > 255) { - v = 255; - } else if (v < 0) { - v = 0; - } - - fprintf(fdest, "%c", (unsigned char)v); - } - if (has_alpha) { - v = *alpha++; - if (v > 255) { - v = 255; - } else if (v < 0) { - v = 0; - } - - fprintf(fdest, "%c", (unsigned char)v); - } - } /* for(i */ - - fclose(fdest); - return 0; - } - - /* YUV or MONO: */ - - if (image->numcomps > ncomp) { - fprintf(stderr, "WARNING -> [PGM file] Only the first component\n"); - fprintf(stderr, " is written to the file\n"); - } - destname = (char*)malloc(strlen(outfile) + 8); - if (destname == NULL) { - fprintf(stderr, "imagetopnm: memory out\n"); - return 1; - } - for (compno = 0; compno < ncomp; compno++) { - if (ncomp > 1) { - /*sprintf(destname, "%d.%s", compno, outfile);*/ - const size_t olen = strlen(outfile); - const size_t dotpos = olen - 4; - - strncpy(destname, outfile, dotpos); - sprintf(destname + dotpos, "_%u.pgm", compno); - } else { - sprintf(destname, "%s", outfile); + fprintf(fdest, "%c", (unsigned char)v); + v = *blue++; + if (v > 255) { + v = 255; + } else if (v < 0) { + v = 0; } - fdest = fopen(destname, "wb"); - if (!fdest) { - fprintf(stderr, "ERROR -> failed to open %s for writing\n", destname); - free(destname); - return 1; - } - wr = (int)image->comps[compno].w; - hr = (int)image->comps[compno].h; - prec = (int)image->comps[compno].prec; - max = (1 << prec) - 1; - - fprintf(fdest, "P5\n#OpenJPEG-%s\n%d %d\n%d\n", - opj_version(), wr, hr, max); - - red = image->comps[compno].data; - - if (!red) { - fclose(fdest); - continue; + fprintf(fdest, "%c", (unsigned char)v); + } + if (has_alpha) { + v = *alpha++; + if (v > 255) { + v = 255; + } else if (v < 0) { + v = 0; } - adjustR = - (image->comps[compno].sgnd ? 1 << (image->comps[compno].prec - 1) : 0); - - if (prec > 8) { - for (i = 0; i < wr * hr; i++) { - v = *red + adjustR; - ++red; - if (v > 65535) { - v = 65535; - } else if (v < 0) { - v = 0; - } - - /* netpbm: */ - fprintf(fdest, "%c%c", (unsigned char)(v >> 8), (unsigned char)v); - - if (has_alpha) { - v = *alpha++; - if (v > 65535) { - v = 65535; - } else if (v < 0) { - v = 0; - } - - /* netpbm: */ - fprintf(fdest, "%c%c", (unsigned char)(v >> 8), (unsigned char)v); - } - }/* for(i */ - } else { /* prec <= 8 */ - for (i = 0; i < wr * hr; ++i) { - v = *red + adjustR; - ++red; - if (v > 255) { - v = 255; - } else if (v < 0) { - v = 0; - } - fprintf(fdest, "%c", (unsigned char)v); - } - } - fclose(fdest); - } /* for (compno */ - free(destname); + fprintf(fdest, "%c", (unsigned char)v); + } + } /* for(i */ + fclose(fdest); return 0; -}/* imagetopnm() */ + } + + /* YUV or MONO: */ + + if (image->numcomps > ncomp) { + fprintf(stderr, "WARNING -> [PGM file] Only the first component\n"); + fprintf(stderr, " is written to the file\n"); + } + destname = (char *)malloc(strlen(outfile) + 8); + if (destname == NULL) { + fprintf(stderr, "imagetopnm: memory out\n"); + return 1; + } + for (compno = 0; compno < ncomp; compno++) { + if (ncomp > 1) { + /*sprintf(destname, "%d.%s", compno, outfile);*/ + const size_t olen = strlen(outfile); + const size_t dotpos = olen - 4; + + strncpy(destname, outfile, dotpos); + sprintf(destname + dotpos, "_%u.pgm", compno); + } else { + sprintf(destname, "%s", outfile); + } + + fdest = fopen(destname, "wb"); + if (!fdest) { + fprintf(stderr, "ERROR -> failed to open %s for writing\n", destname); + free(destname); + return 1; + } + wr = (int)image->comps[compno].w; + hr = (int)image->comps[compno].h; + prec = (int)image->comps[compno].prec; + max = (1 << prec) - 1; + + fprintf(fdest, "P5\n#OpenJPEG-%s\n%d %d\n%d\n", opj_version(), wr, hr, max); + + red = image->comps[compno].data; + + if (!red) { + fclose(fdest); + continue; + } + + adjustR = + (image->comps[compno].sgnd ? 1 << (image->comps[compno].prec - 1) : 0); + + if (prec > 8) { + for (i = 0; i < wr * hr; i++) { + v = *red + adjustR; + ++red; + if (v > 65535) { + v = 65535; + } else if (v < 0) { + v = 0; + } + + /* netpbm: */ + fprintf(fdest, "%c%c", (unsigned char)(v >> 8), (unsigned char)v); + + if (has_alpha) { + v = *alpha++; + if (v > 65535) { + v = 65535; + } else if (v < 0) { + v = 0; + } + + /* netpbm: */ + fprintf(fdest, "%c%c", (unsigned char)(v >> 8), (unsigned char)v); + } + } /* for(i */ + } else { /* prec <= 8 */ + for (i = 0; i < wr * hr; ++i) { + v = *red + adjustR; + ++red; + if (v > 255) { + v = 255; + } else if (v < 0) { + v = 0; + } + fprintf(fdest, "%c", (unsigned char)v); + } + } + fclose(fdest); + } /* for (compno */ + free(destname); + + return 0; +} /* imagetopnm() */ diff --git a/oss-internship-2020/openjpeg/examples/convert_helper.h b/oss-internship-2020/openjpeg/examples/convert_helper.h index 3c21b32..517e212 100644 --- a/oss-internship-2020/openjpeg/examples/convert_helper.h +++ b/oss-internship-2020/openjpeg/examples/convert_helper.h @@ -1,8 +1,11 @@ +// imagetopnm and the two functions it calls internaly are copied from the +// library's tools; from openjpeg/src/bin/jp2/convert.c + #include "openjp2_sapi.sapi.h" #define OPJ_TRUE 1 #define OPJ_FALSE 0 const char* opj_version(void); -int imagetopnm(opj_image_t * image, const char *outfile, int force_split); -static int are_comps_similar(opj_image_t * image); +static int are_comps_similar(opj_image_t* image); +int imagetopnm(opj_image_t* image, const char* outfile, int force_split); diff --git a/oss-internship-2020/openjpeg/examples/decompress_example.cc b/oss-internship-2020/openjpeg/examples/decompress_example.cc index 8189ccf..0c9ce22 100644 --- a/oss-internship-2020/openjpeg/examples/decompress_example.cc +++ b/oss-internship-2020/openjpeg/examples/decompress_example.cc @@ -12,19 +12,16 @@ // See the License for the specific language governing permissions and // limitations under the License. - -// Perform decompression from *.jp2 to *.pnm format - +// Perform decompression from *.jp2 to *.pnm format #include #include -#include -#include #include +#include +#include #include "convert_helper.h" - #include "openjp2_sapi.sapi.h" #include "sandboxed_api/util/flag.h" @@ -33,8 +30,7 @@ class Opj_image_t : public sapi::v::Struct {}; class Openjp2SapiSandbox : public Openjp2Sandbox { public: - Openjp2SapiSandbox(const std::string& in_file) - : in_file_(in_file) {} + Openjp2SapiSandbox(const std::string& in_file) : in_file_(in_file) {} std::unique_ptr ModifyPolicy( sandbox2::PolicyBuilder*) override { @@ -47,9 +43,9 @@ class Openjp2SapiSandbox : public Openjp2Sandbox { .AllowSystemMalloc() .AllowExit() .AllowSyscalls({ - __NR_futex, - __NR_close, - __NR_lseek, + __NR_futex, + __NR_close, + __NR_lseek, }) .AddFile(in_file_) .BuildOrDie(); @@ -59,105 +55,105 @@ class Openjp2SapiSandbox : public Openjp2Sandbox { std::string in_file_; }; - int main(int argc, char* argv[]) { - gflags::ParseCommandLineFlags(&argc, &argv, true); + gflags::ParseCommandLineFlags(&argc, &argv, true); - if (argc != 3) { - std::cerr << "usage: " - << basename(argv[0]) - << " absolute/path/to/INPUT.jp2" - << " absolute/path/to/OUTPUT.pnm\n"; - return EXIT_FAILURE; - } + if (argc != 3) { + std::cerr << "usage: " << basename(argv[0]) << " absolute/path/to/INPUT.jp2" + << " absolute/path/to/OUTPUT.pnm\n"; + return EXIT_FAILURE; + } - std::string in_file(argv[1]); + std::string in_file(argv[1]); - Openjp2SapiSandbox sandbox(in_file); - absl::Status status = sandbox.Init(); - assert(status.ok()); + // initialize sandbox + Openjp2SapiSandbox sandbox(in_file); + absl::Status status = sandbox.Init(); + assert(status.ok()); - Openjp2Api api(&sandbox); - sapi::v::ConstCStr in_file_v(in_file.c_str()); + Openjp2Api api(&sandbox); + sapi::v::ConstCStr in_file_v(in_file.c_str()); - sapi::StatusOr stream_status - = api.opj_stream_create_default_file_stream(in_file_v.PtrBefore(), 1); - assert(stream_status.ok()); - void* stream_status_value = stream_status.value(); - sapi::v::RemotePtr stream_pointer(stream_status_value); + // initialize library's main data-holders + sapi::StatusOr stream_status = + api.opj_stream_create_default_file_stream(in_file_v.PtrBefore(), 1); + assert(stream_status.ok()); + void* stream_status_value = stream_status.value(); + sapi::v::RemotePtr stream_pointer(stream_status_value); - sapi::StatusOr codec_status - = api.opj_create_decompress(OPJ_CODEC_JP2); - assert(codec_status.ok()); - void* codec_status_value = codec_status.value(); - sapi::v::RemotePtr codec_pointer(codec_status_value); + sapi::StatusOr codec_status = + api.opj_create_decompress(OPJ_CODEC_JP2); + assert(codec_status.ok()); + void* codec_status_value = codec_status.value(); + sapi::v::RemotePtr codec_pointer(codec_status_value); - Parameters parameters; - status = api.opj_set_default_decoder_parameters(parameters.PtrBoth()); - assert(status.ok()); + Parameters parameters; + status = api.opj_set_default_decoder_parameters(parameters.PtrBoth()); + assert(status.ok()); - sapi::StatusOr bool_status - = api.opj_setup_decoder(&codec_pointer, parameters.PtrBefore()); - assert(bool_status.ok()); - assert(bool_status.value()); + sapi::StatusOr bool_status = + api.opj_setup_decoder(&codec_pointer, parameters.PtrBefore()); + assert(bool_status.ok()); + assert(bool_status.value()); - sapi::v::GenericPtr image_pointer; - bool_status = api.opj_read_header(&stream_pointer, - &codec_pointer, - image_pointer.PtrAfter()); - assert(bool_status.ok()); - assert(bool_status.value()); + // start reading image from the input file + sapi::v::GenericPtr image_pointer; + bool_status = api.opj_read_header(&stream_pointer, &codec_pointer, + image_pointer.PtrAfter()); + assert(bool_status.ok()); + assert(bool_status.value()); - Opj_image_t image; - image.SetRemote((void*)image_pointer.GetValue()); - assert(sandbox.TransferFromSandboxee(&image).ok()); + Opj_image_t image; + image.SetRemote((void*)image_pointer.GetValue()); + assert(sandbox.TransferFromSandboxee(&image).ok()); - bool_status = api.opj_decode(&codec_pointer, - &stream_pointer, - (sapi::v::Ptr*)&image_pointer); - assert(bool_status.ok()); - assert(bool_status.value()); + bool_status = api.opj_decode(&codec_pointer, &stream_pointer, + (sapi::v::Ptr*)&image_pointer); + assert(bool_status.ok()); + assert(bool_status.value()); - bool_status = api.opj_end_decompress(&codec_pointer, &stream_pointer); - assert(bool_status.ok()); - assert(bool_status.value()); + bool_status = api.opj_end_decompress(&codec_pointer, &stream_pointer); + assert(bool_status.ok()); + assert(bool_status.value()); - status = api.opj_stream_destroy(&stream_pointer); - assert(status.ok()); + int components = image.data().numcomps; - int components = image.data().numcomps; + // transfer the read data to the main process + sapi::v::Array image_components(components); + image_components.SetRemote(image.data().comps); + assert(sandbox.TransferFromSandboxee(&image_components).ok()); - sapi::v::Array image_components(components); - image_components.SetRemote(image.data().comps); - assert(sandbox.TransferFromSandboxee(&image_components).ok()); + image.mutable_data()->comps = (opj_image_comp_t*)image_components.GetLocal(); - image.mutable_data()->comps - = (opj_image_comp_t*)image_components.GetLocal(); + int width = (int)image.data().comps[0].w; + int height = (int)image.data().comps[0].h; - int width = (int)image.data().comps[0].w; - int height = (int)image.data().comps[0].h; + OPJ_INT32 data[components][width * height]; + sapi::v::Array image_components_data(width * height); - int data[components][width * height]; - sapi::v::Array image_components_data(width * height); + for (int i = 0; i < components; i++) { + image_components_data.SetRemote(image.data().comps[i].data); + assert(sandbox.TransferFromSandboxee(&image_components_data).ok()); + for (int j = 0; j < width * height; j++) { + data[i][j] = image_components_data[j]; + } + image_components[i].data = data[i]; + } - for (int i = 0; i < components; i++) { - image_components_data.SetRemote(image.data().comps[i].data); - assert(sandbox.TransferFromSandboxee(&image_components_data).ok()); - for (int j = 0; j < width * height; j++) { - data[i][j] = image_components_data[j]; - } - image_components[i].data = data[i]; - } + // convert the image to the desired format and save it to the file + int error = imagetopnm((opj_image_t*)image.GetLocal(), argv[2], 0); + assert(error == 0); - int error = imagetopnm((opj_image_t*)image.GetLocal(), argv[2], 0); - assert(error == 0); + // cleanup + sapi::v::RemotePtr remote_image_pointer(image.GetRemote()); + status = api.opj_image_destroy(&remote_image_pointer); + assert(status.ok()); - status = api.opj_destroy_codec(&codec_pointer); - assert(status.ok()); + status = api.opj_stream_destroy(&stream_pointer); + assert(status.ok()); - sapi::v::RemotePtr remote_image_pointer(image.GetRemote()); - status = api.opj_image_destroy(&remote_image_pointer); - assert(status.ok()); + status = api.opj_destroy_codec(&codec_pointer); + assert(status.ok()); - return EXIT_SUCCESS; + return EXIT_SUCCESS; } From 80dd8733755bdb2bde2452e929868c69407a0350 Mon Sep 17 00:00:00 2001 From: doinachiroiu Date: Mon, 17 Aug 2020 11:20:56 +0000 Subject: [PATCH 16/82] Initial version sandbox --- oss-internship-2020/pffft/CMakeLists.txt | 91 + oss-internship-2020/pffft/Makefile | 15 + oss-internship-2020/pffft/README.txt | 416 +++ oss-internship-2020/pffft/fftpack.c | 3112 +++++++++++++++++ oss-internship-2020/pffft/fftpack.h | 799 +++++ oss-internship-2020/pffft/myNotes.txt | 101 + oss-internship-2020/pffft/pffft.c | 1881 ++++++++++ oss-internship-2020/pffft/pffft.h | 177 + oss-internship-2020/pffft/test_pffft.c | 419 +++ .../pffft/test_pffft_sandboxed.cc | 150 + 10 files changed, 7161 insertions(+) create mode 100644 oss-internship-2020/pffft/CMakeLists.txt create mode 100644 oss-internship-2020/pffft/Makefile create mode 100644 oss-internship-2020/pffft/README.txt create mode 100644 oss-internship-2020/pffft/fftpack.c create mode 100644 oss-internship-2020/pffft/fftpack.h create mode 100644 oss-internship-2020/pffft/myNotes.txt create mode 100644 oss-internship-2020/pffft/pffft.c create mode 100644 oss-internship-2020/pffft/pffft.h create mode 100644 oss-internship-2020/pffft/test_pffft.c create mode 100644 oss-internship-2020/pffft/test_pffft_sandboxed.cc diff --git a/oss-internship-2020/pffft/CMakeLists.txt b/oss-internship-2020/pffft/CMakeLists.txt new file mode 100644 index 0000000..c0d62cb --- /dev/null +++ b/oss-internship-2020/pffft/CMakeLists.txt @@ -0,0 +1,91 @@ +cmake_minimum_required(VERSION 3.10) + +project(pffft CXX C) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED True) + +add_library(pffft STATIC + pffft.c + pffft.h + fftpack.c + fftpack.h +) + +add_executable(pffft_main + test_pffft.c +) + +target_link_libraries(pffft_main PRIVATE + pffft +) + +set(MATH_LIBS "") +include(CheckLibraryExists) +check_library_exists(m sin "" LIBM) +if(LIBM) + list(APPEND MATH_LIBS "m") +endif() + +target_link_libraries(pffft PUBLIC ${MATH_LIBS}) + + +# Adding dependencies +set(SAPI_ROOT "/usr/local/google/home/inach/sandboxed-api" CACHE PATH "Path to the Sandboxed API source tree") +# Then configure: +# mkdir -p build && cd build +# cmake .. -G Ninja -DSAPI_ROOT=$HOME/sapi_root + +set(SAPI_ENABLE_EXAMPLES OFF CACHE BOOL "") +set(SAPI_ENABLE_TESTS OFF CACHE BOOL "") +add_subdirectory("${SAPI_ROOT}" + "${CMAKE_BINARY_DIR}/sandboxed-api-build" + # Omit this to have the full Sandboxed API in IDE + EXCLUDE_FROM_ALL) + +add_sapi_library(pffft_sapi + FUNCTIONS pffft_new_setup + pffft_destroy_setup + pffft_transform + pffft_transform_ordered + pffft_zreorder + pffft_zconvolve_accumulate + pffft_aligned_malloc + pffft_aligned_free + pffft_simd_size + cffti + cfftf + cfftb + rffti + rfftf + rfftb + cosqi + cosqf + cosqb + costi + cost + sinqi + sinqb + sinqf + sinti + sint + + INPUTS pffft.h fftpack.h + LIBRARY pffft + LIBRARY_NAME pffft + + NAMESPACE "" +) + +target_include_directories(pffft_sapi INTERFACE + "${PROJECT_BINARY_DIR}" +) + +add_executable(pffft_sandboxed + test_pffft_sandboxed.cc +) + +target_link_libraries(pffft_sandboxed PRIVATE + pffft_sapi + sapi::sapi +) \ No newline at end of file diff --git a/oss-internship-2020/pffft/Makefile b/oss-internship-2020/pffft/Makefile new file mode 100644 index 0000000..326fd90 --- /dev/null +++ b/oss-internship-2020/pffft/Makefile @@ -0,0 +1,15 @@ +CXXFLAGS ?= -std=c++17 + +pffft_main: test_pffft.o libpffft.a + $(CXX) -o $@ $(CXXFLAGS) $(LDFLAGS) $^ + +libpffft.a: pffft.o fftpack.o + ar rcs $@ $^ + +pffft.c: pffft.h + +fftpack.c: fftpack.h + +.PHONY: clean +clean: + rm -f *.o *.a pffft_main \ No newline at end of file diff --git a/oss-internship-2020/pffft/README.txt b/oss-internship-2020/pffft/README.txt new file mode 100644 index 0000000..ee20b42 --- /dev/null +++ b/oss-internship-2020/pffft/README.txt @@ -0,0 +1,416 @@ +PFFFT: a pretty fast FFT. + +TL;DR +-- + +PFFFT does 1D Fast Fourier Transforms, of single precision real and +complex vectors. It tries do it fast, it tries to be correct, and it +tries to be small. Computations do take advantage of SSE1 instructions +on x86 cpus, Altivec on powerpc cpus, and NEON on ARM cpus. The +license is BSD-like. + + +Why does it exist: +-- + +I was in search of a good performing FFT library , preferably very +small and with a very liberal license. + +When one says "fft library", FFTW ("Fastest Fourier Transform in the +West") is probably the first name that comes to mind -- I guess that +99% of open-source projects that need a FFT do use FFTW, and are happy +with it. However, it is quite a large library , which does everything +fft related (2d transforms, 3d transforms, other transformations such +as discrete cosine , or fast hartley). And it is licensed under the +GNU GPL , which means that it cannot be used in non open-source +products. + +An alternative to FFTW that is really small, is the venerable FFTPACK +v4, which is available on NETLIB. A more recent version (v5) exists, +but it is larger as it deals with multi-dimensional transforms. This +is a library that is written in FORTRAN 77, a language that is now +considered as a bit antiquated by many. FFTPACKv4 was written in 1985, +by Dr Paul Swarztrauber of NCAR, more than 25 years ago ! And despite +its age, benchmarks show it that it still a very good performing FFT +library, see for example the 1d single precision benchmarks here: +http://www.fftw.org/speed/opteron-2.2GHz-32bit/ . It is however not +competitive with the fastest ones, such as FFTW, Intel MKL, AMD ACML, +Apple vDSP. The reason for that is that those libraries do take +advantage of the SSE SIMD instructions available on Intel CPUs, +available since the days of the Pentium III. These instructions deal +with small vectors of 4 floats at a time, instead of a single float +for a traditionnal FPU, so when using these instructions one may expect +a 4-fold performance improvement. + +The idea was to take this fortran fftpack v4 code, translate to C, +modify it to deal with those SSE instructions, and check that the +final performance is not completely ridiculous when compared to other +SIMD FFT libraries. Translation to C was performed with f2c ( +http://www.netlib.org/f2c/ ). The resulting file was a bit edited in +order to remove the thousands of gotos that were introduced by +f2c. You will find the fftpack.h and fftpack.c sources in the +repository, this a complete translation of +http://www.netlib.org/fftpack/ , with the discrete cosine transform +and the test program. There is no license information in the netlib +repository, but it was confirmed to me by the fftpack v5 curators that +the same terms do apply to fftpack v4: +http://www.cisl.ucar.edu/css/software/fftpack5/ftpk.html . This is a +"BSD-like" license, it is compatible with proprietary projects. + +Adapting fftpack to deal with the SIMD 4-element vectors instead of +scalar single precision numbers was more complex than I originally +thought, especially with the real transforms, and I ended up writing +more code than I planned.. + + +The code: +-- + +Only two files, in good old C, pffft.c and pffft.h . The API is very +very simple, just make sure that you read the comments in pffft.h. + + +Comparison with other FFTs: +-- + +The idea was not to break speed records, but to get a decently fast +fft that is at least 50% as fast as the fastest FFT -- especially on +slowest computers . I'm more focused on getting the best performance +on slow cpus (Atom, Intel Core 1, old Athlons, ARM Cortex-A9...), than +on getting top performance on today fastest cpus. + +It can be used in a real-time context as the fft functions do not +perform any memory allocation -- that is why they accept a 'work' +array in their arguments. + +It is also a bit focused on performing 1D convolutions, that is why it +provides "unordered" FFTs , and a fourier domain convolution +operation. + + +Benchmark results (cpu tested: core i7 2600, core 2 quad, core 1 duo, atom N270, cortex-A9, cortex-A15, A8X) +-- + +The benchmark shows the performance of various fft implementations measured in +MFlops, with the number of floating point operations being defined as 5Nlog2(N) +for a length N complex fft, and 2.5*Nlog2(N) for a real fft. +See http://www.fftw.org/speed/method.html for an explanation of these formulas. + +MacOS Lion, gcc 4.2, 64-bit, fftw 3.3 on a 3.4 GHz core i7 2600 + +Built with: + + gcc-4.2 -o test_pffft -arch x86_64 -O3 -Wall -W pffft.c test_pffft.c fftpack.c -L/usr/local/lib -I/usr/local/include/ -DHAVE_VECLIB -framework veclib -DHAVE_FFTW -lfftw3f + +| input len |real FFTPack| real vDSP | real FFTW | real PFFFT | |cplx FFTPack| cplx vDSP | cplx FFTW | cplx PFFFT | +|-----------+------------+------------+------------+------------| |------------+------------+------------+------------| +| 64 | 2816 | 8596 | 7329 | 8187 | | 2887 | 14898 | 14668 | 11108 | +| 96 | 3298 | n/a | 8378 | 7727 | | 3953 | n/a | 15680 | 10878 | +| 128 | 3507 | 11575 | 9266 | 10108 | | 4233 | 17598 | 16427 | 12000 | +| 160 | 3391 | n/a | 9838 | 10711 | | 4220 | n/a | 16653 | 11187 | +| 192 | 3919 | n/a | 9868 | 10956 | | 4297 | n/a | 15770 | 12540 | +| 256 | 4283 | 13179 | 10694 | 13128 | | 4545 | 19550 | 16350 | 13822 | +| 384 | 3136 | n/a | 10810 | 12061 | | 3600 | n/a | 16103 | 13240 | +| 480 | 3477 | n/a | 10632 | 12074 | | 3536 | n/a | 11630 | 12522 | +| 512 | 3783 | 15141 | 11267 | 13838 | | 3649 | 20002 | 16560 | 13580 | +| 640 | 3639 | n/a | 11164 | 13946 | | 3695 | n/a | 15416 | 13890 | +| 768 | 3800 | n/a | 11245 | 13495 | | 3590 | n/a | 15802 | 14552 | +| 800 | 3440 | n/a | 10499 | 13301 | | 3659 | n/a | 12056 | 13268 | +| 1024 | 3924 | 15605 | 11450 | 15339 | | 3769 | 20963 | 13941 | 15467 | +| 2048 | 4518 | 16195 | 11551 | 15532 | | 4258 | 20413 | 13723 | 15042 | +| 2400 | 4294 | n/a | 10685 | 13078 | | 4093 | n/a | 12777 | 13119 | +| 4096 | 4750 | 16596 | 11672 | 15817 | | 4157 | 19662 | 14316 | 14336 | +| 8192 | 3820 | 16227 | 11084 | 12555 | | 3691 | 18132 | 12102 | 13813 | +| 9216 | 3864 | n/a | 10254 | 12870 | | 3586 | n/a | 12119 | 13994 | +| 16384 | 3822 | 15123 | 10454 | 12822 | | 3613 | 16874 | 12370 | 13881 | +| 32768 | 4175 | 14512 | 10662 | 11095 | | 3881 | 14702 | 11619 | 11524 | +| 262144 | 3317 | 11429 | 6269 | 9517 | | 2810 | 11729 | 7757 | 10179 | +| 1048576 | 2913 | 10551 | 4730 | 5867 | | 2661 | 7881 | 3520 | 5350 | +|-----------+------------+------------+------------+------------| |------------+------------+------------+------------| + + +Debian 6, gcc 4.4.5, 64-bit, fftw 3.3.1 on a 3.4 GHz core i7 2600 + +Built with: +gcc -o test_pffft -DHAVE_FFTW -msse2 -O3 -Wall -W pffft.c test_pffft.c fftpack.c -L$HOME/local/lib -I$HOME/local/include/ -lfftw3f -lm + +| N (input length) | real FFTPack | real FFTW | real PFFFT | | cplx FFTPack | cplx FFTW | cplx PFFFT | +|------------------+--------------+--------------+--------------| |--------------+--------------+--------------| +| 64 | 3840 | 7680 | 8777 | | 4389 | 20480 | 11171 | +| 96 | 4214 | 9633 | 8429 | | 4816 | 22477 | 11238 | +| 128 | 3584 | 10240 | 10240 | | 5120 | 23893 | 11947 | +| 192 | 4854 | 11095 | 12945 | | 4854 | 22191 | 14121 | +| 256 | 4096 | 11703 | 16384 | | 5120 | 23406 | 13653 | +| 384 | 4395 | 14651 | 12558 | | 4884 | 19535 | 14651 | +| 512 | 5760 | 13166 | 15360 | | 4608 | 23040 | 15360 | +| 768 | 4907 | 14020 | 16357 | | 4461 | 19628 | 14020 | +| 1024 | 5120 | 14629 | 14629 | | 5120 | 20480 | 15754 | +| 2048 | 5632 | 14080 | 18773 | | 4693 | 12516 | 16091 | +| 4096 | 5120 | 13653 | 17554 | | 4726 | 7680 | 14456 | +| 8192 | 4160 | 7396 | 13312 | | 4437 | 14791 | 13312 | +| 9216 | 4210 | 6124 | 13473 | | 4491 | 7282 | 14970 | +| 16384 | 3976 | 11010 | 14313 | | 4210 | 11450 | 13631 | +| 32768 | 4260 | 10224 | 10954 | | 4260 | 6816 | 11797 | +| 262144 | 3736 | 6896 | 9961 | | 2359 | 8965 | 9437 | +| 1048576 | 2796 | 4534 | 6453 | | 1864 | 3078 | 5592 | +|------------------+--------------+--------------+--------------| |--------------+--------------+--------------| + + + +MacOS Snow Leopard, gcc 4.0, 32-bit, fftw 3.3 on a 1.83 GHz core 1 duo + +Built with: + + gcc -o test_pffft -DHAVE_FFTW -DHAVE_VECLIB -O3 -Wall -W pffft.c test_pffft.c fftpack.c -L/usr/local/lib -I/usr/local/include/ -lfftw3f -framework veclib + +| input len |real FFTPack| real vDSP | real FFTW | real PFFFT | |cplx FFTPack| cplx vDSP | cplx FFTW | cplx PFFFT | +|-----------+------------+------------+------------+------------| |------------+------------+------------+------------| +| 64 | 745 | 2145 | 1706 | 2028 | | 961 | 3356 | 3313 | 2300 | +| 96 | 877 | n/a | 1976 | 1978 | | 1059 | n/a | 3333 | 2233 | +| 128 | 951 | 2808 | 2213 | 2279 | | 1202 | 3803 | 3739 | 2494 | +| 192 | 1002 | n/a | 2456 | 2429 | | 1186 | n/a | 3701 | 2508 | +| 256 | 1065 | 3205 | 2641 | 2793 | | 1302 | 4013 | 3912 | 2663 | +| 384 | 845 | n/a | 2759 | 2499 | | 948 | n/a | 3729 | 2504 | +| 512 | 900 | 3476 | 2956 | 2759 | | 974 | 4057 | 3954 | 2645 | +| 768 | 910 | n/a | 2912 | 2737 | | 975 | n/a | 3837 | 2614 | +| 1024 | 936 | 3583 | 3107 | 3009 | | 1006 | 4124 | 3821 | 2697 | +| 2048 | 1057 | 3585 | 3091 | 2837 | | 1089 | 3889 | 3701 | 2513 | +| 4096 | 1083 | 3524 | 3092 | 2733 | | 1039 | 3617 | 3462 | 2364 | +| 8192 | 874 | 3252 | 2967 | 2363 | | 911 | 3106 | 2789 | 2302 | +| 9216 | 898 | n/a | 2420 | 2290 | | 865 | n/a | 2676 | 2204 | +| 16384 | 903 | 2892 | 2506 | 2421 | | 899 | 3026 | 2797 | 2289 | +| 32768 | 965 | 2837 | 2550 | 2358 | | 920 | 2922 | 2763 | 2240 | +| 262144 | 738 | 2422 | 1589 | 1708 | | 610 | 2038 | 1436 | 1091 | +| 1048576 | 528 | 1207 | 845 | 880 | | 606 | 1020 | 669 | 1036 | +|-----------+------------+------------+------------+------------| |------------+------------+------------+------------| + + + +Ubuntu 11.04, gcc 4.5, 32-bit, fftw 3.2 on a 2.66 core 2 quad + +Built with: +gcc -o test_pffft -DHAVE_FFTW -msse -mfpmath=sse -O3 -Wall -W pffft.c test_pffft.c fftpack.c -L/usr/local/lib -I/usr/local/include/ -lfftw3f -lm + +| input len |real FFTPack| real FFTW | real PFFFT | |cplx FFTPack| cplx FFTW | cplx PFFFT | +|-----------+------------+------------+------------| |------------+------------+------------| +| 64 | 1920 | 3614 | 5120 | | 2194 | 7680 | 6467 | +| 96 | 1873 | 3549 | 5187 | | 2107 | 8429 | 5863 | +| 128 | 2240 | 3773 | 5514 | | 2560 | 7964 | 6827 | +| 192 | 1765 | 4569 | 7767 | | 2284 | 9137 | 7061 | +| 256 | 2048 | 5461 | 7447 | | 2731 | 9638 | 7802 | +| 384 | 1998 | 5861 | 6762 | | 2313 | 9253 | 7644 | +| 512 | 2095 | 6144 | 7680 | | 2194 | 10240 | 7089 | +| 768 | 2230 | 5773 | 7549 | | 2045 | 10331 | 7010 | +| 1024 | 2133 | 6400 | 8533 | | 2133 | 10779 | 7877 | +| 2048 | 2011 | 7040 | 8665 | | 1942 | 10240 | 7768 | +| 4096 | 2194 | 6827 | 8777 | | 1755 | 9452 | 6827 | +| 8192 | 1849 | 6656 | 6656 | | 1752 | 7831 | 6827 | +| 9216 | 1871 | 5858 | 6416 | | 1643 | 6909 | 6266 | +| 16384 | 1883 | 6223 | 6506 | | 1664 | 7340 | 6982 | +| 32768 | 1826 | 6390 | 6667 | | 1631 | 7481 | 6971 | +| 262144 | 1546 | 4075 | 5977 | | 1299 | 3415 | 3551 | +| 1048576 | 1104 | 2071 | 1730 | | 1104 | 1149 | 1834 | +|-----------+------------+------------+------------| |------------+------------+------------| + + + +Ubuntu 11.04, gcc 4.5, 32-bit, fftw 3.3 on a 1.6 GHz Atom N270 + +Built with: +gcc -o test_pffft -DHAVE_FFTW -msse -mfpmath=sse -O3 -Wall -W pffft.c test_pffft.c fftpack.c -L/usr/local/lib -I/usr/local/include/ -lfftw3f -lm + +| N (input length) | real FFTPack | real FFTW | real PFFFT | | cplx FFTPack | cplx FFTW | cplx PFFFT | +|------------------+--------------+--------------+--------------| |--------------+--------------+--------------| +| 64 | 452 | 1041 | 1336 | | 549 | 2318 | 1781 | +| 96 | 444 | 1297 | 1297 | | 503 | 2408 | 1686 | +| 128 | 527 | 1525 | 1707 | | 543 | 2655 | 1886 | +| 192 | 498 | 1653 | 1849 | | 539 | 2678 | 1942 | +| 256 | 585 | 1862 | 2156 | | 594 | 2777 | 2244 | +| 384 | 499 | 1870 | 1998 | | 511 | 2586 | 1890 | +| 512 | 562 | 2095 | 2194 | | 542 | 2973 | 2194 | +| 768 | 545 | 2045 | 2133 | | 545 | 2365 | 2133 | +| 1024 | 595 | 2133 | 2438 | | 569 | 2695 | 2179 | +| 2048 | 587 | 2125 | 2347 | | 521 | 2230 | 1707 | +| 4096 | 495 | 1890 | 1834 | | 492 | 1876 | 1672 | +| 8192 | 469 | 1548 | 1729 | | 438 | 1740 | 1664 | +| 9216 | 468 | 1663 | 1663 | | 446 | 1585 | 1531 | +| 16384 | 453 | 1608 | 1767 | | 398 | 1476 | 1664 | +| 32768 | 456 | 1420 | 1503 | | 387 | 1388 | 1345 | +| 262144 | 309 | 385 | 726 | | 262 | 415 | 840 | +| 1048576 | 280 | 351 | 739 | | 261 | 313 | 797 | +|------------------+--------------+--------------+--------------| |--------------+--------------+--------------| + + + +Windows 7, visual c++ 2010 on a 1.6 GHz Atom N270 + +Built with: +cl /Ox -D_USE_MATH_DEFINES /arch:SSE test_pffft.c pffft.c fftpack.c + +(visual c++ is definitively not very good with SSE intrinsics...) + +| N (input length) | real FFTPack | real PFFFT | | cplx FFTPack | cplx PFFFT | +|------------------+--------------+--------------| |--------------+--------------| +| 64 | 173 | 1009 | | 174 | 1159 | +| 96 | 169 | 1029 | | 188 | 1201 | +| 128 | 195 | 1242 | | 191 | 1275 | +| 192 | 178 | 1312 | | 184 | 1276 | +| 256 | 196 | 1591 | | 186 | 1281 | +| 384 | 172 | 1409 | | 181 | 1281 | +| 512 | 187 | 1640 | | 181 | 1313 | +| 768 | 171 | 1614 | | 176 | 1258 | +| 1024 | 186 | 1812 | | 178 | 1223 | +| 2048 | 190 | 1707 | | 186 | 1099 | +| 4096 | 182 | 1446 | | 177 | 975 | +| 8192 | 175 | 1345 | | 169 | 1034 | +| 9216 | 165 | 1271 | | 168 | 1023 | +| 16384 | 166 | 1396 | | 165 | 949 | +| 32768 | 172 | 1311 | | 161 | 881 | +| 262144 | 136 | 632 | | 134 | 629 | +| 1048576 | 134 | 698 | | 127 | 623 | +|------------------+--------------+--------------| |--------------+--------------| + + + +Ubuntu 12.04, gcc-4.7.3, 32-bit, with fftw 3.3.3 (built with --enable-neon), on a 1.2GHz ARM Cortex A9 (Tegra 3) + +Built with: +gcc-4.7 -O3 -DHAVE_FFTW -march=armv7-a -mtune=cortex-a9 -mfloat-abi=hard -mfpu=neon -ffast-math test_pffft.c pffft.c -o test_pffft_arm fftpack.c -lm -I/usr/local/include/ -L/usr/local/lib/ -lfftw3f + +| input len |real FFTPack| real FFTW | real PFFFT | |cplx FFTPack| cplx FFTW | cplx PFFFT | +|-----------+------------+------------+------------| |------------+------------+------------| +| 64 | 549 | 452 | 731 | | 512 | 602 | 640 | +| 96 | 421 | 272 | 702 | | 496 | 571 | 602 | +| 128 | 498 | 512 | 815 | | 597 | 618 | 652 | +| 160 | 521 | 536 | 815 | | 586 | 669 | 625 | +| 192 | 539 | 571 | 883 | | 485 | 597 | 626 | +| 256 | 640 | 539 | 975 | | 569 | 611 | 671 | +| 384 | 499 | 610 | 879 | | 499 | 602 | 637 | +| 480 | 518 | 507 | 877 | | 496 | 661 | 616 | +| 512 | 524 | 591 | 1002 | | 549 | 678 | 668 | +| 640 | 542 | 612 | 955 | | 568 | 663 | 645 | +| 768 | 557 | 613 | 981 | | 491 | 663 | 598 | +| 800 | 514 | 353 | 882 | | 514 | 360 | 574 | +| 1024 | 640 | 640 | 1067 | | 492 | 683 | 602 | +| 2048 | 587 | 640 | 908 | | 486 | 640 | 552 | +| 2400 | 479 | 368 | 777 | | 422 | 376 | 518 | +| 4096 | 511 | 614 | 853 | | 426 | 640 | 534 | +| 8192 | 415 | 584 | 708 | | 386 | 622 | 516 | +| 9216 | 419 | 571 | 687 | | 364 | 586 | 506 | +| 16384 | 426 | 577 | 716 | | 398 | 606 | 530 | +| 32768 | 417 | 572 | 673 | | 399 | 572 | 468 | +| 262144 | 219 | 380 | 293 | | 255 | 431 | 343 | +| 1048576 | 202 | 274 | 237 | | 265 | 282 | 355 | +|-----------+------------+------------+------------| |------------+------------+------------| + +Same platform as above, but this time pffft and fftpack are built with clang 3.2: + +clang -O3 -DHAVE_FFTW -march=armv7-a -mtune=cortex-a9 -mfloat-abi=hard -mfpu=neon -ffast-math test_pffft.c pffft.c -o test_pffft_arm fftpack.c -lm -I/usr/local/include/ -L/usr/local/lib/ -lfftw3f + +| input len |real FFTPack| real FFTW | real PFFFT | |cplx FFTPack| cplx FFTW | cplx PFFFT | +|-----------+------------+------------+------------| |------------+------------+------------| +| 64 | 427 | 452 | 853 | | 427 | 602 | 1024 | +| 96 | 351 | 276 | 843 | | 337 | 571 | 963 | +| 128 | 373 | 512 | 996 | | 390 | 618 | 1054 | +| 160 | 426 | 536 | 987 | | 375 | 669 | 914 | +| 192 | 404 | 571 | 1079 | | 388 | 588 | 1079 | +| 256 | 465 | 539 | 1205 | | 445 | 602 | 1170 | +| 384 | 366 | 610 | 1099 | | 343 | 594 | 1099 | +| 480 | 356 | 507 | 1140 | | 335 | 651 | 931 | +| 512 | 411 | 591 | 1213 | | 384 | 649 | 1124 | +| 640 | 398 | 612 | 1193 | | 373 | 654 | 901 | +| 768 | 409 | 613 | 1227 | | 383 | 663 | 1044 | +| 800 | 411 | 348 | 1073 | | 353 | 358 | 809 | +| 1024 | 427 | 640 | 1280 | | 413 | 692 | 1004 | +| 2048 | 414 | 626 | 1126 | | 371 | 640 | 853 | +| 2400 | 399 | 373 | 898 | | 319 | 368 | 653 | +| 4096 | 404 | 602 | 1059 | | 357 | 633 | 778 | +| 8192 | 332 | 584 | 792 | | 308 | 616 | 716 | +| 9216 | 322 | 561 | 783 | | 299 | 586 | 687 | +| 16384 | 344 | 568 | 778 | | 314 | 617 | 745 | +| 32768 | 342 | 564 | 737 | | 314 | 552 | 629 | +| 262144 | 201 | 383 | 313 | | 227 | 435 | 413 | +| 1048576 | 187 | 262 | 251 | | 228 | 281 | 409 | +|-----------+------------+------------+------------| |------------+------------+------------| + +So it looks like, on ARM, gcc 4.7 is the best at scalar floating point +(the fftpack performance numbers are better with gcc), while clang is +the best with neon intrinsics (see how pffft perf has improved with +clang 3.2). + + +NVIDIA Jetson TK1 board, gcc-4.8.2. The cpu is a 2.3GHz cortex A15 (Tegra K1). + +Built with: +gcc -O3 -march=armv7-a -mtune=native -mfloat-abi=hard -mfpu=neon -ffast-math test_pffft.c pffft.c -o test_pffft_arm fftpack.c -lm + +| input len |real FFTPack| real PFFFT | |cplx FFTPack| cplx PFFFT | +|-----------+------------+------------| |------------+------------| +| 64 | 1735 | 3308 | | 1994 | 3744 | +| 96 | 1596 | 3448 | | 1987 | 3572 | +| 128 | 1807 | 4076 | | 2255 | 3960 | +| 160 | 1769 | 4083 | | 2071 | 3845 | +| 192 | 1990 | 4233 | | 2017 | 3939 | +| 256 | 2191 | 4882 | | 2254 | 4346 | +| 384 | 1878 | 4492 | | 2073 | 4012 | +| 480 | 1748 | 4398 | | 1923 | 3951 | +| 512 | 2030 | 5064 | | 2267 | 4195 | +| 640 | 1918 | 4756 | | 2094 | 4184 | +| 768 | 2099 | 4907 | | 2048 | 4297 | +| 800 | 1822 | 4555 | | 1880 | 4063 | +| 1024 | 2232 | 5355 | | 2187 | 4420 | +| 2048 | 2176 | 4983 | | 2027 | 3602 | +| 2400 | 1741 | 4256 | | 1710 | 3344 | +| 4096 | 1816 | 3914 | | 1851 | 3349 | +| 8192 | 1716 | 3481 | | 1700 | 3255 | +| 9216 | 1735 | 3589 | | 1653 | 3094 | +| 16384 | 1567 | 3483 | | 1637 | 3244 | +| 32768 | 1624 | 3240 | | 1655 | 3156 | +| 262144 | 1012 | 1898 | | 983 | 1503 | +| 1048576 | 876 | 1154 | | 868 | 1341 | +|-----------+------------+------------| |------------+------------| + +The performance on the tegra K1 is pretty impressive. I'm not +including the FFTW numbers as they as slightly below the scalar +fftpack numbers, so something must be wrong (however it seems to be +correctly configured and is using neon simd instructions). + +When using clang 3.4 the pffft version is even a bit faster, reaching +5.7 GFlops for real ffts of size 1024. + + +iPad Air 2 with iOS9, xcode 8.0, arm64. The cpu is an Apple A8X, supposedly running at 1.5GHz. + +| input len |real FFTPack| real vDSP | real PFFFT | |cplx FFTPack| cplx vDSP | cplx PFFFT | +|-----------+------------+------------+------------| |------------+------------+------------| +| 64 | 2517 | 7995 | 6086 | | 2725 | 13006 | 8495 | +| 96 | 2442 | n/a | 6691 | | 2256 | n/a | 7991 | +| 128 | 2664 | 10186 | 7877 | | 2575 | 15115 | 9115 | +| 160 | 2638 | n/a | 8283 | | 2682 | n/a | 8806 | +| 192 | 2903 | n/a | 9083 | | 2634 | n/a | 8980 | +| 256 | 3184 | 11452 | 10039 | | 3026 | 15410 | 10199 | +| 384 | 2665 | n/a | 10100 | | 2275 | n/a | 9247 | +| 480 | 2546 | n/a | 9863 | | 2341 | n/a | 8892 | +| 512 | 2832 | 12197 | 10989 | | 2547 | 16768 | 10154 | +| 640 | 2755 | n/a | 10461 | | 2569 | n/a | 9666 | +| 768 | 2998 | n/a | 11355 | | 2585 | n/a | 9813 | +| 800 | 2516 | n/a | 10332 | | 2433 | n/a | 9164 | +| 1024 | 3109 | 12965 | 12114 | | 2869 | 16448 | 10519 | +| 2048 | 3027 | 12996 | 12023 | | 2648 | 17304 | 10307 | +| 2400 | 2515 | n/a | 10372 | | 2355 | n/a | 8443 | +| 4096 | 3204 | 13603 | 12359 | | 2814 | 16570 | 9780 | +| 8192 | 2759 | 13422 | 10824 | | 2153 | 15652 | 7884 | +| 9216 | 2700 | n/a | 9938 | | 2241 | n/a | 7900 | +| 16384 | 2280 | 13057 | 7976 | | 593 | 4272 | 2534 | +| 32768 | 768 | 4269 | 2882 | | 606 | 4405 | 2604 | +| 262144 | 724 | 3527 | 2630 | | 534 | 2418 | 2157 | +| 1048576 | 674 | 1467 | 2135 | | 530 | 1621 | 2055 | +|-----------+------------+------------+------------| |------------+------------+------------| + +I double-checked to make sure I did not make a mistake in the time +measurements, as the numbers are much higher than what I initially +expected. They are in fact higher than the number I get on the 2.8GHz +Xeon of my 2008 mac pro.. (except for FFT lengths >= 32768 where +having a big cache is useful). A good surprise is also that the perf +is not too far from apple's vDSP (at least for the real FFT). + diff --git a/oss-internship-2020/pffft/fftpack.c b/oss-internship-2020/pffft/fftpack.c new file mode 100644 index 0000000..b6375a8 --- /dev/null +++ b/oss-internship-2020/pffft/fftpack.c @@ -0,0 +1,3112 @@ +/* + compile with cc -DTESTING_FFTPACK fftpack.c in order to build the + test application. + + This is an f2c translation of the full fftpack sources as found on + http://www.netlib.org/fftpack/ The translated code has been + slightlty edited to remove the ugliest artefacts of the translation + (a hundred of wild GOTOs were wiped during that operation). + + The original fftpack file was written by Paul N. Swarztrauber + (Version 4, 1985), in fortran 77. + + FFTPACK license: + + http://www.cisl.ucar.edu/css/software/fftpack5/ftpk.html + + Copyright (c) 2004 the University Corporation for Atmospheric + Research ("UCAR"). All rights reserved. Developed by NCAR's + Computational and Information Systems Laboratory, UCAR, + www.cisl.ucar.edu. + + Redistribution and use of the Software in source and binary forms, + with or without modification, is permitted provided that the + following conditions are met: + + - Neither the names of NCAR's Computational and Information Systems + Laboratory, the University Corporation for Atmospheric Research, + nor the names of its sponsors or contributors may be used to + endorse or promote products derived from this Software without + specific prior written permission. + + - Redistributions of source code must retain the above copyright + notices, this list of conditions, and the disclaimer below. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer below in the + documentation and/or other materials provided with the + distribution. + + THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES OR OTHER LIABILITY, WHETHER IN AN + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE + SOFTWARE. + + ChangeLog: + 2011/10/02: this is my first release of this file. +*/ + +#include "fftpack.h" +#include + +typedef fftpack_real real; +typedef fftpack_int integer; + +typedef struct f77complex { + real r, i; +} f77complex; + +#ifdef TESTING_FFTPACK +static real c_abs(f77complex *c) { return sqrt(c->r*c->r + c->i*c->i); } +static double dmax(double a, double b) { return a < b ? b : a; } +#endif + +/* translated by f2c (version 20061008), and slightly edited */ + +static void passfb(integer *nac, integer ido, integer ip, integer l1, integer idl1, + real *cc, real *c1, real *c2, real *ch, real *ch2, const real *wa, real fsign) +{ + /* System generated locals */ + integer ch_offset, cc_offset, + c1_offset, c2_offset, ch2_offset; + + /* Local variables */ + integer i, j, k, l, jc, lc, ik, idj, idl, inc, idp; + real wai, war; + integer ipp2, idij, idlj, idot, ipph; + + +#define c1_ref(a_1,a_2,a_3) c1[((a_3)*l1 + (a_2))*ido + a_1] +#define c2_ref(a_1,a_2) c2[(a_2)*idl1 + a_1] +#define cc_ref(a_1,a_2,a_3) cc[((a_3)*ip + (a_2))*ido + a_1] +#define ch_ref(a_1,a_2,a_3) ch[((a_3)*l1 + (a_2))*ido + a_1] +#define ch2_ref(a_1,a_2) ch2[(a_2)*idl1 + a_1] + + /* Parameter adjustments */ + ch_offset = 1 + ido * (1 + l1); + ch -= ch_offset; + c1_offset = 1 + ido * (1 + l1); + c1 -= c1_offset; + cc_offset = 1 + ido * (1 + ip); + cc -= cc_offset; + ch2_offset = 1 + idl1; + ch2 -= ch2_offset; + c2_offset = 1 + idl1; + c2 -= c2_offset; + --wa; + + /* Function Body */ + idot = ido / 2; + ipp2 = ip + 2; + ipph = (ip + 1) / 2; + idp = ip * ido; + + if (ido >= l1) { + for (j = 2; j <= ipph; ++j) { + jc = ipp2 - j; + for (k = 1; k <= l1; ++k) { + for (i = 1; i <= ido; ++i) { + ch_ref(i, k, j) = cc_ref(i, j, k) + cc_ref(i, jc, k); + ch_ref(i, k, jc) = cc_ref(i, j, k) - cc_ref(i, jc, k); + } + } + } + for (k = 1; k <= l1; ++k) { + for (i = 1; i <= ido; ++i) { + ch_ref(i, k, 1) = cc_ref(i, 1, k); + } + } + } else { + for (j = 2; j <= ipph; ++j) { + jc = ipp2 - j; + for (i = 1; i <= ido; ++i) { + for (k = 1; k <= l1; ++k) { + ch_ref(i, k, j) = cc_ref(i, j, k) + cc_ref(i, jc, k); + ch_ref(i, k, jc) = cc_ref(i, j, k) - cc_ref(i, jc, k); + } + } + } + for (i = 1; i <= ido; ++i) { + for (k = 1; k <= l1; ++k) { + ch_ref(i, k, 1) = cc_ref(i, 1, k); + } + } + } + idl = 2 - ido; + inc = 0; + for (l = 2; l <= ipph; ++l) { + lc = ipp2 - l; + idl += ido; + for (ik = 1; ik <= idl1; ++ik) { + c2_ref(ik, l) = ch2_ref(ik, 1) + wa[idl - 1] * ch2_ref(ik, 2); + c2_ref(ik, lc) = fsign*wa[idl] * ch2_ref(ik, ip); + } + idlj = idl; + inc += ido; + for (j = 3; j <= ipph; ++j) { + jc = ipp2 - j; + idlj += inc; + if (idlj > idp) { + idlj -= idp; + } + war = wa[idlj - 1]; + wai = wa[idlj]; + for (ik = 1; ik <= idl1; ++ik) { + c2_ref(ik, l) = c2_ref(ik, l) + war * ch2_ref(ik, j); + c2_ref(ik, lc) = c2_ref(ik, lc) + fsign*wai * ch2_ref(ik, jc); + } + } + } + for (j = 2; j <= ipph; ++j) { + for (ik = 1; ik <= idl1; ++ik) { + ch2_ref(ik, 1) = ch2_ref(ik, 1) + ch2_ref(ik, j); + } + } + for (j = 2; j <= ipph; ++j) { + jc = ipp2 - j; + for (ik = 2; ik <= idl1; ik += 2) { + ch2_ref(ik - 1, j) = c2_ref(ik - 1, j) - c2_ref(ik, jc); + ch2_ref(ik - 1, jc) = c2_ref(ik - 1, j) + c2_ref(ik, jc); + ch2_ref(ik, j) = c2_ref(ik, j) + c2_ref(ik - 1, jc); + ch2_ref(ik, jc) = c2_ref(ik, j) - c2_ref(ik - 1, jc); + } + } + *nac = 1; + if (ido == 2) { + return; + } + *nac = 0; + for (ik = 1; ik <= idl1; ++ik) { + c2_ref(ik, 1) = ch2_ref(ik, 1); + } + for (j = 2; j <= ip; ++j) { + for (k = 1; k <= l1; ++k) { + c1_ref(1, k, j) = ch_ref(1, k, j); + c1_ref(2, k, j) = ch_ref(2, k, j); + } + } + if (idot <= l1) { + idij = 0; + for (j = 2; j <= ip; ++j) { + idij += 2; + for (i = 4; i <= ido; i += 2) { + idij += 2; + for (k = 1; k <= l1; ++k) { + c1_ref(i - 1, k, j) = wa[idij - 1] * ch_ref(i - 1, k, j) - fsign*wa[idij] * ch_ref(i, k, j); + c1_ref(i, k, j) = wa[idij - 1] * ch_ref(i, k, j) + fsign*wa[idij] * ch_ref(i - 1, k, j); + } + } + } + return; + } + idj = 2 - ido; + for (j = 2; j <= ip; ++j) { + idj += ido; + for (k = 1; k <= l1; ++k) { + idij = idj; + for (i = 4; i <= ido; i += 2) { + idij += 2; + c1_ref(i - 1, k, j) = wa[idij - 1] * ch_ref(i - 1, k, j) - fsign*wa[idij] * ch_ref(i, k, j); + c1_ref(i, k, j) = wa[idij - 1] * ch_ref(i, k, j) + fsign*wa[idij] * ch_ref(i - 1, k, j); + } + } + } +} /* passb */ + +#undef ch2_ref +#undef ch_ref +#undef cc_ref +#undef c2_ref +#undef c1_ref + + +static void passb2(integer ido, integer l1, const real *cc, real *ch, const real *wa1) +{ + /* System generated locals */ + integer cc_offset, ch_offset; + + /* Local variables */ + integer i, k; + real ti2, tr2; + + +#define cc_ref(a_1,a_2,a_3) cc[((a_3)*2 + (a_2))*ido + a_1] +#define ch_ref(a_1,a_2,a_3) ch[((a_3)*l1 + (a_2))*ido + a_1] + + /* Parameter adjustments */ + ch_offset = 1 + ido * (1 + l1); + ch -= ch_offset; + cc_offset = 1 + ido * 3; + cc -= cc_offset; + --wa1; + + /* Function Body */ + if (ido <= 2) { + for (k = 1; k <= l1; ++k) { + ch_ref(1, k, 1) = cc_ref(1, 1, k) + cc_ref(1, 2, k); + ch_ref(1, k, 2) = cc_ref(1, 1, k) - cc_ref(1, 2, k); + ch_ref(2, k, 1) = cc_ref(2, 1, k) + cc_ref(2, 2, k); + ch_ref(2, k, 2) = cc_ref(2, 1, k) - cc_ref(2, 2, k); + } + return; + } + for (k = 1; k <= l1; ++k) { + for (i = 2; i <= ido; i += 2) { + ch_ref(i - 1, k, 1) = cc_ref(i - 1, 1, k) + cc_ref(i - 1, 2, k); + tr2 = cc_ref(i - 1, 1, k) - cc_ref(i - 1, 2, k); + ch_ref(i, k, 1) = cc_ref(i, 1, k) + cc_ref(i, 2, k); + ti2 = cc_ref(i, 1, k) - cc_ref(i, 2, k); + ch_ref(i, k, 2) = wa1[i - 1] * ti2 + wa1[i] * tr2; + ch_ref(i - 1, k, 2) = wa1[i - 1] * tr2 - wa1[i] * ti2; + } + } +} /* passb2 */ + +#undef ch_ref +#undef cc_ref + + +static void passb3(integer ido, integer l1, const real *cc, real *ch, const real *wa1, const real *wa2) +{ + static const real taur = -.5f; + static const real taui = .866025403784439f; + + /* System generated locals */ + integer cc_offset, ch_offset; + + /* Local variables */ + integer i, k; + real ci2, ci3, di2, di3, cr2, cr3, dr2, dr3, ti2, tr2; + + +#define cc_ref(a_1,a_2,a_3) cc[((a_3)*3 + (a_2))*ido + a_1] +#define ch_ref(a_1,a_2,a_3) ch[((a_3)*l1 + (a_2))*ido + a_1] + + /* Parameter adjustments */ + ch_offset = 1 + ido * (1 + l1); + ch -= ch_offset; + cc_offset = 1 + (ido << 2); + cc -= cc_offset; + --wa1; + --wa2; + + /* Function Body */ + if (ido == 2) { + for (k = 1; k <= l1; ++k) { + tr2 = cc_ref(1, 2, k) + cc_ref(1, 3, k); + cr2 = cc_ref(1, 1, k) + taur * tr2; + ch_ref(1, k, 1) = cc_ref(1, 1, k) + tr2; + ti2 = cc_ref(2, 2, k) + cc_ref(2, 3, k); + ci2 = cc_ref(2, 1, k) + taur * ti2; + ch_ref(2, k, 1) = cc_ref(2, 1, k) + ti2; + cr3 = taui * (cc_ref(1, 2, k) - cc_ref(1, 3, k)); + ci3 = taui * (cc_ref(2, 2, k) - cc_ref(2, 3, k)); + ch_ref(1, k, 2) = cr2 - ci3; + ch_ref(1, k, 3) = cr2 + ci3; + ch_ref(2, k, 2) = ci2 + cr3; + ch_ref(2, k, 3) = ci2 - cr3; + } + } else { + for (k = 1; k <= l1; ++k) { + for (i = 2; i <= ido; i += 2) { + tr2 = cc_ref(i - 1, 2, k) + cc_ref(i - 1, 3, k); + cr2 = cc_ref(i - 1, 1, k) + taur * tr2; + ch_ref(i - 1, k, 1) = cc_ref(i - 1, 1, k) + tr2; + ti2 = cc_ref(i, 2, k) + cc_ref(i, 3, k); + ci2 = cc_ref(i, 1, k) + taur * ti2; + ch_ref(i, k, 1) = cc_ref(i, 1, k) + ti2; + cr3 = taui * (cc_ref(i - 1, 2, k) - cc_ref(i - 1, 3, k)); + ci3 = taui * (cc_ref(i, 2, k) - cc_ref(i, 3, k)); + dr2 = cr2 - ci3; + dr3 = cr2 + ci3; + di2 = ci2 + cr3; + di3 = ci2 - cr3; + ch_ref(i, k, 2) = wa1[i - 1] * di2 + wa1[i] * dr2; + ch_ref(i - 1, k, 2) = wa1[i - 1] * dr2 - wa1[i] * di2; + ch_ref(i, k, 3) = wa2[i - 1] * di3 + wa2[i] * dr3; + ch_ref(i - 1, k, 3) = wa2[i - 1] * dr3 - wa2[i] * di3; + } + } + } +} /* passb3 */ + +#undef ch_ref +#undef cc_ref + + +static void passb4(integer ido, integer l1, const real *cc, real *ch, + const real *wa1, const real *wa2, const real *wa3) +{ + /* System generated locals */ + integer cc_offset, ch_offset; + + /* Local variables */ + integer i, k; + real ci2, ci3, ci4, cr2, cr3, cr4, ti1, ti2, ti3, ti4, tr1, tr2, tr3, tr4; + + +#define cc_ref(a_1,a_2,a_3) cc[((a_3)*4 + (a_2))*ido + a_1] +#define ch_ref(a_1,a_2,a_3) ch[((a_3)*l1 + (a_2))*ido + a_1] + + /* Parameter adjustments */ + ch_offset = 1 + ido * (1 + l1); + ch -= ch_offset; + cc_offset = 1 + ido * 5; + cc -= cc_offset; + --wa1; + --wa2; + --wa3; + + /* Function Body */ + if (ido == 2) { + for (k = 1; k <= l1; ++k) { + ti1 = cc_ref(2, 1, k) - cc_ref(2, 3, k); + ti2 = cc_ref(2, 1, k) + cc_ref(2, 3, k); + tr4 = cc_ref(2, 4, k) - cc_ref(2, 2, k); + ti3 = cc_ref(2, 2, k) + cc_ref(2, 4, k); + tr1 = cc_ref(1, 1, k) - cc_ref(1, 3, k); + tr2 = cc_ref(1, 1, k) + cc_ref(1, 3, k); + ti4 = cc_ref(1, 2, k) - cc_ref(1, 4, k); + tr3 = cc_ref(1, 2, k) + cc_ref(1, 4, k); + ch_ref(1, k, 1) = tr2 + tr3; + ch_ref(1, k, 3) = tr2 - tr3; + ch_ref(2, k, 1) = ti2 + ti3; + ch_ref(2, k, 3) = ti2 - ti3; + ch_ref(1, k, 2) = tr1 + tr4; + ch_ref(1, k, 4) = tr1 - tr4; + ch_ref(2, k, 2) = ti1 + ti4; + ch_ref(2, k, 4) = ti1 - ti4; + } + } else { + for (k = 1; k <= l1; ++k) { + for (i = 2; i <= ido; i += 2) { + ti1 = cc_ref(i, 1, k) - cc_ref(i, 3, k); + ti2 = cc_ref(i, 1, k) + cc_ref(i, 3, k); + ti3 = cc_ref(i, 2, k) + cc_ref(i, 4, k); + tr4 = cc_ref(i, 4, k) - cc_ref(i, 2, k); + tr1 = cc_ref(i - 1, 1, k) - cc_ref(i - 1, 3, k); + tr2 = cc_ref(i - 1, 1, k) + cc_ref(i - 1, 3, k); + ti4 = cc_ref(i - 1, 2, k) - cc_ref(i - 1, 4, k); + tr3 = cc_ref(i - 1, 2, k) + cc_ref(i - 1, 4, k); + ch_ref(i - 1, k, 1) = tr2 + tr3; + cr3 = tr2 - tr3; + ch_ref(i, k, 1) = ti2 + ti3; + ci3 = ti2 - ti3; + cr2 = tr1 + tr4; + cr4 = tr1 - tr4; + ci2 = ti1 + ti4; + ci4 = ti1 - ti4; + ch_ref(i - 1, k, 2) = wa1[i - 1] * cr2 - wa1[i] * ci2; + ch_ref(i, k, 2) = wa1[i - 1] * ci2 + wa1[i] * cr2; + ch_ref(i - 1, k, 3) = wa2[i - 1] * cr3 - wa2[i] * ci3; + ch_ref(i, k, 3) = wa2[i - 1] * ci3 + wa2[i] * cr3; + ch_ref(i - 1, k, 4) = wa3[i - 1] * cr4 - wa3[i] * ci4; + ch_ref(i, k, 4) = wa3[i - 1] * ci4 + wa3[i] * cr4; + } + } + } +} /* passb4 */ + +#undef ch_ref +#undef cc_ref + +/* passf5 and passb5 merged */ +static void passfb5(integer ido, integer l1, const real *cc, real *ch, + const real *wa1, const real *wa2, const real *wa3, const real *wa4, real fsign) +{ + const real tr11 = .309016994374947f; + const real ti11 = .951056516295154f*fsign; + const real tr12 = -.809016994374947f; + const real ti12 = .587785252292473f*fsign; + + /* System generated locals */ + integer cc_offset, ch_offset; + + /* Local variables */ + integer i, k; + real ci2, ci3, ci4, ci5, di3, di4, di5, di2, cr2, cr3, cr5, cr4, ti2, ti3, + ti4, ti5, dr3, dr4, dr5, dr2, tr2, tr3, tr4, tr5; + + +#define cc_ref(a_1,a_2,a_3) cc[((a_3)*5 + (a_2))*ido + a_1] +#define ch_ref(a_1,a_2,a_3) ch[((a_3)*l1 + (a_2))*ido + a_1] + + /* Parameter adjustments */ + ch_offset = 1 + ido * (1 + l1); + ch -= ch_offset; + cc_offset = 1 + ido * 6; + cc -= cc_offset; + --wa1; + --wa2; + --wa3; + --wa4; + + /* Function Body */ + if (ido == 2) { + for (k = 1; k <= l1; ++k) { + ti5 = cc_ref(2, 2, k) - cc_ref(2, 5, k); + ti2 = cc_ref(2, 2, k) + cc_ref(2, 5, k); + ti4 = cc_ref(2, 3, k) - cc_ref(2, 4, k); + ti3 = cc_ref(2, 3, k) + cc_ref(2, 4, k); + tr5 = cc_ref(1, 2, k) - cc_ref(1, 5, k); + tr2 = cc_ref(1, 2, k) + cc_ref(1, 5, k); + tr4 = cc_ref(1, 3, k) - cc_ref(1, 4, k); + tr3 = cc_ref(1, 3, k) + cc_ref(1, 4, k); + ch_ref(1, k, 1) = cc_ref(1, 1, k) + tr2 + tr3; + ch_ref(2, k, 1) = cc_ref(2, 1, k) + ti2 + ti3; + cr2 = cc_ref(1, 1, k) + tr11 * tr2 + tr12 * tr3; + ci2 = cc_ref(2, 1, k) + tr11 * ti2 + tr12 * ti3; + cr3 = cc_ref(1, 1, k) + tr12 * tr2 + tr11 * tr3; + ci3 = cc_ref(2, 1, k) + tr12 * ti2 + tr11 * ti3; + cr5 = ti11 * tr5 + ti12 * tr4; + ci5 = ti11 * ti5 + ti12 * ti4; + cr4 = ti12 * tr5 - ti11 * tr4; + ci4 = ti12 * ti5 - ti11 * ti4; + ch_ref(1, k, 2) = cr2 - ci5; + ch_ref(1, k, 5) = cr2 + ci5; + ch_ref(2, k, 2) = ci2 + cr5; + ch_ref(2, k, 3) = ci3 + cr4; + ch_ref(1, k, 3) = cr3 - ci4; + ch_ref(1, k, 4) = cr3 + ci4; + ch_ref(2, k, 4) = ci3 - cr4; + ch_ref(2, k, 5) = ci2 - cr5; + } + } else { + for (k = 1; k <= l1; ++k) { + for (i = 2; i <= ido; i += 2) { + ti5 = cc_ref(i, 2, k) - cc_ref(i, 5, k); + ti2 = cc_ref(i, 2, k) + cc_ref(i, 5, k); + ti4 = cc_ref(i, 3, k) - cc_ref(i, 4, k); + ti3 = cc_ref(i, 3, k) + cc_ref(i, 4, k); + tr5 = cc_ref(i - 1, 2, k) - cc_ref(i - 1, 5, k); + tr2 = cc_ref(i - 1, 2, k) + cc_ref(i - 1, 5, k); + tr4 = cc_ref(i - 1, 3, k) - cc_ref(i - 1, 4, k); + tr3 = cc_ref(i - 1, 3, k) + cc_ref(i - 1, 4, k); + ch_ref(i - 1, k, 1) = cc_ref(i - 1, 1, k) + tr2 + tr3; + ch_ref(i, k, 1) = cc_ref(i, 1, k) + ti2 + ti3; + cr2 = cc_ref(i - 1, 1, k) + tr11 * tr2 + tr12 * tr3; + ci2 = cc_ref(i, 1, k) + tr11 * ti2 + tr12 * ti3; + cr3 = cc_ref(i - 1, 1, k) + tr12 * tr2 + tr11 * tr3; + ci3 = cc_ref(i, 1, k) + tr12 * ti2 + tr11 * ti3; + cr5 = ti11 * tr5 + ti12 * tr4; + ci5 = ti11 * ti5 + ti12 * ti4; + cr4 = ti12 * tr5 - ti11 * tr4; + ci4 = ti12 * ti5 - ti11 * ti4; + dr3 = cr3 - ci4; + dr4 = cr3 + ci4; + di3 = ci3 + cr4; + di4 = ci3 - cr4; + dr5 = cr2 + ci5; + dr2 = cr2 - ci5; + di5 = ci2 - cr5; + di2 = ci2 + cr5; + ch_ref(i - 1, k, 2) = wa1[i - 1] * dr2 - fsign*wa1[i] * di2; + ch_ref(i, k, 2) = wa1[i - 1] * di2 + fsign*wa1[i] * dr2; + ch_ref(i - 1, k, 3) = wa2[i - 1] * dr3 - fsign*wa2[i] * di3; + ch_ref(i, k, 3) = wa2[i - 1] * di3 + fsign*wa2[i] * dr3; + ch_ref(i - 1, k, 4) = wa3[i - 1] * dr4 - fsign*wa3[i] * di4; + ch_ref(i, k, 4) = wa3[i - 1] * di4 + fsign*wa3[i] * dr4; + ch_ref(i - 1, k, 5) = wa4[i - 1] * dr5 - fsign*wa4[i] * di5; + ch_ref(i, k, 5) = wa4[i - 1] * di5 + fsign*wa4[i] * dr5; + } + } + } +} /* passb5 */ + +#undef ch_ref +#undef cc_ref + +static void passf2(integer ido, integer l1, const real *cc, real *ch, const real *wa1) +{ + /* System generated locals */ + integer cc_offset, ch_offset; + + /* Local variables */ + integer i, k; + real ti2, tr2; + + +#define cc_ref(a_1,a_2,a_3) cc[((a_3)*2 + (a_2))*ido + a_1] +#define ch_ref(a_1,a_2,a_3) ch[((a_3)*l1 + (a_2))*ido + a_1] + + /* Parameter adjustments */ + ch_offset = 1 + ido * (1 + l1); + ch -= ch_offset; + cc_offset = 1 + ido * 3; + cc -= cc_offset; + --wa1; + + /* Function Body */ + if (ido == 2) { + for (k = 1; k <= l1; ++k) { + ch_ref(1, k, 1) = cc_ref(1, 1, k) + cc_ref(1, 2, k); + ch_ref(1, k, 2) = cc_ref(1, 1, k) - cc_ref(1, 2, k); + ch_ref(2, k, 1) = cc_ref(2, 1, k) + cc_ref(2, 2, k); + ch_ref(2, k, 2) = cc_ref(2, 1, k) - cc_ref(2, 2, k); + } + } else { + for (k = 1; k <= l1; ++k) { + for (i = 2; i <= ido; i += 2) { + ch_ref(i - 1, k, 1) = cc_ref(i - 1, 1, k) + cc_ref(i - 1, 2, + k); + tr2 = cc_ref(i - 1, 1, k) - cc_ref(i - 1, 2, k); + ch_ref(i, k, 1) = cc_ref(i, 1, k) + cc_ref(i, 2, k); + ti2 = cc_ref(i, 1, k) - cc_ref(i, 2, k); + ch_ref(i, k, 2) = wa1[i - 1] * ti2 - wa1[i] * tr2; + ch_ref(i - 1, k, 2) = wa1[i - 1] * tr2 + wa1[i] * ti2; + } + } + } +} /* passf2 */ + +#undef ch_ref +#undef cc_ref + + +static void passf3(integer ido, integer l1, const real *cc, real *ch, + const real *wa1, const real *wa2) +{ + static const real taur = -.5f; + static const real taui = -.866025403784439f; + + /* System generated locals */ + integer cc_offset, ch_offset; + + /* Local variables */ + integer i, k; + real ci2, ci3, di2, di3, cr2, cr3, dr2, dr3, ti2, tr2; + + +#define cc_ref(a_1,a_2,a_3) cc[((a_3)*3 + (a_2))*ido + a_1] +#define ch_ref(a_1,a_2,a_3) ch[((a_3)*l1 + (a_2))*ido + a_1] + + /* Parameter adjustments */ + ch_offset = 1 + ido * (1 + l1); + ch -= ch_offset; + cc_offset = 1 + (ido << 2); + cc -= cc_offset; + --wa1; + --wa2; + + /* Function Body */ + if (ido == 2) { + for (k = 1; k <= l1; ++k) { + tr2 = cc_ref(1, 2, k) + cc_ref(1, 3, k); + cr2 = cc_ref(1, 1, k) + taur * tr2; + ch_ref(1, k, 1) = cc_ref(1, 1, k) + tr2; + ti2 = cc_ref(2, 2, k) + cc_ref(2, 3, k); + ci2 = cc_ref(2, 1, k) + taur * ti2; + ch_ref(2, k, 1) = cc_ref(2, 1, k) + ti2; + cr3 = taui * (cc_ref(1, 2, k) - cc_ref(1, 3, k)); + ci3 = taui * (cc_ref(2, 2, k) - cc_ref(2, 3, k)); + ch_ref(1, k, 2) = cr2 - ci3; + ch_ref(1, k, 3) = cr2 + ci3; + ch_ref(2, k, 2) = ci2 + cr3; + ch_ref(2, k, 3) = ci2 - cr3; + } + } else { + for (k = 1; k <= l1; ++k) { + for (i = 2; i <= ido; i += 2) { + tr2 = cc_ref(i - 1, 2, k) + cc_ref(i - 1, 3, k); + cr2 = cc_ref(i - 1, 1, k) + taur * tr2; + ch_ref(i - 1, k, 1) = cc_ref(i - 1, 1, k) + tr2; + ti2 = cc_ref(i, 2, k) + cc_ref(i, 3, k); + ci2 = cc_ref(i, 1, k) + taur * ti2; + ch_ref(i, k, 1) = cc_ref(i, 1, k) + ti2; + cr3 = taui * (cc_ref(i - 1, 2, k) - cc_ref(i - 1, 3, k)); + ci3 = taui * (cc_ref(i, 2, k) - cc_ref(i, 3, k)); + dr2 = cr2 - ci3; + dr3 = cr2 + ci3; + di2 = ci2 + cr3; + di3 = ci2 - cr3; + ch_ref(i, k, 2) = wa1[i - 1] * di2 - wa1[i] * dr2; + ch_ref(i - 1, k, 2) = wa1[i - 1] * dr2 + wa1[i] * di2; + ch_ref(i, k, 3) = wa2[i - 1] * di3 - wa2[i] * dr3; + ch_ref(i - 1, k, 3) = wa2[i - 1] * dr3 + wa2[i] * di3; + } + } + } +} /* passf3 */ + +#undef ch_ref +#undef cc_ref + + +static void passf4(integer ido, integer l1, const real *cc, real *ch, + const real *wa1, const real *wa2, const real *wa3) +{ + /* System generated locals */ + integer cc_offset, ch_offset; + + /* Local variables */ + integer i, k; + real ci2, ci3, ci4, cr2, cr3, cr4, ti1, ti2, ti3, ti4, tr1, tr2, tr3, tr4; + + +#define cc_ref(a_1,a_2,a_3) cc[((a_3)*4 + (a_2))*ido + a_1] +#define ch_ref(a_1,a_2,a_3) ch[((a_3)*l1 + (a_2))*ido + a_1] + + /* Parameter adjustments */ + ch_offset = 1 + ido * (1 + l1); + ch -= ch_offset; + cc_offset = 1 + ido * 5; + cc -= cc_offset; + --wa1; + --wa2; + --wa3; + + /* Function Body */ + if (ido == 2) { + for (k = 1; k <= l1; ++k) { + ti1 = cc_ref(2, 1, k) - cc_ref(2, 3, k); + ti2 = cc_ref(2, 1, k) + cc_ref(2, 3, k); + tr4 = cc_ref(2, 2, k) - cc_ref(2, 4, k); + ti3 = cc_ref(2, 2, k) + cc_ref(2, 4, k); + tr1 = cc_ref(1, 1, k) - cc_ref(1, 3, k); + tr2 = cc_ref(1, 1, k) + cc_ref(1, 3, k); + ti4 = cc_ref(1, 4, k) - cc_ref(1, 2, k); + tr3 = cc_ref(1, 2, k) + cc_ref(1, 4, k); + ch_ref(1, k, 1) = tr2 + tr3; + ch_ref(1, k, 3) = tr2 - tr3; + ch_ref(2, k, 1) = ti2 + ti3; + ch_ref(2, k, 3) = ti2 - ti3; + ch_ref(1, k, 2) = tr1 + tr4; + ch_ref(1, k, 4) = tr1 - tr4; + ch_ref(2, k, 2) = ti1 + ti4; + ch_ref(2, k, 4) = ti1 - ti4; + } + } else { + for (k = 1; k <= l1; ++k) { + for (i = 2; i <= ido; i += 2) { + ti1 = cc_ref(i, 1, k) - cc_ref(i, 3, k); + ti2 = cc_ref(i, 1, k) + cc_ref(i, 3, k); + ti3 = cc_ref(i, 2, k) + cc_ref(i, 4, k); + tr4 = cc_ref(i, 2, k) - cc_ref(i, 4, k); + tr1 = cc_ref(i - 1, 1, k) - cc_ref(i - 1, 3, k); + tr2 = cc_ref(i - 1, 1, k) + cc_ref(i - 1, 3, k); + ti4 = cc_ref(i - 1, 4, k) - cc_ref(i - 1, 2, k); + tr3 = cc_ref(i - 1, 2, k) + cc_ref(i - 1, 4, k); + ch_ref(i - 1, k, 1) = tr2 + tr3; + cr3 = tr2 - tr3; + ch_ref(i, k, 1) = ti2 + ti3; + ci3 = ti2 - ti3; + cr2 = tr1 + tr4; + cr4 = tr1 - tr4; + ci2 = ti1 + ti4; + ci4 = ti1 - ti4; + ch_ref(i - 1, k, 2) = wa1[i - 1] * cr2 + wa1[i] * ci2; + ch_ref(i, k, 2) = wa1[i - 1] * ci2 - wa1[i] * cr2; + ch_ref(i - 1, k, 3) = wa2[i - 1] * cr3 + wa2[i] * ci3; + ch_ref(i, k, 3) = wa2[i - 1] * ci3 - wa2[i] * cr3; + ch_ref(i - 1, k, 4) = wa3[i - 1] * cr4 + wa3[i] * ci4; + ch_ref(i, k, 4) = wa3[i - 1] * ci4 - wa3[i] * cr4; + } + } + } +} /* passf4 */ + +#undef ch_ref +#undef cc_ref + +static void radb2(integer ido, integer l1, const real *cc, real *ch, const real *wa1) +{ + /* System generated locals */ + integer cc_offset, ch_offset; + + /* Local variables */ + integer i, k, ic; + real ti2, tr2; + integer idp2; + + +#define cc_ref(a_1,a_2,a_3) cc[((a_3)*2 + (a_2))*ido + a_1] +#define ch_ref(a_1,a_2,a_3) ch[((a_3)*l1 + (a_2))*ido + a_1] + + /* Parameter adjustments */ + ch_offset = 1 + ido * (1 + l1); + ch -= ch_offset; + cc_offset = 1 + ido * 3; + cc -= cc_offset; + --wa1; + + /* Function Body */ + for (k = 1; k <= l1; ++k) { + ch_ref(1, k, 1) = cc_ref(1, 1, k) + cc_ref(ido, 2, k); + ch_ref(1, k, 2) = cc_ref(1, 1, k) - cc_ref(ido, 2, k); + } + if (ido < 2) return; + else if (ido != 2) { + idp2 = ido + 2; + for (k = 1; k <= l1; ++k) { + for (i = 3; i <= ido; i += 2) { + ic = idp2 - i; + ch_ref(i - 1, k, 1) = cc_ref(i - 1, 1, k) + cc_ref(ic - 1, 2, + k); + tr2 = cc_ref(i - 1, 1, k) - cc_ref(ic - 1, 2, k); + ch_ref(i, k, 1) = cc_ref(i, 1, k) - cc_ref(ic, 2, k); + ti2 = cc_ref(i, 1, k) + cc_ref(ic, 2, k); + ch_ref(i - 1, k, 2) = wa1[i - 2] * tr2 - wa1[i - 1] * ti2; + ch_ref(i, k, 2) = wa1[i - 2] * ti2 + wa1[i - 1] * tr2; + } + } + if (ido % 2 == 1) return; + } + for (k = 1; k <= l1; ++k) { + ch_ref(ido, k, 1) = cc_ref(ido, 1, k) + cc_ref(ido, 1, k); + ch_ref(ido, k, 2) = -(cc_ref(1, 2, k) + cc_ref(1, 2, k)); + } +} /* radb2 */ + +#undef ch_ref +#undef cc_ref + + +static void radb3(integer ido, integer l1, const real *cc, real *ch, + const real *wa1, const real *wa2) +{ + /* Initialized data */ + + static const real taur = -.5f; + static const real taui = .866025403784439f; + + /* System generated locals */ + integer cc_offset, ch_offset; + + /* Local variables */ + integer i, k, ic; + real ci2, ci3, di2, di3, cr2, cr3, dr2, dr3, ti2, tr2; + integer idp2; + + +#define cc_ref(a_1,a_2,a_3) cc[((a_3)*3 + (a_2))*ido + a_1] +#define ch_ref(a_1,a_2,a_3) ch[((a_3)*l1 + (a_2))*ido + a_1] + + /* Parameter adjustments */ + ch_offset = 1 + ido * (1 + l1); + ch -= ch_offset; + cc_offset = 1 + (ido << 2); + cc -= cc_offset; + --wa1; + --wa2; + + /* Function Body */ + for (k = 1; k <= l1; ++k) { + tr2 = cc_ref(ido, 2, k) + cc_ref(ido, 2, k); + cr2 = cc_ref(1, 1, k) + taur * tr2; + ch_ref(1, k, 1) = cc_ref(1, 1, k) + tr2; + ci3 = taui * (cc_ref(1, 3, k) + cc_ref(1, 3, k)); + ch_ref(1, k, 2) = cr2 - ci3; + ch_ref(1, k, 3) = cr2 + ci3; + } + if (ido == 1) { + return; + } + idp2 = ido + 2; + for (k = 1; k <= l1; ++k) { + for (i = 3; i <= ido; i += 2) { + ic = idp2 - i; + tr2 = cc_ref(i - 1, 3, k) + cc_ref(ic - 1, 2, k); + cr2 = cc_ref(i - 1, 1, k) + taur * tr2; + ch_ref(i - 1, k, 1) = cc_ref(i - 1, 1, k) + tr2; + ti2 = cc_ref(i, 3, k) - cc_ref(ic, 2, k); + ci2 = cc_ref(i, 1, k) + taur * ti2; + ch_ref(i, k, 1) = cc_ref(i, 1, k) + ti2; + cr3 = taui * (cc_ref(i - 1, 3, k) - cc_ref(ic - 1, 2, k)); + ci3 = taui * (cc_ref(i, 3, k) + cc_ref(ic, 2, k)); + dr2 = cr2 - ci3; + dr3 = cr2 + ci3; + di2 = ci2 + cr3; + di3 = ci2 - cr3; + ch_ref(i - 1, k, 2) = wa1[i - 2] * dr2 - wa1[i - 1] * di2; + ch_ref(i, k, 2) = wa1[i - 2] * di2 + wa1[i - 1] * dr2; + ch_ref(i - 1, k, 3) = wa2[i - 2] * dr3 - wa2[i - 1] * di3; + ch_ref(i, k, 3) = wa2[i - 2] * di3 + wa2[i - 1] * dr3; + } + } +} /* radb3 */ + +#undef ch_ref +#undef cc_ref + + +static void radb4(integer ido, integer l1, const real *cc, real *ch, + const real *wa1, const real *wa2, const real *wa3) +{ + /* Initialized data */ + + static const real sqrt2 = 1.414213562373095f; + + /* System generated locals */ + integer cc_offset, ch_offset; + + /* Local variables */ + integer i, k, ic; + real ci2, ci3, ci4, cr2, cr3, cr4, ti1, ti2, ti3, ti4, tr1, tr2, tr3, tr4; + integer idp2; + + +#define cc_ref(a_1,a_2,a_3) cc[((a_3)*4 + (a_2))*ido + a_1] +#define ch_ref(a_1,a_2,a_3) ch[((a_3)*l1 + (a_2))*ido + a_1] + + /* Parameter adjustments */ + ch_offset = 1 + ido * (1 + l1); + ch -= ch_offset; + cc_offset = 1 + ido * 5; + cc -= cc_offset; + --wa1; + --wa2; + --wa3; + + /* Function Body */ + for (k = 1; k <= l1; ++k) { + tr1 = cc_ref(1, 1, k) - cc_ref(ido, 4, k); + tr2 = cc_ref(1, 1, k) + cc_ref(ido, 4, k); + tr3 = cc_ref(ido, 2, k) + cc_ref(ido, 2, k); + tr4 = cc_ref(1, 3, k) + cc_ref(1, 3, k); + ch_ref(1, k, 1) = tr2 + tr3; + ch_ref(1, k, 2) = tr1 - tr4; + ch_ref(1, k, 3) = tr2 - tr3; + ch_ref(1, k, 4) = tr1 + tr4; + } + if (ido < 2) return; + if (ido != 2) { + idp2 = ido + 2; + for (k = 1; k <= l1; ++k) { + for (i = 3; i <= ido; i += 2) { + ic = idp2 - i; + ti1 = cc_ref(i, 1, k) + cc_ref(ic, 4, k); + ti2 = cc_ref(i, 1, k) - cc_ref(ic, 4, k); + ti3 = cc_ref(i, 3, k) - cc_ref(ic, 2, k); + tr4 = cc_ref(i, 3, k) + cc_ref(ic, 2, k); + tr1 = cc_ref(i - 1, 1, k) - cc_ref(ic - 1, 4, k); + tr2 = cc_ref(i - 1, 1, k) + cc_ref(ic - 1, 4, k); + ti4 = cc_ref(i - 1, 3, k) - cc_ref(ic - 1, 2, k); + tr3 = cc_ref(i - 1, 3, k) + cc_ref(ic - 1, 2, k); + ch_ref(i - 1, k, 1) = tr2 + tr3; + cr3 = tr2 - tr3; + ch_ref(i, k, 1) = ti2 + ti3; + ci3 = ti2 - ti3; + cr2 = tr1 - tr4; + cr4 = tr1 + tr4; + ci2 = ti1 + ti4; + ci4 = ti1 - ti4; + ch_ref(i - 1, k, 2) = wa1[i - 2] * cr2 - wa1[i - 1] * ci2; + ch_ref(i, k, 2) = wa1[i - 2] * ci2 + wa1[i - 1] * cr2; + ch_ref(i - 1, k, 3) = wa2[i - 2] * cr3 - wa2[i - 1] * ci3; + ch_ref(i, k, 3) = wa2[i - 2] * ci3 + wa2[i - 1] * cr3; + ch_ref(i - 1, k, 4) = wa3[i - 2] * cr4 - wa3[i - 1] * ci4; + ch_ref(i, k, 4) = wa3[i - 2] * ci4 + wa3[i - 1] * cr4; + } + } + if (ido % 2 == 1) return; + } + for (k = 1; k <= l1; ++k) { + ti1 = cc_ref(1, 2, k) + cc_ref(1, 4, k); + ti2 = cc_ref(1, 4, k) - cc_ref(1, 2, k); + tr1 = cc_ref(ido, 1, k) - cc_ref(ido, 3, k); + tr2 = cc_ref(ido, 1, k) + cc_ref(ido, 3, k); + ch_ref(ido, k, 1) = tr2 + tr2; + ch_ref(ido, k, 2) = sqrt2 * (tr1 - ti1); + ch_ref(ido, k, 3) = ti2 + ti2; + ch_ref(ido, k, 4) = -sqrt2 * (tr1 + ti1); + } +} /* radb4 */ + +#undef ch_ref +#undef cc_ref + + +static void radb5(integer ido, integer l1, const real *cc, real *ch, + const real *wa1, const real *wa2, const real *wa3, const real *wa4) +{ + /* Initialized data */ + + static const real tr11 = .309016994374947f; + static const real ti11 = .951056516295154f; + static const real tr12 = -.809016994374947f; + static const real ti12 = .587785252292473f; + + /* System generated locals */ + integer cc_offset, ch_offset; + + /* Local variables */ + integer i, k, ic; + real ci2, ci3, ci4, ci5, di3, di4, di5, di2, cr2, cr3, cr5, cr4, ti2, ti3, + ti4, ti5, dr3, dr4, dr5, dr2, tr2, tr3, tr4, tr5; + integer idp2; + + +#define cc_ref(a_1,a_2,a_3) cc[((a_3)*5 + (a_2))*ido + a_1] +#define ch_ref(a_1,a_2,a_3) ch[((a_3)*l1 + (a_2))*ido + a_1] + + /* Parameter adjustments */ + ch_offset = 1 + ido * (1 + l1); + ch -= ch_offset; + cc_offset = 1 + ido * 6; + cc -= cc_offset; + --wa1; + --wa2; + --wa3; + --wa4; + + /* Function Body */ + for (k = 1; k <= l1; ++k) { + ti5 = cc_ref(1, 3, k) + cc_ref(1, 3, k); + ti4 = cc_ref(1, 5, k) + cc_ref(1, 5, k); + tr2 = cc_ref(ido, 2, k) + cc_ref(ido, 2, k); + tr3 = cc_ref(ido, 4, k) + cc_ref(ido, 4, k); + ch_ref(1, k, 1) = cc_ref(1, 1, k) + tr2 + tr3; + cr2 = cc_ref(1, 1, k) + tr11 * tr2 + tr12 * tr3; + cr3 = cc_ref(1, 1, k) + tr12 * tr2 + tr11 * tr3; + ci5 = ti11 * ti5 + ti12 * ti4; + ci4 = ti12 * ti5 - ti11 * ti4; + ch_ref(1, k, 2) = cr2 - ci5; + ch_ref(1, k, 3) = cr3 - ci4; + ch_ref(1, k, 4) = cr3 + ci4; + ch_ref(1, k, 5) = cr2 + ci5; + } + if (ido == 1) { + return; + } + idp2 = ido + 2; + for (k = 1; k <= l1; ++k) { + for (i = 3; i <= ido; i += 2) { + ic = idp2 - i; + ti5 = cc_ref(i, 3, k) + cc_ref(ic, 2, k); + ti2 = cc_ref(i, 3, k) - cc_ref(ic, 2, k); + ti4 = cc_ref(i, 5, k) + cc_ref(ic, 4, k); + ti3 = cc_ref(i, 5, k) - cc_ref(ic, 4, k); + tr5 = cc_ref(i - 1, 3, k) - cc_ref(ic - 1, 2, k); + tr2 = cc_ref(i - 1, 3, k) + cc_ref(ic - 1, 2, k); + tr4 = cc_ref(i - 1, 5, k) - cc_ref(ic - 1, 4, k); + tr3 = cc_ref(i - 1, 5, k) + cc_ref(ic - 1, 4, k); + ch_ref(i - 1, k, 1) = cc_ref(i - 1, 1, k) + tr2 + tr3; + ch_ref(i, k, 1) = cc_ref(i, 1, k) + ti2 + ti3; + cr2 = cc_ref(i - 1, 1, k) + tr11 * tr2 + tr12 * tr3; + ci2 = cc_ref(i, 1, k) + tr11 * ti2 + tr12 * ti3; + cr3 = cc_ref(i - 1, 1, k) + tr12 * tr2 + tr11 * tr3; + ci3 = cc_ref(i, 1, k) + tr12 * ti2 + tr11 * ti3; + cr5 = ti11 * tr5 + ti12 * tr4; + ci5 = ti11 * ti5 + ti12 * ti4; + cr4 = ti12 * tr5 - ti11 * tr4; + ci4 = ti12 * ti5 - ti11 * ti4; + dr3 = cr3 - ci4; + dr4 = cr3 + ci4; + di3 = ci3 + cr4; + di4 = ci3 - cr4; + dr5 = cr2 + ci5; + dr2 = cr2 - ci5; + di5 = ci2 - cr5; + di2 = ci2 + cr5; + ch_ref(i - 1, k, 2) = wa1[i - 2] * dr2 - wa1[i - 1] * di2; + ch_ref(i, k, 2) = wa1[i - 2] * di2 + wa1[i - 1] * dr2; + ch_ref(i - 1, k, 3) = wa2[i - 2] * dr3 - wa2[i - 1] * di3; + ch_ref(i, k, 3) = wa2[i - 2] * di3 + wa2[i - 1] * dr3; + ch_ref(i - 1, k, 4) = wa3[i - 2] * dr4 - wa3[i - 1] * di4; + ch_ref(i, k, 4) = wa3[i - 2] * di4 + wa3[i - 1] * dr4; + ch_ref(i - 1, k, 5) = wa4[i - 2] * dr5 - wa4[i - 1] * di5; + ch_ref(i, k, 5) = wa4[i - 2] * di5 + wa4[i - 1] * dr5; + } + } +} /* radb5 */ + +#undef ch_ref +#undef cc_ref + + +static void radbg(integer ido, integer ip, integer l1, integer idl1, + const real *cc, real *c1, real *c2, real *ch, real *ch2, const real *wa) +{ + /* System generated locals */ + integer ch_offset, cc_offset, + c1_offset, c2_offset, ch2_offset; + + /* Local variables */ + integer i, j, k, l, j2, ic, jc, lc, ik, is; + real dc2, ai1, ai2, ar1, ar2, ds2; + integer nbd; + real dcp, arg, dsp, ar1h, ar2h; + integer idp2, ipp2, idij, ipph; + + +#define c1_ref(a_1,a_2,a_3) c1[((a_3)*l1 + (a_2))*ido + a_1] +#define c2_ref(a_1,a_2) c2[(a_2)*idl1 + a_1] +#define cc_ref(a_1,a_2,a_3) cc[((a_3)*ip + (a_2))*ido + a_1] +#define ch_ref(a_1,a_2,a_3) ch[((a_3)*l1 + (a_2))*ido + a_1] +#define ch2_ref(a_1,a_2) ch2[(a_2)*idl1 + a_1] + + /* Parameter adjustments */ + ch_offset = 1 + ido * (1 + l1); + ch -= ch_offset; + c1_offset = 1 + ido * (1 + l1); + c1 -= c1_offset; + cc_offset = 1 + ido * (1 + ip); + cc -= cc_offset; + ch2_offset = 1 + idl1; + ch2 -= ch2_offset; + c2_offset = 1 + idl1; + c2 -= c2_offset; + --wa; + + /* Function Body */ + arg = (2*M_PI) / (real) (ip); + dcp = cos(arg); + dsp = sin(arg); + idp2 = ido + 2; + nbd = (ido - 1) / 2; + ipp2 = ip + 2; + ipph = (ip + 1) / 2; + if (ido >= l1) { + for (k = 1; k <= l1; ++k) { + for (i = 1; i <= ido; ++i) { + ch_ref(i, k, 1) = cc_ref(i, 1, k); + } + } + } else { + for (i = 1; i <= ido; ++i) { + for (k = 1; k <= l1; ++k) { + ch_ref(i, k, 1) = cc_ref(i, 1, k); + } + } + } + for (j = 2; j <= ipph; ++j) { + jc = ipp2 - j; + j2 = j + j; + for (k = 1; k <= l1; ++k) { + ch_ref(1, k, j) = cc_ref(ido, j2 - 2, k) + cc_ref(ido, j2 - 2, k); + ch_ref(1, k, jc) = cc_ref(1, j2 - 1, k) + cc_ref(1, j2 - 1, k); + } + } + if (ido != 1) { + if (nbd >= l1) { + for (j = 2; j <= ipph; ++j) { + jc = ipp2 - j; + for (k = 1; k <= l1; ++k) { + for (i = 3; i <= ido; i += 2) { + ic = idp2 - i; + ch_ref(i - 1, k, j) = cc_ref(i - 1, (j << 1) - 1, k) + cc_ref(ic - 1, (j << 1) - 2, k); + ch_ref(i - 1, k, jc) = cc_ref(i - 1, (j << 1) - 1, k) - cc_ref(ic - 1, (j << 1) - 2, k); + ch_ref(i, k, j) = cc_ref(i, (j << 1) - 1, k) - cc_ref(ic, (j << 1) - 2, k); + ch_ref(i, k, jc) = cc_ref(i, (j << 1) - 1, k) + cc_ref(ic, (j << 1) - 2, k); + } + } + } + } else { + for (j = 2; j <= ipph; ++j) { + jc = ipp2 - j; + for (i = 3; i <= ido; i += 2) { + ic = idp2 - i; + for (k = 1; k <= l1; ++k) { + ch_ref(i - 1, k, j) = cc_ref(i - 1, (j << 1) - 1, k) + cc_ref(ic - 1, (j << 1) - 2, k); + ch_ref(i - 1, k, jc) = cc_ref(i - 1, (j << 1) - 1, k) - cc_ref(ic - 1, (j << 1) - 2, k); + ch_ref(i, k, j) = cc_ref(i, (j << 1) - 1, k) - cc_ref(ic, (j << 1) - 2, k); + ch_ref(i, k, jc) = cc_ref(i, (j << 1) - 1, k) + cc_ref(ic, (j << 1) - 2, k); + } + } + } + } + } + ar1 = 1.f; + ai1 = 0.f; + for (l = 2; l <= ipph; ++l) { + lc = ipp2 - l; + ar1h = dcp * ar1 - dsp * ai1; + ai1 = dcp * ai1 + dsp * ar1; + ar1 = ar1h; + for (ik = 1; ik <= idl1; ++ik) { + c2_ref(ik, l) = ch2_ref(ik, 1) + ar1 * ch2_ref(ik, 2); + c2_ref(ik, lc) = ai1 * ch2_ref(ik, ip); + } + dc2 = ar1; + ds2 = ai1; + ar2 = ar1; + ai2 = ai1; + for (j = 3; j <= ipph; ++j) { + jc = ipp2 - j; + ar2h = dc2 * ar2 - ds2 * ai2; + ai2 = dc2 * ai2 + ds2 * ar2; + ar2 = ar2h; + for (ik = 1; ik <= idl1; ++ik) { + c2_ref(ik, l) = c2_ref(ik, l) + ar2 * ch2_ref(ik, j); + c2_ref(ik, lc) = c2_ref(ik, lc) + ai2 * ch2_ref(ik, jc); + } + } + } + for (j = 2; j <= ipph; ++j) { + for (ik = 1; ik <= idl1; ++ik) { + ch2_ref(ik, 1) = ch2_ref(ik, 1) + ch2_ref(ik, j); + } + } + for (j = 2; j <= ipph; ++j) { + jc = ipp2 - j; + for (k = 1; k <= l1; ++k) { + ch_ref(1, k, j) = c1_ref(1, k, j) - c1_ref(1, k, jc); + ch_ref(1, k, jc) = c1_ref(1, k, j) + c1_ref(1, k, jc); + } + } + if (ido != 1) { + if (nbd >= l1) { + for (j = 2; j <= ipph; ++j) { + jc = ipp2 - j; + for (k = 1; k <= l1; ++k) { + for (i = 3; i <= ido; i += 2) { + ch_ref(i - 1, k, j) = c1_ref(i - 1, k, j) - c1_ref(i, k, jc); + ch_ref(i - 1, k, jc) = c1_ref(i - 1, k, j) + c1_ref(i, k, jc); + ch_ref(i, k, j) = c1_ref(i, k, j) + c1_ref(i - 1, k, jc); + ch_ref(i, k, jc) = c1_ref(i, k, j) - c1_ref(i - 1, k, jc); + } + } + } + } else { + for (j = 2; j <= ipph; ++j) { + jc = ipp2 - j; + for (i = 3; i <= ido; i += 2) { + for (k = 1; k <= l1; ++k) { + ch_ref(i - 1, k, j) = c1_ref(i - 1, k, j) - c1_ref(i, k, jc); + ch_ref(i - 1, k, jc) = c1_ref(i - 1, k, j) + c1_ref(i, k, jc); + ch_ref(i, k, j) = c1_ref(i, k, j) + c1_ref(i - 1, k, jc); + ch_ref(i, k, jc) = c1_ref(i, k, j) - c1_ref(i - 1, k, jc); + } + } + } + } + } + if (ido == 1) { + return; + } + for (ik = 1; ik <= idl1; ++ik) { + c2_ref(ik, 1) = ch2_ref(ik, 1); + } + for (j = 2; j <= ip; ++j) { + for (k = 1; k <= l1; ++k) { + c1_ref(1, k, j) = ch_ref(1, k, j); + } + } + if (nbd <= l1) { + is = -(ido); + for (j = 2; j <= ip; ++j) { + is += ido; + idij = is; + for (i = 3; i <= ido; i += 2) { + idij += 2; + for (k = 1; k <= l1; ++k) { + c1_ref(i - 1, k, j) = wa[idij - 1] * ch_ref(i - 1, k, j) + - wa[idij] * ch_ref(i, k, j); + c1_ref(i, k, j) = wa[idij - 1] * ch_ref(i, k, j) + wa[idij] * ch_ref(i - 1, k, j); + } + } + } + } else { + is = -(ido); + for (j = 2; j <= ip; ++j) { + is += ido; + for (k = 1; k <= l1; ++k) { + idij = is; + for (i = 3; i <= ido; i += 2) { + idij += 2; + c1_ref(i - 1, k, j) = wa[idij - 1] * ch_ref(i - 1, k, j) + - wa[idij] * ch_ref(i, k, j); + c1_ref(i, k, j) = wa[idij - 1] * ch_ref(i, k, j) + wa[idij] * ch_ref(i - 1, k, j); + } + } + } + } +} /* radbg */ + +#undef ch2_ref +#undef ch_ref +#undef cc_ref +#undef c2_ref +#undef c1_ref + + +static void radf2(integer ido, integer l1, const real *cc, real *ch, + const real *wa1) +{ + /* System generated locals */ + integer ch_offset, cc_offset; + + /* Local variables */ + integer i, k, ic; + real ti2, tr2; + integer idp2; + + +#define cc_ref(a_1,a_2,a_3) cc[((a_3)*l1 + (a_2))*ido + a_1] +#define ch_ref(a_1,a_2,a_3) ch[((a_3)*2 + (a_2))*ido + a_1] + + /* Parameter adjustments */ + ch_offset = 1 + ido * 3; + ch -= ch_offset; + cc_offset = 1 + ido * (1 + l1); + cc -= cc_offset; + --wa1; + + /* Function Body */ + for (k = 1; k <= l1; ++k) { + ch_ref(1, 1, k) = cc_ref(1, k, 1) + cc_ref(1, k, 2); + ch_ref(ido, 2, k) = cc_ref(1, k, 1) - cc_ref(1, k, 2); + } + if (ido < 2) return; + if (ido != 2) { + idp2 = ido + 2; + for (k = 1; k <= l1; ++k) { + for (i = 3; i <= ido; i += 2) { + ic = idp2 - i; + tr2 = wa1[i - 2] * cc_ref(i - 1, k, 2) + wa1[i - 1] * + cc_ref(i, k, 2); + ti2 = wa1[i - 2] * cc_ref(i, k, 2) - wa1[i - 1] * cc_ref( + i - 1, k, 2); + ch_ref(i, 1, k) = cc_ref(i, k, 1) + ti2; + ch_ref(ic, 2, k) = ti2 - cc_ref(i, k, 1); + ch_ref(i - 1, 1, k) = cc_ref(i - 1, k, 1) + tr2; + ch_ref(ic - 1, 2, k) = cc_ref(i - 1, k, 1) - tr2; + } + } + if (ido % 2 == 1) { + return; + } + } + for (k = 1; k <= l1; ++k) { + ch_ref(1, 2, k) = -cc_ref(ido, k, 2); + ch_ref(ido, 1, k) = cc_ref(ido, k, 1); + } +} /* radf2 */ + +#undef ch_ref +#undef cc_ref + + +static void radf3(integer ido, integer l1, const real *cc, real *ch, + const real *wa1, const real *wa2) +{ + static const real taur = -.5f; + static const real taui = .866025403784439f; + + /* System generated locals */ + integer ch_offset, cc_offset; + + /* Local variables */ + integer i, k, ic; + real ci2, di2, di3, cr2, dr2, dr3, ti2, ti3, tr2, tr3; + integer idp2; + + +#define cc_ref(a_1,a_2,a_3) cc[((a_3)*l1 + (a_2))*ido + a_1] +#define ch_ref(a_1,a_2,a_3) ch[((a_3)*3 + (a_2))*ido + a_1] + + /* Parameter adjustments */ + ch_offset = 1 + (ido << 2); + ch -= ch_offset; + cc_offset = 1 + ido * (1 + l1); + cc -= cc_offset; + --wa1; + --wa2; + + /* Function Body */ + for (k = 1; k <= l1; ++k) { + cr2 = cc_ref(1, k, 2) + cc_ref(1, k, 3); + ch_ref(1, 1, k) = cc_ref(1, k, 1) + cr2; + ch_ref(1, 3, k) = taui * (cc_ref(1, k, 3) - cc_ref(1, k, 2)); + ch_ref(ido, 2, k) = cc_ref(1, k, 1) + taur * cr2; + } + if (ido == 1) { + return; + } + idp2 = ido + 2; + for (k = 1; k <= l1; ++k) { + for (i = 3; i <= ido; i += 2) { + ic = idp2 - i; + dr2 = wa1[i - 2] * cc_ref(i - 1, k, 2) + wa1[i - 1] * + cc_ref(i, k, 2); + di2 = wa1[i - 2] * cc_ref(i, k, 2) - wa1[i - 1] * cc_ref( + i - 1, k, 2); + dr3 = wa2[i - 2] * cc_ref(i - 1, k, 3) + wa2[i - 1] * + cc_ref(i, k, 3); + di3 = wa2[i - 2] * cc_ref(i, k, 3) - wa2[i - 1] * cc_ref( + i - 1, k, 3); + cr2 = dr2 + dr3; + ci2 = di2 + di3; + ch_ref(i - 1, 1, k) = cc_ref(i - 1, k, 1) + cr2; + ch_ref(i, 1, k) = cc_ref(i, k, 1) + ci2; + tr2 = cc_ref(i - 1, k, 1) + taur * cr2; + ti2 = cc_ref(i, k, 1) + taur * ci2; + tr3 = taui * (di2 - di3); + ti3 = taui * (dr3 - dr2); + ch_ref(i - 1, 3, k) = tr2 + tr3; + ch_ref(ic - 1, 2, k) = tr2 - tr3; + ch_ref(i, 3, k) = ti2 + ti3; + ch_ref(ic, 2, k) = ti3 - ti2; + } + } +} /* radf3 */ + +#undef ch_ref +#undef cc_ref + + +static void radf4(integer ido, integer l1, const real *cc, real *ch, + const real *wa1, const real *wa2, const real *wa3) +{ + /* Initialized data */ + + static const real hsqt2 = .7071067811865475f; + + /* System generated locals */ + integer cc_offset, ch_offset; + + /* Local variables */ + integer i, k, ic; + real ci2, ci3, ci4, cr2, cr3, cr4, ti1, ti2, ti3, ti4, tr1, tr2, tr3, tr4; + integer idp2; + + +#define cc_ref(a_1,a_2,a_3) cc[((a_3)*l1 + (a_2))*ido + a_1] +#define ch_ref(a_1,a_2,a_3) ch[((a_3)*4 + (a_2))*ido + a_1] + + /* Parameter adjustments */ + ch_offset = 1 + ido * 5; + ch -= ch_offset; + cc_offset = 1 + ido * (1 + l1); + cc -= cc_offset; + --wa1; + --wa2; + --wa3; + + /* Function Body */ + for (k = 1; k <= l1; ++k) { + tr1 = cc_ref(1, k, 2) + cc_ref(1, k, 4); + tr2 = cc_ref(1, k, 1) + cc_ref(1, k, 3); + ch_ref(1, 1, k) = tr1 + tr2; + ch_ref(ido, 4, k) = tr2 - tr1; + ch_ref(ido, 2, k) = cc_ref(1, k, 1) - cc_ref(1, k, 3); + ch_ref(1, 3, k) = cc_ref(1, k, 4) - cc_ref(1, k, 2); + } + if (ido < 2) return; + if (ido != 2) { + idp2 = ido + 2; + for (k = 1; k <= l1; ++k) { + for (i = 3; i <= ido; i += 2) { + ic = idp2 - i; + cr2 = wa1[i - 2] * cc_ref(i - 1, k, 2) + wa1[i - 1] * + cc_ref(i, k, 2); + ci2 = wa1[i - 2] * cc_ref(i, k, 2) - wa1[i - 1] * cc_ref( + i - 1, k, 2); + cr3 = wa2[i - 2] * cc_ref(i - 1, k, 3) + wa2[i - 1] * + cc_ref(i, k, 3); + ci3 = wa2[i - 2] * cc_ref(i, k, 3) - wa2[i - 1] * cc_ref( + i - 1, k, 3); + cr4 = wa3[i - 2] * cc_ref(i - 1, k, 4) + wa3[i - 1] * + cc_ref(i, k, 4); + ci4 = wa3[i - 2] * cc_ref(i, k, 4) - wa3[i - 1] * cc_ref( + i - 1, k, 4); + tr1 = cr2 + cr4; + tr4 = cr4 - cr2; + ti1 = ci2 + ci4; + ti4 = ci2 - ci4; + ti2 = cc_ref(i, k, 1) + ci3; + ti3 = cc_ref(i, k, 1) - ci3; + tr2 = cc_ref(i - 1, k, 1) + cr3; + tr3 = cc_ref(i - 1, k, 1) - cr3; + ch_ref(i - 1, 1, k) = tr1 + tr2; + ch_ref(ic - 1, 4, k) = tr2 - tr1; + ch_ref(i, 1, k) = ti1 + ti2; + ch_ref(ic, 4, k) = ti1 - ti2; + ch_ref(i - 1, 3, k) = ti4 + tr3; + ch_ref(ic - 1, 2, k) = tr3 - ti4; + ch_ref(i, 3, k) = tr4 + ti3; + ch_ref(ic, 2, k) = tr4 - ti3; + } + } + if (ido % 2 == 1) { + return; + } + } + for (k = 1; k <= l1; ++k) { + ti1 = -hsqt2 * (cc_ref(ido, k, 2) + cc_ref(ido, k, 4)); + tr1 = hsqt2 * (cc_ref(ido, k, 2) - cc_ref(ido, k, 4)); + ch_ref(ido, 1, k) = tr1 + cc_ref(ido, k, 1); + ch_ref(ido, 3, k) = cc_ref(ido, k, 1) - tr1; + ch_ref(1, 2, k) = ti1 - cc_ref(ido, k, 3); + ch_ref(1, 4, k) = ti1 + cc_ref(ido, k, 3); + } +} /* radf4 */ + +#undef ch_ref +#undef cc_ref + + +static void radf5(integer ido, integer l1, const real *cc, real *ch, + const real *wa1, const real *wa2, const real *wa3, const real *wa4) +{ + /* Initialized data */ + + static const real tr11 = .309016994374947f; + static const real ti11 = .951056516295154f; + static const real tr12 = -.809016994374947f; + static const real ti12 = .587785252292473f; + + /* System generated locals */ + integer cc_offset, ch_offset; + + /* Local variables */ + integer i, k, ic; + real ci2, di2, ci4, ci5, di3, di4, di5, ci3, cr2, cr3, dr2, dr3, dr4, dr5, + cr5, cr4, ti2, ti3, ti5, ti4, tr2, tr3, tr4, tr5; + integer idp2; + + +#define cc_ref(a_1,a_2,a_3) cc[((a_3)*l1 + (a_2))*ido + a_1] +#define ch_ref(a_1,a_2,a_3) ch[((a_3)*5 + (a_2))*ido + a_1] + + /* Parameter adjustments */ + ch_offset = 1 + ido * 6; + ch -= ch_offset; + cc_offset = 1 + ido * (1 + l1); + cc -= cc_offset; + --wa1; + --wa2; + --wa3; + --wa4; + + /* Function Body */ + for (k = 1; k <= l1; ++k) { + cr2 = cc_ref(1, k, 5) + cc_ref(1, k, 2); + ci5 = cc_ref(1, k, 5) - cc_ref(1, k, 2); + cr3 = cc_ref(1, k, 4) + cc_ref(1, k, 3); + ci4 = cc_ref(1, k, 4) - cc_ref(1, k, 3); + ch_ref(1, 1, k) = cc_ref(1, k, 1) + cr2 + cr3; + ch_ref(ido, 2, k) = cc_ref(1, k, 1) + tr11 * cr2 + tr12 * cr3; + ch_ref(1, 3, k) = ti11 * ci5 + ti12 * ci4; + ch_ref(ido, 4, k) = cc_ref(1, k, 1) + tr12 * cr2 + tr11 * cr3; + ch_ref(1, 5, k) = ti12 * ci5 - ti11 * ci4; + } + if (ido == 1) { + return; + } + idp2 = ido + 2; + for (k = 1; k <= l1; ++k) { + for (i = 3; i <= ido; i += 2) { + ic = idp2 - i; + dr2 = wa1[i - 2] * cc_ref(i - 1, k, 2) + wa1[i - 1] * cc_ref(i, k, 2); + di2 = wa1[i - 2] * cc_ref(i, k, 2) - wa1[i - 1] * cc_ref(i - 1, k, 2); + dr3 = wa2[i - 2] * cc_ref(i - 1, k, 3) + wa2[i - 1] * cc_ref(i, k, 3); + di3 = wa2[i - 2] * cc_ref(i, k, 3) - wa2[i - 1] * cc_ref(i - 1, k, 3); + dr4 = wa3[i - 2] * cc_ref(i - 1, k, 4) + wa3[i - 1] * cc_ref(i, k, 4); + di4 = wa3[i - 2] * cc_ref(i, k, 4) - wa3[i - 1] * cc_ref(i - 1, k, 4); + dr5 = wa4[i - 2] * cc_ref(i - 1, k, 5) + wa4[i - 1] * cc_ref(i, k, 5); + di5 = wa4[i - 2] * cc_ref(i, k, 5) - wa4[i - 1] * cc_ref(i - 1, k, 5); + cr2 = dr2 + dr5; + ci5 = dr5 - dr2; + cr5 = di2 - di5; + ci2 = di2 + di5; + cr3 = dr3 + dr4; + ci4 = dr4 - dr3; + cr4 = di3 - di4; + ci3 = di3 + di4; + ch_ref(i - 1, 1, k) = cc_ref(i - 1, k, 1) + cr2 + cr3; + ch_ref(i, 1, k) = cc_ref(i, k, 1) + ci2 + ci3; + tr2 = cc_ref(i - 1, k, 1) + tr11 * cr2 + tr12 * cr3; + ti2 = cc_ref(i, k, 1) + tr11 * ci2 + tr12 * ci3; + tr3 = cc_ref(i - 1, k, 1) + tr12 * cr2 + tr11 * cr3; + ti3 = cc_ref(i, k, 1) + tr12 * ci2 + tr11 * ci3; + tr5 = ti11 * cr5 + ti12 * cr4; + ti5 = ti11 * ci5 + ti12 * ci4; + tr4 = ti12 * cr5 - ti11 * cr4; + ti4 = ti12 * ci5 - ti11 * ci4; + ch_ref(i - 1, 3, k) = tr2 + tr5; + ch_ref(ic - 1, 2, k) = tr2 - tr5; + ch_ref(i, 3, k) = ti2 + ti5; + ch_ref(ic, 2, k) = ti5 - ti2; + ch_ref(i - 1, 5, k) = tr3 + tr4; + ch_ref(ic - 1, 4, k) = tr3 - tr4; + ch_ref(i, 5, k) = ti3 + ti4; + ch_ref(ic, 4, k) = ti4 - ti3; + } + } +} /* radf5 */ + +#undef ch_ref +#undef cc_ref + + +static void radfg(integer ido, integer ip, integer l1, integer idl1, + real *cc, real *c1, real *c2, real *ch, real *ch2, const real *wa) +{ + /* System generated locals */ + integer ch_offset, cc_offset, + c1_offset, c2_offset, ch2_offset; + + /* Local variables */ + integer i, j, k, l, j2, ic, jc, lc, ik, is; + real dc2, ai1, ai2, ar1, ar2, ds2; + integer nbd; + real dcp, arg, dsp, ar1h, ar2h; + integer idp2, ipp2, idij, ipph; + + +#define c1_ref(a_1,a_2,a_3) c1[((a_3)*l1 + (a_2))*ido + a_1] +#define c2_ref(a_1,a_2) c2[(a_2)*idl1 + a_1] +#define cc_ref(a_1,a_2,a_3) cc[((a_3)*ip + (a_2))*ido + a_1] +#define ch_ref(a_1,a_2,a_3) ch[((a_3)*l1 + (a_2))*ido + a_1] +#define ch2_ref(a_1,a_2) ch2[(a_2)*idl1 + a_1] + + /* Parameter adjustments */ + ch_offset = 1 + ido * (1 + l1); + ch -= ch_offset; + c1_offset = 1 + ido * (1 + l1); + c1 -= c1_offset; + cc_offset = 1 + ido * (1 + ip); + cc -= cc_offset; + ch2_offset = 1 + idl1; + ch2 -= ch2_offset; + c2_offset = 1 + idl1; + c2 -= c2_offset; + --wa; + + /* Function Body */ + arg = (2*M_PI) / (real) (ip); + dcp = cos(arg); + dsp = sin(arg); + ipph = (ip + 1) / 2; + ipp2 = ip + 2; + idp2 = ido + 2; + nbd = (ido - 1) / 2; + if (ido == 1) { + for (ik = 1; ik <= idl1; ++ik) { + c2_ref(ik, 1) = ch2_ref(ik, 1); + } + } else { + for (ik = 1; ik <= idl1; ++ik) { + ch2_ref(ik, 1) = c2_ref(ik, 1); + } + for (j = 2; j <= ip; ++j) { + for (k = 1; k <= l1; ++k) { + ch_ref(1, k, j) = c1_ref(1, k, j); + } + } + if (nbd <= l1) { + is = -(ido); + for (j = 2; j <= ip; ++j) { + is += ido; + idij = is; + for (i = 3; i <= ido; i += 2) { + idij += 2; + for (k = 1; k <= l1; ++k) { + ch_ref(i - 1, k, j) = wa[idij - 1] * c1_ref(i - 1, k, j) + + wa[idij] * c1_ref(i, k, j); + ch_ref(i, k, j) = wa[idij - 1] * c1_ref(i, k, j) - wa[ + idij] * c1_ref(i - 1, k, j); + } + } + } + } else { + is = -(ido); + for (j = 2; j <= ip; ++j) { + is += ido; + for (k = 1; k <= l1; ++k) { + idij = is; + for (i = 3; i <= ido; i += 2) { + idij += 2; + ch_ref(i - 1, k, j) = wa[idij - 1] * c1_ref(i - 1, k, j) + + wa[idij] * c1_ref(i, k, j); + ch_ref(i, k, j) = wa[idij - 1] * c1_ref(i, k, j) - wa[ + idij] * c1_ref(i - 1, k, j); + } + } + } + } + if (nbd >= l1) { + for (j = 2; j <= ipph; ++j) { + jc = ipp2 - j; + for (k = 1; k <= l1; ++k) { + for (i = 3; i <= ido; i += 2) { + c1_ref(i - 1, k, j) = ch_ref(i - 1, k, j) + ch_ref(i - + 1, k, jc); + c1_ref(i - 1, k, jc) = ch_ref(i, k, j) - ch_ref(i, k, + jc); + c1_ref(i, k, j) = ch_ref(i, k, j) + ch_ref(i, k, jc); + c1_ref(i, k, jc) = ch_ref(i - 1, k, jc) - ch_ref(i - 1, + k, j); + } + } + } + } else { + for (j = 2; j <= ipph; ++j) { + jc = ipp2 - j; + for (i = 3; i <= ido; i += 2) { + for (k = 1; k <= l1; ++k) { + c1_ref(i - 1, k, j) = ch_ref(i - 1, k, j) + ch_ref(i - + 1, k, jc); + c1_ref(i - 1, k, jc) = ch_ref(i, k, j) - ch_ref(i, k, + jc); + c1_ref(i, k, j) = ch_ref(i, k, j) + ch_ref(i, k, jc); + c1_ref(i, k, jc) = ch_ref(i - 1, k, jc) - ch_ref(i - 1, + k, j); + } + } + } + } + } + for (j = 2; j <= ipph; ++j) { + jc = ipp2 - j; + for (k = 1; k <= l1; ++k) { + c1_ref(1, k, j) = ch_ref(1, k, j) + ch_ref(1, k, jc); + c1_ref(1, k, jc) = ch_ref(1, k, jc) - ch_ref(1, k, j); + } + } + + ar1 = 1.f; + ai1 = 0.f; + for (l = 2; l <= ipph; ++l) { + lc = ipp2 - l; + ar1h = dcp * ar1 - dsp * ai1; + ai1 = dcp * ai1 + dsp * ar1; + ar1 = ar1h; + for (ik = 1; ik <= idl1; ++ik) { + ch2_ref(ik, l) = c2_ref(ik, 1) + ar1 * c2_ref(ik, 2); + ch2_ref(ik, lc) = ai1 * c2_ref(ik, ip); + } + dc2 = ar1; + ds2 = ai1; + ar2 = ar1; + ai2 = ai1; + for (j = 3; j <= ipph; ++j) { + jc = ipp2 - j; + ar2h = dc2 * ar2 - ds2 * ai2; + ai2 = dc2 * ai2 + ds2 * ar2; + ar2 = ar2h; + for (ik = 1; ik <= idl1; ++ik) { + ch2_ref(ik, l) = ch2_ref(ik, l) + ar2 * c2_ref(ik, j); + ch2_ref(ik, lc) = ch2_ref(ik, lc) + ai2 * c2_ref(ik, jc); + } + } + } + for (j = 2; j <= ipph; ++j) { + for (ik = 1; ik <= idl1; ++ik) { + ch2_ref(ik, 1) = ch2_ref(ik, 1) + c2_ref(ik, j); + } + } + + if (ido >= l1) { + for (k = 1; k <= l1; ++k) { + for (i = 1; i <= ido; ++i) { + cc_ref(i, 1, k) = ch_ref(i, k, 1); + } + } + } else { + for (i = 1; i <= ido; ++i) { + for (k = 1; k <= l1; ++k) { + cc_ref(i, 1, k) = ch_ref(i, k, 1); + } + } + } + for (j = 2; j <= ipph; ++j) { + jc = ipp2 - j; + j2 = j + j; + for (k = 1; k <= l1; ++k) { + cc_ref(ido, j2 - 2, k) = ch_ref(1, k, j); + cc_ref(1, j2 - 1, k) = ch_ref(1, k, jc); + } + } + if (ido == 1) { + return; + } + if (nbd >= l1) { + for (j = 2; j <= ipph; ++j) { + jc = ipp2 - j; + j2 = j + j; + for (k = 1; k <= l1; ++k) { + for (i = 3; i <= ido; i += 2) { + ic = idp2 - i; + cc_ref(i - 1, j2 - 1, k) = ch_ref(i - 1, k, j) + ch_ref( + i - 1, k, jc); + cc_ref(ic - 1, j2 - 2, k) = ch_ref(i - 1, k, j) - ch_ref( + i - 1, k, jc); + cc_ref(i, j2 - 1, k) = ch_ref(i, k, j) + ch_ref(i, k, + jc); + cc_ref(ic, j2 - 2, k) = ch_ref(i, k, jc) - ch_ref(i, k, j) + ; + } + } + } + } else { + for (j = 2; j <= ipph; ++j) { + jc = ipp2 - j; + j2 = j + j; + for (i = 3; i <= ido; i += 2) { + ic = idp2 - i; + for (k = 1; k <= l1; ++k) { + cc_ref(i - 1, j2 - 1, k) = ch_ref(i - 1, k, j) + ch_ref( + i - 1, k, jc); + cc_ref(ic - 1, j2 - 2, k) = ch_ref(i - 1, k, j) - ch_ref( + i - 1, k, jc); + cc_ref(i, j2 - 1, k) = ch_ref(i, k, j) + ch_ref(i, k, + jc); + cc_ref(ic, j2 - 2, k) = ch_ref(i, k, jc) - ch_ref(i, k, j) + ; + } + } + } + } +} /* radfg */ + +#undef ch2_ref +#undef ch_ref +#undef cc_ref +#undef c2_ref +#undef c1_ref + + +static void cfftb1(integer n, real *c, real *ch, const real *wa, integer *ifac) +{ + integer i, k1, l1, l2, na, nf, ip, iw, ix2, ix3, ix4, nac, ido, + idl1, idot; + + /* Function Body */ + nf = ifac[1]; + na = 0; + l1 = 1; + iw = 0; + for (k1 = 1; k1 <= nf; ++k1) { + ip = ifac[k1 + 1]; + l2 = ip * l1; + ido = n / l2; + idot = ido + ido; + idl1 = idot * l1; + switch (ip) { + case 4: + ix2 = iw + idot; + ix3 = ix2 + idot; + passb4(idot, l1, na?ch:c, na?c:ch, &wa[iw], &wa[ix2], &wa[ix3]); + na = 1 - na; + break; + case 2: + passb2(idot, l1, na?ch:c, na?c:ch, &wa[iw]); + na = 1 - na; + break; + case 3: + ix2 = iw + idot; + passb3(idot, l1, na?ch:c, na?c:ch, &wa[iw], &wa[ix2]); + na = 1 - na; + break; + case 5: + ix2 = iw + idot; + ix3 = ix2 + idot; + ix4 = ix3 + idot; + passfb5(idot, l1, na?ch:c, na?c:ch, &wa[iw], &wa[ix2], &wa[ix3], &wa[ix4], +1); + na = 1 - na; + break; + default: + if (na == 0) { + passfb(&nac, idot, ip, l1, idl1, c, c, c, ch, ch, &wa[iw], +1); + } else { + passfb(&nac, idot, ip, l1, idl1, ch, ch, ch, c, c, &wa[iw], +1); + } + if (nac != 0) { + na = 1 - na; + } + break; + } + l1 = l2; + iw += (ip - 1) * idot; + } + if (na == 0) { + return; + } + for (i = 0; i < 2*n; ++i) { + c[i] = ch[i]; + } +} /* cfftb1 */ + +void cfftb(integer n, real *c, real *wsave) +{ + integer iw1, iw2; + + /* Parameter adjustments */ + --wsave; + --c; + + /* Function Body */ + if (n == 1) { + return; + } + iw1 = 2*n + 1; + iw2 = iw1 + 2*n; + cfftb1(n, &c[1], &wsave[1], &wsave[iw1], (int*)&wsave[iw2]); +} /* cfftb */ + +static void cfftf1(integer n, real *c, real *ch, const real *wa, integer *ifac) +{ + /* Local variables */ + integer i, k1, l1, l2, na, nf, ip, iw, ix2, ix3, ix4, nac, ido, + idl1, idot; + + /* Function Body */ + nf = ifac[1]; + na = 0; + l1 = 1; + iw = 0; + for (k1 = 1; k1 <= nf; ++k1) { + ip = ifac[k1 + 1]; + l2 = ip * l1; + ido = n / l2; + idot = ido + ido; + idl1 = idot * l1; + switch (ip) { + case 4: + ix2 = iw + idot; + ix3 = ix2 + idot; + passf4(idot, l1, na?ch:c, na?c:ch, &wa[iw], &wa[ix2], &wa[ix3]); + na = 1 - na; + break; + case 2: + passf2(idot, l1, na?ch:c, na?c:ch, &wa[iw]); + na = 1 - na; + break; + case 3: + ix2 = iw + idot; + passf3(idot, l1, na?ch:c, na?c:ch, &wa[iw], &wa[ix2]); + na = 1 - na; + break; + case 5: + ix2 = iw + idot; + ix3 = ix2 + idot; + ix4 = ix3 + idot; + passfb5(idot, l1, na?ch:c, na?c:ch, &wa[iw], &wa[ix2], &wa[ix3], &wa[ix4], -1); + na = 1 - na; + break; + default: + if (na == 0) { + passfb(&nac, idot, ip, l1, idl1, c, c, c, ch, ch, &wa[iw], -1); + } else { + passfb(&nac, idot, ip, l1, idl1, ch, ch, ch, c, c, &wa[iw], -1); + } + if (nac != 0) { + na = 1 - na; + } + break; + } + l1 = l2; + iw += (ip - 1)*idot; + } + if (na == 0) { + return; + } + for (i = 0; i < 2*n; ++i) { + c[i] = ch[i]; + } +} /* cfftf1 */ + +void cfftf(integer n, real *c, real *wsave) +{ + integer iw1, iw2; + + /* Parameter adjustments */ + --wsave; + --c; + + /* Function Body */ + if (n == 1) { + return; + } + iw1 = 2*n + 1; + iw2 = iw1 + 2*n; + cfftf1(n, &c[1], &wsave[1], &wsave[iw1], (int*)&wsave[iw2]); +} /* cfftf */ + +static int decompose(integer n, integer *ifac, integer ntryh[4]) { + integer ntry=0, nl = n, nf = 0, nq, nr, i, j = 0; + do { + if (j < 4) { + ntry = ntryh[j]; + } else { + ntry += 2; + } + ++j; + L104: + nq = nl / ntry; + nr = nl - ntry * nq; + if (nr != 0) continue; + ++nf; + ifac[nf + 2] = ntry; + nl = nq; + if (ntry == 2 && nf != 1) { + for (i = 2; i <= nf; ++i) { + integer ib = nf - i + 2; + ifac[ib + 2] = ifac[ib + 1]; + } + ifac[3] = 2; + } + if (nl != 1) { + goto L104; + } + } while (nl != 1); + ifac[1] = n; + ifac[2] = nf; + return nf; +} + +static void cffti1(integer n, real *wa, integer *ifac) +{ + static integer ntryh[4] = { 3,4,2,5 }; + + /* Local variables */ + integer i, j, i1, k1, l1, l2; + real fi; + integer ld, ii, nf, ip; + real arg; + integer ido, ipm; + real argh; + integer idot; + real argld; + + /* Parameter adjustments */ + --ifac; + --wa; + + nf = decompose(n, ifac, ntryh); + + argh = (2*M_PI) / (real) (n); + i = 2; + l1 = 1; + for (k1 = 1; k1 <= nf; ++k1) { + ip = ifac[k1 + 2]; + ld = 0; + l2 = l1 * ip; + ido = n / l2; + idot = ido + ido + 2; + ipm = ip - 1; + for (j = 1; j <= ipm; ++j) { + i1 = i; + wa[i - 1] = 1.f; + wa[i] = 0.f; + ld += l1; + fi = 0.f; + argld = (real) ld * argh; + for (ii = 4; ii <= idot; ii += 2) { + i += 2; + fi += 1.f; + arg = fi * argld; + wa[i - 1] = cos(arg); + wa[i] = sin(arg); + } + if (ip > 5) { + wa[i1 - 1] = wa[i - 1]; + wa[i1] = wa[i]; + }; + } + l1 = l2; + } +} /* cffti1 */ + +void cffti(integer n, real *wsave) +{ + integer iw1, iw2; + /* Parameter adjustments */ + --wsave; + + /* Function Body */ + if (n == 1) { + return; + } + iw1 = 2*n + 1; + iw2 = iw1 + 2*n; + cffti1(n, &wsave[iw1], (int*)&wsave[iw2]); + return; +} /* cffti */ + +static void rfftb1(integer n, real *c, real *ch, const real *wa, integer *ifac) +{ + /* Local variables */ + integer i, k1, l1, l2, na, nf, ip, iw, ix2, ix3, ix4, ido, idl1; + + /* Function Body */ + nf = ifac[1]; + na = 0; + l1 = 1; + iw = 0; + for (k1 = 1; k1 <= nf; ++k1) { + ip = ifac[k1 + 1]; + l2 = ip * l1; + ido = n / l2; + idl1 = ido * l1; + switch (ip) { + case 4: + ix2 = iw + ido; + ix3 = ix2 + ido; + radb4(ido, l1, na?ch:c, na?c:ch, &wa[iw], &wa[ix2], &wa[ix3]); + na = 1 - na; + break; + case 2: + radb2(ido, l1, na?ch:c, na?c:ch, &wa[iw]); + na = 1 - na; + break; + case 3: + ix2 = iw + ido; + radb3(ido, l1, na?ch:c, na?c:ch, &wa[iw], &wa[ix2]); + na = 1 - na; + break; + case 5: + ix2 = iw + ido; + ix3 = ix2 + ido; + ix4 = ix3 + ido; + radb5(ido, l1, na?ch:c, na?c:ch, &wa[iw], &wa[ix2], &wa[ix3], &wa[ix4]); + na = 1 - na; + break; + default: + if (na == 0) { + radbg(ido, ip, l1, idl1, c, c, c, ch, ch, &wa[iw]); + } else { + radbg(ido, ip, l1, idl1, ch, ch, ch, c, c, &wa[iw]); + } + if (ido == 1) { + na = 1 - na; + } + break; + } + l1 = l2; + iw += (ip - 1) * ido; + } + if (na == 0) { + return; + } + for (i = 0; i < n; ++i) { + c[i] = ch[i]; + } +} /* rfftb1 */ + +static void rfftf1(integer n, real *c, real *ch, const real *wa, integer *ifac) +{ + /* Local variables */ + integer i, k1, l1, l2, na, kh, nf, ip, iw, ix2, ix3, ix4, ido, idl1; + + /* Function Body */ + nf = ifac[1]; + na = 1; + l2 = n; + iw = n-1; + for (k1 = 1; k1 <= nf; ++k1) { + kh = nf - k1; + ip = ifac[kh + 2]; + l1 = l2 / ip; + ido = n / l2; + idl1 = ido * l1; + iw -= (ip - 1) * ido; + na = 1 - na; + switch (ip) { + case 4: + ix2 = iw + ido; + ix3 = ix2 + ido; + radf4(ido, l1, na ? ch : c, na ? c : ch, &wa[iw], &wa[ix2], &wa[ix3]); + break; + case 2: + radf2(ido, l1, na ? ch : c, na ? c : ch, &wa[iw]); + break; + case 3: + ix2 = iw + ido; + radf3(ido, l1, na ? ch : c, na ? c : ch, &wa[iw], &wa[ix2]); + break; + case 5: + ix2 = iw + ido; + ix3 = ix2 + ido; + ix4 = ix3 + ido; + radf5(ido, l1, na ? ch : c, na ? c : ch, &wa[iw], &wa[ix2], &wa[ix3], &wa[ix4]); + break; + default: + if (ido == 1) { + na = 1 - na; + } + if (na == 0) { + radfg(ido, ip, l1, idl1, c, c, c, ch, ch, &wa[iw]); + na = 1; + } else { + radfg(ido, ip, l1, idl1, ch, ch, ch, c, c, &wa[iw]); + na = 0; + } + break; + } + l2 = l1; + } + if (na == 1) { + return; + } + for (i = 0; i < n; ++i) { + c[i] = ch[i]; + } +} + +void rfftb(integer n, real *r, real *wsave) +{ + + /* Parameter adjustments */ + --wsave; + --r; + + /* Function Body */ + if (n == 1) { + return; + } + rfftb1(n, &r[1], &wsave[1], &wsave[n + 1], (int*)&wsave[(n << 1) + 1]); +} /* rfftb */ + +static void rffti1(integer n, real *wa, integer *ifac) +{ + static integer ntryh[4] = { 4,2,3,5 }; + + /* Local variables */ + integer i, j, k1, l1, l2; + real fi; + integer ld, ii, nf, ip, is; + real arg; + integer ido, ipm; + integer nfm1; + real argh; + real argld; + + /* Parameter adjustments */ + --ifac; + --wa; + + nf = decompose(n, ifac, ntryh); + + argh = (2*M_PI) / (real) (n); + is = 0; + nfm1 = nf - 1; + l1 = 1; + if (nfm1 == 0) { + return; + } + for (k1 = 1; k1 <= nfm1; ++k1) { + ip = ifac[k1 + 2]; + ld = 0; + l2 = l1 * ip; + ido = n / l2; + ipm = ip - 1; + for (j = 1; j <= ipm; ++j) { + ld += l1; + i = is; + argld = (real) ld * argh; + fi = 0.f; + for (ii = 3; ii <= ido; ii += 2) { + i += 2; + fi += 1.f; + arg = fi * argld; + wa[i - 1] = cos(arg); + wa[i] = sin(arg); + } + is += ido; + } + l1 = l2; + } +} /* rffti1 */ + +void rfftf(integer n, real *r, real *wsave) +{ + + /* Parameter adjustments */ + --wsave; + --r; + + /* Function Body */ + if (n == 1) { + return; + } + rfftf1(n, &r[1], &wsave[1], &wsave[n + 1], (int*)&wsave[(n << 1) + 1]); +} /* rfftf */ + +void rffti(integer n, real *wsave) +{ + /* Parameter adjustments */ + --wsave; + + /* Function Body */ + if (n == 1) { + return; + } + rffti1(n, &wsave[n + 1], (int*)&wsave[(n << 1) + 1]); + return; +} /* rffti */ + +static void cosqb1(integer n, real *x, real *w, real *xh) +{ + /* Local variables */ + integer i, k, kc, np2, ns2; + real xim1; + integer modn; + + /* Parameter adjustments */ + --xh; + --w; + --x; + + /* Function Body */ + ns2 = (n + 1) / 2; + np2 = n + 2; + for (i = 3; i <= n; i += 2) { + xim1 = x[i - 1] + x[i]; + x[i] -= x[i - 1]; + x[i - 1] = xim1; + } + x[1] += x[1]; + modn = n % 2; + if (modn == 0) { + x[n] += x[n]; + } + rfftb(n, &x[1], &xh[1]); + for (k = 2; k <= ns2; ++k) { + kc = np2 - k; + xh[k] = w[k - 1] * x[kc] + w[kc - 1] * x[k]; + xh[kc] = w[k - 1] * x[k] - w[kc - 1] * x[kc]; + } + if (modn == 0) { + x[ns2 + 1] = w[ns2] * (x[ns2 + 1] + x[ns2 + 1]); + } + for (k = 2; k <= ns2; ++k) { + kc = np2 - k; + x[k] = xh[k] + xh[kc]; + x[kc] = xh[k] - xh[kc]; + } + x[1] += x[1]; +} /* cosqb1 */ + +void cosqb(integer n, real *x, real *wsave) +{ + static const real tsqrt2 = 2.82842712474619f; + + /* Local variables */ + real x1; + + /* Parameter adjustments */ + --wsave; + --x; + + if (n < 2) { + x[1] *= 4.f; + } else if (n == 2) { + x1 = (x[1] + x[2]) * 4.f; + x[2] = tsqrt2 * (x[1] - x[2]); + x[1] = x1; + } else { + cosqb1(n, &x[1], &wsave[1], &wsave[n + 1]); + } +} /* cosqb */ + +static void cosqf1(integer n, real *x, real *w, real *xh) +{ + /* Local variables */ + integer i, k, kc, np2, ns2; + real xim1; + integer modn; + + /* Parameter adjustments */ + --xh; + --w; + --x; + + /* Function Body */ + ns2 = (n + 1) / 2; + np2 = n + 2; + for (k = 2; k <= ns2; ++k) { + kc = np2 - k; + xh[k] = x[k] + x[kc]; + xh[kc] = x[k] - x[kc]; + } + modn = n % 2; + if (modn == 0) { + xh[ns2 + 1] = x[ns2 + 1] + x[ns2 + 1]; + } + for (k = 2; k <= ns2; ++k) { + kc = np2 - k; + x[k] = w[k - 1] * xh[kc] + w[kc - 1] * xh[k]; + x[kc] = w[k - 1] * xh[k] - w[kc - 1] * xh[kc]; + } + if (modn == 0) { + x[ns2 + 1] = w[ns2] * xh[ns2 + 1]; + } + rfftf(n, &x[1], &xh[1]); + for (i = 3; i <= n; i += 2) { + xim1 = x[i - 1] - x[i]; + x[i] = x[i - 1] + x[i]; + x[i - 1] = xim1; + } +} /* cosqf1 */ + +void cosqf(integer n, real *x, real *wsave) +{ + static const real sqrt2 = 1.4142135623731f; + + /* Local variables */ + real tsqx; + + /* Parameter adjustments */ + --wsave; + --x; + + if (n == 2) { + tsqx = sqrt2 * x[2]; + x[2] = x[1] - tsqx; + x[1] += tsqx; + } else if (n > 2) { + cosqf1(n, &x[1], &wsave[1], &wsave[n + 1]); + } +} /* cosqf */ + +void cosqi(integer n, real *wsave) +{ + /* Local variables */ + integer k; + real fk, dt; + + /* Parameter adjustments */ + --wsave; + + dt = M_PI/2 / (real) (n); + fk = 0.f; + for (k = 1; k <= n; ++k) { + fk += 1.f; + wsave[k] = cos(fk * dt); + } + rffti(n, &wsave[n + 1]); +} /* cosqi */ + +void cost(integer n, real *x, real *wsave) +{ + /* Local variables */ + integer i, k; + real c1, t1, t2; + integer kc; + real xi; + integer nm1, np1; + real x1h; + integer ns2; + real tx2, x1p3, xim2; + integer modn; + + /* Parameter adjustments */ + --wsave; + --x; + + /* Function Body */ + nm1 = n - 1; + np1 = n + 1; + ns2 = n / 2; + if (n < 2) { + } else if (n == 2) { + x1h = x[1] + x[2]; + x[2] = x[1] - x[2]; + x[1] = x1h; + } else if (n == 3) { + x1p3 = x[1] + x[3]; + tx2 = x[2] + x[2]; + x[2] = x[1] - x[3]; + x[1] = x1p3 + tx2; + x[3] = x1p3 - tx2; + } else { + c1 = x[1] - x[n]; + x[1] += x[n]; + for (k = 2; k <= ns2; ++k) { + kc = np1 - k; + t1 = x[k] + x[kc]; + t2 = x[k] - x[kc]; + c1 += wsave[kc] * t2; + t2 = wsave[k] * t2; + x[k] = t1 - t2; + x[kc] = t1 + t2; + } + modn = n % 2; + if (modn != 0) { + x[ns2 + 1] += x[ns2 + 1]; + } + rfftf(nm1, &x[1], &wsave[n + 1]); + xim2 = x[2]; + x[2] = c1; + for (i = 4; i <= n; i += 2) { + xi = x[i]; + x[i] = x[i - 2] - x[i - 1]; + x[i - 1] = xim2; + xim2 = xi; + } + if (modn != 0) { + x[n] = xim2; + } + } +} /* cost */ + +void costi(integer n, real *wsave) +{ + /* Initialized data */ + + /* Local variables */ + integer k, kc; + real fk, dt; + integer nm1, np1, ns2; + + /* Parameter adjustments */ + --wsave; + + /* Function Body */ + if (n <= 3) { + return; + } + nm1 = n - 1; + np1 = n + 1; + ns2 = n / 2; + dt = M_PI / (real) nm1; + fk = 0.f; + for (k = 2; k <= ns2; ++k) { + kc = np1 - k; + fk += 1.f; + wsave[k] = sin(fk * dt) * 2.f; + wsave[kc] = cos(fk * dt) * 2.f; + } + rffti(nm1, &wsave[n + 1]); +} /* costi */ + +void sinqb(integer n, real *x, real *wsave) +{ + /* Local variables */ + integer k, kc, ns2; + real xhold; + + /* Parameter adjustments */ + --wsave; + --x; + + /* Function Body */ + if (n <= 1) { + x[1] *= 4.f; + return; + } + ns2 = n / 2; + for (k = 2; k <= n; k += 2) { + x[k] = -x[k]; + } + cosqb(n, &x[1], &wsave[1]); + for (k = 1; k <= ns2; ++k) { + kc = n - k; + xhold = x[k]; + x[k] = x[kc + 1]; + x[kc + 1] = xhold; + } +} /* sinqb */ + +void sinqf(integer n, real *x, real *wsave) +{ + /* Local variables */ + integer k, kc, ns2; + real xhold; + + /* Parameter adjustments */ + --wsave; + --x; + + /* Function Body */ + if (n == 1) { + return; + } + ns2 = n / 2; + for (k = 1; k <= ns2; ++k) { + kc = n - k; + xhold = x[k]; + x[k] = x[kc + 1]; + x[kc + 1] = xhold; + } + cosqf(n, &x[1], &wsave[1]); + for (k = 2; k <= n; k += 2) { + x[k] = -x[k]; + } +} /* sinqf */ + +void sinqi(integer n, real *wsave) +{ + + /* Parameter adjustments */ + --wsave; + + /* Function Body */ + cosqi(n, &wsave[1]); +} /* sinqi */ + +static void sint1(integer n, real *war, real *was, real *xh, real * + x, integer *ifac) +{ + /* Initialized data */ + + static const real sqrt3 = 1.73205080756888f; + + /* Local variables */ + integer i, k; + real t1, t2; + integer kc, np1, ns2, modn; + real xhold; + + /* Parameter adjustments */ + --ifac; + --x; + --xh; + --was; + --war; + + /* Function Body */ + for (i = 1; i <= n; ++i) { + xh[i] = war[i]; + war[i] = x[i]; + } + + if (n < 2) { + xh[1] += xh[1]; + } else if (n == 2) { + xhold = sqrt3 * (xh[1] + xh[2]); + xh[2] = sqrt3 * (xh[1] - xh[2]); + xh[1] = xhold; + } else { + np1 = n + 1; + ns2 = n / 2; + x[1] = 0.f; + for (k = 1; k <= ns2; ++k) { + kc = np1 - k; + t1 = xh[k] - xh[kc]; + t2 = was[k] * (xh[k] + xh[kc]); + x[k + 1] = t1 + t2; + x[kc + 1] = t2 - t1; + } + modn = n % 2; + if (modn != 0) { + x[ns2 + 2] = xh[ns2 + 1] * 4.f; + } + rfftf1(np1, &x[1], &xh[1], &war[1], &ifac[1]); + xh[1] = x[1] * .5f; + for (i = 3; i <= n; i += 2) { + xh[i - 1] = -x[i]; + xh[i] = xh[i - 2] + x[i - 1]; + } + if (modn == 0) { + xh[n] = -x[n + 1]; + } + } + for (i = 1; i <= n; ++i) { + x[i] = war[i]; + war[i] = xh[i]; + } +} /* sint1 */ + +void sinti(integer n, real *wsave) +{ + /* Local variables */ + integer k; + real dt; + integer np1, ns2; + + /* Parameter adjustments */ + --wsave; + + /* Function Body */ + if (n <= 1) { + return; + } + ns2 = n / 2; + np1 = n + 1; + dt = M_PI / (real) np1; + for (k = 1; k <= ns2; ++k) { + wsave[k] = sin(k * dt) * 2.f; + } + rffti(np1, &wsave[ns2 + 1]); +} /* sinti */ + +void sint(integer n, real *x, real *wsave) +{ + integer np1, iw1, iw2, iw3; + + /* Parameter adjustments */ + --wsave; + --x; + + /* Function Body */ + np1 = n + 1; + iw1 = n / 2 + 1; + iw2 = iw1 + np1; + iw3 = iw2 + np1; + sint1(n, &x[1], &wsave[1], &wsave[iw1], &wsave[iw2], (int*)&wsave[iw3]); +} /* sint */ + +#ifdef TESTING_FFTPACK +#include + +int main(void) +{ + static integer nd[] = { 120,91,54,49,32,28,24,8,4,3,2 }; + + /* System generated locals */ + real r1, r2, r3; + f77complex q1, q2, q3; + + /* Local variables */ + integer i, j, k, n; + real w[2000], x[200], y[200], cf, fn, dt; + f77complex cx[200], cy[200]; + real xh[200]; + integer nz, nm1, np1, ns2; + real arg, tfn; + real sum, arg1, arg2; + real sum1, sum2, dcfb; + integer modn; + real rftb, rftf; + real sqrt2; + real rftfb; + real costt, sintt, dcfftb, dcfftf, cosqfb, costfb; + real sinqfb; + real sintfb; + real cosqbt, cosqft, sinqbt, sinqft; + + + + /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + + /* VERSION 4 APRIL 1985 */ + + /* A TEST DRIVER FOR */ + /* A PACKAGE OF FORTRAN SUBPROGRAMS FOR THE FAST FOURIER */ + /* TRANSFORM OF PERIODIC AND OTHER SYMMETRIC SEQUENCES */ + + /* BY */ + + /* PAUL N SWARZTRAUBER */ + + /* NATIONAL CENTER FOR ATMOSPHERIC RESEARCH BOULDER,COLORADO 80307 */ + + /* WHICH IS SPONSORED BY THE NATIONAL SCIENCE FOUNDATION */ + + /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + + + /* THIS PROGRAM TESTS THE PACKAGE OF FAST FOURIER */ + /* TRANSFORMS FOR BOTH COMPLEX AND REAL PERIODIC SEQUENCES AND */ + /* CERTIAN OTHER SYMMETRIC SEQUENCES THAT ARE LISTED BELOW. */ + + /* 1. RFFTI INITIALIZE RFFTF AND RFFTB */ + /* 2. RFFTF FORWARD TRANSFORM OF A REAL PERIODIC SEQUENCE */ + /* 3. RFFTB BACKWARD TRANSFORM OF A REAL COEFFICIENT ARRAY */ + + /* 4. EZFFTI INITIALIZE EZFFTF AND EZFFTB */ + /* 5. EZFFTF A SIMPLIFIED REAL PERIODIC FORWARD TRANSFORM */ + /* 6. EZFFTB A SIMPLIFIED REAL PERIODIC BACKWARD TRANSFORM */ + + /* 7. SINTI INITIALIZE SINT */ + /* 8. SINT SINE TRANSFORM OF A REAL ODD SEQUENCE */ + + /* 9. COSTI INITIALIZE COST */ + /* 10. COST COSINE TRANSFORM OF A REAL EVEN SEQUENCE */ + + /* 11. SINQI INITIALIZE SINQF AND SINQB */ + /* 12. SINQF FORWARD SINE TRANSFORM WITH ODD WAVE NUMBERS */ + /* 13. SINQB UNNORMALIZED INVERSE OF SINQF */ + + /* 14. COSQI INITIALIZE COSQF AND COSQB */ + /* 15. COSQF FORWARD COSINE TRANSFORM WITH ODD WAVE NUMBERS */ + /* 16. COSQB UNNORMALIZED INVERSE OF COSQF */ + + /* 17. CFFTI INITIALIZE CFFTF AND CFFTB */ + /* 18. CFFTF FORWARD TRANSFORM OF A COMPLEX PERIODIC SEQUENCE */ + /* 19. CFFTB UNNORMALIZED INVERSE OF CFFTF */ + + + sqrt2 = sqrt(2.f); + int all_ok = 1; + for (nz = 1; nz <= (int)(sizeof nd/sizeof nd[0]); ++nz) { + n = nd[nz - 1]; + modn = n % 2; + fn = (real) n; + tfn = fn + fn; + np1 = n + 1; + nm1 = n - 1; + for (j = 1; j <= np1; ++j) { + x[j - 1] = sin((real) j * sqrt2); + y[j - 1] = x[j - 1]; + xh[j - 1] = x[j - 1]; + } + + /* TEST SUBROUTINES RFFTI,RFFTF AND RFFTB */ + + rffti(n, w); + dt = (2*M_PI) / fn; + ns2 = (n + 1) / 2; + if (ns2 < 2) { + goto L104; + } + for (k = 2; k <= ns2; ++k) { + sum1 = 0.f; + sum2 = 0.f; + arg = (real) (k - 1) * dt; + for (i = 1; i <= n; ++i) { + arg1 = (real) (i - 1) * arg; + sum1 += x[i - 1] * cos(arg1); + sum2 += x[i - 1] * sin(arg1); + } + y[(k << 1) - 3] = sum1; + y[(k << 1) - 2] = -sum2; + } + L104: + sum1 = 0.f; + sum2 = 0.f; + for (i = 1; i <= nm1; i += 2) { + sum1 += x[i - 1]; + sum2 += x[i]; + } + if (modn == 1) { + sum1 += x[n - 1]; + } + y[0] = sum1 + sum2; + if (modn == 0) { + y[n - 1] = sum1 - sum2; + } + rfftf(n, x, w); + rftf = 0.f; + for (i = 1; i <= n; ++i) { + /* Computing MAX */ + r2 = rftf, r3 = (r1 = x[i - 1] - y[i - 1], fabs(r1)); + rftf = dmax(r2,r3); + x[i - 1] = xh[i - 1]; + } + rftf /= fn; + for (i = 1; i <= n; ++i) { + sum = x[0] * .5f; + arg = (real) (i - 1) * dt; + if (ns2 < 2) { + goto L108; + } + for (k = 2; k <= ns2; ++k) { + arg1 = (real) (k - 1) * arg; + sum = sum + x[(k << 1) - 3] * cos(arg1) - x[(k << 1) - 2] * + sin(arg1); + } + L108: + if (modn == 0) { + sum += (real)pow(-1, i-1) * .5f * x[n - 1]; + } + y[i - 1] = sum + sum; + } + rfftb(n, x, w); + rftb = 0.f; + for (i = 1; i <= n; ++i) { + /* Computing MAX */ + r2 = rftb, r3 = (r1 = x[i - 1] - y[i - 1], fabs(r1)); + rftb = dmax(r2,r3); + x[i - 1] = xh[i - 1]; + y[i - 1] = xh[i - 1]; + } + rfftb(n, y, w); + rfftf(n, y, w); + cf = 1.f / fn; + rftfb = 0.f; + for (i = 1; i <= n; ++i) { + /* Computing MAX */ + r2 = rftfb, r3 = (r1 = cf * y[i - 1] - x[i - 1], fabs( + r1)); + rftfb = dmax(r2,r3); + } + + /* TEST SUBROUTINES SINTI AND SINT */ + + dt = M_PI / fn; + for (i = 1; i <= nm1; ++i) { + x[i - 1] = xh[i - 1]; + } + for (i = 1; i <= nm1; ++i) { + y[i - 1] = 0.f; + arg1 = (real) i * dt; + for (k = 1; k <= nm1; ++k) { + y[i - 1] += x[k - 1] * sin((real) k * arg1); + } + y[i - 1] += y[i - 1]; + } + sinti(nm1, w); + sint(nm1, x, w); + cf = .5f / fn; + sintt = 0.f; + for (i = 1; i <= nm1; ++i) { + /* Computing MAX */ + r2 = sintt, r3 = (r1 = x[i - 1] - y[i - 1], fabs(r1)); + sintt = dmax(r2,r3); + x[i - 1] = xh[i - 1]; + y[i - 1] = x[i - 1]; + } + sintt = cf * sintt; + sint(nm1, x, w); + sint(nm1, x, w); + sintfb = 0.f; + for (i = 1; i <= nm1; ++i) { + /* Computing MAX */ + r2 = sintfb, r3 = (r1 = cf * x[i - 1] - y[i - 1], fabs( + r1)); + sintfb = dmax(r2,r3); + } + + /* TEST SUBROUTINES COSTI AND COST */ + + for (i = 1; i <= np1; ++i) { + x[i - 1] = xh[i - 1]; + } + for (i = 1; i <= np1; ++i) { + y[i - 1] = (x[0] + (real) pow(-1, i+1) * x[n]) * .5f; + arg = (real) (i - 1) * dt; + for (k = 2; k <= n; ++k) { + y[i - 1] += x[k - 1] * cos((real) (k - 1) * arg); + } + y[i - 1] += y[i - 1]; + } + costi(np1, w); + cost(np1, x, w); + costt = 0.f; + for (i = 1; i <= np1; ++i) { + /* Computing MAX */ + r2 = costt, r3 = (r1 = x[i - 1] - y[i - 1], fabs(r1)); + costt = dmax(r2,r3); + x[i - 1] = xh[i - 1]; + y[i - 1] = xh[i - 1]; + } + costt = cf * costt; + cost(np1, x, w); + cost(np1, x, w); + costfb = 0.f; + for (i = 1; i <= np1; ++i) { + /* Computing MAX */ + r2 = costfb, r3 = (r1 = cf * x[i - 1] - y[i - 1], fabs( + r1)); + costfb = dmax(r2,r3); + } + + /* TEST SUBROUTINES SINQI,SINQF AND SINQB */ + + cf = .25f / fn; + for (i = 1; i <= n; ++i) { + y[i - 1] = xh[i - 1]; + } + dt = M_PI / (fn + fn); + for (i = 1; i <= n; ++i) { + x[i - 1] = 0.f; + arg = dt * (real) i; + for (k = 1; k <= n; ++k) { + x[i - 1] += y[k - 1] * sin((real) (k + k - 1) * arg); + } + x[i - 1] *= 4.f; + } + sinqi(n, w); + sinqb(n, y, w); + sinqbt = 0.f; + for (i = 1; i <= n; ++i) { + /* Computing MAX */ + r2 = sinqbt, r3 = (r1 = y[i - 1] - x[i - 1], fabs(r1)) + ; + sinqbt = dmax(r2,r3); + x[i - 1] = xh[i - 1]; + } + sinqbt = cf * sinqbt; + for (i = 1; i <= n; ++i) { + arg = (real) (i + i - 1) * dt; + y[i - 1] = (real) pow(-1, i+1) * .5f * x[n - 1]; + for (k = 1; k <= nm1; ++k) { + y[i - 1] += x[k - 1] * sin((real) k * arg); + } + y[i - 1] += y[i - 1]; + } + sinqf(n, x, w); + sinqft = 0.f; + for (i = 1; i <= n; ++i) { + /* Computing MAX */ + r2 = sinqft, r3 = (r1 = x[i - 1] - y[i - 1], fabs(r1)) + ; + sinqft = dmax(r2,r3); + y[i - 1] = xh[i - 1]; + x[i - 1] = xh[i - 1]; + } + sinqf(n, y, w); + sinqb(n, y, w); + sinqfb = 0.f; + for (i = 1; i <= n; ++i) { + /* Computing MAX */ + r2 = sinqfb, r3 = (r1 = cf * y[i - 1] - x[i - 1], fabs( + r1)); + sinqfb = dmax(r2,r3); + } + + /* TEST SUBROUTINES COSQI,COSQF AND COSQB */ + + for (i = 1; i <= n; ++i) { + y[i - 1] = xh[i - 1]; + } + for (i = 1; i <= n; ++i) { + x[i - 1] = 0.f; + arg = (real) (i - 1) * dt; + for (k = 1; k <= n; ++k) { + x[i - 1] += y[k - 1] * cos((real) (k + k - 1) * arg); + } + x[i - 1] *= 4.f; + } + cosqi(n, w); + cosqb(n, y, w); + cosqbt = 0.f; + for (i = 1; i <= n; ++i) { + /* Computing MAX */ + r2 = cosqbt, r3 = (r1 = x[i - 1] - y[i - 1], fabs(r1)) + ; + cosqbt = dmax(r2,r3); + x[i - 1] = xh[i - 1]; + } + cosqbt = cf * cosqbt; + for (i = 1; i <= n; ++i) { + y[i - 1] = x[0] * .5f; + arg = (real) (i + i - 1) * dt; + for (k = 2; k <= n; ++k) { + y[i - 1] += x[k - 1] * cos((real) (k - 1) * arg); + } + y[i - 1] += y[i - 1]; + } + cosqf(n, x, w); + cosqft = 0.f; + for (i = 1; i <= n; ++i) { + /* Computing MAX */ + r2 = cosqft, r3 = (r1 = y[i - 1] - x[i - 1], fabs(r1)) + ; + cosqft = dmax(r2,r3); + x[i - 1] = xh[i - 1]; + y[i - 1] = xh[i - 1]; + } + cosqft = cf * cosqft; + cosqb(n, x, w); + cosqf(n, x, w); + cosqfb = 0.f; + for (i = 1; i <= n; ++i) { + /* Computing MAX */ + r2 = cosqfb, r3 = (r1 = cf * x[i - 1] - y[i - 1], fabs(r1)); + cosqfb = dmax(r2,r3); + } + + /* TEST CFFTI,CFFTF,CFFTB */ + + for (i = 1; i <= n; ++i) { + r1 = cos(sqrt2 * (real) i); + r2 = sin(sqrt2 * (real) (i * i)); + q1.r = r1, q1.i = r2; + cx[i-1].r = q1.r, cx[i-1].i = q1.i; + } + dt = (2*M_PI) / fn; + for (i = 1; i <= n; ++i) { + arg1 = -((real) (i - 1)) * dt; + cy[i-1].r = 0.f, cy[i-1].i = 0.f; + for (k = 1; k <= n; ++k) { + arg2 = (real) (k - 1) * arg1; + r1 = cos(arg2); + r2 = sin(arg2); + q3.r = r1, q3.i = r2; + q2.r = q3.r * cx[k-1].r - q3.i * cx[k-1].i, q2.i = + q3.r * cx[k-1].i + q3.i * cx[k-1].r; + q1.r = cy[i-1].r + q2.r, q1.i = cy[i-1].i + q2.i; + cy[i-1].r = q1.r, cy[i-1].i = q1.i; + } + } + cffti(n, w); + cfftf(n, (real*)cx, w); + dcfftf = 0.f; + for (i = 1; i <= n; ++i) { + /* Computing MAX */ + q1.r = cx[i-1].r - cy[i-1].r, q1.i = cx[i-1].i - cy[i-1] + .i; + r1 = dcfftf, r2 = c_abs(&q1); + dcfftf = dmax(r1,r2); + q1.r = cx[i-1].r / fn, q1.i = cx[i-1].i / fn; + cx[i-1].r = q1.r, cx[i-1].i = q1.i; + } + dcfftf /= fn; + for (i = 1; i <= n; ++i) { + arg1 = (real) (i - 1) * dt; + cy[i-1].r = 0.f, cy[i-1].i = 0.f; + for (k = 1; k <= n; ++k) { + arg2 = (real) (k - 1) * arg1; + r1 = cos(arg2); + r2 = sin(arg2); + q3.r = r1, q3.i = r2; + q2.r = q3.r * cx[k-1].r - q3.i * cx[k-1].i, q2.i = + q3.r * cx[k-1].i + q3.i * cx[k-1].r; + q1.r = cy[i-1].r + q2.r, q1.i = cy[i-1].i + q2.i; + cy[i-1].r = q1.r, cy[i-1].i = q1.i; + } + } + cfftb(n, (real*)cx, w); + dcfftb = 0.f; + for (i = 1; i <= n; ++i) { + /* Computing MAX */ + q1.r = cx[i-1].r - cy[i-1].r, q1.i = cx[i-1].i - cy[i-1].i; + r1 = dcfftb, r2 = c_abs(&q1); + dcfftb = dmax(r1,r2); + cx[i-1].r = cy[i-1].r, cx[i-1].i = cy[i-1].i; + } + cf = 1.f / fn; + cfftf(n, (real*)cx, w); + cfftb(n, (real*)cx, w); + dcfb = 0.f; + for (i = 1; i <= n; ++i) { + /* Computing MAX */ + q2.r = cf * cx[i-1].r, q2.i = cf * cx[i-1].i; + q1.r = q2.r - cy[i-1].r, q1.i = q2.i - cy[i-1].i; + r1 = dcfb, r2 = c_abs(&q1); + dcfb = dmax(r1,r2); + } + printf("%d\tRFFTF %10.3g\tRFFTB %10.ge\tRFFTFB %10.3g", n, rftf, rftb, rftfb); + printf( "\tSINT %10.3g\tSINTFB %10.ge\tCOST %10.3g\n", sintt, sintfb, costt); + printf( "\tCOSTFB %10.3g\tSINQF %10.ge\tSINQB %10.3g", costfb, sinqft, sinqbt); + printf( "\tSINQFB %10.3g\tCOSQF %10.ge\tCOSQB %10.3g\n", sinqfb, cosqft, cosqbt); + printf( "\tCOSQFB %10.3g\t", cosqfb); + printf( "\tCFFTF %10.ge\tCFFTB %10.3g\n", dcfftf, dcfftb); + printf( "\tCFFTFB %10.3g\n", dcfb); + +#define CHECK(x) if (x > 1e-3) { printf(#x " failed: %g\n", x); all_ok = 0; } + CHECK(rftf); CHECK(rftb); CHECK(rftfb); CHECK(sintt); CHECK(sintfb); CHECK(costt); + CHECK(costfb); CHECK(sinqft); CHECK(sinqbt); CHECK(sinqfb); CHECK(cosqft); CHECK(cosqbt); + CHECK(cosqfb); CHECK(dcfftf); CHECK(dcfftb); + } + + if (all_ok) printf("Everything looks fine.\n"); + else printf("ERRORS WERE DETECTED.\n"); + /* + expected: + 120 RFFTF 2.786e-06 RFFTB 6.847e-04 RFFTFB 2.795e-07 SINT 1.312e-06 SINTFB 1.237e-06 COST 1.319e-06 + COSTFB 4.355e-06 SINQF 3.281e-04 SINQB 1.876e-06 SINQFB 2.198e-07 COSQF 6.199e-07 COSQB 2.193e-06 + COSQFB 2.300e-07 DEZF 5.573e-06 DEZB 1.363e-05 DEZFB 1.371e-06 CFFTF 5.590e-06 CFFTB 4.751e-05 + CFFTFB 4.215e-07 + 54 RFFTF 4.708e-07 RFFTB 3.052e-05 RFFTFB 3.439e-07 SINT 3.532e-07 SINTFB 4.145e-07 COST 3.002e-07 + COSTFB 6.343e-07 SINQF 4.959e-05 SINQB 4.415e-07 SINQFB 2.882e-07 COSQF 2.826e-07 COSQB 2.472e-07 + COSQFB 3.439e-07 DEZF 9.388e-07 DEZB 5.066e-06 DEZFB 5.960e-07 CFFTF 1.426e-06 CFFTB 9.482e-06 + CFFTFB 2.980e-07 + 49 RFFTF 4.476e-07 RFFTB 5.341e-05 RFFTFB 2.574e-07 SINT 9.196e-07 SINTFB 9.401e-07 COST 8.174e-07 + COSTFB 1.331e-06 SINQF 4.005e-05 SINQB 9.342e-07 SINQFB 3.057e-07 COSQF 2.530e-07 COSQB 6.228e-07 + COSQFB 4.826e-07 DEZF 9.071e-07 DEZB 4.590e-06 DEZFB 5.960e-07 CFFTF 2.095e-06 CFFTB 1.414e-05 + CFFTFB 7.398e-07 + 32 RFFTF 4.619e-07 RFFTB 2.861e-05 RFFTFB 1.192e-07 SINT 3.874e-07 SINTFB 4.172e-07 COST 4.172e-07 + COSTFB 1.699e-06 SINQF 2.551e-05 SINQB 6.407e-07 SINQFB 2.980e-07 COSQF 1.639e-07 COSQB 1.714e-07 + COSQFB 2.384e-07 DEZF 1.013e-06 DEZB 2.339e-06 DEZFB 7.749e-07 CFFTF 1.127e-06 CFFTB 6.744e-06 + CFFTFB 2.666e-07 + 4 RFFTF 1.490e-08 RFFTB 1.490e-07 RFFTFB 5.960e-08 SINT 7.451e-09 SINTFB 0.000e+00 COST 2.980e-08 + COSTFB 1.192e-07 SINQF 4.768e-07 SINQB 2.980e-08 SINQFB 5.960e-08 COSQF 2.608e-08 COSQB 5.960e-08 + COSQFB 1.192e-07 DEZF 2.980e-08 DEZB 5.960e-08 DEZFB 0.000e+00 CFFTF 6.664e-08 CFFTB 5.960e-08 + CFFTFB 6.144e-08 + 3 RFFTF 3.974e-08 RFFTB 1.192e-07 RFFTFB 3.303e-08 SINT 1.987e-08 SINTFB 1.069e-08 COST 4.967e-08 + COSTFB 5.721e-08 SINQF 8.941e-08 SINQB 2.980e-08 SINQFB 1.259e-07 COSQF 7.451e-09 COSQB 4.967e-08 + COSQFB 7.029e-08 DEZF 1.192e-07 DEZB 5.960e-08 DEZFB 5.960e-08 CFFTF 7.947e-08 CFFTB 8.429e-08 + CFFTFB 9.064e-08 + 2 RFFTF 0.000e+00 RFFTB 0.000e+00 RFFTFB 0.000e+00 SINT 0.000e+00 SINTFB 0.000e+00 COST 0.000e+00 + COSTFB 0.000e+00 SINQF 1.192e-07 SINQB 2.980e-08 SINQFB 5.960e-08 COSQF 7.451e-09 COSQB 1.490e-08 + COSQFB 0.000e+00 DEZF 0.000e+00 DEZB 0.000e+00 DEZFB 0.000e+00 CFFTF 0.000e+00 CFFTB 5.960e-08 + CFFTFB 5.960e-08 + Everything looks fine. + + */ + + return all_ok ? 0 : 1; +} +#endif //TESTING_FFTPACK diff --git a/oss-internship-2020/pffft/fftpack.h b/oss-internship-2020/pffft/fftpack.h new file mode 100644 index 0000000..5971b9f --- /dev/null +++ b/oss-internship-2020/pffft/fftpack.h @@ -0,0 +1,799 @@ +/* + Interface for the f2c translation of fftpack as found on http://www.netlib.org/fftpack/ + + FFTPACK license: + + http://www.cisl.ucar.edu/css/software/fftpack5/ftpk.html + + Copyright (c) 2004 the University Corporation for Atmospheric + Research ("UCAR"). All rights reserved. Developed by NCAR's + Computational and Information Systems Laboratory, UCAR, + www.cisl.ucar.edu. + + Redistribution and use of the Software in source and binary forms, + with or without modification, is permitted provided that the + following conditions are met: + + - Neither the names of NCAR's Computational and Information Systems + Laboratory, the University Corporation for Atmospheric Research, + nor the names of its sponsors or contributors may be used to + endorse or promote products derived from this Software without + specific prior written permission. + + - Redistributions of source code must retain the above copyright + notices, this list of conditions, and the disclaimer below. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer below in the + documentation and/or other materials provided with the + distribution. + + THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES OR OTHER LIABILITY, WHETHER IN AN + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE + SOFTWARE. + + ChangeLog: + 2011/10/02: this is my first release of this file. +*/ + +#ifndef FFTPACK_H +#define FFTPACK_H + +#ifdef __cplusplus +extern "C" { +#endif + +// just define FFTPACK_DOUBLE_PRECISION if you want to build it as a double precision fft + +#ifndef FFTPACK_DOUBLE_PRECISION + typedef float fftpack_real; + typedef int fftpack_int; +#else + typedef double fftpack_real; + typedef int fftpack_int; +#endif + + void cffti(fftpack_int n, fftpack_real *wsave); + + void cfftf(fftpack_int n, fftpack_real *c, fftpack_real *wsave); + + void cfftb(fftpack_int n, fftpack_real *c, fftpack_real *wsave); + + void rffti(fftpack_int n, fftpack_real *wsave); + void rfftf(fftpack_int n, fftpack_real *r, fftpack_real *wsave); + void rfftb(fftpack_int n, fftpack_real *r, fftpack_real *wsave); + + void cosqi(fftpack_int n, fftpack_real *wsave); + void cosqf(fftpack_int n, fftpack_real *x, fftpack_real *wsave); + void cosqb(fftpack_int n, fftpack_real *x, fftpack_real *wsave); + + void costi(fftpack_int n, fftpack_real *wsave); + void cost(fftpack_int n, fftpack_real *x, fftpack_real *wsave); + + void sinqi(fftpack_int n, fftpack_real *wsave); + void sinqb(fftpack_int n, fftpack_real *x, fftpack_real *wsave); + void sinqf(fftpack_int n, fftpack_real *x, fftpack_real *wsave); + + void sinti(fftpack_int n, fftpack_real *wsave); + void sint(fftpack_int n, fftpack_real *x, fftpack_real *wsave); + +#ifdef __cplusplus +} +#endif + +#endif /* FFTPACK_H */ + +/* + + FFTPACK + +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + version 4 april 1985 + + a package of fortran subprograms for the fast fourier + transform of periodic and other symmetric sequences + + by + + paul n swarztrauber + + national center for atmospheric research boulder,colorado 80307 + + which is sponsored by the national science foundation + +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + +this package consists of programs which perform fast fourier +transforms for both complex and real periodic sequences and +certain other symmetric sequences that are listed below. + +1. rffti initialize rfftf and rfftb +2. rfftf forward transform of a real periodic sequence +3. rfftb backward transform of a real coefficient array + +4. ezffti initialize ezfftf and ezfftb +5. ezfftf a simplified real periodic forward transform +6. ezfftb a simplified real periodic backward transform + +7. sinti initialize sint +8. sint sine transform of a real odd sequence + +9. costi initialize cost +10. cost cosine transform of a real even sequence + +11. sinqi initialize sinqf and sinqb +12. sinqf forward sine transform with odd wave numbers +13. sinqb unnormalized inverse of sinqf + +14. cosqi initialize cosqf and cosqb +15. cosqf forward cosine transform with odd wave numbers +16. cosqb unnormalized inverse of cosqf + +17. cffti initialize cfftf and cfftb +18. cfftf forward transform of a complex periodic sequence +19. cfftb unnormalized inverse of cfftf + + +****************************************************************** + +subroutine rffti(n,wsave) + + **************************************************************** + +subroutine rffti initializes the array wsave which is used in +both rfftf and rfftb. the prime factorization of n together with +a tabulation of the trigonometric functions are computed and +stored in wsave. + +input parameter + +n the length of the sequence to be transformed. + +output parameter + +wsave a work array which must be dimensioned at least 2*n+15. + the same work array can be used for both rfftf and rfftb + as long as n remains unchanged. different wsave arrays + are required for different values of n. the contents of + wsave must not be changed between calls of rfftf or rfftb. + +****************************************************************** + +subroutine rfftf(n,r,wsave) + +****************************************************************** + +subroutine rfftf computes the fourier coefficients of a real +perodic sequence (fourier analysis). the transform is defined +below at output parameter r. + +input parameters + +n the length of the array r to be transformed. the method + is most efficient when n is a product of small primes. + n may change so long as different work arrays are provided + +r a real array of length n which contains the sequence + to be transformed + +wsave a work array which must be dimensioned at least 2*n+15. + in the program that calls rfftf. the wsave array must be + initialized by calling subroutine rffti(n,wsave) and a + different wsave array must be used for each different + value of n. this initialization does not have to be + repeated so long as n remains unchanged thus subsequent + transforms can be obtained faster than the first. + the same wsave array can be used by rfftf and rfftb. + + +output parameters + +r r(1) = the sum from i=1 to i=n of r(i) + + if n is even set l =n/2 , if n is odd set l = (n+1)/2 + + then for k = 2,...,l + + r(2*k-2) = the sum from i = 1 to i = n of + + r(i)*cos((k-1)*(i-1)*2*pi/n) + + r(2*k-1) = the sum from i = 1 to i = n of + + -r(i)*sin((k-1)*(i-1)*2*pi/n) + + if n is even + + r(n) = the sum from i = 1 to i = n of + + (-1)**(i-1)*r(i) + + ***** note + this transform is unnormalized since a call of rfftf + followed by a call of rfftb will multiply the input + sequence by n. + +wsave contains results which must not be destroyed between + calls of rfftf or rfftb. + + +****************************************************************** + +subroutine rfftb(n,r,wsave) + +****************************************************************** + +subroutine rfftb computes the real perodic sequence from its +fourier coefficients (fourier synthesis). the transform is defined +below at output parameter r. + +input parameters + +n the length of the array r to be transformed. the method + is most efficient when n is a product of small primes. + n may change so long as different work arrays are provided + +r a real array of length n which contains the sequence + to be transformed + +wsave a work array which must be dimensioned at least 2*n+15. + in the program that calls rfftb. the wsave array must be + initialized by calling subroutine rffti(n,wsave) and a + different wsave array must be used for each different + value of n. this initialization does not have to be + repeated so long as n remains unchanged thus subsequent + transforms can be obtained faster than the first. + the same wsave array can be used by rfftf and rfftb. + + +output parameters + +r for n even and for i = 1,...,n + + r(i) = r(1)+(-1)**(i-1)*r(n) + + plus the sum from k=2 to k=n/2 of + + 2.*r(2*k-2)*cos((k-1)*(i-1)*2*pi/n) + + -2.*r(2*k-1)*sin((k-1)*(i-1)*2*pi/n) + + for n odd and for i = 1,...,n + + r(i) = r(1) plus the sum from k=2 to k=(n+1)/2 of + + 2.*r(2*k-2)*cos((k-1)*(i-1)*2*pi/n) + + -2.*r(2*k-1)*sin((k-1)*(i-1)*2*pi/n) + + ***** note + this transform is unnormalized since a call of rfftf + followed by a call of rfftb will multiply the input + sequence by n. + +wsave contains results which must not be destroyed between + calls of rfftb or rfftf. + +****************************************************************** + +subroutine sinti(n,wsave) + +****************************************************************** + +subroutine sinti initializes the array wsave which is used in +subroutine sint. the prime factorization of n together with +a tabulation of the trigonometric functions are computed and +stored in wsave. + +input parameter + +n the length of the sequence to be transformed. the method + is most efficient when n+1 is a product of small primes. + +output parameter + +wsave a work array with at least int(2.5*n+15) locations. + different wsave arrays are required for different values + of n. the contents of wsave must not be changed between + calls of sint. + +****************************************************************** + +subroutine sint(n,x,wsave) + +****************************************************************** + +subroutine sint computes the discrete fourier sine transform +of an odd sequence x(i). the transform is defined below at +output parameter x. + +sint is the unnormalized inverse of itself since a call of sint +followed by another call of sint will multiply the input sequence +x by 2*(n+1). + +the array wsave which is used by subroutine sint must be +initialized by calling subroutine sinti(n,wsave). + +input parameters + +n the length of the sequence to be transformed. the method + is most efficient when n+1 is the product of small primes. + +x an array which contains the sequence to be transformed + + +wsave a work array with dimension at least int(2.5*n+15) + in the program that calls sint. the wsave array must be + initialized by calling subroutine sinti(n,wsave) and a + different wsave array must be used for each different + value of n. this initialization does not have to be + repeated so long as n remains unchanged thus subsequent + transforms can be obtained faster than the first. + +output parameters + +x for i=1,...,n + + x(i)= the sum from k=1 to k=n + + 2*x(k)*sin(k*i*pi/(n+1)) + + a call of sint followed by another call of + sint will multiply the sequence x by 2*(n+1). + hence sint is the unnormalized inverse + of itself. + +wsave contains initialization calculations which must not be + destroyed between calls of sint. + +****************************************************************** + +subroutine costi(n,wsave) + +****************************************************************** + +subroutine costi initializes the array wsave which is used in +subroutine cost. the prime factorization of n together with +a tabulation of the trigonometric functions are computed and +stored in wsave. + +input parameter + +n the length of the sequence to be transformed. the method + is most efficient when n-1 is a product of small primes. + +output parameter + +wsave a work array which must be dimensioned at least 3*n+15. + different wsave arrays are required for different values + of n. the contents of wsave must not be changed between + calls of cost. + +****************************************************************** + +subroutine cost(n,x,wsave) + +****************************************************************** + +subroutine cost computes the discrete fourier cosine transform +of an even sequence x(i). the transform is defined below at output +parameter x. + +cost is the unnormalized inverse of itself since a call of cost +followed by another call of cost will multiply the input sequence +x by 2*(n-1). the transform is defined below at output parameter x + +the array wsave which is used by subroutine cost must be +initialized by calling subroutine costi(n,wsave). + +input parameters + +n the length of the sequence x. n must be greater than 1. + the method is most efficient when n-1 is a product of + small primes. + +x an array which contains the sequence to be transformed + +wsave a work array which must be dimensioned at least 3*n+15 + in the program that calls cost. the wsave array must be + initialized by calling subroutine costi(n,wsave) and a + different wsave array must be used for each different + value of n. this initialization does not have to be + repeated so long as n remains unchanged thus subsequent + transforms can be obtained faster than the first. + +output parameters + +x for i=1,...,n + + x(i) = x(1)+(-1)**(i-1)*x(n) + + + the sum from k=2 to k=n-1 + + 2*x(k)*cos((k-1)*(i-1)*pi/(n-1)) + + a call of cost followed by another call of + cost will multiply the sequence x by 2*(n-1) + hence cost is the unnormalized inverse + of itself. + +wsave contains initialization calculations which must not be + destroyed between calls of cost. + +****************************************************************** + +subroutine sinqi(n,wsave) + +****************************************************************** + +subroutine sinqi initializes the array wsave which is used in +both sinqf and sinqb. the prime factorization of n together with +a tabulation of the trigonometric functions are computed and +stored in wsave. + +input parameter + +n the length of the sequence to be transformed. the method + is most efficient when n is a product of small primes. + +output parameter + +wsave a work array which must be dimensioned at least 3*n+15. + the same work array can be used for both sinqf and sinqb + as long as n remains unchanged. different wsave arrays + are required for different values of n. the contents of + wsave must not be changed between calls of sinqf or sinqb. + +****************************************************************** + +subroutine sinqf(n,x,wsave) + +****************************************************************** + +subroutine sinqf computes the fast fourier transform of quarter +wave data. that is , sinqf computes the coefficients in a sine +series representation with only odd wave numbers. the transform +is defined below at output parameter x. + +sinqb is the unnormalized inverse of sinqf since a call of sinqf +followed by a call of sinqb will multiply the input sequence x +by 4*n. + +the array wsave which is used by subroutine sinqf must be +initialized by calling subroutine sinqi(n,wsave). + + +input parameters + +n the length of the array x to be transformed. the method + is most efficient when n is a product of small primes. + +x an array which contains the sequence to be transformed + +wsave a work array which must be dimensioned at least 3*n+15. + in the program that calls sinqf. the wsave array must be + initialized by calling subroutine sinqi(n,wsave) and a + different wsave array must be used for each different + value of n. this initialization does not have to be + repeated so long as n remains unchanged thus subsequent + transforms can be obtained faster than the first. + +output parameters + +x for i=1,...,n + + x(i) = (-1)**(i-1)*x(n) + + + the sum from k=1 to k=n-1 of + + 2*x(k)*sin((2*i-1)*k*pi/(2*n)) + + a call of sinqf followed by a call of + sinqb will multiply the sequence x by 4*n. + therefore sinqb is the unnormalized inverse + of sinqf. + +wsave contains initialization calculations which must not + be destroyed between calls of sinqf or sinqb. + +****************************************************************** + +subroutine sinqb(n,x,wsave) + +****************************************************************** + +subroutine sinqb computes the fast fourier transform of quarter +wave data. that is , sinqb computes a sequence from its +representation in terms of a sine series with odd wave numbers. +the transform is defined below at output parameter x. + +sinqf is the unnormalized inverse of sinqb since a call of sinqb +followed by a call of sinqf will multiply the input sequence x +by 4*n. + +the array wsave which is used by subroutine sinqb must be +initialized by calling subroutine sinqi(n,wsave). + + +input parameters + +n the length of the array x to be transformed. the method + is most efficient when n is a product of small primes. + +x an array which contains the sequence to be transformed + +wsave a work array which must be dimensioned at least 3*n+15. + in the program that calls sinqb. the wsave array must be + initialized by calling subroutine sinqi(n,wsave) and a + different wsave array must be used for each different + value of n. this initialization does not have to be + repeated so long as n remains unchanged thus subsequent + transforms can be obtained faster than the first. + +output parameters + +x for i=1,...,n + + x(i)= the sum from k=1 to k=n of + + 4*x(k)*sin((2k-1)*i*pi/(2*n)) + + a call of sinqb followed by a call of + sinqf will multiply the sequence x by 4*n. + therefore sinqf is the unnormalized inverse + of sinqb. + +wsave contains initialization calculations which must not + be destroyed between calls of sinqb or sinqf. + +****************************************************************** + +subroutine cosqi(n,wsave) + +****************************************************************** + +subroutine cosqi initializes the array wsave which is used in +both cosqf and cosqb. the prime factorization of n together with +a tabulation of the trigonometric functions are computed and +stored in wsave. + +input parameter + +n the length of the array to be transformed. the method + is most efficient when n is a product of small primes. + +output parameter + +wsave a work array which must be dimensioned at least 3*n+15. + the same work array can be used for both cosqf and cosqb + as long as n remains unchanged. different wsave arrays + are required for different values of n. the contents of + wsave must not be changed between calls of cosqf or cosqb. + +****************************************************************** + +subroutine cosqf(n,x,wsave) + +****************************************************************** + +subroutine cosqf computes the fast fourier transform of quarter +wave data. that is , cosqf computes the coefficients in a cosine +series representation with only odd wave numbers. the transform +is defined below at output parameter x + +cosqf is the unnormalized inverse of cosqb since a call of cosqf +followed by a call of cosqb will multiply the input sequence x +by 4*n. + +the array wsave which is used by subroutine cosqf must be +initialized by calling subroutine cosqi(n,wsave). + + +input parameters + +n the length of the array x to be transformed. the method + is most efficient when n is a product of small primes. + +x an array which contains the sequence to be transformed + +wsave a work array which must be dimensioned at least 3*n+15 + in the program that calls cosqf. the wsave array must be + initialized by calling subroutine cosqi(n,wsave) and a + different wsave array must be used for each different + value of n. this initialization does not have to be + repeated so long as n remains unchanged thus subsequent + transforms can be obtained faster than the first. + +output parameters + +x for i=1,...,n + + x(i) = x(1) plus the sum from k=2 to k=n of + + 2*x(k)*cos((2*i-1)*(k-1)*pi/(2*n)) + + a call of cosqf followed by a call of + cosqb will multiply the sequence x by 4*n. + therefore cosqb is the unnormalized inverse + of cosqf. + +wsave contains initialization calculations which must not + be destroyed between calls of cosqf or cosqb. + +****************************************************************** + +subroutine cosqb(n,x,wsave) + +****************************************************************** + +subroutine cosqb computes the fast fourier transform of quarter +wave data. that is , cosqb computes a sequence from its +representation in terms of a cosine series with odd wave numbers. +the transform is defined below at output parameter x. + +cosqb is the unnormalized inverse of cosqf since a call of cosqb +followed by a call of cosqf will multiply the input sequence x +by 4*n. + +the array wsave which is used by subroutine cosqb must be +initialized by calling subroutine cosqi(n,wsave). + + +input parameters + +n the length of the array x to be transformed. the method + is most efficient when n is a product of small primes. + +x an array which contains the sequence to be transformed + +wsave a work array that must be dimensioned at least 3*n+15 + in the program that calls cosqb. the wsave array must be + initialized by calling subroutine cosqi(n,wsave) and a + different wsave array must be used for each different + value of n. this initialization does not have to be + repeated so long as n remains unchanged thus subsequent + transforms can be obtained faster than the first. + +output parameters + +x for i=1,...,n + + x(i)= the sum from k=1 to k=n of + + 4*x(k)*cos((2*k-1)*(i-1)*pi/(2*n)) + + a call of cosqb followed by a call of + cosqf will multiply the sequence x by 4*n. + therefore cosqf is the unnormalized inverse + of cosqb. + +wsave contains initialization calculations which must not + be destroyed between calls of cosqb or cosqf. + +****************************************************************** + +subroutine cffti(n,wsave) + +****************************************************************** + +subroutine cffti initializes the array wsave which is used in +both cfftf and cfftb. the prime factorization of n together with +a tabulation of the trigonometric functions are computed and +stored in wsave. + +input parameter + +n the length of the sequence to be transformed + +output parameter + +wsave a work array which must be dimensioned at least 4*n+15 + the same work array can be used for both cfftf and cfftb + as long as n remains unchanged. different wsave arrays + are required for different values of n. the contents of + wsave must not be changed between calls of cfftf or cfftb. + +****************************************************************** + +subroutine cfftf(n,c,wsave) + +****************************************************************** + +subroutine cfftf computes the forward complex discrete fourier +transform (the fourier analysis). equivalently , cfftf computes +the fourier coefficients of a complex periodic sequence. +the transform is defined below at output parameter c. + +the transform is not normalized. to obtain a normalized transform +the output must be divided by n. otherwise a call of cfftf +followed by a call of cfftb will multiply the sequence by n. + +the array wsave which is used by subroutine cfftf must be +initialized by calling subroutine cffti(n,wsave). + +input parameters + + +n the length of the complex sequence c. the method is + more efficient when n is the product of small primes. n + +c a complex array of length n which contains the sequence + +wsave a real work array which must be dimensioned at least 4n+15 + in the program that calls cfftf. the wsave array must be + initialized by calling subroutine cffti(n,wsave) and a + different wsave array must be used for each different + value of n. this initialization does not have to be + repeated so long as n remains unchanged thus subsequent + transforms can be obtained faster than the first. + the same wsave array can be used by cfftf and cfftb. + +output parameters + +c for j=1,...,n + + c(j)=the sum from k=1,...,n of + + c(k)*exp(-i*(j-1)*(k-1)*2*pi/n) + + where i=sqrt(-1) + +wsave contains initialization calculations which must not be + destroyed between calls of subroutine cfftf or cfftb + +****************************************************************** + +subroutine cfftb(n,c,wsave) + +****************************************************************** + +subroutine cfftb computes the backward complex discrete fourier +transform (the fourier synthesis). equivalently , cfftb computes +a complex periodic sequence from its fourier coefficients. +the transform is defined below at output parameter c. + +a call of cfftf followed by a call of cfftb will multiply the +sequence by n. + +the array wsave which is used by subroutine cfftb must be +initialized by calling subroutine cffti(n,wsave). + +input parameters + + +n the length of the complex sequence c. the method is + more efficient when n is the product of small primes. + +c a complex array of length n which contains the sequence + +wsave a real work array which must be dimensioned at least 4n+15 + in the program that calls cfftb. the wsave array must be + initialized by calling subroutine cffti(n,wsave) and a + different wsave array must be used for each different + value of n. this initialization does not have to be + repeated so long as n remains unchanged thus subsequent + transforms can be obtained faster than the first. + the same wsave array can be used by cfftf and cfftb. + +output parameters + +c for j=1,...,n + + c(j)=the sum from k=1,...,n of + + c(k)*exp(i*(j-1)*(k-1)*2*pi/n) + + where i=sqrt(-1) + +wsave contains initialization calculations which must not be + destroyed between calls of subroutine cfftf or cfftb + +*/ diff --git a/oss-internship-2020/pffft/myNotes.txt b/oss-internship-2020/pffft/myNotes.txt new file mode 100644 index 0000000..0dd3bcb --- /dev/null +++ b/oss-internship-2020/pffft/myNotes.txt @@ -0,0 +1,101 @@ +About library's functions: + * pffft_aligned_malloc(size_t) + returns an allocated array considering the alignment offset + * pffft_aligned_free(void *) + frees the memory + * pffft_simd_size() + returns the SIMD_SZ = 4 (regarding simd vector) + * pffft_new_setup(int, ...) + with a fft size (first argument) being a multiple of 16, 32. + +Deleted part (validate function) + /*for (pass = 0; pass < 2; pass++) { + if (pass == 0) { + for (k = 0; k < Nfloat; k++) { + ref_[k] = in_[k] = frand() * 2 - 1; + out_[k] = 1e30; + } + + if (!cplx) { + api.rffti(N, wrk_.PtrBoth()).IgnoreError(); + api.rfftf(N, ref_.PtrBoth(), wrk_.PtrBoth()).IgnoreError(); + + { + float refN = ref_[N - 1]; + for (k = N - 2; k >= 1; --k) { + ref_[k + 1] = ref_[k]; + } + ref_[1] = refN; + } + } else { + api.cffti(N, wrk_.PtrBoth()).IgnoreError(); + api.cfftf(N, ref_.PtrBoth(), wrk_.PtrBoth()).IgnoreError(); + } + } + + for (k = 0; k < Nfloat; ++k) { + ref_max = MAX(ref_max, fabs(ref_[k])); + } + + if (pass == 0) { + api.pffft_transform(s_reg.PtrBefore(), in_.PtrBoth(), tmp_.PtrBoth(), wrk_.PtrBoth(), PFFFT_FORWARD).IgnoreError(); + + memcpy(tmp2, tmp, Nbytes); + memcpy(tmp, in, Nbytes); + + api.pffft_transform(s_reg.PtrBefore(), tmp_.PtrBoth(), tmp_.PtrBoth(), wrk_.PtrBoth(), PFFFT_FORWARD).IgnoreError(); + + printf("Forward transformation test passed.\n"); + + api.pffft_zreorder(s_reg.PtrBefore(), tmp_.PtrBoth(), out_.PtrBoth(), PFFFT_FORWARD).IgnoreError(); + api.pffft_zreorder(s_reg.PtrBefore(), out_.PtrBoth(), tmp_.PtrBoth(), PFFFT_BACKWARD).IgnoreError(); + + printf("Reordering test passed.\n"); + } else { + api.pffft_transform_ordered(s_reg.PtrBefore(), in_.PtrBoth(), tmp_.PtrBoth(), wrk_.PtrBoth(), PFFFT_FORWARD).IgnoreError(); + + } + } */ + + +MACRO for testing +TEST(AssignOrReturn, AssignsMultipleVariablesInSequence) { + auto func = []() -> absl::Status { + int value1; + SAPI_ASSIGN_OR_RETURN(value1, StatusOr(1)); + EXPECT_EQ(1, value1); + int value2; + SAPI_ASSIGN_OR_RETURN(value2, StatusOr(2)); + EXPECT_EQ(2, value2); + int value3; + SAPI_ASSIGN_OR_RETURN(value3, StatusOr(3)); + EXPECT_EQ(3, value3); + int value4; + SAPI_ASSIGN_OR_RETURN(value4, + StatusOr(absl::UnknownError("EXPECTED" + int value1; + SAPI_ASSIGN_OR_RETURN(value1, StatusOr(1)); + + + // PFFFT benchmark + /*{ + sapi::StatusOr s = api.pffft_new_setup(N, cplx ? PFFFT_COMPLEX : PFFFT_REAL); + if (s.ok()) { + sapi::v::GenericPtr s_reg(s.value()); + + t0 = uclock_sec(); + for (iter = 0; iter < max_iter; ++iter) { + printf("%s 1\n", api.pffft_transform(s_reg.PtrBoth(), X_.PtrBoth(), Z_.PtrBoth(), Y_.PtrBoth(), PFFFT_FORWARD).ToString().c_str()); + printf("%s 2\n", api.pffft_transform(s_reg.PtrBoth(), X_.PtrBoth(), Z_.PtrBoth(), Y_.PtrBoth(), PFFFT_FORWARD).ToString().c_str()); + } + t1 = uclock_sec(); + printf("%s 3 \n", api.pffft_destroy_setup(s_reg.PtrBoth()).ToString().c_str()); + + + flops = (max_iter*2) * ((cplx ? 5 : 2.5) * N * log((double)N) / M_LN2); + show_output("PFFFT", N, cplx, flops, t0, t1, max_iter); + } else { + fprintf(stderr, "s NULL :(\n\n"); + } + }*/ + \ No newline at end of file diff --git a/oss-internship-2020/pffft/pffft.c b/oss-internship-2020/pffft/pffft.c new file mode 100644 index 0000000..1686e15 --- /dev/null +++ b/oss-internship-2020/pffft/pffft.c @@ -0,0 +1,1881 @@ +/* Copyright (c) 2013 Julien Pommier ( pommier@modartt.com ) + + Based on original fortran 77 code from FFTPACKv4 from NETLIB + (http://www.netlib.org/fftpack), authored by Dr Paul Swarztrauber + of NCAR, in 1985. + + As confirmed by the NCAR fftpack software curators, the following + FFTPACKv5 license applies to FFTPACKv4 sources. My changes are + released under the same terms. + + FFTPACK license: + + http://www.cisl.ucar.edu/css/software/fftpack5/ftpk.html + + Copyright (c) 2004 the University Corporation for Atmospheric + Research ("UCAR"). All rights reserved. Developed by NCAR's + Computational and Information Systems Laboratory, UCAR, + www.cisl.ucar.edu. + + Redistribution and use of the Software in source and binary forms, + with or without modification, is permitted provided that the + following conditions are met: + + - Neither the names of NCAR's Computational and Information Systems + Laboratory, the University Corporation for Atmospheric Research, + nor the names of its sponsors or contributors may be used to + endorse or promote products derived from this Software without + specific prior written permission. + + - Redistributions of source code must retain the above copyright + notices, this list of conditions, and the disclaimer below. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer below in the + documentation and/or other materials provided with the + distribution. + + THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES OR OTHER LIABILITY, WHETHER IN AN + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE + SOFTWARE. + + + PFFFT : a Pretty Fast FFT. + + This file is largerly based on the original FFTPACK implementation, modified in + order to take advantage of SIMD instructions of modern CPUs. +*/ + +/* + ChangeLog: + - 2011/10/02, version 1: This is the very first release of this file. +*/ + +#include "pffft.h" +#include +#include +#include +#include + +/* detect compiler flavour */ +#if defined(_MSC_VER) +# define COMPILER_MSVC +#elif defined(__GNUC__) +# define COMPILER_GCC +#endif + +#if defined(COMPILER_GCC) +# define ALWAYS_INLINE(return_type) inline return_type __attribute__ ((always_inline)) +# define NEVER_INLINE(return_type) return_type __attribute__ ((noinline)) +# define RESTRICT __restrict +# define VLA_ARRAY_ON_STACK(type__, varname__, size__) type__ varname__[size__]; +#elif defined(COMPILER_MSVC) +# define ALWAYS_INLINE(return_type) __forceinline return_type +# define NEVER_INLINE(return_type) __declspec(noinline) return_type +# define RESTRICT __restrict +# define VLA_ARRAY_ON_STACK(type__, varname__, size__) type__ *varname__ = (type__*)_alloca(size__ * sizeof(type__)) +#endif + + +/* + vector support macros: the rest of the code is independant of + SSE/Altivec/NEON -- adding support for other platforms with 4-element + vectors should be limited to these macros +*/ + + +// define PFFFT_SIMD_DISABLE if you want to use scalar code instead of simd code +//#define PFFFT_SIMD_DISABLE + +/* + Altivec support macros +*/ +#if !defined(PFFFT_SIMD_DISABLE) && (defined(__ppc__) || defined(__ppc64__)) +typedef vector float v4sf; +# define SIMD_SZ 4 +# define VZERO() ((vector float) vec_splat_u8(0)) +# define VMUL(a,b) vec_madd(a,b, VZERO()) +# define VADD(a,b) vec_add(a,b) +# define VMADD(a,b,c) vec_madd(a,b,c) +# define VSUB(a,b) vec_sub(a,b) +inline v4sf ld_ps1(const float *p) { v4sf v=vec_lde(0,p); return vec_splat(vec_perm(v, v, vec_lvsl(0, p)), 0); } +# define LD_PS1(p) ld_ps1(&p) +# define INTERLEAVE2(in1, in2, out1, out2) { v4sf tmp__ = vec_mergeh(in1, in2); out2 = vec_mergel(in1, in2); out1 = tmp__; } +# define UNINTERLEAVE2(in1, in2, out1, out2) { \ + vector unsigned char vperm1 = (vector unsigned char)(0,1,2,3,8,9,10,11,16,17,18,19,24,25,26,27); \ + vector unsigned char vperm2 = (vector unsigned char)(4,5,6,7,12,13,14,15,20,21,22,23,28,29,30,31); \ + v4sf tmp__ = vec_perm(in1, in2, vperm1); out2 = vec_perm(in1, in2, vperm2); out1 = tmp__; \ + } +# define VTRANSPOSE4(x0,x1,x2,x3) { \ + v4sf y0 = vec_mergeh(x0, x2); \ + v4sf y1 = vec_mergel(x0, x2); \ + v4sf y2 = vec_mergeh(x1, x3); \ + v4sf y3 = vec_mergel(x1, x3); \ + x0 = vec_mergeh(y0, y2); \ + x1 = vec_mergel(y0, y2); \ + x2 = vec_mergeh(y1, y3); \ + x3 = vec_mergel(y1, y3); \ + } +# define VSWAPHL(a,b) vec_perm(a,b, (vector unsigned char)(16,17,18,19,20,21,22,23,8,9,10,11,12,13,14,15)) +# define VALIGNED(ptr) ((((long)(ptr)) & 0xF) == 0) + +/* + SSE1 support macros +*/ +#elif !defined(PFFFT_SIMD_DISABLE) && (defined(__x86_64__) || defined(_M_X64) || defined(i386) || defined(_M_IX86)) + +#include +typedef __m128 v4sf; +# define SIMD_SZ 4 // 4 floats by simd vector -- this is pretty much hardcoded in the preprocess/finalize functions anyway so you will have to work if you want to enable AVX with its 256-bit vectors. +# define VZERO() _mm_setzero_ps() +# define VMUL(a,b) _mm_mul_ps(a,b) +# define VADD(a,b) _mm_add_ps(a,b) +# define VMADD(a,b,c) _mm_add_ps(_mm_mul_ps(a,b), c) +# define VSUB(a,b) _mm_sub_ps(a,b) +# define LD_PS1(p) _mm_set1_ps(p) +# define INTERLEAVE2(in1, in2, out1, out2) { v4sf tmp__ = _mm_unpacklo_ps(in1, in2); out2 = _mm_unpackhi_ps(in1, in2); out1 = tmp__; } +# define UNINTERLEAVE2(in1, in2, out1, out2) { v4sf tmp__ = _mm_shuffle_ps(in1, in2, _MM_SHUFFLE(2,0,2,0)); out2 = _mm_shuffle_ps(in1, in2, _MM_SHUFFLE(3,1,3,1)); out1 = tmp__; } +# define VTRANSPOSE4(x0,x1,x2,x3) _MM_TRANSPOSE4_PS(x0,x1,x2,x3) +# define VSWAPHL(a,b) _mm_shuffle_ps(b, a, _MM_SHUFFLE(3,2,1,0)) +# define VALIGNED(ptr) ((((long)(ptr)) & 0xF) == 0) + +/* + ARM NEON support macros +*/ +#elif !defined(PFFFT_SIMD_DISABLE) && (defined(__arm__) || defined(__aarch64__) || defined(__arm64__)) +# include +typedef float32x4_t v4sf; +# define SIMD_SZ 4 +# define VZERO() vdupq_n_f32(0) +# define VMUL(a,b) vmulq_f32(a,b) +# define VADD(a,b) vaddq_f32(a,b) +# define VMADD(a,b,c) vmlaq_f32(c,a,b) +# define VSUB(a,b) vsubq_f32(a,b) +# define LD_PS1(p) vld1q_dup_f32(&(p)) +# define INTERLEAVE2(in1, in2, out1, out2) { float32x4x2_t tmp__ = vzipq_f32(in1,in2); out1=tmp__.val[0]; out2=tmp__.val[1]; } +# define UNINTERLEAVE2(in1, in2, out1, out2) { float32x4x2_t tmp__ = vuzpq_f32(in1,in2); out1=tmp__.val[0]; out2=tmp__.val[1]; } +# define VTRANSPOSE4(x0,x1,x2,x3) { \ + float32x4x2_t t0_ = vzipq_f32(x0, x2); \ + float32x4x2_t t1_ = vzipq_f32(x1, x3); \ + float32x4x2_t u0_ = vzipq_f32(t0_.val[0], t1_.val[0]); \ + float32x4x2_t u1_ = vzipq_f32(t0_.val[1], t1_.val[1]); \ + x0 = u0_.val[0]; x1 = u0_.val[1]; x2 = u1_.val[0]; x3 = u1_.val[1]; \ + } +// marginally faster version +//# define VTRANSPOSE4(x0,x1,x2,x3) { asm("vtrn.32 %q0, %q1;\n vtrn.32 %q2,%q3\n vswp %f0,%e2\n vswp %f1,%e3" : "+w"(x0), "+w"(x1), "+w"(x2), "+w"(x3)::); } +# define VSWAPHL(a,b) vcombine_f32(vget_low_f32(b), vget_high_f32(a)) +# define VALIGNED(ptr) ((((long)(ptr)) & 0x3) == 0) +#else +# if !defined(PFFFT_SIMD_DISABLE) +# warning "building with simd disabled !\n"; +# define PFFFT_SIMD_DISABLE // fallback to scalar code +# endif +#endif + +// fallback mode for situations where SSE/Altivec are not available, use scalar mode instead +#ifdef PFFFT_SIMD_DISABLE +typedef float v4sf; +# define SIMD_SZ 1 +# define VZERO() 0.f +# define VMUL(a,b) ((a)*(b)) +# define VADD(a,b) ((a)+(b)) +# define VMADD(a,b,c) ((a)*(b)+(c)) +# define VSUB(a,b) ((a)-(b)) +# define LD_PS1(p) (p) +# define VALIGNED(ptr) ((((long)(ptr)) & 0x3) == 0) +#endif + +// shortcuts for complex multiplcations +#define VCPLXMUL(ar,ai,br,bi) { v4sf tmp; tmp=VMUL(ar,bi); ar=VMUL(ar,br); ar=VSUB(ar,VMUL(ai,bi)); ai=VMUL(ai,br); ai=VADD(ai,tmp); } +#define VCPLXMULCONJ(ar,ai,br,bi) { v4sf tmp; tmp=VMUL(ar,bi); ar=VMUL(ar,br); ar=VADD(ar,VMUL(ai,bi)); ai=VMUL(ai,br); ai=VSUB(ai,tmp); } +#ifndef SVMUL +// multiply a scalar with a vector +#define SVMUL(f,v) VMUL(LD_PS1(f),v) +#endif + +#if !defined(PFFFT_SIMD_DISABLE) +typedef union v4sf_union { + v4sf v; + float f[4]; +} v4sf_union; + +#include + +#define assertv4(v,f0,f1,f2,f3) assert(v.f[0] == (f0) && v.f[1] == (f1) && v.f[2] == (f2) && v.f[3] == (f3)) + +/* detect bugs with the vector support macros */ +void validate_pffft_simd() { + float f[16] = { 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 }; + v4sf_union a0, a1, a2, a3, t, u; + memcpy(a0.f, f, 4*sizeof(float)); + memcpy(a1.f, f+4, 4*sizeof(float)); + memcpy(a2.f, f+8, 4*sizeof(float)); + memcpy(a3.f, f+12, 4*sizeof(float)); + + t = a0; u = a1; t.v = VZERO(); + printf("VZERO=[%2g %2g %2g %2g]\n", t.f[0], t.f[1], t.f[2], t.f[3]); assertv4(t, 0, 0, 0, 0); + t.v = VADD(a1.v, a2.v); + printf("VADD(4:7,8:11)=[%2g %2g %2g %2g]\n", t.f[0], t.f[1], t.f[2], t.f[3]); assertv4(t, 12, 14, 16, 18); + t.v = VMUL(a1.v, a2.v); + printf("VMUL(4:7,8:11)=[%2g %2g %2g %2g]\n", t.f[0], t.f[1], t.f[2], t.f[3]); assertv4(t, 32, 45, 60, 77); + t.v = VMADD(a1.v, a2.v,a0.v); + printf("VMADD(4:7,8:11,0:3)=[%2g %2g %2g %2g]\n", t.f[0], t.f[1], t.f[2], t.f[3]); assertv4(t, 32, 46, 62, 80); + + INTERLEAVE2(a1.v,a2.v,t.v,u.v); + printf("INTERLEAVE2(4:7,8:11)=[%2g %2g %2g %2g] [%2g %2g %2g %2g]\n", t.f[0], t.f[1], t.f[2], t.f[3], u.f[0], u.f[1], u.f[2], u.f[3]); + assertv4(t, 4, 8, 5, 9); assertv4(u, 6, 10, 7, 11); + UNINTERLEAVE2(a1.v,a2.v,t.v,u.v); + printf("UNINTERLEAVE2(4:7,8:11)=[%2g %2g %2g %2g] [%2g %2g %2g %2g]\n", t.f[0], t.f[1], t.f[2], t.f[3], u.f[0], u.f[1], u.f[2], u.f[3]); + assertv4(t, 4, 6, 8, 10); assertv4(u, 5, 7, 9, 11); + + t.v=LD_PS1(f[15]); + printf("LD_PS1(15)=[%2g %2g %2g %2g]\n", t.f[0], t.f[1], t.f[2], t.f[3]); + assertv4(t, 15, 15, 15, 15); + t.v = VSWAPHL(a1.v, a2.v); + printf("VSWAPHL(4:7,8:11)=[%2g %2g %2g %2g]\n", t.f[0], t.f[1], t.f[2], t.f[3]); + assertv4(t, 8, 9, 6, 7); + VTRANSPOSE4(a0.v, a1.v, a2.v, a3.v); + printf("VTRANSPOSE4(0:3,4:7,8:11,12:15)=[%2g %2g %2g %2g] [%2g %2g %2g %2g] [%2g %2g %2g %2g] [%2g %2g %2g %2g]\n", + a0.f[0], a0.f[1], a0.f[2], a0.f[3], a1.f[0], a1.f[1], a1.f[2], a1.f[3], + a2.f[0], a2.f[1], a2.f[2], a2.f[3], a3.f[0], a3.f[1], a3.f[2], a3.f[3]); + assertv4(a0, 0, 4, 8, 12); assertv4(a1, 1, 5, 9, 13); assertv4(a2, 2, 6, 10, 14); assertv4(a3, 3, 7, 11, 15); +} +#endif //!PFFFT_SIMD_DISABLE + +/* SSE and co like 16-bytes aligned pointers */ +#define MALLOC_V4SF_ALIGNMENT 64 // with a 64-byte alignment, we are even aligned on L2 cache lines... +void *pffft_aligned_malloc(size_t nb_bytes) { + void *p, *p0 = malloc(nb_bytes + MALLOC_V4SF_ALIGNMENT); + if (!p0) return (void *) 0; + p = (void *) (((size_t) p0 + MALLOC_V4SF_ALIGNMENT) & (~((size_t) (MALLOC_V4SF_ALIGNMENT-1)))); + *((void **) p - 1) = p0; + return p; +} + +void pffft_aligned_free(void *p) { + if (p) free(*((void **) p - 1)); +} + +int pffft_simd_size() { return SIMD_SZ; } + +/* + passf2 and passb2 has been merged here, fsign = -1 for passf2, +1 for passb2 +*/ +static NEVER_INLINE(void) passf2_ps(int ido, int l1, const v4sf *cc, v4sf *ch, const float *wa1, float fsign) { + int k, i; + int l1ido = l1*ido; + if (ido <= 2) { + for (k=0; k < l1ido; k += ido, ch += ido, cc+= 2*ido) { + ch[0] = VADD(cc[0], cc[ido+0]); + ch[l1ido] = VSUB(cc[0], cc[ido+0]); + ch[1] = VADD(cc[1], cc[ido+1]); + ch[l1ido + 1] = VSUB(cc[1], cc[ido+1]); + } + } else { + for (k=0; k < l1ido; k += ido, ch += ido, cc += 2*ido) { + for (i=0; i 2); + for (k=0; k< l1ido; k += ido, cc+= 3*ido, ch +=ido) { + for (i=0; i 2); + for (k = 0; k < l1; ++k, cc += 5*ido, ch += ido) { + for (i = 0; i < ido-1; i += 2) { + ti5 = VSUB(cc_ref(i , 2), cc_ref(i , 5)); + ti2 = VADD(cc_ref(i , 2), cc_ref(i , 5)); + ti4 = VSUB(cc_ref(i , 3), cc_ref(i , 4)); + ti3 = VADD(cc_ref(i , 3), cc_ref(i , 4)); + tr5 = VSUB(cc_ref(i-1, 2), cc_ref(i-1, 5)); + tr2 = VADD(cc_ref(i-1, 2), cc_ref(i-1, 5)); + tr4 = VSUB(cc_ref(i-1, 3), cc_ref(i-1, 4)); + tr3 = VADD(cc_ref(i-1, 3), cc_ref(i-1, 4)); + ch_ref(i-1, 1) = VADD(cc_ref(i-1, 1), VADD(tr2, tr3)); + ch_ref(i , 1) = VADD(cc_ref(i , 1), VADD(ti2, ti3)); + cr2 = VADD(cc_ref(i-1, 1), VADD(SVMUL(tr11, tr2),SVMUL(tr12, tr3))); + ci2 = VADD(cc_ref(i , 1), VADD(SVMUL(tr11, ti2),SVMUL(tr12, ti3))); + cr3 = VADD(cc_ref(i-1, 1), VADD(SVMUL(tr12, tr2),SVMUL(tr11, tr3))); + ci3 = VADD(cc_ref(i , 1), VADD(SVMUL(tr12, ti2),SVMUL(tr11, ti3))); + cr5 = VADD(SVMUL(ti11, tr5), SVMUL(ti12, tr4)); + ci5 = VADD(SVMUL(ti11, ti5), SVMUL(ti12, ti4)); + cr4 = VSUB(SVMUL(ti12, tr5), SVMUL(ti11, tr4)); + ci4 = VSUB(SVMUL(ti12, ti5), SVMUL(ti11, ti4)); + dr3 = VSUB(cr3, ci4); + dr4 = VADD(cr3, ci4); + di3 = VADD(ci3, cr4); + di4 = VSUB(ci3, cr4); + dr5 = VADD(cr2, ci5); + dr2 = VSUB(cr2, ci5); + di5 = VSUB(ci2, cr5); + di2 = VADD(ci2, cr5); + wr1=wa1[i], wi1=fsign*wa1[i+1], wr2=wa2[i], wi2=fsign*wa2[i+1]; + wr3=wa3[i], wi3=fsign*wa3[i+1], wr4=wa4[i], wi4=fsign*wa4[i+1]; + VCPLXMUL(dr2, di2, LD_PS1(wr1), LD_PS1(wi1)); + ch_ref(i - 1, 2) = dr2; + ch_ref(i, 2) = di2; + VCPLXMUL(dr3, di3, LD_PS1(wr2), LD_PS1(wi2)); + ch_ref(i - 1, 3) = dr3; + ch_ref(i, 3) = di3; + VCPLXMUL(dr4, di4, LD_PS1(wr3), LD_PS1(wi3)); + ch_ref(i - 1, 4) = dr4; + ch_ref(i, 4) = di4; + VCPLXMUL(dr5, di5, LD_PS1(wr4), LD_PS1(wi4)); + ch_ref(i - 1, 5) = dr5; + ch_ref(i, 5) = di5; + } + } +#undef ch_ref +#undef cc_ref +} + +static NEVER_INLINE(void) radf2_ps(int ido, int l1, const v4sf * RESTRICT cc, v4sf * RESTRICT ch, const float *wa1) { + static const float minus_one = -1.f; + int i, k, l1ido = l1*ido; + for (k=0; k < l1ido; k += ido) { + v4sf a = cc[k], b = cc[k + l1ido]; + ch[2*k] = VADD(a, b); + ch[2*(k+ido)-1] = VSUB(a, b); + } + if (ido < 2) return; + if (ido != 2) { + for (k=0; k < l1ido; k += ido) { + for (i=2; i 5) { + wa[i1-1] = wa[i-1]; + wa[i1] = wa[i]; + } + } + l1 = l2; + } +} /* cffti1 */ + + +v4sf *cfftf1_ps(int n, const v4sf *input_readonly, v4sf *work1, v4sf *work2, const float *wa, const int *ifac, int isign) { + v4sf *in = (v4sf*)input_readonly; + v4sf *out = (in == work2 ? work1 : work2); + int nf = ifac[1], k1; + int l1 = 1; + int iw = 0; + assert(in != out && work1 != work2); + for (k1=2; k1<=nf+1; k1++) { + int ip = ifac[k1]; + int l2 = ip*l1; + int ido = n / l2; + int idot = ido + ido; + switch (ip) { + case 5: { + int ix2 = iw + idot; + int ix3 = ix2 + idot; + int ix4 = ix3 + idot; + passf5_ps(idot, l1, in, out, &wa[iw], &wa[ix2], &wa[ix3], &wa[ix4], isign); + } break; + case 4: { + int ix2 = iw + idot; + int ix3 = ix2 + idot; + passf4_ps(idot, l1, in, out, &wa[iw], &wa[ix2], &wa[ix3], isign); + } break; + case 2: { + passf2_ps(idot, l1, in, out, &wa[iw], isign); + } break; + case 3: { + int ix2 = iw + idot; + passf3_ps(idot, l1, in, out, &wa[iw], &wa[ix2], isign); + } break; + default: + assert(0); + } + l1 = l2; + iw += (ip - 1)*idot; + if (out == work2) { + out = work1; in = work2; + } else { + out = work2; in = work1; + } + } + + return in; /* this is in fact the output .. */ +} + + +struct PFFFT_Setup { + int N; + int Ncvec; // nb of complex simd vectors (N/4 if PFFFT_COMPLEX, N/8 if PFFFT_REAL) + int ifac[15]; + pffft_transform_t transform; + v4sf *data; // allocated room for twiddle coefs + float *e; // points into 'data' , N/4*3 elements + float *twiddle; // points into 'data', N/4 elements +}; + +PFFFT_Setup *pffft_new_setup(int N, pffft_transform_t transform) { + PFFFT_Setup *s = (PFFFT_Setup*)malloc(sizeof(PFFFT_Setup)); + int k, m; + /* unfortunately, the fft size must be a multiple of 16 for complex FFTs + and 32 for real FFTs -- a lot of stuff would need to be rewritten to + handle other cases (or maybe just switch to a scalar fft, I don't know..) */ + if (transform == PFFFT_REAL) { assert((N%(2*SIMD_SZ*SIMD_SZ))==0 && N>0); } + if (transform == PFFFT_COMPLEX) { assert((N%(SIMD_SZ*SIMD_SZ))==0 && N>0); } + //assert((N % 32) == 0); + s->N = N; + s->transform = transform; + /* nb of complex simd vectors */ + s->Ncvec = (transform == PFFFT_REAL ? N/2 : N)/SIMD_SZ; + s->data = (v4sf*)pffft_aligned_malloc(2*s->Ncvec * sizeof(v4sf)); + s->e = (float*)s->data; + s->twiddle = (float*)(s->data + (2*s->Ncvec*(SIMD_SZ-1))/SIMD_SZ); + + if (transform == PFFFT_REAL) { + for (k=0; k < s->Ncvec; ++k) { + int i = k/SIMD_SZ; + int j = k%SIMD_SZ; + for (m=0; m < SIMD_SZ-1; ++m) { + float A = -2*M_PI*(m+1)*k / N; + s->e[(2*(i*3 + m) + 0) * SIMD_SZ + j] = cos(A); + s->e[(2*(i*3 + m) + 1) * SIMD_SZ + j] = sin(A); + } + } + rffti1_ps(N/SIMD_SZ, s->twiddle, s->ifac); + } else { + for (k=0; k < s->Ncvec; ++k) { + int i = k/SIMD_SZ; + int j = k%SIMD_SZ; + for (m=0; m < SIMD_SZ-1; ++m) { + float A = -2*M_PI*(m+1)*k / N; + s->e[(2*(i*3 + m) + 0)*SIMD_SZ + j] = cos(A); + s->e[(2*(i*3 + m) + 1)*SIMD_SZ + j] = sin(A); + } + } + cffti1_ps(N/SIMD_SZ, s->twiddle, s->ifac); + } + + /* check that N is decomposable with allowed prime factors */ + for (k=0, m=1; k < s->ifac[1]; ++k) { m *= s->ifac[2+k]; } + if (m != N/SIMD_SZ) { + pffft_destroy_setup(s); s = 0; + } + + return s; +} + + +void pffft_destroy_setup(PFFFT_Setup *s) { + pffft_aligned_free(s->data); + free(s); +} + +#if !defined(PFFFT_SIMD_DISABLE) + +/* [0 0 1 2 3 4 5 6 7 8] -> [0 8 7 6 5 4 3 2 1] */ +static void reversed_copy(int N, const v4sf *in, int in_stride, v4sf *out) { + v4sf g0, g1; + int k; + INTERLEAVE2(in[0], in[1], g0, g1); in += in_stride; + + *--out = VSWAPHL(g0, g1); // [g0l, g0h], [g1l g1h] -> [g1l, g0h] + for (k=1; k < N; ++k) { + v4sf h0, h1; + INTERLEAVE2(in[0], in[1], h0, h1); in += in_stride; + *--out = VSWAPHL(g1, h0); + *--out = VSWAPHL(h0, h1); + g1 = h1; + } + *--out = VSWAPHL(g1, g0); +} + +static void unreversed_copy(int N, const v4sf *in, v4sf *out, int out_stride) { + v4sf g0, g1, h0, h1; + int k; + g0 = g1 = in[0]; ++in; + for (k=1; k < N; ++k) { + h0 = *in++; h1 = *in++; + g1 = VSWAPHL(g1, h0); + h0 = VSWAPHL(h0, h1); + UNINTERLEAVE2(h0, g1, out[0], out[1]); out += out_stride; + g1 = h1; + } + h0 = *in++; h1 = g0; + g1 = VSWAPHL(g1, h0); + h0 = VSWAPHL(h0, h1); + UNINTERLEAVE2(h0, g1, out[0], out[1]); +} + +void pffft_zreorder(PFFFT_Setup *setup, const float *in, float *out, pffft_direction_t direction) { + int k, N = setup->N, Ncvec = setup->Ncvec; + const v4sf *vin = (const v4sf*)in; + v4sf *vout = (v4sf*)out; + assert(in != out); + if (setup->transform == PFFFT_REAL) { + int k, dk = N/32; + if (direction == PFFFT_FORWARD) { + for (k=0; k < dk; ++k) { + INTERLEAVE2(vin[k*8 + 0], vin[k*8 + 1], vout[2*(0*dk + k) + 0], vout[2*(0*dk + k) + 1]); + INTERLEAVE2(vin[k*8 + 4], vin[k*8 + 5], vout[2*(2*dk + k) + 0], vout[2*(2*dk + k) + 1]); + } + reversed_copy(dk, vin+2, 8, (v4sf*)(out + N/2)); + reversed_copy(dk, vin+6, 8, (v4sf*)(out + N)); + } else { + for (k=0; k < dk; ++k) { + UNINTERLEAVE2(vin[2*(0*dk + k) + 0], vin[2*(0*dk + k) + 1], vout[k*8 + 0], vout[k*8 + 1]); + UNINTERLEAVE2(vin[2*(2*dk + k) + 0], vin[2*(2*dk + k) + 1], vout[k*8 + 4], vout[k*8 + 5]); + } + unreversed_copy(dk, (v4sf*)(in + N/4), (v4sf*)(out + N - 6*SIMD_SZ), -8); + unreversed_copy(dk, (v4sf*)(in + 3*N/4), (v4sf*)(out + N - 2*SIMD_SZ), -8); + } + } else { + if (direction == PFFFT_FORWARD) { + for (k=0; k < Ncvec; ++k) { + int kk = (k/4) + (k%4)*(Ncvec/4); + INTERLEAVE2(vin[k*2], vin[k*2+1], vout[kk*2], vout[kk*2+1]); + } + } else { + for (k=0; k < Ncvec; ++k) { + int kk = (k/4) + (k%4)*(Ncvec/4); + UNINTERLEAVE2(vin[kk*2], vin[kk*2+1], vout[k*2], vout[k*2+1]); + } + } + } +} + +void pffft_cplx_finalize(int Ncvec, const v4sf *in, v4sf *out, const v4sf *e) { + int k, dk = Ncvec/SIMD_SZ; // number of 4x4 matrix blocks + v4sf r0, i0, r1, i1, r2, i2, r3, i3; + v4sf sr0, dr0, sr1, dr1, si0, di0, si1, di1; + assert(in != out); + for (k=0; k < dk; ++k) { + r0 = in[8*k+0]; i0 = in[8*k+1]; + r1 = in[8*k+2]; i1 = in[8*k+3]; + r2 = in[8*k+4]; i2 = in[8*k+5]; + r3 = in[8*k+6]; i3 = in[8*k+7]; + VTRANSPOSE4(r0,r1,r2,r3); + VTRANSPOSE4(i0,i1,i2,i3); + VCPLXMUL(r1,i1,e[k*6+0],e[k*6+1]); + VCPLXMUL(r2,i2,e[k*6+2],e[k*6+3]); + VCPLXMUL(r3,i3,e[k*6+4],e[k*6+5]); + + sr0 = VADD(r0,r2); dr0 = VSUB(r0, r2); + sr1 = VADD(r1,r3); dr1 = VSUB(r1, r3); + si0 = VADD(i0,i2); di0 = VSUB(i0, i2); + si1 = VADD(i1,i3); di1 = VSUB(i1, i3); + + /* + transformation for each column is: + + [1 1 1 1 0 0 0 0] [r0] + [1 0 -1 0 0 -1 0 1] [r1] + [1 -1 1 -1 0 0 0 0] [r2] + [1 0 -1 0 0 1 0 -1] [r3] + [0 0 0 0 1 1 1 1] * [i0] + [0 1 0 -1 1 0 -1 0] [i1] + [0 0 0 0 1 -1 1 -1] [i2] + [0 -1 0 1 1 0 -1 0] [i3] + */ + + r0 = VADD(sr0, sr1); i0 = VADD(si0, si1); + r1 = VADD(dr0, di1); i1 = VSUB(di0, dr1); + r2 = VSUB(sr0, sr1); i2 = VSUB(si0, si1); + r3 = VSUB(dr0, di1); i3 = VADD(di0, dr1); + + *out++ = r0; *out++ = i0; *out++ = r1; *out++ = i1; + *out++ = r2; *out++ = i2; *out++ = r3; *out++ = i3; + } +} + +void pffft_cplx_preprocess(int Ncvec, const v4sf *in, v4sf *out, const v4sf *e) { + int k, dk = Ncvec/SIMD_SZ; // number of 4x4 matrix blocks + v4sf r0, i0, r1, i1, r2, i2, r3, i3; + v4sf sr0, dr0, sr1, dr1, si0, di0, si1, di1; + assert(in != out); + for (k=0; k < dk; ++k) { + r0 = in[8*k+0]; i0 = in[8*k+1]; + r1 = in[8*k+2]; i1 = in[8*k+3]; + r2 = in[8*k+4]; i2 = in[8*k+5]; + r3 = in[8*k+6]; i3 = in[8*k+7]; + + sr0 = VADD(r0,r2); dr0 = VSUB(r0, r2); + sr1 = VADD(r1,r3); dr1 = VSUB(r1, r3); + si0 = VADD(i0,i2); di0 = VSUB(i0, i2); + si1 = VADD(i1,i3); di1 = VSUB(i1, i3); + + r0 = VADD(sr0, sr1); i0 = VADD(si0, si1); + r1 = VSUB(dr0, di1); i1 = VADD(di0, dr1); + r2 = VSUB(sr0, sr1); i2 = VSUB(si0, si1); + r3 = VADD(dr0, di1); i3 = VSUB(di0, dr1); + + VCPLXMULCONJ(r1,i1,e[k*6+0],e[k*6+1]); + VCPLXMULCONJ(r2,i2,e[k*6+2],e[k*6+3]); + VCPLXMULCONJ(r3,i3,e[k*6+4],e[k*6+5]); + + VTRANSPOSE4(r0,r1,r2,r3); + VTRANSPOSE4(i0,i1,i2,i3); + + *out++ = r0; *out++ = i0; *out++ = r1; *out++ = i1; + *out++ = r2; *out++ = i2; *out++ = r3; *out++ = i3; + } +} + + +static ALWAYS_INLINE(void) pffft_real_finalize_4x4(const v4sf *in0, const v4sf *in1, const v4sf *in, + const v4sf *e, v4sf *out) { + v4sf r0, i0, r1, i1, r2, i2, r3, i3; + v4sf sr0, dr0, sr1, dr1, si0, di0, si1, di1; + r0 = *in0; i0 = *in1; + r1 = *in++; i1 = *in++; r2 = *in++; i2 = *in++; r3 = *in++; i3 = *in++; + VTRANSPOSE4(r0,r1,r2,r3); + VTRANSPOSE4(i0,i1,i2,i3); + + /* + transformation for each column is: + + [1 1 1 1 0 0 0 0] [r0] + [1 0 -1 0 0 -1 0 1] [r1] + [1 0 -1 0 0 1 0 -1] [r2] + [1 -1 1 -1 0 0 0 0] [r3] + [0 0 0 0 1 1 1 1] * [i0] + [0 -1 0 1 -1 0 1 0] [i1] + [0 -1 0 1 1 0 -1 0] [i2] + [0 0 0 0 -1 1 -1 1] [i3] + */ + + //cerr << "matrix initial, before e , REAL:\n 1: " << r0 << "\n 1: " << r1 << "\n 1: " << r2 << "\n 1: " << r3 << "\n"; + //cerr << "matrix initial, before e, IMAG :\n 1: " << i0 << "\n 1: " << i1 << "\n 1: " << i2 << "\n 1: " << i3 << "\n"; + + VCPLXMUL(r1,i1,e[0],e[1]); + VCPLXMUL(r2,i2,e[2],e[3]); + VCPLXMUL(r3,i3,e[4],e[5]); + + //cerr << "matrix initial, real part:\n 1: " << r0 << "\n 1: " << r1 << "\n 1: " << r2 << "\n 1: " << r3 << "\n"; + //cerr << "matrix initial, imag part:\n 1: " << i0 << "\n 1: " << i1 << "\n 1: " << i2 << "\n 1: " << i3 << "\n"; + + sr0 = VADD(r0,r2); dr0 = VSUB(r0,r2); + sr1 = VADD(r1,r3); dr1 = VSUB(r3,r1); + si0 = VADD(i0,i2); di0 = VSUB(i0,i2); + si1 = VADD(i1,i3); di1 = VSUB(i3,i1); + + r0 = VADD(sr0, sr1); + r3 = VSUB(sr0, sr1); + i0 = VADD(si0, si1); + i3 = VSUB(si1, si0); + r1 = VADD(dr0, di1); + r2 = VSUB(dr0, di1); + i1 = VSUB(dr1, di0); + i2 = VADD(dr1, di0); + + *out++ = r0; + *out++ = i0; + *out++ = r1; + *out++ = i1; + *out++ = r2; + *out++ = i2; + *out++ = r3; + *out++ = i3; + +} + +static NEVER_INLINE(void) pffft_real_finalize(int Ncvec, const v4sf *in, v4sf *out, const v4sf *e) { + int k, dk = Ncvec/SIMD_SZ; // number of 4x4 matrix blocks + /* fftpack order is f0r f1r f1i f2r f2i ... f(n-1)r f(n-1)i f(n)r */ + + v4sf_union cr, ci, *uout = (v4sf_union*)out; + v4sf save = in[7], zero=VZERO(); + float xr0, xi0, xr1, xi1, xr2, xi2, xr3, xi3; + static const float s = M_SQRT2/2; + + cr.v = in[0]; ci.v = in[Ncvec*2-1]; + assert(in != out); + pffft_real_finalize_4x4(&zero, &zero, in+1, e, out); + + /* + [cr0 cr1 cr2 cr3 ci0 ci1 ci2 ci3] + + [Xr(1)] ] [1 1 1 1 0 0 0 0] + [Xr(N/4) ] [0 0 0 0 1 s 0 -s] + [Xr(N/2) ] [1 0 -1 0 0 0 0 0] + [Xr(3N/4)] [0 0 0 0 1 -s 0 s] + [Xi(1) ] [1 -1 1 -1 0 0 0 0] + [Xi(N/4) ] [0 0 0 0 0 -s -1 -s] + [Xi(N/2) ] [0 -1 0 1 0 0 0 0] + [Xi(3N/4)] [0 0 0 0 0 -s 1 -s] + */ + + xr0=(cr.f[0]+cr.f[2]) + (cr.f[1]+cr.f[3]); uout[0].f[0] = xr0; + xi0=(cr.f[0]+cr.f[2]) - (cr.f[1]+cr.f[3]); uout[1].f[0] = xi0; + xr2=(cr.f[0]-cr.f[2]); uout[4].f[0] = xr2; + xi2=(cr.f[3]-cr.f[1]); uout[5].f[0] = xi2; + xr1= ci.f[0] + s*(ci.f[1]-ci.f[3]); uout[2].f[0] = xr1; + xi1=-ci.f[2] - s*(ci.f[1]+ci.f[3]); uout[3].f[0] = xi1; + xr3= ci.f[0] - s*(ci.f[1]-ci.f[3]); uout[6].f[0] = xr3; + xi3= ci.f[2] - s*(ci.f[1]+ci.f[3]); uout[7].f[0] = xi3; + + for (k=1; k < dk; ++k) { + v4sf save_next = in[8*k+7]; + pffft_real_finalize_4x4(&save, &in[8*k+0], in + 8*k+1, + e + k*6, out + k*8); + save = save_next; + } + +} + +static ALWAYS_INLINE(void) pffft_real_preprocess_4x4(const v4sf *in, + const v4sf *e, v4sf *out, int first) { + v4sf r0=in[0], i0=in[1], r1=in[2], i1=in[3], r2=in[4], i2=in[5], r3=in[6], i3=in[7]; + /* + transformation for each column is: + + [1 1 1 1 0 0 0 0] [r0] + [1 0 0 -1 0 -1 -1 0] [r1] + [1 -1 -1 1 0 0 0 0] [r2] + [1 0 0 -1 0 1 1 0] [r3] + [0 0 0 0 1 -1 1 -1] * [i0] + [0 -1 1 0 1 0 0 1] [i1] + [0 0 0 0 1 1 -1 -1] [i2] + [0 1 -1 0 1 0 0 1] [i3] + */ + + v4sf sr0 = VADD(r0,r3), dr0 = VSUB(r0,r3); + v4sf sr1 = VADD(r1,r2), dr1 = VSUB(r1,r2); + v4sf si0 = VADD(i0,i3), di0 = VSUB(i0,i3); + v4sf si1 = VADD(i1,i2), di1 = VSUB(i1,i2); + + r0 = VADD(sr0, sr1); + r2 = VSUB(sr0, sr1); + r1 = VSUB(dr0, si1); + r3 = VADD(dr0, si1); + i0 = VSUB(di0, di1); + i2 = VADD(di0, di1); + i1 = VSUB(si0, dr1); + i3 = VADD(si0, dr1); + + VCPLXMULCONJ(r1,i1,e[0],e[1]); + VCPLXMULCONJ(r2,i2,e[2],e[3]); + VCPLXMULCONJ(r3,i3,e[4],e[5]); + + VTRANSPOSE4(r0,r1,r2,r3); + VTRANSPOSE4(i0,i1,i2,i3); + + if (!first) { + *out++ = r0; + *out++ = i0; + } + *out++ = r1; + *out++ = i1; + *out++ = r2; + *out++ = i2; + *out++ = r3; + *out++ = i3; +} + +static NEVER_INLINE(void) pffft_real_preprocess(int Ncvec, const v4sf *in, v4sf *out, const v4sf *e) { + int k, dk = Ncvec/SIMD_SZ; // number of 4x4 matrix blocks + /* fftpack order is f0r f1r f1i f2r f2i ... f(n-1)r f(n-1)i f(n)r */ + + v4sf_union Xr, Xi, *uout = (v4sf_union*)out; + float cr0, ci0, cr1, ci1, cr2, ci2, cr3, ci3; + static const float s = M_SQRT2; + assert(in != out); + for (k=0; k < 4; ++k) { + Xr.f[k] = ((float*)in)[8*k]; + Xi.f[k] = ((float*)in)[8*k+4]; + } + + pffft_real_preprocess_4x4(in, e, out+1, 1); // will write only 6 values + + /* + [Xr0 Xr1 Xr2 Xr3 Xi0 Xi1 Xi2 Xi3] + + [cr0] [1 0 2 0 1 0 0 0] + [cr1] [1 0 0 0 -1 0 -2 0] + [cr2] [1 0 -2 0 1 0 0 0] + [cr3] [1 0 0 0 -1 0 2 0] + [ci0] [0 2 0 2 0 0 0 0] + [ci1] [0 s 0 -s 0 -s 0 -s] + [ci2] [0 0 0 0 0 -2 0 2] + [ci3] [0 -s 0 s 0 -s 0 -s] + */ + for (k=1; k < dk; ++k) { + pffft_real_preprocess_4x4(in+8*k, e + k*6, out-1+k*8, 0); + } + + cr0=(Xr.f[0]+Xi.f[0]) + 2*Xr.f[2]; uout[0].f[0] = cr0; + cr1=(Xr.f[0]-Xi.f[0]) - 2*Xi.f[2]; uout[0].f[1] = cr1; + cr2=(Xr.f[0]+Xi.f[0]) - 2*Xr.f[2]; uout[0].f[2] = cr2; + cr3=(Xr.f[0]-Xi.f[0]) + 2*Xi.f[2]; uout[0].f[3] = cr3; + ci0= 2*(Xr.f[1]+Xr.f[3]); uout[2*Ncvec-1].f[0] = ci0; + ci1= s*(Xr.f[1]-Xr.f[3]) - s*(Xi.f[1]+Xi.f[3]); uout[2*Ncvec-1].f[1] = ci1; + ci2= 2*(Xi.f[3]-Xi.f[1]); uout[2*Ncvec-1].f[2] = ci2; + ci3=-s*(Xr.f[1]-Xr.f[3]) - s*(Xi.f[1]+Xi.f[3]); uout[2*Ncvec-1].f[3] = ci3; +} + + +void pffft_transform_internal(PFFFT_Setup *setup, const float *finput, float *foutput, v4sf *scratch, + pffft_direction_t direction, int ordered) { + int k, Ncvec = setup->Ncvec; + int nf_odd = (setup->ifac[1] & 1); + + // temporary buffer is allocated on the stack if the scratch pointer is NULL + int stack_allocate = (scratch == 0 ? Ncvec*2 : 1); + VLA_ARRAY_ON_STACK(v4sf, scratch_on_stack, stack_allocate); + + const v4sf *vinput = (const v4sf*)finput; + v4sf *voutput = (v4sf*)foutput; + v4sf *buff[2] = { voutput, scratch ? scratch : scratch_on_stack }; + int ib = (nf_odd ^ ordered ? 1 : 0); + + assert(VALIGNED(finput) && VALIGNED(foutput)); + + //assert(finput != foutput); + if (direction == PFFFT_FORWARD) { + ib = !ib; + if (setup->transform == PFFFT_REAL) { + ib = (rfftf1_ps(Ncvec*2, vinput, buff[ib], buff[!ib], + setup->twiddle, &setup->ifac[0]) == buff[0] ? 0 : 1); + pffft_real_finalize(Ncvec, buff[ib], buff[!ib], (v4sf*)setup->e); + } else { + v4sf *tmp = buff[ib]; + for (k=0; k < Ncvec; ++k) { + UNINTERLEAVE2(vinput[k*2], vinput[k*2+1], tmp[k*2], tmp[k*2+1]); + } + ib = (cfftf1_ps(Ncvec, buff[ib], buff[!ib], buff[ib], + setup->twiddle, &setup->ifac[0], -1) == buff[0] ? 0 : 1); + pffft_cplx_finalize(Ncvec, buff[ib], buff[!ib], (v4sf*)setup->e); + } + if (ordered) { + pffft_zreorder(setup, (float*)buff[!ib], (float*)buff[ib], PFFFT_FORWARD); + } else ib = !ib; + } else { + if (vinput == buff[ib]) { + ib = !ib; // may happen when finput == foutput + } + if (ordered) { + pffft_zreorder(setup, (float*)vinput, (float*)buff[ib], PFFFT_BACKWARD); + vinput = buff[ib]; ib = !ib; + } + if (setup->transform == PFFFT_REAL) { + pffft_real_preprocess(Ncvec, vinput, buff[ib], (v4sf*)setup->e); + ib = (rfftb1_ps(Ncvec*2, buff[ib], buff[0], buff[1], + setup->twiddle, &setup->ifac[0]) == buff[0] ? 0 : 1); + } else { + pffft_cplx_preprocess(Ncvec, vinput, buff[ib], (v4sf*)setup->e); + ib = (cfftf1_ps(Ncvec, buff[ib], buff[0], buff[1], + setup->twiddle, &setup->ifac[0], +1) == buff[0] ? 0 : 1); + for (k=0; k < Ncvec; ++k) { + INTERLEAVE2(buff[ib][k*2], buff[ib][k*2+1], buff[ib][k*2], buff[ib][k*2+1]); + } + } + } + + if (buff[ib] != voutput) { + /* extra copy required -- this situation should only happen when finput == foutput */ + assert(finput==foutput); + for (k=0; k < Ncvec; ++k) { + v4sf a = buff[ib][2*k], b = buff[ib][2*k+1]; + voutput[2*k] = a; voutput[2*k+1] = b; + } + ib = !ib; + } + assert(buff[ib] == voutput); +} + +void pffft_zconvolve_accumulate(PFFFT_Setup *s, const float *a, const float *b, float *ab, float scaling) { + int Ncvec = s->Ncvec; + const v4sf * RESTRICT va = (const v4sf*)a; + const v4sf * RESTRICT vb = (const v4sf*)b; + v4sf * RESTRICT vab = (v4sf*)ab; + +#ifdef __arm__ + __builtin_prefetch(va); + __builtin_prefetch(vb); + __builtin_prefetch(vab); + __builtin_prefetch(va+2); + __builtin_prefetch(vb+2); + __builtin_prefetch(vab+2); + __builtin_prefetch(va+4); + __builtin_prefetch(vb+4); + __builtin_prefetch(vab+4); + __builtin_prefetch(va+6); + __builtin_prefetch(vb+6); + __builtin_prefetch(vab+6); +# ifndef __clang__ +# define ZCONVOLVE_USING_INLINE_NEON_ASM +# endif +#endif + + float ar, ai, br, bi, abr, abi; +#ifndef ZCONVOLVE_USING_INLINE_ASM + v4sf vscal = LD_PS1(scaling); + int i; +#endif + + assert(VALIGNED(a) && VALIGNED(b) && VALIGNED(ab)); + ar = ((v4sf_union*)va)[0].f[0]; + ai = ((v4sf_union*)va)[1].f[0]; + br = ((v4sf_union*)vb)[0].f[0]; + bi = ((v4sf_union*)vb)[1].f[0]; + abr = ((v4sf_union*)vab)[0].f[0]; + abi = ((v4sf_union*)vab)[1].f[0]; + +#ifdef ZCONVOLVE_USING_INLINE_ASM // inline asm version, unfortunately miscompiled by clang 3.2, at least on ubuntu.. so this will be restricted to gcc + const float *a_ = a, *b_ = b; float *ab_ = ab; + int N = Ncvec; + asm volatile("mov r8, %2 \n" + "vdup.f32 q15, %4 \n" + "1: \n" + "pld [%0,#64] \n" + "pld [%1,#64] \n" + "pld [%2,#64] \n" + "pld [%0,#96] \n" + "pld [%1,#96] \n" + "pld [%2,#96] \n" + "vld1.f32 {q0,q1}, [%0,:128]! \n" + "vld1.f32 {q4,q5}, [%1,:128]! \n" + "vld1.f32 {q2,q3}, [%0,:128]! \n" + "vld1.f32 {q6,q7}, [%1,:128]! \n" + "vld1.f32 {q8,q9}, [r8,:128]! \n" + + "vmul.f32 q10, q0, q4 \n" + "vmul.f32 q11, q0, q5 \n" + "vmul.f32 q12, q2, q6 \n" + "vmul.f32 q13, q2, q7 \n" + "vmls.f32 q10, q1, q5 \n" + "vmla.f32 q11, q1, q4 \n" + "vld1.f32 {q0,q1}, [r8,:128]! \n" + "vmls.f32 q12, q3, q7 \n" + "vmla.f32 q13, q3, q6 \n" + "vmla.f32 q8, q10, q15 \n" + "vmla.f32 q9, q11, q15 \n" + "vmla.f32 q0, q12, q15 \n" + "vmla.f32 q1, q13, q15 \n" + "vst1.f32 {q8,q9},[%2,:128]! \n" + "vst1.f32 {q0,q1},[%2,:128]! \n" + "subs %3, #2 \n" + "bne 1b \n" + : "+r"(a_), "+r"(b_), "+r"(ab_), "+r"(N) : "r"(scaling) : "r8", "q0","q1","q2","q3","q4","q5","q6","q7","q8","q9", "q10","q11","q12","q13","q15","memory"); +#else // default routine, works fine for non-arm cpus with current compilers + for (i=0; i < Ncvec; i += 2) { + v4sf ar, ai, br, bi; + ar = va[2*i+0]; ai = va[2*i+1]; + br = vb[2*i+0]; bi = vb[2*i+1]; + VCPLXMUL(ar, ai, br, bi); + vab[2*i+0] = VMADD(ar, vscal, vab[2*i+0]); + vab[2*i+1] = VMADD(ai, vscal, vab[2*i+1]); + ar = va[2*i+2]; ai = va[2*i+3]; + br = vb[2*i+2]; bi = vb[2*i+3]; + VCPLXMUL(ar, ai, br, bi); + vab[2*i+2] = VMADD(ar, vscal, vab[2*i+2]); + vab[2*i+3] = VMADD(ai, vscal, vab[2*i+3]); + } +#endif + if (s->transform == PFFFT_REAL) { + ((v4sf_union*)vab)[0].f[0] = abr + ar*br*scaling; + ((v4sf_union*)vab)[1].f[0] = abi + ai*bi*scaling; + } +} + + +#else // defined(PFFFT_SIMD_DISABLE) + +// standard routine using scalar floats, without SIMD stuff. + +#define pffft_zreorder_nosimd pffft_zreorder +void pffft_zreorder_nosimd(PFFFT_Setup *setup, const float *in, float *out, pffft_direction_t direction) { + int k, N = setup->N; + if (setup->transform == PFFFT_COMPLEX) { + for (k=0; k < 2*N; ++k) out[k] = in[k]; + return; + } + else if (direction == PFFFT_FORWARD) { + float x_N = in[N-1]; + for (k=N-1; k > 1; --k) out[k] = in[k-1]; + out[0] = in[0]; + out[1] = x_N; + } else { + float x_N = in[1]; + for (k=1; k < N-1; ++k) out[k] = in[k+1]; + out[0] = in[0]; + out[N-1] = x_N; + } +} + +#define pffft_transform_internal_nosimd pffft_transform_internal +void pffft_transform_internal_nosimd(PFFFT_Setup *setup, const float *input, float *output, float *scratch, + pffft_direction_t direction, int ordered) { + int Ncvec = setup->Ncvec; + int nf_odd = (setup->ifac[1] & 1); + + // temporary buffer is allocated on the stack if the scratch pointer is NULL + int stack_allocate = (scratch == 0 ? Ncvec*2 : 1); + VLA_ARRAY_ON_STACK(v4sf, scratch_on_stack, stack_allocate); + float *buff[2]; + int ib; + if (scratch == 0) scratch = scratch_on_stack; + buff[0] = output; buff[1] = scratch; + + if (setup->transform == PFFFT_COMPLEX) ordered = 0; // it is always ordered. + ib = (nf_odd ^ ordered ? 1 : 0); + + if (direction == PFFFT_FORWARD) { + if (setup->transform == PFFFT_REAL) { + ib = (rfftf1_ps(Ncvec*2, input, buff[ib], buff[!ib], + setup->twiddle, &setup->ifac[0]) == buff[0] ? 0 : 1); + } else { + ib = (cfftf1_ps(Ncvec, input, buff[ib], buff[!ib], + setup->twiddle, &setup->ifac[0], -1) == buff[0] ? 0 : 1); + } + if (ordered) { + pffft_zreorder(setup, buff[ib], buff[!ib], PFFFT_FORWARD); ib = !ib; + } + } else { + if (input == buff[ib]) { + ib = !ib; // may happen when finput == foutput + } + if (ordered) { + pffft_zreorder(setup, input, buff[!ib], PFFFT_BACKWARD); + input = buff[!ib]; + } + if (setup->transform == PFFFT_REAL) { + ib = (rfftb1_ps(Ncvec*2, input, buff[ib], buff[!ib], + setup->twiddle, &setup->ifac[0]) == buff[0] ? 0 : 1); + } else { + ib = (cfftf1_ps(Ncvec, input, buff[ib], buff[!ib], + setup->twiddle, &setup->ifac[0], +1) == buff[0] ? 0 : 1); + } + } + if (buff[ib] != output) { + int k; + // extra copy required -- this situation should happens only when finput == foutput + assert(input==output); + for (k=0; k < Ncvec; ++k) { + float a = buff[ib][2*k], b = buff[ib][2*k+1]; + output[2*k] = a; output[2*k+1] = b; + } + ib = !ib; + } + assert(buff[ib] == output); +} + +#define pffft_zconvolve_accumulate_nosimd pffft_zconvolve_accumulate +void pffft_zconvolve_accumulate_nosimd(PFFFT_Setup *s, const float *a, const float *b, + float *ab, float scaling) { + int i, Ncvec = s->Ncvec; + + if (s->transform == PFFFT_REAL) { + // take care of the fftpack ordering + ab[0] += a[0]*b[0]*scaling; + ab[2*Ncvec-1] += a[2*Ncvec-1]*b[2*Ncvec-1]*scaling; + ++ab; ++a; ++b; --Ncvec; + } + for (i=0; i < Ncvec; ++i) { + float ar, ai, br, bi; + ar = a[2*i+0]; ai = a[2*i+1]; + br = b[2*i+0]; bi = b[2*i+1]; + VCPLXMUL(ar, ai, br, bi); + ab[2*i+0] += ar*scaling; + ab[2*i+1] += ai*scaling; + } +} + +#endif // defined(PFFFT_SIMD_DISABLE) + +void pffft_transform(PFFFT_Setup *setup, const float *input, float *output, float *work, pffft_direction_t direction) { + pffft_transform_internal(setup, input, output, (v4sf*)work, direction, 0); +} + +void pffft_transform_ordered(PFFFT_Setup *setup, const float *input, float *output, float *work, pffft_direction_t direction) { + pffft_transform_internal(setup, input, output, (v4sf*)work, direction, 1); +} diff --git a/oss-internship-2020/pffft/pffft.h b/oss-internship-2020/pffft/pffft.h new file mode 100644 index 0000000..2bfa7b3 --- /dev/null +++ b/oss-internship-2020/pffft/pffft.h @@ -0,0 +1,177 @@ +/* Copyright (c) 2013 Julien Pommier ( pommier@modartt.com ) + + Based on original fortran 77 code from FFTPACKv4 from NETLIB, + authored by Dr Paul Swarztrauber of NCAR, in 1985. + + As confirmed by the NCAR fftpack software curators, the following + FFTPACKv5 license applies to FFTPACKv4 sources. My changes are + released under the same terms. + + FFTPACK license: + + http://www.cisl.ucar.edu/css/software/fftpack5/ftpk.html + + Copyright (c) 2004 the University Corporation for Atmospheric + Research ("UCAR"). All rights reserved. Developed by NCAR's + Computational and Information Systems Laboratory, UCAR, + www.cisl.ucar.edu. + + Redistribution and use of the Software in source and binary forms, + with or without modification, is permitted provided that the + following conditions are met: + + - Neither the names of NCAR's Computational and Information Systems + Laboratory, the University Corporation for Atmospheric Research, + nor the names of its sponsors or contributors may be used to + endorse or promote products derived from this Software without + specific prior written permission. + + - Redistributions of source code must retain the above copyright + notices, this list of conditions, and the disclaimer below. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer below in the + documentation and/or other materials provided with the + distribution. + + THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES OR OTHER LIABILITY, WHETHER IN AN + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE + SOFTWARE. +*/ + +/* + PFFFT : a Pretty Fast FFT. + + This is basically an adaptation of the single precision fftpack + (v4) as found on netlib taking advantage of SIMD instruction found + on cpus such as intel x86 (SSE1), powerpc (Altivec), and arm (NEON). + + For architectures where no SIMD instruction is available, the code + falls back to a scalar version. + + Restrictions: + + - 1D transforms only, with 32-bit single precision. + + - supports only transforms for inputs of length N of the form + N=(2^a)*(3^b)*(5^c), a >= 5, b >=0, c >= 0 (32, 48, 64, 96, 128, + 144, 160, etc are all acceptable lengths). Performance is best for + 128<=N<=8192. + + - all (float*) pointers in the functions below are expected to + have an "simd-compatible" alignment, that is 16 bytes on x86 and + powerpc CPUs. + + You can allocate such buffers with the functions + pffft_aligned_malloc / pffft_aligned_free (or with stuff like + posix_memalign..) + +*/ + +#ifndef PFFFT_H +#define PFFFT_H + +#include // for size_t + +#ifdef __cplusplus +extern "C" { +#endif + + /* opaque struct holding internal stuff (precomputed twiddle factors) + this struct can be shared by many threads as it contains only + read-only data. + */ + typedef struct PFFFT_Setup PFFFT_Setup; + + /* direction of the transform */ + typedef enum { PFFFT_FORWARD, PFFFT_BACKWARD } pffft_direction_t; + + /* type of transform */ + typedef enum { PFFFT_REAL, PFFFT_COMPLEX } pffft_transform_t; + + /* + prepare for performing transforms of size N -- the returned + PFFFT_Setup structure is read-only so it can safely be shared by + multiple concurrent threads. + */ + PFFFT_Setup *pffft_new_setup(int N, pffft_transform_t transform); + void pffft_destroy_setup(PFFFT_Setup *); + /* + Perform a Fourier transform , The z-domain data is stored in the + most efficient order for transforming it back, or using it for + convolution. If you need to have its content sorted in the + "usual" way, that is as an array of interleaved complex numbers, + either use pffft_transform_ordered , or call pffft_zreorder after + the forward fft, and before the backward fft. + + Transforms are not scaled: PFFFT_BACKWARD(PFFFT_FORWARD(x)) = N*x. + Typically you will want to scale the backward transform by 1/N. + + The 'work' pointer should point to an area of N (2*N for complex + fft) floats, properly aligned. If 'work' is NULL, then stack will + be used instead (this is probably the best strategy for small + FFTs, say for N < 16384). + + input and output may alias. + */ + void pffft_transform(PFFFT_Setup *setup, const float *input, float *output, float *work, pffft_direction_t direction); + + /* + Similar to pffft_transform, but makes sure that the output is + ordered as expected (interleaved complex numbers). This is + similar to calling pffft_transform and then pffft_zreorder. + + input and output may alias. + */ + void pffft_transform_ordered(PFFFT_Setup *setup, const float *input, float *output, float *work, pffft_direction_t direction); + + /* + call pffft_zreorder(.., PFFFT_FORWARD) after pffft_transform(..., + PFFFT_FORWARD) if you want to have the frequency components in + the correct "canonical" order, as interleaved complex numbers. + + (for real transforms, both 0-frequency and half frequency + components, which are real, are assembled in the first entry as + F(0)+i*F(n/2+1). Note that the original fftpack did place + F(n/2+1) at the end of the arrays). + + input and output should not alias. + */ + void pffft_zreorder(PFFFT_Setup *setup, const float *input, float *output, pffft_direction_t direction); + + /* + Perform a multiplication of the frequency components of dft_a and + dft_b and accumulate them into dft_ab. The arrays should have + been obtained with pffft_transform(.., PFFFT_FORWARD) and should + *not* have been reordered with pffft_zreorder (otherwise just + perform the operation yourself as the dft coefs are stored as + interleaved complex numbers). + + the operation performed is: dft_ab += (dft_a * fdt_b)*scaling + + The dft_a, dft_b and dft_ab pointers may alias. + */ + void pffft_zconvolve_accumulate(PFFFT_Setup *setup, const float *dft_a, const float *dft_b, float *dft_ab, float scaling); + + /* + the float buffers must have the correct alignment (16-byte boundary + on intel and powerpc). This function may be used to obtain such + correctly aligned buffers. + */ + void *pffft_aligned_malloc(size_t nb_bytes); + void pffft_aligned_free(void *); + + /* return 4 or 1 wether support SSE/Altivec instructions was enable when building pffft.c */ + int pffft_simd_size(); + +#ifdef __cplusplus +} +#endif + +#endif // PFFFT_H diff --git a/oss-internship-2020/pffft/test_pffft.c b/oss-internship-2020/pffft/test_pffft.c new file mode 100644 index 0000000..a5d20c2 --- /dev/null +++ b/oss-internship-2020/pffft/test_pffft.c @@ -0,0 +1,419 @@ +/* + Copyright (c) 2013 Julien Pommier. + + Small test & bench for PFFFT, comparing its performance with the scalar FFTPACK, FFTW, and Apple vDSP + + How to build: + + on linux, with fftw3: + gcc -o test_pffft -DHAVE_FFTW -msse -mfpmath=sse -O3 -Wall -W pffft.c test_pffft.c fftpack.c -L/usr/local/lib -I/usr/local/include/ -lfftw3f -lm + + on macos, without fftw3: + clang -o test_pffft -DHAVE_VECLIB -O3 -Wall -W pffft.c test_pffft.c fftpack.c -L/usr/local/lib -I/usr/local/include/ -framework Accelerate + + on macos, with fftw3: + clang -o test_pffft -DHAVE_FFTW -DHAVE_VECLIB -O3 -Wall -W pffft.c test_pffft.c fftpack.c -L/usr/local/lib -I/usr/local/include/ -lfftw3f -framework Accelerate + + on windows, with visual c++: + cl /Ox -D_USE_MATH_DEFINES /arch:SSE test_pffft.c pffft.c fftpack.c + + build without SIMD instructions: + gcc -o test_pffft -DPFFFT_SIMD_DISABLE -O3 -Wall -W pffft.c test_pffft.c fftpack.c -lm + + */ + +#include "pffft.h" +#include "fftpack.h" + +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_SYS_TIMES +# include +# include +#endif + +#ifdef HAVE_VECLIB +# include +#endif + +#ifdef HAVE_FFTW +# include +#endif + +#define MAX(x,y) ((x)>(y)?(x):(y)) + +double frand() { + return rand()/(double)RAND_MAX; +} + +#if defined(HAVE_SYS_TIMES) + inline double uclock_sec(void) { + static double ttclk = 0.; + if (ttclk == 0.) ttclk = sysconf(_SC_CLK_TCK); + struct tms t; return ((double)times(&t)) / ttclk; + } +# else + double uclock_sec(void) +{ return (double)clock()/(double)CLOCKS_PER_SEC; } +#endif + + +/* compare results with the regular fftpack */ +void pffft_validate_N(int N, int cplx) { + int Nfloat = N*(cplx?2:1); + int Nbytes = Nfloat * sizeof(float); + float *ref, *in, *out, *tmp, *tmp2; + PFFFT_Setup *s = pffft_new_setup(N, cplx ? PFFFT_COMPLEX : PFFFT_REAL); + int pass; + + if (!s) { printf("Skipping N=%d, not supported\n", N); return; } + ref = pffft_aligned_malloc(Nbytes); + in = pffft_aligned_malloc(Nbytes); + out = pffft_aligned_malloc(Nbytes); + tmp = pffft_aligned_malloc(Nbytes); + tmp2 = pffft_aligned_malloc(Nbytes); + + for (pass=0; pass < 2; ++pass) { + float ref_max = 0; + int k; + //printf("N=%d pass=%d cplx=%d\n", N, pass, cplx); + // compute reference solution with FFTPACK + if (pass == 0) { + float *wrk = malloc(2*Nbytes+15*sizeof(float)); + for (k=0; k < Nfloat; ++k) { + ref[k] = in[k] = frand()*2-1; + out[k] = 1e30; + } + if (!cplx) { + rffti(N, wrk); + rfftf(N, ref, wrk); + // use our ordering for real ffts instead of the one of fftpack + { + float refN=ref[N-1]; + for (k=N-2; k >= 1; --k) ref[k+1] = ref[k]; + ref[1] = refN; + } + } else { + cffti(N, wrk); + cfftf(N, ref, wrk); + } + free(wrk); + } + + for (k = 0; k < Nfloat; ++k) ref_max = MAX(ref_max, fabs(ref[k])); + + + // pass 0 : non canonical ordering of transform coefficients + if (pass == 0) { + // test forward transform, with different input / output + pffft_transform(s, in, tmp, 0, PFFFT_FORWARD); + memcpy(tmp2, tmp, Nbytes); + memcpy(tmp, in, Nbytes); + pffft_transform(s, tmp, tmp, 0, PFFFT_FORWARD); + for (k = 0; k < Nfloat; ++k) { + assert(tmp2[k] == tmp[k]); + } + + // test reordering + pffft_zreorder(s, tmp, out, PFFFT_FORWARD); + pffft_zreorder(s, out, tmp, PFFFT_BACKWARD); + for (k = 0; k < Nfloat; ++k) { + assert(tmp2[k] == tmp[k]); + } + pffft_zreorder(s, tmp, out, PFFFT_FORWARD); + } else { + // pass 1 : canonical ordering of transform coeffs. + pffft_transform_ordered(s, in, tmp, 0, PFFFT_FORWARD); + memcpy(tmp2, tmp, Nbytes); + memcpy(tmp, in, Nbytes); + pffft_transform_ordered(s, tmp, tmp, 0, PFFFT_FORWARD); + for (k = 0; k < Nfloat; ++k) { + assert(tmp2[k] == tmp[k]); + } + memcpy(out, tmp, Nbytes); + } + + { + for (k=0; k < Nfloat; ++k) { + if (!(fabs(ref[k] - out[k]) < 1e-3*ref_max)) { + printf("%s forward PFFFT mismatch found for N=%d\n", (cplx?"CPLX":"REAL"), N); + exit(1); + } + } + + if (pass == 0) pffft_transform(s, tmp, out, 0, PFFFT_BACKWARD); + else pffft_transform_ordered(s, tmp, out, 0, PFFFT_BACKWARD); + memcpy(tmp2, out, Nbytes); + memcpy(out, tmp, Nbytes); + if (pass == 0) pffft_transform(s, out, out, 0, PFFFT_BACKWARD); + else pffft_transform_ordered(s, out, out, 0, PFFFT_BACKWARD); + for (k = 0; k < Nfloat; ++k) { + assert(tmp2[k] == out[k]); + out[k] *= 1.f/N; + } + for (k = 0; k < Nfloat; ++k) { + if (fabs(in[k] - out[k]) > 1e-3 * ref_max) { + printf("pass=%d, %s IFFFT does not match for N=%d\n", pass, (cplx?"CPLX":"REAL"), N); break; + exit(1); + } + } + } + + // quick test of the circular convolution in fft domain + { + float conv_err = 0, conv_max = 0; + + pffft_zreorder(s, ref, tmp, PFFFT_FORWARD); + memset(out, 0, Nbytes); + pffft_zconvolve_accumulate(s, ref, ref, out, 1.0); + pffft_zreorder(s, out, tmp2, PFFFT_FORWARD); + + for (k=0; k < Nfloat; k += 2) { + float ar = tmp[k], ai=tmp[k+1]; + if (cplx || k > 0) { + tmp[k] = ar*ar - ai*ai; + tmp[k+1] = 2*ar*ai; + } else { + tmp[0] = ar*ar; + tmp[1] = ai*ai; + } + } + + for (k=0; k < Nfloat; ++k) { + float d = fabs(tmp[k] - tmp2[k]), e = fabs(tmp[k]); + if (d > conv_err) conv_err = d; + if (e > conv_max) conv_max = e; + } + if (conv_err > 1e-5*conv_max) { + printf("zconvolve error ? %g %g\n", conv_err, conv_max); exit(1); + } + } + + } + + printf("%s PFFFT is OK for N=%d\n", (cplx?"CPLX":"REAL"), N); fflush(stdout); + + pffft_destroy_setup(s); + pffft_aligned_free(ref); + pffft_aligned_free(in); + pffft_aligned_free(out); + pffft_aligned_free(tmp); + pffft_aligned_free(tmp2); +} + +void pffft_validate(int cplx) { + static int Ntest[] = { 16, 32, 64, 96, 128, 160, 192, 256, 288, 384, 5*96, 512, 576, 5*128, 800, 864, 1024, 2048, 2592, 4000, 4096, 12000, 36864, 0}; + int k; + for (k = 0; Ntest[k]; ++k) { + int N = Ntest[k]; + if (N == 16 && !cplx) continue; + pffft_validate_N(N, cplx); + } +} + +int array_output_format = 0; + +void show_output(const char *name, int N, int cplx, float flops, float t0, float t1, int max_iter) { + float mflops = flops/1e6/(t1 - t0 + 1e-16); + if (array_output_format) { + if (flops != -1) { + printf("|%9.0f ", mflops); + } else printf("| n/a "); + } else { + if (flops != -1) { + printf("N=%5d, %s %16s : %6.0f MFlops [t=%6.0f ns, %d runs]\n", N, (cplx?"CPLX":"REAL"), name, mflops, (t1-t0)/2/max_iter * 1e9, max_iter); + } + } + fflush(stdout); +} + +void benchmark_ffts(int N, int cplx) { + int Nfloat = (cplx ? N*2 : N); + int Nbytes = Nfloat * sizeof(float); + float *X = pffft_aligned_malloc(Nbytes), *Y = pffft_aligned_malloc(Nbytes), *Z = pffft_aligned_malloc(Nbytes); + + double t0, t1, flops; + + int k; + int max_iter = 5120000/N*4; +#ifdef __arm__ + max_iter /= 4; +#endif + int iter; + + for (k = 0; k < Nfloat; ++k) { + X[k] = 0; //sqrtf(k+1); + } + + // FFTPack benchmark + { + float *wrk = malloc(2*Nbytes + 15*sizeof(float)); + int max_iter_ = max_iter/pffft_simd_size(); if (max_iter_ == 0) max_iter_ = 1; + if (cplx) cffti(N, wrk); + else rffti(N, wrk); + t0 = uclock_sec(); + + for (iter = 0; iter < max_iter_; ++iter) { + if (cplx) { + cfftf(N, X, wrk); + cfftb(N, X, wrk); + } else { + rfftf(N, X, wrk); + rfftb(N, X, wrk); + } + } + t1 = uclock_sec(); + free(wrk); + + flops = (max_iter_*2) * ((cplx ? 5 : 2.5)*N*log((double)N)/M_LN2); // see http://www.fftw.org/speed/method.html + show_output("FFTPack", N, cplx, flops, t0, t1, max_iter_); + } + +#ifdef HAVE_VECLIB + int log2N = (int)(log(N)/log(2) + 0.5f); + if (N == (1< 1 && strcmp(argv[1], "--array-format") == 0) { + array_output_format = 1; + } + +#ifndef PFFFT_SIMD_DISABLE + validate_pffft_simd(); +#endif + pffft_validate(1); + pffft_validate(0); + if (!array_output_format) { + for (i=0; Nvalues[i] > 0; ++i) { + benchmark_ffts(Nvalues[i], 0 /* real fft */); + } + for (i=0; Nvalues[i] > 0; ++i) { + benchmark_ffts(Nvalues[i], 1 /* cplx fft */); + } + } else { + printf("| input len "); + printf("|real FFTPack"); +#ifdef HAVE_VECLIB + printf("| real vDSP "); +#endif +#ifdef HAVE_FFTW + printf("| real FFTW "); +#endif + printf("| real PFFFT | "); + + printf("|cplx FFTPack"); +#ifdef HAVE_VECLIB + printf("| cplx vDSP "); +#endif +#ifdef HAVE_FFTW + printf("| cplx FFTW "); +#endif + printf("| cplx PFFFT |\n"); + for (i=0; Nvalues[i] > 0; ++i) { + printf("|%9d ", Nvalues[i]); + benchmark_ffts(Nvalues[i], 0); + printf("| "); + benchmark_ffts(Nvalues[i], 1); + printf("|\n"); + } + printf(" (numbers are given in MFlops)\n"); + } + + + return 0; +} diff --git a/oss-internship-2020/pffft/test_pffft_sandboxed.cc b/oss-internship-2020/pffft/test_pffft_sandboxed.cc new file mode 100644 index 0000000..dd6dc0b --- /dev/null +++ b/oss-internship-2020/pffft/test_pffft_sandboxed.cc @@ -0,0 +1,150 @@ +#include "fftpack.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pffft_sapi.sapi.h" +#include "sandboxed_api/util/flag.h" +#include "sandboxed_api/vars.h" + +ABSL_DECLARE_FLAG(string, sandbox2_danger_danger_permit_all); +ABSL_DECLARE_FLAG(string, sandbox2_danger_danger_permit_all_and_log); + +class pffftSapiSandbox : public pffftSandbox { + public: + std::unique_ptr ModifyPolicy(sandbox2::PolicyBuilder*) override { + return sandbox2::PolicyBuilder() + .AllowStaticStartup() + .AllowOpen() + .AllowRead() + .AllowWrite() + .AllowSystemMalloc() + .AllowExit() + .AllowSyscalls({ + __NR_futex, + __NR_close, + __NR_getrusage, + }) + .DisableNamespaces() + .BuildOrDie(); + } +}; + +double frand() { + return rand()/(double)RAND_MAX; +} + +double uclock_sec(void) { + return (double)clock()/(double)CLOCKS_PER_SEC; +} + +int array_output_format = 0; + +void show_output(const char *name, int N, int cplx, float flops, float t0, float t1, int max_iter) { + float mflops = flops/1e6/(t1 - t0 + 1e-16); + if (array_output_format) { + if (flops != -1) { + printf("|%9.0f ", mflops); + } else printf("| n/a "); + } else { + if (flops != -1) { + printf("N=%5d, %s %16s : %6.0f MFlops [t=%6.0f ns, %d runs]\n", N, (cplx?"CPLX":"REAL"), name, mflops, (t1-t0)/2/max_iter * 1e9, max_iter); + } + } + fflush(stdout); +} + +/* + For debug: + SAPI_VLOG_LEVEL=1 ./pffft_sandboxed --v=100 --sandbox2_danger_danger_permit_all_and_log my_aux_file +*/ + +int main(int argc, char* argv[]) { + gflags::ParseCommandLineFlags(&argc, &argv, true); + int Nvalues[] = { 64, 96, 128, 160, 192, 256, 384, 5*96, 512, 5*128, 3*256, 800, 1024, 2048, 2400, 4096, 8192, 9*1024, 16384, 32768, 256*1024, 1024*1024, -1 }; + int i; + + printf("initializing sandbox...\n"); + + pffftSapiSandbox sandbox; + sandbox.Init().IgnoreError(); + + pffftApi api(&sandbox); + + int N, cplx; + + cplx = 0; + + for (i = 0; i < 5; i++) { + N = Nvalues[i]; + + int Nfloat = N * (cplx ? 2 : 1); + int Nbytes = Nfloat * sizeof(float); + int pass; + + float ref[Nbytes], in[Nbytes], out[Nbytes], tmp[Nbytes], tmp2[Nbytes]; + + sapi::v::Array ref_(ref, Nbytes); + sapi::v::Array in_(in, Nbytes); + sapi::v::Array out_(out, Nbytes); + sapi::v::Array tmp_(tmp, Nbytes); + sapi::v::Array tmp2_(tmp2, Nbytes); + + float wrk[2 * Nbytes + 15 * sizeof(float)]; + sapi::v::Array wrk_(wrk, 2 * Nbytes + 15 * sizeof(float)); + + float ref_max = 0; + int k; + + Nfloat = (cplx ? N * 2 : N); + float X[Nbytes], Y[Nbytes], Z[Nbytes]; + sapi::v::Array X_(X, Nbytes), Y_(Y, Nbytes), Z_(Z, Nbytes); + + double t0, t1, flops; + + int max_iter = 5120000/N*4; + #ifdef __arm__ + max_iter /= 4; + #endif + int iter; + + for (k = 0; k < Nfloat; ++k) { + X[k] = 0; + } + + // FFTPack benchmark + { + int max_iter_ = max_iter/4; // SIMD_SZ == 4 (returning value of pffft_simd_size()) + if (max_iter_ == 0) max_iter_ = 1; + if (cplx) { + api.cffti(N, wrk_.PtrBoth()).IgnoreError(); + } else { + api.rffti(N, wrk_.PtrBoth()).IgnoreError(); + } + t0 = uclock_sec(); + + for (iter = 0; iter < max_iter_; ++iter) { + if (cplx) { + api.cfftf(N, X_.PtrBoth(), wrk_.PtrBoth()).IgnoreError(); + api.cfftb(N, X_.PtrBoth(), wrk_.PtrBoth()).IgnoreError(); + } else { + api.rfftf(N, X_.PtrBoth(), wrk_.PtrBoth()).IgnoreError(); + api.rfftb(N, X_.PtrBoth(), wrk_.PtrBoth()).IgnoreError(); + } + } + t1 = uclock_sec(); + + flops = (max_iter_ * 2) * ((cplx ? 5 : 2.5) * N * log((double)N) / M_LN2); + show_output("FFTPack", N, cplx, flops, t0, t1, max_iter_); + + } + } + + return 0; +} \ No newline at end of file From deb8869bb660a6f2cae4f44dd14b25a9c746debe Mon Sep 17 00:00:00 2001 From: doinachiroiu Date: Mon, 17 Aug 2020 11:21:11 +0000 Subject: [PATCH 17/82] Added .gitignore --- oss-internship-2020/pffft/.gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 oss-internship-2020/pffft/.gitignore diff --git a/oss-internship-2020/pffft/.gitignore b/oss-internship-2020/pffft/.gitignore new file mode 100644 index 0000000..f9ed608 --- /dev/null +++ b/oss-internship-2020/pffft/.gitignore @@ -0,0 +1 @@ +.hg/ From 50921e222d27198717c3ae174fe93299306ae01b Mon Sep 17 00:00:00 2001 From: doinachiroiu Date: Mon, 17 Aug 2020 11:21:19 +0000 Subject: [PATCH 18/82] Coding style adaptation --- .../pffft/test_pffft_sandboxed.cc | 82 ++++++++++--------- 1 file changed, 43 insertions(+), 39 deletions(-) diff --git a/oss-internship-2020/pffft/test_pffft_sandboxed.cc b/oss-internship-2020/pffft/test_pffft_sandboxed.cc index dd6dc0b..d50dff3 100644 --- a/oss-internship-2020/pffft/test_pffft_sandboxed.cc +++ b/oss-internship-2020/pffft/test_pffft_sandboxed.cc @@ -1,14 +1,13 @@ -#include "fftpack.h" - +#include #include #include #include -#include -#include #include -#include #include +#include +#include +#include "fftpack.h" #include "pffft_sapi.sapi.h" #include "sandboxed_api/util/flag.h" #include "sandboxed_api/vars.h" @@ -17,9 +16,10 @@ ABSL_DECLARE_FLAG(string, sandbox2_danger_danger_permit_all); ABSL_DECLARE_FLAG(string, sandbox2_danger_danger_permit_all_and_log); class pffftSapiSandbox : public pffftSandbox { - public: - std::unique_ptr ModifyPolicy(sandbox2::PolicyBuilder*) override { - return sandbox2::PolicyBuilder() + public: + std::unique_ptr ModifyPolicy( + sandbox2::PolicyBuilder*) override { + return sandbox2::PolicyBuilder() .AllowStaticStartup() .AllowOpen() .AllowRead() @@ -27,47 +27,51 @@ class pffftSapiSandbox : public pffftSandbox { .AllowSystemMalloc() .AllowExit() .AllowSyscalls({ - __NR_futex, - __NR_close, - __NR_getrusage, + __NR_futex, + __NR_close, + __NR_getrusage, }) .DisableNamespaces() .BuildOrDie(); - } + } }; -double frand() { - return rand()/(double)RAND_MAX; -} +double frand() { return rand() / (double)RAND_MAX; } -double uclock_sec(void) { - return (double)clock()/(double)CLOCKS_PER_SEC; -} +double uclock_sec(void) { return (double)clock() / (double)CLOCKS_PER_SEC; } int array_output_format = 0; -void show_output(const char *name, int N, int cplx, float flops, float t0, float t1, int max_iter) { - float mflops = flops/1e6/(t1 - t0 + 1e-16); +void show_output(const char* name, int N, int cplx, float flops, float t0, + float t1, int max_iter) { + float mflops = flops / 1e6 / (t1 - t0 + 1e-16); if (array_output_format) { if (flops != -1) { printf("|%9.0f ", mflops); - } else printf("| n/a "); + } else + printf("| n/a "); } else { if (flops != -1) { - printf("N=%5d, %s %16s : %6.0f MFlops [t=%6.0f ns, %d runs]\n", N, (cplx?"CPLX":"REAL"), name, mflops, (t1-t0)/2/max_iter * 1e9, max_iter); + printf("N=%5d, %s %16s : %6.0f MFlops [t=%6.0f ns, %d runs]\n", N, + (cplx ? "CPLX" : "REAL"), name, mflops, + (t1 - t0) / 2 / max_iter * 1e9, max_iter); } } fflush(stdout); } -/* +/* For debug: - SAPI_VLOG_LEVEL=1 ./pffft_sandboxed --v=100 --sandbox2_danger_danger_permit_all_and_log my_aux_file + SAPI_VLOG_LEVEL=1 ./pffft_sandboxed --v=100 + --sandbox2_danger_danger_permit_all_and_log my_aux_file */ int main(int argc, char* argv[]) { gflags::ParseCommandLineFlags(&argc, &argv, true); - int Nvalues[] = { 64, 96, 128, 160, 192, 256, 384, 5*96, 512, 5*128, 3*256, 800, 1024, 2048, 2400, 4096, 8192, 9*1024, 16384, 32768, 256*1024, 1024*1024, -1 }; + int Nvalues[] = {64, 96, 128, 160, 192, 256, + 384, 5 * 96, 512, 5 * 128, 3 * 256, 800, + 1024, 2048, 2400, 4096, 8192, 9 * 1024, + 16384, 32768, 256 * 1024, 1024 * 1024, -1}; int i; printf("initializing sandbox...\n"); @@ -76,14 +80,14 @@ int main(int argc, char* argv[]) { sandbox.Init().IgnoreError(); pffftApi api(&sandbox); - + int N, cplx; cplx = 0; for (i = 0; i < 5; i++) { N = Nvalues[i]; - + int Nfloat = N * (cplx ? 2 : 1); int Nbytes = Nfloat * sizeof(float); int pass; @@ -108,27 +112,28 @@ int main(int argc, char* argv[]) { double t0, t1, flops; - int max_iter = 5120000/N*4; - #ifdef __arm__ - max_iter /= 4; - #endif + int max_iter = 5120000 / N * 4; +#ifdef __arm__ + max_iter /= 4; +#endif int iter; for (k = 0; k < Nfloat; ++k) { - X[k] = 0; + X[k] = 0; } // FFTPack benchmark { - int max_iter_ = max_iter/4; // SIMD_SZ == 4 (returning value of pffft_simd_size()) + int max_iter_ = + max_iter / 4; // SIMD_SZ == 4 (returning value of pffft_simd_size()) if (max_iter_ == 0) max_iter_ = 1; if (cplx) { api.cffti(N, wrk_.PtrBoth()).IgnoreError(); } else { api.rffti(N, wrk_.PtrBoth()).IgnoreError(); } - t0 = uclock_sec(); - + t0 = uclock_sec(); + for (iter = 0; iter < max_iter_; ++iter) { if (cplx) { api.cfftf(N, X_.PtrBoth(), wrk_.PtrBoth()).IgnoreError(); @@ -139,10 +144,9 @@ int main(int argc, char* argv[]) { } } t1 = uclock_sec(); - - flops = (max_iter_ * 2) * ((cplx ? 5 : 2.5) * N * log((double)N) / M_LN2); - show_output("FFTPack", N, cplx, flops, t0, t1, max_iter_); - + + flops = (max_iter_ * 2) * ((cplx ? 5 : 2.5) * N * log((double)N) / M_LN2); + show_output("FFTPack", N, cplx, flops, t0, t1, max_iter_); } } From 2ab097f82fe012956e0e5299bd76a5db19530788 Mon Sep 17 00:00:00 2001 From: doinachiroiu Date: Mon, 17 Aug 2020 11:21:27 +0000 Subject: [PATCH 19/82] Added README --- oss-internship-2020/pffft/README.txt | 425 +-------------------- oss-internship-2020/pffft/README_pffft.txt | 416 ++++++++++++++++++++ 2 files changed, 430 insertions(+), 411 deletions(-) create mode 100644 oss-internship-2020/pffft/README_pffft.txt diff --git a/oss-internship-2020/pffft/README.txt b/oss-internship-2020/pffft/README.txt index ee20b42..ef51f1a 100644 --- a/oss-internship-2020/pffft/README.txt +++ b/oss-internship-2020/pffft/README.txt @@ -1,416 +1,19 @@ -PFFFT: a pretty fast FFT. +Sandboxing PFFFT library -TL;DR --- +Builder: CMake -PFFFT does 1D Fast Fourier Transforms, of single precision real and -complex vectors. It tries do it fast, it tries to be correct, and it -tries to be small. Computations do take advantage of SSE1 instructions -on x86 cpus, Altivec on powerpc cpus, and NEON on ARM cpus. The -license is BSD-like. +For testing: +`cd build`, then `./pffft_sandboxed` +For debug: +`SAPI_VLOG_LEVEL=1 ./pffft_sandboxed --v=100 +--sandbox2_danger_danger_permit_all_and_log ` -Why does it exist: --- - -I was in search of a good performing FFT library , preferably very -small and with a very liberal license. - -When one says "fft library", FFTW ("Fastest Fourier Transform in the -West") is probably the first name that comes to mind -- I guess that -99% of open-source projects that need a FFT do use FFTW, and are happy -with it. However, it is quite a large library , which does everything -fft related (2d transforms, 3d transforms, other transformations such -as discrete cosine , or fast hartley). And it is licensed under the -GNU GPL , which means that it cannot be used in non open-source -products. - -An alternative to FFTW that is really small, is the venerable FFTPACK -v4, which is available on NETLIB. A more recent version (v5) exists, -but it is larger as it deals with multi-dimensional transforms. This -is a library that is written in FORTRAN 77, a language that is now -considered as a bit antiquated by many. FFTPACKv4 was written in 1985, -by Dr Paul Swarztrauber of NCAR, more than 25 years ago ! And despite -its age, benchmarks show it that it still a very good performing FFT -library, see for example the 1d single precision benchmarks here: -http://www.fftw.org/speed/opteron-2.2GHz-32bit/ . It is however not -competitive with the fastest ones, such as FFTW, Intel MKL, AMD ACML, -Apple vDSP. The reason for that is that those libraries do take -advantage of the SSE SIMD instructions available on Intel CPUs, -available since the days of the Pentium III. These instructions deal -with small vectors of 4 floats at a time, instead of a single float -for a traditionnal FPU, so when using these instructions one may expect -a 4-fold performance improvement. - -The idea was to take this fortran fftpack v4 code, translate to C, -modify it to deal with those SSE instructions, and check that the -final performance is not completely ridiculous when compared to other -SIMD FFT libraries. Translation to C was performed with f2c ( -http://www.netlib.org/f2c/ ). The resulting file was a bit edited in -order to remove the thousands of gotos that were introduced by -f2c. You will find the fftpack.h and fftpack.c sources in the -repository, this a complete translation of -http://www.netlib.org/fftpack/ , with the discrete cosine transform -and the test program. There is no license information in the netlib -repository, but it was confirmed to me by the fftpack v5 curators that -the same terms do apply to fftpack v4: -http://www.cisl.ucar.edu/css/software/fftpack5/ftpk.html . This is a -"BSD-like" license, it is compatible with proprietary projects. - -Adapting fftpack to deal with the SIMD 4-element vectors instead of -scalar single precision numbers was more complex than I originally -thought, especially with the real transforms, and I ended up writing -more code than I planned.. - - -The code: --- - -Only two files, in good old C, pffft.c and pffft.h . The API is very -very simple, just make sure that you read the comments in pffft.h. - - -Comparison with other FFTs: --- - -The idea was not to break speed records, but to get a decently fast -fft that is at least 50% as fast as the fastest FFT -- especially on -slowest computers . I'm more focused on getting the best performance -on slow cpus (Atom, Intel Core 1, old Athlons, ARM Cortex-A9...), than -on getting top performance on today fastest cpus. - -It can be used in a real-time context as the fft functions do not -perform any memory allocation -- that is why they accept a 'work' -array in their arguments. - -It is also a bit focused on performing 1D convolutions, that is why it -provides "unordered" FFTs , and a fourier domain convolution -operation. - - -Benchmark results (cpu tested: core i7 2600, core 2 quad, core 1 duo, atom N270, cortex-A9, cortex-A15, A8X) --- - -The benchmark shows the performance of various fft implementations measured in -MFlops, with the number of floating point operations being defined as 5Nlog2(N) -for a length N complex fft, and 2.5*Nlog2(N) for a real fft. -See http://www.fftw.org/speed/method.html for an explanation of these formulas. - -MacOS Lion, gcc 4.2, 64-bit, fftw 3.3 on a 3.4 GHz core i7 2600 - -Built with: - - gcc-4.2 -o test_pffft -arch x86_64 -O3 -Wall -W pffft.c test_pffft.c fftpack.c -L/usr/local/lib -I/usr/local/include/ -DHAVE_VECLIB -framework veclib -DHAVE_FFTW -lfftw3f - -| input len |real FFTPack| real vDSP | real FFTW | real PFFFT | |cplx FFTPack| cplx vDSP | cplx FFTW | cplx PFFFT | -|-----------+------------+------------+------------+------------| |------------+------------+------------+------------| -| 64 | 2816 | 8596 | 7329 | 8187 | | 2887 | 14898 | 14668 | 11108 | -| 96 | 3298 | n/a | 8378 | 7727 | | 3953 | n/a | 15680 | 10878 | -| 128 | 3507 | 11575 | 9266 | 10108 | | 4233 | 17598 | 16427 | 12000 | -| 160 | 3391 | n/a | 9838 | 10711 | | 4220 | n/a | 16653 | 11187 | -| 192 | 3919 | n/a | 9868 | 10956 | | 4297 | n/a | 15770 | 12540 | -| 256 | 4283 | 13179 | 10694 | 13128 | | 4545 | 19550 | 16350 | 13822 | -| 384 | 3136 | n/a | 10810 | 12061 | | 3600 | n/a | 16103 | 13240 | -| 480 | 3477 | n/a | 10632 | 12074 | | 3536 | n/a | 11630 | 12522 | -| 512 | 3783 | 15141 | 11267 | 13838 | | 3649 | 20002 | 16560 | 13580 | -| 640 | 3639 | n/a | 11164 | 13946 | | 3695 | n/a | 15416 | 13890 | -| 768 | 3800 | n/a | 11245 | 13495 | | 3590 | n/a | 15802 | 14552 | -| 800 | 3440 | n/a | 10499 | 13301 | | 3659 | n/a | 12056 | 13268 | -| 1024 | 3924 | 15605 | 11450 | 15339 | | 3769 | 20963 | 13941 | 15467 | -| 2048 | 4518 | 16195 | 11551 | 15532 | | 4258 | 20413 | 13723 | 15042 | -| 2400 | 4294 | n/a | 10685 | 13078 | | 4093 | n/a | 12777 | 13119 | -| 4096 | 4750 | 16596 | 11672 | 15817 | | 4157 | 19662 | 14316 | 14336 | -| 8192 | 3820 | 16227 | 11084 | 12555 | | 3691 | 18132 | 12102 | 13813 | -| 9216 | 3864 | n/a | 10254 | 12870 | | 3586 | n/a | 12119 | 13994 | -| 16384 | 3822 | 15123 | 10454 | 12822 | | 3613 | 16874 | 12370 | 13881 | -| 32768 | 4175 | 14512 | 10662 | 11095 | | 3881 | 14702 | 11619 | 11524 | -| 262144 | 3317 | 11429 | 6269 | 9517 | | 2810 | 11729 | 7757 | 10179 | -| 1048576 | 2913 | 10551 | 4730 | 5867 | | 2661 | 7881 | 3520 | 5350 | -|-----------+------------+------------+------------+------------| |------------+------------+------------+------------| - - -Debian 6, gcc 4.4.5, 64-bit, fftw 3.3.1 on a 3.4 GHz core i7 2600 - -Built with: -gcc -o test_pffft -DHAVE_FFTW -msse2 -O3 -Wall -W pffft.c test_pffft.c fftpack.c -L$HOME/local/lib -I$HOME/local/include/ -lfftw3f -lm - -| N (input length) | real FFTPack | real FFTW | real PFFFT | | cplx FFTPack | cplx FFTW | cplx PFFFT | -|------------------+--------------+--------------+--------------| |--------------+--------------+--------------| -| 64 | 3840 | 7680 | 8777 | | 4389 | 20480 | 11171 | -| 96 | 4214 | 9633 | 8429 | | 4816 | 22477 | 11238 | -| 128 | 3584 | 10240 | 10240 | | 5120 | 23893 | 11947 | -| 192 | 4854 | 11095 | 12945 | | 4854 | 22191 | 14121 | -| 256 | 4096 | 11703 | 16384 | | 5120 | 23406 | 13653 | -| 384 | 4395 | 14651 | 12558 | | 4884 | 19535 | 14651 | -| 512 | 5760 | 13166 | 15360 | | 4608 | 23040 | 15360 | -| 768 | 4907 | 14020 | 16357 | | 4461 | 19628 | 14020 | -| 1024 | 5120 | 14629 | 14629 | | 5120 | 20480 | 15754 | -| 2048 | 5632 | 14080 | 18773 | | 4693 | 12516 | 16091 | -| 4096 | 5120 | 13653 | 17554 | | 4726 | 7680 | 14456 | -| 8192 | 4160 | 7396 | 13312 | | 4437 | 14791 | 13312 | -| 9216 | 4210 | 6124 | 13473 | | 4491 | 7282 | 14970 | -| 16384 | 3976 | 11010 | 14313 | | 4210 | 11450 | 13631 | -| 32768 | 4260 | 10224 | 10954 | | 4260 | 6816 | 11797 | -| 262144 | 3736 | 6896 | 9961 | | 2359 | 8965 | 9437 | -| 1048576 | 2796 | 4534 | 6453 | | 1864 | 3078 | 5592 | -|------------------+--------------+--------------+--------------| |--------------+--------------+--------------| - - - -MacOS Snow Leopard, gcc 4.0, 32-bit, fftw 3.3 on a 1.83 GHz core 1 duo - -Built with: - - gcc -o test_pffft -DHAVE_FFTW -DHAVE_VECLIB -O3 -Wall -W pffft.c test_pffft.c fftpack.c -L/usr/local/lib -I/usr/local/include/ -lfftw3f -framework veclib - -| input len |real FFTPack| real vDSP | real FFTW | real PFFFT | |cplx FFTPack| cplx vDSP | cplx FFTW | cplx PFFFT | -|-----------+------------+------------+------------+------------| |------------+------------+------------+------------| -| 64 | 745 | 2145 | 1706 | 2028 | | 961 | 3356 | 3313 | 2300 | -| 96 | 877 | n/a | 1976 | 1978 | | 1059 | n/a | 3333 | 2233 | -| 128 | 951 | 2808 | 2213 | 2279 | | 1202 | 3803 | 3739 | 2494 | -| 192 | 1002 | n/a | 2456 | 2429 | | 1186 | n/a | 3701 | 2508 | -| 256 | 1065 | 3205 | 2641 | 2793 | | 1302 | 4013 | 3912 | 2663 | -| 384 | 845 | n/a | 2759 | 2499 | | 948 | n/a | 3729 | 2504 | -| 512 | 900 | 3476 | 2956 | 2759 | | 974 | 4057 | 3954 | 2645 | -| 768 | 910 | n/a | 2912 | 2737 | | 975 | n/a | 3837 | 2614 | -| 1024 | 936 | 3583 | 3107 | 3009 | | 1006 | 4124 | 3821 | 2697 | -| 2048 | 1057 | 3585 | 3091 | 2837 | | 1089 | 3889 | 3701 | 2513 | -| 4096 | 1083 | 3524 | 3092 | 2733 | | 1039 | 3617 | 3462 | 2364 | -| 8192 | 874 | 3252 | 2967 | 2363 | | 911 | 3106 | 2789 | 2302 | -| 9216 | 898 | n/a | 2420 | 2290 | | 865 | n/a | 2676 | 2204 | -| 16384 | 903 | 2892 | 2506 | 2421 | | 899 | 3026 | 2797 | 2289 | -| 32768 | 965 | 2837 | 2550 | 2358 | | 920 | 2922 | 2763 | 2240 | -| 262144 | 738 | 2422 | 1589 | 1708 | | 610 | 2038 | 1436 | 1091 | -| 1048576 | 528 | 1207 | 845 | 880 | | 606 | 1020 | 669 | 1036 | -|-----------+------------+------------+------------+------------| |------------+------------+------------+------------| - - - -Ubuntu 11.04, gcc 4.5, 32-bit, fftw 3.2 on a 2.66 core 2 quad - -Built with: -gcc -o test_pffft -DHAVE_FFTW -msse -mfpmath=sse -O3 -Wall -W pffft.c test_pffft.c fftpack.c -L/usr/local/lib -I/usr/local/include/ -lfftw3f -lm - -| input len |real FFTPack| real FFTW | real PFFFT | |cplx FFTPack| cplx FFTW | cplx PFFFT | -|-----------+------------+------------+------------| |------------+------------+------------| -| 64 | 1920 | 3614 | 5120 | | 2194 | 7680 | 6467 | -| 96 | 1873 | 3549 | 5187 | | 2107 | 8429 | 5863 | -| 128 | 2240 | 3773 | 5514 | | 2560 | 7964 | 6827 | -| 192 | 1765 | 4569 | 7767 | | 2284 | 9137 | 7061 | -| 256 | 2048 | 5461 | 7447 | | 2731 | 9638 | 7802 | -| 384 | 1998 | 5861 | 6762 | | 2313 | 9253 | 7644 | -| 512 | 2095 | 6144 | 7680 | | 2194 | 10240 | 7089 | -| 768 | 2230 | 5773 | 7549 | | 2045 | 10331 | 7010 | -| 1024 | 2133 | 6400 | 8533 | | 2133 | 10779 | 7877 | -| 2048 | 2011 | 7040 | 8665 | | 1942 | 10240 | 7768 | -| 4096 | 2194 | 6827 | 8777 | | 1755 | 9452 | 6827 | -| 8192 | 1849 | 6656 | 6656 | | 1752 | 7831 | 6827 | -| 9216 | 1871 | 5858 | 6416 | | 1643 | 6909 | 6266 | -| 16384 | 1883 | 6223 | 6506 | | 1664 | 7340 | 6982 | -| 32768 | 1826 | 6390 | 6667 | | 1631 | 7481 | 6971 | -| 262144 | 1546 | 4075 | 5977 | | 1299 | 3415 | 3551 | -| 1048576 | 1104 | 2071 | 1730 | | 1104 | 1149 | 1834 | -|-----------+------------+------------+------------| |------------+------------+------------| - - - -Ubuntu 11.04, gcc 4.5, 32-bit, fftw 3.3 on a 1.6 GHz Atom N270 - -Built with: -gcc -o test_pffft -DHAVE_FFTW -msse -mfpmath=sse -O3 -Wall -W pffft.c test_pffft.c fftpack.c -L/usr/local/lib -I/usr/local/include/ -lfftw3f -lm - -| N (input length) | real FFTPack | real FFTW | real PFFFT | | cplx FFTPack | cplx FFTW | cplx PFFFT | -|------------------+--------------+--------------+--------------| |--------------+--------------+--------------| -| 64 | 452 | 1041 | 1336 | | 549 | 2318 | 1781 | -| 96 | 444 | 1297 | 1297 | | 503 | 2408 | 1686 | -| 128 | 527 | 1525 | 1707 | | 543 | 2655 | 1886 | -| 192 | 498 | 1653 | 1849 | | 539 | 2678 | 1942 | -| 256 | 585 | 1862 | 2156 | | 594 | 2777 | 2244 | -| 384 | 499 | 1870 | 1998 | | 511 | 2586 | 1890 | -| 512 | 562 | 2095 | 2194 | | 542 | 2973 | 2194 | -| 768 | 545 | 2045 | 2133 | | 545 | 2365 | 2133 | -| 1024 | 595 | 2133 | 2438 | | 569 | 2695 | 2179 | -| 2048 | 587 | 2125 | 2347 | | 521 | 2230 | 1707 | -| 4096 | 495 | 1890 | 1834 | | 492 | 1876 | 1672 | -| 8192 | 469 | 1548 | 1729 | | 438 | 1740 | 1664 | -| 9216 | 468 | 1663 | 1663 | | 446 | 1585 | 1531 | -| 16384 | 453 | 1608 | 1767 | | 398 | 1476 | 1664 | -| 32768 | 456 | 1420 | 1503 | | 387 | 1388 | 1345 | -| 262144 | 309 | 385 | 726 | | 262 | 415 | 840 | -| 1048576 | 280 | 351 | 739 | | 261 | 313 | 797 | -|------------------+--------------+--------------+--------------| |--------------+--------------+--------------| - - - -Windows 7, visual c++ 2010 on a 1.6 GHz Atom N270 - -Built with: -cl /Ox -D_USE_MATH_DEFINES /arch:SSE test_pffft.c pffft.c fftpack.c - -(visual c++ is definitively not very good with SSE intrinsics...) - -| N (input length) | real FFTPack | real PFFFT | | cplx FFTPack | cplx PFFFT | -|------------------+--------------+--------------| |--------------+--------------| -| 64 | 173 | 1009 | | 174 | 1159 | -| 96 | 169 | 1029 | | 188 | 1201 | -| 128 | 195 | 1242 | | 191 | 1275 | -| 192 | 178 | 1312 | | 184 | 1276 | -| 256 | 196 | 1591 | | 186 | 1281 | -| 384 | 172 | 1409 | | 181 | 1281 | -| 512 | 187 | 1640 | | 181 | 1313 | -| 768 | 171 | 1614 | | 176 | 1258 | -| 1024 | 186 | 1812 | | 178 | 1223 | -| 2048 | 190 | 1707 | | 186 | 1099 | -| 4096 | 182 | 1446 | | 177 | 975 | -| 8192 | 175 | 1345 | | 169 | 1034 | -| 9216 | 165 | 1271 | | 168 | 1023 | -| 16384 | 166 | 1396 | | 165 | 949 | -| 32768 | 172 | 1311 | | 161 | 881 | -| 262144 | 136 | 632 | | 134 | 629 | -| 1048576 | 134 | 698 | | 127 | 623 | -|------------------+--------------+--------------| |--------------+--------------| - - - -Ubuntu 12.04, gcc-4.7.3, 32-bit, with fftw 3.3.3 (built with --enable-neon), on a 1.2GHz ARM Cortex A9 (Tegra 3) - -Built with: -gcc-4.7 -O3 -DHAVE_FFTW -march=armv7-a -mtune=cortex-a9 -mfloat-abi=hard -mfpu=neon -ffast-math test_pffft.c pffft.c -o test_pffft_arm fftpack.c -lm -I/usr/local/include/ -L/usr/local/lib/ -lfftw3f - -| input len |real FFTPack| real FFTW | real PFFFT | |cplx FFTPack| cplx FFTW | cplx PFFFT | -|-----------+------------+------------+------------| |------------+------------+------------| -| 64 | 549 | 452 | 731 | | 512 | 602 | 640 | -| 96 | 421 | 272 | 702 | | 496 | 571 | 602 | -| 128 | 498 | 512 | 815 | | 597 | 618 | 652 | -| 160 | 521 | 536 | 815 | | 586 | 669 | 625 | -| 192 | 539 | 571 | 883 | | 485 | 597 | 626 | -| 256 | 640 | 539 | 975 | | 569 | 611 | 671 | -| 384 | 499 | 610 | 879 | | 499 | 602 | 637 | -| 480 | 518 | 507 | 877 | | 496 | 661 | 616 | -| 512 | 524 | 591 | 1002 | | 549 | 678 | 668 | -| 640 | 542 | 612 | 955 | | 568 | 663 | 645 | -| 768 | 557 | 613 | 981 | | 491 | 663 | 598 | -| 800 | 514 | 353 | 882 | | 514 | 360 | 574 | -| 1024 | 640 | 640 | 1067 | | 492 | 683 | 602 | -| 2048 | 587 | 640 | 908 | | 486 | 640 | 552 | -| 2400 | 479 | 368 | 777 | | 422 | 376 | 518 | -| 4096 | 511 | 614 | 853 | | 426 | 640 | 534 | -| 8192 | 415 | 584 | 708 | | 386 | 622 | 516 | -| 9216 | 419 | 571 | 687 | | 364 | 586 | 506 | -| 16384 | 426 | 577 | 716 | | 398 | 606 | 530 | -| 32768 | 417 | 572 | 673 | | 399 | 572 | 468 | -| 262144 | 219 | 380 | 293 | | 255 | 431 | 343 | -| 1048576 | 202 | 274 | 237 | | 265 | 282 | 355 | -|-----------+------------+------------+------------| |------------+------------+------------| - -Same platform as above, but this time pffft and fftpack are built with clang 3.2: - -clang -O3 -DHAVE_FFTW -march=armv7-a -mtune=cortex-a9 -mfloat-abi=hard -mfpu=neon -ffast-math test_pffft.c pffft.c -o test_pffft_arm fftpack.c -lm -I/usr/local/include/ -L/usr/local/lib/ -lfftw3f - -| input len |real FFTPack| real FFTW | real PFFFT | |cplx FFTPack| cplx FFTW | cplx PFFFT | -|-----------+------------+------------+------------| |------------+------------+------------| -| 64 | 427 | 452 | 853 | | 427 | 602 | 1024 | -| 96 | 351 | 276 | 843 | | 337 | 571 | 963 | -| 128 | 373 | 512 | 996 | | 390 | 618 | 1054 | -| 160 | 426 | 536 | 987 | | 375 | 669 | 914 | -| 192 | 404 | 571 | 1079 | | 388 | 588 | 1079 | -| 256 | 465 | 539 | 1205 | | 445 | 602 | 1170 | -| 384 | 366 | 610 | 1099 | | 343 | 594 | 1099 | -| 480 | 356 | 507 | 1140 | | 335 | 651 | 931 | -| 512 | 411 | 591 | 1213 | | 384 | 649 | 1124 | -| 640 | 398 | 612 | 1193 | | 373 | 654 | 901 | -| 768 | 409 | 613 | 1227 | | 383 | 663 | 1044 | -| 800 | 411 | 348 | 1073 | | 353 | 358 | 809 | -| 1024 | 427 | 640 | 1280 | | 413 | 692 | 1004 | -| 2048 | 414 | 626 | 1126 | | 371 | 640 | 853 | -| 2400 | 399 | 373 | 898 | | 319 | 368 | 653 | -| 4096 | 404 | 602 | 1059 | | 357 | 633 | 778 | -| 8192 | 332 | 584 | 792 | | 308 | 616 | 716 | -| 9216 | 322 | 561 | 783 | | 299 | 586 | 687 | -| 16384 | 344 | 568 | 778 | | 314 | 617 | 745 | -| 32768 | 342 | 564 | 737 | | 314 | 552 | 629 | -| 262144 | 201 | 383 | 313 | | 227 | 435 | 413 | -| 1048576 | 187 | 262 | 251 | | 228 | 281 | 409 | -|-----------+------------+------------+------------| |------------+------------+------------| - -So it looks like, on ARM, gcc 4.7 is the best at scalar floating point -(the fftpack performance numbers are better with gcc), while clang is -the best with neon intrinsics (see how pffft perf has improved with -clang 3.2). - - -NVIDIA Jetson TK1 board, gcc-4.8.2. The cpu is a 2.3GHz cortex A15 (Tegra K1). - -Built with: -gcc -O3 -march=armv7-a -mtune=native -mfloat-abi=hard -mfpu=neon -ffast-math test_pffft.c pffft.c -o test_pffft_arm fftpack.c -lm - -| input len |real FFTPack| real PFFFT | |cplx FFTPack| cplx PFFFT | -|-----------+------------+------------| |------------+------------| -| 64 | 1735 | 3308 | | 1994 | 3744 | -| 96 | 1596 | 3448 | | 1987 | 3572 | -| 128 | 1807 | 4076 | | 2255 | 3960 | -| 160 | 1769 | 4083 | | 2071 | 3845 | -| 192 | 1990 | 4233 | | 2017 | 3939 | -| 256 | 2191 | 4882 | | 2254 | 4346 | -| 384 | 1878 | 4492 | | 2073 | 4012 | -| 480 | 1748 | 4398 | | 1923 | 3951 | -| 512 | 2030 | 5064 | | 2267 | 4195 | -| 640 | 1918 | 4756 | | 2094 | 4184 | -| 768 | 2099 | 4907 | | 2048 | 4297 | -| 800 | 1822 | 4555 | | 1880 | 4063 | -| 1024 | 2232 | 5355 | | 2187 | 4420 | -| 2048 | 2176 | 4983 | | 2027 | 3602 | -| 2400 | 1741 | 4256 | | 1710 | 3344 | -| 4096 | 1816 | 3914 | | 1851 | 3349 | -| 8192 | 1716 | 3481 | | 1700 | 3255 | -| 9216 | 1735 | 3589 | | 1653 | 3094 | -| 16384 | 1567 | 3483 | | 1637 | 3244 | -| 32768 | 1624 | 3240 | | 1655 | 3156 | -| 262144 | 1012 | 1898 | | 983 | 1503 | -| 1048576 | 876 | 1154 | | 868 | 1341 | -|-----------+------------+------------| |------------+------------| - -The performance on the tegra K1 is pretty impressive. I'm not -including the FFTW numbers as they as slightly below the scalar -fftpack numbers, so something must be wrong (however it seems to be -correctly configured and is using neon simd instructions). - -When using clang 3.4 the pffft version is even a bit faster, reaching -5.7 GFlops for real ffts of size 1024. - - -iPad Air 2 with iOS9, xcode 8.0, arm64. The cpu is an Apple A8X, supposedly running at 1.5GHz. - -| input len |real FFTPack| real vDSP | real PFFFT | |cplx FFTPack| cplx vDSP | cplx PFFFT | -|-----------+------------+------------+------------| |------------+------------+------------| -| 64 | 2517 | 7995 | 6086 | | 2725 | 13006 | 8495 | -| 96 | 2442 | n/a | 6691 | | 2256 | n/a | 7991 | -| 128 | 2664 | 10186 | 7877 | | 2575 | 15115 | 9115 | -| 160 | 2638 | n/a | 8283 | | 2682 | n/a | 8806 | -| 192 | 2903 | n/a | 9083 | | 2634 | n/a | 8980 | -| 256 | 3184 | 11452 | 10039 | | 3026 | 15410 | 10199 | -| 384 | 2665 | n/a | 10100 | | 2275 | n/a | 9247 | -| 480 | 2546 | n/a | 9863 | | 2341 | n/a | 8892 | -| 512 | 2832 | 12197 | 10989 | | 2547 | 16768 | 10154 | -| 640 | 2755 | n/a | 10461 | | 2569 | n/a | 9666 | -| 768 | 2998 | n/a | 11355 | | 2585 | n/a | 9813 | -| 800 | 2516 | n/a | 10332 | | 2433 | n/a | 9164 | -| 1024 | 3109 | 12965 | 12114 | | 2869 | 16448 | 10519 | -| 2048 | 3027 | 12996 | 12023 | | 2648 | 17304 | 10307 | -| 2400 | 2515 | n/a | 10372 | | 2355 | n/a | 8443 | -| 4096 | 3204 | 13603 | 12359 | | 2814 | 16570 | 9780 | -| 8192 | 2759 | 13422 | 10824 | | 2153 | 15652 | 7884 | -| 9216 | 2700 | n/a | 9938 | | 2241 | n/a | 7900 | -| 16384 | 2280 | 13057 | 7976 | | 593 | 4272 | 2534 | -| 32768 | 768 | 4269 | 2882 | | 606 | 4405 | 2604 | -| 262144 | 724 | 3527 | 2630 | | 534 | 2418 | 2157 | -| 1048576 | 674 | 1467 | 2135 | | 530 | 1621 | 2055 | -|-----------+------------+------------+------------| |------------+------------+------------| - -I double-checked to make sure I did not make a mistake in the time -measurements, as the numbers are much higher than what I initially -expected. They are in fact higher than the number I get on the 2.8GHz -Xeon of my 2008 mac pro.. (except for FFT lengths >= 32768 where -having a big cache is useful). A good surprise is also that the perf -is not too far from apple's vDSP (at least for the real FFT). +CMake observations: + * linking pffft and fftpack (which contains necessary functions for pffft) + * set math library +Sandboxed main observations: + * containing two testing parts (fft / pffft benchmarks) + ! current stage: fft - works :) + pffft - not implemented diff --git a/oss-internship-2020/pffft/README_pffft.txt b/oss-internship-2020/pffft/README_pffft.txt new file mode 100644 index 0000000..ee20b42 --- /dev/null +++ b/oss-internship-2020/pffft/README_pffft.txt @@ -0,0 +1,416 @@ +PFFFT: a pretty fast FFT. + +TL;DR +-- + +PFFFT does 1D Fast Fourier Transforms, of single precision real and +complex vectors. It tries do it fast, it tries to be correct, and it +tries to be small. Computations do take advantage of SSE1 instructions +on x86 cpus, Altivec on powerpc cpus, and NEON on ARM cpus. The +license is BSD-like. + + +Why does it exist: +-- + +I was in search of a good performing FFT library , preferably very +small and with a very liberal license. + +When one says "fft library", FFTW ("Fastest Fourier Transform in the +West") is probably the first name that comes to mind -- I guess that +99% of open-source projects that need a FFT do use FFTW, and are happy +with it. However, it is quite a large library , which does everything +fft related (2d transforms, 3d transforms, other transformations such +as discrete cosine , or fast hartley). And it is licensed under the +GNU GPL , which means that it cannot be used in non open-source +products. + +An alternative to FFTW that is really small, is the venerable FFTPACK +v4, which is available on NETLIB. A more recent version (v5) exists, +but it is larger as it deals with multi-dimensional transforms. This +is a library that is written in FORTRAN 77, a language that is now +considered as a bit antiquated by many. FFTPACKv4 was written in 1985, +by Dr Paul Swarztrauber of NCAR, more than 25 years ago ! And despite +its age, benchmarks show it that it still a very good performing FFT +library, see for example the 1d single precision benchmarks here: +http://www.fftw.org/speed/opteron-2.2GHz-32bit/ . It is however not +competitive with the fastest ones, such as FFTW, Intel MKL, AMD ACML, +Apple vDSP. The reason for that is that those libraries do take +advantage of the SSE SIMD instructions available on Intel CPUs, +available since the days of the Pentium III. These instructions deal +with small vectors of 4 floats at a time, instead of a single float +for a traditionnal FPU, so when using these instructions one may expect +a 4-fold performance improvement. + +The idea was to take this fortran fftpack v4 code, translate to C, +modify it to deal with those SSE instructions, and check that the +final performance is not completely ridiculous when compared to other +SIMD FFT libraries. Translation to C was performed with f2c ( +http://www.netlib.org/f2c/ ). The resulting file was a bit edited in +order to remove the thousands of gotos that were introduced by +f2c. You will find the fftpack.h and fftpack.c sources in the +repository, this a complete translation of +http://www.netlib.org/fftpack/ , with the discrete cosine transform +and the test program. There is no license information in the netlib +repository, but it was confirmed to me by the fftpack v5 curators that +the same terms do apply to fftpack v4: +http://www.cisl.ucar.edu/css/software/fftpack5/ftpk.html . This is a +"BSD-like" license, it is compatible with proprietary projects. + +Adapting fftpack to deal with the SIMD 4-element vectors instead of +scalar single precision numbers was more complex than I originally +thought, especially with the real transforms, and I ended up writing +more code than I planned.. + + +The code: +-- + +Only two files, in good old C, pffft.c and pffft.h . The API is very +very simple, just make sure that you read the comments in pffft.h. + + +Comparison with other FFTs: +-- + +The idea was not to break speed records, but to get a decently fast +fft that is at least 50% as fast as the fastest FFT -- especially on +slowest computers . I'm more focused on getting the best performance +on slow cpus (Atom, Intel Core 1, old Athlons, ARM Cortex-A9...), than +on getting top performance on today fastest cpus. + +It can be used in a real-time context as the fft functions do not +perform any memory allocation -- that is why they accept a 'work' +array in their arguments. + +It is also a bit focused on performing 1D convolutions, that is why it +provides "unordered" FFTs , and a fourier domain convolution +operation. + + +Benchmark results (cpu tested: core i7 2600, core 2 quad, core 1 duo, atom N270, cortex-A9, cortex-A15, A8X) +-- + +The benchmark shows the performance of various fft implementations measured in +MFlops, with the number of floating point operations being defined as 5Nlog2(N) +for a length N complex fft, and 2.5*Nlog2(N) for a real fft. +See http://www.fftw.org/speed/method.html for an explanation of these formulas. + +MacOS Lion, gcc 4.2, 64-bit, fftw 3.3 on a 3.4 GHz core i7 2600 + +Built with: + + gcc-4.2 -o test_pffft -arch x86_64 -O3 -Wall -W pffft.c test_pffft.c fftpack.c -L/usr/local/lib -I/usr/local/include/ -DHAVE_VECLIB -framework veclib -DHAVE_FFTW -lfftw3f + +| input len |real FFTPack| real vDSP | real FFTW | real PFFFT | |cplx FFTPack| cplx vDSP | cplx FFTW | cplx PFFFT | +|-----------+------------+------------+------------+------------| |------------+------------+------------+------------| +| 64 | 2816 | 8596 | 7329 | 8187 | | 2887 | 14898 | 14668 | 11108 | +| 96 | 3298 | n/a | 8378 | 7727 | | 3953 | n/a | 15680 | 10878 | +| 128 | 3507 | 11575 | 9266 | 10108 | | 4233 | 17598 | 16427 | 12000 | +| 160 | 3391 | n/a | 9838 | 10711 | | 4220 | n/a | 16653 | 11187 | +| 192 | 3919 | n/a | 9868 | 10956 | | 4297 | n/a | 15770 | 12540 | +| 256 | 4283 | 13179 | 10694 | 13128 | | 4545 | 19550 | 16350 | 13822 | +| 384 | 3136 | n/a | 10810 | 12061 | | 3600 | n/a | 16103 | 13240 | +| 480 | 3477 | n/a | 10632 | 12074 | | 3536 | n/a | 11630 | 12522 | +| 512 | 3783 | 15141 | 11267 | 13838 | | 3649 | 20002 | 16560 | 13580 | +| 640 | 3639 | n/a | 11164 | 13946 | | 3695 | n/a | 15416 | 13890 | +| 768 | 3800 | n/a | 11245 | 13495 | | 3590 | n/a | 15802 | 14552 | +| 800 | 3440 | n/a | 10499 | 13301 | | 3659 | n/a | 12056 | 13268 | +| 1024 | 3924 | 15605 | 11450 | 15339 | | 3769 | 20963 | 13941 | 15467 | +| 2048 | 4518 | 16195 | 11551 | 15532 | | 4258 | 20413 | 13723 | 15042 | +| 2400 | 4294 | n/a | 10685 | 13078 | | 4093 | n/a | 12777 | 13119 | +| 4096 | 4750 | 16596 | 11672 | 15817 | | 4157 | 19662 | 14316 | 14336 | +| 8192 | 3820 | 16227 | 11084 | 12555 | | 3691 | 18132 | 12102 | 13813 | +| 9216 | 3864 | n/a | 10254 | 12870 | | 3586 | n/a | 12119 | 13994 | +| 16384 | 3822 | 15123 | 10454 | 12822 | | 3613 | 16874 | 12370 | 13881 | +| 32768 | 4175 | 14512 | 10662 | 11095 | | 3881 | 14702 | 11619 | 11524 | +| 262144 | 3317 | 11429 | 6269 | 9517 | | 2810 | 11729 | 7757 | 10179 | +| 1048576 | 2913 | 10551 | 4730 | 5867 | | 2661 | 7881 | 3520 | 5350 | +|-----------+------------+------------+------------+------------| |------------+------------+------------+------------| + + +Debian 6, gcc 4.4.5, 64-bit, fftw 3.3.1 on a 3.4 GHz core i7 2600 + +Built with: +gcc -o test_pffft -DHAVE_FFTW -msse2 -O3 -Wall -W pffft.c test_pffft.c fftpack.c -L$HOME/local/lib -I$HOME/local/include/ -lfftw3f -lm + +| N (input length) | real FFTPack | real FFTW | real PFFFT | | cplx FFTPack | cplx FFTW | cplx PFFFT | +|------------------+--------------+--------------+--------------| |--------------+--------------+--------------| +| 64 | 3840 | 7680 | 8777 | | 4389 | 20480 | 11171 | +| 96 | 4214 | 9633 | 8429 | | 4816 | 22477 | 11238 | +| 128 | 3584 | 10240 | 10240 | | 5120 | 23893 | 11947 | +| 192 | 4854 | 11095 | 12945 | | 4854 | 22191 | 14121 | +| 256 | 4096 | 11703 | 16384 | | 5120 | 23406 | 13653 | +| 384 | 4395 | 14651 | 12558 | | 4884 | 19535 | 14651 | +| 512 | 5760 | 13166 | 15360 | | 4608 | 23040 | 15360 | +| 768 | 4907 | 14020 | 16357 | | 4461 | 19628 | 14020 | +| 1024 | 5120 | 14629 | 14629 | | 5120 | 20480 | 15754 | +| 2048 | 5632 | 14080 | 18773 | | 4693 | 12516 | 16091 | +| 4096 | 5120 | 13653 | 17554 | | 4726 | 7680 | 14456 | +| 8192 | 4160 | 7396 | 13312 | | 4437 | 14791 | 13312 | +| 9216 | 4210 | 6124 | 13473 | | 4491 | 7282 | 14970 | +| 16384 | 3976 | 11010 | 14313 | | 4210 | 11450 | 13631 | +| 32768 | 4260 | 10224 | 10954 | | 4260 | 6816 | 11797 | +| 262144 | 3736 | 6896 | 9961 | | 2359 | 8965 | 9437 | +| 1048576 | 2796 | 4534 | 6453 | | 1864 | 3078 | 5592 | +|------------------+--------------+--------------+--------------| |--------------+--------------+--------------| + + + +MacOS Snow Leopard, gcc 4.0, 32-bit, fftw 3.3 on a 1.83 GHz core 1 duo + +Built with: + + gcc -o test_pffft -DHAVE_FFTW -DHAVE_VECLIB -O3 -Wall -W pffft.c test_pffft.c fftpack.c -L/usr/local/lib -I/usr/local/include/ -lfftw3f -framework veclib + +| input len |real FFTPack| real vDSP | real FFTW | real PFFFT | |cplx FFTPack| cplx vDSP | cplx FFTW | cplx PFFFT | +|-----------+------------+------------+------------+------------| |------------+------------+------------+------------| +| 64 | 745 | 2145 | 1706 | 2028 | | 961 | 3356 | 3313 | 2300 | +| 96 | 877 | n/a | 1976 | 1978 | | 1059 | n/a | 3333 | 2233 | +| 128 | 951 | 2808 | 2213 | 2279 | | 1202 | 3803 | 3739 | 2494 | +| 192 | 1002 | n/a | 2456 | 2429 | | 1186 | n/a | 3701 | 2508 | +| 256 | 1065 | 3205 | 2641 | 2793 | | 1302 | 4013 | 3912 | 2663 | +| 384 | 845 | n/a | 2759 | 2499 | | 948 | n/a | 3729 | 2504 | +| 512 | 900 | 3476 | 2956 | 2759 | | 974 | 4057 | 3954 | 2645 | +| 768 | 910 | n/a | 2912 | 2737 | | 975 | n/a | 3837 | 2614 | +| 1024 | 936 | 3583 | 3107 | 3009 | | 1006 | 4124 | 3821 | 2697 | +| 2048 | 1057 | 3585 | 3091 | 2837 | | 1089 | 3889 | 3701 | 2513 | +| 4096 | 1083 | 3524 | 3092 | 2733 | | 1039 | 3617 | 3462 | 2364 | +| 8192 | 874 | 3252 | 2967 | 2363 | | 911 | 3106 | 2789 | 2302 | +| 9216 | 898 | n/a | 2420 | 2290 | | 865 | n/a | 2676 | 2204 | +| 16384 | 903 | 2892 | 2506 | 2421 | | 899 | 3026 | 2797 | 2289 | +| 32768 | 965 | 2837 | 2550 | 2358 | | 920 | 2922 | 2763 | 2240 | +| 262144 | 738 | 2422 | 1589 | 1708 | | 610 | 2038 | 1436 | 1091 | +| 1048576 | 528 | 1207 | 845 | 880 | | 606 | 1020 | 669 | 1036 | +|-----------+------------+------------+------------+------------| |------------+------------+------------+------------| + + + +Ubuntu 11.04, gcc 4.5, 32-bit, fftw 3.2 on a 2.66 core 2 quad + +Built with: +gcc -o test_pffft -DHAVE_FFTW -msse -mfpmath=sse -O3 -Wall -W pffft.c test_pffft.c fftpack.c -L/usr/local/lib -I/usr/local/include/ -lfftw3f -lm + +| input len |real FFTPack| real FFTW | real PFFFT | |cplx FFTPack| cplx FFTW | cplx PFFFT | +|-----------+------------+------------+------------| |------------+------------+------------| +| 64 | 1920 | 3614 | 5120 | | 2194 | 7680 | 6467 | +| 96 | 1873 | 3549 | 5187 | | 2107 | 8429 | 5863 | +| 128 | 2240 | 3773 | 5514 | | 2560 | 7964 | 6827 | +| 192 | 1765 | 4569 | 7767 | | 2284 | 9137 | 7061 | +| 256 | 2048 | 5461 | 7447 | | 2731 | 9638 | 7802 | +| 384 | 1998 | 5861 | 6762 | | 2313 | 9253 | 7644 | +| 512 | 2095 | 6144 | 7680 | | 2194 | 10240 | 7089 | +| 768 | 2230 | 5773 | 7549 | | 2045 | 10331 | 7010 | +| 1024 | 2133 | 6400 | 8533 | | 2133 | 10779 | 7877 | +| 2048 | 2011 | 7040 | 8665 | | 1942 | 10240 | 7768 | +| 4096 | 2194 | 6827 | 8777 | | 1755 | 9452 | 6827 | +| 8192 | 1849 | 6656 | 6656 | | 1752 | 7831 | 6827 | +| 9216 | 1871 | 5858 | 6416 | | 1643 | 6909 | 6266 | +| 16384 | 1883 | 6223 | 6506 | | 1664 | 7340 | 6982 | +| 32768 | 1826 | 6390 | 6667 | | 1631 | 7481 | 6971 | +| 262144 | 1546 | 4075 | 5977 | | 1299 | 3415 | 3551 | +| 1048576 | 1104 | 2071 | 1730 | | 1104 | 1149 | 1834 | +|-----------+------------+------------+------------| |------------+------------+------------| + + + +Ubuntu 11.04, gcc 4.5, 32-bit, fftw 3.3 on a 1.6 GHz Atom N270 + +Built with: +gcc -o test_pffft -DHAVE_FFTW -msse -mfpmath=sse -O3 -Wall -W pffft.c test_pffft.c fftpack.c -L/usr/local/lib -I/usr/local/include/ -lfftw3f -lm + +| N (input length) | real FFTPack | real FFTW | real PFFFT | | cplx FFTPack | cplx FFTW | cplx PFFFT | +|------------------+--------------+--------------+--------------| |--------------+--------------+--------------| +| 64 | 452 | 1041 | 1336 | | 549 | 2318 | 1781 | +| 96 | 444 | 1297 | 1297 | | 503 | 2408 | 1686 | +| 128 | 527 | 1525 | 1707 | | 543 | 2655 | 1886 | +| 192 | 498 | 1653 | 1849 | | 539 | 2678 | 1942 | +| 256 | 585 | 1862 | 2156 | | 594 | 2777 | 2244 | +| 384 | 499 | 1870 | 1998 | | 511 | 2586 | 1890 | +| 512 | 562 | 2095 | 2194 | | 542 | 2973 | 2194 | +| 768 | 545 | 2045 | 2133 | | 545 | 2365 | 2133 | +| 1024 | 595 | 2133 | 2438 | | 569 | 2695 | 2179 | +| 2048 | 587 | 2125 | 2347 | | 521 | 2230 | 1707 | +| 4096 | 495 | 1890 | 1834 | | 492 | 1876 | 1672 | +| 8192 | 469 | 1548 | 1729 | | 438 | 1740 | 1664 | +| 9216 | 468 | 1663 | 1663 | | 446 | 1585 | 1531 | +| 16384 | 453 | 1608 | 1767 | | 398 | 1476 | 1664 | +| 32768 | 456 | 1420 | 1503 | | 387 | 1388 | 1345 | +| 262144 | 309 | 385 | 726 | | 262 | 415 | 840 | +| 1048576 | 280 | 351 | 739 | | 261 | 313 | 797 | +|------------------+--------------+--------------+--------------| |--------------+--------------+--------------| + + + +Windows 7, visual c++ 2010 on a 1.6 GHz Atom N270 + +Built with: +cl /Ox -D_USE_MATH_DEFINES /arch:SSE test_pffft.c pffft.c fftpack.c + +(visual c++ is definitively not very good with SSE intrinsics...) + +| N (input length) | real FFTPack | real PFFFT | | cplx FFTPack | cplx PFFFT | +|------------------+--------------+--------------| |--------------+--------------| +| 64 | 173 | 1009 | | 174 | 1159 | +| 96 | 169 | 1029 | | 188 | 1201 | +| 128 | 195 | 1242 | | 191 | 1275 | +| 192 | 178 | 1312 | | 184 | 1276 | +| 256 | 196 | 1591 | | 186 | 1281 | +| 384 | 172 | 1409 | | 181 | 1281 | +| 512 | 187 | 1640 | | 181 | 1313 | +| 768 | 171 | 1614 | | 176 | 1258 | +| 1024 | 186 | 1812 | | 178 | 1223 | +| 2048 | 190 | 1707 | | 186 | 1099 | +| 4096 | 182 | 1446 | | 177 | 975 | +| 8192 | 175 | 1345 | | 169 | 1034 | +| 9216 | 165 | 1271 | | 168 | 1023 | +| 16384 | 166 | 1396 | | 165 | 949 | +| 32768 | 172 | 1311 | | 161 | 881 | +| 262144 | 136 | 632 | | 134 | 629 | +| 1048576 | 134 | 698 | | 127 | 623 | +|------------------+--------------+--------------| |--------------+--------------| + + + +Ubuntu 12.04, gcc-4.7.3, 32-bit, with fftw 3.3.3 (built with --enable-neon), on a 1.2GHz ARM Cortex A9 (Tegra 3) + +Built with: +gcc-4.7 -O3 -DHAVE_FFTW -march=armv7-a -mtune=cortex-a9 -mfloat-abi=hard -mfpu=neon -ffast-math test_pffft.c pffft.c -o test_pffft_arm fftpack.c -lm -I/usr/local/include/ -L/usr/local/lib/ -lfftw3f + +| input len |real FFTPack| real FFTW | real PFFFT | |cplx FFTPack| cplx FFTW | cplx PFFFT | +|-----------+------------+------------+------------| |------------+------------+------------| +| 64 | 549 | 452 | 731 | | 512 | 602 | 640 | +| 96 | 421 | 272 | 702 | | 496 | 571 | 602 | +| 128 | 498 | 512 | 815 | | 597 | 618 | 652 | +| 160 | 521 | 536 | 815 | | 586 | 669 | 625 | +| 192 | 539 | 571 | 883 | | 485 | 597 | 626 | +| 256 | 640 | 539 | 975 | | 569 | 611 | 671 | +| 384 | 499 | 610 | 879 | | 499 | 602 | 637 | +| 480 | 518 | 507 | 877 | | 496 | 661 | 616 | +| 512 | 524 | 591 | 1002 | | 549 | 678 | 668 | +| 640 | 542 | 612 | 955 | | 568 | 663 | 645 | +| 768 | 557 | 613 | 981 | | 491 | 663 | 598 | +| 800 | 514 | 353 | 882 | | 514 | 360 | 574 | +| 1024 | 640 | 640 | 1067 | | 492 | 683 | 602 | +| 2048 | 587 | 640 | 908 | | 486 | 640 | 552 | +| 2400 | 479 | 368 | 777 | | 422 | 376 | 518 | +| 4096 | 511 | 614 | 853 | | 426 | 640 | 534 | +| 8192 | 415 | 584 | 708 | | 386 | 622 | 516 | +| 9216 | 419 | 571 | 687 | | 364 | 586 | 506 | +| 16384 | 426 | 577 | 716 | | 398 | 606 | 530 | +| 32768 | 417 | 572 | 673 | | 399 | 572 | 468 | +| 262144 | 219 | 380 | 293 | | 255 | 431 | 343 | +| 1048576 | 202 | 274 | 237 | | 265 | 282 | 355 | +|-----------+------------+------------+------------| |------------+------------+------------| + +Same platform as above, but this time pffft and fftpack are built with clang 3.2: + +clang -O3 -DHAVE_FFTW -march=armv7-a -mtune=cortex-a9 -mfloat-abi=hard -mfpu=neon -ffast-math test_pffft.c pffft.c -o test_pffft_arm fftpack.c -lm -I/usr/local/include/ -L/usr/local/lib/ -lfftw3f + +| input len |real FFTPack| real FFTW | real PFFFT | |cplx FFTPack| cplx FFTW | cplx PFFFT | +|-----------+------------+------------+------------| |------------+------------+------------| +| 64 | 427 | 452 | 853 | | 427 | 602 | 1024 | +| 96 | 351 | 276 | 843 | | 337 | 571 | 963 | +| 128 | 373 | 512 | 996 | | 390 | 618 | 1054 | +| 160 | 426 | 536 | 987 | | 375 | 669 | 914 | +| 192 | 404 | 571 | 1079 | | 388 | 588 | 1079 | +| 256 | 465 | 539 | 1205 | | 445 | 602 | 1170 | +| 384 | 366 | 610 | 1099 | | 343 | 594 | 1099 | +| 480 | 356 | 507 | 1140 | | 335 | 651 | 931 | +| 512 | 411 | 591 | 1213 | | 384 | 649 | 1124 | +| 640 | 398 | 612 | 1193 | | 373 | 654 | 901 | +| 768 | 409 | 613 | 1227 | | 383 | 663 | 1044 | +| 800 | 411 | 348 | 1073 | | 353 | 358 | 809 | +| 1024 | 427 | 640 | 1280 | | 413 | 692 | 1004 | +| 2048 | 414 | 626 | 1126 | | 371 | 640 | 853 | +| 2400 | 399 | 373 | 898 | | 319 | 368 | 653 | +| 4096 | 404 | 602 | 1059 | | 357 | 633 | 778 | +| 8192 | 332 | 584 | 792 | | 308 | 616 | 716 | +| 9216 | 322 | 561 | 783 | | 299 | 586 | 687 | +| 16384 | 344 | 568 | 778 | | 314 | 617 | 745 | +| 32768 | 342 | 564 | 737 | | 314 | 552 | 629 | +| 262144 | 201 | 383 | 313 | | 227 | 435 | 413 | +| 1048576 | 187 | 262 | 251 | | 228 | 281 | 409 | +|-----------+------------+------------+------------| |------------+------------+------------| + +So it looks like, on ARM, gcc 4.7 is the best at scalar floating point +(the fftpack performance numbers are better with gcc), while clang is +the best with neon intrinsics (see how pffft perf has improved with +clang 3.2). + + +NVIDIA Jetson TK1 board, gcc-4.8.2. The cpu is a 2.3GHz cortex A15 (Tegra K1). + +Built with: +gcc -O3 -march=armv7-a -mtune=native -mfloat-abi=hard -mfpu=neon -ffast-math test_pffft.c pffft.c -o test_pffft_arm fftpack.c -lm + +| input len |real FFTPack| real PFFFT | |cplx FFTPack| cplx PFFFT | +|-----------+------------+------------| |------------+------------| +| 64 | 1735 | 3308 | | 1994 | 3744 | +| 96 | 1596 | 3448 | | 1987 | 3572 | +| 128 | 1807 | 4076 | | 2255 | 3960 | +| 160 | 1769 | 4083 | | 2071 | 3845 | +| 192 | 1990 | 4233 | | 2017 | 3939 | +| 256 | 2191 | 4882 | | 2254 | 4346 | +| 384 | 1878 | 4492 | | 2073 | 4012 | +| 480 | 1748 | 4398 | | 1923 | 3951 | +| 512 | 2030 | 5064 | | 2267 | 4195 | +| 640 | 1918 | 4756 | | 2094 | 4184 | +| 768 | 2099 | 4907 | | 2048 | 4297 | +| 800 | 1822 | 4555 | | 1880 | 4063 | +| 1024 | 2232 | 5355 | | 2187 | 4420 | +| 2048 | 2176 | 4983 | | 2027 | 3602 | +| 2400 | 1741 | 4256 | | 1710 | 3344 | +| 4096 | 1816 | 3914 | | 1851 | 3349 | +| 8192 | 1716 | 3481 | | 1700 | 3255 | +| 9216 | 1735 | 3589 | | 1653 | 3094 | +| 16384 | 1567 | 3483 | | 1637 | 3244 | +| 32768 | 1624 | 3240 | | 1655 | 3156 | +| 262144 | 1012 | 1898 | | 983 | 1503 | +| 1048576 | 876 | 1154 | | 868 | 1341 | +|-----------+------------+------------| |------------+------------| + +The performance on the tegra K1 is pretty impressive. I'm not +including the FFTW numbers as they as slightly below the scalar +fftpack numbers, so something must be wrong (however it seems to be +correctly configured and is using neon simd instructions). + +When using clang 3.4 the pffft version is even a bit faster, reaching +5.7 GFlops for real ffts of size 1024. + + +iPad Air 2 with iOS9, xcode 8.0, arm64. The cpu is an Apple A8X, supposedly running at 1.5GHz. + +| input len |real FFTPack| real vDSP | real PFFFT | |cplx FFTPack| cplx vDSP | cplx PFFFT | +|-----------+------------+------------+------------| |------------+------------+------------| +| 64 | 2517 | 7995 | 6086 | | 2725 | 13006 | 8495 | +| 96 | 2442 | n/a | 6691 | | 2256 | n/a | 7991 | +| 128 | 2664 | 10186 | 7877 | | 2575 | 15115 | 9115 | +| 160 | 2638 | n/a | 8283 | | 2682 | n/a | 8806 | +| 192 | 2903 | n/a | 9083 | | 2634 | n/a | 8980 | +| 256 | 3184 | 11452 | 10039 | | 3026 | 15410 | 10199 | +| 384 | 2665 | n/a | 10100 | | 2275 | n/a | 9247 | +| 480 | 2546 | n/a | 9863 | | 2341 | n/a | 8892 | +| 512 | 2832 | 12197 | 10989 | | 2547 | 16768 | 10154 | +| 640 | 2755 | n/a | 10461 | | 2569 | n/a | 9666 | +| 768 | 2998 | n/a | 11355 | | 2585 | n/a | 9813 | +| 800 | 2516 | n/a | 10332 | | 2433 | n/a | 9164 | +| 1024 | 3109 | 12965 | 12114 | | 2869 | 16448 | 10519 | +| 2048 | 3027 | 12996 | 12023 | | 2648 | 17304 | 10307 | +| 2400 | 2515 | n/a | 10372 | | 2355 | n/a | 8443 | +| 4096 | 3204 | 13603 | 12359 | | 2814 | 16570 | 9780 | +| 8192 | 2759 | 13422 | 10824 | | 2153 | 15652 | 7884 | +| 9216 | 2700 | n/a | 9938 | | 2241 | n/a | 7900 | +| 16384 | 2280 | 13057 | 7976 | | 593 | 4272 | 2534 | +| 32768 | 768 | 4269 | 2882 | | 606 | 4405 | 2604 | +| 262144 | 724 | 3527 | 2630 | | 534 | 2418 | 2157 | +| 1048576 | 674 | 1467 | 2135 | | 530 | 1621 | 2055 | +|-----------+------------+------------+------------| |------------+------------+------------| + +I double-checked to make sure I did not make a mistake in the time +measurements, as the numbers are much higher than what I initially +expected. They are in fact higher than the number I get on the 2.8GHz +Xeon of my 2008 mac pro.. (except for FFT lengths >= 32768 where +having a big cache is useful). A good surprise is also that the perf +is not too far from apple's vDSP (at least for the real FFT). + From c39787ddc7b874553ecd60c641ce9dd0dceb51e2 Mon Sep 17 00:00:00 2001 From: doinachiroiu Date: Mon, 17 Aug 2020 11:21:33 +0000 Subject: [PATCH 20/82] Testing pffft - elaborate output --- oss-internship-2020/pffft/README.txt | 6 ++- .../pffft/test_pffft_sandboxed.cc | 41 ++++++++++++++++--- 2 files changed, 40 insertions(+), 7 deletions(-) diff --git a/oss-internship-2020/pffft/README.txt b/oss-internship-2020/pffft/README.txt index ef51f1a..5688ac9 100644 --- a/oss-internship-2020/pffft/README.txt +++ b/oss-internship-2020/pffft/README.txt @@ -16,4 +16,8 @@ CMake observations: Sandboxed main observations: * containing two testing parts (fft / pffft benchmarks) ! current stage: fft - works :) - pffft - not implemented + pffft - implemented + * pffft benchmark bug: "Sandbox not active" + => loop in pffft_transform for N = 64 (why?); + N = 64, status OK, pffft_transform generates error + N > 64, status not OK diff --git a/oss-internship-2020/pffft/test_pffft_sandboxed.cc b/oss-internship-2020/pffft/test_pffft_sandboxed.cc index d50dff3..5a56a6f 100644 --- a/oss-internship-2020/pffft/test_pffft_sandboxed.cc +++ b/oss-internship-2020/pffft/test_pffft_sandboxed.cc @@ -60,12 +60,6 @@ void show_output(const char* name, int N, int cplx, float flops, float t0, fflush(stdout); } -/* - For debug: - SAPI_VLOG_LEVEL=1 ./pffft_sandboxed --v=100 - --sandbox2_danger_danger_permit_all_and_log my_aux_file -*/ - int main(int argc, char* argv[]) { gflags::ParseCommandLineFlags(&argc, &argv, true); int Nvalues[] = {64, 96, 128, 160, 192, 256, @@ -148,6 +142,41 @@ int main(int argc, char* argv[]) { flops = (max_iter_ * 2) * ((cplx ? 5 : 2.5) * N * log((double)N) / M_LN2); show_output("FFTPack", N, cplx, flops, t0, t1, max_iter_); } + + // PFFFT benchmark + { + sapi::StatusOr s = + api.pffft_new_setup(N, cplx ? PFFFT_COMPLEX : PFFFT_REAL); + + printf("%s\n", s.status().ToString().c_str()); + + if (s.ok()) { + sapi::v::GenericPtr s_reg(s.value()); + + t0 = uclock_sec(); + for (iter = 0; iter < max_iter; ++iter) { + printf("%s\n", + api.pffft_transform(s_reg.PtrBoth(), X_.PtrBoth(), + Z_.PtrBoth(), Y_.PtrBoth(), PFFFT_FORWARD) + .ToString() + .c_str()); + printf("%s\n", + api.pffft_transform(s_reg.PtrBoth(), X_.PtrBoth(), + Z_.PtrBoth(), Y_.PtrBoth(), PFFFT_FORWARD) + .ToString() + .c_str()); + } + + t1 = uclock_sec(); + printf("%s\n", + api.pffft_destroy_setup(s_reg.PtrBoth()).ToString().c_str()); + + flops = + (max_iter * 2) * ((cplx ? 5 : 2.5) * N * log((double)N) / M_LN2); + show_output("PFFFT", N, cplx, flops, t0, t1, max_iter); + } + printf("\n\n"); + } } return 0; From d51d558083b57481971b46998f20928b57e9b59e Mon Sep 17 00:00:00 2001 From: doinachiroiu Date: Mon, 17 Aug 2020 11:21:42 +0000 Subject: [PATCH 21/82] Update .gitignore --- oss-internship-2020/pffft/.gitignore | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/oss-internship-2020/pffft/.gitignore b/oss-internship-2020/pffft/.gitignore index f9ed608..0469302 100644 --- a/oss-internship-2020/pffft/.gitignore +++ b/oss-internship-2020/pffft/.gitignore @@ -1 +1,6 @@ .hg/ +pffft.o +test_pffft.o +fftpack.o +libpffft.a +pffft_main \ No newline at end of file From 3fb4d5954513dbdc43272a7220ebdb3aca9c68ff Mon Sep 17 00:00:00 2001 From: doinachiroiu Date: Mon, 17 Aug 2020 11:21:49 +0000 Subject: [PATCH 22/82] Sandbox not active error tracking observations --- oss-internship-2020/pffft/README.txt | 7 +++++++ oss-internship-2020/pffft/test_pffft_sandboxed.cc | 6 ++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/oss-internship-2020/pffft/README.txt b/oss-internship-2020/pffft/README.txt index 5688ac9..9c2556a 100644 --- a/oss-internship-2020/pffft/README.txt +++ b/oss-internship-2020/pffft/README.txt @@ -21,3 +21,10 @@ Sandboxed main observations: => loop in pffft_transform for N = 64 (why?); N = 64, status OK, pffft_transform generates error N > 64, status not OK + Problem on initialising sapi::StatusOr s; + the memory that stays for s is not the same with the address passed + in pffft_transform function. + (sapi::v::GenericPtr to be changed?) + + Temporary solution (not done): change the generated files to accept + uintptr_t instead of PFFFT_Setup diff --git a/oss-internship-2020/pffft/test_pffft_sandboxed.cc b/oss-internship-2020/pffft/test_pffft_sandboxed.cc index 5a56a6f..8c91a77 100644 --- a/oss-internship-2020/pffft/test_pffft_sandboxed.cc +++ b/oss-internship-2020/pffft/test_pffft_sandboxed.cc @@ -73,6 +73,8 @@ int main(int argc, char* argv[]) { pffftSapiSandbox sandbox; sandbox.Init().IgnoreError(); + printf("%s\n", sandbox.Init().ToString().c_str()); + pffftApi api(&sandbox); int N, cplx; @@ -164,7 +166,7 @@ int main(int argc, char* argv[]) { api.pffft_transform(s_reg.PtrBoth(), X_.PtrBoth(), Z_.PtrBoth(), Y_.PtrBoth(), PFFFT_FORWARD) .ToString() - .c_str()); + .c_str()); } t1 = uclock_sec(); @@ -175,7 +177,7 @@ int main(int argc, char* argv[]) { (max_iter * 2) * ((cplx ? 5 : 2.5) * N * log((double)N) / M_LN2); show_output("PFFFT", N, cplx, flops, t0, t1, max_iter); } - printf("\n\n"); + printf("\n\n"); } } From 06bf6cdd34a172bdd0048720d62bbda856d5593f Mon Sep 17 00:00:00 2001 From: doinachiroiu Date: Mon, 17 Aug 2020 11:21:56 +0000 Subject: [PATCH 23/82] Sandbox not active error - resolved --- oss-internship-2020/pffft/README.txt | 26 +++++++++-------- .../pffft/test_pffft_sandboxed.cc | 29 ++++++++----------- 2 files changed, 26 insertions(+), 29 deletions(-) diff --git a/oss-internship-2020/pffft/README.txt b/oss-internship-2020/pffft/README.txt index 9c2556a..c7ac70b 100644 --- a/oss-internship-2020/pffft/README.txt +++ b/oss-internship-2020/pffft/README.txt @@ -16,15 +16,17 @@ CMake observations: Sandboxed main observations: * containing two testing parts (fft / pffft benchmarks) ! current stage: fft - works :) - pffft - implemented - * pffft benchmark bug: "Sandbox not active" - => loop in pffft_transform for N = 64 (why?); - N = 64, status OK, pffft_transform generates error - N > 64, status not OK - Problem on initialising sapi::StatusOr s; - the memory that stays for s is not the same with the address passed - in pffft_transform function. - (sapi::v::GenericPtr to be changed?) - - Temporary solution (not done): change the generated files to accept - uintptr_t instead of PFFFT_Setup + pffft - implemented + * (Solved) pffft benchmark bug: "Sandbox not active" + N = 64, status OK, pffft_transform generates error + N > 64, status not OK + Problem on initialising sapi::StatusOr s; + the memory that stays for s is not the same with the address passed + in pffft_transform function. + (sapi :: v :: GenericPtr - to be changed) + + Temporary solution: change the generated files to accept + uintptr_t instead of PFFFT_Setup + + Solution: using "sapi :: v :: RemotePtr" instead of "sapi :: v :: GenericPtr" + to access the memory of object s diff --git a/oss-internship-2020/pffft/test_pffft_sandboxed.cc b/oss-internship-2020/pffft/test_pffft_sandboxed.cc index 8c91a77..e4b4602 100644 --- a/oss-internship-2020/pffft/test_pffft_sandboxed.cc +++ b/oss-internship-2020/pffft/test_pffft_sandboxed.cc @@ -73,7 +73,7 @@ int main(int argc, char* argv[]) { pffftSapiSandbox sandbox; sandbox.Init().IgnoreError(); - printf("%s\n", sandbox.Init().ToString().c_str()); + printf("Initialization: %s\n", sandbox.Init().ToString().c_str()); pffftApi api(&sandbox); @@ -81,7 +81,7 @@ int main(int argc, char* argv[]) { cplx = 0; - for (i = 0; i < 5; i++) { + for (i = 0; i < 23; i++) { N = Nvalues[i]; int Nfloat = N * (cplx ? 2 : 1); @@ -150,34 +150,29 @@ int main(int argc, char* argv[]) { sapi::StatusOr s = api.pffft_new_setup(N, cplx ? PFFFT_COMPLEX : PFFFT_REAL); - printf("%s\n", s.status().ToString().c_str()); + printf("Setup status is: %s\n", s.status().ToString().c_str()); if (s.ok()) { - sapi::v::GenericPtr s_reg(s.value()); + sapi::v::RemotePtr s_reg(s.value()); t0 = uclock_sec(); for (iter = 0; iter < max_iter; ++iter) { - printf("%s\n", - api.pffft_transform(s_reg.PtrBoth(), X_.PtrBoth(), - Z_.PtrBoth(), Y_.PtrBoth(), PFFFT_FORWARD) - .ToString() - .c_str()); - printf("%s\n", - api.pffft_transform(s_reg.PtrBoth(), X_.PtrBoth(), - Z_.PtrBoth(), Y_.PtrBoth(), PFFFT_FORWARD) - .ToString() - .c_str()); + api.pffft_transform(&s_reg, X_.PtrBoth(), Z_.PtrBoth(), Y_.PtrBoth(), + PFFFT_FORWARD) + .IgnoreError(); + api.pffft_transform(&s_reg, X_.PtrBoth(), Z_.PtrBoth(), Y_.PtrBoth(), + PFFFT_FORWARD) + .IgnoreError(); } t1 = uclock_sec(); - printf("%s\n", - api.pffft_destroy_setup(s_reg.PtrBoth()).ToString().c_str()); + api.pffft_destroy_setup(&s_reg).IgnoreError(); flops = (max_iter * 2) * ((cplx ? 5 : 2.5) * N * log((double)N) / M_LN2); show_output("PFFFT", N, cplx, flops, t0, t1, max_iter); } - printf("\n\n"); + printf("\n\n"); } } From b4aca053002bae11c629caa69421d6ef59a2919d Mon Sep 17 00:00:00 2001 From: Bohdan Tyshchenko Date: Mon, 17 Aug 2020 13:29:07 -0700 Subject: [PATCH 24/82] Update after code review --- oss-internship-2020/guetzli/BUILD.bazel | 14 +- oss-internship-2020/guetzli/README.md | 29 + oss-internship-2020/guetzli/WORKSPACE | 47 +- .../guetzli/external/butteraugli.BUILD | 28 + .../guetzli/external/guetzli.BUILD | 18 +- .../butteraugli => external}/jpeg.BUILD | 1 + .../guetzli/guetzli_entry_points.cc | 155 +- oss-internship-2020/guetzli/guetzli_sandbox.h | 7 +- .../guetzli/guetzli_sandboxed.cc | 55 +- .../guetzli/guetzli_transaction.cc | 105 +- .../guetzli/guetzli_transaction.h | 22 +- oss-internship-2020/guetzli/tests/BUILD.bazel | 4 +- .../guetzli/tests/guetzli_sapi_test.cc | 33 +- .../guetzli/tests/guetzli_transaction_test.cc | 80 +- .../guetzli/third_party/butteraugli/BUILD | 24 - .../guetzli/third_party/butteraugli/LICENSE | 201 -- .../guetzli/third_party/butteraugli/README.md | 68 - .../guetzli/third_party/butteraugli/WORKSPACE | 25 - .../butteraugli/butteraugli/Makefile | 7 - .../butteraugli/butteraugli/butteraugli.cc | 1994 ----------------- .../butteraugli/butteraugli/butteraugli.h | 619 ----- .../butteraugli/butteraugli_main.cc | 457 ---- .../guetzli/third_party/butteraugli/png.BUILD | 33 - .../third_party/butteraugli/zlib.BUILD | 36 - 24 files changed, 324 insertions(+), 3738 deletions(-) create mode 100644 oss-internship-2020/guetzli/external/butteraugli.BUILD rename oss-internship-2020/guetzli/{third_party/butteraugli => external}/jpeg.BUILD (99%) mode change 100755 => 100644 delete mode 100755 oss-internship-2020/guetzli/third_party/butteraugli/BUILD delete mode 100755 oss-internship-2020/guetzli/third_party/butteraugli/LICENSE delete mode 100755 oss-internship-2020/guetzli/third_party/butteraugli/README.md delete mode 100755 oss-internship-2020/guetzli/third_party/butteraugli/WORKSPACE delete mode 100755 oss-internship-2020/guetzli/third_party/butteraugli/butteraugli/Makefile delete mode 100755 oss-internship-2020/guetzli/third_party/butteraugli/butteraugli/butteraugli.cc delete mode 100755 oss-internship-2020/guetzli/third_party/butteraugli/butteraugli/butteraugli.h delete mode 100755 oss-internship-2020/guetzli/third_party/butteraugli/butteraugli/butteraugli_main.cc delete mode 100755 oss-internship-2020/guetzli/third_party/butteraugli/png.BUILD delete mode 100755 oss-internship-2020/guetzli/third_party/butteraugli/zlib.BUILD diff --git a/oss-internship-2020/guetzli/BUILD.bazel b/oss-internship-2020/guetzli/BUILD.bazel index fe5bfb8..88a6da2 100644 --- a/oss-internship-2020/guetzli/BUILD.bazel +++ b/oss-internship-2020/guetzli/BUILD.bazel @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +licenses(["unencumbered"]) # code authored by Google + load( "@com_google_sandboxed_api//sandboxed_api/bazel:sapi.bzl", "sapi_library", @@ -25,7 +27,7 @@ cc_library( "@guetzli//:guetzli_lib", "@com_google_sandboxed_api//sandboxed_api:lenval_core", "@com_google_sandboxed_api//sandboxed_api:vars", - "@png_archive//:png" + "@png_archive//:png", ], ) @@ -36,19 +38,19 @@ sapi_library( functions = [ "ProcessJpeg", "ProcessRgb", - "WriteDataToFd" + "WriteDataToFd", ], input_files = ["guetzli_entry_points.h"], lib = ":guetzli_wrapper", lib_name = "Guetzli", visibility = ["//visibility:public"], - namespace = "guetzli::sandbox" + namespace = "guetzli::sandbox", ) cc_binary( name="guetzli_sandboxed", srcs=["guetzli_sandboxed.cc"], deps = [ - ":guetzli_sapi" - ] -) \ No newline at end of file + ":guetzli_sapi", + ], +) diff --git a/oss-internship-2020/guetzli/README.md b/oss-internship-2020/guetzli/README.md index e69de29..042f165 100644 --- a/oss-internship-2020/guetzli/README.md +++ b/oss-internship-2020/guetzli/README.md @@ -0,0 +1,29 @@ +# Guetzli Sandboxed +This is an example implementation of a sandbox for the [Guetzli](https://github.com/google/guetzli) library using [Sandboxed API](https://github.com/google/sandboxed-api). +Please read Guetzli's [documentation](https://github.com/google/guetzli#introduction) to learn more about it. + +## Implementation details +Because Guetzli provides C++ API and SAPI requires functions to be `extern "C"` a wrapper library has been written for the compatibility. SAPI provides a Transaction class, which is a convenient way to create a wrapper for your sandboxed API that handles internal errors. Original Guetzli has command-line utility to encode images, so fully compatible utility that uses sandboxed Guetzli is provided. + +Wrapper around Guetzli uses file descriptors to pass data to the sandbox. This approach restricts the sandbox from using open() syscall and also helps to prevent making copies of data, because you need to synchronize it between processes. + +## Build Guetzli Sandboxed +Right now Sandboxed API support only Linux systems, so you need one to build it. Guetzli sandboxed uses [Bazel](https://bazel.build/) as a build system so you need to [install it](https://docs.bazel.build/versions/3.4.0/install.html) before building. + +To build Guetzli sandboxed encoding utility you can use this command: +`bazel build //:guetzli_sandboxed` + +Than you can use it in this way: +``` +guetzli_sandboxed [--quality Q] [--verbose] original.png output.jpg +guetzli_sandboxed [--quality Q] [--verbose] original.jpg output.jpg +``` +Refer to Guetzli's [documentation](https://github.com/google/guetzli#using) to read more about usage. + +## Examples +There are two different sets of unit tests which demonstrate how to use different parts of Guetzli sandboxed: +* `tests/guetzli_sapi_test.cc` - example usage of Guetzli sandboxed API. +* `tests/guetzli_transaction_test.cc` - example usage of Guetzli transaction. + +Also, there is an example of custom security policy for your sandbox in +`guetzli_sandbox.h` diff --git a/oss-internship-2020/guetzli/WORKSPACE b/oss-internship-2020/guetzli/WORKSPACE index f7ec64c..7f2b667 100644 --- a/oss-internship-2020/guetzli/WORKSPACE +++ b/oss-internship-2020/guetzli/WORKSPACE @@ -47,38 +47,49 @@ load("@com_google_protobuf//:protobuf_deps.bzl", "protobuf_deps") protobuf_deps() -local_repository( - name = "butteraugli", - path = "third_party/butteraugli/" -) - http_archive( name = "guetzli", build_file = "guetzli.BUILD", sha256 = "39632357e49db83d9560bf0de560ad833352f36d23b109b0e995b01a37bddb57", strip_prefix = "guetzli-master", - url = "https://github.com/google/guetzli/archive/master.zip" + url = "https://github.com/google/guetzli/archive/master.zip", ) http_archive( - name = "png_archive", - build_file = "png.BUILD", - sha256 = "a941dc09ca00148fe7aaf4ecdd6a67579c293678ed1e1cf633b5ffc02f4f8cf7", - strip_prefix = "libpng-1.2.57", - url = "http://github.com/glennrp/libpng/archive/v1.2.57.zip", + name = "butteraugli", + build_file = "butteraugli.BUILD", + sha256 = "39632357e49db83d9560bf0de560ad833352f36d23b109b0e995b01a37bddb57", + strip_prefix = "guetzli-master/third_party/butteraugli", + url = "https://github.com/google/guetzli/archive/master.zip", ) http_archive( - name = "zlib_archive", - build_file = "zlib.BUILD", - sha256 = "8d7e9f698ce48787b6e1c67e6bff79e487303e66077e25cb9784ac8835978017", - strip_prefix = "zlib-1.2.10", - url = "http://zlib.net/fossils/zlib-1.2.10.tar.gz", + name = "png_archive", + build_file = "png.BUILD", + sha256 = "a941dc09ca00148fe7aaf4ecdd6a67579c293678ed1e1cf633b5ffc02f4f8cf7", + strip_prefix = "libpng-1.2.57", + url = "http://github.com/glennrp/libpng/archive/v1.2.57.zip", +) + +http_archive( + name = "zlib_archive", + build_file = "zlib.BUILD", + sha256 = "8d7e9f698ce48787b6e1c67e6bff79e487303e66077e25cb9784ac8835978017", + strip_prefix = "zlib-1.2.10", + url = "http://zlib.net/fossils/zlib-1.2.10.tar.gz", +) + +http_archive( + name = "jpeg_archive", + url = "http://www.ijg.org/files/jpegsrc.v9b.tar.gz", + sha256 = "240fd398da741669bf3c90366f58452ea59041cacc741a489b99f2f6a0bad052", + strip_prefix = "jpeg-9b", + build_file = "jpeg.BUILD", ) maybe( git_repository, name = "googletest", remote = "https://github.com/google/googletest", - tag = "release-1.10.0" -) \ No newline at end of file + tag = "release-1.10.0", +) diff --git a/oss-internship-2020/guetzli/external/butteraugli.BUILD b/oss-internship-2020/guetzli/external/butteraugli.BUILD new file mode 100644 index 0000000..9058593 --- /dev/null +++ b/oss-internship-2020/guetzli/external/butteraugli.BUILD @@ -0,0 +1,28 @@ +# Copyright 2020 Google LLC +# +# 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. + +licenses(["unencumbered"]) # code authored by Google + +cc_library( + name = "butteraugli_lib", + srcs = [ + "butteraugli/butteraugli.cc", + "butteraugli/butteraugli.h", + ], + hdrs = [ + "butteraugli/butteraugli.h", + ], + copts = ["-Wno-sign-compare"], + visibility = ["//visibility:public"], +) diff --git a/oss-internship-2020/guetzli/external/guetzli.BUILD b/oss-internship-2020/guetzli/external/guetzli.BUILD index dec29a1..d2d4f32 100644 --- a/oss-internship-2020/guetzli/external/guetzli.BUILD +++ b/oss-internship-2020/guetzli/external/guetzli.BUILD @@ -1,4 +1,18 @@ -package(default_visibility = ["//visibility:public"]) +# Copyright 2020 Google LLC +# +# 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. + +licenses(["unencumbered"]) # code authored by Google cc_library( name = "guetzli_lib", @@ -15,4 +29,4 @@ cc_library( deps = [ "@butteraugli//:butteraugli_lib", ], -) \ No newline at end of file +) diff --git a/oss-internship-2020/guetzli/third_party/butteraugli/jpeg.BUILD b/oss-internship-2020/guetzli/external/jpeg.BUILD old mode 100755 new mode 100644 similarity index 99% rename from oss-internship-2020/guetzli/third_party/butteraugli/jpeg.BUILD rename to oss-internship-2020/guetzli/external/jpeg.BUILD index 92c9ddc..71eb87b --- a/oss-internship-2020/guetzli/third_party/butteraugli/jpeg.BUILD +++ b/oss-internship-2020/guetzli/external/jpeg.BUILD @@ -1,3 +1,4 @@ + # Description: # The Independent JPEG Group's JPEG runtime library. diff --git a/oss-internship-2020/guetzli/guetzli_entry_points.cc b/oss-internship-2020/guetzli/guetzli_entry_points.cc index ec0743b..a36431b 100644 --- a/oss-internship-2020/guetzli/guetzli_entry_points.cc +++ b/oss-internship-2020/guetzli/guetzli_entry_points.cc @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include "guetzli_entry_points.h" + #include #include @@ -22,7 +24,6 @@ #include "guetzli/jpeg_data_reader.h" #include "guetzli/quality.h" -#include "guetzli_entry_points.h" #include "png.h" #include "sandboxed_api/sandbox2/util/fileops.h" #include "sandboxed_api/util/statusor.h" @@ -38,19 +39,21 @@ struct GuetzliInitData { guetzli::ProcessStats stats; }; -template -void CopyMemoryToLenVal(const T* data, size_t size, - sapi::LenValStruct* out_data) { - free(out_data->data); // Not sure about this - out_data->size = size; - T* new_out = static_cast(malloc(size)); - memcpy(new_out, data, size); - out_data->data = new_out; +struct ImageData { + int xsize; + int ysize; + std::vector rgb; +}; + +sapi::LenValStruct CreateLenValFromData(const void* data, size_t size) { + void* new_data = malloc(size); + memcpy(new_data, data, size); + return { size, new_data }; } sapi::StatusOr ReadFromFd(int fd) { struct stat file_data; - auto status = fstat(fd, &file_data); + int status = fstat(fd, &file_data); if (status < 0) { return absl::FailedPreconditionError( @@ -58,10 +61,9 @@ sapi::StatusOr ReadFromFd(int fd) { ); } - auto fsize = file_data.st_size; - - std::unique_ptr buf(new char[fsize]); - status = read(fd, buf.get(), fsize); + std::string result; + result.resize(file_data.st_size); + status = read(fd, result.data(), result.size()); if (status < 0) { return absl::FailedPreconditionError( @@ -69,15 +71,15 @@ sapi::StatusOr ReadFromFd(int fd) { ); } - return std::string(buf.get(), fsize); + return result; } sapi::StatusOr PrepareDataForProcessing( const ProcessingParams* processing_params) { - auto input_status = ReadFromFd(processing_params->remote_fd); + sapi::StatusOr input = ReadFromFd(processing_params->remote_fd); - if (!input_status.ok()) { - return input_status.status(); + if (!input.ok()) { + return input.status(); } guetzli::Params guetzli_params; @@ -91,7 +93,7 @@ sapi::StatusOr PrepareDataForProcessing( } return GuetzliInitData{ - std::move(input_status.value()), + std::move(input.value()), guetzli_params, stats }; @@ -101,29 +103,36 @@ inline uint8_t BlendOnBlack(const uint8_t val, const uint8_t alpha) { return (static_cast(val) * static_cast(alpha) + 128) / 255; } -bool ReadPNG(const std::string& data, int* xsize, int* ysize, - std::vector* rgb) { +// Modified version of ReadPNG from original guetzli.cc +sapi::StatusOr ReadPNG(const std::string& data) { + std::vector rgb; + int xsize, ysize; png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); if (!png_ptr) { - return false; + return absl::FailedPreconditionError( + "Error reading PNG data from input file"); } png_infop info_ptr = png_create_info_struct(png_ptr); if (!info_ptr) { png_destroy_read_struct(&png_ptr, nullptr, nullptr); - return false; + return absl::FailedPreconditionError( + "Error reading PNG data from input file"); } if (setjmp(png_jmpbuf(png_ptr)) != 0) { // Ok we are here because of the setjmp. png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); - return false; + return absl::FailedPreconditionError( + "Error reading PNG data from input file"); } std::istringstream memstream(data, std::ios::in | std::ios::binary); - png_set_read_fn(png_ptr, static_cast(&memstream), [](png_structp png_ptr, png_bytep outBytes, png_size_t byteCountToRead) { - std::istringstream& memstream = *static_cast(png_get_io_ptr(png_ptr)); + png_set_read_fn(png_ptr, static_cast(&memstream), + [](png_structp png_ptr, png_bytep outBytes, png_size_t byteCountToRead) { + std::istringstream& memstream = + *static_cast(png_get_io_ptr(png_ptr)); memstream.read(reinterpret_cast(outBytes), byteCountToRead); @@ -143,18 +152,18 @@ bool ReadPNG(const std::string& data, int* xsize, int* ysize, png_bytep* row_pointers = png_get_rows(png_ptr, info_ptr); - *xsize = png_get_image_width(png_ptr, info_ptr); - *ysize = png_get_image_height(png_ptr, info_ptr); - rgb->resize(3 * (*xsize) * (*ysize)); + xsize = png_get_image_width(png_ptr, info_ptr); + ysize = png_get_image_height(png_ptr, info_ptr); + rgb.resize(3 * (xsize) * (ysize)); const int components = png_get_channels(png_ptr, info_ptr); switch (components) { case 1: { // GRAYSCALE - for (int y = 0; y < *ysize; ++y) { + for (int y = 0; y < ysize; ++y) { const uint8_t* row_in = row_pointers[y]; - uint8_t* row_out = &(*rgb)[3 * y * (*xsize)]; - for (int x = 0; x < *xsize; ++x) { + uint8_t* row_out = &(rgb)[3 * y * (xsize)]; + for (int x = 0; x < xsize; ++x) { const uint8_t gray = row_in[x]; row_out[3 * x + 0] = gray; row_out[3 * x + 1] = gray; @@ -165,10 +174,10 @@ bool ReadPNG(const std::string& data, int* xsize, int* ysize, } case 2: { // GRAYSCALE + ALPHA - for (int y = 0; y < *ysize; ++y) { + for (int y = 0; y < ysize; ++y) { const uint8_t* row_in = row_pointers[y]; - uint8_t* row_out = &(*rgb)[3 * y * (*xsize)]; - for (int x = 0; x < *xsize; ++x) { + uint8_t* row_out = &(rgb)[3 * y * (xsize)]; + for (int x = 0; x < xsize; ++x) { const uint8_t gray = BlendOnBlack(row_in[2 * x], row_in[2 * x + 1]); row_out[3 * x + 0] = gray; row_out[3 * x + 1] = gray; @@ -179,19 +188,19 @@ bool ReadPNG(const std::string& data, int* xsize, int* ysize, } case 3: { // RGB - for (int y = 0; y < *ysize; ++y) { + for (int y = 0; y < ysize; ++y) { const uint8_t* row_in = row_pointers[y]; - uint8_t* row_out = &(*rgb)[3 * y * (*xsize)]; - memcpy(row_out, row_in, 3 * (*xsize)); + uint8_t* row_out = &(rgb)[3 * y * (xsize)]; + memcpy(row_out, row_in, 3 * (xsize)); } break; } case 4: { // RGBA - for (int y = 0; y < *ysize; ++y) { + for (int y = 0; y < ysize; ++y) { const uint8_t* row_in = row_pointers[y]; - uint8_t* row_out = &(*rgb)[3 * y * (*xsize)]; - for (int x = 0; x < *xsize; ++x) { + uint8_t* row_out = &(rgb)[3 * y * (xsize)]; + for (int x = 0; x < xsize; ++x) { const uint8_t alpha = row_in[4 * x + 3]; row_out[3 * x + 0] = BlendOnBlack(row_in[4 * x + 0], alpha); row_out[3 * x + 1] = BlendOnBlack(row_in[4 * x + 1], alpha); @@ -202,10 +211,16 @@ bool ReadPNG(const std::string& data, int* xsize, int* ysize, } default: png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); - return false; + return absl::FailedPreconditionError( + "Error reading PNG data from input file"); } png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); - return true; + + return ImageData{ + xsize, + ysize, + std::move(rgb) + }; } bool CheckMemoryLimitExceeded(int memlimit_mb, int xsize, int ysize) { @@ -219,70 +234,72 @@ bool CheckMemoryLimitExceeded(int memlimit_mb, int xsize, int ysize) { extern "C" bool ProcessJpeg(const ProcessingParams* processing_params, sapi::LenValStruct* output) { - auto processing_data_status = PrepareDataForProcessing(processing_params); + auto processing_data = PrepareDataForProcessing(processing_params); - if (!processing_data_status.status().ok()) { - fprintf(stderr, "%s\n", processing_data_status.status().ToString().c_str()); + if (!processing_data.ok()) { + std::cerr << processing_data.status().ToString() << std::endl; return false; } guetzli::JPEGData jpg_header; - if (!guetzli::ReadJpeg(processing_data_status.value().in_data, + if (!guetzli::ReadJpeg(processing_data->in_data, guetzli::JPEG_READ_HEADER, &jpg_header)) { - fprintf(stderr, "Error reading JPG data from input file\n"); + std::cerr << "Error reading JPG data from input file" << std::endl; return false; } if (CheckMemoryLimitExceeded(processing_params->memlimit_mb, jpg_header.width, jpg_header.height)) { - fprintf(stderr, "Memory limit would be exceeded.\n"); + std::cerr << "Memory limit would be exceeded" << std::endl; return false; } std::string out_data; - if (!guetzli::Process(processing_data_status.value().params, - &processing_data_status.value().stats, - processing_data_status.value().in_data, + if (!guetzli::Process(processing_data->params, + &processing_data->stats, + processing_data->in_data, &out_data)) { - fprintf(stderr, "Guezli processing failed\n"); + std::cerr << "Guezli processing failed" << std::endl; return false; } - CopyMemoryToLenVal(out_data.data(), out_data.size(), output); + *output = CreateLenValFromData(out_data.data(), out_data.size()); return true; } extern "C" bool ProcessRgb(const ProcessingParams* processing_params, sapi::LenValStruct* output) { - auto processing_data_status = PrepareDataForProcessing(processing_params); + auto processing_data = PrepareDataForProcessing(processing_params); - if (!processing_data_status.status().ok()) { - fprintf(stderr, "%s\n", processing_data_status.status().ToString().c_str()); + if (!processing_data.ok()) { + std::cerr << processing_data.status().ToString() << std::endl; return false; } - int xsize, ysize; - std::vector rgb; - - if (!ReadPNG(processing_data_status.value().in_data, &xsize, &ysize, &rgb)) { - fprintf(stderr, "Error reading PNG data from input file\n"); + auto png_data = ReadPNG(processing_data->in_data); + if (!png_data.ok()) { + std::cerr << "Error reading PNG data from input file" << std::endl; return false; } - if (CheckMemoryLimitExceeded(processing_params->memlimit_mb, xsize, ysize)) { - fprintf(stderr, "Memory limit would be exceeded.\n"); + if (CheckMemoryLimitExceeded(processing_params->memlimit_mb, + png_data->xsize, png_data->ysize)) { + std::cerr << "Memory limit would be exceeded" << std::endl; return false; } std::string out_data; - if (!guetzli::Process(processing_data_status.value().params, - &processing_data_status.value().stats, - rgb, xsize, ysize, &out_data)) { - fprintf(stderr, "Guetzli processing failed\n"); + if (!guetzli::Process(processing_data->params, + &processing_data->stats, + png_data->rgb, + png_data->xsize, + png_data->ysize, + &out_data)) { + std::cerr << "Guetzli processing failed" << std::endl; return false; } - CopyMemoryToLenVal(out_data.data(), out_data.size(), output); + *output = CreateLenValFromData(out_data.data(), out_data.size()); return true; } diff --git a/oss-internship-2020/guetzli/guetzli_sandbox.h b/oss-internship-2020/guetzli/guetzli_sandbox.h index e55875a..d1da1f8 100644 --- a/oss-internship-2020/guetzli/guetzli_sandbox.h +++ b/oss-internship-2020/guetzli/guetzli_sandbox.h @@ -15,13 +15,8 @@ #ifndef GUETZLI_SANDBOXED_GUETZLI_SANDBOX_H_ #define GUETZLI_SANDBOXED_GUETZLI_SANDBOX_H_ -#include #include -#include "sandboxed_api/sandbox2/policy.h" -#include "sandboxed_api/sandbox2/policybuilder.h" -#include "sandboxed_api/util/flag.h" - #include "guetzli_sapi.sapi.h" namespace guetzli { @@ -42,7 +37,7 @@ class GuetzliSapiSandbox : public GuetzliSandbox { .AllowSyscalls({ __NR_futex, __NR_close, - __NR_recvmsg // Seems like this one needed to work with remote file descriptors + __NR_recvmsg // To work with remote fd }) .BuildOrDie(); } diff --git a/oss-internship-2020/guetzli/guetzli_sandboxed.cc b/oss-internship-2020/guetzli/guetzli_sandboxed.cc index cb6543c..6628f3e 100644 --- a/oss-internship-2020/guetzli/guetzli_sandboxed.cc +++ b/oss-internship-2020/guetzli/guetzli_sandboxed.cc @@ -30,12 +30,6 @@ namespace { constexpr int kDefaultJPEGQuality = 95; constexpr int kDefaultMemlimitMB = 6000; -void TerminateHandler() { - fprintf(stderr, "Unhandled exception. Most likely insufficient memory available.\n" - "Make sure that there is 300MB/MPix of memory available.\n"); - exit(1); -} - void Usage() { fprintf(stderr, "Guetzli JPEG compressor. Usage: \n" @@ -54,8 +48,6 @@ void Usage() { } // namespace int main(int argc, const char** argv) { - std::set_terminate(TerminateHandler); - int verbose = 0; int quality = kDefaultJPEGQuality; int memlimit_mb = kDefaultMemlimitMB; @@ -92,25 +84,9 @@ int main(int argc, const char** argv) { Usage(); } - sandbox2::file_util::fileops::FDCloser in_fd_closer( - open(argv[opt_idx], O_RDONLY)); - - if (in_fd_closer.get() < 0) { - fprintf(stderr, "Can't open input file: %s\n", argv[opt_idx]); - return 1; - } - - sandbox2::file_util::fileops::FDCloser out_fd_closer( - open(".", O_TMPFILE | O_RDWR, S_IRUSR | S_IWUSR)); - - if (out_fd_closer.get() < 0) { - fprintf(stderr, "Can't create temporary output file: %s\n", argv[opt_idx]); - return 1; - } - guetzli::sandbox::TransactionParams params = { - in_fd_closer.get(), - out_fd_closer.get(), + argv[opt_idx], + argv[opt_idx + 1], verbose, quality, memlimit_mb @@ -119,29 +95,10 @@ int main(int argc, const char** argv) { guetzli::sandbox::GuetzliTransaction transaction(std::move(params)); auto result = transaction.Run(); - if (result.ok()) { - if (access(argv[opt_idx + 1], F_OK) != -1) { - if (remove(argv[opt_idx + 1]) < 0) { - fprintf(stderr, "Error deleting existing output file: %s\n", - argv[opt_idx + 1]); - return 1; - } - } - - std::stringstream path; - path << "/proc/self/fd/" << out_fd_closer.get(); - - if (linkat(AT_FDCWD, path.str().c_str(), AT_FDCWD, argv[opt_idx + 1], - AT_SYMLINK_FOLLOW) < 0) { - fprintf(stderr, "Error linking %s\n", - argv[opt_idx + 1]); - return 1; - } - } - else { - fprintf(stderr, "%s\n", result.ToString().c_str()); // Use cerr instead ? - return 1; + if (!result.ok()) { + std::cerr << result.ToString() << std::endl; + return EXIT_FAILURE; } - return 0; + return EXIT_SUCCESS; } diff --git a/oss-internship-2020/guetzli/guetzli_transaction.cc b/oss-internship-2020/guetzli/guetzli_transaction.cc index 2613bdf..64fac51 100644 --- a/oss-internship-2020/guetzli/guetzli_transaction.cc +++ b/oss-internship-2020/guetzli/guetzli_transaction.cc @@ -14,73 +14,49 @@ #include "guetzli_transaction.h" +#include +#include +#include +#include + #include #include namespace guetzli { namespace sandbox { -absl::Status GuetzliTransaction::Init() { - // Close remote fd if transaction is repeated - if (in_fd_.GetRemoteFd() != -1) { - SAPI_RETURN_IF_ERROR(in_fd_.CloseRemoteFd(sandbox()->GetRpcChannel())); - } - if (out_fd_.GetRemoteFd() != -1) { - SAPI_RETURN_IF_ERROR(out_fd_.CloseRemoteFd(sandbox()->GetRpcChannel())); +absl::Status GuetzliTransaction::Main() { + sapi::v::Fd in_fd(open(params_.in_file, O_RDONLY)); + + if (in_fd.GetValue() < 0) { + return absl::FailedPreconditionError( + "Error opening input file" + ); } - // Reposition back to the beginning of file - if (lseek(in_fd_.GetValue(), 0, SEEK_CUR) != 0) { - if (lseek(in_fd_.GetValue(), 0, SEEK_SET) != 0) { - return absl::FailedPreconditionError( - "Error returnig cursor to the beginning" - ); - } - } + SAPI_ASSIGN_OR_RETURN(image_type_, GetImageTypeFromFd(in_fd.GetValue())); + SAPI_RETURN_IF_ERROR(sandbox()->TransferToSandboxee(&in_fd)); - // Choosing between jpg and png modes - sapi::StatusOr image_type = GetImageTypeFromFd(in_fd_.GetValue()); - - if (!image_type.ok()) { - return image_type.status(); - } - - image_type_ = image_type.value(); - - SAPI_RETURN_IF_ERROR(sandbox()->TransferToSandboxee(&in_fd_)); - SAPI_RETURN_IF_ERROR(sandbox()->TransferToSandboxee(&out_fd_)); - - if (in_fd_.GetRemoteFd() < 0) { + if (in_fd.GetRemoteFd() < 0) { return absl::FailedPreconditionError( "Error receiving remote FD: remote input fd is set to -1"); } - if (out_fd_.GetRemoteFd() < 0) { - return absl::FailedPreconditionError( - "Error receiving remote FD: remote output fd is set to -1"); - } - in_fd_.OwnLocalFd(false); // FDCloser will close local fd - out_fd_.OwnLocalFd(false); // FDCloser will close local fd - - return absl::OkStatus(); -} - -absl::Status GuetzliTransaction::Main() { GuetzliApi api(sandbox()); sapi::v::LenVal output(0); sapi::v::Struct processing_params; - *processing_params.mutable_data() = {in_fd_.GetRemoteFd(), + *processing_params.mutable_data() = {in_fd.GetRemoteFd(), params_.verbose, params_.quality, params_.memlimit_mb }; - auto result_status = image_type_ == ImageType::kJpeg ? + auto result = image_type_ == ImageType::kJpeg ? api.ProcessJpeg(processing_params.PtrBefore(), output.PtrBoth()) : api.ProcessRgb(processing_params.PtrBefore(), output.PtrBoth()); - if (!result_status.value_or(false)) { + if (!result.value_or(false)) { std::stringstream error_stream; error_stream << "Error processing " << (image_type_ == ImageType::kJpeg ? "jpeg" : "rgb") << " data" @@ -91,7 +67,22 @@ absl::Status GuetzliTransaction::Main() { ); } - auto write_result = api.WriteDataToFd(out_fd_.GetRemoteFd(), + sapi::v::Fd out_fd(open(".", O_TMPFILE | O_RDWR, S_IRUSR | S_IWUSR)); + + if (out_fd.GetValue() < 0) { + return absl::FailedPreconditionError( + "Error creating temp output file" + ); + } + + SAPI_RETURN_IF_ERROR(sandbox()->TransferToSandboxee(&out_fd)); + + if (out_fd.GetRemoteFd() < 0) { + return absl::FailedPreconditionError( + "Error receiving remote FD: remote output fd is set to -1"); + } + + auto write_result = api.WriteDataToFd(out_fd.GetRemoteFd(), output.PtrBefore()); if (!write_result.value_or(false)) { @@ -100,21 +91,39 @@ absl::Status GuetzliTransaction::Main() { ); } + SAPI_RETURN_IF_ERROR(LinkOutFile(out_fd.GetValue())); + return absl::OkStatus(); } -time_t GuetzliTransaction::CalculateTimeLimitFromImageSize( - uint64_t pixels) const { - return (pixels / kMpixPixels + 5) * 60; +absl::Status GuetzliTransaction::LinkOutFile(int out_fd) const { + if (access(params_.out_file, F_OK) != -1) { + if (remove(params_.out_file) < 0) { + std::stringstream error; + error << "Error deleting existing output file: " << params_.out_file; + return absl::FailedPreconditionError(error.str()); + } + } + + std::stringstream path; + path << "/proc/self/fd/" << out_fd; + if (linkat(AT_FDCWD, path.str().c_str(), AT_FDCWD, params_.out_file, + AT_SYMLINK_FOLLOW) < 0) { + std::stringstream error; + error << "Error linking: " << params_.out_file; + return absl::FailedPreconditionError(error.str()); + } + + return absl::OkStatus(); } sapi::StatusOr GuetzliTransaction::GetImageTypeFromFd(int fd) const { static const unsigned char kPNGMagicBytes[] = { 0x89, 'P', 'N', 'G', '\r', '\n', 0x1a, '\n', }; - char read_buf[8]; + char read_buf[sizeof(kPNGMagicBytes)]; - if (read(fd, read_buf, 8) != 8) { + if (read(fd, read_buf, sizeof(kPNGMagicBytes)) != sizeof(kPNGMagicBytes)) { return absl::FailedPreconditionError( "Error determining type of the input file" ); diff --git a/oss-internship-2020/guetzli/guetzli_transaction.h b/oss-internship-2020/guetzli/guetzli_transaction.h index 2db681d..be66633 100644 --- a/oss-internship-2020/guetzli/guetzli_transaction.h +++ b/oss-internship-2020/guetzli/guetzli_transaction.h @@ -15,7 +15,6 @@ #ifndef GUETZLI_SANDBOXED_GUETZLI_TRANSACTION_H_ #define GUETZLI_SANDBOXED_GUETZLI_TRANSACTION_H_ -#include #include #include "sandboxed_api/transaction.h" @@ -32,8 +31,8 @@ enum class ImageType { }; struct TransactionParams { - int in_fd = -1; - int out_fd = -1; + const char* in_file = nullptr; + const char* out_file = nullptr; int verbose = 0; int quality = 0; int memlimit_mb = 0; @@ -43,35 +42,26 @@ struct TransactionParams { // Create a new one for each processing operation class GuetzliTransaction : public sapi::Transaction { public: - GuetzliTransaction(TransactionParams&& params) + GuetzliTransaction(TransactionParams params) : sapi::Transaction(std::make_unique()) , params_(std::move(params)) - , in_fd_(params_.in_fd) - , out_fd_(params_.out_fd) { //TODO: Add retry count as a parameter sapi::Transaction::set_retry_count(kDefaultTransactionRetryCount); - //TODO: Try to use sandbox().set_wall_limit instead of infinite time limit - sapi::Transaction::SetTimeLimit(0); + sapi::Transaction::SetTimeLimit(0); // Infinite time limit } private: - absl::Status Init() override; + //absl::Status Init() override; absl::Status Main() final; + absl::Status LinkOutFile(int out_fd) const; sapi::StatusOr GetImageTypeFromFd(int fd) const; - // As guetzli takes roughly 1 minute of CPU per 1 MPix we need to calculate - // approximate time for transaction to complete - time_t CalculateTimeLimitFromImageSize(uint64_t pixels) const; - const TransactionParams params_; - sapi::v::Fd in_fd_; - sapi::v::Fd out_fd_; ImageType image_type_ = ImageType::kJpeg; static const int kDefaultTransactionRetryCount = 0; - static const uint64_t kMpixPixels = 1'000'000; }; } // namespace sandbox diff --git a/oss-internship-2020/guetzli/tests/BUILD.bazel b/oss-internship-2020/guetzli/tests/BUILD.bazel index 34f3f76..a59237e 100644 --- a/oss-internship-2020/guetzli/tests/BUILD.bazel +++ b/oss-internship-2020/guetzli/tests/BUILD.bazel @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +licenses(["unencumbered"]) # code authored by Google + cc_test( name = "transaction_tests", srcs = ["guetzli_transaction_test.cc"], @@ -34,4 +36,4 @@ cc_test( ], size = "large", data = glob(["testdata/*"]) -) \ No newline at end of file +) diff --git a/oss-internship-2020/guetzli/tests/guetzli_sapi_test.cc b/oss-internship-2020/guetzli/tests/guetzli_sapi_test.cc index 71439d7..66f2ef6 100644 --- a/oss-internship-2020/guetzli/tests/guetzli_sapi_test.cc +++ b/oss-internship-2020/guetzli/tests/guetzli_sapi_test.cc @@ -39,9 +39,6 @@ constexpr const char* kInJpegFilename = "nature.jpg"; constexpr const char* kPngReferenceFilename = "bees_reference.jpg"; constexpr const char* kJpegReferenceFIlename = "nature_reference.jpg"; -constexpr int kPngExpectedSize = 38'625; -constexpr int kJpegExpectedSize = 10'816; - constexpr int kDefaultQualityTarget = 95; constexpr int kDefaultMemlimitMb = 6000; @@ -49,9 +46,7 @@ constexpr const char* kRelativePathToTestdata = "/guetzli_sandboxed/tests/testdata/"; std::string GetPathToInputFile(const char* filename) { - return std::string(getenv("TEST_SRCDIR")) - + std::string(kRelativePathToTestdata) - + std::string(filename); + return absl::StrCat(getenv("TEST_SRCDIR"), kRelativePathToTestdata, filename); } std::string ReadFromFile(const std::string& filename) { @@ -66,18 +61,6 @@ std::string ReadFromFile(const std::string& filename) { return result.str(); } -template -bool CompareBytesInLenValAndContainer(const sapi::v::LenVal& lenval, - const Container& container) { - return std::equal( - lenval.GetData(), lenval.GetData() + lenval.GetDataSize(), - container.begin(), - [](const uint8_t lhs, const auto rhs) { - return lhs == static_cast(rhs); - } - ); -} - } // namespace class GuetzliSapiTest : public ::testing::Test { @@ -110,14 +93,13 @@ TEST_F(GuetzliSapiTest, ProcessRGB) { auto processing_result = api_->ProcessRgb(processing_params.PtrBefore(), output.PtrBoth()); ASSERT_TRUE(processing_result.value_or(false)) << "Error processing rgb data"; - ASSERT_EQ(output.GetDataSize(), kPngExpectedSize) - << "Incorrect result data size"; std::string reference_data = ReadFromFile(GetPathToInputFile(kPngReferenceFilename)); ASSERT_EQ(output.GetDataSize(), reference_data.size()) << "Incorrect result data size"; - ASSERT_TRUE(CompareBytesInLenValAndContainer(output, reference_data)) - << "Processed data doesn't match reference output"; + ASSERT_EQ(std::string(output.GetData(), + output.GetData() + output.GetDataSize()), reference_data) + << "Processed data doesn't match reference output"; } // This test can take up to few minutes depending on your hardware @@ -138,14 +120,13 @@ TEST_F(GuetzliSapiTest, ProcessJpeg) { auto processing_result = api_->ProcessJpeg(processing_params.PtrBefore(), output.PtrBoth()); ASSERT_TRUE(processing_result.value_or(false)) << "Error processing jpg data"; - ASSERT_EQ(output.GetDataSize(), kJpegExpectedSize) - << "Incorrect result data size"; std::string reference_data = ReadFromFile(GetPathToInputFile(kJpegReferenceFIlename)); ASSERT_EQ(output.GetDataSize(), reference_data.size()) << "Incorrect result data size"; - ASSERT_TRUE(CompareBytesInLenValAndContainer(output, reference_data)) - << "Processed data doesn't match reference output"; + ASSERT_EQ(std::string(output.GetData(), + output.GetData() + output.GetDataSize()), reference_data) + << "Processed data doesn't match reference output"; } } // namespace tests diff --git a/oss-internship-2020/guetzli/tests/guetzli_transaction_test.cc b/oss-internship-2020/guetzli/tests/guetzli_transaction_test.cc index da76dde..4b33b61 100644 --- a/oss-internship-2020/guetzli/tests/guetzli_transaction_test.cc +++ b/oss-internship-2020/guetzli/tests/guetzli_transaction_test.cc @@ -34,6 +34,8 @@ namespace { constexpr const char* kInPngFilename = "bees.png"; constexpr const char* kInJpegFilename = "nature.jpg"; +constexpr const char* kOutJpegFilename = "out_jpeg.jpg"; +constexpr const char* kOutPngFilename = "out_png.png"; constexpr const char* kPngReferenceFilename = "bees_reference.jpg"; constexpr const char* kJpegReferenceFIlename = "nature_reference.jpg"; @@ -46,10 +48,8 @@ constexpr int kDefaultMemlimitMb = 6000; constexpr const char* kRelativePathToTestdata = "/guetzli_sandboxed/tests/testdata/"; -std::string GetPathToInputFile(const char* filename) { - return std::string(getenv("TEST_SRCDIR")) - + std::string(kRelativePathToTestdata) - + std::string(filename); +std::string GetPathToFile(const char* filename) { + return absl::StrCat(getenv("TEST_SRCDIR"), kRelativePathToTestdata, filename); } std::string ReadFromFile(const std::string& filename) { @@ -64,18 +64,35 @@ std::string ReadFromFile(const std::string& filename) { return result.str(); } +// Helper class to delete file after opening +class FileRemover { + public: + explicit FileRemover(const char* path) + : path_(path) + , fd_(open(path, O_RDONLY)) + {} + + ~FileRemover() { + close(fd_); + remove(path_); + } + + int get() const { return fd_; } + + private: + const char* path_; + int fd_; +}; + } // namespace TEST(GuetzliTransactionTest, TestTransactionJpg) { - sandbox2::file_util::fileops::FDCloser in_fd_closer( - open(GetPathToInputFile(kInJpegFilename).c_str(), O_RDONLY)); - ASSERT_TRUE(in_fd_closer.get() != -1) << "Error opening input jpg file"; - sandbox2::file_util::fileops::FDCloser out_fd_closer( - open(".", O_TMPFILE | O_RDWR, S_IRUSR | S_IWUSR)); - ASSERT_TRUE(out_fd_closer.get() != -1) << "Error creating temp output file"; + std::string in_path = GetPathToFile(kInJpegFilename); + std::string out_path = GetPathToFile(kOutJpegFilename); + TransactionParams params = { - in_fd_closer.get(), - out_fd_closer.get(), + in_path.c_str(), + out_path.c_str(), 0, kDefaultQualityTarget, kDefaultMemlimitMb @@ -86,17 +103,17 @@ TEST(GuetzliTransactionTest, TestTransactionJpg) { ASSERT_TRUE(result.ok()) << result.ToString(); } - ASSERT_TRUE(fcntl(out_fd_closer.get(), F_GETFD) != -1 || errno != EBADF) - << "Local output fd closed"; - auto reference_data = ReadFromFile(GetPathToInputFile(kJpegReferenceFIlename)); - auto output_size = lseek(out_fd_closer.get(), 0, SEEK_END); + auto reference_data = ReadFromFile(GetPathToFile(kJpegReferenceFIlename)); + FileRemover file_remover(out_path.c_str()); + ASSERT_TRUE(file_remover.get() != -1) << "Error opening output file"; + auto output_size = lseek(file_remover.get(), 0, SEEK_END); ASSERT_EQ(reference_data.size(), output_size) << "Different sizes of reference and returned data"; - ASSERT_EQ(lseek(out_fd_closer.get(), 0, SEEK_SET), 0) + ASSERT_EQ(lseek(file_remover.get(), 0, SEEK_SET), 0) << "Error repositioning out file"; std::unique_ptr buf(new char[output_size]); - auto status = read(out_fd_closer.get(), buf.get(), output_size); + auto status = read(file_remover.get(), buf.get(), output_size); ASSERT_EQ(status, output_size) << "Error reading data from temp output file"; ASSERT_TRUE( @@ -105,16 +122,13 @@ TEST(GuetzliTransactionTest, TestTransactionJpg) { } TEST(GuetzliTransactionTest, TestTransactionPng) { - sandbox2::file_util::fileops::FDCloser in_fd_closer( - open(GetPathToInputFile(kInPngFilename).c_str(), O_RDONLY)); - ASSERT_TRUE(in_fd_closer.get() != -1) << "Error opening input png file"; - sandbox2::file_util::fileops::FDCloser out_fd_closer( - open(".", O_TMPFILE | O_RDWR, S_IRUSR | S_IWUSR)); - ASSERT_TRUE(out_fd_closer.get() != -1) << "Error creating temp output file"; + std::string in_path = GetPathToFile(kInPngFilename); + std::string out_path = GetPathToFile(kOutPngFilename); + TransactionParams params = { - in_fd_closer.get(), - out_fd_closer.get(), - 0, + in_path.c_str(), + out_path.c_str(), + 0, kDefaultQualityTarget, kDefaultMemlimitMb }; @@ -124,17 +138,17 @@ TEST(GuetzliTransactionTest, TestTransactionPng) { ASSERT_TRUE(result.ok()) << result.ToString(); } - ASSERT_TRUE(fcntl(out_fd_closer.get(), F_GETFD) != -1 || errno != EBADF) - << "Local output fd closed"; - auto reference_data = ReadFromFile(GetPathToInputFile(kPngReferenceFilename)); - auto output_size = lseek(out_fd_closer.get(), 0, SEEK_END); + auto reference_data = ReadFromFile(GetPathToFile(kPngReferenceFilename)); + FileRemover file_remover(out_path.c_str()); + ASSERT_TRUE(file_remover.get() != -1) << "Error opening output file"; + auto output_size = lseek(file_remover.get(), 0, SEEK_END); ASSERT_EQ(reference_data.size(), output_size) << "Different sizes of reference and returned data"; - ASSERT_EQ(lseek(out_fd_closer.get(), 0, SEEK_SET), 0) + ASSERT_EQ(lseek(file_remover.get(), 0, SEEK_SET), 0) << "Error repositioning out file"; std::unique_ptr buf(new char[output_size]); - auto status = read(out_fd_closer.get(), buf.get(), output_size); + auto status = read(file_remover.get(), buf.get(), output_size); ASSERT_EQ(status, output_size) << "Error reading data from temp output file"; ASSERT_TRUE( diff --git a/oss-internship-2020/guetzli/third_party/butteraugli/BUILD b/oss-internship-2020/guetzli/third_party/butteraugli/BUILD deleted file mode 100755 index f553c0b..0000000 --- a/oss-internship-2020/guetzli/third_party/butteraugli/BUILD +++ /dev/null @@ -1,24 +0,0 @@ -cc_library( - name = "butteraugli_lib", - srcs = [ - "butteraugli/butteraugli.cc", - "butteraugli/butteraugli.h", - ], - hdrs = [ - "butteraugli/butteraugli.h", - ], - copts = ["-Wno-sign-compare"], - visibility = ["//visibility:public"], -) - -cc_binary( - name = "butteraugli", - srcs = ["butteraugli/butteraugli_main.cc"], - copts = ["-Wno-sign-compare"], - visibility = ["//visibility:public"], - deps = [ - ":butteraugli_lib", - "@jpeg_archive//:jpeg", - "@png_archive//:png", - ], -) \ No newline at end of file diff --git a/oss-internship-2020/guetzli/third_party/butteraugli/LICENSE b/oss-internship-2020/guetzli/third_party/butteraugli/LICENSE deleted file mode 100755 index 261eeb9..0000000 --- a/oss-internship-2020/guetzli/third_party/butteraugli/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - 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. diff --git a/oss-internship-2020/guetzli/third_party/butteraugli/README.md b/oss-internship-2020/guetzli/third_party/butteraugli/README.md deleted file mode 100755 index 4623442..0000000 --- a/oss-internship-2020/guetzli/third_party/butteraugli/README.md +++ /dev/null @@ -1,68 +0,0 @@ -# butteraugli - -> A tool for measuring perceived differences between images - -## Introduction - -Butteraugli is a project that estimates the psychovisual similarity of two -images. It gives a score for the images that is reliable in the domain of barely -noticeable differences. Butteraugli not only gives a scalar score, but also -computes a spatial map of the level of differences. - -One of the main motivations for this project is the statistical differences in -location and density of different color receptors, particularly the low density -of blue cones in the fovea. Another motivation comes from more accurate modeling -of ganglion cells, particularly the frequency space inhibition. - -## Use - -Butteraugli can work as a quality metric for lossy image and video compression. -On our small test corpus butteraugli performs better than our implementations of -the reference methods, psnrhsv-m, ssim, and our yuv-color-space variant of ssim. -One possible use is to define the quality level setting used in a jpeg -compressor, or to compare two or more compression methods at the same level of -psychovisual differences. - -Butteraugli is intended to be a research tool more than a practical tool for -choosing compression formats. We don't know how well butteraugli performs with -major deformations -- we have mostly tuned it within a small range of quality, -roughly corresponding to jpeg qualities 90 to 95. - -## Interface - -Only a C++ interface is provided. The interface takes two images and outputs a -map together with a scalar value defining the difference. The scalar value can -be compared to two reference values that divide the value space into three -experience classes: 'great', 'acceptable' and 'not acceptable'. - -## Build instructions - -Install [Bazel](http://bazel.build) by following the -[instructions](https://www.bazel.build/docs/install.html). Run `bazel build -c opt -//:butteraugli` in the directory that contains this README file to build the -[command-line utility](#cmdline-tool). If you want to use Butteraugli as a -library, depend on the `//:butteraugli_lib` target. - -Alternatively, you can use the Makefile provided in the `butteraugli` directory, -after ensuring that [libpng](http://www.libpng.org/) and -[libjpeg](http://ijg.org/) are installed. On some systems you might need to also -install corresponding `-dev` packages. - -The code is portable and also compiles on Windows after defining -`_CRT_SECURE_NO_WARNINGS` in the project settings. - -## Command-line utility {#cmdline-tool} - -Butteraugli, apart from the library, comes bundled with a comparison tool. The -comparison tool supports PNG and JPG images as inputs. To compare images, run: - -``` -butteraugli image1.{png|jpg} image2.{png|jpg} -``` - -The tool can also produce a heatmap of differences between images. The heatmap -will be output as a PNM image. To produce one, run: - -``` -butteraugli image1.{png|jpg} image2.{png|jpg} heatmap.pnm -``` diff --git a/oss-internship-2020/guetzli/third_party/butteraugli/WORKSPACE b/oss-internship-2020/guetzli/third_party/butteraugli/WORKSPACE deleted file mode 100755 index 4d6ed65..0000000 --- a/oss-internship-2020/guetzli/third_party/butteraugli/WORKSPACE +++ /dev/null @@ -1,25 +0,0 @@ -workspace(name = "butteraugli") - -new_http_archive( - name = "png_archive", - url = "http://github.com/glennrp/libpng/archive/v1.2.57.zip", - sha256 = "a941dc09ca00148fe7aaf4ecdd6a67579c293678ed1e1cf633b5ffc02f4f8cf7", - strip_prefix = "libpng-1.2.57", - build_file = "png.BUILD", -) - -new_http_archive( - name = "zlib_archive", - url = "http://zlib.net/fossils/zlib-1.2.10.tar.gz", - sha256 = "8d7e9f698ce48787b6e1c67e6bff79e487303e66077e25cb9784ac8835978017", - strip_prefix = "zlib-1.2.10", - build_file = "zlib.BUILD", -) - -new_http_archive( - name = "jpeg_archive", - url = "http://www.ijg.org/files/jpegsrc.v9b.tar.gz", - sha256 = "240fd398da741669bf3c90366f58452ea59041cacc741a489b99f2f6a0bad052", - strip_prefix = "jpeg-9b", - build_file = "jpeg.BUILD", -) diff --git a/oss-internship-2020/guetzli/third_party/butteraugli/butteraugli/Makefile b/oss-internship-2020/guetzli/third_party/butteraugli/butteraugli/Makefile deleted file mode 100755 index 76b3a9b..0000000 --- a/oss-internship-2020/guetzli/third_party/butteraugli/butteraugli/Makefile +++ /dev/null @@ -1,7 +0,0 @@ -LDLIBS += -lpng -ljpeg -CXXFLAGS += -std=c++11 -I.. -LINK.o = $(LINK.cc) - -all: butteraugli.o butteraugli_main.o butteraugli - -butteraugli: butteraugli.o butteraugli_main.o diff --git a/oss-internship-2020/guetzli/third_party/butteraugli/butteraugli/butteraugli.cc b/oss-internship-2020/guetzli/third_party/butteraugli/butteraugli/butteraugli.cc deleted file mode 100755 index 77c91cc..0000000 --- a/oss-internship-2020/guetzli/third_party/butteraugli/butteraugli/butteraugli.cc +++ /dev/null @@ -1,1994 +0,0 @@ -// Copyright 2016 Google Inc. All Rights Reserved. -// -// 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. -// -// Author: Jyrki Alakuijala (jyrki.alakuijala@gmail.com) -// -// The physical architecture of butteraugli is based on the following naming -// convention: -// * Opsin - dynamics of the photosensitive chemicals in the retina -// with their immediate electrical processing -// * Xyb - hybrid opponent/trichromatic color space -// x is roughly red-subtract-green. -// y is yellow. -// b is blue. -// Xyb values are computed from Opsin mixing, not directly from rgb. -// * Mask - for visual masking -// * Hf - color modeling for spatially high-frequency features -// * Lf - color modeling for spatially low-frequency features -// * Diffmap - to cluster and build an image of error between the images -// * Blur - to hold the smoothing code - -#include "butteraugli/butteraugli.h" - -#include -#include -#include -#include -#include - -#include -#include - - -// Restricted pointers speed up Convolution(); MSVC uses a different keyword. -#ifdef _MSC_VER -#define __restrict__ __restrict -#endif - -#ifndef PROFILER_ENABLED -#define PROFILER_ENABLED 0 -#endif -#if PROFILER_ENABLED -#else -#define PROFILER_FUNC -#define PROFILER_ZONE(name) -#endif - -namespace butteraugli { - -void *CacheAligned::Allocate(const size_t bytes) { - char *const allocated = static_cast(malloc(bytes + kCacheLineSize)); - if (allocated == nullptr) { - return nullptr; - } - const uintptr_t misalignment = - reinterpret_cast(allocated) & (kCacheLineSize - 1); - // malloc is at least kPointerSize aligned, so we can store the "allocated" - // pointer immediately before the aligned memory. - assert(misalignment % kPointerSize == 0); - char *const aligned = allocated + kCacheLineSize - misalignment; - memcpy(aligned - kPointerSize, &allocated, kPointerSize); - return BUTTERAUGLI_ASSUME_ALIGNED(aligned, 64); -} - -void CacheAligned::Free(void *aligned_pointer) { - if (aligned_pointer == nullptr) { - return; - } - char *const aligned = static_cast(aligned_pointer); - assert(reinterpret_cast(aligned) % kCacheLineSize == 0); - char *allocated; - memcpy(&allocated, aligned - kPointerSize, kPointerSize); - assert(allocated <= aligned - kPointerSize); - assert(allocated >= aligned - kCacheLineSize); - free(allocated); -} - -static inline bool IsNan(const float x) { - uint32_t bits; - memcpy(&bits, &x, sizeof(bits)); - const uint32_t bitmask_exp = 0x7F800000; - return (bits & bitmask_exp) == bitmask_exp && (bits & 0x7FFFFF); -} - -static inline bool IsNan(const double x) { - uint64_t bits; - memcpy(&bits, &x, sizeof(bits)); - return (0x7ff0000000000001ULL <= bits && bits <= 0x7fffffffffffffffULL) || - (0xfff0000000000001ULL <= bits && bits <= 0xffffffffffffffffULL); -} - -static inline void CheckImage(const ImageF &image, const char *name) { - for (size_t y = 0; y < image.ysize(); ++y) { - const float * const BUTTERAUGLI_RESTRICT row = image.Row(y); - for (size_t x = 0; x < image.xsize(); ++x) { - if (IsNan(row[x])) { - printf("Image %s @ %lu,%lu (of %lu,%lu)\n", name, x, y, image.xsize(), - image.ysize()); - exit(1); - } - } - } -} - -#if BUTTERAUGLI_ENABLE_CHECKS - -#define CHECK_NAN(x, str) \ - do { \ - if (IsNan(x)) { \ - printf("%d: %s\n", __LINE__, str); \ - abort(); \ - } \ - } while (0) - -#define CHECK_IMAGE(image, name) CheckImage(image, name) - -#else - -#define CHECK_NAN(x, str) -#define CHECK_IMAGE(image, name) - -#endif - - -// Purpose of kInternalGoodQualityThreshold: -// Normalize 'ok' image degradation to 1.0 across different versions of -// butteraugli. -static const double kInternalGoodQualityThreshold = 20.35; -static const double kGlobalScale = 1.0 / kInternalGoodQualityThreshold; - -inline float DotProduct(const float u[3], const float v[3]) { - return u[0] * v[0] + u[1] * v[1] + u[2] * v[2]; -} - -std::vector ComputeKernel(float sigma) { - const float m = 2.25; // Accuracy increases when m is increased. - const float scaler = -1.0 / (2 * sigma * sigma); - const int diff = std::max(1, m * fabs(sigma)); - std::vector kernel(2 * diff + 1); - for (int i = -diff; i <= diff; ++i) { - kernel[i + diff] = exp(scaler * i * i); - } - return kernel; -} - -void ConvolveBorderColumn( - const ImageF& in, - const std::vector& kernel, - const float weight_no_border, - const float border_ratio, - const size_t x, - float* const BUTTERAUGLI_RESTRICT row_out) { - const int offset = kernel.size() / 2; - int minx = x < offset ? 0 : x - offset; - int maxx = std::min(in.xsize() - 1, x + offset); - float weight = 0.0f; - for (int j = minx; j <= maxx; ++j) { - weight += kernel[j - x + offset]; - } - // Interpolate linearly between the no-border scaling and border scaling. - weight = (1.0f - border_ratio) * weight + border_ratio * weight_no_border; - float scale = 1.0f / weight; - for (size_t y = 0; y < in.ysize(); ++y) { - const float* const BUTTERAUGLI_RESTRICT row_in = in.Row(y); - float sum = 0.0f; - for (int j = minx; j <= maxx; ++j) { - sum += row_in[j] * kernel[j - x + offset]; - } - row_out[y] = sum * scale; - } -} - -// Computes a horizontal convolution and transposes the result. -ImageF Convolution(const ImageF& in, - const std::vector& kernel, - const float border_ratio) { - ImageF out(in.ysize(), in.xsize()); - const int len = kernel.size(); - const int offset = kernel.size() / 2; - float weight_no_border = 0.0f; - for (int j = 0; j < len; ++j) { - weight_no_border += kernel[j]; - } - float scale_no_border = 1.0f / weight_no_border; - const int border1 = in.xsize() <= offset ? in.xsize() : offset; - const int border2 = in.xsize() - offset; - std::vector scaled_kernel = kernel; - for (int i = 0; i < scaled_kernel.size(); ++i) { - scaled_kernel[i] *= scale_no_border; - } - // left border - for (int x = 0; x < border1; ++x) { - ConvolveBorderColumn(in, kernel, weight_no_border, border_ratio, x, - out.Row(x)); - } - // middle - for (size_t y = 0; y < in.ysize(); ++y) { - const float* const BUTTERAUGLI_RESTRICT row_in = in.Row(y); - for (int x = border1; x < border2; ++x) { - const int d = x - offset; - float* const BUTTERAUGLI_RESTRICT row_out = out.Row(x); - float sum = 0.0f; - for (int j = 0; j < len; ++j) { - sum += row_in[d + j] * scaled_kernel[j]; - } - row_out[y] = sum; - } - } - // right border - for (int x = border2; x < in.xsize(); ++x) { - ConvolveBorderColumn(in, kernel, weight_no_border, border_ratio, x, - out.Row(x)); - } - return out; -} - -// A blur somewhat similar to a 2D Gaussian blur. -// See: https://en.wikipedia.org/wiki/Gaussian_blur -ImageF Blur(const ImageF& in, float sigma, float border_ratio) { - std::vector kernel = ComputeKernel(sigma); - return Convolution(Convolution(in, kernel, border_ratio), - kernel, border_ratio); -} - -// Clamping linear interpolator. -inline double InterpolateClampNegative(const double *array, - int size, double ix) { - if (ix < 0) { - ix = 0; - } - int baseix = static_cast(ix); - double res; - if (baseix >= size - 1) { - res = array[size - 1]; - } else { - double mix = ix - baseix; - int nextix = baseix + 1; - res = array[baseix] + mix * (array[nextix] - array[baseix]); - } - return res; -} - -double GammaMinArg() { - double out0, out1, out2; - OpsinAbsorbance(0.0, 0.0, 0.0, &out0, &out1, &out2); - return std::min(out0, std::min(out1, out2)); -} - -double GammaMaxArg() { - double out0, out1, out2; - OpsinAbsorbance(255.0, 255.0, 255.0, &out0, &out1, &out2); - return std::max(out0, std::max(out1, out2)); -} - -double SimpleGamma(double v) { - static const double kGamma = 0.372322653176; - static const double limit = 37.8000499603; - double bright = v - limit; - if (bright >= 0) { - static const double mul = 0.0950819040934; - v -= bright * mul; - } - { - static const double limit2 = 74.6154406429; - double bright2 = v - limit2; - if (bright2 >= 0) { - static const double mul = 0.01; - v -= bright2 * mul; - } - } - { - static const double limit2 = 82.8505938033; - double bright2 = v - limit2; - if (bright2 >= 0) { - static const double mul = 0.0316722592629; - v -= bright2 * mul; - } - } - { - static const double limit2 = 92.8505938033; - double bright2 = v - limit2; - if (bright2 >= 0) { - static const double mul = 0.221249885752; - v -= bright2 * mul; - } - } - { - static const double limit2 = 102.8505938033; - double bright2 = v - limit2; - if (bright2 >= 0) { - static const double mul = 0.0402547853939; - v -= bright2 * mul; - } - } - { - static const double limit2 = 112.8505938033; - double bright2 = v - limit2; - if (bright2 >= 0) { - static const double mul = 0.021471798711500003; - v -= bright2 * mul; - } - } - static const double offset = 0.106544447664; - static const double scale = 10.7950943969; - double retval = scale * (offset + pow(v, kGamma)); - return retval; -} - -static inline double Gamma(double v) { - //return SimpleGamma(v); - return GammaPolynomial(v); -} - -std::vector OpsinDynamicsImage(const std::vector& rgb) { - PROFILER_FUNC; - std::vector xyb(3); - std::vector blurred(3); - const double kSigma = 1.2; - for (int i = 0; i < 3; ++i) { - xyb[i] = ImageF(rgb[i].xsize(), rgb[i].ysize()); - blurred[i] = Blur(rgb[i], kSigma, 0.0f); - } - for (size_t y = 0; y < rgb[0].ysize(); ++y) { - const float* const BUTTERAUGLI_RESTRICT row_r = rgb[0].Row(y); - const float* const BUTTERAUGLI_RESTRICT row_g = rgb[1].Row(y); - const float* const BUTTERAUGLI_RESTRICT row_b = rgb[2].Row(y); - const float* const BUTTERAUGLI_RESTRICT row_blurred_r = blurred[0].Row(y); - const float* const BUTTERAUGLI_RESTRICT row_blurred_g = blurred[1].Row(y); - const float* const BUTTERAUGLI_RESTRICT row_blurred_b = blurred[2].Row(y); - float* const BUTTERAUGLI_RESTRICT row_out_x = xyb[0].Row(y); - float* const BUTTERAUGLI_RESTRICT row_out_y = xyb[1].Row(y); - float* const BUTTERAUGLI_RESTRICT row_out_b = xyb[2].Row(y); - for (size_t x = 0; x < rgb[0].xsize(); ++x) { - float sensitivity[3]; - { - // Calculate sensitivity based on the smoothed image gamma derivative. - float pre_mixed0, pre_mixed1, pre_mixed2; - OpsinAbsorbance(row_blurred_r[x], row_blurred_g[x], row_blurred_b[x], - &pre_mixed0, &pre_mixed1, &pre_mixed2); - // TODO: use new polynomial to compute Gamma(x)/x derivative. - sensitivity[0] = Gamma(pre_mixed0) / pre_mixed0; - sensitivity[1] = Gamma(pre_mixed1) / pre_mixed1; - sensitivity[2] = Gamma(pre_mixed2) / pre_mixed2; - } - float cur_mixed0, cur_mixed1, cur_mixed2; - OpsinAbsorbance(row_r[x], row_g[x], row_b[x], - &cur_mixed0, &cur_mixed1, &cur_mixed2); - cur_mixed0 *= sensitivity[0]; - cur_mixed1 *= sensitivity[1]; - cur_mixed2 *= sensitivity[2]; - RgbToXyb(cur_mixed0, cur_mixed1, cur_mixed2, - &row_out_x[x], &row_out_y[x], &row_out_b[x]); - } - } - return xyb; -} - -// Make area around zero less important (remove it). -static BUTTERAUGLI_INLINE float RemoveRangeAroundZero(float w, float x) { - return x > w ? x - w : x < -w ? x + w : 0.0f; -} - -// Make area around zero more important (2x it until the limit). -static BUTTERAUGLI_INLINE float AmplifyRangeAroundZero(float w, float x) { - return x > w ? x + w : x < -w ? x - w : 2.0f * x; -} - -// XybLowFreqToVals converts from low-frequency XYB space to the 'vals' space. -// Vals space can be converted to L2-norm space (Euclidean and normalized) -// through visual masking. -template -BUTTERAUGLI_INLINE void XybLowFreqToVals(const V &x, const V &y, const V &b_arg, - V *BUTTERAUGLI_RESTRICT valx, - V *BUTTERAUGLI_RESTRICT valy, - V *BUTTERAUGLI_RESTRICT valb) { - static const double xmuli = 5.57547552483; - static const double ymuli = 1.20828034498; - static const double bmuli = 6.08319517575; - static const double y_to_b_muli = -0.628811683685; - - const V xmul(xmuli); - const V ymul(ymuli); - const V bmul(bmuli); - const V y_to_b_mul(y_to_b_muli); - const V b = b_arg + y_to_b_mul * y; - *valb = b * bmul; - *valx = x * xmul; - *valy = y * ymul; -} - -static ImageF SuppressInBrightAreas(size_t xsize, size_t ysize, - double mul, double mul2, double reg, - const ImageF& hf, - const ImageF& brightness) { - ImageF inew(xsize, ysize); - for (size_t y = 0; y < ysize; ++y) { - const float* const rowhf = hf.Row(y); - const float* const rowbr = brightness.Row(y); - float* const rownew = inew.Row(y); - for (size_t x = 0; x < xsize; ++x) { - float v = rowhf[x]; - float scaler = mul * reg / (reg + rowbr[x]); - rownew[x] = scaler * v; - } - } - return inew; -} - - -static float SuppressHfInBrightAreas(float hf, float brightness, - float mul, float reg) { - float scaler = mul * reg / (reg + brightness); - return scaler * hf; -} - -static float SuppressUhfInBrightAreas(float hf, float brightness, - float mul, float reg) { - float scaler = mul * reg / (reg + brightness); - return scaler * hf; -} - -static float MaximumClamp(float v, float maxval) { - static const double kMul = 0.688059627878; - if (v >= maxval) { - v -= maxval; - v *= kMul; - v += maxval; - } else if (v < -maxval) { - v += maxval; - v *= kMul; - v -= maxval; - } - return v; -} - -static ImageF MaximumClamping(size_t xsize, size_t ysize, const ImageF& ix, - double yw) { - static const double kMul = 0.688059627878; - ImageF inew(xsize, ysize); - for (size_t y = 0; y < ysize; ++y) { - const float* const rowx = ix.Row(y); - float* const rownew = inew.Row(y); - for (size_t x = 0; x < xsize; ++x) { - double v = rowx[x]; - if (v >= yw) { - v -= yw; - v *= kMul; - v += yw; - } else if (v < -yw) { - v += yw; - v *= kMul; - v -= yw; - } - rownew[x] = v; - } - } - return inew; -} - -static ImageF SuppressXByY(size_t xsize, size_t ysize, - const ImageF& ix, const ImageF& iy, - const double yw) { - static const double s = 0.745954517135; - ImageF inew(xsize, ysize); - for (size_t y = 0; y < ysize; ++y) { - const float* const rowx = ix.Row(y); - const float* const rowy = iy.Row(y); - float* const rownew = inew.Row(y); - for (size_t x = 0; x < xsize; ++x) { - const double xval = rowx[x]; - const double yval = rowy[x]; - const double scaler = s + (yw * (1.0 - s)) / (yw + yval * yval); - rownew[x] = scaler * xval; - } - } - return inew; -} - -static void SeparateFrequencies( - size_t xsize, size_t ysize, - const std::vector& xyb, - PsychoImage &ps) { - PROFILER_FUNC; - ps.lf.resize(3); // XYB - ps.mf.resize(3); // XYB - ps.hf.resize(2); // XY - ps.uhf.resize(2); // XY - // Extract lf ... - static const double kSigmaLf = 7.46953768697; - static const double kSigmaHf = 3.734768843485; - static const double kSigmaUhf = 1.8673844217425; - // At borders we move some more of the energy to the high frequency - // parts, because there can be unfortunate continuations in tiling - // background color etc. So we want to represent the borders with - // some more accuracy. - static double border_lf = -0.00457628248637; - static double border_mf = -0.271277366628; - static double border_hf = 0.147068973249; - for (int i = 0; i < 3; ++i) { - ps.lf[i] = Blur(xyb[i], kSigmaLf, border_lf); - // ... and keep everything else in mf. - ps.mf[i] = ImageF(xsize, ysize); - for (size_t y = 0; y < ysize; ++y) { - for (size_t x = 0; x < xsize; ++x) { - ps.mf[i].Row(y)[x] = xyb[i].Row(y)[x] - ps.lf[i].Row(y)[x]; - } - } - if (i == 2) { - ps.mf[i] = Blur(ps.mf[i], kSigmaHf, border_mf); - break; - } - // Divide mf into mf and hf. - ps.hf[i] = ImageF(xsize, ysize); - for (size_t y = 0; y < ysize; ++y) { - float* BUTTERAUGLI_RESTRICT const row_mf = ps.mf[i].Row(y); - float* BUTTERAUGLI_RESTRICT const row_hf = ps.hf[i].Row(y); - for (size_t x = 0; x < xsize; ++x) { - row_hf[x] = row_mf[x]; - } - } - ps.mf[i] = Blur(ps.mf[i], kSigmaHf, border_mf); - static const double w0 = 0.120079806822; - static const double w1 = 0.03430529365; - if (i == 0) { - for (size_t y = 0; y < ysize; ++y) { - float* BUTTERAUGLI_RESTRICT const row_mf = ps.mf[0].Row(y); - float* BUTTERAUGLI_RESTRICT const row_hf = ps.hf[0].Row(y); - for (size_t x = 0; x < xsize; ++x) { - row_hf[x] -= row_mf[x]; - row_mf[x] = RemoveRangeAroundZero(w0, row_mf[x]); - } - } - } else { - for (size_t y = 0; y < ysize; ++y) { - float* BUTTERAUGLI_RESTRICT const row_mf = ps.mf[1].Row(y); - float* BUTTERAUGLI_RESTRICT const row_hf = ps.hf[1].Row(y); - for (size_t x = 0; x < xsize; ++x) { - row_hf[x] -= row_mf[x]; - row_mf[x] = AmplifyRangeAroundZero(w1, row_mf[x]); - } - } - } - } - // Suppress red-green by intensity change in the high freq channels. - static const double suppress = 2.96534974403; - ps.hf[0] = SuppressXByY(xsize, ysize, ps.hf[0], ps.hf[1], suppress); - - for (int i = 0; i < 2; ++i) { - // Divide hf into hf and uhf. - ps.uhf[i] = ImageF(xsize, ysize); - for (size_t y = 0; y < ysize; ++y) { - float* BUTTERAUGLI_RESTRICT const row_uhf = ps.uhf[i].Row(y); - float* BUTTERAUGLI_RESTRICT const row_hf = ps.hf[i].Row(y); - for (size_t x = 0; x < xsize; ++x) { - row_uhf[x] = row_hf[x]; - } - } - ps.hf[i] = Blur(ps.hf[i], kSigmaUhf, border_hf); - static const double kRemoveHfRange = 0.0287615200377; - static const double kMaxclampHf = 78.8223237675; - static const double kMaxclampUhf = 5.8907152736; - static const float kMulSuppressHf = 1.10684769012; - static const float kMulRegHf = 0.478741530298; - static const float kRegHf = 2000 * kMulRegHf; - static const float kMulSuppressUhf = 1.76905001176; - static const float kMulRegUhf = 0.310148420674; - static const float kRegUhf = 2000 * kMulRegUhf; - - if (i == 0) { - for (size_t y = 0; y < ysize; ++y) { - float* BUTTERAUGLI_RESTRICT const row_uhf = ps.uhf[0].Row(y); - float* BUTTERAUGLI_RESTRICT const row_hf = ps.hf[0].Row(y); - for (size_t x = 0; x < xsize; ++x) { - row_uhf[x] -= row_hf[x]; - row_hf[x] = RemoveRangeAroundZero(kRemoveHfRange, row_hf[x]); - } - } - } else { - for (size_t y = 0; y < ysize; ++y) { - float* BUTTERAUGLI_RESTRICT const row_uhf = ps.uhf[1].Row(y); - float* BUTTERAUGLI_RESTRICT const row_hf = ps.hf[1].Row(y); - float* BUTTERAUGLI_RESTRICT const row_lf = ps.lf[1].Row(y); - for (size_t x = 0; x < xsize; ++x) { - row_uhf[x] -= row_hf[x]; - row_hf[x] = MaximumClamp(row_hf[x], kMaxclampHf); - row_uhf[x] = MaximumClamp(row_uhf[x], kMaxclampUhf); - row_uhf[x] = SuppressUhfInBrightAreas(row_uhf[x], row_lf[x], - kMulSuppressUhf, kRegUhf); - row_hf[x] = SuppressHfInBrightAreas(row_hf[x], row_lf[x], - kMulSuppressHf, kRegHf); - - } - } - } - } - // Modify range around zero code only concerns the high frequency - // planes and only the X and Y channels. - // Convert low freq xyb to vals space so that we can do a simple squared sum - // diff on the low frequencies later. - for (size_t y = 0; y < ysize; ++y) { - float* BUTTERAUGLI_RESTRICT const row_x = ps.lf[0].Row(y); - float* BUTTERAUGLI_RESTRICT const row_y = ps.lf[1].Row(y); - float* BUTTERAUGLI_RESTRICT const row_b = ps.lf[2].Row(y); - for (size_t x = 0; x < xsize; ++x) { - float valx, valy, valb; - XybLowFreqToVals(row_x[x], row_y[x], row_b[x], &valx, &valy, &valb); - row_x[x] = valx; - row_y[x] = valy; - row_b[x] = valb; - } - } -} - -static void SameNoiseLevels(const ImageF& i0, const ImageF& i1, - const double kSigma, - const double w, - const double maxclamp, - ImageF* BUTTERAUGLI_RESTRICT diffmap) { - ImageF blurred(i0.xsize(), i0.ysize()); - for (size_t y = 0; y < i0.ysize(); ++y) { - const float* BUTTERAUGLI_RESTRICT const row0 = i0.Row(y); - const float* BUTTERAUGLI_RESTRICT const row1 = i1.Row(y); - float* BUTTERAUGLI_RESTRICT const to = blurred.Row(y); - for (size_t x = 0; x < i0.xsize(); ++x) { - double v0 = fabs(row0[x]); - double v1 = fabs(row1[x]); - if (v0 > maxclamp) v0 = maxclamp; - if (v1 > maxclamp) v1 = maxclamp; - to[x] = v0 - v1; - } - - } - blurred = Blur(blurred, kSigma, 0.0); - for (size_t y = 0; y < i0.ysize(); ++y) { - const float* BUTTERAUGLI_RESTRICT const row = blurred.Row(y); - float* BUTTERAUGLI_RESTRICT const row_diff = diffmap->Row(y); - for (size_t x = 0; x < i0.xsize(); ++x) { - double diff = row[x]; - row_diff[x] += w * diff * diff; - } - } -} - -static void L2Diff(const ImageF& i0, const ImageF& i1, const double w, - ImageF* BUTTERAUGLI_RESTRICT diffmap) { - if (w == 0) { - return; - } - for (size_t y = 0; y < i0.ysize(); ++y) { - const float* BUTTERAUGLI_RESTRICT const row0 = i0.Row(y); - const float* BUTTERAUGLI_RESTRICT const row1 = i1.Row(y); - float* BUTTERAUGLI_RESTRICT const row_diff = diffmap->Row(y); - for (size_t x = 0; x < i0.xsize(); ++x) { - double diff = row0[x] - row1[x]; - row_diff[x] += w * diff * diff; - } - } -} - -// i0 is the original image. -// i1 is the deformed copy. -static void L2DiffAsymmetric(const ImageF& i0, const ImageF& i1, - double w_0gt1, - double w_0lt1, - ImageF* BUTTERAUGLI_RESTRICT diffmap) { - if (w_0gt1 == 0 && w_0lt1 == 0) { - return; - } - w_0gt1 *= 0.8; - w_0lt1 *= 0.8; - for (size_t y = 0; y < i0.ysize(); ++y) { - const float* BUTTERAUGLI_RESTRICT const row0 = i0.Row(y); - const float* BUTTERAUGLI_RESTRICT const row1 = i1.Row(y); - float* BUTTERAUGLI_RESTRICT const row_diff = diffmap->Row(y); - for (size_t x = 0; x < i0.xsize(); ++x) { - // Primary symmetric quadratic objective. - double diff = row0[x] - row1[x]; - row_diff[x] += w_0gt1 * diff * diff; - - // Secondary half-open quadratic objectives. - const double fabs0 = fabs(row0[x]); - const double too_small = 0.4 * fabs0; - const double too_big = 1.0 * fabs0; - - if (row0[x] < 0) { - if (row1[x] > -too_small) { - double v = row1[x] + too_small; - row_diff[x] += w_0lt1 * v * v; - } else if (row1[x] < -too_big) { - double v = -row1[x] - too_big; - row_diff[x] += w_0lt1 * v * v; - } - } else { - if (row1[x] < too_small) { - double v = too_small - row1[x]; - row_diff[x] += w_0lt1 * v * v; - } else if (row1[x] > too_big) { - double v = row1[x] - too_big; - row_diff[x] += w_0lt1 * v * v; - } - } - } - } -} - -// Making a cluster of local errors to be more impactful than -// just a single error. -ImageF CalculateDiffmap(const ImageF& diffmap_in) { - PROFILER_FUNC; - // Take square root. - ImageF diffmap(diffmap_in.xsize(), diffmap_in.ysize()); - static const float kInitialSlope = 100.0f; - for (size_t y = 0; y < diffmap.ysize(); ++y) { - const float* const BUTTERAUGLI_RESTRICT row_in = diffmap_in.Row(y); - float* const BUTTERAUGLI_RESTRICT row_out = diffmap.Row(y); - for (size_t x = 0; x < diffmap.xsize(); ++x) { - const float orig_val = row_in[x]; - // TODO(b/29974893): Until that is fixed do not call sqrt on very small - // numbers. - row_out[x] = (orig_val < (1.0f / (kInitialSlope * kInitialSlope)) - ? kInitialSlope * orig_val - : std::sqrt(orig_val)); - } - } - { - static const double kSigma = 1.72547472444; - static const double mul1 = 0.458794906198; - static const float scale = 1.0f / (1.0f + mul1); - static const double border_ratio = 1.0; // 2.01209066992; - ImageF blurred = Blur(diffmap, kSigma, border_ratio); - for (int y = 0; y < diffmap.ysize(); ++y) { - const float* const BUTTERAUGLI_RESTRICT row_blurred = blurred.Row(y); - float* const BUTTERAUGLI_RESTRICT row = diffmap.Row(y); - for (int x = 0; x < diffmap.xsize(); ++x) { - row[x] += mul1 * row_blurred[x]; - row[x] *= scale; - } - } - } - return diffmap; -} - -void MaskPsychoImage(const PsychoImage& pi0, const PsychoImage& pi1, - const size_t xsize, const size_t ysize, - std::vector* BUTTERAUGLI_RESTRICT mask, - std::vector* BUTTERAUGLI_RESTRICT mask_dc) { - std::vector mask_xyb0 = CreatePlanes(xsize, ysize, 3); - std::vector mask_xyb1 = CreatePlanes(xsize, ysize, 3); - static const double muls[4] = { - 0, - 1.64178305129, - 0.831081703362, - 3.23680933546, - }; - for (int i = 0; i < 2; ++i) { - double a = muls[2 * i]; - double b = muls[2 * i + 1]; - for (size_t y = 0; y < ysize; ++y) { - const float* const BUTTERAUGLI_RESTRICT row_hf0 = pi0.hf[i].Row(y); - const float* const BUTTERAUGLI_RESTRICT row_hf1 = pi1.hf[i].Row(y); - const float* const BUTTERAUGLI_RESTRICT row_uhf0 = pi0.uhf[i].Row(y); - const float* const BUTTERAUGLI_RESTRICT row_uhf1 = pi1.uhf[i].Row(y); - float* const BUTTERAUGLI_RESTRICT row0 = mask_xyb0[i].Row(y); - float* const BUTTERAUGLI_RESTRICT row1 = mask_xyb1[i].Row(y); - for (size_t x = 0; x < xsize; ++x) { - row0[x] = a * row_uhf0[x] + b * row_hf0[x]; - row1[x] = a * row_uhf1[x] + b * row_hf1[x]; - } - } - } - Mask(mask_xyb0, mask_xyb1, mask, mask_dc); -} - -ButteraugliComparator::ButteraugliComparator(const std::vector& rgb0) - : xsize_(rgb0[0].xsize()), - ysize_(rgb0[0].ysize()), - num_pixels_(xsize_ * ysize_) { - if (xsize_ < 8 || ysize_ < 8) return; - std::vector xyb0 = OpsinDynamicsImage(rgb0); - SeparateFrequencies(xsize_, ysize_, xyb0, pi0_); -} - -void ButteraugliComparator::Mask( - std::vector* BUTTERAUGLI_RESTRICT mask, - std::vector* BUTTERAUGLI_RESTRICT mask_dc) const { - MaskPsychoImage(pi0_, pi0_, xsize_, ysize_, mask, mask_dc); -} - -void ButteraugliComparator::Diffmap(const std::vector& rgb1, - ImageF &result) const { - PROFILER_FUNC; - if (xsize_ < 8 || ysize_ < 8) return; - DiffmapOpsinDynamicsImage(OpsinDynamicsImage(rgb1), result); -} - -void ButteraugliComparator::DiffmapOpsinDynamicsImage( - const std::vector& xyb1, - ImageF &result) const { - PROFILER_FUNC; - if (xsize_ < 8 || ysize_ < 8) return; - PsychoImage pi1; - SeparateFrequencies(xsize_, ysize_, xyb1, pi1); - result = ImageF(xsize_, ysize_); - DiffmapPsychoImage(pi1, result); -} - -void ButteraugliComparator::DiffmapPsychoImage(const PsychoImage& pi1, - ImageF& result) const { - PROFILER_FUNC; - const float hf_asymmetry_ = 0.8f; - if (xsize_ < 8 || ysize_ < 8) { - return; - } - std::vector block_diff_dc(3); - std::vector block_diff_ac(3); - for (int c = 0; c < 3; ++c) { - block_diff_dc[c] = ImageF(xsize_, ysize_, 0.0); - block_diff_ac[c] = ImageF(xsize_, ysize_, 0.0); - } - - static const double wUhfMalta = 5.1409625726; - static const double norm1Uhf = 58.5001247061; - MaltaDiffMap(pi0_.uhf[1], pi1.uhf[1], - wUhfMalta * hf_asymmetry_, - wUhfMalta / hf_asymmetry_, - norm1Uhf, - &block_diff_ac[1]); - - static const double wUhfMaltaX = 4.91743441556; - static const double norm1UhfX = 687196.39002; - MaltaDiffMap(pi0_.uhf[0], pi1.uhf[0], - wUhfMaltaX * hf_asymmetry_, - wUhfMaltaX / hf_asymmetry_, - norm1UhfX, - &block_diff_ac[0]); - - static const double wHfMalta = 153.671655716; - static const double norm1Hf = 83150785.9592; - MaltaDiffMapLF(pi0_.hf[1], pi1.hf[1], - wHfMalta * sqrt(hf_asymmetry_), - wHfMalta / sqrt(hf_asymmetry_), - norm1Hf, - &block_diff_ac[1]); - - static const double wHfMaltaX = 668.358918152; - static const double norm1HfX = 0.882954368025; - MaltaDiffMapLF(pi0_.hf[0], pi1.hf[0], - wHfMaltaX * sqrt(hf_asymmetry_), - wHfMaltaX / sqrt(hf_asymmetry_), - norm1HfX, - &block_diff_ac[0]); - - static const double wMfMalta = 6841.81248144; - static const double norm1Mf = 0.0135134962487; - MaltaDiffMapLF(pi0_.mf[1], pi1.mf[1], wMfMalta, wMfMalta, norm1Mf, - &block_diff_ac[1]); - - static const double wMfMaltaX = 813.901703816; - static const double norm1MfX = 16792.9322251; - MaltaDiffMapLF(pi0_.mf[0], pi1.mf[0], wMfMaltaX, wMfMaltaX, norm1MfX, - &block_diff_ac[0]); - - static const double wmul[9] = { - 0, - 32.4449876135, - 0, - 0, - 0, - 0, - 1.01370836411, - 0, - 1.74566011615, - }; - - static const double maxclamp = 85.7047444518; - static const double kSigmaHfX = 10.6666499623; - static const double w = 884.809801415; - SameNoiseLevels(pi0_.hf[1], pi1.hf[1], kSigmaHfX, w, maxclamp, - &block_diff_ac[1]); - - for (int c = 0; c < 3; ++c) { - if (c < 2) { - L2DiffAsymmetric(pi0_.hf[c], pi1.hf[c], - wmul[c] * hf_asymmetry_, - wmul[c] / hf_asymmetry_, - &block_diff_ac[c]); - } - L2Diff(pi0_.mf[c], pi1.mf[c], wmul[3 + c], &block_diff_ac[c]); - L2Diff(pi0_.lf[c], pi1.lf[c], wmul[6 + c], &block_diff_dc[c]); - } - - std::vector mask_xyb; - std::vector mask_xyb_dc; - MaskPsychoImage(pi0_, pi1, xsize_, ysize_, &mask_xyb, &mask_xyb_dc); - - result = CalculateDiffmap( - CombineChannels(mask_xyb, mask_xyb_dc, block_diff_dc, block_diff_ac)); -} - -// Allows PaddedMaltaUnit to call either function via overloading. -struct MaltaTagLF {}; -struct MaltaTag {}; - -static float MaltaUnit(MaltaTagLF, const float* BUTTERAUGLI_RESTRICT d, - const int xs) { - const int xs3 = 3 * xs; - float retval = 0; - { - // x grows, y constant - float sum = - d[-4] + - d[-2] + - d[0] + - d[2] + - d[4]; - retval += sum * sum; - } - { - // y grows, x constant - float sum = - d[-xs3 - xs] + - d[-xs - xs] + - d[0] + - d[xs + xs] + - d[xs3 + xs]; - retval += sum * sum; - } - { - // both grow - float sum = - d[-xs3 - 3] + - d[-xs - xs - 2] + - d[0] + - d[xs + xs + 2] + - d[xs3 + 3]; - retval += sum * sum; - } - { - // y grows, x shrinks - float sum = - d[-xs3 + 3] + - d[-xs - xs + 2] + - d[0] + - d[xs + xs - 2] + - d[xs3 - 3]; - retval += sum * sum; - } - { - // y grows -4 to 4, x shrinks 1 -> -1 - float sum = - d[-xs3 - xs + 1] + - d[-xs - xs + 1] + - d[0] + - d[xs + xs - 1] + - d[xs3 + xs - 1]; - retval += sum * sum; - } - { - // y grows -4 to 4, x grows -1 -> 1 - float sum = - d[-xs3 - xs - 1] + - d[-xs - xs - 1] + - d[0] + - d[xs + xs + 1] + - d[xs3 + xs + 1]; - retval += sum * sum; - } - { - // x grows -4 to 4, y grows -1 to 1 - float sum = - d[-4 - xs] + - d[-2 - xs] + - d[0] + - d[2 + xs] + - d[4 + xs]; - retval += sum * sum; - } - { - // x grows -4 to 4, y shrinks 1 to -1 - float sum = - d[-4 + xs] + - d[-2 + xs] + - d[0] + - d[2 - xs] + - d[4 - xs]; - retval += sum * sum; - } - { - /* 0_________ - 1__*______ - 2___*_____ - 3_________ - 4____0____ - 5_________ - 6_____*___ - 7______*__ - 8_________ */ - float sum = - d[-xs3 - 2] + - d[-xs - xs - 1] + - d[0] + - d[xs + xs + 1] + - d[xs3 + 2]; - retval += sum * sum; - } - { - /* 0_________ - 1______*__ - 2_____*___ - 3_________ - 4____0____ - 5_________ - 6___*_____ - 7__*______ - 8_________ */ - float sum = - d[-xs3 + 2] + - d[-xs - xs + 1] + - d[0] + - d[xs + xs - 1] + - d[xs3 - 2]; - retval += sum * sum; - } - { - /* 0_________ - 1_________ - 2_*_______ - 3__*______ - 4____0____ - 5______*__ - 6_______*_ - 7_________ - 8_________ */ - float sum = - d[-xs - xs - 3] + - d[-xs - 2] + - d[0] + - d[xs + 2] + - d[xs + xs + 3]; - retval += sum * sum; - } - { - /* 0_________ - 1_________ - 2_______*_ - 3______*__ - 4____0____ - 5__*______ - 6_*_______ - 7_________ - 8_________ */ - float sum = - d[-xs - xs + 3] + - d[-xs + 2] + - d[0] + - d[xs - 2] + - d[xs + xs - 3]; - retval += sum * sum; - } - { - /* 0_________ - 1_________ - 2________* - 3______*__ - 4____0____ - 5__*______ - 6*________ - 7_________ - 8_________ */ - - float sum = - d[xs + xs - 4] + - d[xs - 2] + - d[0] + - d[-xs + 2] + - d[-xs - xs + 4]; - retval += sum * sum; - } - { - /* 0_________ - 1_________ - 2*________ - 3__*______ - 4____0____ - 5______*__ - 6________* - 7_________ - 8_________ */ - float sum = - d[-xs - xs - 4] + - d[-xs - 2] + - d[0] + - d[xs + 2] + - d[xs + xs + 4]; - retval += sum * sum; - } - { - /* 0__*______ - 1_________ - 2___*_____ - 3_________ - 4____0____ - 5_________ - 6_____*___ - 7_________ - 8______*__ */ - float sum = - d[-xs3 - xs - 2] + - d[-xs - xs - 1] + - d[0] + - d[xs + xs + 1] + - d[xs3 + xs + 2]; - retval += sum * sum; - } - { - /* 0______*__ - 1_________ - 2_____*___ - 3_________ - 4____0____ - 5_________ - 6___*_____ - 7_________ - 8__*______ */ - float sum = - d[-xs3 - xs + 2] + - d[-xs - xs + 1] + - d[0] + - d[xs + xs - 1] + - d[xs3 + xs - 2]; - retval += sum * sum; - } - return retval; -} - -static float MaltaUnit(MaltaTag, const float* BUTTERAUGLI_RESTRICT d, - const int xs) { - const int xs3 = 3 * xs; - float retval = 0; - { - // x grows, y constant - float sum = - d[-4] + - d[-3] + - d[-2] + - d[-1] + - d[0] + - d[1] + - d[2] + - d[3] + - d[4]; - retval += sum * sum; - } - { - // y grows, x constant - float sum = - d[-xs3 - xs] + - d[-xs3] + - d[-xs - xs] + - d[-xs] + - d[0] + - d[xs] + - d[xs + xs] + - d[xs3] + - d[xs3 + xs]; - retval += sum * sum; - } - { - // both grow - float sum = - d[-xs3 - 3] + - d[-xs - xs - 2] + - d[-xs - 1] + - d[0] + - d[xs + 1] + - d[xs + xs + 2] + - d[xs3 + 3]; - retval += sum * sum; - } - { - // y grows, x shrinks - float sum = - d[-xs3 + 3] + - d[-xs - xs + 2] + - d[-xs + 1] + - d[0] + - d[xs - 1] + - d[xs + xs - 2] + - d[xs3 - 3]; - retval += sum * sum; - } - { - // y grows -4 to 4, x shrinks 1 -> -1 - float sum = - d[-xs3 - xs + 1] + - d[-xs3 + 1] + - d[-xs - xs + 1] + - d[-xs] + - d[0] + - d[xs] + - d[xs + xs - 1] + - d[xs3 - 1] + - d[xs3 + xs - 1]; - retval += sum * sum; - } - { - // y grows -4 to 4, x grows -1 -> 1 - float sum = - d[-xs3 - xs - 1] + - d[-xs3 - 1] + - d[-xs - xs - 1] + - d[-xs] + - d[0] + - d[xs] + - d[xs + xs + 1] + - d[xs3 + 1] + - d[xs3 + xs + 1]; - retval += sum * sum; - } - { - // x grows -4 to 4, y grows -1 to 1 - float sum = - d[-4 - xs] + - d[-3 - xs] + - d[-2 - xs] + - d[-1] + - d[0] + - d[1] + - d[2 + xs] + - d[3 + xs] + - d[4 + xs]; - retval += sum * sum; - } - { - // x grows -4 to 4, y shrinks 1 to -1 - float sum = - d[-4 + xs] + - d[-3 + xs] + - d[-2 + xs] + - d[-1] + - d[0] + - d[1] + - d[2 - xs] + - d[3 - xs] + - d[4 - xs]; - retval += sum * sum; - } - { - /* 0_________ - 1__*______ - 2___*_____ - 3___*_____ - 4____0____ - 5_____*___ - 6_____*___ - 7______*__ - 8_________ */ - float sum = - d[-xs3 - 2] + - d[-xs - xs - 1] + - d[-xs - 1] + - d[0] + - d[xs + 1] + - d[xs + xs + 1] + - d[xs3 + 2]; - retval += sum * sum; - } - { - /* 0_________ - 1______*__ - 2_____*___ - 3_____*___ - 4____0____ - 5___*_____ - 6___*_____ - 7__*______ - 8_________ */ - float sum = - d[-xs3 + 2] + - d[-xs - xs + 1] + - d[-xs + 1] + - d[0] + - d[xs - 1] + - d[xs + xs - 1] + - d[xs3 - 2]; - retval += sum * sum; - } - { - /* 0_________ - 1_________ - 2_*_______ - 3__**_____ - 4____0____ - 5_____**__ - 6_______*_ - 7_________ - 8_________ */ - float sum = - d[-xs - xs - 3] + - d[-xs - 2] + - d[-xs - 1] + - d[0] + - d[xs + 1] + - d[xs + 2] + - d[xs + xs + 3]; - retval += sum * sum; - } - { - /* 0_________ - 1_________ - 2_______*_ - 3_____**__ - 4____0____ - 5__**_____ - 6_*_______ - 7_________ - 8_________ */ - float sum = - d[-xs - xs + 3] + - d[-xs + 2] + - d[-xs + 1] + - d[0] + - d[xs - 1] + - d[xs - 2] + - d[xs + xs - 3]; - retval += sum * sum; - } - { - /* 0_________ - 1_________ - 2_________ - 3______**_ - 4____0*___ - 5__**_____ - 6**_______ - 7_________ - 8_________ */ - - float sum = - d[xs + xs - 4] + - d[xs + xs - 3] + - d[xs - 2] + - d[xs - 1] + - d[0] + - d[1] + - d[-xs + 2] + - d[-xs + 3]; - retval += sum * sum; - } - { - /* 0_________ - 1_________ - 2**_______ - 3__**_____ - 4____0*___ - 5______**_ - 6_________ - 7_________ - 8_________ */ - float sum = - d[-xs - xs - 4] + - d[-xs - xs - 3] + - d[-xs - 2] + - d[-xs - 1] + - d[0] + - d[1] + - d[xs + 2] + - d[xs + 3]; - retval += sum * sum; - } - { - /* 0__*______ - 1__*______ - 2___*_____ - 3___*_____ - 4____0____ - 5____*____ - 6_____*___ - 7_____*___ - 8_________ */ - float sum = - d[-xs3 - xs - 2] + - d[-xs3 - 2] + - d[-xs - xs - 1] + - d[-xs - 1] + - d[0] + - d[xs] + - d[xs + xs + 1] + - d[xs3 + 1]; - retval += sum * sum; - } - { - /* 0______*__ - 1______*__ - 2_____*___ - 3_____*___ - 4____0____ - 5____*____ - 6___*_____ - 7___*_____ - 8_________ */ - float sum = - d[-xs3 - xs + 2] + - d[-xs3 + 2] + - d[-xs - xs + 1] + - d[-xs + 1] + - d[0] + - d[xs] + - d[xs + xs - 1] + - d[xs3 - 1]; - retval += sum * sum; - } - return retval; -} - -// Returns MaltaUnit. "fastMode" avoids bounds-checks when x0 and y0 are known -// to be far enough from the image borders. -template -static BUTTERAUGLI_INLINE float PaddedMaltaUnit( - float* const BUTTERAUGLI_RESTRICT diffs, const size_t x0, const size_t y0, - const size_t xsize_, const size_t ysize_) { - int ix0 = y0 * xsize_ + x0; - const float* BUTTERAUGLI_RESTRICT d = &diffs[ix0]; - if (fastMode || - (x0 >= 4 && y0 >= 4 && x0 < (xsize_ - 4) && y0 < (ysize_ - 4))) { - return MaltaUnit(Tag(), d, xsize_); - } - - float borderimage[9 * 9]; - for (int dy = 0; dy < 9; ++dy) { - int y = y0 + dy - 4; - if (y < 0 || y >= ysize_) { - for (int dx = 0; dx < 9; ++dx) { - borderimage[dy * 9 + dx] = 0.0f; - } - } else { - for (int dx = 0; dx < 9; ++dx) { - int x = x0 + dx - 4; - if (x < 0 || x >= xsize_) { - borderimage[dy * 9 + dx] = 0.0f; - } else { - borderimage[dy * 9 + dx] = diffs[y * xsize_ + x]; - } - } - } - } - return MaltaUnit(Tag(), &borderimage[4 * 9 + 4], 9); -} - -template -static void MaltaDiffMapImpl(const ImageF& lum0, const ImageF& lum1, - const size_t xsize_, const size_t ysize_, - const double w_0gt1, - const double w_0lt1, - double norm1, - const double len, const double mulli, - ImageF* block_diff_ac) { - const float kWeight0 = 0.5; - const float kWeight1 = 0.33; - - const double w_pre0gt1 = mulli * sqrt(kWeight0 * w_0gt1) / (len * 2 + 1); - const double w_pre0lt1 = mulli * sqrt(kWeight1 * w_0lt1) / (len * 2 + 1); - const float norm2_0gt1 = w_pre0gt1 * norm1; - const float norm2_0lt1 = w_pre0lt1 * norm1; - - std::vector diffs(ysize_ * xsize_); - for (size_t y = 0, ix = 0; y < ysize_; ++y) { - const float* BUTTERAUGLI_RESTRICT const row0 = lum0.Row(y); - const float* BUTTERAUGLI_RESTRICT const row1 = lum1.Row(y); - for (size_t x = 0; x < xsize_; ++x, ++ix) { - const float absval = 0.5 * std::abs(row0[x]) + 0.5 * std::abs(row1[x]); - const float diff = row0[x] - row1[x]; - const float scaler = norm2_0gt1 / (static_cast(norm1) + absval); - - // Primary symmetric quadratic objective. - diffs[ix] = scaler * diff; - - const float scaler2 = norm2_0lt1 / (static_cast(norm1) + absval); - const double fabs0 = fabs(row0[x]); - - // Secondary half-open quadratic objectives. - const double too_small = 0.55 * fabs0; - const double too_big = 1.05 * fabs0; - - if (row0[x] < 0) { - if (row1[x] > -too_small) { - double impact = scaler2 * (row1[x] + too_small); - if (diff < 0) { - diffs[ix] -= impact; - } else { - diffs[ix] += impact; - } - } else if (row1[x] < -too_big) { - double impact = scaler2 * (-row1[x] - too_big); - if (diff < 0) { - diffs[ix] -= impact; - } else { - diffs[ix] += impact; - } - } - } else { - if (row1[x] < too_small) { - double impact = scaler2 * (too_small - row1[x]); - if (diff < 0) { - diffs[ix] -= impact; - } else { - diffs[ix] += impact; - } - } else if (row1[x] > too_big) { - double impact = scaler2 * (row1[x] - too_big); - if (diff < 0) { - diffs[ix] -= impact; - } else { - diffs[ix] += impact; - } - } - } - } - } - - size_t y0 = 0; - // Top - for (; y0 < 4; ++y0) { - float* const BUTTERAUGLI_RESTRICT row_diff = block_diff_ac->Row(y0); - for (size_t x0 = 0; x0 < xsize_; ++x0) { - row_diff[x0] += - PaddedMaltaUnit(&diffs[0], x0, y0, xsize_, ysize_); - } - } - - // Middle - for (; y0 < ysize_ - 4; ++y0) { - float* const BUTTERAUGLI_RESTRICT row_diff = block_diff_ac->Row(y0); - size_t x0 = 0; - for (; x0 < 4; ++x0) { - row_diff[x0] += - PaddedMaltaUnit(&diffs[0], x0, y0, xsize_, ysize_); - } - for (; x0 < xsize_ - 4; ++x0) { - row_diff[x0] += - PaddedMaltaUnit(&diffs[0], x0, y0, xsize_, ysize_); - } - - for (; x0 < xsize_; ++x0) { - row_diff[x0] += - PaddedMaltaUnit(&diffs[0], x0, y0, xsize_, ysize_); - } - } - - // Bottom - for (; y0 < ysize_; ++y0) { - float* const BUTTERAUGLI_RESTRICT row_diff = block_diff_ac->Row(y0); - for (size_t x0 = 0; x0 < xsize_; ++x0) { - row_diff[x0] += - PaddedMaltaUnit(&diffs[0], x0, y0, xsize_, ysize_); - } - } -} - -void ButteraugliComparator::MaltaDiffMap( - const ImageF& lum0, const ImageF& lum1, - const double w_0gt1, - const double w_0lt1, - const double norm1, ImageF* BUTTERAUGLI_RESTRICT block_diff_ac) const { - PROFILER_FUNC; - const double len = 3.75; - static const double mulli = 0.354191303559; - MaltaDiffMapImpl(lum0, lum1, xsize_, ysize_, w_0gt1, w_0lt1, - norm1, len, - mulli, block_diff_ac); -} - -void ButteraugliComparator::MaltaDiffMapLF( - const ImageF& lum0, const ImageF& lum1, - const double w_0gt1, - const double w_0lt1, - const double norm1, ImageF* BUTTERAUGLI_RESTRICT block_diff_ac) const { - PROFILER_FUNC; - const double len = 3.75; - static const double mulli = 0.405371989604; - MaltaDiffMapImpl(lum0, lum1, xsize_, ysize_, - w_0gt1, w_0lt1, - norm1, len, - mulli, block_diff_ac); -} - -ImageF ButteraugliComparator::CombineChannels( - const std::vector& mask_xyb, - const std::vector& mask_xyb_dc, - const std::vector& block_diff_dc, - const std::vector& block_diff_ac) const { - PROFILER_FUNC; - ImageF result(xsize_, ysize_); - for (size_t y = 0; y < ysize_; ++y) { - float* const BUTTERAUGLI_RESTRICT row_out = result.Row(y); - for (size_t x = 0; x < xsize_; ++x) { - float mask[3]; - float dc_mask[3]; - float diff_dc[3]; - float diff_ac[3]; - for (int i = 0; i < 3; ++i) { - mask[i] = mask_xyb[i].Row(y)[x]; - dc_mask[i] = mask_xyb_dc[i].Row(y)[x]; - diff_dc[i] = block_diff_dc[i].Row(y)[x]; - diff_ac[i] = block_diff_ac[i].Row(y)[x]; - } - row_out[x] = (DotProduct(diff_dc, dc_mask) + DotProduct(diff_ac, mask)); - } - } - return result; -} - -double ButteraugliScoreFromDiffmap(const ImageF& diffmap) { - PROFILER_FUNC; - float retval = 0.0f; - for (size_t y = 0; y < diffmap.ysize(); ++y) { - const float * const BUTTERAUGLI_RESTRICT row = diffmap.Row(y); - for (size_t x = 0; x < diffmap.xsize(); ++x) { - retval = std::max(retval, row[x]); - } - } - return retval; -} - -#include - -// ===== Functions used by Mask only ===== -static std::array MakeMask( - double extmul, double extoff, - double mul, double offset, - double scaler) { - std::array lut; - for (int i = 0; i < lut.size(); ++i) { - const double c = mul / ((0.01 * scaler * i) + offset); - lut[i] = kGlobalScale * (1.0 + extmul * (c + extoff)); - if (lut[i] < 1e-5) { - lut[i] = 1e-5; - } - assert(lut[i] >= 0.0); - lut[i] *= lut[i]; - } - return lut; -} - -double MaskX(double delta) { - static const double extmul = 2.59885507073; - static const double extoff = 3.08805636789; - static const double offset = 0.315424196682; - static const double scaler = 16.2770141832; - static const double mul = 5.62939030582; - static const std::array lut = - MakeMask(extmul, extoff, mul, offset, scaler); - return InterpolateClampNegative(lut.data(), lut.size(), delta); -} - -double MaskY(double delta) { - static const double extmul = 0.9613705131; - static const double extoff = -0.581933100068; - static const double offset = 1.00846207765; - static const double scaler = 2.2342321176; - static const double mul = 6.64307621174; - static const std::array lut = - MakeMask(extmul, extoff, mul, offset, scaler); - return InterpolateClampNegative(lut.data(), lut.size(), delta); -} - -double MaskDcX(double delta) { - static const double extmul = 10.0470705878; - static const double extoff = 3.18472654033; - static const double offset = 0.0551512255218; - static const double scaler = 70.0; - static const double mul = 0.373092999662; - static const std::array lut = - MakeMask(extmul, extoff, mul, offset, scaler); - return InterpolateClampNegative(lut.data(), lut.size(), delta); -} - -double MaskDcY(double delta) { - static const double extmul = 0.0115640939227; - static const double extoff = 45.9483175519; - static const double offset = 0.0142290066313; - static const double scaler = 5.0; - static const double mul = 2.52611324247; - static const std::array lut = - MakeMask(extmul, extoff, mul, offset, scaler); - return InterpolateClampNegative(lut.data(), lut.size(), delta); -} - -ImageF DiffPrecompute(const ImageF& xyb0, const ImageF& xyb1) { - PROFILER_FUNC; - const size_t xsize = xyb0.xsize(); - const size_t ysize = xyb0.ysize(); - ImageF result(xsize, ysize); - size_t x2, y2; - for (size_t y = 0; y < ysize; ++y) { - if (y + 1 < ysize) { - y2 = y + 1; - } else if (y > 0) { - y2 = y - 1; - } else { - y2 = y; - } - const float* const BUTTERAUGLI_RESTRICT row0_in = xyb0.Row(y); - const float* const BUTTERAUGLI_RESTRICT row1_in = xyb1.Row(y); - const float* const BUTTERAUGLI_RESTRICT row0_in2 = xyb0.Row(y2); - const float* const BUTTERAUGLI_RESTRICT row1_in2 = xyb1.Row(y2); - float* const BUTTERAUGLI_RESTRICT row_out = result.Row(y); - for (size_t x = 0; x < xsize; ++x) { - if (x + 1 < xsize) { - x2 = x + 1; - } else if (x > 0) { - x2 = x - 1; - } else { - x2 = x; - } - double sup0 = (fabs(row0_in[x] - row0_in[x2]) + - fabs(row0_in[x] - row0_in2[x])); - double sup1 = (fabs(row1_in[x] - row1_in[x2]) + - fabs(row1_in[x] - row1_in2[x])); - static const double mul0 = 0.918416534734; - row_out[x] = mul0 * std::min(sup0, sup1); - static const double cutoff = 55.0184555849; - if (row_out[x] >= cutoff) { - row_out[x] = cutoff; - } - } - } - return result; -} - -void Mask(const std::vector& xyb0, - const std::vector& xyb1, - std::vector* BUTTERAUGLI_RESTRICT mask, - std::vector* BUTTERAUGLI_RESTRICT mask_dc) { - PROFILER_FUNC; - const size_t xsize = xyb0[0].xsize(); - const size_t ysize = xyb0[0].ysize(); - mask->resize(3); - *mask_dc = CreatePlanes(xsize, ysize, 3); - double muls[2] = { - 0.207017089891, - 0.267138152891, - }; - double normalizer = { - 1.0 / (muls[0] + muls[1]), - }; - static const double r0 = 2.3770330432; - static const double r1 = 9.04353323561; - static const double r2 = 9.24456601467; - static const double border_ratio = -0.0724948220913; - - { - // X component - ImageF diff = DiffPrecompute(xyb0[0], xyb1[0]); - ImageF blurred = Blur(diff, r2, border_ratio); - (*mask)[0] = ImageF(xsize, ysize); - for (size_t y = 0; y < ysize; ++y) { - for (size_t x = 0; x < xsize; ++x) { - (*mask)[0].Row(y)[x] = blurred.Row(y)[x]; - } - } - } - { - // Y component - (*mask)[1] = ImageF(xsize, ysize); - ImageF diff = DiffPrecompute(xyb0[1], xyb1[1]); - ImageF blurred1 = Blur(diff, r0, border_ratio); - ImageF blurred2 = Blur(diff, r1, border_ratio); - for (size_t y = 0; y < ysize; ++y) { - for (size_t x = 0; x < xsize; ++x) { - const double val = normalizer * ( - muls[0] * blurred1.Row(y)[x] + - muls[1] * blurred2.Row(y)[x]); - (*mask)[1].Row(y)[x] = val; - } - } - } - // B component - (*mask)[2] = ImageF(xsize, ysize); - static const double mul[2] = { - 16.6963293877, - 2.1364621982, - }; - static const double w00 = 36.4671237619; - static const double w11 = 2.1887170895; - static const double w_ytob_hf = std::max( - 0.086624184478, - 0.0); - static const double w_ytob_lf = 21.6804277046; - static const double p1_to_p0 = 0.0513061271723; - - for (size_t y = 0; y < ysize; ++y) { - for (size_t x = 0; x < xsize; ++x) { - const double s0 = (*mask)[0].Row(y)[x]; - const double s1 = (*mask)[1].Row(y)[x]; - const double p1 = mul[1] * w11 * s1; - const double p0 = mul[0] * w00 * s0 + p1_to_p0 * p1; - - (*mask)[0].Row(y)[x] = MaskX(p0); - (*mask)[1].Row(y)[x] = MaskY(p1); - (*mask)[2].Row(y)[x] = w_ytob_hf * MaskY(p1); - (*mask_dc)[0].Row(y)[x] = MaskDcX(p0); - (*mask_dc)[1].Row(y)[x] = MaskDcY(p1); - (*mask_dc)[2].Row(y)[x] = w_ytob_lf * MaskDcY(p1); - } - } -} - -void ButteraugliDiffmap(const std::vector &rgb0_image, - const std::vector &rgb1_image, - ImageF &result_image) { - const size_t xsize = rgb0_image[0].xsize(); - const size_t ysize = rgb0_image[0].ysize(); - static const int kMax = 8; - if (xsize < kMax || ysize < kMax) { - // Butteraugli values for small (where xsize or ysize is smaller - // than 8 pixels) images are non-sensical, but most likely it is - // less disruptive to try to compute something than just give up. - // Temporarily extend the borders of the image to fit 8 x 8 size. - int xborder = xsize < kMax ? (kMax - xsize) / 2 : 0; - int yborder = ysize < kMax ? (kMax - ysize) / 2 : 0; - size_t xscaled = std::max(kMax, xsize); - size_t yscaled = std::max(kMax, ysize); - std::vector scaled0 = CreatePlanes(xscaled, yscaled, 3); - std::vector scaled1 = CreatePlanes(xscaled, yscaled, 3); - for (int i = 0; i < 3; ++i) { - for (int y = 0; y < yscaled; ++y) { - for (int x = 0; x < xscaled; ++x) { - size_t x2 = std::min(xsize - 1, std::max(0, x - xborder)); - size_t y2 = std::min(ysize - 1, std::max(0, y - yborder)); - scaled0[i].Row(y)[x] = rgb0_image[i].Row(y2)[x2]; - scaled1[i].Row(y)[x] = rgb1_image[i].Row(y2)[x2]; - } - } - } - ImageF diffmap_scaled; - ButteraugliDiffmap(scaled0, scaled1, diffmap_scaled); - result_image = ImageF(xsize, ysize); - for (int y = 0; y < ysize; ++y) { - for (int x = 0; x < xsize; ++x) { - result_image.Row(y)[x] = diffmap_scaled.Row(y + yborder)[x + xborder]; - } - } - return; - } - ButteraugliComparator butteraugli(rgb0_image); - butteraugli.Diffmap(rgb1_image, result_image); -} - -bool ButteraugliInterface(const std::vector &rgb0, - const std::vector &rgb1, - ImageF &diffmap, - double &diffvalue) { - const size_t xsize = rgb0[0].xsize(); - const size_t ysize = rgb0[0].ysize(); - if (xsize < 1 || ysize < 1) { - return false; // No image. - } - for (int i = 1; i < 3; i++) { - if (rgb0[i].xsize() != xsize || rgb0[i].ysize() != ysize || - rgb1[i].xsize() != xsize || rgb1[i].ysize() != ysize) { - return false; // Image planes must have same dimensions. - } - } - ButteraugliDiffmap(rgb0, rgb1, diffmap); - diffvalue = ButteraugliScoreFromDiffmap(diffmap); - return true; -} - -bool ButteraugliAdaptiveQuantization(size_t xsize, size_t ysize, - const std::vector > &rgb, std::vector &quant) { - if (xsize < 16 || ysize < 16) { - return false; // Butteraugli is undefined for small images. - } - size_t size = xsize * ysize; - - std::vector rgb_planes = PlanesFromPacked(xsize, ysize, rgb); - std::vector scale_xyb; - std::vector scale_xyb_dc; - Mask(rgb_planes, rgb_planes, &scale_xyb, &scale_xyb_dc); - quant.reserve(size); - - // Mask gives us values in 3 color channels, but for now we take only - // the intensity channel. - for (size_t y = 0; y < ysize; ++y) { - for (size_t x = 0; x < xsize; ++x) { - quant.push_back(scale_xyb[1].Row(y)[x]); - } - } - return true; -} - -double ButteraugliFuzzyClass(double score) { - static const double fuzzy_width_up = 6.07887388532; - static const double fuzzy_width_down = 5.50793514384; - static const double m0 = 2.0; - static const double scaler = 0.840253347958; - double val; - if (score < 1.0) { - // val in [scaler .. 2.0] - val = m0 / (1.0 + exp((score - 1.0) * fuzzy_width_down)); - val -= 1.0; // from [1 .. 2] to [0 .. 1] - val *= 2.0 - scaler; // from [0 .. 1] to [0 .. 2.0 - scaler] - val += scaler; // from [0 .. 2.0 - scaler] to [scaler .. 2.0] - } else { - // val in [0 .. scaler] - val = m0 / (1.0 + exp((score - 1.0) * fuzzy_width_up)); - val *= scaler; - } - return val; -} - -double ButteraugliFuzzyInverse(double seek) { - double pos = 0; - for (double range = 1.0; range >= 1e-10; range *= 0.5) { - double cur = ButteraugliFuzzyClass(pos); - if (cur < seek) { - pos -= range; - } else { - pos += range; - } - } - return pos; -} - -namespace { - -void ScoreToRgb(double score, double good_threshold, double bad_threshold, - uint8_t rgb[3]) { - double heatmap[12][3] = { - {0, 0, 0}, - {0, 0, 1}, - {0, 1, 1}, - {0, 1, 0}, // Good level - {1, 1, 0}, - {1, 0, 0}, // Bad level - {1, 0, 1}, - {0.5, 0.5, 1.0}, - {1.0, 0.5, 0.5}, // Pastel colors for the very bad quality range. - {1.0, 1.0, 0.5}, - { - 1, 1, 1, - }, - { - 1, 1, 1, - }, - }; - if (score < good_threshold) { - score = (score / good_threshold) * 0.3; - } else if (score < bad_threshold) { - score = 0.3 + - (score - good_threshold) / (bad_threshold - good_threshold) * 0.15; - } else { - score = 0.45 + (score - bad_threshold) / (bad_threshold * 12) * 0.5; - } - static const int kTableSize = sizeof(heatmap) / sizeof(heatmap[0]); - score = std::min(std::max(score * (kTableSize - 1), 0.0), - kTableSize - 2); - int ix = static_cast(score); - double mix = score - ix; - for (int i = 0; i < 3; ++i) { - double v = mix * heatmap[ix + 1][i] + (1 - mix) * heatmap[ix][i]; - rgb[i] = static_cast(255 * pow(v, 0.5) + 0.5); - } -} - -} // namespace - -void CreateHeatMapImage(const std::vector& distmap, - double good_threshold, double bad_threshold, - size_t xsize, size_t ysize, - std::vector* heatmap) { - heatmap->resize(3 * xsize * ysize); - for (size_t y = 0; y < ysize; ++y) { - for (size_t x = 0; x < xsize; ++x) { - int px = xsize * y + x; - double d = distmap[px]; - uint8_t* rgb = &(*heatmap)[3 * px]; - ScoreToRgb(d, good_threshold, bad_threshold, rgb); - } - } -} - -} // namespace butteraugli diff --git a/oss-internship-2020/guetzli/third_party/butteraugli/butteraugli/butteraugli.h b/oss-internship-2020/guetzli/third_party/butteraugli/butteraugli/butteraugli.h deleted file mode 100755 index 2f5d938..0000000 --- a/oss-internship-2020/guetzli/third_party/butteraugli/butteraugli/butteraugli.h +++ /dev/null @@ -1,619 +0,0 @@ -// Copyright 2016 Google Inc. All Rights Reserved. -// -// 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. -// -// Disclaimer: This is not an official Google product. -// -// Author: Jyrki Alakuijala (jyrki.alakuijala@gmail.com) - -#ifndef BUTTERAUGLI_BUTTERAUGLI_H_ -#define BUTTERAUGLI_BUTTERAUGLI_H_ - -#include -#include -#include -#include -#include -#include -#include -#include - -#define BUTTERAUGLI_ENABLE_CHECKS 0 - -// This is the main interface to butteraugli image similarity -// analysis function. - -namespace butteraugli { - -template -class Image; - -using Image8 = Image; -using ImageF = Image; - -// ButteraugliInterface defines the public interface for butteraugli. -// -// It calculates the difference between rgb0 and rgb1. -// -// rgb0 and rgb1 contain the images. rgb0[c][px] and rgb1[c][px] contains -// the red image for c == 0, green for c == 1, blue for c == 2. Location index -// px is calculated as y * xsize + x. -// -// Value of pixels of images rgb0 and rgb1 need to be represented as raw -// intensity. Most image formats store gamma corrected intensity in pixel -// values. This gamma correction has to be removed, by applying the following -// function: -// butteraugli_val = 255.0 * pow(png_val / 255.0, gamma); -// A typical value of gamma is 2.2. It is usually stored in the image header. -// Take care not to confuse that value with its inverse. The gamma value should -// be always greater than one. -// Butteraugli does not work as intended if the caller does not perform -// gamma correction. -// -// diffmap will contain an image of the size xsize * ysize, containing -// localized differences for values px (indexed with the px the same as rgb0 -// and rgb1). diffvalue will give a global score of similarity. -// -// A diffvalue smaller than kButteraugliGood indicates that images can be -// observed as the same image. -// diffvalue larger than kButteraugliBad indicates that a difference between -// the images can be observed. -// A diffvalue between kButteraugliGood and kButteraugliBad indicates that -// a subtle difference can be observed between the images. -// -// Returns true on success. - -bool ButteraugliInterface(const std::vector &rgb0, - const std::vector &rgb1, - ImageF &diffmap, - double &diffvalue); - -const double kButteraugliQuantLow = 0.26; -const double kButteraugliQuantHigh = 1.454; - -// Converts the butteraugli score into fuzzy class values that are continuous -// at the class boundary. The class boundary location is based on human -// raters, but the slope is arbitrary. Particularly, it does not reflect -// the expectation value of probabilities of the human raters. It is just -// expected that a smoother class boundary will allow for higher-level -// optimization algorithms to work faster. -// -// Returns 2.0 for a perfect match, and 1.0 for 'ok', 0.0 for bad. Because the -// scoring is fuzzy, a butteraugli score of 0.96 would return a class of -// around 1.9. -double ButteraugliFuzzyClass(double score); - -// Input values should be in range 0 (bad) to 2 (good). Use -// kButteraugliNormalization as normalization. -double ButteraugliFuzzyInverse(double seek); - -// Returns a map which can be used for adaptive quantization. Values can -// typically range from kButteraugliQuantLow to kButteraugliQuantHigh. Low -// values require coarse quantization (e.g. near random noise), high values -// require fine quantization (e.g. in smooth bright areas). -bool ButteraugliAdaptiveQuantization(size_t xsize, size_t ysize, - const std::vector > &rgb, std::vector &quant); - -// Implementation details, don't use anything below or your code will -// break in the future. - -#ifdef _MSC_VER -#define BUTTERAUGLI_RESTRICT __restrict -#else -#define BUTTERAUGLI_RESTRICT __restrict__ -#endif - -#ifdef _MSC_VER -#define BUTTERAUGLI_INLINE __forceinline -#else -#define BUTTERAUGLI_INLINE inline -#endif - -#ifdef __clang__ -// Early versions of Clang did not support __builtin_assume_aligned. -#define BUTTERAUGLI_HAS_ASSUME_ALIGNED __has_builtin(__builtin_assume_aligned) -#elif defined(__GNUC__) -#define BUTTERAUGLI_HAS_ASSUME_ALIGNED 1 -#else -#define BUTTERAUGLI_HAS_ASSUME_ALIGNED 0 -#endif - -// Returns a void* pointer which the compiler then assumes is N-byte aligned. -// Example: float* PIK_RESTRICT aligned = (float*)PIK_ASSUME_ALIGNED(in, 32); -// -// The assignment semantics are required by GCC/Clang. ICC provides an in-place -// __assume_aligned, whereas MSVC's __assume appears unsuitable. -#if BUTTERAUGLI_HAS_ASSUME_ALIGNED -#define BUTTERAUGLI_ASSUME_ALIGNED(ptr, align) __builtin_assume_aligned((ptr), (align)) -#else -#define BUTTERAUGLI_ASSUME_ALIGNED(ptr, align) (ptr) -#endif // BUTTERAUGLI_HAS_ASSUME_ALIGNED - -// Functions that depend on the cache line size. -class CacheAligned { - public: - static constexpr size_t kPointerSize = sizeof(void *); - static constexpr size_t kCacheLineSize = 64; - - // The aligned-return annotation is only allowed on function declarations. - static void *Allocate(const size_t bytes); - static void Free(void *aligned_pointer); -}; - -template -using CacheAlignedUniquePtrT = std::unique_ptr; - -using CacheAlignedUniquePtr = CacheAlignedUniquePtrT; - -template -static inline CacheAlignedUniquePtrT Allocate(const size_t entries) { - return CacheAlignedUniquePtrT( - static_cast( - CacheAligned::Allocate(entries * sizeof(T))), - CacheAligned::Free); -} - -// Returns the smallest integer not less than "amount" that is divisible by -// "multiple", which must be a power of two. -template -static inline size_t Align(const size_t amount) { - static_assert(multiple != 0 && ((multiple & (multiple - 1)) == 0), - "Align<> argument must be a power of two"); - return (amount + multiple - 1) & ~(multiple - 1); -} - -// Single channel, contiguous (cache-aligned) rows separated by padding. -// T must be POD. -// -// Rationale: vectorization benefits from aligned operands - unaligned loads and -// especially stores are expensive when the address crosses cache line -// boundaries. Introducing padding after each row ensures the start of a row is -// aligned, and that row loops can process entire vectors (writes to the padding -// are allowed and ignored). -// -// We prefer a planar representation, where channels are stored as separate -// 2D arrays, because that simplifies vectorization (repeating the same -// operation on multiple adjacent components) without the complexity of a -// hybrid layout (8 R, 8 G, 8 B, ...). In particular, clients can easily iterate -// over all components in a row and Image requires no knowledge of the pixel -// format beyond the component type "T". The downside is that we duplicate the -// xsize/ysize members for each channel. -// -// This image layout could also be achieved with a vector and a row accessor -// function, but a class wrapper with support for "deleter" allows wrapping -// existing memory allocated by clients without copying the pixels. It also -// provides convenient accessors for xsize/ysize, which shortens function -// argument lists. Supports move-construction so it can be stored in containers. -template -class Image { - // Returns cache-aligned row stride, being careful to avoid 2K aliasing. - static size_t BytesPerRow(const size_t xsize) { - // Allow reading one extra AVX-2 vector on the right margin. - const size_t row_size = xsize * sizeof(T) + 32; - const size_t align = CacheAligned::kCacheLineSize; - size_t bytes_per_row = (row_size + align - 1) & ~(align - 1); - // During the lengthy window before writes are committed to memory, CPUs - // guard against read after write hazards by checking the address, but - // only the lower 11 bits. We avoid a false dependency between writes to - // consecutive rows by ensuring their sizes are not multiples of 2 KiB. - if (bytes_per_row % 2048 == 0) { - bytes_per_row += align; - } - return bytes_per_row; - } - - public: - using T = ComponentType; - - Image() : xsize_(0), ysize_(0), bytes_per_row_(0), - bytes_(static_cast(nullptr), Ignore) {} - - Image(const size_t xsize, const size_t ysize) - : xsize_(xsize), - ysize_(ysize), - bytes_per_row_(BytesPerRow(xsize)), - bytes_(Allocate(bytes_per_row_ * ysize)) {} - - Image(const size_t xsize, const size_t ysize, T val) - : xsize_(xsize), - ysize_(ysize), - bytes_per_row_(BytesPerRow(xsize)), - bytes_(Allocate(bytes_per_row_ * ysize)) { - for (size_t y = 0; y < ysize_; ++y) { - T* const BUTTERAUGLI_RESTRICT row = Row(y); - for (int x = 0; x < xsize_; ++x) { - row[x] = val; - } - } - } - - Image(const size_t xsize, const size_t ysize, - uint8_t * const BUTTERAUGLI_RESTRICT bytes, - const size_t bytes_per_row) - : xsize_(xsize), - ysize_(ysize), - bytes_per_row_(bytes_per_row), - bytes_(bytes, Ignore) {} - - // Move constructor (required for returning Image from function) - Image(Image &&other) - : xsize_(other.xsize_), - ysize_(other.ysize_), - bytes_per_row_(other.bytes_per_row_), - bytes_(std::move(other.bytes_)) {} - - // Move assignment (required for std::vector) - Image &operator=(Image &&other) { - xsize_ = other.xsize_; - ysize_ = other.ysize_; - bytes_per_row_ = other.bytes_per_row_; - bytes_ = std::move(other.bytes_); - return *this; - } - - void Swap(Image &other) { - std::swap(xsize_, other.xsize_); - std::swap(ysize_, other.ysize_); - std::swap(bytes_per_row_, other.bytes_per_row_); - std::swap(bytes_, other.bytes_); - } - - // How many pixels. - size_t xsize() const { return xsize_; } - size_t ysize() const { return ysize_; } - - T *const BUTTERAUGLI_RESTRICT Row(const size_t y) { -#ifdef BUTTERAUGLI_ENABLE_CHECKS - if (y >= ysize_) { - printf("Row %zu out of bounds (ysize=%zu)\n", y, ysize_); - abort(); - } -#endif - void *row = bytes_.get() + y * bytes_per_row_; - return reinterpret_cast(BUTTERAUGLI_ASSUME_ALIGNED(row, 64)); - } - - const T *const BUTTERAUGLI_RESTRICT Row(const size_t y) const { -#ifdef BUTTERAUGLI_ENABLE_CHECKS - if (y >= ysize_) { - printf("Const row %zu out of bounds (ysize=%zu)\n", y, ysize_); - abort(); - } -#endif - void *row = bytes_.get() + y * bytes_per_row_; - return reinterpret_cast(BUTTERAUGLI_ASSUME_ALIGNED(row, 64)); - } - - // Raw access to byte contents, for interfacing with other libraries. - // Unsigned char instead of char to avoid surprises (sign extension). - uint8_t * const BUTTERAUGLI_RESTRICT bytes() { return bytes_.get(); } - const uint8_t * const BUTTERAUGLI_RESTRICT bytes() const { - return bytes_.get(); - } - size_t bytes_per_row() const { return bytes_per_row_; } - - // Returns number of pixels (some of which are padding) per row. Useful for - // computing other rows via pointer arithmetic. - intptr_t PixelsPerRow() const { - static_assert(CacheAligned::kCacheLineSize % sizeof(T) == 0, - "Padding must be divisible by the pixel size."); - return static_cast(bytes_per_row_ / sizeof(T)); - } - - private: - // Deleter used when bytes are not owned. - static void Ignore(void *ptr) {} - - // (Members are non-const to enable assignment during move-assignment.) - size_t xsize_; // original intended pixels, not including any padding. - size_t ysize_; - size_t bytes_per_row_; // [bytes] including padding. - CacheAlignedUniquePtr bytes_; -}; - -// Returns newly allocated planes of the given dimensions. -template -static inline std::vector> CreatePlanes(const size_t xsize, - const size_t ysize, - const size_t num_planes) { - std::vector> planes; - planes.reserve(num_planes); - for (size_t i = 0; i < num_planes; ++i) { - planes.emplace_back(xsize, ysize); - } - return planes; -} - -// Returns a new image with the same dimensions and pixel values. -template -static inline Image CopyPixels(const Image &other) { - Image copy(other.xsize(), other.ysize()); - const void *BUTTERAUGLI_RESTRICT from = other.bytes(); - void *BUTTERAUGLI_RESTRICT to = copy.bytes(); - memcpy(to, from, other.ysize() * other.bytes_per_row()); - return copy; -} - -// Returns new planes with the same dimensions and pixel values. -template -static inline std::vector> CopyPlanes( - const std::vector> &planes) { - std::vector> copy; - copy.reserve(planes.size()); - for (const Image &plane : planes) { - copy.push_back(CopyPixels(plane)); - } - return copy; -} - -// Compacts a padded image into a preallocated packed vector. -template -static inline void CopyToPacked(const Image &from, std::vector *to) { - const size_t xsize = from.xsize(); - const size_t ysize = from.ysize(); -#if BUTTERAUGLI_ENABLE_CHECKS - if (to->size() < xsize * ysize) { - printf("%zu x %zu exceeds %zu capacity\n", xsize, ysize, to->size()); - abort(); - } -#endif - for (size_t y = 0; y < ysize; ++y) { - const float* const BUTTERAUGLI_RESTRICT row_from = from.Row(y); - float* const BUTTERAUGLI_RESTRICT row_to = to->data() + y * xsize; - memcpy(row_to, row_from, xsize * sizeof(T)); - } -} - -// Expands a packed vector into a preallocated padded image. -template -static inline void CopyFromPacked(const std::vector &from, Image *to) { - const size_t xsize = to->xsize(); - const size_t ysize = to->ysize(); - assert(from.size() == xsize * ysize); - for (size_t y = 0; y < ysize; ++y) { - const float* const BUTTERAUGLI_RESTRICT row_from = - from.data() + y * xsize; - float* const BUTTERAUGLI_RESTRICT row_to = to->Row(y); - memcpy(row_to, row_from, xsize * sizeof(T)); - } -} - -template -static inline std::vector> PlanesFromPacked( - const size_t xsize, const size_t ysize, - const std::vector> &packed) { - std::vector> planes; - planes.reserve(packed.size()); - for (const std::vector &p : packed) { - planes.push_back(Image(xsize, ysize)); - CopyFromPacked(p, &planes.back()); - } - return planes; -} - -template -static inline std::vector> PackedFromPlanes( - const std::vector> &planes) { - assert(!planes.empty()); - const size_t num_pixels = planes[0].xsize() * planes[0].ysize(); - std::vector> packed; - packed.reserve(planes.size()); - for (const Image &image : planes) { - packed.push_back(std::vector(num_pixels)); - CopyToPacked(image, &packed.back()); - } - return packed; -} - -struct PsychoImage { - std::vector uhf; - std::vector hf; - std::vector mf; - std::vector lf; -}; - -class ButteraugliComparator { - public: - ButteraugliComparator(const std::vector& rgb0); - - // Computes the butteraugli map between the original image given in the - // constructor and the distorted image give here. - void Diffmap(const std::vector& rgb1, ImageF& result) const; - - // Same as above, but OpsinDynamicsImage() was already applied. - void DiffmapOpsinDynamicsImage(const std::vector& xyb1, - ImageF& result) const; - - // Same as above, but the frequency decomposition was already applied. - void DiffmapPsychoImage(const PsychoImage& ps1, ImageF &result) const; - - void Mask(std::vector* BUTTERAUGLI_RESTRICT mask, - std::vector* BUTTERAUGLI_RESTRICT mask_dc) const; - - private: - void MaltaDiffMapLF(const ImageF& y0, - const ImageF& y1, - double w_0gt1, - double w_0lt1, - double normalization, - ImageF* BUTTERAUGLI_RESTRICT block_diff_ac) const; - - void MaltaDiffMap(const ImageF& y0, - const ImageF& y1, - double w_0gt1, - double w_0lt1, - double normalization, - ImageF* BUTTERAUGLI_RESTRICT block_diff_ac) const; - - ImageF CombineChannels(const std::vector& scale_xyb, - const std::vector& scale_xyb_dc, - const std::vector& block_diff_dc, - const std::vector& block_diff_ac) const; - - const size_t xsize_; - const size_t ysize_; - const size_t num_pixels_; - PsychoImage pi0_; -}; - -void ButteraugliDiffmap(const std::vector &rgb0, - const std::vector &rgb1, - ImageF &diffmap); - -double ButteraugliScoreFromDiffmap(const ImageF& distmap); - -// Generate rgb-representation of the distance between two images. -void CreateHeatMapImage(const std::vector &distmap, - double good_threshold, double bad_threshold, - size_t xsize, size_t ysize, - std::vector *heatmap); - -// Compute values of local frequency and dc masking based on the activity -// in the two images. -void Mask(const std::vector& xyb0, - const std::vector& xyb1, - std::vector* BUTTERAUGLI_RESTRICT mask, - std::vector* BUTTERAUGLI_RESTRICT mask_dc); - -template -BUTTERAUGLI_INLINE void RgbToXyb(const V &r, const V &g, const V &b, - V *BUTTERAUGLI_RESTRICT valx, - V *BUTTERAUGLI_RESTRICT valy, - V *BUTTERAUGLI_RESTRICT valb) { - *valx = r - g; - *valy = r + g; - *valb = b; -} - -template -BUTTERAUGLI_INLINE void OpsinAbsorbance(const V &in0, const V &in1, - const V &in2, - V *BUTTERAUGLI_RESTRICT out0, - V *BUTTERAUGLI_RESTRICT out1, - V *BUTTERAUGLI_RESTRICT out2) { - // https://en.wikipedia.org/wiki/Photopsin absorbance modeling. - static const double mixi0 = 0.254462330846; - static const double mixi1 = 0.488238255095; - static const double mixi2 = 0.0635278003854; - static const double mixi3 = 1.01681026909; - static const double mixi4 = 0.195214015766; - static const double mixi5 = 0.568019861857; - static const double mixi6 = 0.0860755536007; - static const double mixi7 = 1.1510118369; - static const double mixi8 = 0.07374607900105684; - static const double mixi9 = 0.06142425304154509; - static const double mixi10 = 0.24416850520714256; - static const double mixi11 = 1.20481945273; - - const V mix0(mixi0); - const V mix1(mixi1); - const V mix2(mixi2); - const V mix3(mixi3); - const V mix4(mixi4); - const V mix5(mixi5); - const V mix6(mixi6); - const V mix7(mixi7); - const V mix8(mixi8); - const V mix9(mixi9); - const V mix10(mixi10); - const V mix11(mixi11); - - *out0 = mix0 * in0 + mix1 * in1 + mix2 * in2 + mix3; - *out1 = mix4 * in0 + mix5 * in1 + mix6 * in2 + mix7; - *out2 = mix8 * in0 + mix9 * in1 + mix10 * in2 + mix11; -} - -std::vector OpsinDynamicsImage(const std::vector& rgb); - -ImageF Blur(const ImageF& in, float sigma, float border_ratio); - -double SimpleGamma(double v); - -double GammaMinArg(); -double GammaMaxArg(); - -// Polynomial evaluation via Clenshaw's scheme (similar to Horner's). -// Template enables compile-time unrolling of the recursion, but must reside -// outside of a class due to the specialization. -template -static inline void ClenshawRecursion(const double x, const double *coefficients, - double *b1, double *b2) { - const double x_b1 = x * (*b1); - const double t = (x_b1 + x_b1) - (*b2) + coefficients[INDEX]; - *b2 = *b1; - *b1 = t; - - ClenshawRecursion(x, coefficients, b1, b2); -} - -// Base case -template <> -inline void ClenshawRecursion<0>(const double x, const double *coefficients, - double *b1, double *b2) { - const double x_b1 = x * (*b1); - // The final iteration differs - no 2 * x_b1 here. - *b1 = x_b1 - (*b2) + coefficients[0]; -} - -// Rational polynomial := dividing two polynomial evaluations. These are easier -// to find than minimax polynomials. -struct RationalPolynomial { - template - static double EvaluatePolynomial(const double x, - const double (&coefficients)[N]) { - double b1 = 0.0; - double b2 = 0.0; - ClenshawRecursion(x, coefficients, &b1, &b2); - return b1; - } - - // Evaluates the polynomial at x (in [min_value, max_value]). - inline double operator()(const double x) const { - // First normalize to [0, 1]. - const double x01 = (x - min_value) / (max_value - min_value); - // And then to [-1, 1] domain of Chebyshev polynomials. - const double xc = 2.0 * x01 - 1.0; - - const double yp = EvaluatePolynomial(xc, p); - const double yq = EvaluatePolynomial(xc, q); - if (yq == 0.0) return 0.0; - return static_cast(yp / yq); - } - - // Domain of the polynomials; they are undefined elsewhere. - double min_value; - double max_value; - - // Coefficients of T_n (Chebyshev polynomials of the first kind). - // Degree 5/5 is a compromise between accuracy (0.1%) and numerical stability. - double p[5 + 1]; - double q[5 + 1]; -}; - -static inline double GammaPolynomial(double value) { - static const RationalPolynomial r = { - 0.971783, 590.188894, - { - 98.7821300963361, 164.273222212631, 92.948112871376, - 33.8165311212688, 6.91626704983562, 0.556380877028234 - }, - { - 1, 1.64339473427892, 0.89392405219969, 0.298947051776379, - 0.0507146002577288, 0.00226495093949756 - }}; - return r(value); -} - -} // namespace butteraugli - -#endif // BUTTERAUGLI_BUTTERAUGLI_H_ diff --git a/oss-internship-2020/guetzli/third_party/butteraugli/butteraugli/butteraugli_main.cc b/oss-internship-2020/guetzli/third_party/butteraugli/butteraugli/butteraugli_main.cc deleted file mode 100755 index f38af1d..0000000 --- a/oss-internship-2020/guetzli/third_party/butteraugli/butteraugli/butteraugli_main.cc +++ /dev/null @@ -1,457 +0,0 @@ -#include -#include -#include -#include -#include "butteraugli/butteraugli.h" - -extern "C" { -#include "png.h" -#include "jpeglib.h" -} - -namespace butteraugli { -namespace { - -// "rgb": cleared and filled with same-sized image planes (one per channel); -// either RGB, or RGBA if the PNG contains an alpha channel. -bool ReadPNG(FILE* f, std::vector* rgb) { - png_structp png_ptr = - png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); - if (!png_ptr) { - return false; - } - - png_infop info_ptr = png_create_info_struct(png_ptr); - if (!info_ptr) { - png_destroy_read_struct(&png_ptr, NULL, NULL); - return false; - } - - if (setjmp(png_jmpbuf(png_ptr)) != 0) { - // Ok we are here because of the setjmp. - png_destroy_read_struct(&png_ptr, &info_ptr, NULL); - return false; - } - - rewind(f); - png_init_io(png_ptr, f); - - // The png_transforms flags are as follows: - // packing == convert 1,2,4 bit images, - // strip == 16 -> 8 bits / channel, - // shift == use sBIT dynamics, and - // expand == palettes -> rgb, grayscale -> 8 bit images, tRNS -> alpha. - const unsigned int png_transforms = - PNG_TRANSFORM_PACKING | PNG_TRANSFORM_EXPAND | PNG_TRANSFORM_STRIP_16; - - png_read_png(png_ptr, info_ptr, png_transforms, NULL); - - png_bytep* row_pointers = png_get_rows(png_ptr, info_ptr); - - const int xsize = png_get_image_width(png_ptr, info_ptr); - const int ysize = png_get_image_height(png_ptr, info_ptr); - const int components = png_get_channels(png_ptr, info_ptr); - - *rgb = CreatePlanes(xsize, ysize, 3); - - switch (components) { - case 1: { - // GRAYSCALE - for (int y = 0; y < ysize; ++y) { - const uint8_t* const BUTTERAUGLI_RESTRICT row = row_pointers[y]; - uint8_t* const BUTTERAUGLI_RESTRICT row0 = (*rgb)[0].Row(y); - uint8_t* const BUTTERAUGLI_RESTRICT row1 = (*rgb)[1].Row(y); - uint8_t* const BUTTERAUGLI_RESTRICT row2 = (*rgb)[2].Row(y); - - for (int x = 0; x < xsize; ++x) { - const uint8_t gray = row[x]; - row0[x] = row1[x] = row2[x] = gray; - } - } - break; - } - case 2: { - // GRAYSCALE_ALPHA - rgb->push_back(Image8(xsize, ysize)); - for (int y = 0; y < ysize; ++y) { - const uint8_t* const BUTTERAUGLI_RESTRICT row = row_pointers[y]; - uint8_t* const BUTTERAUGLI_RESTRICT row0 = (*rgb)[0].Row(y); - uint8_t* const BUTTERAUGLI_RESTRICT row1 = (*rgb)[1].Row(y); - uint8_t* const BUTTERAUGLI_RESTRICT row2 = (*rgb)[2].Row(y); - uint8_t* const BUTTERAUGLI_RESTRICT row3 = (*rgb)[3].Row(y); - - for (int x = 0; x < xsize; ++x) { - const uint8_t gray = row[2 * x + 0]; - const uint8_t alpha = row[2 * x + 1]; - row0[x] = gray; - row1[x] = gray; - row2[x] = gray; - row3[x] = alpha; - } - } - break; - } - case 3: { - // RGB - for (int y = 0; y < ysize; ++y) { - const uint8_t* const BUTTERAUGLI_RESTRICT row = row_pointers[y]; - uint8_t* const BUTTERAUGLI_RESTRICT row0 = (*rgb)[0].Row(y); - uint8_t* const BUTTERAUGLI_RESTRICT row1 = (*rgb)[1].Row(y); - uint8_t* const BUTTERAUGLI_RESTRICT row2 = (*rgb)[2].Row(y); - - for (int x = 0; x < xsize; ++x) { - row0[x] = row[3 * x + 0]; - row1[x] = row[3 * x + 1]; - row2[x] = row[3 * x + 2]; - } - } - break; - } - case 4: { - // RGBA - rgb->push_back(Image8(xsize, ysize)); - for (int y = 0; y < ysize; ++y) { - const uint8_t* const BUTTERAUGLI_RESTRICT row = row_pointers[y]; - uint8_t* const BUTTERAUGLI_RESTRICT row0 = (*rgb)[0].Row(y); - uint8_t* const BUTTERAUGLI_RESTRICT row1 = (*rgb)[1].Row(y); - uint8_t* const BUTTERAUGLI_RESTRICT row2 = (*rgb)[2].Row(y); - uint8_t* const BUTTERAUGLI_RESTRICT row3 = (*rgb)[3].Row(y); - - for (int x = 0; x < xsize; ++x) { - row0[x] = row[4 * x + 0]; - row1[x] = row[4 * x + 1]; - row2[x] = row[4 * x + 2]; - row3[x] = row[4 * x + 3]; - } - } - break; - } - default: - png_destroy_read_struct(&png_ptr, &info_ptr, NULL); - return false; - } - png_destroy_read_struct(&png_ptr, &info_ptr, NULL); - return true; -} - -const double* NewSrgbToLinearTable() { - double* table = new double[256]; - for (int i = 0; i < 256; ++i) { - const double srgb = i / 255.0; - table[i] = - 255.0 * (srgb <= 0.04045 ? srgb / 12.92 - : std::pow((srgb + 0.055) / 1.055, 2.4)); - } - return table; -} - -void jpeg_catch_error(j_common_ptr cinfo) { - (*cinfo->err->output_message) (cinfo); - jmp_buf* jpeg_jmpbuf = (jmp_buf*) cinfo->client_data; - jpeg_destroy(cinfo); - longjmp(*jpeg_jmpbuf, 1); -} - -// "rgb": cleared and filled with same-sized image planes (one per channel); -// either RGB, or RGBA if the PNG contains an alpha channel. -bool ReadJPEG(FILE* f, std::vector* rgb) { - rewind(f); - - struct jpeg_decompress_struct cinfo; - struct jpeg_error_mgr jerr; - cinfo.err = jpeg_std_error(&jerr); - jmp_buf jpeg_jmpbuf; - cinfo.client_data = &jpeg_jmpbuf; - jerr.error_exit = jpeg_catch_error; - if (setjmp(jpeg_jmpbuf)) { - return false; - } - - jpeg_create_decompress(&cinfo); - - jpeg_stdio_src(&cinfo, f); - jpeg_read_header(&cinfo, TRUE); - jpeg_start_decompress(&cinfo); - - int row_stride = cinfo.output_width * cinfo.output_components; - JSAMPARRAY buffer = (*cinfo.mem->alloc_sarray) - ((j_common_ptr) &cinfo, JPOOL_IMAGE, row_stride, 1); - - const size_t xsize = cinfo.output_width; - const size_t ysize = cinfo.output_height; - - *rgb = CreatePlanes(xsize, ysize, 3); - - switch (cinfo.out_color_space) { - case JCS_GRAYSCALE: - while (cinfo.output_scanline < cinfo.output_height) { - jpeg_read_scanlines(&cinfo, buffer, 1); - - const uint8_t* const BUTTERAUGLI_RESTRICT row = buffer[0]; - uint8_t* const BUTTERAUGLI_RESTRICT row0 = - (*rgb)[0].Row(cinfo.output_scanline - 1); - uint8_t* const BUTTERAUGLI_RESTRICT row1 = - (*rgb)[1].Row(cinfo.output_scanline - 1); - uint8_t* const BUTTERAUGLI_RESTRICT row2 = - (*rgb)[2].Row(cinfo.output_scanline - 1); - - for (int x = 0; x < xsize; x++) { - const uint8_t gray = row[x]; - row0[x] = row1[x] = row2[x] = gray; - } - } - break; - - case JCS_RGB: - while (cinfo.output_scanline < cinfo.output_height) { - jpeg_read_scanlines(&cinfo, buffer, 1); - - const uint8_t* const BUTTERAUGLI_RESTRICT row = buffer[0]; - uint8_t* const BUTTERAUGLI_RESTRICT row0 = - (*rgb)[0].Row(cinfo.output_scanline - 1); - uint8_t* const BUTTERAUGLI_RESTRICT row1 = - (*rgb)[1].Row(cinfo.output_scanline - 1); - uint8_t* const BUTTERAUGLI_RESTRICT row2 = - (*rgb)[2].Row(cinfo.output_scanline - 1); - - for (int x = 0; x < xsize; x++) { - row0[x] = row[3 * x + 0]; - row1[x] = row[3 * x + 1]; - row2[x] = row[3 * x + 2]; - } - } - break; - - default: - jpeg_destroy_decompress(&cinfo); - return false; - } - - jpeg_finish_decompress(&cinfo); - jpeg_destroy_decompress(&cinfo); - return true; -} - -// Translate R, G, B channels from sRGB to linear space. If an alpha channel -// is present, overlay the image over a black or white background. Overlaying -// is done in the sRGB space; while technically incorrect, this is aligned with -// many other software (web browsers, WebP near lossless). -void FromSrgbToLinear(const std::vector& rgb, - std::vector& linear, int background) { - const size_t xsize = rgb[0].xsize(); - const size_t ysize = rgb[0].ysize(); - static const double* const kSrgbToLinearTable = NewSrgbToLinearTable(); - - if (rgb.size() == 3) { // RGB - for (int c = 0; c < 3; c++) { - linear.push_back(ImageF(xsize, ysize)); - for (int y = 0; y < ysize; ++y) { - const uint8_t* const BUTTERAUGLI_RESTRICT row_rgb = rgb[c].Row(y); - float* const BUTTERAUGLI_RESTRICT row_linear = linear[c].Row(y); - for (size_t x = 0; x < xsize; x++) { - const int value = row_rgb[x]; - row_linear[x] = kSrgbToLinearTable[value]; - } - } - } - } else { // RGBA - for (int c = 0; c < 3; c++) { - linear.push_back(ImageF(xsize, ysize)); - for (int y = 0; y < ysize; ++y) { - const uint8_t* const BUTTERAUGLI_RESTRICT row_rgb = rgb[c].Row(y); - float* const BUTTERAUGLI_RESTRICT row_linear = linear[c].Row(y); - const uint8_t* const BUTTERAUGLI_RESTRICT row_alpha = rgb[3].Row(y); - for (size_t x = 0; x < xsize; x++) { - int value; - if (row_alpha[x] == 255) { - value = row_rgb[x]; - } else if (row_alpha[x] == 0) { - value = background; - } else { - const int fg_weight = row_alpha[x]; - const int bg_weight = 255 - fg_weight; - value = - (row_rgb[x] * fg_weight + background * bg_weight + 127) / 255; - } - row_linear[x] = kSrgbToLinearTable[value]; - } - } - } - } -} - -std::vector ReadImageOrDie(const char* filename) { - std::vector rgb; - FILE* f = fopen(filename, "rb"); - if (!f) { - fprintf(stderr, "Cannot open %s\n", filename); - exit(1); - } - unsigned char magic[2]; - if (fread(magic, 1, 2, f) != 2) { - fprintf(stderr, "Cannot read from %s\n", filename); - exit(1); - } - if (magic[0] == 0xFF && magic[1] == 0xD8) { - if (!ReadJPEG(f, &rgb)) { - fprintf(stderr, "File %s is a malformed JPEG.\n", filename); - exit(1); - } - } else { - if (!ReadPNG(f, &rgb)) { - fprintf(stderr, "File %s is neither a valid JPEG nor a valid PNG.\n", - filename); - exit(1); - } - } - fclose(f); - return rgb; -} - -static void ScoreToRgb(double score, double good_threshold, - double bad_threshold, uint8_t rgb[3]) { - double heatmap[12][3] = { - { 0, 0, 0 }, - { 0, 0, 1 }, - { 0, 1, 1 }, - { 0, 1, 0 }, // Good level - { 1, 1, 0 }, - { 1, 0, 0 }, // Bad level - { 1, 0, 1 }, - { 0.5, 0.5, 1.0 }, - { 1.0, 0.5, 0.5 }, // Pastel colors for the very bad quality range. - { 1.0, 1.0, 0.5 }, - { 1, 1, 1, }, - { 1, 1, 1, }, - }; - if (score < good_threshold) { - score = (score / good_threshold) * 0.3; - } else if (score < bad_threshold) { - score = 0.3 + (score - good_threshold) / - (bad_threshold - good_threshold) * 0.15; - } else { - score = 0.45 + (score - bad_threshold) / - (bad_threshold * 12) * 0.5; - } - static const int kTableSize = sizeof(heatmap) / sizeof(heatmap[0]); - score = std::min(std::max( - score * (kTableSize - 1), 0.0), kTableSize - 2); - int ix = static_cast(score); - double mix = score - ix; - for (int i = 0; i < 3; ++i) { - double v = mix * heatmap[ix + 1][i] + (1 - mix) * heatmap[ix][i]; - rgb[i] = static_cast(255 * pow(v, 0.5) + 0.5); - } -} - -void CreateHeatMapImage(const ImageF& distmap, double good_threshold, - double bad_threshold, size_t xsize, size_t ysize, - std::vector* heatmap) { - heatmap->resize(3 * xsize * ysize); - for (size_t y = 0; y < ysize; ++y) { - for (size_t x = 0; x < xsize; ++x) { - int px = xsize * y + x; - double d = distmap.Row(y)[x]; - uint8_t* rgb = &(*heatmap)[3 * px]; - ScoreToRgb(d, good_threshold, bad_threshold, rgb); - } - } -} - -// main() function, within butteraugli namespace for convenience. -int Run(int argc, char* argv[]) { - if (argc != 3 && argc != 4) { - fprintf(stderr, - "Usage: %s {image1.(png|jpg|jpeg)} {image2.(png|jpg|jpeg)} " - "[heatmap.ppm]\n", - argv[0]); - return 1; - } - - std::vector rgb1 = ReadImageOrDie(argv[1]); - std::vector rgb2 = ReadImageOrDie(argv[2]); - - if (rgb1.size() != rgb2.size()) { - fprintf(stderr, "Different number of channels: %lu vs %lu\n", rgb1.size(), - rgb2.size()); - exit(1); - } - - for (size_t c = 0; c < rgb1.size(); ++c) { - if (rgb1[c].xsize() != rgb2[c].xsize() || - rgb1[c].ysize() != rgb2[c].ysize()) { - fprintf( - stderr, "The images are not equal in size: (%lu,%lu) vs (%lu,%lu)\n", - rgb1[c].xsize(), rgb2[c].xsize(), rgb1[c].ysize(), rgb2[c].ysize()); - return 1; - } - } - - // TODO: Figure out if it is a good idea to fetch the gamma from the image - // instead of applying sRGB conversion. - std::vector linear1, linear2; - // Overlay the image over a black background. - FromSrgbToLinear(rgb1, linear1, 0); - FromSrgbToLinear(rgb2, linear2, 0); - ImageF diff_map, diff_map_on_white; - double diff_value; - if (!butteraugli::ButteraugliInterface(linear1, linear2, diff_map, - diff_value)) { - fprintf(stderr, "Butteraugli comparison failed\n"); - return 1; - } - ImageF* diff_map_ptr = &diff_map; - if (rgb1.size() == 4 || rgb2.size() == 4) { - // If the alpha channel is present, overlay the image over a white - // background as well. - FromSrgbToLinear(rgb1, linear1, 255); - FromSrgbToLinear(rgb2, linear2, 255); - double diff_value_on_white; - if (!butteraugli::ButteraugliInterface(linear1, linear2, diff_map_on_white, - diff_value_on_white)) { - fprintf(stderr, "Butteraugli comparison failed\n"); - return 1; - } - if (diff_value_on_white > diff_value) { - diff_value = diff_value_on_white; - diff_map_ptr = &diff_map_on_white; - } - } - printf("%lf\n", diff_value); - - if (argc == 4) { - const double good_quality = ::butteraugli::ButteraugliFuzzyInverse(1.5); - const double bad_quality = ::butteraugli::ButteraugliFuzzyInverse(0.5); - std::vector rgb; - CreateHeatMapImage(*diff_map_ptr, good_quality, bad_quality, - rgb1[0].xsize(), rgb2[0].ysize(), &rgb); - FILE* const fmap = fopen(argv[3], "wb"); - if (fmap == NULL) { - fprintf(stderr, "Cannot open %s\n", argv[3]); - perror("fopen"); - return 1; - } - bool ok = true; - if (fprintf(fmap, "P6\n%lu %lu\n255\n", - rgb1[0].xsize(), rgb1[0].ysize()) < 0){ - perror("fprintf"); - ok = false; - } - if (ok && fwrite(rgb.data(), 1, rgb.size(), fmap) != rgb.size()) { - perror("fwrite"); - ok = false; - } - if (fclose(fmap) != 0) { - perror("fclose"); - ok = false; - } - if (!ok) return 1; - } - - return 0; -} - -} // namespace -} // namespace butteraugli - -int main(int argc, char** argv) { return butteraugli::Run(argc, argv); } diff --git a/oss-internship-2020/guetzli/third_party/butteraugli/png.BUILD b/oss-internship-2020/guetzli/third_party/butteraugli/png.BUILD deleted file mode 100755 index 9ff982b..0000000 --- a/oss-internship-2020/guetzli/third_party/butteraugli/png.BUILD +++ /dev/null @@ -1,33 +0,0 @@ -# Description: -# libpng is the official PNG reference library. - -licenses(["notice"]) # BSD/MIT-like license - -cc_library( - name = "png", - srcs = [ - "png.c", - "pngerror.c", - "pngget.c", - "pngmem.c", - "pngpread.c", - "pngread.c", - "pngrio.c", - "pngrtran.c", - "pngrutil.c", - "pngset.c", - "pngtrans.c", - "pngwio.c", - "pngwrite.c", - "pngwtran.c", - "pngwutil.c", - ], - hdrs = [ - "png.h", - "pngconf.h", - ], - includes = ["."], - linkopts = ["-lm"], - visibility = ["//visibility:public"], - deps = ["@zlib_archive//:zlib"], -) diff --git a/oss-internship-2020/guetzli/third_party/butteraugli/zlib.BUILD b/oss-internship-2020/guetzli/third_party/butteraugli/zlib.BUILD deleted file mode 100755 index edb77fd..0000000 --- a/oss-internship-2020/guetzli/third_party/butteraugli/zlib.BUILD +++ /dev/null @@ -1,36 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -licenses(["notice"]) # BSD/MIT-like license (for zlib) - -cc_library( - name = "zlib", - srcs = [ - "adler32.c", - "compress.c", - "crc32.c", - "crc32.h", - "deflate.c", - "deflate.h", - "gzclose.c", - "gzguts.h", - "gzlib.c", - "gzread.c", - "gzwrite.c", - "infback.c", - "inffast.c", - "inffast.h", - "inffixed.h", - "inflate.c", - "inflate.h", - "inftrees.c", - "inftrees.h", - "trees.c", - "trees.h", - "uncompr.c", - "zconf.h", - "zutil.c", - "zutil.h", - ], - hdrs = ["zlib.h"], - includes = ["."], -) From e99de21e9559cf9e00b5ed975975a74e8f8532ba Mon Sep 17 00:00:00 2001 From: Bohdan Tyshchenko Date: Mon, 17 Aug 2020 13:31:14 -0700 Subject: [PATCH 25/82] Quick update --- oss-internship-2020/guetzli/guetzli_transaction.h | 1 - 1 file changed, 1 deletion(-) diff --git a/oss-internship-2020/guetzli/guetzli_transaction.h b/oss-internship-2020/guetzli/guetzli_transaction.h index be66633..1a7c7d3 100644 --- a/oss-internship-2020/guetzli/guetzli_transaction.h +++ b/oss-internship-2020/guetzli/guetzli_transaction.h @@ -52,7 +52,6 @@ class GuetzliTransaction : public sapi::Transaction { } private: - //absl::Status Init() override; absl::Status Main() final; absl::Status LinkOutFile(int out_fd) const; From 465cf9c4bb55e46d5a9b2ccceb3f5e49ff9b83e3 Mon Sep 17 00:00:00 2001 From: Bohdan Tyshchenko Date: Wed, 19 Aug 2020 01:21:30 -0700 Subject: [PATCH 26/82] Codestyle fix --- oss-internship-2020/guetzli/README.md | 3 +++ oss-internship-2020/guetzli/guetzli_entry_points.cc | 12 ++++++------ oss-internship-2020/guetzli/guetzli_transaction.cc | 1 + oss-internship-2020/guetzli/guetzli_transaction.h | 7 ++----- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/oss-internship-2020/guetzli/README.md b/oss-internship-2020/guetzli/README.md index 042f165..97b3bc3 100644 --- a/oss-internship-2020/guetzli/README.md +++ b/oss-internship-2020/guetzli/README.md @@ -25,5 +25,8 @@ There are two different sets of unit tests which demonstrate how to use differen * `tests/guetzli_sapi_test.cc` - example usage of Guetzli sandboxed API. * `tests/guetzli_transaction_test.cc` - example usage of Guetzli transaction. +To run tests use following command: +`bazel test ...` + Also, there is an example of custom security policy for your sandbox in `guetzli_sandbox.h` diff --git a/oss-internship-2020/guetzli/guetzli_entry_points.cc b/oss-internship-2020/guetzli/guetzli_entry_points.cc index a36431b..7368928 100644 --- a/oss-internship-2020/guetzli/guetzli_entry_points.cc +++ b/oss-internship-2020/guetzli/guetzli_entry_points.cc @@ -154,7 +154,7 @@ sapi::StatusOr ReadPNG(const std::string& data) { xsize = png_get_image_width(png_ptr, info_ptr); ysize = png_get_image_height(png_ptr, info_ptr); - rgb.resize(3 * (xsize) * (ysize)); + rgb.resize(3 * xsize * ysize); const int components = png_get_channels(png_ptr, info_ptr); switch (components) { @@ -162,7 +162,7 @@ sapi::StatusOr ReadPNG(const std::string& data) { // GRAYSCALE for (int y = 0; y < ysize; ++y) { const uint8_t* row_in = row_pointers[y]; - uint8_t* row_out = &(rgb)[3 * y * (xsize)]; + uint8_t* row_out = &rgb[3 * y * xsize]; for (int x = 0; x < xsize; ++x) { const uint8_t gray = row_in[x]; row_out[3 * x + 0] = gray; @@ -176,7 +176,7 @@ sapi::StatusOr ReadPNG(const std::string& data) { // GRAYSCALE + ALPHA for (int y = 0; y < ysize; ++y) { const uint8_t* row_in = row_pointers[y]; - uint8_t* row_out = &(rgb)[3 * y * (xsize)]; + uint8_t* row_out = &rgb[3 * y * xsize]; for (int x = 0; x < xsize; ++x) { const uint8_t gray = BlendOnBlack(row_in[2 * x], row_in[2 * x + 1]); row_out[3 * x + 0] = gray; @@ -190,8 +190,8 @@ sapi::StatusOr ReadPNG(const std::string& data) { // RGB for (int y = 0; y < ysize; ++y) { const uint8_t* row_in = row_pointers[y]; - uint8_t* row_out = &(rgb)[3 * y * (xsize)]; - memcpy(row_out, row_in, 3 * (xsize)); + uint8_t* row_out = &rgb[3 * y * xsize]; + memcpy(row_out, row_in, 3 * xsize); } break; } @@ -199,7 +199,7 @@ sapi::StatusOr ReadPNG(const std::string& data) { // RGBA for (int y = 0; y < ysize; ++y) { const uint8_t* row_in = row_pointers[y]; - uint8_t* row_out = &(rgb)[3 * y * (xsize)]; + uint8_t* row_out = &rgb[3 * y * xsize]; for (int x = 0; x < xsize; ++x) { const uint8_t alpha = row_in[4 * x + 3]; row_out[3 * x + 0] = BlendOnBlack(row_in[4 * x + 0], alpha); diff --git a/oss-internship-2020/guetzli/guetzli_transaction.cc b/oss-internship-2020/guetzli/guetzli_transaction.cc index 64fac51..8fd1db4 100644 --- a/oss-internship-2020/guetzli/guetzli_transaction.cc +++ b/oss-internship-2020/guetzli/guetzli_transaction.cc @@ -107,6 +107,7 @@ absl::Status GuetzliTransaction::LinkOutFile(int out_fd) const { std::stringstream path; path << "/proc/self/fd/" << out_fd; + if (linkat(AT_FDCWD, path.str().c_str(), AT_FDCWD, params_.out_file, AT_SYMLINK_FOLLOW) < 0) { std::stringstream error; diff --git a/oss-internship-2020/guetzli/guetzli_transaction.h b/oss-internship-2020/guetzli/guetzli_transaction.h index 1a7c7d3..c9c0d7d 100644 --- a/oss-internship-2020/guetzli/guetzli_transaction.h +++ b/oss-internship-2020/guetzli/guetzli_transaction.h @@ -42,12 +42,11 @@ struct TransactionParams { // Create a new one for each processing operation class GuetzliTransaction : public sapi::Transaction { public: - GuetzliTransaction(TransactionParams params) + GuetzliTransaction(TransactionParams params, int retry_count = 0) : sapi::Transaction(std::make_unique()) , params_(std::move(params)) { - //TODO: Add retry count as a parameter - sapi::Transaction::set_retry_count(kDefaultTransactionRetryCount); + sapi::Transaction::set_retry_count(retry_count); sapi::Transaction::SetTimeLimit(0); // Infinite time limit } @@ -59,8 +58,6 @@ class GuetzliTransaction : public sapi::Transaction { const TransactionParams params_; ImageType image_type_ = ImageType::kJpeg; - - static const int kDefaultTransactionRetryCount = 0; }; } // namespace sandbox From 096d02625dd616fadb536cd29ed204aa8e2452ea Mon Sep 17 00:00:00 2001 From: doinachiroiu Date: Thu, 20 Aug 2020 07:44:27 +0000 Subject: [PATCH 27/82] Modified sapi root to a general path --- oss-internship-2020/pffft/.gitignore | 7 ++----- oss-internship-2020/pffft/CMakeLists.txt | 2 +- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/oss-internship-2020/pffft/.gitignore b/oss-internship-2020/pffft/.gitignore index 0469302..ad64a9d 100644 --- a/oss-internship-2020/pffft/.gitignore +++ b/oss-internship-2020/pffft/.gitignore @@ -1,6 +1,3 @@ -.hg/ -pffft.o -test_pffft.o -fftpack.o -libpffft.a +*.o +*.a pffft_main \ No newline at end of file diff --git a/oss-internship-2020/pffft/CMakeLists.txt b/oss-internship-2020/pffft/CMakeLists.txt index c0d62cb..a9ba38a 100644 --- a/oss-internship-2020/pffft/CMakeLists.txt +++ b/oss-internship-2020/pffft/CMakeLists.txt @@ -31,7 +31,7 @@ target_link_libraries(pffft PUBLIC ${MATH_LIBS}) # Adding dependencies -set(SAPI_ROOT "/usr/local/google/home/inach/sandboxed-api" CACHE PATH "Path to the Sandboxed API source tree") +set(SAPI_ROOT "../.." CACHE PATH "Path to the Sandboxed API source tree") # Then configure: # mkdir -p build && cd build # cmake .. -G Ninja -DSAPI_ROOT=$HOME/sapi_root From 94fcf82dd3aaf68481f0145338ec2f4b3d9941e6 Mon Sep 17 00:00:00 2001 From: doinachiroiu Date: Thu, 20 Aug 2020 08:21:32 +0000 Subject: [PATCH 28/82] Added comment on Nvalues[] and N purpose --- .../pffft/test_pffft_sandboxed.cc | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/oss-internship-2020/pffft/test_pffft_sandboxed.cc b/oss-internship-2020/pffft/test_pffft_sandboxed.cc index e4b4602..e1b897e 100644 --- a/oss-internship-2020/pffft/test_pffft_sandboxed.cc +++ b/oss-internship-2020/pffft/test_pffft_sandboxed.cc @@ -62,6 +62,13 @@ void show_output(const char* name, int N, int cplx, float flops, float t0, int main(int argc, char* argv[]) { gflags::ParseCommandLineFlags(&argc, &argv, true); + /* + * Nvalues is a vector keeping the values by which iterates N, its value + * representing the input length. More concrete, N is the number of + * data points the caclulus is up to (determinating its accuracy). + * To show the performance of Fast-Fourier Transformations the program is + * testing for various values of N. + */ int Nvalues[] = {64, 96, 128, 160, 192, 256, 384, 5 * 96, 512, 5 * 128, 3 * 256, 800, 1024, 2048, 2400, 4096, 8192, 9 * 1024, @@ -118,10 +125,16 @@ int main(int argc, char* argv[]) { X[k] = 0; } - // FFTPack benchmark + /* + * FFTPack benchmark + */ { + /* + * SIMD_SZ == 4 (returning value of pffft_simd_size()) + */ int max_iter_ = - max_iter / 4; // SIMD_SZ == 4 (returning value of pffft_simd_size()) + max_iter / 4; + if (max_iter_ == 0) max_iter_ = 1; if (cplx) { api.cffti(N, wrk_.PtrBoth()).IgnoreError(); @@ -145,7 +158,9 @@ int main(int argc, char* argv[]) { show_output("FFTPack", N, cplx, flops, t0, t1, max_iter_); } - // PFFFT benchmark + /* + * PFFFT benchmark + */ { sapi::StatusOr s = api.pffft_new_setup(N, cplx ? PFFFT_COMPLEX : PFFFT_REAL); From 257e87e07669c29bc8fb3b32bddd66b6a43d44d7 Mon Sep 17 00:00:00 2001 From: doinachiroiu Date: Thu, 20 Aug 2020 08:58:30 +0000 Subject: [PATCH 29/82] Added log printing for debug --- .../pffft/test_pffft_sandboxed.cc | 179 ++++++++++-------- 1 file changed, 95 insertions(+), 84 deletions(-) diff --git a/oss-internship-2020/pffft/test_pffft_sandboxed.cc b/oss-internship-2020/pffft/test_pffft_sandboxed.cc index e1b897e..523dce0 100644 --- a/oss-internship-2020/pffft/test_pffft_sandboxed.cc +++ b/oss-internship-2020/pffft/test_pffft_sandboxed.cc @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -61,6 +62,11 @@ void show_output(const char* name, int N, int cplx, float flops, float t0, } int main(int argc, char* argv[]) { + /* + * Initialize Google's logging library. + */ + google::InitGoogleLogging(argv[0]); + gflags::ParseCommandLineFlags(&argc, &argv, true); /* * Nvalues is a vector keeping the values by which iterates N, its value @@ -75,12 +81,12 @@ int main(int argc, char* argv[]) { 16384, 32768, 256 * 1024, 1024 * 1024, -1}; int i; - printf("initializing sandbox...\n"); + VLOG(1) << "Initializing sandbox...\n"; pffftSapiSandbox sandbox; sandbox.Init().IgnoreError(); - printf("Initialization: %s\n", sandbox.Init().ToString().c_str()); + VLOG(1) << "Initialization: " << sandbox.Init().ToString().c_str() << "\n"; pffftApi api(&sandbox); @@ -88,108 +94,113 @@ int main(int argc, char* argv[]) { cplx = 0; - for (i = 0; i < 23; i++) { - N = Nvalues[i]; + do { + for (i = 0; i < 23; i++) { + N = Nvalues[i]; - int Nfloat = N * (cplx ? 2 : 1); - int Nbytes = Nfloat * sizeof(float); - int pass; + int Nfloat = N * (cplx ? 2 : 1); + int Nbytes = Nfloat * sizeof(float); + int pass; - float ref[Nbytes], in[Nbytes], out[Nbytes], tmp[Nbytes], tmp2[Nbytes]; + float ref[Nbytes], in[Nbytes], out[Nbytes], tmp[Nbytes], tmp2[Nbytes]; - sapi::v::Array ref_(ref, Nbytes); - sapi::v::Array in_(in, Nbytes); - sapi::v::Array out_(out, Nbytes); - sapi::v::Array tmp_(tmp, Nbytes); - sapi::v::Array tmp2_(tmp2, Nbytes); + sapi::v::Array ref_(ref, Nbytes); + sapi::v::Array in_(in, Nbytes); + sapi::v::Array out_(out, Nbytes); + sapi::v::Array tmp_(tmp, Nbytes); + sapi::v::Array tmp2_(tmp2, Nbytes); - float wrk[2 * Nbytes + 15 * sizeof(float)]; - sapi::v::Array wrk_(wrk, 2 * Nbytes + 15 * sizeof(float)); + float wrk[2 * Nbytes + 15 * sizeof(float)]; + sapi::v::Array wrk_(wrk, 2 * Nbytes + 15 * sizeof(float)); - float ref_max = 0; - int k; + float ref_max = 0; + int k; - Nfloat = (cplx ? N * 2 : N); - float X[Nbytes], Y[Nbytes], Z[Nbytes]; - sapi::v::Array X_(X, Nbytes), Y_(Y, Nbytes), Z_(Z, Nbytes); + Nfloat = (cplx ? N * 2 : N); + float X[Nbytes], Y[Nbytes], Z[Nbytes]; + sapi::v::Array X_(X, Nbytes), Y_(Y, Nbytes), Z_(Z, Nbytes); - double t0, t1, flops; + double t0, t1, flops; - int max_iter = 5120000 / N * 4; -#ifdef __arm__ - max_iter /= 4; -#endif - int iter; + int max_iter = 5120000 / N * 4; + #ifdef __arm__ + max_iter /= 4; + #endif + int iter; - for (k = 0; k < Nfloat; ++k) { - X[k] = 0; - } + for (k = 0; k < Nfloat; ++k) { + X[k] = 0; + } - /* - * FFTPack benchmark - */ - { /* - * SIMD_SZ == 4 (returning value of pffft_simd_size()) + * FFTPack benchmark */ - int max_iter_ = - max_iter / 4; + { + /* + * SIMD_SZ == 4 (returning value of pffft_simd_size()) + */ + int max_iter_ = + max_iter / 4; - if (max_iter_ == 0) max_iter_ = 1; - if (cplx) { - api.cffti(N, wrk_.PtrBoth()).IgnoreError(); - } else { - api.rffti(N, wrk_.PtrBoth()).IgnoreError(); - } - t0 = uclock_sec(); - - for (iter = 0; iter < max_iter_; ++iter) { + if (max_iter_ == 0) max_iter_ = 1; if (cplx) { - api.cfftf(N, X_.PtrBoth(), wrk_.PtrBoth()).IgnoreError(); - api.cfftb(N, X_.PtrBoth(), wrk_.PtrBoth()).IgnoreError(); + api.cffti(N, wrk_.PtrBoth()).IgnoreError(); } else { - api.rfftf(N, X_.PtrBoth(), wrk_.PtrBoth()).IgnoreError(); - api.rfftb(N, X_.PtrBoth(), wrk_.PtrBoth()).IgnoreError(); + api.rffti(N, wrk_.PtrBoth()).IgnoreError(); } - } - t1 = uclock_sec(); - - flops = (max_iter_ * 2) * ((cplx ? 5 : 2.5) * N * log((double)N) / M_LN2); - show_output("FFTPack", N, cplx, flops, t0, t1, max_iter_); - } - - /* - * PFFFT benchmark - */ - { - sapi::StatusOr s = - api.pffft_new_setup(N, cplx ? PFFFT_COMPLEX : PFFFT_REAL); - - printf("Setup status is: %s\n", s.status().ToString().c_str()); - - if (s.ok()) { - sapi::v::RemotePtr s_reg(s.value()); - t0 = uclock_sec(); - for (iter = 0; iter < max_iter; ++iter) { - api.pffft_transform(&s_reg, X_.PtrBoth(), Z_.PtrBoth(), Y_.PtrBoth(), - PFFFT_FORWARD) - .IgnoreError(); - api.pffft_transform(&s_reg, X_.PtrBoth(), Z_.PtrBoth(), Y_.PtrBoth(), - PFFFT_FORWARD) - .IgnoreError(); + + for (iter = 0; iter < max_iter_; ++iter) { + if (cplx) { + api.cfftf(N, X_.PtrBoth(), wrk_.PtrBoth()).IgnoreError(); + api.cfftb(N, X_.PtrBoth(), wrk_.PtrBoth()).IgnoreError(); + } else { + api.rfftf(N, X_.PtrBoth(), wrk_.PtrBoth()).IgnoreError(); + api.rfftb(N, X_.PtrBoth(), wrk_.PtrBoth()).IgnoreError(); + } + } + t1 = uclock_sec(); + + flops = (max_iter_ * 2) * ((cplx ? 5 : 2.5) * N * log((double)N) / M_LN2); + show_output("FFTPack", N, cplx, flops, t0, t1, max_iter_); + } + + /* + * PFFFT benchmark + */ + { + sapi::StatusOr s = + api.pffft_new_setup(N, cplx ? PFFFT_COMPLEX : PFFFT_REAL); + + VLOG(1) << "Setup status is: " << s.status().ToString().c_str() << "\n"; + + if (s.ok()) { + sapi::v::RemotePtr s_reg(s.value()); + + t0 = uclock_sec(); + for (iter = 0; iter < max_iter; ++iter) { + api.pffft_transform(&s_reg, X_.PtrBoth(), Z_.PtrBoth(), Y_.PtrBoth(), + PFFFT_FORWARD) + .IgnoreError(); + api.pffft_transform(&s_reg, X_.PtrBoth(), Z_.PtrBoth(), Y_.PtrBoth(), + PFFFT_FORWARD) + .IgnoreError(); + } + + t1 = uclock_sec(); + api.pffft_destroy_setup(&s_reg).IgnoreError(); + + flops = + (max_iter * 2) * ((cplx ? 5 : 2.5) * N * log((double)N) / M_LN2); + show_output("PFFFT", N, cplx, flops, t0, t1, max_iter); } - t1 = uclock_sec(); - api.pffft_destroy_setup(&s_reg).IgnoreError(); - - flops = - (max_iter * 2) * ((cplx ? 5 : 2.5) * N * log((double)N) / M_LN2); - show_output("PFFFT", N, cplx, flops, t0, t1, max_iter); + VLOG(1) << "N = " << N << " SUCCESSFULLY\n\n"; } - printf("\n\n"); - } - } + } + + cplx = !cplx; + } while (cplx); return 0; } \ No newline at end of file From 25d18f985d258a34bc9b89fb41151174ab8f729b Mon Sep 17 00:00:00 2001 From: doinachiroiu Date: Thu, 20 Aug 2020 11:19:49 +0000 Subject: [PATCH 30/82] Updated README and change names of main files --- oss-internship-2020/pffft/README.md | 67 +++ oss-internship-2020/pffft/main_pffft.c | 419 ++++++++++++++++++ .../pffft/main_pffft_sandboxed.cc | 208 +++++++++ .../pffft/pffft_library_notes.txt | 416 +++++++++++++++++ 4 files changed, 1110 insertions(+) create mode 100644 oss-internship-2020/pffft/README.md create mode 100644 oss-internship-2020/pffft/main_pffft.c create mode 100644 oss-internship-2020/pffft/main_pffft_sandboxed.cc create mode 100644 oss-internship-2020/pffft/pffft_library_notes.txt diff --git a/oss-internship-2020/pffft/README.md b/oss-internship-2020/pffft/README.md new file mode 100644 index 0000000..52facf3 --- /dev/null +++ b/oss-internship-2020/pffft/README.md @@ -0,0 +1,67 @@ +# Sandboxing PFFFT library + +Builder: CMake +OS: Linux + +### For testing: +`cd build`, then `./pffft_sandboxed` + +### For debug: +`SAPI_VLOG_LEVEL=1 ./pffft_sandboxed --v=100 +--sandbox2_danger_danger_permit_all_and_log ` + +## ***About the project*** +*PFFFT library is concerned with 1D Fast-Fourier Transformations finding a +compromise between accuracy and speed. It deals with real and complex +vectors, both cases being illustrated in the testing part (`main_pffft.c` +for initially and original version, `main_pffft_sandboxed.cc` for our +currently implemented sandboxed version). +The original files can be found at: https://bitbucket.org/jpommier/pffft/src.* + +*The purpose of sandboxing is to limit the permissions and capabilities of +library’s methods, in order to secure the usage of them. +After obtaining the sandbox, the functions will be called through an +Sandbox API (being called `api` in the current test) and so, the +operations, system calls or namspaces access may be controlled. +From both `pffft.h` and `fftpack.h` headers, useful methods are added to +sapi library builded with CMake. There is also a need to link math library +as the transformations made require mathematical operators. +Regarding the testing of the methods, one main is doing this job by +iterating through a set of values, that represents the accuracy of +transformations and print the speed for each value and type of +transformation. More specifically, the input length is the target for +accuracy (named as `N`) and it stands for the number of data points from +the series that calculate the result of transformation. It is also +important to mention that the `cplx` variable stands for a boolean value +that tells the type of transformation (0 for REAL and 1 for COMPLEX) and +it is taken into account while testing. +In the end, the performance of PFFFT library it is outlined by the output.* + +#### CMake observations resume: + * linking pffft and fftpack (which contains necessary functions for pffft) + * set math library + +#### Sandboxed main observations resume: + * containing two testing parts (fft / pffft benchmarks) + * showing the performance of the transformations implies + testing them through various FFT dimenstions. + Variable N, the input length, will take specific values + meaning the number of points to which it is set the calculus + (more details of mathematical purpose of N - https://en.wikipedia.org/wiki/Cooley%E2%80%93Tukey_FFT_algorithm). + * output shows speed depending on the input length + + + +### Bugs history + - [Solved] pffft benchmark bug: "Sandbox not active" + N = 64, status OK, pffft_transform generates error + N > 64, status not OK + Problem on initialising sapi::StatusOr s; the memory that stays + for s is not the same with the address passed in pffft_transform function. + (sapi :: v :: GenericPtr - to be changed) + + Temporary solution: change the generated files to accept + uintptr_t instead of PFFFT_Setup + + Solution: using "sapi::v::RemotePtr" instead of "sapi::v::GenericPtr" + to access the memory of object s \ No newline at end of file diff --git a/oss-internship-2020/pffft/main_pffft.c b/oss-internship-2020/pffft/main_pffft.c new file mode 100644 index 0000000..a5d20c2 --- /dev/null +++ b/oss-internship-2020/pffft/main_pffft.c @@ -0,0 +1,419 @@ +/* + Copyright (c) 2013 Julien Pommier. + + Small test & bench for PFFFT, comparing its performance with the scalar FFTPACK, FFTW, and Apple vDSP + + How to build: + + on linux, with fftw3: + gcc -o test_pffft -DHAVE_FFTW -msse -mfpmath=sse -O3 -Wall -W pffft.c test_pffft.c fftpack.c -L/usr/local/lib -I/usr/local/include/ -lfftw3f -lm + + on macos, without fftw3: + clang -o test_pffft -DHAVE_VECLIB -O3 -Wall -W pffft.c test_pffft.c fftpack.c -L/usr/local/lib -I/usr/local/include/ -framework Accelerate + + on macos, with fftw3: + clang -o test_pffft -DHAVE_FFTW -DHAVE_VECLIB -O3 -Wall -W pffft.c test_pffft.c fftpack.c -L/usr/local/lib -I/usr/local/include/ -lfftw3f -framework Accelerate + + on windows, with visual c++: + cl /Ox -D_USE_MATH_DEFINES /arch:SSE test_pffft.c pffft.c fftpack.c + + build without SIMD instructions: + gcc -o test_pffft -DPFFFT_SIMD_DISABLE -O3 -Wall -W pffft.c test_pffft.c fftpack.c -lm + + */ + +#include "pffft.h" +#include "fftpack.h" + +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_SYS_TIMES +# include +# include +#endif + +#ifdef HAVE_VECLIB +# include +#endif + +#ifdef HAVE_FFTW +# include +#endif + +#define MAX(x,y) ((x)>(y)?(x):(y)) + +double frand() { + return rand()/(double)RAND_MAX; +} + +#if defined(HAVE_SYS_TIMES) + inline double uclock_sec(void) { + static double ttclk = 0.; + if (ttclk == 0.) ttclk = sysconf(_SC_CLK_TCK); + struct tms t; return ((double)times(&t)) / ttclk; + } +# else + double uclock_sec(void) +{ return (double)clock()/(double)CLOCKS_PER_SEC; } +#endif + + +/* compare results with the regular fftpack */ +void pffft_validate_N(int N, int cplx) { + int Nfloat = N*(cplx?2:1); + int Nbytes = Nfloat * sizeof(float); + float *ref, *in, *out, *tmp, *tmp2; + PFFFT_Setup *s = pffft_new_setup(N, cplx ? PFFFT_COMPLEX : PFFFT_REAL); + int pass; + + if (!s) { printf("Skipping N=%d, not supported\n", N); return; } + ref = pffft_aligned_malloc(Nbytes); + in = pffft_aligned_malloc(Nbytes); + out = pffft_aligned_malloc(Nbytes); + tmp = pffft_aligned_malloc(Nbytes); + tmp2 = pffft_aligned_malloc(Nbytes); + + for (pass=0; pass < 2; ++pass) { + float ref_max = 0; + int k; + //printf("N=%d pass=%d cplx=%d\n", N, pass, cplx); + // compute reference solution with FFTPACK + if (pass == 0) { + float *wrk = malloc(2*Nbytes+15*sizeof(float)); + for (k=0; k < Nfloat; ++k) { + ref[k] = in[k] = frand()*2-1; + out[k] = 1e30; + } + if (!cplx) { + rffti(N, wrk); + rfftf(N, ref, wrk); + // use our ordering for real ffts instead of the one of fftpack + { + float refN=ref[N-1]; + for (k=N-2; k >= 1; --k) ref[k+1] = ref[k]; + ref[1] = refN; + } + } else { + cffti(N, wrk); + cfftf(N, ref, wrk); + } + free(wrk); + } + + for (k = 0; k < Nfloat; ++k) ref_max = MAX(ref_max, fabs(ref[k])); + + + // pass 0 : non canonical ordering of transform coefficients + if (pass == 0) { + // test forward transform, with different input / output + pffft_transform(s, in, tmp, 0, PFFFT_FORWARD); + memcpy(tmp2, tmp, Nbytes); + memcpy(tmp, in, Nbytes); + pffft_transform(s, tmp, tmp, 0, PFFFT_FORWARD); + for (k = 0; k < Nfloat; ++k) { + assert(tmp2[k] == tmp[k]); + } + + // test reordering + pffft_zreorder(s, tmp, out, PFFFT_FORWARD); + pffft_zreorder(s, out, tmp, PFFFT_BACKWARD); + for (k = 0; k < Nfloat; ++k) { + assert(tmp2[k] == tmp[k]); + } + pffft_zreorder(s, tmp, out, PFFFT_FORWARD); + } else { + // pass 1 : canonical ordering of transform coeffs. + pffft_transform_ordered(s, in, tmp, 0, PFFFT_FORWARD); + memcpy(tmp2, tmp, Nbytes); + memcpy(tmp, in, Nbytes); + pffft_transform_ordered(s, tmp, tmp, 0, PFFFT_FORWARD); + for (k = 0; k < Nfloat; ++k) { + assert(tmp2[k] == tmp[k]); + } + memcpy(out, tmp, Nbytes); + } + + { + for (k=0; k < Nfloat; ++k) { + if (!(fabs(ref[k] - out[k]) < 1e-3*ref_max)) { + printf("%s forward PFFFT mismatch found for N=%d\n", (cplx?"CPLX":"REAL"), N); + exit(1); + } + } + + if (pass == 0) pffft_transform(s, tmp, out, 0, PFFFT_BACKWARD); + else pffft_transform_ordered(s, tmp, out, 0, PFFFT_BACKWARD); + memcpy(tmp2, out, Nbytes); + memcpy(out, tmp, Nbytes); + if (pass == 0) pffft_transform(s, out, out, 0, PFFFT_BACKWARD); + else pffft_transform_ordered(s, out, out, 0, PFFFT_BACKWARD); + for (k = 0; k < Nfloat; ++k) { + assert(tmp2[k] == out[k]); + out[k] *= 1.f/N; + } + for (k = 0; k < Nfloat; ++k) { + if (fabs(in[k] - out[k]) > 1e-3 * ref_max) { + printf("pass=%d, %s IFFFT does not match for N=%d\n", pass, (cplx?"CPLX":"REAL"), N); break; + exit(1); + } + } + } + + // quick test of the circular convolution in fft domain + { + float conv_err = 0, conv_max = 0; + + pffft_zreorder(s, ref, tmp, PFFFT_FORWARD); + memset(out, 0, Nbytes); + pffft_zconvolve_accumulate(s, ref, ref, out, 1.0); + pffft_zreorder(s, out, tmp2, PFFFT_FORWARD); + + for (k=0; k < Nfloat; k += 2) { + float ar = tmp[k], ai=tmp[k+1]; + if (cplx || k > 0) { + tmp[k] = ar*ar - ai*ai; + tmp[k+1] = 2*ar*ai; + } else { + tmp[0] = ar*ar; + tmp[1] = ai*ai; + } + } + + for (k=0; k < Nfloat; ++k) { + float d = fabs(tmp[k] - tmp2[k]), e = fabs(tmp[k]); + if (d > conv_err) conv_err = d; + if (e > conv_max) conv_max = e; + } + if (conv_err > 1e-5*conv_max) { + printf("zconvolve error ? %g %g\n", conv_err, conv_max); exit(1); + } + } + + } + + printf("%s PFFFT is OK for N=%d\n", (cplx?"CPLX":"REAL"), N); fflush(stdout); + + pffft_destroy_setup(s); + pffft_aligned_free(ref); + pffft_aligned_free(in); + pffft_aligned_free(out); + pffft_aligned_free(tmp); + pffft_aligned_free(tmp2); +} + +void pffft_validate(int cplx) { + static int Ntest[] = { 16, 32, 64, 96, 128, 160, 192, 256, 288, 384, 5*96, 512, 576, 5*128, 800, 864, 1024, 2048, 2592, 4000, 4096, 12000, 36864, 0}; + int k; + for (k = 0; Ntest[k]; ++k) { + int N = Ntest[k]; + if (N == 16 && !cplx) continue; + pffft_validate_N(N, cplx); + } +} + +int array_output_format = 0; + +void show_output(const char *name, int N, int cplx, float flops, float t0, float t1, int max_iter) { + float mflops = flops/1e6/(t1 - t0 + 1e-16); + if (array_output_format) { + if (flops != -1) { + printf("|%9.0f ", mflops); + } else printf("| n/a "); + } else { + if (flops != -1) { + printf("N=%5d, %s %16s : %6.0f MFlops [t=%6.0f ns, %d runs]\n", N, (cplx?"CPLX":"REAL"), name, mflops, (t1-t0)/2/max_iter * 1e9, max_iter); + } + } + fflush(stdout); +} + +void benchmark_ffts(int N, int cplx) { + int Nfloat = (cplx ? N*2 : N); + int Nbytes = Nfloat * sizeof(float); + float *X = pffft_aligned_malloc(Nbytes), *Y = pffft_aligned_malloc(Nbytes), *Z = pffft_aligned_malloc(Nbytes); + + double t0, t1, flops; + + int k; + int max_iter = 5120000/N*4; +#ifdef __arm__ + max_iter /= 4; +#endif + int iter; + + for (k = 0; k < Nfloat; ++k) { + X[k] = 0; //sqrtf(k+1); + } + + // FFTPack benchmark + { + float *wrk = malloc(2*Nbytes + 15*sizeof(float)); + int max_iter_ = max_iter/pffft_simd_size(); if (max_iter_ == 0) max_iter_ = 1; + if (cplx) cffti(N, wrk); + else rffti(N, wrk); + t0 = uclock_sec(); + + for (iter = 0; iter < max_iter_; ++iter) { + if (cplx) { + cfftf(N, X, wrk); + cfftb(N, X, wrk); + } else { + rfftf(N, X, wrk); + rfftb(N, X, wrk); + } + } + t1 = uclock_sec(); + free(wrk); + + flops = (max_iter_*2) * ((cplx ? 5 : 2.5)*N*log((double)N)/M_LN2); // see http://www.fftw.org/speed/method.html + show_output("FFTPack", N, cplx, flops, t0, t1, max_iter_); + } + +#ifdef HAVE_VECLIB + int log2N = (int)(log(N)/log(2) + 0.5f); + if (N == (1< 1 && strcmp(argv[1], "--array-format") == 0) { + array_output_format = 1; + } + +#ifndef PFFFT_SIMD_DISABLE + validate_pffft_simd(); +#endif + pffft_validate(1); + pffft_validate(0); + if (!array_output_format) { + for (i=0; Nvalues[i] > 0; ++i) { + benchmark_ffts(Nvalues[i], 0 /* real fft */); + } + for (i=0; Nvalues[i] > 0; ++i) { + benchmark_ffts(Nvalues[i], 1 /* cplx fft */); + } + } else { + printf("| input len "); + printf("|real FFTPack"); +#ifdef HAVE_VECLIB + printf("| real vDSP "); +#endif +#ifdef HAVE_FFTW + printf("| real FFTW "); +#endif + printf("| real PFFFT | "); + + printf("|cplx FFTPack"); +#ifdef HAVE_VECLIB + printf("| cplx vDSP "); +#endif +#ifdef HAVE_FFTW + printf("| cplx FFTW "); +#endif + printf("| cplx PFFFT |\n"); + for (i=0; Nvalues[i] > 0; ++i) { + printf("|%9d ", Nvalues[i]); + benchmark_ffts(Nvalues[i], 0); + printf("| "); + benchmark_ffts(Nvalues[i], 1); + printf("|\n"); + } + printf(" (numbers are given in MFlops)\n"); + } + + + return 0; +} diff --git a/oss-internship-2020/pffft/main_pffft_sandboxed.cc b/oss-internship-2020/pffft/main_pffft_sandboxed.cc new file mode 100644 index 0000000..662b949 --- /dev/null +++ b/oss-internship-2020/pffft/main_pffft_sandboxed.cc @@ -0,0 +1,208 @@ +#define GOOGLE_STRIP_LOG 1 + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "fftpack.h" +#include "pffft_sapi.sapi.h" +#include "sandboxed_api/util/flag.h" +#include "sandboxed_api/vars.h" + +ABSL_DECLARE_FLAG(string, sandbox2_danger_danger_permit_all); +ABSL_DECLARE_FLAG(string, sandbox2_danger_danger_permit_all_and_log); + +class pffftSapiSandbox : public pffftSandbox { + public: + std::unique_ptr ModifyPolicy( + sandbox2::PolicyBuilder*) override { + return sandbox2::PolicyBuilder() + .AllowStaticStartup() + .AllowOpen() + .AllowRead() + .AllowWrite() + .AllowSystemMalloc() + .AllowExit() + .AllowSyscalls({ + __NR_futex, + __NR_close, + __NR_getrusage, + }) + .DisableNamespaces() + .BuildOrDie(); + } +}; + +double frand() { return rand() / (double)RAND_MAX; } + +double uclock_sec(void) { return (double)clock() / (double)CLOCKS_PER_SEC; } + +int array_output_format = 0; + +void show_output(const char* name, int N, int cplx, float flops, float t0, + float t1, int max_iter) { + float mflops = flops / 1e6 / (t1 - t0 + 1e-16); + if (array_output_format) { + if (flops != -1) { + printf("|%9.0f ", mflops); + } else + printf("| n/a "); + } else { + if (flops != -1) { + printf("N=%5d, %s %16s : %6.0f MFlops [t=%6.0f ns, %d runs]\n", N, + (cplx ? "CPLX" : "REAL"), name, mflops, + (t1 - t0) / 2 / max_iter * 1e9, max_iter); + } + } + fflush(stdout); +} + +int main(int argc, char* argv[]) { + /* + * Initialize Google's logging library. + */ + google::InitGoogleLogging(argv[0]); + + gflags::ParseCommandLineFlags(&argc, &argv, true); + /* + * Nvalues is a vector keeping the values by which iterates N, its value + * representing the input length. More concrete, N is the number of + * data points the caclulus is up to (determinating its accuracy). + * To show the performance of Fast-Fourier Transformations the program is + * testing for various values of N. + */ + int Nvalues[] = {64, 96, 128, 160, 192, 256, + 384, 5 * 96, 512, 5 * 128, 3 * 256, 800, + 1024, 2048, 2400, 4096, 8192, 9 * 1024, + 16384, 32768, 256 * 1024, 1024 * 1024, -1}; + int i; + + VLOG(1) << "Initializing sandbox...\n"; + + pffftSapiSandbox sandbox; + sandbox.Init().IgnoreError(); + + VLOG(1) << "Initialization: " << sandbox.Init().ToString().c_str() << "\n"; + + pffftApi api(&sandbox); + + int N, cplx; + + cplx = 0; + + do { + for (i = 0; i < 23; i++) { + N = Nvalues[i]; + + int Nfloat = N * (cplx ? 2 : 1); + int Nbytes = Nfloat * sizeof(float); + int pass; + + float ref[Nbytes], in[Nbytes], out[Nbytes], tmp[Nbytes], tmp2[Nbytes]; + + sapi::v::Array ref_(ref, Nbytes); + sapi::v::Array in_(in, Nbytes); + sapi::v::Array out_(out, Nbytes); + sapi::v::Array tmp_(tmp, Nbytes); + sapi::v::Array tmp2_(tmp2, Nbytes); + + float wrk[2 * Nbytes + 15 * sizeof(float)]; + sapi::v::Array wrk_(wrk, 2 * Nbytes + 15 * sizeof(float)); + + float ref_max = 0; + int k; + + Nfloat = (cplx ? N * 2 : N); + float X[Nbytes], Y[Nbytes], Z[Nbytes]; + sapi::v::Array X_(X, Nbytes), Y_(Y, Nbytes), Z_(Z, Nbytes); + + double t0, t1, flops; + + int max_iter = 5120000 / N * 4; +#ifdef __arm__ + max_iter /= 4; +#endif + int iter; + + for (k = 0; k < Nfloat; ++k) { + X[k] = 0; + } + + /* + * FFTPack benchmark + */ + { + /* + * SIMD_SZ == 4 (returning value of pffft_simd_size()) + */ + int max_iter_ = max_iter / 4; + + if (max_iter_ == 0) max_iter_ = 1; + if (cplx) { + api.cffti(N, wrk_.PtrBoth()).IgnoreError(); + } else { + api.rffti(N, wrk_.PtrBoth()).IgnoreError(); + } + t0 = uclock_sec(); + + for (iter = 0; iter < max_iter_; ++iter) { + if (cplx) { + api.cfftf(N, X_.PtrBoth(), wrk_.PtrBoth()).IgnoreError(); + api.cfftb(N, X_.PtrBoth(), wrk_.PtrBoth()).IgnoreError(); + } else { + api.rfftf(N, X_.PtrBoth(), wrk_.PtrBoth()).IgnoreError(); + api.rfftb(N, X_.PtrBoth(), wrk_.PtrBoth()).IgnoreError(); + } + } + t1 = uclock_sec(); + + flops = + (max_iter_ * 2) * ((cplx ? 5 : 2.5) * N * log((double)N) / M_LN2); + show_output("FFTPack", N, cplx, flops, t0, t1, max_iter_); + } + + /* + * PFFFT benchmark + */ + { + sapi::StatusOr s = + api.pffft_new_setup(N, cplx ? PFFFT_COMPLEX : PFFFT_REAL); + + VLOG(1) << "Setup status is: " << s.status().ToString().c_str() << "\n"; + + if (s.ok()) { + sapi::v::RemotePtr s_reg(s.value()); + + t0 = uclock_sec(); + for (iter = 0; iter < max_iter; ++iter) { + api.pffft_transform(&s_reg, X_.PtrBoth(), Z_.PtrBoth(), + Y_.PtrBoth(), PFFFT_FORWARD) + .IgnoreError(); + api.pffft_transform(&s_reg, X_.PtrBoth(), Z_.PtrBoth(), + Y_.PtrBoth(), PFFFT_FORWARD) + .IgnoreError(); + } + + t1 = uclock_sec(); + api.pffft_destroy_setup(&s_reg).IgnoreError(); + + flops = + (max_iter * 2) * ((cplx ? 5 : 2.5) * N * log((double)N) / M_LN2); + show_output("PFFFT", N, cplx, flops, t0, t1, max_iter); + } + + VLOG(1) << "N = " << N << " SUCCESSFULLY\n\n"; + } + } + + cplx = !cplx; + } while (cplx); + + return 0; +} \ No newline at end of file diff --git a/oss-internship-2020/pffft/pffft_library_notes.txt b/oss-internship-2020/pffft/pffft_library_notes.txt new file mode 100644 index 0000000..ee20b42 --- /dev/null +++ b/oss-internship-2020/pffft/pffft_library_notes.txt @@ -0,0 +1,416 @@ +PFFFT: a pretty fast FFT. + +TL;DR +-- + +PFFFT does 1D Fast Fourier Transforms, of single precision real and +complex vectors. It tries do it fast, it tries to be correct, and it +tries to be small. Computations do take advantage of SSE1 instructions +on x86 cpus, Altivec on powerpc cpus, and NEON on ARM cpus. The +license is BSD-like. + + +Why does it exist: +-- + +I was in search of a good performing FFT library , preferably very +small and with a very liberal license. + +When one says "fft library", FFTW ("Fastest Fourier Transform in the +West") is probably the first name that comes to mind -- I guess that +99% of open-source projects that need a FFT do use FFTW, and are happy +with it. However, it is quite a large library , which does everything +fft related (2d transforms, 3d transforms, other transformations such +as discrete cosine , or fast hartley). And it is licensed under the +GNU GPL , which means that it cannot be used in non open-source +products. + +An alternative to FFTW that is really small, is the venerable FFTPACK +v4, which is available on NETLIB. A more recent version (v5) exists, +but it is larger as it deals with multi-dimensional transforms. This +is a library that is written in FORTRAN 77, a language that is now +considered as a bit antiquated by many. FFTPACKv4 was written in 1985, +by Dr Paul Swarztrauber of NCAR, more than 25 years ago ! And despite +its age, benchmarks show it that it still a very good performing FFT +library, see for example the 1d single precision benchmarks here: +http://www.fftw.org/speed/opteron-2.2GHz-32bit/ . It is however not +competitive with the fastest ones, such as FFTW, Intel MKL, AMD ACML, +Apple vDSP. The reason for that is that those libraries do take +advantage of the SSE SIMD instructions available on Intel CPUs, +available since the days of the Pentium III. These instructions deal +with small vectors of 4 floats at a time, instead of a single float +for a traditionnal FPU, so when using these instructions one may expect +a 4-fold performance improvement. + +The idea was to take this fortran fftpack v4 code, translate to C, +modify it to deal with those SSE instructions, and check that the +final performance is not completely ridiculous when compared to other +SIMD FFT libraries. Translation to C was performed with f2c ( +http://www.netlib.org/f2c/ ). The resulting file was a bit edited in +order to remove the thousands of gotos that were introduced by +f2c. You will find the fftpack.h and fftpack.c sources in the +repository, this a complete translation of +http://www.netlib.org/fftpack/ , with the discrete cosine transform +and the test program. There is no license information in the netlib +repository, but it was confirmed to me by the fftpack v5 curators that +the same terms do apply to fftpack v4: +http://www.cisl.ucar.edu/css/software/fftpack5/ftpk.html . This is a +"BSD-like" license, it is compatible with proprietary projects. + +Adapting fftpack to deal with the SIMD 4-element vectors instead of +scalar single precision numbers was more complex than I originally +thought, especially with the real transforms, and I ended up writing +more code than I planned.. + + +The code: +-- + +Only two files, in good old C, pffft.c and pffft.h . The API is very +very simple, just make sure that you read the comments in pffft.h. + + +Comparison with other FFTs: +-- + +The idea was not to break speed records, but to get a decently fast +fft that is at least 50% as fast as the fastest FFT -- especially on +slowest computers . I'm more focused on getting the best performance +on slow cpus (Atom, Intel Core 1, old Athlons, ARM Cortex-A9...), than +on getting top performance on today fastest cpus. + +It can be used in a real-time context as the fft functions do not +perform any memory allocation -- that is why they accept a 'work' +array in their arguments. + +It is also a bit focused on performing 1D convolutions, that is why it +provides "unordered" FFTs , and a fourier domain convolution +operation. + + +Benchmark results (cpu tested: core i7 2600, core 2 quad, core 1 duo, atom N270, cortex-A9, cortex-A15, A8X) +-- + +The benchmark shows the performance of various fft implementations measured in +MFlops, with the number of floating point operations being defined as 5Nlog2(N) +for a length N complex fft, and 2.5*Nlog2(N) for a real fft. +See http://www.fftw.org/speed/method.html for an explanation of these formulas. + +MacOS Lion, gcc 4.2, 64-bit, fftw 3.3 on a 3.4 GHz core i7 2600 + +Built with: + + gcc-4.2 -o test_pffft -arch x86_64 -O3 -Wall -W pffft.c test_pffft.c fftpack.c -L/usr/local/lib -I/usr/local/include/ -DHAVE_VECLIB -framework veclib -DHAVE_FFTW -lfftw3f + +| input len |real FFTPack| real vDSP | real FFTW | real PFFFT | |cplx FFTPack| cplx vDSP | cplx FFTW | cplx PFFFT | +|-----------+------------+------------+------------+------------| |------------+------------+------------+------------| +| 64 | 2816 | 8596 | 7329 | 8187 | | 2887 | 14898 | 14668 | 11108 | +| 96 | 3298 | n/a | 8378 | 7727 | | 3953 | n/a | 15680 | 10878 | +| 128 | 3507 | 11575 | 9266 | 10108 | | 4233 | 17598 | 16427 | 12000 | +| 160 | 3391 | n/a | 9838 | 10711 | | 4220 | n/a | 16653 | 11187 | +| 192 | 3919 | n/a | 9868 | 10956 | | 4297 | n/a | 15770 | 12540 | +| 256 | 4283 | 13179 | 10694 | 13128 | | 4545 | 19550 | 16350 | 13822 | +| 384 | 3136 | n/a | 10810 | 12061 | | 3600 | n/a | 16103 | 13240 | +| 480 | 3477 | n/a | 10632 | 12074 | | 3536 | n/a | 11630 | 12522 | +| 512 | 3783 | 15141 | 11267 | 13838 | | 3649 | 20002 | 16560 | 13580 | +| 640 | 3639 | n/a | 11164 | 13946 | | 3695 | n/a | 15416 | 13890 | +| 768 | 3800 | n/a | 11245 | 13495 | | 3590 | n/a | 15802 | 14552 | +| 800 | 3440 | n/a | 10499 | 13301 | | 3659 | n/a | 12056 | 13268 | +| 1024 | 3924 | 15605 | 11450 | 15339 | | 3769 | 20963 | 13941 | 15467 | +| 2048 | 4518 | 16195 | 11551 | 15532 | | 4258 | 20413 | 13723 | 15042 | +| 2400 | 4294 | n/a | 10685 | 13078 | | 4093 | n/a | 12777 | 13119 | +| 4096 | 4750 | 16596 | 11672 | 15817 | | 4157 | 19662 | 14316 | 14336 | +| 8192 | 3820 | 16227 | 11084 | 12555 | | 3691 | 18132 | 12102 | 13813 | +| 9216 | 3864 | n/a | 10254 | 12870 | | 3586 | n/a | 12119 | 13994 | +| 16384 | 3822 | 15123 | 10454 | 12822 | | 3613 | 16874 | 12370 | 13881 | +| 32768 | 4175 | 14512 | 10662 | 11095 | | 3881 | 14702 | 11619 | 11524 | +| 262144 | 3317 | 11429 | 6269 | 9517 | | 2810 | 11729 | 7757 | 10179 | +| 1048576 | 2913 | 10551 | 4730 | 5867 | | 2661 | 7881 | 3520 | 5350 | +|-----------+------------+------------+------------+------------| |------------+------------+------------+------------| + + +Debian 6, gcc 4.4.5, 64-bit, fftw 3.3.1 on a 3.4 GHz core i7 2600 + +Built with: +gcc -o test_pffft -DHAVE_FFTW -msse2 -O3 -Wall -W pffft.c test_pffft.c fftpack.c -L$HOME/local/lib -I$HOME/local/include/ -lfftw3f -lm + +| N (input length) | real FFTPack | real FFTW | real PFFFT | | cplx FFTPack | cplx FFTW | cplx PFFFT | +|------------------+--------------+--------------+--------------| |--------------+--------------+--------------| +| 64 | 3840 | 7680 | 8777 | | 4389 | 20480 | 11171 | +| 96 | 4214 | 9633 | 8429 | | 4816 | 22477 | 11238 | +| 128 | 3584 | 10240 | 10240 | | 5120 | 23893 | 11947 | +| 192 | 4854 | 11095 | 12945 | | 4854 | 22191 | 14121 | +| 256 | 4096 | 11703 | 16384 | | 5120 | 23406 | 13653 | +| 384 | 4395 | 14651 | 12558 | | 4884 | 19535 | 14651 | +| 512 | 5760 | 13166 | 15360 | | 4608 | 23040 | 15360 | +| 768 | 4907 | 14020 | 16357 | | 4461 | 19628 | 14020 | +| 1024 | 5120 | 14629 | 14629 | | 5120 | 20480 | 15754 | +| 2048 | 5632 | 14080 | 18773 | | 4693 | 12516 | 16091 | +| 4096 | 5120 | 13653 | 17554 | | 4726 | 7680 | 14456 | +| 8192 | 4160 | 7396 | 13312 | | 4437 | 14791 | 13312 | +| 9216 | 4210 | 6124 | 13473 | | 4491 | 7282 | 14970 | +| 16384 | 3976 | 11010 | 14313 | | 4210 | 11450 | 13631 | +| 32768 | 4260 | 10224 | 10954 | | 4260 | 6816 | 11797 | +| 262144 | 3736 | 6896 | 9961 | | 2359 | 8965 | 9437 | +| 1048576 | 2796 | 4534 | 6453 | | 1864 | 3078 | 5592 | +|------------------+--------------+--------------+--------------| |--------------+--------------+--------------| + + + +MacOS Snow Leopard, gcc 4.0, 32-bit, fftw 3.3 on a 1.83 GHz core 1 duo + +Built with: + + gcc -o test_pffft -DHAVE_FFTW -DHAVE_VECLIB -O3 -Wall -W pffft.c test_pffft.c fftpack.c -L/usr/local/lib -I/usr/local/include/ -lfftw3f -framework veclib + +| input len |real FFTPack| real vDSP | real FFTW | real PFFFT | |cplx FFTPack| cplx vDSP | cplx FFTW | cplx PFFFT | +|-----------+------------+------------+------------+------------| |------------+------------+------------+------------| +| 64 | 745 | 2145 | 1706 | 2028 | | 961 | 3356 | 3313 | 2300 | +| 96 | 877 | n/a | 1976 | 1978 | | 1059 | n/a | 3333 | 2233 | +| 128 | 951 | 2808 | 2213 | 2279 | | 1202 | 3803 | 3739 | 2494 | +| 192 | 1002 | n/a | 2456 | 2429 | | 1186 | n/a | 3701 | 2508 | +| 256 | 1065 | 3205 | 2641 | 2793 | | 1302 | 4013 | 3912 | 2663 | +| 384 | 845 | n/a | 2759 | 2499 | | 948 | n/a | 3729 | 2504 | +| 512 | 900 | 3476 | 2956 | 2759 | | 974 | 4057 | 3954 | 2645 | +| 768 | 910 | n/a | 2912 | 2737 | | 975 | n/a | 3837 | 2614 | +| 1024 | 936 | 3583 | 3107 | 3009 | | 1006 | 4124 | 3821 | 2697 | +| 2048 | 1057 | 3585 | 3091 | 2837 | | 1089 | 3889 | 3701 | 2513 | +| 4096 | 1083 | 3524 | 3092 | 2733 | | 1039 | 3617 | 3462 | 2364 | +| 8192 | 874 | 3252 | 2967 | 2363 | | 911 | 3106 | 2789 | 2302 | +| 9216 | 898 | n/a | 2420 | 2290 | | 865 | n/a | 2676 | 2204 | +| 16384 | 903 | 2892 | 2506 | 2421 | | 899 | 3026 | 2797 | 2289 | +| 32768 | 965 | 2837 | 2550 | 2358 | | 920 | 2922 | 2763 | 2240 | +| 262144 | 738 | 2422 | 1589 | 1708 | | 610 | 2038 | 1436 | 1091 | +| 1048576 | 528 | 1207 | 845 | 880 | | 606 | 1020 | 669 | 1036 | +|-----------+------------+------------+------------+------------| |------------+------------+------------+------------| + + + +Ubuntu 11.04, gcc 4.5, 32-bit, fftw 3.2 on a 2.66 core 2 quad + +Built with: +gcc -o test_pffft -DHAVE_FFTW -msse -mfpmath=sse -O3 -Wall -W pffft.c test_pffft.c fftpack.c -L/usr/local/lib -I/usr/local/include/ -lfftw3f -lm + +| input len |real FFTPack| real FFTW | real PFFFT | |cplx FFTPack| cplx FFTW | cplx PFFFT | +|-----------+------------+------------+------------| |------------+------------+------------| +| 64 | 1920 | 3614 | 5120 | | 2194 | 7680 | 6467 | +| 96 | 1873 | 3549 | 5187 | | 2107 | 8429 | 5863 | +| 128 | 2240 | 3773 | 5514 | | 2560 | 7964 | 6827 | +| 192 | 1765 | 4569 | 7767 | | 2284 | 9137 | 7061 | +| 256 | 2048 | 5461 | 7447 | | 2731 | 9638 | 7802 | +| 384 | 1998 | 5861 | 6762 | | 2313 | 9253 | 7644 | +| 512 | 2095 | 6144 | 7680 | | 2194 | 10240 | 7089 | +| 768 | 2230 | 5773 | 7549 | | 2045 | 10331 | 7010 | +| 1024 | 2133 | 6400 | 8533 | | 2133 | 10779 | 7877 | +| 2048 | 2011 | 7040 | 8665 | | 1942 | 10240 | 7768 | +| 4096 | 2194 | 6827 | 8777 | | 1755 | 9452 | 6827 | +| 8192 | 1849 | 6656 | 6656 | | 1752 | 7831 | 6827 | +| 9216 | 1871 | 5858 | 6416 | | 1643 | 6909 | 6266 | +| 16384 | 1883 | 6223 | 6506 | | 1664 | 7340 | 6982 | +| 32768 | 1826 | 6390 | 6667 | | 1631 | 7481 | 6971 | +| 262144 | 1546 | 4075 | 5977 | | 1299 | 3415 | 3551 | +| 1048576 | 1104 | 2071 | 1730 | | 1104 | 1149 | 1834 | +|-----------+------------+------------+------------| |------------+------------+------------| + + + +Ubuntu 11.04, gcc 4.5, 32-bit, fftw 3.3 on a 1.6 GHz Atom N270 + +Built with: +gcc -o test_pffft -DHAVE_FFTW -msse -mfpmath=sse -O3 -Wall -W pffft.c test_pffft.c fftpack.c -L/usr/local/lib -I/usr/local/include/ -lfftw3f -lm + +| N (input length) | real FFTPack | real FFTW | real PFFFT | | cplx FFTPack | cplx FFTW | cplx PFFFT | +|------------------+--------------+--------------+--------------| |--------------+--------------+--------------| +| 64 | 452 | 1041 | 1336 | | 549 | 2318 | 1781 | +| 96 | 444 | 1297 | 1297 | | 503 | 2408 | 1686 | +| 128 | 527 | 1525 | 1707 | | 543 | 2655 | 1886 | +| 192 | 498 | 1653 | 1849 | | 539 | 2678 | 1942 | +| 256 | 585 | 1862 | 2156 | | 594 | 2777 | 2244 | +| 384 | 499 | 1870 | 1998 | | 511 | 2586 | 1890 | +| 512 | 562 | 2095 | 2194 | | 542 | 2973 | 2194 | +| 768 | 545 | 2045 | 2133 | | 545 | 2365 | 2133 | +| 1024 | 595 | 2133 | 2438 | | 569 | 2695 | 2179 | +| 2048 | 587 | 2125 | 2347 | | 521 | 2230 | 1707 | +| 4096 | 495 | 1890 | 1834 | | 492 | 1876 | 1672 | +| 8192 | 469 | 1548 | 1729 | | 438 | 1740 | 1664 | +| 9216 | 468 | 1663 | 1663 | | 446 | 1585 | 1531 | +| 16384 | 453 | 1608 | 1767 | | 398 | 1476 | 1664 | +| 32768 | 456 | 1420 | 1503 | | 387 | 1388 | 1345 | +| 262144 | 309 | 385 | 726 | | 262 | 415 | 840 | +| 1048576 | 280 | 351 | 739 | | 261 | 313 | 797 | +|------------------+--------------+--------------+--------------| |--------------+--------------+--------------| + + + +Windows 7, visual c++ 2010 on a 1.6 GHz Atom N270 + +Built with: +cl /Ox -D_USE_MATH_DEFINES /arch:SSE test_pffft.c pffft.c fftpack.c + +(visual c++ is definitively not very good with SSE intrinsics...) + +| N (input length) | real FFTPack | real PFFFT | | cplx FFTPack | cplx PFFFT | +|------------------+--------------+--------------| |--------------+--------------| +| 64 | 173 | 1009 | | 174 | 1159 | +| 96 | 169 | 1029 | | 188 | 1201 | +| 128 | 195 | 1242 | | 191 | 1275 | +| 192 | 178 | 1312 | | 184 | 1276 | +| 256 | 196 | 1591 | | 186 | 1281 | +| 384 | 172 | 1409 | | 181 | 1281 | +| 512 | 187 | 1640 | | 181 | 1313 | +| 768 | 171 | 1614 | | 176 | 1258 | +| 1024 | 186 | 1812 | | 178 | 1223 | +| 2048 | 190 | 1707 | | 186 | 1099 | +| 4096 | 182 | 1446 | | 177 | 975 | +| 8192 | 175 | 1345 | | 169 | 1034 | +| 9216 | 165 | 1271 | | 168 | 1023 | +| 16384 | 166 | 1396 | | 165 | 949 | +| 32768 | 172 | 1311 | | 161 | 881 | +| 262144 | 136 | 632 | | 134 | 629 | +| 1048576 | 134 | 698 | | 127 | 623 | +|------------------+--------------+--------------| |--------------+--------------| + + + +Ubuntu 12.04, gcc-4.7.3, 32-bit, with fftw 3.3.3 (built with --enable-neon), on a 1.2GHz ARM Cortex A9 (Tegra 3) + +Built with: +gcc-4.7 -O3 -DHAVE_FFTW -march=armv7-a -mtune=cortex-a9 -mfloat-abi=hard -mfpu=neon -ffast-math test_pffft.c pffft.c -o test_pffft_arm fftpack.c -lm -I/usr/local/include/ -L/usr/local/lib/ -lfftw3f + +| input len |real FFTPack| real FFTW | real PFFFT | |cplx FFTPack| cplx FFTW | cplx PFFFT | +|-----------+------------+------------+------------| |------------+------------+------------| +| 64 | 549 | 452 | 731 | | 512 | 602 | 640 | +| 96 | 421 | 272 | 702 | | 496 | 571 | 602 | +| 128 | 498 | 512 | 815 | | 597 | 618 | 652 | +| 160 | 521 | 536 | 815 | | 586 | 669 | 625 | +| 192 | 539 | 571 | 883 | | 485 | 597 | 626 | +| 256 | 640 | 539 | 975 | | 569 | 611 | 671 | +| 384 | 499 | 610 | 879 | | 499 | 602 | 637 | +| 480 | 518 | 507 | 877 | | 496 | 661 | 616 | +| 512 | 524 | 591 | 1002 | | 549 | 678 | 668 | +| 640 | 542 | 612 | 955 | | 568 | 663 | 645 | +| 768 | 557 | 613 | 981 | | 491 | 663 | 598 | +| 800 | 514 | 353 | 882 | | 514 | 360 | 574 | +| 1024 | 640 | 640 | 1067 | | 492 | 683 | 602 | +| 2048 | 587 | 640 | 908 | | 486 | 640 | 552 | +| 2400 | 479 | 368 | 777 | | 422 | 376 | 518 | +| 4096 | 511 | 614 | 853 | | 426 | 640 | 534 | +| 8192 | 415 | 584 | 708 | | 386 | 622 | 516 | +| 9216 | 419 | 571 | 687 | | 364 | 586 | 506 | +| 16384 | 426 | 577 | 716 | | 398 | 606 | 530 | +| 32768 | 417 | 572 | 673 | | 399 | 572 | 468 | +| 262144 | 219 | 380 | 293 | | 255 | 431 | 343 | +| 1048576 | 202 | 274 | 237 | | 265 | 282 | 355 | +|-----------+------------+------------+------------| |------------+------------+------------| + +Same platform as above, but this time pffft and fftpack are built with clang 3.2: + +clang -O3 -DHAVE_FFTW -march=armv7-a -mtune=cortex-a9 -mfloat-abi=hard -mfpu=neon -ffast-math test_pffft.c pffft.c -o test_pffft_arm fftpack.c -lm -I/usr/local/include/ -L/usr/local/lib/ -lfftw3f + +| input len |real FFTPack| real FFTW | real PFFFT | |cplx FFTPack| cplx FFTW | cplx PFFFT | +|-----------+------------+------------+------------| |------------+------------+------------| +| 64 | 427 | 452 | 853 | | 427 | 602 | 1024 | +| 96 | 351 | 276 | 843 | | 337 | 571 | 963 | +| 128 | 373 | 512 | 996 | | 390 | 618 | 1054 | +| 160 | 426 | 536 | 987 | | 375 | 669 | 914 | +| 192 | 404 | 571 | 1079 | | 388 | 588 | 1079 | +| 256 | 465 | 539 | 1205 | | 445 | 602 | 1170 | +| 384 | 366 | 610 | 1099 | | 343 | 594 | 1099 | +| 480 | 356 | 507 | 1140 | | 335 | 651 | 931 | +| 512 | 411 | 591 | 1213 | | 384 | 649 | 1124 | +| 640 | 398 | 612 | 1193 | | 373 | 654 | 901 | +| 768 | 409 | 613 | 1227 | | 383 | 663 | 1044 | +| 800 | 411 | 348 | 1073 | | 353 | 358 | 809 | +| 1024 | 427 | 640 | 1280 | | 413 | 692 | 1004 | +| 2048 | 414 | 626 | 1126 | | 371 | 640 | 853 | +| 2400 | 399 | 373 | 898 | | 319 | 368 | 653 | +| 4096 | 404 | 602 | 1059 | | 357 | 633 | 778 | +| 8192 | 332 | 584 | 792 | | 308 | 616 | 716 | +| 9216 | 322 | 561 | 783 | | 299 | 586 | 687 | +| 16384 | 344 | 568 | 778 | | 314 | 617 | 745 | +| 32768 | 342 | 564 | 737 | | 314 | 552 | 629 | +| 262144 | 201 | 383 | 313 | | 227 | 435 | 413 | +| 1048576 | 187 | 262 | 251 | | 228 | 281 | 409 | +|-----------+------------+------------+------------| |------------+------------+------------| + +So it looks like, on ARM, gcc 4.7 is the best at scalar floating point +(the fftpack performance numbers are better with gcc), while clang is +the best with neon intrinsics (see how pffft perf has improved with +clang 3.2). + + +NVIDIA Jetson TK1 board, gcc-4.8.2. The cpu is a 2.3GHz cortex A15 (Tegra K1). + +Built with: +gcc -O3 -march=armv7-a -mtune=native -mfloat-abi=hard -mfpu=neon -ffast-math test_pffft.c pffft.c -o test_pffft_arm fftpack.c -lm + +| input len |real FFTPack| real PFFFT | |cplx FFTPack| cplx PFFFT | +|-----------+------------+------------| |------------+------------| +| 64 | 1735 | 3308 | | 1994 | 3744 | +| 96 | 1596 | 3448 | | 1987 | 3572 | +| 128 | 1807 | 4076 | | 2255 | 3960 | +| 160 | 1769 | 4083 | | 2071 | 3845 | +| 192 | 1990 | 4233 | | 2017 | 3939 | +| 256 | 2191 | 4882 | | 2254 | 4346 | +| 384 | 1878 | 4492 | | 2073 | 4012 | +| 480 | 1748 | 4398 | | 1923 | 3951 | +| 512 | 2030 | 5064 | | 2267 | 4195 | +| 640 | 1918 | 4756 | | 2094 | 4184 | +| 768 | 2099 | 4907 | | 2048 | 4297 | +| 800 | 1822 | 4555 | | 1880 | 4063 | +| 1024 | 2232 | 5355 | | 2187 | 4420 | +| 2048 | 2176 | 4983 | | 2027 | 3602 | +| 2400 | 1741 | 4256 | | 1710 | 3344 | +| 4096 | 1816 | 3914 | | 1851 | 3349 | +| 8192 | 1716 | 3481 | | 1700 | 3255 | +| 9216 | 1735 | 3589 | | 1653 | 3094 | +| 16384 | 1567 | 3483 | | 1637 | 3244 | +| 32768 | 1624 | 3240 | | 1655 | 3156 | +| 262144 | 1012 | 1898 | | 983 | 1503 | +| 1048576 | 876 | 1154 | | 868 | 1341 | +|-----------+------------+------------| |------------+------------| + +The performance on the tegra K1 is pretty impressive. I'm not +including the FFTW numbers as they as slightly below the scalar +fftpack numbers, so something must be wrong (however it seems to be +correctly configured and is using neon simd instructions). + +When using clang 3.4 the pffft version is even a bit faster, reaching +5.7 GFlops for real ffts of size 1024. + + +iPad Air 2 with iOS9, xcode 8.0, arm64. The cpu is an Apple A8X, supposedly running at 1.5GHz. + +| input len |real FFTPack| real vDSP | real PFFFT | |cplx FFTPack| cplx vDSP | cplx PFFFT | +|-----------+------------+------------+------------| |------------+------------+------------| +| 64 | 2517 | 7995 | 6086 | | 2725 | 13006 | 8495 | +| 96 | 2442 | n/a | 6691 | | 2256 | n/a | 7991 | +| 128 | 2664 | 10186 | 7877 | | 2575 | 15115 | 9115 | +| 160 | 2638 | n/a | 8283 | | 2682 | n/a | 8806 | +| 192 | 2903 | n/a | 9083 | | 2634 | n/a | 8980 | +| 256 | 3184 | 11452 | 10039 | | 3026 | 15410 | 10199 | +| 384 | 2665 | n/a | 10100 | | 2275 | n/a | 9247 | +| 480 | 2546 | n/a | 9863 | | 2341 | n/a | 8892 | +| 512 | 2832 | 12197 | 10989 | | 2547 | 16768 | 10154 | +| 640 | 2755 | n/a | 10461 | | 2569 | n/a | 9666 | +| 768 | 2998 | n/a | 11355 | | 2585 | n/a | 9813 | +| 800 | 2516 | n/a | 10332 | | 2433 | n/a | 9164 | +| 1024 | 3109 | 12965 | 12114 | | 2869 | 16448 | 10519 | +| 2048 | 3027 | 12996 | 12023 | | 2648 | 17304 | 10307 | +| 2400 | 2515 | n/a | 10372 | | 2355 | n/a | 8443 | +| 4096 | 3204 | 13603 | 12359 | | 2814 | 16570 | 9780 | +| 8192 | 2759 | 13422 | 10824 | | 2153 | 15652 | 7884 | +| 9216 | 2700 | n/a | 9938 | | 2241 | n/a | 7900 | +| 16384 | 2280 | 13057 | 7976 | | 593 | 4272 | 2534 | +| 32768 | 768 | 4269 | 2882 | | 606 | 4405 | 2604 | +| 262144 | 724 | 3527 | 2630 | | 534 | 2418 | 2157 | +| 1048576 | 674 | 1467 | 2135 | | 530 | 1621 | 2055 | +|-----------+------------+------------+------------| |------------+------------+------------| + +I double-checked to make sure I did not make a mistake in the time +measurements, as the numbers are much higher than what I initially +expected. They are in fact higher than the number I get on the 2.8GHz +Xeon of my 2008 mac pro.. (except for FFT lengths >= 32768 where +having a big cache is useful). A good surprise is also that the perf +is not too far from apple's vDSP (at least for the real FFT). + From fc6e9e82c60987271cbb286327ea6dcf2ca4656d Mon Sep 17 00:00:00 2001 From: doinachiroiu Date: Thu, 20 Aug 2020 11:24:19 +0000 Subject: [PATCH 31/82] Unnecessary files removed --- oss-internship-2020/pffft/CMakeLists.txt | 4 +- oss-internship-2020/pffft/README.txt | 32 -- oss-internship-2020/pffft/README_pffft.txt | 416 ----------------- oss-internship-2020/pffft/myNotes.txt | 101 ----- oss-internship-2020/pffft/test_pffft.c | 419 ------------------ .../pffft/test_pffft_sandboxed.cc | 206 --------- 6 files changed, 2 insertions(+), 1176 deletions(-) delete mode 100644 oss-internship-2020/pffft/README.txt delete mode 100644 oss-internship-2020/pffft/README_pffft.txt delete mode 100644 oss-internship-2020/pffft/myNotes.txt delete mode 100644 oss-internship-2020/pffft/test_pffft.c delete mode 100644 oss-internship-2020/pffft/test_pffft_sandboxed.cc diff --git a/oss-internship-2020/pffft/CMakeLists.txt b/oss-internship-2020/pffft/CMakeLists.txt index a9ba38a..6c7e776 100644 --- a/oss-internship-2020/pffft/CMakeLists.txt +++ b/oss-internship-2020/pffft/CMakeLists.txt @@ -13,7 +13,7 @@ add_library(pffft STATIC ) add_executable(pffft_main - test_pffft.c + main_pffft.c ) target_link_libraries(pffft_main PRIVATE @@ -82,7 +82,7 @@ target_include_directories(pffft_sapi INTERFACE ) add_executable(pffft_sandboxed - test_pffft_sandboxed.cc + main_pffft_sandboxed.cc ) target_link_libraries(pffft_sandboxed PRIVATE diff --git a/oss-internship-2020/pffft/README.txt b/oss-internship-2020/pffft/README.txt deleted file mode 100644 index c7ac70b..0000000 --- a/oss-internship-2020/pffft/README.txt +++ /dev/null @@ -1,32 +0,0 @@ -Sandboxing PFFFT library - -Builder: CMake - -For testing: -`cd build`, then `./pffft_sandboxed` - -For debug: -`SAPI_VLOG_LEVEL=1 ./pffft_sandboxed --v=100 ---sandbox2_danger_danger_permit_all_and_log ` - -CMake observations: - * linking pffft and fftpack (which contains necessary functions for pffft) - * set math library - -Sandboxed main observations: - * containing two testing parts (fft / pffft benchmarks) - ! current stage: fft - works :) - pffft - implemented - * (Solved) pffft benchmark bug: "Sandbox not active" - N = 64, status OK, pffft_transform generates error - N > 64, status not OK - Problem on initialising sapi::StatusOr s; - the memory that stays for s is not the same with the address passed - in pffft_transform function. - (sapi :: v :: GenericPtr - to be changed) - - Temporary solution: change the generated files to accept - uintptr_t instead of PFFFT_Setup - - Solution: using "sapi :: v :: RemotePtr" instead of "sapi :: v :: GenericPtr" - to access the memory of object s diff --git a/oss-internship-2020/pffft/README_pffft.txt b/oss-internship-2020/pffft/README_pffft.txt deleted file mode 100644 index ee20b42..0000000 --- a/oss-internship-2020/pffft/README_pffft.txt +++ /dev/null @@ -1,416 +0,0 @@ -PFFFT: a pretty fast FFT. - -TL;DR --- - -PFFFT does 1D Fast Fourier Transforms, of single precision real and -complex vectors. It tries do it fast, it tries to be correct, and it -tries to be small. Computations do take advantage of SSE1 instructions -on x86 cpus, Altivec on powerpc cpus, and NEON on ARM cpus. The -license is BSD-like. - - -Why does it exist: --- - -I was in search of a good performing FFT library , preferably very -small and with a very liberal license. - -When one says "fft library", FFTW ("Fastest Fourier Transform in the -West") is probably the first name that comes to mind -- I guess that -99% of open-source projects that need a FFT do use FFTW, and are happy -with it. However, it is quite a large library , which does everything -fft related (2d transforms, 3d transforms, other transformations such -as discrete cosine , or fast hartley). And it is licensed under the -GNU GPL , which means that it cannot be used in non open-source -products. - -An alternative to FFTW that is really small, is the venerable FFTPACK -v4, which is available on NETLIB. A more recent version (v5) exists, -but it is larger as it deals with multi-dimensional transforms. This -is a library that is written in FORTRAN 77, a language that is now -considered as a bit antiquated by many. FFTPACKv4 was written in 1985, -by Dr Paul Swarztrauber of NCAR, more than 25 years ago ! And despite -its age, benchmarks show it that it still a very good performing FFT -library, see for example the 1d single precision benchmarks here: -http://www.fftw.org/speed/opteron-2.2GHz-32bit/ . It is however not -competitive with the fastest ones, such as FFTW, Intel MKL, AMD ACML, -Apple vDSP. The reason for that is that those libraries do take -advantage of the SSE SIMD instructions available on Intel CPUs, -available since the days of the Pentium III. These instructions deal -with small vectors of 4 floats at a time, instead of a single float -for a traditionnal FPU, so when using these instructions one may expect -a 4-fold performance improvement. - -The idea was to take this fortran fftpack v4 code, translate to C, -modify it to deal with those SSE instructions, and check that the -final performance is not completely ridiculous when compared to other -SIMD FFT libraries. Translation to C was performed with f2c ( -http://www.netlib.org/f2c/ ). The resulting file was a bit edited in -order to remove the thousands of gotos that were introduced by -f2c. You will find the fftpack.h and fftpack.c sources in the -repository, this a complete translation of -http://www.netlib.org/fftpack/ , with the discrete cosine transform -and the test program. There is no license information in the netlib -repository, but it was confirmed to me by the fftpack v5 curators that -the same terms do apply to fftpack v4: -http://www.cisl.ucar.edu/css/software/fftpack5/ftpk.html . This is a -"BSD-like" license, it is compatible with proprietary projects. - -Adapting fftpack to deal with the SIMD 4-element vectors instead of -scalar single precision numbers was more complex than I originally -thought, especially with the real transforms, and I ended up writing -more code than I planned.. - - -The code: --- - -Only two files, in good old C, pffft.c and pffft.h . The API is very -very simple, just make sure that you read the comments in pffft.h. - - -Comparison with other FFTs: --- - -The idea was not to break speed records, but to get a decently fast -fft that is at least 50% as fast as the fastest FFT -- especially on -slowest computers . I'm more focused on getting the best performance -on slow cpus (Atom, Intel Core 1, old Athlons, ARM Cortex-A9...), than -on getting top performance on today fastest cpus. - -It can be used in a real-time context as the fft functions do not -perform any memory allocation -- that is why they accept a 'work' -array in their arguments. - -It is also a bit focused on performing 1D convolutions, that is why it -provides "unordered" FFTs , and a fourier domain convolution -operation. - - -Benchmark results (cpu tested: core i7 2600, core 2 quad, core 1 duo, atom N270, cortex-A9, cortex-A15, A8X) --- - -The benchmark shows the performance of various fft implementations measured in -MFlops, with the number of floating point operations being defined as 5Nlog2(N) -for a length N complex fft, and 2.5*Nlog2(N) for a real fft. -See http://www.fftw.org/speed/method.html for an explanation of these formulas. - -MacOS Lion, gcc 4.2, 64-bit, fftw 3.3 on a 3.4 GHz core i7 2600 - -Built with: - - gcc-4.2 -o test_pffft -arch x86_64 -O3 -Wall -W pffft.c test_pffft.c fftpack.c -L/usr/local/lib -I/usr/local/include/ -DHAVE_VECLIB -framework veclib -DHAVE_FFTW -lfftw3f - -| input len |real FFTPack| real vDSP | real FFTW | real PFFFT | |cplx FFTPack| cplx vDSP | cplx FFTW | cplx PFFFT | -|-----------+------------+------------+------------+------------| |------------+------------+------------+------------| -| 64 | 2816 | 8596 | 7329 | 8187 | | 2887 | 14898 | 14668 | 11108 | -| 96 | 3298 | n/a | 8378 | 7727 | | 3953 | n/a | 15680 | 10878 | -| 128 | 3507 | 11575 | 9266 | 10108 | | 4233 | 17598 | 16427 | 12000 | -| 160 | 3391 | n/a | 9838 | 10711 | | 4220 | n/a | 16653 | 11187 | -| 192 | 3919 | n/a | 9868 | 10956 | | 4297 | n/a | 15770 | 12540 | -| 256 | 4283 | 13179 | 10694 | 13128 | | 4545 | 19550 | 16350 | 13822 | -| 384 | 3136 | n/a | 10810 | 12061 | | 3600 | n/a | 16103 | 13240 | -| 480 | 3477 | n/a | 10632 | 12074 | | 3536 | n/a | 11630 | 12522 | -| 512 | 3783 | 15141 | 11267 | 13838 | | 3649 | 20002 | 16560 | 13580 | -| 640 | 3639 | n/a | 11164 | 13946 | | 3695 | n/a | 15416 | 13890 | -| 768 | 3800 | n/a | 11245 | 13495 | | 3590 | n/a | 15802 | 14552 | -| 800 | 3440 | n/a | 10499 | 13301 | | 3659 | n/a | 12056 | 13268 | -| 1024 | 3924 | 15605 | 11450 | 15339 | | 3769 | 20963 | 13941 | 15467 | -| 2048 | 4518 | 16195 | 11551 | 15532 | | 4258 | 20413 | 13723 | 15042 | -| 2400 | 4294 | n/a | 10685 | 13078 | | 4093 | n/a | 12777 | 13119 | -| 4096 | 4750 | 16596 | 11672 | 15817 | | 4157 | 19662 | 14316 | 14336 | -| 8192 | 3820 | 16227 | 11084 | 12555 | | 3691 | 18132 | 12102 | 13813 | -| 9216 | 3864 | n/a | 10254 | 12870 | | 3586 | n/a | 12119 | 13994 | -| 16384 | 3822 | 15123 | 10454 | 12822 | | 3613 | 16874 | 12370 | 13881 | -| 32768 | 4175 | 14512 | 10662 | 11095 | | 3881 | 14702 | 11619 | 11524 | -| 262144 | 3317 | 11429 | 6269 | 9517 | | 2810 | 11729 | 7757 | 10179 | -| 1048576 | 2913 | 10551 | 4730 | 5867 | | 2661 | 7881 | 3520 | 5350 | -|-----------+------------+------------+------------+------------| |------------+------------+------------+------------| - - -Debian 6, gcc 4.4.5, 64-bit, fftw 3.3.1 on a 3.4 GHz core i7 2600 - -Built with: -gcc -o test_pffft -DHAVE_FFTW -msse2 -O3 -Wall -W pffft.c test_pffft.c fftpack.c -L$HOME/local/lib -I$HOME/local/include/ -lfftw3f -lm - -| N (input length) | real FFTPack | real FFTW | real PFFFT | | cplx FFTPack | cplx FFTW | cplx PFFFT | -|------------------+--------------+--------------+--------------| |--------------+--------------+--------------| -| 64 | 3840 | 7680 | 8777 | | 4389 | 20480 | 11171 | -| 96 | 4214 | 9633 | 8429 | | 4816 | 22477 | 11238 | -| 128 | 3584 | 10240 | 10240 | | 5120 | 23893 | 11947 | -| 192 | 4854 | 11095 | 12945 | | 4854 | 22191 | 14121 | -| 256 | 4096 | 11703 | 16384 | | 5120 | 23406 | 13653 | -| 384 | 4395 | 14651 | 12558 | | 4884 | 19535 | 14651 | -| 512 | 5760 | 13166 | 15360 | | 4608 | 23040 | 15360 | -| 768 | 4907 | 14020 | 16357 | | 4461 | 19628 | 14020 | -| 1024 | 5120 | 14629 | 14629 | | 5120 | 20480 | 15754 | -| 2048 | 5632 | 14080 | 18773 | | 4693 | 12516 | 16091 | -| 4096 | 5120 | 13653 | 17554 | | 4726 | 7680 | 14456 | -| 8192 | 4160 | 7396 | 13312 | | 4437 | 14791 | 13312 | -| 9216 | 4210 | 6124 | 13473 | | 4491 | 7282 | 14970 | -| 16384 | 3976 | 11010 | 14313 | | 4210 | 11450 | 13631 | -| 32768 | 4260 | 10224 | 10954 | | 4260 | 6816 | 11797 | -| 262144 | 3736 | 6896 | 9961 | | 2359 | 8965 | 9437 | -| 1048576 | 2796 | 4534 | 6453 | | 1864 | 3078 | 5592 | -|------------------+--------------+--------------+--------------| |--------------+--------------+--------------| - - - -MacOS Snow Leopard, gcc 4.0, 32-bit, fftw 3.3 on a 1.83 GHz core 1 duo - -Built with: - - gcc -o test_pffft -DHAVE_FFTW -DHAVE_VECLIB -O3 -Wall -W pffft.c test_pffft.c fftpack.c -L/usr/local/lib -I/usr/local/include/ -lfftw3f -framework veclib - -| input len |real FFTPack| real vDSP | real FFTW | real PFFFT | |cplx FFTPack| cplx vDSP | cplx FFTW | cplx PFFFT | -|-----------+------------+------------+------------+------------| |------------+------------+------------+------------| -| 64 | 745 | 2145 | 1706 | 2028 | | 961 | 3356 | 3313 | 2300 | -| 96 | 877 | n/a | 1976 | 1978 | | 1059 | n/a | 3333 | 2233 | -| 128 | 951 | 2808 | 2213 | 2279 | | 1202 | 3803 | 3739 | 2494 | -| 192 | 1002 | n/a | 2456 | 2429 | | 1186 | n/a | 3701 | 2508 | -| 256 | 1065 | 3205 | 2641 | 2793 | | 1302 | 4013 | 3912 | 2663 | -| 384 | 845 | n/a | 2759 | 2499 | | 948 | n/a | 3729 | 2504 | -| 512 | 900 | 3476 | 2956 | 2759 | | 974 | 4057 | 3954 | 2645 | -| 768 | 910 | n/a | 2912 | 2737 | | 975 | n/a | 3837 | 2614 | -| 1024 | 936 | 3583 | 3107 | 3009 | | 1006 | 4124 | 3821 | 2697 | -| 2048 | 1057 | 3585 | 3091 | 2837 | | 1089 | 3889 | 3701 | 2513 | -| 4096 | 1083 | 3524 | 3092 | 2733 | | 1039 | 3617 | 3462 | 2364 | -| 8192 | 874 | 3252 | 2967 | 2363 | | 911 | 3106 | 2789 | 2302 | -| 9216 | 898 | n/a | 2420 | 2290 | | 865 | n/a | 2676 | 2204 | -| 16384 | 903 | 2892 | 2506 | 2421 | | 899 | 3026 | 2797 | 2289 | -| 32768 | 965 | 2837 | 2550 | 2358 | | 920 | 2922 | 2763 | 2240 | -| 262144 | 738 | 2422 | 1589 | 1708 | | 610 | 2038 | 1436 | 1091 | -| 1048576 | 528 | 1207 | 845 | 880 | | 606 | 1020 | 669 | 1036 | -|-----------+------------+------------+------------+------------| |------------+------------+------------+------------| - - - -Ubuntu 11.04, gcc 4.5, 32-bit, fftw 3.2 on a 2.66 core 2 quad - -Built with: -gcc -o test_pffft -DHAVE_FFTW -msse -mfpmath=sse -O3 -Wall -W pffft.c test_pffft.c fftpack.c -L/usr/local/lib -I/usr/local/include/ -lfftw3f -lm - -| input len |real FFTPack| real FFTW | real PFFFT | |cplx FFTPack| cplx FFTW | cplx PFFFT | -|-----------+------------+------------+------------| |------------+------------+------------| -| 64 | 1920 | 3614 | 5120 | | 2194 | 7680 | 6467 | -| 96 | 1873 | 3549 | 5187 | | 2107 | 8429 | 5863 | -| 128 | 2240 | 3773 | 5514 | | 2560 | 7964 | 6827 | -| 192 | 1765 | 4569 | 7767 | | 2284 | 9137 | 7061 | -| 256 | 2048 | 5461 | 7447 | | 2731 | 9638 | 7802 | -| 384 | 1998 | 5861 | 6762 | | 2313 | 9253 | 7644 | -| 512 | 2095 | 6144 | 7680 | | 2194 | 10240 | 7089 | -| 768 | 2230 | 5773 | 7549 | | 2045 | 10331 | 7010 | -| 1024 | 2133 | 6400 | 8533 | | 2133 | 10779 | 7877 | -| 2048 | 2011 | 7040 | 8665 | | 1942 | 10240 | 7768 | -| 4096 | 2194 | 6827 | 8777 | | 1755 | 9452 | 6827 | -| 8192 | 1849 | 6656 | 6656 | | 1752 | 7831 | 6827 | -| 9216 | 1871 | 5858 | 6416 | | 1643 | 6909 | 6266 | -| 16384 | 1883 | 6223 | 6506 | | 1664 | 7340 | 6982 | -| 32768 | 1826 | 6390 | 6667 | | 1631 | 7481 | 6971 | -| 262144 | 1546 | 4075 | 5977 | | 1299 | 3415 | 3551 | -| 1048576 | 1104 | 2071 | 1730 | | 1104 | 1149 | 1834 | -|-----------+------------+------------+------------| |------------+------------+------------| - - - -Ubuntu 11.04, gcc 4.5, 32-bit, fftw 3.3 on a 1.6 GHz Atom N270 - -Built with: -gcc -o test_pffft -DHAVE_FFTW -msse -mfpmath=sse -O3 -Wall -W pffft.c test_pffft.c fftpack.c -L/usr/local/lib -I/usr/local/include/ -lfftw3f -lm - -| N (input length) | real FFTPack | real FFTW | real PFFFT | | cplx FFTPack | cplx FFTW | cplx PFFFT | -|------------------+--------------+--------------+--------------| |--------------+--------------+--------------| -| 64 | 452 | 1041 | 1336 | | 549 | 2318 | 1781 | -| 96 | 444 | 1297 | 1297 | | 503 | 2408 | 1686 | -| 128 | 527 | 1525 | 1707 | | 543 | 2655 | 1886 | -| 192 | 498 | 1653 | 1849 | | 539 | 2678 | 1942 | -| 256 | 585 | 1862 | 2156 | | 594 | 2777 | 2244 | -| 384 | 499 | 1870 | 1998 | | 511 | 2586 | 1890 | -| 512 | 562 | 2095 | 2194 | | 542 | 2973 | 2194 | -| 768 | 545 | 2045 | 2133 | | 545 | 2365 | 2133 | -| 1024 | 595 | 2133 | 2438 | | 569 | 2695 | 2179 | -| 2048 | 587 | 2125 | 2347 | | 521 | 2230 | 1707 | -| 4096 | 495 | 1890 | 1834 | | 492 | 1876 | 1672 | -| 8192 | 469 | 1548 | 1729 | | 438 | 1740 | 1664 | -| 9216 | 468 | 1663 | 1663 | | 446 | 1585 | 1531 | -| 16384 | 453 | 1608 | 1767 | | 398 | 1476 | 1664 | -| 32768 | 456 | 1420 | 1503 | | 387 | 1388 | 1345 | -| 262144 | 309 | 385 | 726 | | 262 | 415 | 840 | -| 1048576 | 280 | 351 | 739 | | 261 | 313 | 797 | -|------------------+--------------+--------------+--------------| |--------------+--------------+--------------| - - - -Windows 7, visual c++ 2010 on a 1.6 GHz Atom N270 - -Built with: -cl /Ox -D_USE_MATH_DEFINES /arch:SSE test_pffft.c pffft.c fftpack.c - -(visual c++ is definitively not very good with SSE intrinsics...) - -| N (input length) | real FFTPack | real PFFFT | | cplx FFTPack | cplx PFFFT | -|------------------+--------------+--------------| |--------------+--------------| -| 64 | 173 | 1009 | | 174 | 1159 | -| 96 | 169 | 1029 | | 188 | 1201 | -| 128 | 195 | 1242 | | 191 | 1275 | -| 192 | 178 | 1312 | | 184 | 1276 | -| 256 | 196 | 1591 | | 186 | 1281 | -| 384 | 172 | 1409 | | 181 | 1281 | -| 512 | 187 | 1640 | | 181 | 1313 | -| 768 | 171 | 1614 | | 176 | 1258 | -| 1024 | 186 | 1812 | | 178 | 1223 | -| 2048 | 190 | 1707 | | 186 | 1099 | -| 4096 | 182 | 1446 | | 177 | 975 | -| 8192 | 175 | 1345 | | 169 | 1034 | -| 9216 | 165 | 1271 | | 168 | 1023 | -| 16384 | 166 | 1396 | | 165 | 949 | -| 32768 | 172 | 1311 | | 161 | 881 | -| 262144 | 136 | 632 | | 134 | 629 | -| 1048576 | 134 | 698 | | 127 | 623 | -|------------------+--------------+--------------| |--------------+--------------| - - - -Ubuntu 12.04, gcc-4.7.3, 32-bit, with fftw 3.3.3 (built with --enable-neon), on a 1.2GHz ARM Cortex A9 (Tegra 3) - -Built with: -gcc-4.7 -O3 -DHAVE_FFTW -march=armv7-a -mtune=cortex-a9 -mfloat-abi=hard -mfpu=neon -ffast-math test_pffft.c pffft.c -o test_pffft_arm fftpack.c -lm -I/usr/local/include/ -L/usr/local/lib/ -lfftw3f - -| input len |real FFTPack| real FFTW | real PFFFT | |cplx FFTPack| cplx FFTW | cplx PFFFT | -|-----------+------------+------------+------------| |------------+------------+------------| -| 64 | 549 | 452 | 731 | | 512 | 602 | 640 | -| 96 | 421 | 272 | 702 | | 496 | 571 | 602 | -| 128 | 498 | 512 | 815 | | 597 | 618 | 652 | -| 160 | 521 | 536 | 815 | | 586 | 669 | 625 | -| 192 | 539 | 571 | 883 | | 485 | 597 | 626 | -| 256 | 640 | 539 | 975 | | 569 | 611 | 671 | -| 384 | 499 | 610 | 879 | | 499 | 602 | 637 | -| 480 | 518 | 507 | 877 | | 496 | 661 | 616 | -| 512 | 524 | 591 | 1002 | | 549 | 678 | 668 | -| 640 | 542 | 612 | 955 | | 568 | 663 | 645 | -| 768 | 557 | 613 | 981 | | 491 | 663 | 598 | -| 800 | 514 | 353 | 882 | | 514 | 360 | 574 | -| 1024 | 640 | 640 | 1067 | | 492 | 683 | 602 | -| 2048 | 587 | 640 | 908 | | 486 | 640 | 552 | -| 2400 | 479 | 368 | 777 | | 422 | 376 | 518 | -| 4096 | 511 | 614 | 853 | | 426 | 640 | 534 | -| 8192 | 415 | 584 | 708 | | 386 | 622 | 516 | -| 9216 | 419 | 571 | 687 | | 364 | 586 | 506 | -| 16384 | 426 | 577 | 716 | | 398 | 606 | 530 | -| 32768 | 417 | 572 | 673 | | 399 | 572 | 468 | -| 262144 | 219 | 380 | 293 | | 255 | 431 | 343 | -| 1048576 | 202 | 274 | 237 | | 265 | 282 | 355 | -|-----------+------------+------------+------------| |------------+------------+------------| - -Same platform as above, but this time pffft and fftpack are built with clang 3.2: - -clang -O3 -DHAVE_FFTW -march=armv7-a -mtune=cortex-a9 -mfloat-abi=hard -mfpu=neon -ffast-math test_pffft.c pffft.c -o test_pffft_arm fftpack.c -lm -I/usr/local/include/ -L/usr/local/lib/ -lfftw3f - -| input len |real FFTPack| real FFTW | real PFFFT | |cplx FFTPack| cplx FFTW | cplx PFFFT | -|-----------+------------+------------+------------| |------------+------------+------------| -| 64 | 427 | 452 | 853 | | 427 | 602 | 1024 | -| 96 | 351 | 276 | 843 | | 337 | 571 | 963 | -| 128 | 373 | 512 | 996 | | 390 | 618 | 1054 | -| 160 | 426 | 536 | 987 | | 375 | 669 | 914 | -| 192 | 404 | 571 | 1079 | | 388 | 588 | 1079 | -| 256 | 465 | 539 | 1205 | | 445 | 602 | 1170 | -| 384 | 366 | 610 | 1099 | | 343 | 594 | 1099 | -| 480 | 356 | 507 | 1140 | | 335 | 651 | 931 | -| 512 | 411 | 591 | 1213 | | 384 | 649 | 1124 | -| 640 | 398 | 612 | 1193 | | 373 | 654 | 901 | -| 768 | 409 | 613 | 1227 | | 383 | 663 | 1044 | -| 800 | 411 | 348 | 1073 | | 353 | 358 | 809 | -| 1024 | 427 | 640 | 1280 | | 413 | 692 | 1004 | -| 2048 | 414 | 626 | 1126 | | 371 | 640 | 853 | -| 2400 | 399 | 373 | 898 | | 319 | 368 | 653 | -| 4096 | 404 | 602 | 1059 | | 357 | 633 | 778 | -| 8192 | 332 | 584 | 792 | | 308 | 616 | 716 | -| 9216 | 322 | 561 | 783 | | 299 | 586 | 687 | -| 16384 | 344 | 568 | 778 | | 314 | 617 | 745 | -| 32768 | 342 | 564 | 737 | | 314 | 552 | 629 | -| 262144 | 201 | 383 | 313 | | 227 | 435 | 413 | -| 1048576 | 187 | 262 | 251 | | 228 | 281 | 409 | -|-----------+------------+------------+------------| |------------+------------+------------| - -So it looks like, on ARM, gcc 4.7 is the best at scalar floating point -(the fftpack performance numbers are better with gcc), while clang is -the best with neon intrinsics (see how pffft perf has improved with -clang 3.2). - - -NVIDIA Jetson TK1 board, gcc-4.8.2. The cpu is a 2.3GHz cortex A15 (Tegra K1). - -Built with: -gcc -O3 -march=armv7-a -mtune=native -mfloat-abi=hard -mfpu=neon -ffast-math test_pffft.c pffft.c -o test_pffft_arm fftpack.c -lm - -| input len |real FFTPack| real PFFFT | |cplx FFTPack| cplx PFFFT | -|-----------+------------+------------| |------------+------------| -| 64 | 1735 | 3308 | | 1994 | 3744 | -| 96 | 1596 | 3448 | | 1987 | 3572 | -| 128 | 1807 | 4076 | | 2255 | 3960 | -| 160 | 1769 | 4083 | | 2071 | 3845 | -| 192 | 1990 | 4233 | | 2017 | 3939 | -| 256 | 2191 | 4882 | | 2254 | 4346 | -| 384 | 1878 | 4492 | | 2073 | 4012 | -| 480 | 1748 | 4398 | | 1923 | 3951 | -| 512 | 2030 | 5064 | | 2267 | 4195 | -| 640 | 1918 | 4756 | | 2094 | 4184 | -| 768 | 2099 | 4907 | | 2048 | 4297 | -| 800 | 1822 | 4555 | | 1880 | 4063 | -| 1024 | 2232 | 5355 | | 2187 | 4420 | -| 2048 | 2176 | 4983 | | 2027 | 3602 | -| 2400 | 1741 | 4256 | | 1710 | 3344 | -| 4096 | 1816 | 3914 | | 1851 | 3349 | -| 8192 | 1716 | 3481 | | 1700 | 3255 | -| 9216 | 1735 | 3589 | | 1653 | 3094 | -| 16384 | 1567 | 3483 | | 1637 | 3244 | -| 32768 | 1624 | 3240 | | 1655 | 3156 | -| 262144 | 1012 | 1898 | | 983 | 1503 | -| 1048576 | 876 | 1154 | | 868 | 1341 | -|-----------+------------+------------| |------------+------------| - -The performance on the tegra K1 is pretty impressive. I'm not -including the FFTW numbers as they as slightly below the scalar -fftpack numbers, so something must be wrong (however it seems to be -correctly configured and is using neon simd instructions). - -When using clang 3.4 the pffft version is even a bit faster, reaching -5.7 GFlops for real ffts of size 1024. - - -iPad Air 2 with iOS9, xcode 8.0, arm64. The cpu is an Apple A8X, supposedly running at 1.5GHz. - -| input len |real FFTPack| real vDSP | real PFFFT | |cplx FFTPack| cplx vDSP | cplx PFFFT | -|-----------+------------+------------+------------| |------------+------------+------------| -| 64 | 2517 | 7995 | 6086 | | 2725 | 13006 | 8495 | -| 96 | 2442 | n/a | 6691 | | 2256 | n/a | 7991 | -| 128 | 2664 | 10186 | 7877 | | 2575 | 15115 | 9115 | -| 160 | 2638 | n/a | 8283 | | 2682 | n/a | 8806 | -| 192 | 2903 | n/a | 9083 | | 2634 | n/a | 8980 | -| 256 | 3184 | 11452 | 10039 | | 3026 | 15410 | 10199 | -| 384 | 2665 | n/a | 10100 | | 2275 | n/a | 9247 | -| 480 | 2546 | n/a | 9863 | | 2341 | n/a | 8892 | -| 512 | 2832 | 12197 | 10989 | | 2547 | 16768 | 10154 | -| 640 | 2755 | n/a | 10461 | | 2569 | n/a | 9666 | -| 768 | 2998 | n/a | 11355 | | 2585 | n/a | 9813 | -| 800 | 2516 | n/a | 10332 | | 2433 | n/a | 9164 | -| 1024 | 3109 | 12965 | 12114 | | 2869 | 16448 | 10519 | -| 2048 | 3027 | 12996 | 12023 | | 2648 | 17304 | 10307 | -| 2400 | 2515 | n/a | 10372 | | 2355 | n/a | 8443 | -| 4096 | 3204 | 13603 | 12359 | | 2814 | 16570 | 9780 | -| 8192 | 2759 | 13422 | 10824 | | 2153 | 15652 | 7884 | -| 9216 | 2700 | n/a | 9938 | | 2241 | n/a | 7900 | -| 16384 | 2280 | 13057 | 7976 | | 593 | 4272 | 2534 | -| 32768 | 768 | 4269 | 2882 | | 606 | 4405 | 2604 | -| 262144 | 724 | 3527 | 2630 | | 534 | 2418 | 2157 | -| 1048576 | 674 | 1467 | 2135 | | 530 | 1621 | 2055 | -|-----------+------------+------------+------------| |------------+------------+------------| - -I double-checked to make sure I did not make a mistake in the time -measurements, as the numbers are much higher than what I initially -expected. They are in fact higher than the number I get on the 2.8GHz -Xeon of my 2008 mac pro.. (except for FFT lengths >= 32768 where -having a big cache is useful). A good surprise is also that the perf -is not too far from apple's vDSP (at least for the real FFT). - diff --git a/oss-internship-2020/pffft/myNotes.txt b/oss-internship-2020/pffft/myNotes.txt deleted file mode 100644 index 0dd3bcb..0000000 --- a/oss-internship-2020/pffft/myNotes.txt +++ /dev/null @@ -1,101 +0,0 @@ -About library's functions: - * pffft_aligned_malloc(size_t) - returns an allocated array considering the alignment offset - * pffft_aligned_free(void *) - frees the memory - * pffft_simd_size() - returns the SIMD_SZ = 4 (regarding simd vector) - * pffft_new_setup(int, ...) - with a fft size (first argument) being a multiple of 16, 32. - -Deleted part (validate function) - /*for (pass = 0; pass < 2; pass++) { - if (pass == 0) { - for (k = 0; k < Nfloat; k++) { - ref_[k] = in_[k] = frand() * 2 - 1; - out_[k] = 1e30; - } - - if (!cplx) { - api.rffti(N, wrk_.PtrBoth()).IgnoreError(); - api.rfftf(N, ref_.PtrBoth(), wrk_.PtrBoth()).IgnoreError(); - - { - float refN = ref_[N - 1]; - for (k = N - 2; k >= 1; --k) { - ref_[k + 1] = ref_[k]; - } - ref_[1] = refN; - } - } else { - api.cffti(N, wrk_.PtrBoth()).IgnoreError(); - api.cfftf(N, ref_.PtrBoth(), wrk_.PtrBoth()).IgnoreError(); - } - } - - for (k = 0; k < Nfloat; ++k) { - ref_max = MAX(ref_max, fabs(ref_[k])); - } - - if (pass == 0) { - api.pffft_transform(s_reg.PtrBefore(), in_.PtrBoth(), tmp_.PtrBoth(), wrk_.PtrBoth(), PFFFT_FORWARD).IgnoreError(); - - memcpy(tmp2, tmp, Nbytes); - memcpy(tmp, in, Nbytes); - - api.pffft_transform(s_reg.PtrBefore(), tmp_.PtrBoth(), tmp_.PtrBoth(), wrk_.PtrBoth(), PFFFT_FORWARD).IgnoreError(); - - printf("Forward transformation test passed.\n"); - - api.pffft_zreorder(s_reg.PtrBefore(), tmp_.PtrBoth(), out_.PtrBoth(), PFFFT_FORWARD).IgnoreError(); - api.pffft_zreorder(s_reg.PtrBefore(), out_.PtrBoth(), tmp_.PtrBoth(), PFFFT_BACKWARD).IgnoreError(); - - printf("Reordering test passed.\n"); - } else { - api.pffft_transform_ordered(s_reg.PtrBefore(), in_.PtrBoth(), tmp_.PtrBoth(), wrk_.PtrBoth(), PFFFT_FORWARD).IgnoreError(); - - } - } */ - - -MACRO for testing -TEST(AssignOrReturn, AssignsMultipleVariablesInSequence) { - auto func = []() -> absl::Status { - int value1; - SAPI_ASSIGN_OR_RETURN(value1, StatusOr(1)); - EXPECT_EQ(1, value1); - int value2; - SAPI_ASSIGN_OR_RETURN(value2, StatusOr(2)); - EXPECT_EQ(2, value2); - int value3; - SAPI_ASSIGN_OR_RETURN(value3, StatusOr(3)); - EXPECT_EQ(3, value3); - int value4; - SAPI_ASSIGN_OR_RETURN(value4, - StatusOr(absl::UnknownError("EXPECTED" - int value1; - SAPI_ASSIGN_OR_RETURN(value1, StatusOr(1)); - - - // PFFFT benchmark - /*{ - sapi::StatusOr s = api.pffft_new_setup(N, cplx ? PFFFT_COMPLEX : PFFFT_REAL); - if (s.ok()) { - sapi::v::GenericPtr s_reg(s.value()); - - t0 = uclock_sec(); - for (iter = 0; iter < max_iter; ++iter) { - printf("%s 1\n", api.pffft_transform(s_reg.PtrBoth(), X_.PtrBoth(), Z_.PtrBoth(), Y_.PtrBoth(), PFFFT_FORWARD).ToString().c_str()); - printf("%s 2\n", api.pffft_transform(s_reg.PtrBoth(), X_.PtrBoth(), Z_.PtrBoth(), Y_.PtrBoth(), PFFFT_FORWARD).ToString().c_str()); - } - t1 = uclock_sec(); - printf("%s 3 \n", api.pffft_destroy_setup(s_reg.PtrBoth()).ToString().c_str()); - - - flops = (max_iter*2) * ((cplx ? 5 : 2.5) * N * log((double)N) / M_LN2); - show_output("PFFFT", N, cplx, flops, t0, t1, max_iter); - } else { - fprintf(stderr, "s NULL :(\n\n"); - } - }*/ - \ No newline at end of file diff --git a/oss-internship-2020/pffft/test_pffft.c b/oss-internship-2020/pffft/test_pffft.c deleted file mode 100644 index a5d20c2..0000000 --- a/oss-internship-2020/pffft/test_pffft.c +++ /dev/null @@ -1,419 +0,0 @@ -/* - Copyright (c) 2013 Julien Pommier. - - Small test & bench for PFFFT, comparing its performance with the scalar FFTPACK, FFTW, and Apple vDSP - - How to build: - - on linux, with fftw3: - gcc -o test_pffft -DHAVE_FFTW -msse -mfpmath=sse -O3 -Wall -W pffft.c test_pffft.c fftpack.c -L/usr/local/lib -I/usr/local/include/ -lfftw3f -lm - - on macos, without fftw3: - clang -o test_pffft -DHAVE_VECLIB -O3 -Wall -W pffft.c test_pffft.c fftpack.c -L/usr/local/lib -I/usr/local/include/ -framework Accelerate - - on macos, with fftw3: - clang -o test_pffft -DHAVE_FFTW -DHAVE_VECLIB -O3 -Wall -W pffft.c test_pffft.c fftpack.c -L/usr/local/lib -I/usr/local/include/ -lfftw3f -framework Accelerate - - on windows, with visual c++: - cl /Ox -D_USE_MATH_DEFINES /arch:SSE test_pffft.c pffft.c fftpack.c - - build without SIMD instructions: - gcc -o test_pffft -DPFFFT_SIMD_DISABLE -O3 -Wall -W pffft.c test_pffft.c fftpack.c -lm - - */ - -#include "pffft.h" -#include "fftpack.h" - -#include -#include -#include -#include -#include -#include - -#ifdef HAVE_SYS_TIMES -# include -# include -#endif - -#ifdef HAVE_VECLIB -# include -#endif - -#ifdef HAVE_FFTW -# include -#endif - -#define MAX(x,y) ((x)>(y)?(x):(y)) - -double frand() { - return rand()/(double)RAND_MAX; -} - -#if defined(HAVE_SYS_TIMES) - inline double uclock_sec(void) { - static double ttclk = 0.; - if (ttclk == 0.) ttclk = sysconf(_SC_CLK_TCK); - struct tms t; return ((double)times(&t)) / ttclk; - } -# else - double uclock_sec(void) -{ return (double)clock()/(double)CLOCKS_PER_SEC; } -#endif - - -/* compare results with the regular fftpack */ -void pffft_validate_N(int N, int cplx) { - int Nfloat = N*(cplx?2:1); - int Nbytes = Nfloat * sizeof(float); - float *ref, *in, *out, *tmp, *tmp2; - PFFFT_Setup *s = pffft_new_setup(N, cplx ? PFFFT_COMPLEX : PFFFT_REAL); - int pass; - - if (!s) { printf("Skipping N=%d, not supported\n", N); return; } - ref = pffft_aligned_malloc(Nbytes); - in = pffft_aligned_malloc(Nbytes); - out = pffft_aligned_malloc(Nbytes); - tmp = pffft_aligned_malloc(Nbytes); - tmp2 = pffft_aligned_malloc(Nbytes); - - for (pass=0; pass < 2; ++pass) { - float ref_max = 0; - int k; - //printf("N=%d pass=%d cplx=%d\n", N, pass, cplx); - // compute reference solution with FFTPACK - if (pass == 0) { - float *wrk = malloc(2*Nbytes+15*sizeof(float)); - for (k=0; k < Nfloat; ++k) { - ref[k] = in[k] = frand()*2-1; - out[k] = 1e30; - } - if (!cplx) { - rffti(N, wrk); - rfftf(N, ref, wrk); - // use our ordering for real ffts instead of the one of fftpack - { - float refN=ref[N-1]; - for (k=N-2; k >= 1; --k) ref[k+1] = ref[k]; - ref[1] = refN; - } - } else { - cffti(N, wrk); - cfftf(N, ref, wrk); - } - free(wrk); - } - - for (k = 0; k < Nfloat; ++k) ref_max = MAX(ref_max, fabs(ref[k])); - - - // pass 0 : non canonical ordering of transform coefficients - if (pass == 0) { - // test forward transform, with different input / output - pffft_transform(s, in, tmp, 0, PFFFT_FORWARD); - memcpy(tmp2, tmp, Nbytes); - memcpy(tmp, in, Nbytes); - pffft_transform(s, tmp, tmp, 0, PFFFT_FORWARD); - for (k = 0; k < Nfloat; ++k) { - assert(tmp2[k] == tmp[k]); - } - - // test reordering - pffft_zreorder(s, tmp, out, PFFFT_FORWARD); - pffft_zreorder(s, out, tmp, PFFFT_BACKWARD); - for (k = 0; k < Nfloat; ++k) { - assert(tmp2[k] == tmp[k]); - } - pffft_zreorder(s, tmp, out, PFFFT_FORWARD); - } else { - // pass 1 : canonical ordering of transform coeffs. - pffft_transform_ordered(s, in, tmp, 0, PFFFT_FORWARD); - memcpy(tmp2, tmp, Nbytes); - memcpy(tmp, in, Nbytes); - pffft_transform_ordered(s, tmp, tmp, 0, PFFFT_FORWARD); - for (k = 0; k < Nfloat; ++k) { - assert(tmp2[k] == tmp[k]); - } - memcpy(out, tmp, Nbytes); - } - - { - for (k=0; k < Nfloat; ++k) { - if (!(fabs(ref[k] - out[k]) < 1e-3*ref_max)) { - printf("%s forward PFFFT mismatch found for N=%d\n", (cplx?"CPLX":"REAL"), N); - exit(1); - } - } - - if (pass == 0) pffft_transform(s, tmp, out, 0, PFFFT_BACKWARD); - else pffft_transform_ordered(s, tmp, out, 0, PFFFT_BACKWARD); - memcpy(tmp2, out, Nbytes); - memcpy(out, tmp, Nbytes); - if (pass == 0) pffft_transform(s, out, out, 0, PFFFT_BACKWARD); - else pffft_transform_ordered(s, out, out, 0, PFFFT_BACKWARD); - for (k = 0; k < Nfloat; ++k) { - assert(tmp2[k] == out[k]); - out[k] *= 1.f/N; - } - for (k = 0; k < Nfloat; ++k) { - if (fabs(in[k] - out[k]) > 1e-3 * ref_max) { - printf("pass=%d, %s IFFFT does not match for N=%d\n", pass, (cplx?"CPLX":"REAL"), N); break; - exit(1); - } - } - } - - // quick test of the circular convolution in fft domain - { - float conv_err = 0, conv_max = 0; - - pffft_zreorder(s, ref, tmp, PFFFT_FORWARD); - memset(out, 0, Nbytes); - pffft_zconvolve_accumulate(s, ref, ref, out, 1.0); - pffft_zreorder(s, out, tmp2, PFFFT_FORWARD); - - for (k=0; k < Nfloat; k += 2) { - float ar = tmp[k], ai=tmp[k+1]; - if (cplx || k > 0) { - tmp[k] = ar*ar - ai*ai; - tmp[k+1] = 2*ar*ai; - } else { - tmp[0] = ar*ar; - tmp[1] = ai*ai; - } - } - - for (k=0; k < Nfloat; ++k) { - float d = fabs(tmp[k] - tmp2[k]), e = fabs(tmp[k]); - if (d > conv_err) conv_err = d; - if (e > conv_max) conv_max = e; - } - if (conv_err > 1e-5*conv_max) { - printf("zconvolve error ? %g %g\n", conv_err, conv_max); exit(1); - } - } - - } - - printf("%s PFFFT is OK for N=%d\n", (cplx?"CPLX":"REAL"), N); fflush(stdout); - - pffft_destroy_setup(s); - pffft_aligned_free(ref); - pffft_aligned_free(in); - pffft_aligned_free(out); - pffft_aligned_free(tmp); - pffft_aligned_free(tmp2); -} - -void pffft_validate(int cplx) { - static int Ntest[] = { 16, 32, 64, 96, 128, 160, 192, 256, 288, 384, 5*96, 512, 576, 5*128, 800, 864, 1024, 2048, 2592, 4000, 4096, 12000, 36864, 0}; - int k; - for (k = 0; Ntest[k]; ++k) { - int N = Ntest[k]; - if (N == 16 && !cplx) continue; - pffft_validate_N(N, cplx); - } -} - -int array_output_format = 0; - -void show_output(const char *name, int N, int cplx, float flops, float t0, float t1, int max_iter) { - float mflops = flops/1e6/(t1 - t0 + 1e-16); - if (array_output_format) { - if (flops != -1) { - printf("|%9.0f ", mflops); - } else printf("| n/a "); - } else { - if (flops != -1) { - printf("N=%5d, %s %16s : %6.0f MFlops [t=%6.0f ns, %d runs]\n", N, (cplx?"CPLX":"REAL"), name, mflops, (t1-t0)/2/max_iter * 1e9, max_iter); - } - } - fflush(stdout); -} - -void benchmark_ffts(int N, int cplx) { - int Nfloat = (cplx ? N*2 : N); - int Nbytes = Nfloat * sizeof(float); - float *X = pffft_aligned_malloc(Nbytes), *Y = pffft_aligned_malloc(Nbytes), *Z = pffft_aligned_malloc(Nbytes); - - double t0, t1, flops; - - int k; - int max_iter = 5120000/N*4; -#ifdef __arm__ - max_iter /= 4; -#endif - int iter; - - for (k = 0; k < Nfloat; ++k) { - X[k] = 0; //sqrtf(k+1); - } - - // FFTPack benchmark - { - float *wrk = malloc(2*Nbytes + 15*sizeof(float)); - int max_iter_ = max_iter/pffft_simd_size(); if (max_iter_ == 0) max_iter_ = 1; - if (cplx) cffti(N, wrk); - else rffti(N, wrk); - t0 = uclock_sec(); - - for (iter = 0; iter < max_iter_; ++iter) { - if (cplx) { - cfftf(N, X, wrk); - cfftb(N, X, wrk); - } else { - rfftf(N, X, wrk); - rfftb(N, X, wrk); - } - } - t1 = uclock_sec(); - free(wrk); - - flops = (max_iter_*2) * ((cplx ? 5 : 2.5)*N*log((double)N)/M_LN2); // see http://www.fftw.org/speed/method.html - show_output("FFTPack", N, cplx, flops, t0, t1, max_iter_); - } - -#ifdef HAVE_VECLIB - int log2N = (int)(log(N)/log(2) + 0.5f); - if (N == (1< 1 && strcmp(argv[1], "--array-format") == 0) { - array_output_format = 1; - } - -#ifndef PFFFT_SIMD_DISABLE - validate_pffft_simd(); -#endif - pffft_validate(1); - pffft_validate(0); - if (!array_output_format) { - for (i=0; Nvalues[i] > 0; ++i) { - benchmark_ffts(Nvalues[i], 0 /* real fft */); - } - for (i=0; Nvalues[i] > 0; ++i) { - benchmark_ffts(Nvalues[i], 1 /* cplx fft */); - } - } else { - printf("| input len "); - printf("|real FFTPack"); -#ifdef HAVE_VECLIB - printf("| real vDSP "); -#endif -#ifdef HAVE_FFTW - printf("| real FFTW "); -#endif - printf("| real PFFFT | "); - - printf("|cplx FFTPack"); -#ifdef HAVE_VECLIB - printf("| cplx vDSP "); -#endif -#ifdef HAVE_FFTW - printf("| cplx FFTW "); -#endif - printf("| cplx PFFFT |\n"); - for (i=0; Nvalues[i] > 0; ++i) { - printf("|%9d ", Nvalues[i]); - benchmark_ffts(Nvalues[i], 0); - printf("| "); - benchmark_ffts(Nvalues[i], 1); - printf("|\n"); - } - printf(" (numbers are given in MFlops)\n"); - } - - - return 0; -} diff --git a/oss-internship-2020/pffft/test_pffft_sandboxed.cc b/oss-internship-2020/pffft/test_pffft_sandboxed.cc deleted file mode 100644 index 523dce0..0000000 --- a/oss-internship-2020/pffft/test_pffft_sandboxed.cc +++ /dev/null @@ -1,206 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "fftpack.h" -#include "pffft_sapi.sapi.h" -#include "sandboxed_api/util/flag.h" -#include "sandboxed_api/vars.h" - -ABSL_DECLARE_FLAG(string, sandbox2_danger_danger_permit_all); -ABSL_DECLARE_FLAG(string, sandbox2_danger_danger_permit_all_and_log); - -class pffftSapiSandbox : public pffftSandbox { - public: - std::unique_ptr ModifyPolicy( - sandbox2::PolicyBuilder*) override { - return sandbox2::PolicyBuilder() - .AllowStaticStartup() - .AllowOpen() - .AllowRead() - .AllowWrite() - .AllowSystemMalloc() - .AllowExit() - .AllowSyscalls({ - __NR_futex, - __NR_close, - __NR_getrusage, - }) - .DisableNamespaces() - .BuildOrDie(); - } -}; - -double frand() { return rand() / (double)RAND_MAX; } - -double uclock_sec(void) { return (double)clock() / (double)CLOCKS_PER_SEC; } - -int array_output_format = 0; - -void show_output(const char* name, int N, int cplx, float flops, float t0, - float t1, int max_iter) { - float mflops = flops / 1e6 / (t1 - t0 + 1e-16); - if (array_output_format) { - if (flops != -1) { - printf("|%9.0f ", mflops); - } else - printf("| n/a "); - } else { - if (flops != -1) { - printf("N=%5d, %s %16s : %6.0f MFlops [t=%6.0f ns, %d runs]\n", N, - (cplx ? "CPLX" : "REAL"), name, mflops, - (t1 - t0) / 2 / max_iter * 1e9, max_iter); - } - } - fflush(stdout); -} - -int main(int argc, char* argv[]) { - /* - * Initialize Google's logging library. - */ - google::InitGoogleLogging(argv[0]); - - gflags::ParseCommandLineFlags(&argc, &argv, true); - /* - * Nvalues is a vector keeping the values by which iterates N, its value - * representing the input length. More concrete, N is the number of - * data points the caclulus is up to (determinating its accuracy). - * To show the performance of Fast-Fourier Transformations the program is - * testing for various values of N. - */ - int Nvalues[] = {64, 96, 128, 160, 192, 256, - 384, 5 * 96, 512, 5 * 128, 3 * 256, 800, - 1024, 2048, 2400, 4096, 8192, 9 * 1024, - 16384, 32768, 256 * 1024, 1024 * 1024, -1}; - int i; - - VLOG(1) << "Initializing sandbox...\n"; - - pffftSapiSandbox sandbox; - sandbox.Init().IgnoreError(); - - VLOG(1) << "Initialization: " << sandbox.Init().ToString().c_str() << "\n"; - - pffftApi api(&sandbox); - - int N, cplx; - - cplx = 0; - - do { - for (i = 0; i < 23; i++) { - N = Nvalues[i]; - - int Nfloat = N * (cplx ? 2 : 1); - int Nbytes = Nfloat * sizeof(float); - int pass; - - float ref[Nbytes], in[Nbytes], out[Nbytes], tmp[Nbytes], tmp2[Nbytes]; - - sapi::v::Array ref_(ref, Nbytes); - sapi::v::Array in_(in, Nbytes); - sapi::v::Array out_(out, Nbytes); - sapi::v::Array tmp_(tmp, Nbytes); - sapi::v::Array tmp2_(tmp2, Nbytes); - - float wrk[2 * Nbytes + 15 * sizeof(float)]; - sapi::v::Array wrk_(wrk, 2 * Nbytes + 15 * sizeof(float)); - - float ref_max = 0; - int k; - - Nfloat = (cplx ? N * 2 : N); - float X[Nbytes], Y[Nbytes], Z[Nbytes]; - sapi::v::Array X_(X, Nbytes), Y_(Y, Nbytes), Z_(Z, Nbytes); - - double t0, t1, flops; - - int max_iter = 5120000 / N * 4; - #ifdef __arm__ - max_iter /= 4; - #endif - int iter; - - for (k = 0; k < Nfloat; ++k) { - X[k] = 0; - } - - /* - * FFTPack benchmark - */ - { - /* - * SIMD_SZ == 4 (returning value of pffft_simd_size()) - */ - int max_iter_ = - max_iter / 4; - - if (max_iter_ == 0) max_iter_ = 1; - if (cplx) { - api.cffti(N, wrk_.PtrBoth()).IgnoreError(); - } else { - api.rffti(N, wrk_.PtrBoth()).IgnoreError(); - } - t0 = uclock_sec(); - - for (iter = 0; iter < max_iter_; ++iter) { - if (cplx) { - api.cfftf(N, X_.PtrBoth(), wrk_.PtrBoth()).IgnoreError(); - api.cfftb(N, X_.PtrBoth(), wrk_.PtrBoth()).IgnoreError(); - } else { - api.rfftf(N, X_.PtrBoth(), wrk_.PtrBoth()).IgnoreError(); - api.rfftb(N, X_.PtrBoth(), wrk_.PtrBoth()).IgnoreError(); - } - } - t1 = uclock_sec(); - - flops = (max_iter_ * 2) * ((cplx ? 5 : 2.5) * N * log((double)N) / M_LN2); - show_output("FFTPack", N, cplx, flops, t0, t1, max_iter_); - } - - /* - * PFFFT benchmark - */ - { - sapi::StatusOr s = - api.pffft_new_setup(N, cplx ? PFFFT_COMPLEX : PFFFT_REAL); - - VLOG(1) << "Setup status is: " << s.status().ToString().c_str() << "\n"; - - if (s.ok()) { - sapi::v::RemotePtr s_reg(s.value()); - - t0 = uclock_sec(); - for (iter = 0; iter < max_iter; ++iter) { - api.pffft_transform(&s_reg, X_.PtrBoth(), Z_.PtrBoth(), Y_.PtrBoth(), - PFFFT_FORWARD) - .IgnoreError(); - api.pffft_transform(&s_reg, X_.PtrBoth(), Z_.PtrBoth(), Y_.PtrBoth(), - PFFFT_FORWARD) - .IgnoreError(); - } - - t1 = uclock_sec(); - api.pffft_destroy_setup(&s_reg).IgnoreError(); - - flops = - (max_iter * 2) * ((cplx ? 5 : 2.5) * N * log((double)N) / M_LN2); - show_output("PFFFT", N, cplx, flops, t0, t1, max_iter); - } - - VLOG(1) << "N = " << N << " SUCCESSFULLY\n\n"; - } - } - - cplx = !cplx; - } while (cplx); - - return 0; -} \ No newline at end of file From 726a9345fffac8c152cc9f36e1a3b0d5b437f3e7 Mon Sep 17 00:00:00 2001 From: doinachiroiu Date: Thu, 20 Aug 2020 12:37:34 +0000 Subject: [PATCH 32/82] Enable namespaces --- oss-internship-2020/pffft/README.md | 8 +++++++- oss-internship-2020/pffft/main_pffft_sandboxed.cc | 9 ++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/oss-internship-2020/pffft/README.md b/oss-internship-2020/pffft/README.md index 52facf3..b3ceef8 100644 --- a/oss-internship-2020/pffft/README.md +++ b/oss-internship-2020/pffft/README.md @@ -64,4 +64,10 @@ In the end, the performance of PFFFT library it is outlined by the output.* uintptr_t instead of PFFFT_Setup Solution: using "sapi::v::RemotePtr" instead of "sapi::v::GenericPtr" - to access the memory of object s \ No newline at end of file + to access the memory of object s + + - [Unresolved] compiling bug: "No spave left on device" + The building process creates some `embed` files that use lots of + memory, trying to write them on /tmp. + + Temporary solution: clean /tmp directory by `sudo rm -rf /tmp/*`. \ No newline at end of file diff --git a/oss-internship-2020/pffft/main_pffft_sandboxed.cc b/oss-internship-2020/pffft/main_pffft_sandboxed.cc index 662b949..b9a5a90 100644 --- a/oss-internship-2020/pffft/main_pffft_sandboxed.cc +++ b/oss-internship-2020/pffft/main_pffft_sandboxed.cc @@ -34,7 +34,6 @@ class pffftSapiSandbox : public pffftSandbox { __NR_close, __NR_getrusage, }) - .DisableNamespaces() .BuildOrDie(); } }; @@ -83,12 +82,12 @@ int main(int argc, char* argv[]) { 16384, 32768, 256 * 1024, 1024 * 1024, -1}; int i; - VLOG(1) << "Initializing sandbox...\n"; + LOG(INFO) << "Initializing sandbox...\n"; pffftSapiSandbox sandbox; sandbox.Init().IgnoreError(); - VLOG(1) << "Initialization: " << sandbox.Init().ToString().c_str() << "\n"; + LOG(INFO) << "Initialization: " << sandbox.Init().ToString().c_str() << "\n"; pffftApi api(&sandbox); @@ -174,7 +173,7 @@ int main(int argc, char* argv[]) { sapi::StatusOr s = api.pffft_new_setup(N, cplx ? PFFFT_COMPLEX : PFFFT_REAL); - VLOG(1) << "Setup status is: " << s.status().ToString().c_str() << "\n"; + LOG(INFO) << "Setup status is: " << s.status().ToString().c_str() << "\n"; if (s.ok()) { sapi::v::RemotePtr s_reg(s.value()); @@ -197,7 +196,7 @@ int main(int argc, char* argv[]) { show_output("PFFFT", N, cplx, flops, t0, t1, max_iter); } - VLOG(1) << "N = " << N << " SUCCESSFULLY\n\n"; + LOG(INFO) << "N = " << N << " SUCCESSFULLY\n\n"; } } From a2873ac099bd08ddd08427ebeb74e6e6f334cf58 Mon Sep 17 00:00:00 2001 From: doinachiroiu Date: Thu, 20 Aug 2020 13:15:02 +0000 Subject: [PATCH 33/82] Update README.md --- oss-internship-2020/pffft/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/oss-internship-2020/pffft/README.md b/oss-internship-2020/pffft/README.md index b3ceef8..8b8b2bd 100644 --- a/oss-internship-2020/pffft/README.md +++ b/oss-internship-2020/pffft/README.md @@ -58,7 +58,7 @@ In the end, the performance of PFFFT library it is outlined by the output.* N > 64, status not OK Problem on initialising sapi::StatusOr s; the memory that stays for s is not the same with the address passed in pffft_transform function. - (sapi :: v :: GenericPtr - to be changed) + (sapi::v::GenericPtr - to be changed) Temporary solution: change the generated files to accept uintptr_t instead of PFFFT_Setup @@ -66,8 +66,8 @@ In the end, the performance of PFFFT library it is outlined by the output.* Solution: using "sapi::v::RemotePtr" instead of "sapi::v::GenericPtr" to access the memory of object s - - [Unresolved] compiling bug: "No spave left on device" + - [Unresolved] compiling bug: "No space left on device" The building process creates some `embed` files that use lots of memory, trying to write them on /tmp. - + Temporary solution: clean /tmp directory by `sudo rm -rf /tmp/*`. \ No newline at end of file From 6554f635fc8a828a55d9362d4048c5ad07fc7ad5 Mon Sep 17 00:00:00 2001 From: Katarzyna Miernikiewicz Date: Mon, 24 Aug 2020 15:01:42 +0000 Subject: [PATCH 34/82] some of the requested changes made --- .../openjpeg/examples/decompress_example.cc | 98 ++++++++++++------- 1 file changed, 61 insertions(+), 37 deletions(-) diff --git a/oss-internship-2020/openjpeg/examples/decompress_example.cc b/oss-internship-2020/openjpeg/examples/decompress_example.cc index 0c9ce22..f19ce0e 100644 --- a/oss-internship-2020/openjpeg/examples/decompress_example.cc +++ b/oss-internship-2020/openjpeg/examples/decompress_example.cc @@ -17,20 +17,18 @@ #include #include -#include #include #include +#include +#include "absl/algorithm/container.h" #include "convert_helper.h" #include "openjp2_sapi.sapi.h" -#include "sandboxed_api/util/flag.h" - -class Parameters : public sapi::v::Struct {}; -class Opj_image_t : public sapi::v::Struct {}; class Openjp2SapiSandbox : public Openjp2Sandbox { public: - Openjp2SapiSandbox(const std::string& in_file) : in_file_(in_file) {} + Openjp2SapiSandbox(const std::string in_file) + : in_file_(std::move(in_file)) {} std::unique_ptr ModifyPolicy( sandbox2::PolicyBuilder*) override { @@ -57,6 +55,7 @@ class Openjp2SapiSandbox : public Openjp2Sandbox { int main(int argc, char* argv[]) { gflags::ParseCommandLineFlags(&argc, &argv, true); + google::InitGoogleLogging(argv[0]); if (argc != 3) { std::cerr << "usage: " << basename(argv[0]) << " absolute/path/to/INPUT.jp2" @@ -69,59 +68,82 @@ int main(int argc, char* argv[]) { // initialize sandbox Openjp2SapiSandbox sandbox(in_file); absl::Status status = sandbox.Init(); - assert(status.ok()); + if (!status.ok()) { + LOG(FATAL) << "sandbox initialization status: " << status; + return EXIT_FAILURE; + } Openjp2Api api(&sandbox); sapi::v::ConstCStr in_file_v(in_file.c_str()); // initialize library's main data-holders - sapi::StatusOr stream_status = + sapi::StatusOr stream = api.opj_stream_create_default_file_stream(in_file_v.PtrBefore(), 1); - assert(stream_status.ok()); - void* stream_status_value = stream_status.value(); - sapi::v::RemotePtr stream_pointer(stream_status_value); + if (!stream.ok()) { + LOG(FATAL) << "opj_stream initialization failed: " << stream.status(); + return EXIT_FAILURE; + } + sapi::v::RemotePtr stream_pointer(stream.value()); - sapi::StatusOr codec_status = - api.opj_create_decompress(OPJ_CODEC_JP2); - assert(codec_status.ok()); - void* codec_status_value = codec_status.value(); - sapi::v::RemotePtr codec_pointer(codec_status_value); + sapi::StatusOr codec = api.opj_create_decompress(OPJ_CODEC_JP2); + if (!codec.ok()) { + LOG(FATAL) << "opj_codec initialization failed: " << codec.status(); + return EXIT_FAILURE; + } + sapi::v::RemotePtr codec_pointer(codec.value()); - Parameters parameters; + sapi::v::Struct parameters; status = api.opj_set_default_decoder_parameters(parameters.PtrBoth()); - assert(status.ok()); + if (!status.ok()) { + LOG(FATAL) << "parameters initialization failed: " << status; + return EXIT_FAILURE; + } sapi::StatusOr bool_status = api.opj_setup_decoder(&codec_pointer, parameters.PtrBefore()); - assert(bool_status.ok()); - assert(bool_status.value()); + if (!bool_status.ok() || !bool_status.value()) { + LOG(FATAL) << "decoder setup failed"; + return EXIT_FAILURE; + } // start reading image from the input file sapi::v::GenericPtr image_pointer; bool_status = api.opj_read_header(&stream_pointer, &codec_pointer, image_pointer.PtrAfter()); - assert(bool_status.ok()); - assert(bool_status.value()); + if (!bool_status.ok() || !bool_status.value()) { + LOG(FATAL) << "reading image header failed"; + return EXIT_FAILURE; + } - Opj_image_t image; + sapi::v::Struct image; image.SetRemote((void*)image_pointer.GetValue()); - assert(sandbox.TransferFromSandboxee(&image).ok()); + if (!sandbox.TransferFromSandboxee(&image).ok()) { + LOG(FATAL) << "transfer from sandboxee failed"; + return EXIT_FAILURE; + } bool_status = api.opj_decode(&codec_pointer, &stream_pointer, (sapi::v::Ptr*)&image_pointer); - assert(bool_status.ok()); - assert(bool_status.value()); + if (!bool_status.ok() || !bool_status.value()) { + LOG(FATAL) << "decoding failed"; + return EXIT_FAILURE; + } bool_status = api.opj_end_decompress(&codec_pointer, &stream_pointer); - assert(bool_status.ok()); - assert(bool_status.value()); + if (!bool_status.ok() || !bool_status.value()) { + LOG(FATAL) << "ending decompress failed"; + return EXIT_FAILURE; + } int components = image.data().numcomps; // transfer the read data to the main process sapi::v::Array image_components(components); image_components.SetRemote(image.data().comps); - assert(sandbox.TransferFromSandboxee(&image_components).ok()); + if (!sandbox.TransferFromSandboxee(&image_components).ok()) { + LOG(FATAL) << "transfer from sandboxee failed"; + return EXIT_FAILURE; + } image.mutable_data()->comps = (opj_image_comp_t*)image_components.GetLocal(); @@ -131,9 +153,12 @@ int main(int argc, char* argv[]) { OPJ_INT32 data[components][width * height]; sapi::v::Array image_components_data(width * height); - for (int i = 0; i < components; i++) { + for (int i = 0; i < components; ++i) { image_components_data.SetRemote(image.data().comps[i].data); - assert(sandbox.TransferFromSandboxee(&image_components_data).ok()); + if (!sandbox.TransferFromSandboxee(&image_components_data).ok()) { + LOG(FATAL) << "transfer from sandboxee failed"; + return EXIT_FAILURE; + } for (int j = 0; j < width * height; j++) { data[i][j] = image_components_data[j]; } @@ -142,18 +167,17 @@ int main(int argc, char* argv[]) { // convert the image to the desired format and save it to the file int error = imagetopnm((opj_image_t*)image.GetLocal(), argv[2], 0); - assert(error == 0); + if (error) LOG(FATAL) << "image convert failed"; // cleanup - sapi::v::RemotePtr remote_image_pointer(image.GetRemote()); - status = api.opj_image_destroy(&remote_image_pointer); - assert(status.ok()); + status = api.opj_image_destroy(image.PtrNone()); + if (!status.ok()) LOG(FATAL) << "image destroy failed: " << status; status = api.opj_stream_destroy(&stream_pointer); - assert(status.ok()); + if (!status.ok()) LOG(FATAL) << "stream destroy failed: " << status; status = api.opj_destroy_codec(&codec_pointer); - assert(status.ok()); + if (!status.ok()) LOG(FATAL) << "codec destroy failed: " << status; return EXIT_SUCCESS; } From 050f0af19a94c0bb384e31ac6715e54ffe1f9ba0 Mon Sep 17 00:00:00 2001 From: Katarzyna Miernikiewicz Date: Tue, 25 Aug 2020 09:50:19 +0000 Subject: [PATCH 35/82] minor change --- oss-internship-2020/openjpeg/examples/decompress_example.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/oss-internship-2020/openjpeg/examples/decompress_example.cc b/oss-internship-2020/openjpeg/examples/decompress_example.cc index f19ce0e..6d81c2c 100644 --- a/oss-internship-2020/openjpeg/examples/decompress_example.cc +++ b/oss-internship-2020/openjpeg/examples/decompress_example.cc @@ -122,8 +122,9 @@ int main(int argc, char* argv[]) { return EXIT_FAILURE; } + sapi::v::RemotePtr image_remote_pointer(image.GetRemote()); bool_status = api.opj_decode(&codec_pointer, &stream_pointer, - (sapi::v::Ptr*)&image_pointer); + &image_remote_pointer); if (!bool_status.ok() || !bool_status.value()) { LOG(FATAL) << "decoding failed"; return EXIT_FAILURE; From 032053d55ddca7de24549bd76f6b874efa06ffb7 Mon Sep 17 00:00:00 2001 From: Katarzyna Miernikiewicz Date: Tue, 25 Aug 2020 10:20:00 +0000 Subject: [PATCH 36/82] minor fix --- oss-internship-2020/openjpeg/examples/decompress_example.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/oss-internship-2020/openjpeg/examples/decompress_example.cc b/oss-internship-2020/openjpeg/examples/decompress_example.cc index 6d81c2c..4a0cb57 100644 --- a/oss-internship-2020/openjpeg/examples/decompress_example.cc +++ b/oss-internship-2020/openjpeg/examples/decompress_example.cc @@ -20,6 +20,7 @@ #include #include #include +#include #include "absl/algorithm/container.h" #include "convert_helper.h" @@ -160,7 +161,7 @@ int main(int argc, char* argv[]) { LOG(FATAL) << "transfer from sandboxee failed"; return EXIT_FAILURE; } - for (int j = 0; j < width * height; j++) { + for (int j = 0; j < width * height; ++j) { data[i][j] = image_components_data[j]; } image_components[i].data = data[i]; From 82c56775ef4d08fe241af21b240cf3be9ac60c60 Mon Sep 17 00:00:00 2001 From: Wiktor Garbacz Date: Tue, 25 Aug 2020 06:21:43 -0700 Subject: [PATCH 37/82] `StatusOr` cleanups PiperOrigin-RevId: 328318284 Change-Id: I207570c0fee6797dbc8995d36ef2130b0bff28fa --- sandboxed_api/proto_helper.h | 2 +- sandboxed_api/rpcchannel.h | 2 +- sandboxed_api/sandbox.cc | 2 +- sandboxed_api/sandbox.h | 5 +++-- sandboxed_api/sandbox2/comms.cc | 2 +- sandboxed_api/sandbox2/examples/network_proxy/BUILD.bazel | 1 + .../sandbox2/examples/network_proxy/networkproxy_bin.cc | 4 ++-- sandboxed_api/sandbox2/forkserver.cc | 2 +- sandboxed_api/sandbox2/mounts.cc | 2 +- sandboxed_api/sandbox2/network_proxy/BUILD.bazel | 2 ++ sandboxed_api/sandbox2/network_proxy/client.cc | 2 +- sandboxed_api/sandbox2/network_proxy/client.h | 2 +- sandboxed_api/sandbox2/network_proxy/filtering.cc | 3 ++- sandboxed_api/sandbox2/network_proxy/filtering.h | 2 +- sandboxed_api/sandbox2/policybuilder.h | 2 +- sandboxed_api/sandbox2/sandbox2.h | 2 +- sandboxed_api/sandbox2/util.cc | 4 ++-- sandboxed_api/sandbox2/util.h | 4 ++-- sandboxed_api/sandbox2/util/minielf.cc | 8 +++----- sandboxed_api/sandbox2/util/minielf.h | 2 +- sandboxed_api/tools/clang_generator/emitter.cc | 2 +- sandboxed_api/tools/clang_generator/emitter.h | 2 +- sandboxed_api/tools/clang_generator/generator.cc | 4 ++-- sandboxed_api/tools/clang_generator/generator.h | 4 ++-- sandboxed_api/tools/clang_generator/types.cc | 2 +- sandboxed_api/util/status.h | 4 ---- 26 files changed, 36 insertions(+), 37 deletions(-) diff --git a/sandboxed_api/proto_helper.h b/sandboxed_api/proto_helper.h index 53ac26d..b00a73e 100644 --- a/sandboxed_api/proto_helper.h +++ b/sandboxed_api/proto_helper.h @@ -22,8 +22,8 @@ #include #include "absl/status/status.h" -#include "sandboxed_api/proto_arg.pb.h" #include "sandboxed_api/util/statusor.h" +#include "sandboxed_api/proto_arg.pb.h" namespace sapi { diff --git a/sandboxed_api/rpcchannel.h b/sandboxed_api/rpcchannel.h index ed50326..c54ef8e 100644 --- a/sandboxed_api/rpcchannel.h +++ b/sandboxed_api/rpcchannel.h @@ -18,11 +18,11 @@ #include #include "absl/status/status.h" +#include "sandboxed_api/util/statusor.h" #include "absl/synchronization/mutex.h" #include "sandboxed_api/call.h" #include "sandboxed_api/sandbox2/comms.h" #include "sandboxed_api/var_type.h" -#include "sandboxed_api/util/statusor.h" namespace sapi { diff --git a/sandboxed_api/sandbox.cc b/sandboxed_api/sandbox.cc index b29646b..18239a4 100644 --- a/sandboxed_api/sandbox.cc +++ b/sandboxed_api/sandbox.cc @@ -392,7 +392,7 @@ absl::Status Sandbox::TransferFromSandboxee(v::Var* var) { } sapi::StatusOr Sandbox::GetCString(const v::RemotePtr& str, - uint64_t max_length) { + uint64_t max_length) { if (!is_active()) { return absl::UnavailableError("Sandbox not active"); } diff --git a/sandboxed_api/sandbox.h b/sandboxed_api/sandbox.h index d8f37ef..8b4ee6a 100644 --- a/sandboxed_api/sandbox.h +++ b/sandboxed_api/sandbox.h @@ -102,8 +102,9 @@ class Sandbox { absl::Status TransferToSandboxee(v::Var* var); absl::Status TransferFromSandboxee(v::Var* var); - sapi::StatusOr GetCString( - const v::RemotePtr& str, uint64_t max_length = 10ULL << 20 /* 10 MiB*/ + sapi::StatusOr GetCString(const v::RemotePtr& str, + uint64_t max_length = 10ULL + << 20 /* 10 MiB*/ ); // Waits until the sandbox terminated and returns the result. diff --git a/sandboxed_api/sandbox2/comms.cc b/sandboxed_api/sandbox2/comms.cc index 6bb891c..3e61013 100644 --- a/sandboxed_api/sandbox2/comms.cc +++ b/sandboxed_api/sandbox2/comms.cc @@ -36,6 +36,7 @@ #include "google/protobuf/message.h" #include "absl/memory/memory.h" #include "absl/status/status.h" +#include "sandboxed_api/util/statusor.h" #include "absl/strings/str_cat.h" #include "absl/strings/str_format.h" #include "absl/synchronization/mutex.h" @@ -44,7 +45,6 @@ #include "sandboxed_api/util/raw_logging.h" #include "sandboxed_api/util/status.h" #include "sandboxed_api/util/status_macros.h" -#include "sandboxed_api/util/statusor.h" #ifdef MEMORY_SANITIZER #include "base/dynamic_annotations.h" diff --git a/sandboxed_api/sandbox2/examples/network_proxy/BUILD.bazel b/sandboxed_api/sandbox2/examples/network_proxy/BUILD.bazel index 133cb88..480c078 100644 --- a/sandboxed_api/sandbox2/examples/network_proxy/BUILD.bazel +++ b/sandboxed_api/sandbox2/examples/network_proxy/BUILD.bazel @@ -49,6 +49,7 @@ cc_binary( "//sandboxed_api/util:flags", "//sandboxed_api/util:status", "//sandboxed_api/util:statusor", + "@com_google_absl//absl/status", "@com_google_absl//absl/strings:str_format", ], ) diff --git a/sandboxed_api/sandbox2/examples/network_proxy/networkproxy_bin.cc b/sandboxed_api/sandbox2/examples/network_proxy/networkproxy_bin.cc index c22947d..b355522 100644 --- a/sandboxed_api/sandbox2/examples/network_proxy/networkproxy_bin.cc +++ b/sandboxed_api/sandbox2/examples/network_proxy/networkproxy_bin.cc @@ -12,15 +12,15 @@ #include #include "sandboxed_api/util/flag.h" +#include "absl/status/status.h" +#include "sandboxed_api/util/statusor.h" #include "absl/strings/str_format.h" #include "sandboxed_api/sandbox2/client.h" #include "sandboxed_api/sandbox2/comms.h" #include "sandboxed_api/sandbox2/network_proxy/client.h" #include "sandboxed_api/sandbox2/util/fileops.h" #include "sandboxed_api/sandbox2/util/strerror.h" -#include "sandboxed_api/util/status.h" #include "sandboxed_api/util/status_macros.h" -#include "sandboxed_api/util/statusor.h" ABSL_FLAG(bool, connect_with_handler, true, "Connect using automatic mode."); diff --git a/sandboxed_api/sandbox2/forkserver.cc b/sandboxed_api/sandbox2/forkserver.cc index 2125927..d0b637c 100644 --- a/sandboxed_api/sandbox2/forkserver.cc +++ b/sandboxed_api/sandbox2/forkserver.cc @@ -36,6 +36,7 @@ #include "absl/memory/memory.h" #include "absl/status/status.h" +#include "sandboxed_api/util/statusor.h" #include "absl/strings/match.h" #include "absl/strings/str_cat.h" #include "absl/strings/str_format.h" @@ -55,7 +56,6 @@ #include "sandboxed_api/sandbox2/util/fileops.h" #include "sandboxed_api/sandbox2/util/strerror.h" #include "sandboxed_api/util/raw_logging.h" -#include "sandboxed_api/util/statusor.h" namespace { // "Moves" the old FD to the new FD number. diff --git a/sandboxed_api/sandbox2/mounts.cc b/sandboxed_api/sandbox2/mounts.cc index 2f734e2..e1c0198 100644 --- a/sandboxed_api/sandbox2/mounts.cc +++ b/sandboxed_api/sandbox2/mounts.cc @@ -27,6 +27,7 @@ #include "google/protobuf/util/message_differencer.h" #include "absl/container/flat_hash_set.h" +#include "sandboxed_api/util/statusor.h" #include "absl/strings/ascii.h" #include "absl/strings/match.h" #include "absl/strings/str_cat.h" @@ -39,7 +40,6 @@ #include "sandboxed_api/sandbox2/util/strerror.h" #include "sandboxed_api/util/raw_logging.h" #include "sandboxed_api/util/status_macros.h" -#include "sandboxed_api/util/statusor.h" namespace sandbox2 { namespace { diff --git a/sandboxed_api/sandbox2/network_proxy/BUILD.bazel b/sandboxed_api/sandbox2/network_proxy/BUILD.bazel index 3a3a9d6..5935390 100644 --- a/sandboxed_api/sandbox2/network_proxy/BUILD.bazel +++ b/sandboxed_api/sandbox2/network_proxy/BUILD.bazel @@ -46,6 +46,7 @@ cc_library( "//sandboxed_api/sandbox2/util:strerror", "//sandboxed_api/util:status", "@com_google_absl//absl/memory", + "@com_google_absl//absl/status", "@com_google_absl//absl/strings", "@com_google_absl//absl/synchronization", "@com_google_glog//:glog", @@ -62,6 +63,7 @@ cc_library( "//sandboxed_api/sandbox2/util:strerror", "//sandboxed_api/util:status", "//sandboxed_api/util:statusor", + "@com_google_absl//absl/status", "@com_google_absl//absl/strings", "@com_google_glog//:glog", ], diff --git a/sandboxed_api/sandbox2/network_proxy/client.cc b/sandboxed_api/sandbox2/network_proxy/client.cc index 586d201..e528041 100644 --- a/sandboxed_api/sandbox2/network_proxy/client.cc +++ b/sandboxed_api/sandbox2/network_proxy/client.cc @@ -25,9 +25,9 @@ #include #include "absl/memory/memory.h" +#include "absl/status/status.h" #include "absl/strings/str_cat.h" #include "sandboxed_api/sandbox2/util/strerror.h" -#include "sandboxed_api/util/status.h" #include "sandboxed_api/util/status_macros.h" namespace sandbox2 { diff --git a/sandboxed_api/sandbox2/network_proxy/client.h b/sandboxed_api/sandbox2/network_proxy/client.h index e7af4dc..7318993 100644 --- a/sandboxed_api/sandbox2/network_proxy/client.h +++ b/sandboxed_api/sandbox2/network_proxy/client.h @@ -17,9 +17,9 @@ #include +#include "absl/status/status.h" #include "absl/synchronization/mutex.h" #include "sandboxed_api/sandbox2/comms.h" -#include "sandboxed_api/util/status.h" namespace sandbox2 { diff --git a/sandboxed_api/sandbox2/network_proxy/filtering.cc b/sandboxed_api/sandbox2/network_proxy/filtering.cc index d6389d8..fc93e6c 100644 --- a/sandboxed_api/sandbox2/network_proxy/filtering.cc +++ b/sandboxed_api/sandbox2/network_proxy/filtering.cc @@ -17,11 +17,12 @@ #include #include +#include "absl/status/status.h" +#include "sandboxed_api/util/statusor.h" #include "absl/strings/numbers.h" #include "absl/strings/str_cat.h" #include "absl/strings/str_split.h" #include "sandboxed_api/sandbox2/util/strerror.h" -#include "sandboxed_api/util/status.h" #include "sandboxed_api/util/status_macros.h" namespace sandbox2 { diff --git a/sandboxed_api/sandbox2/network_proxy/filtering.h b/sandboxed_api/sandbox2/network_proxy/filtering.h index fb6714a..c0a235b 100644 --- a/sandboxed_api/sandbox2/network_proxy/filtering.h +++ b/sandboxed_api/sandbox2/network_proxy/filtering.h @@ -19,8 +19,8 @@ #include -#include "sandboxed_api/sandbox2/comms.h" #include "sandboxed_api/util/statusor.h" +#include "sandboxed_api/sandbox2/comms.h" namespace sandbox2 { diff --git a/sandboxed_api/sandbox2/policybuilder.h b/sandboxed_api/sandbox2/policybuilder.h index f05023c..07095ba 100644 --- a/sandboxed_api/sandbox2/policybuilder.h +++ b/sandboxed_api/sandbox2/policybuilder.h @@ -29,11 +29,11 @@ #include #include "absl/base/macros.h" #include "absl/memory/memory.h" +#include "sandboxed_api/util/statusor.h" #include "absl/strings/string_view.h" #include "sandboxed_api/sandbox2/mounts.h" #include "sandboxed_api/sandbox2/network_proxy/filtering.h" #include "sandboxed_api/sandbox2/policy.h" -#include "sandboxed_api/util/statusor.h" struct bpf_labels; diff --git a/sandboxed_api/sandbox2/sandbox2.h b/sandboxed_api/sandbox2/sandbox2.h index 9527ff2..633297f 100644 --- a/sandboxed_api/sandbox2/sandbox2.h +++ b/sandboxed_api/sandbox2/sandbox2.h @@ -26,6 +26,7 @@ #include #include "absl/base/macros.h" #include "absl/memory/memory.h" +#include "sandboxed_api/util/statusor.h" #include "sandboxed_api/sandbox2/comms.h" #include "sandboxed_api/sandbox2/executor.h" #include "sandboxed_api/sandbox2/ipc.h" @@ -33,7 +34,6 @@ #include "sandboxed_api/sandbox2/notify.h" #include "sandboxed_api/sandbox2/policy.h" #include "sandboxed_api/sandbox2/result.h" -#include "sandboxed_api/util/statusor.h" namespace sandbox2 { diff --git a/sandboxed_api/sandbox2/util.cc b/sandboxed_api/sandbox2/util.cc index 24d4a36..b760d26 100644 --- a/sandboxed_api/sandbox2/util.cc +++ b/sandboxed_api/sandbox2/util.cc @@ -183,8 +183,8 @@ bool CreateMemFd(int* fd, const char* name) { } sapi::StatusOr Communicate(const std::vector& argv, - const std::vector& envv, - std::string* output) { + const std::vector& envv, + std::string* output) { int cout_pipe[2]; posix_spawn_file_actions_t action; diff --git a/sandboxed_api/sandbox2/util.h b/sandboxed_api/sandbox2/util.h index 87fe31c..e42bf72 100644 --- a/sandboxed_api/sandbox2/util.h +++ b/sandboxed_api/sandbox2/util.h @@ -63,8 +63,8 @@ bool CreateMemFd(int* fd, const char* name = "buffer_file"); // Executes a the program given by argv and the specified environment and // captures any output to stdout/stderr. sapi::StatusOr Communicate(const std::vector& argv, - const std::vector& envv, - std::string* output); + const std::vector& envv, + std::string* output); // Returns signal description. std::string GetSignalName(int signo); diff --git a/sandboxed_api/sandbox2/util/minielf.cc b/sandboxed_api/sandbox2/util/minielf.cc index 3eac979..dce033b 100644 --- a/sandboxed_api/sandbox2/util/minielf.cc +++ b/sandboxed_api/sandbox2/util/minielf.cc @@ -219,8 +219,7 @@ absl::Status ElfParser::ReadFileHeader() { return absl::OkStatus(); } -sapi::StatusOr ElfParser::ReadSectionHeader( - absl::string_view src) { +sapi::StatusOr ElfParser::ReadSectionHeader(absl::string_view src) { if (src.size() < sizeof(Elf64_Shdr)) { return absl::FailedPreconditionError( absl::StrCat("invalid section header data: got ", src.size(), @@ -293,8 +292,7 @@ sapi::StatusOr ElfParser::ReadSectionContents( return rv; } -sapi::StatusOr ElfParser::ReadProgramHeader( - absl::string_view src) { +sapi::StatusOr ElfParser::ReadProgramHeader(absl::string_view src) { if (src.size() < sizeof(Elf64_Phdr)) { return absl::FailedPreconditionError( absl::StrCat("invalid program header data: got ", src.size(), @@ -514,7 +512,7 @@ sapi::StatusOr ElfParser::Parse(FILE* elf, uint32_t features) { } sapi::StatusOr ElfFile::ParseFromFile(const std::string& filename, - uint32_t features) { + uint32_t features) { std::unique_ptr elf{fopen(filename.c_str(), "r"), [](FILE* f) { fclose(f); }}; if (!elf) { diff --git a/sandboxed_api/sandbox2/util/minielf.h b/sandboxed_api/sandbox2/util/minielf.h index 247d271..c33a604 100644 --- a/sandboxed_api/sandbox2/util/minielf.h +++ b/sandboxed_api/sandbox2/util/minielf.h @@ -34,7 +34,7 @@ class ElfFile { }; static sapi::StatusOr ParseFromFile(const std::string& filename, - uint32_t features); + uint32_t features); int64_t file_size() const { return file_size_; } const std::string& interpreter() const { return interpreter_; } diff --git a/sandboxed_api/tools/clang_generator/emitter.cc b/sandboxed_api/tools/clang_generator/emitter.cc index 18fdcd9..78b9a9a 100644 --- a/sandboxed_api/tools/clang_generator/emitter.cc +++ b/sandboxed_api/tools/clang_generator/emitter.cc @@ -46,10 +46,10 @@ constexpr absl::string_view kHeaderProlog = #include "absl/base/macros.h" #include "absl/status/status.h" +#include "sandboxed_api/util/statusor.h" #include "sandboxed_api/sandbox.h" #include "sandboxed_api/vars.h" #include "sandboxed_api/util/status_macros.h" -#include "sandboxed_api/util/statusor.h" )"; constexpr absl::string_view kHeaderEpilog = diff --git a/sandboxed_api/tools/clang_generator/emitter.h b/sandboxed_api/tools/clang_generator/emitter.h index 42ba92d..ad9f5f8 100644 --- a/sandboxed_api/tools/clang_generator/emitter.h +++ b/sandboxed_api/tools/clang_generator/emitter.h @@ -18,12 +18,12 @@ #include #include "absl/status/status.h" +#include "sandboxed_api/util/statusor.h" #include "absl/strings/string_view.h" #include "clang/AST/Decl.h" #include "clang/AST/Type.h" #include "sandboxed_api/tools/clang_generator/generator.h" #include "sandboxed_api/tools/clang_generator/types.h" -#include "sandboxed_api/util/statusor.h" namespace sapi { diff --git a/sandboxed_api/tools/clang_generator/generator.cc b/sandboxed_api/tools/clang_generator/generator.cc index 508877e..ffe2106 100644 --- a/sandboxed_api/tools/clang_generator/generator.cc +++ b/sandboxed_api/tools/clang_generator/generator.cc @@ -18,12 +18,12 @@ #include #include "absl/status/status.h" +#include "sandboxed_api/util/statusor.h" #include "clang/Format/Format.h" #include "sandboxed_api/sandbox2/util/fileops.h" #include "sandboxed_api/tools/clang_generator/diagnostics.h" #include "sandboxed_api/tools/clang_generator/emitter.h" #include "sandboxed_api/util/status_macros.h" -#include "sandboxed_api/util/statusor.h" namespace sapi { namespace { @@ -68,7 +68,7 @@ bool GeneratorASTVisitor::VisitFunctionDecl(clang::FunctionDecl* decl) { namespace internal { sapi::StatusOr ReformatGoogleStyle(const std::string& filename, - const std::string& code) { + const std::string& code) { // Configure code style based on Google style, but enforce pointer alignment clang::format::FormatStyle style = clang::format::getGoogleStyle(clang::format::FormatStyle::LK_Cpp); diff --git a/sandboxed_api/tools/clang_generator/generator.h b/sandboxed_api/tools/clang_generator/generator.h index 24e2658..3b9680b 100644 --- a/sandboxed_api/tools/clang_generator/generator.h +++ b/sandboxed_api/tools/clang_generator/generator.h @@ -20,13 +20,13 @@ #include "absl/container/flat_hash_set.h" #include "absl/memory/memory.h" #include "absl/status/status.h" +#include "sandboxed_api/util/statusor.h" #include "clang/AST/ASTConsumer.h" #include "clang/AST/RecursiveASTVisitor.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Frontend/FrontendAction.h" #include "clang/Tooling/Tooling.h" #include "sandboxed_api/tools/clang_generator/types.h" -#include "sandboxed_api/util/statusor.h" namespace sapi { @@ -67,7 +67,7 @@ class GeneratorASTVisitor namespace internal { sapi::StatusOr ReformatGoogleStyle(const std::string& filename, - const std::string& code); + const std::string& code); } // namespace internal diff --git a/sandboxed_api/tools/clang_generator/types.cc b/sandboxed_api/tools/clang_generator/types.cc index 4d91499..321d1e2 100644 --- a/sandboxed_api/tools/clang_generator/types.cc +++ b/sandboxed_api/tools/clang_generator/types.cc @@ -208,7 +208,7 @@ std::string MapQualTypeReturn(const clang::ASTContext& context, return "absl::Status"; } // Remove const qualifier like in MapQualType(). - return absl::StrCat("::sapi::StatusOr<", + return absl::StrCat("sapi::StatusOr<", MaybeRemoveConst(context, qual).getAsString(), ">"); } diff --git a/sandboxed_api/util/status.h b/sandboxed_api/util/status.h index 6390bc0..d9962fd 100644 --- a/sandboxed_api/util/status.h +++ b/sandboxed_api/util/status.h @@ -12,10 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -// This file and it's implementation provide a custom fork of -// util/task/status.h. This will become obsolete and will be replaced once -// Abseil releases absl::Status. - #ifndef THIRD_PARTY_SAPI_UTIL_STATUS_H_ #define THIRD_PARTY_SAPI_UTIL_STATUS_H_ From ba88cd3499be256eaf06672d69e6030af938fc89 Mon Sep 17 00:00:00 2001 From: Katarzyna Miernikiewicz Date: Tue, 25 Aug 2020 14:53:53 +0000 Subject: [PATCH 38/82] changed VLA to vectors --- .../openjpeg/examples/decompress_example.cc | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/oss-internship-2020/openjpeg/examples/decompress_example.cc b/oss-internship-2020/openjpeg/examples/decompress_example.cc index 4a0cb57..1c4e756 100644 --- a/oss-internship-2020/openjpeg/examples/decompress_example.cc +++ b/oss-internship-2020/openjpeg/examples/decompress_example.cc @@ -20,7 +20,6 @@ #include #include #include -#include #include "absl/algorithm/container.h" #include "convert_helper.h" @@ -124,8 +123,8 @@ int main(int argc, char* argv[]) { } sapi::v::RemotePtr image_remote_pointer(image.GetRemote()); - bool_status = api.opj_decode(&codec_pointer, &stream_pointer, - &image_remote_pointer); + bool_status = + api.opj_decode(&codec_pointer, &stream_pointer, &image_remote_pointer); if (!bool_status.ok() || !bool_status.value()) { LOG(FATAL) << "decoding failed"; return EXIT_FAILURE; @@ -152,7 +151,7 @@ int main(int argc, char* argv[]) { int width = (int)image.data().comps[0].w; int height = (int)image.data().comps[0].h; - OPJ_INT32 data[components][width * height]; + std::vector> data; sapi::v::Array image_components_data(width * height); for (int i = 0; i < components; ++i) { @@ -161,10 +160,14 @@ int main(int argc, char* argv[]) { LOG(FATAL) << "transfer from sandboxee failed"; return EXIT_FAILURE; } - for (int j = 0; j < width * height; ++j) { - data[i][j] = image_components_data[j]; - } - image_components[i].data = data[i]; + std::vector component_data( + image_components_data.GetData(), + image_components_data.GetData() + (width * height)); + data.push_back(component_data); + } + + for (int i = 0; i < components; ++i) { + image_components[i].data = &data[i][0]; } // convert the image to the desired format and save it to the file From e7a195ce42a1c3dbcdd1f78c954a4583b85fc789 Mon Sep 17 00:00:00 2001 From: Sandboxed API Team Date: Tue, 25 Aug 2020 09:00:55 -0700 Subject: [PATCH 39/82] Automated rollback of commit 82c56775ef4d08fe241af21b240cf3be9ac60c60. PiperOrigin-RevId: 328340042 Change-Id: Ib225f8012fb373c74e3f1b3e6201b2daca7da40b --- sandboxed_api/proto_helper.h | 2 +- sandboxed_api/rpcchannel.h | 2 +- sandboxed_api/sandbox.cc | 2 +- sandboxed_api/sandbox.h | 5 ++--- sandboxed_api/sandbox2/comms.cc | 2 +- sandboxed_api/sandbox2/examples/network_proxy/BUILD.bazel | 1 - .../sandbox2/examples/network_proxy/networkproxy_bin.cc | 4 ++-- sandboxed_api/sandbox2/forkserver.cc | 2 +- sandboxed_api/sandbox2/mounts.cc | 2 +- sandboxed_api/sandbox2/network_proxy/BUILD.bazel | 2 -- sandboxed_api/sandbox2/network_proxy/client.cc | 2 +- sandboxed_api/sandbox2/network_proxy/client.h | 2 +- sandboxed_api/sandbox2/network_proxy/filtering.cc | 3 +-- sandboxed_api/sandbox2/network_proxy/filtering.h | 2 +- sandboxed_api/sandbox2/policybuilder.h | 2 +- sandboxed_api/sandbox2/sandbox2.h | 2 +- sandboxed_api/sandbox2/util.cc | 4 ++-- sandboxed_api/sandbox2/util.h | 4 ++-- sandboxed_api/sandbox2/util/minielf.cc | 8 +++++--- sandboxed_api/sandbox2/util/minielf.h | 2 +- sandboxed_api/tools/clang_generator/emitter.cc | 2 +- sandboxed_api/tools/clang_generator/emitter.h | 2 +- sandboxed_api/tools/clang_generator/generator.cc | 4 ++-- sandboxed_api/tools/clang_generator/generator.h | 4 ++-- sandboxed_api/tools/clang_generator/types.cc | 2 +- sandboxed_api/util/status.h | 4 ++++ 26 files changed, 37 insertions(+), 36 deletions(-) diff --git a/sandboxed_api/proto_helper.h b/sandboxed_api/proto_helper.h index b00a73e..53ac26d 100644 --- a/sandboxed_api/proto_helper.h +++ b/sandboxed_api/proto_helper.h @@ -22,8 +22,8 @@ #include #include "absl/status/status.h" -#include "sandboxed_api/util/statusor.h" #include "sandboxed_api/proto_arg.pb.h" +#include "sandboxed_api/util/statusor.h" namespace sapi { diff --git a/sandboxed_api/rpcchannel.h b/sandboxed_api/rpcchannel.h index c54ef8e..ed50326 100644 --- a/sandboxed_api/rpcchannel.h +++ b/sandboxed_api/rpcchannel.h @@ -18,11 +18,11 @@ #include #include "absl/status/status.h" -#include "sandboxed_api/util/statusor.h" #include "absl/synchronization/mutex.h" #include "sandboxed_api/call.h" #include "sandboxed_api/sandbox2/comms.h" #include "sandboxed_api/var_type.h" +#include "sandboxed_api/util/statusor.h" namespace sapi { diff --git a/sandboxed_api/sandbox.cc b/sandboxed_api/sandbox.cc index 18239a4..b29646b 100644 --- a/sandboxed_api/sandbox.cc +++ b/sandboxed_api/sandbox.cc @@ -392,7 +392,7 @@ absl::Status Sandbox::TransferFromSandboxee(v::Var* var) { } sapi::StatusOr Sandbox::GetCString(const v::RemotePtr& str, - uint64_t max_length) { + uint64_t max_length) { if (!is_active()) { return absl::UnavailableError("Sandbox not active"); } diff --git a/sandboxed_api/sandbox.h b/sandboxed_api/sandbox.h index 8b4ee6a..d8f37ef 100644 --- a/sandboxed_api/sandbox.h +++ b/sandboxed_api/sandbox.h @@ -102,9 +102,8 @@ class Sandbox { absl::Status TransferToSandboxee(v::Var* var); absl::Status TransferFromSandboxee(v::Var* var); - sapi::StatusOr GetCString(const v::RemotePtr& str, - uint64_t max_length = 10ULL - << 20 /* 10 MiB*/ + sapi::StatusOr GetCString( + const v::RemotePtr& str, uint64_t max_length = 10ULL << 20 /* 10 MiB*/ ); // Waits until the sandbox terminated and returns the result. diff --git a/sandboxed_api/sandbox2/comms.cc b/sandboxed_api/sandbox2/comms.cc index 3e61013..6bb891c 100644 --- a/sandboxed_api/sandbox2/comms.cc +++ b/sandboxed_api/sandbox2/comms.cc @@ -36,7 +36,6 @@ #include "google/protobuf/message.h" #include "absl/memory/memory.h" #include "absl/status/status.h" -#include "sandboxed_api/util/statusor.h" #include "absl/strings/str_cat.h" #include "absl/strings/str_format.h" #include "absl/synchronization/mutex.h" @@ -45,6 +44,7 @@ #include "sandboxed_api/util/raw_logging.h" #include "sandboxed_api/util/status.h" #include "sandboxed_api/util/status_macros.h" +#include "sandboxed_api/util/statusor.h" #ifdef MEMORY_SANITIZER #include "base/dynamic_annotations.h" diff --git a/sandboxed_api/sandbox2/examples/network_proxy/BUILD.bazel b/sandboxed_api/sandbox2/examples/network_proxy/BUILD.bazel index 480c078..133cb88 100644 --- a/sandboxed_api/sandbox2/examples/network_proxy/BUILD.bazel +++ b/sandboxed_api/sandbox2/examples/network_proxy/BUILD.bazel @@ -49,7 +49,6 @@ cc_binary( "//sandboxed_api/util:flags", "//sandboxed_api/util:status", "//sandboxed_api/util:statusor", - "@com_google_absl//absl/status", "@com_google_absl//absl/strings:str_format", ], ) diff --git a/sandboxed_api/sandbox2/examples/network_proxy/networkproxy_bin.cc b/sandboxed_api/sandbox2/examples/network_proxy/networkproxy_bin.cc index b355522..c22947d 100644 --- a/sandboxed_api/sandbox2/examples/network_proxy/networkproxy_bin.cc +++ b/sandboxed_api/sandbox2/examples/network_proxy/networkproxy_bin.cc @@ -12,15 +12,15 @@ #include #include "sandboxed_api/util/flag.h" -#include "absl/status/status.h" -#include "sandboxed_api/util/statusor.h" #include "absl/strings/str_format.h" #include "sandboxed_api/sandbox2/client.h" #include "sandboxed_api/sandbox2/comms.h" #include "sandboxed_api/sandbox2/network_proxy/client.h" #include "sandboxed_api/sandbox2/util/fileops.h" #include "sandboxed_api/sandbox2/util/strerror.h" +#include "sandboxed_api/util/status.h" #include "sandboxed_api/util/status_macros.h" +#include "sandboxed_api/util/statusor.h" ABSL_FLAG(bool, connect_with_handler, true, "Connect using automatic mode."); diff --git a/sandboxed_api/sandbox2/forkserver.cc b/sandboxed_api/sandbox2/forkserver.cc index d0b637c..2125927 100644 --- a/sandboxed_api/sandbox2/forkserver.cc +++ b/sandboxed_api/sandbox2/forkserver.cc @@ -36,7 +36,6 @@ #include "absl/memory/memory.h" #include "absl/status/status.h" -#include "sandboxed_api/util/statusor.h" #include "absl/strings/match.h" #include "absl/strings/str_cat.h" #include "absl/strings/str_format.h" @@ -56,6 +55,7 @@ #include "sandboxed_api/sandbox2/util/fileops.h" #include "sandboxed_api/sandbox2/util/strerror.h" #include "sandboxed_api/util/raw_logging.h" +#include "sandboxed_api/util/statusor.h" namespace { // "Moves" the old FD to the new FD number. diff --git a/sandboxed_api/sandbox2/mounts.cc b/sandboxed_api/sandbox2/mounts.cc index e1c0198..2f734e2 100644 --- a/sandboxed_api/sandbox2/mounts.cc +++ b/sandboxed_api/sandbox2/mounts.cc @@ -27,7 +27,6 @@ #include "google/protobuf/util/message_differencer.h" #include "absl/container/flat_hash_set.h" -#include "sandboxed_api/util/statusor.h" #include "absl/strings/ascii.h" #include "absl/strings/match.h" #include "absl/strings/str_cat.h" @@ -40,6 +39,7 @@ #include "sandboxed_api/sandbox2/util/strerror.h" #include "sandboxed_api/util/raw_logging.h" #include "sandboxed_api/util/status_macros.h" +#include "sandboxed_api/util/statusor.h" namespace sandbox2 { namespace { diff --git a/sandboxed_api/sandbox2/network_proxy/BUILD.bazel b/sandboxed_api/sandbox2/network_proxy/BUILD.bazel index 5935390..3a3a9d6 100644 --- a/sandboxed_api/sandbox2/network_proxy/BUILD.bazel +++ b/sandboxed_api/sandbox2/network_proxy/BUILD.bazel @@ -46,7 +46,6 @@ cc_library( "//sandboxed_api/sandbox2/util:strerror", "//sandboxed_api/util:status", "@com_google_absl//absl/memory", - "@com_google_absl//absl/status", "@com_google_absl//absl/strings", "@com_google_absl//absl/synchronization", "@com_google_glog//:glog", @@ -63,7 +62,6 @@ cc_library( "//sandboxed_api/sandbox2/util:strerror", "//sandboxed_api/util:status", "//sandboxed_api/util:statusor", - "@com_google_absl//absl/status", "@com_google_absl//absl/strings", "@com_google_glog//:glog", ], diff --git a/sandboxed_api/sandbox2/network_proxy/client.cc b/sandboxed_api/sandbox2/network_proxy/client.cc index e528041..586d201 100644 --- a/sandboxed_api/sandbox2/network_proxy/client.cc +++ b/sandboxed_api/sandbox2/network_proxy/client.cc @@ -25,9 +25,9 @@ #include #include "absl/memory/memory.h" -#include "absl/status/status.h" #include "absl/strings/str_cat.h" #include "sandboxed_api/sandbox2/util/strerror.h" +#include "sandboxed_api/util/status.h" #include "sandboxed_api/util/status_macros.h" namespace sandbox2 { diff --git a/sandboxed_api/sandbox2/network_proxy/client.h b/sandboxed_api/sandbox2/network_proxy/client.h index 7318993..e7af4dc 100644 --- a/sandboxed_api/sandbox2/network_proxy/client.h +++ b/sandboxed_api/sandbox2/network_proxy/client.h @@ -17,9 +17,9 @@ #include -#include "absl/status/status.h" #include "absl/synchronization/mutex.h" #include "sandboxed_api/sandbox2/comms.h" +#include "sandboxed_api/util/status.h" namespace sandbox2 { diff --git a/sandboxed_api/sandbox2/network_proxy/filtering.cc b/sandboxed_api/sandbox2/network_proxy/filtering.cc index fc93e6c..d6389d8 100644 --- a/sandboxed_api/sandbox2/network_proxy/filtering.cc +++ b/sandboxed_api/sandbox2/network_proxy/filtering.cc @@ -17,12 +17,11 @@ #include #include -#include "absl/status/status.h" -#include "sandboxed_api/util/statusor.h" #include "absl/strings/numbers.h" #include "absl/strings/str_cat.h" #include "absl/strings/str_split.h" #include "sandboxed_api/sandbox2/util/strerror.h" +#include "sandboxed_api/util/status.h" #include "sandboxed_api/util/status_macros.h" namespace sandbox2 { diff --git a/sandboxed_api/sandbox2/network_proxy/filtering.h b/sandboxed_api/sandbox2/network_proxy/filtering.h index c0a235b..fb6714a 100644 --- a/sandboxed_api/sandbox2/network_proxy/filtering.h +++ b/sandboxed_api/sandbox2/network_proxy/filtering.h @@ -19,8 +19,8 @@ #include -#include "sandboxed_api/util/statusor.h" #include "sandboxed_api/sandbox2/comms.h" +#include "sandboxed_api/util/statusor.h" namespace sandbox2 { diff --git a/sandboxed_api/sandbox2/policybuilder.h b/sandboxed_api/sandbox2/policybuilder.h index 07095ba..f05023c 100644 --- a/sandboxed_api/sandbox2/policybuilder.h +++ b/sandboxed_api/sandbox2/policybuilder.h @@ -29,11 +29,11 @@ #include #include "absl/base/macros.h" #include "absl/memory/memory.h" -#include "sandboxed_api/util/statusor.h" #include "absl/strings/string_view.h" #include "sandboxed_api/sandbox2/mounts.h" #include "sandboxed_api/sandbox2/network_proxy/filtering.h" #include "sandboxed_api/sandbox2/policy.h" +#include "sandboxed_api/util/statusor.h" struct bpf_labels; diff --git a/sandboxed_api/sandbox2/sandbox2.h b/sandboxed_api/sandbox2/sandbox2.h index 633297f..9527ff2 100644 --- a/sandboxed_api/sandbox2/sandbox2.h +++ b/sandboxed_api/sandbox2/sandbox2.h @@ -26,7 +26,6 @@ #include #include "absl/base/macros.h" #include "absl/memory/memory.h" -#include "sandboxed_api/util/statusor.h" #include "sandboxed_api/sandbox2/comms.h" #include "sandboxed_api/sandbox2/executor.h" #include "sandboxed_api/sandbox2/ipc.h" @@ -34,6 +33,7 @@ #include "sandboxed_api/sandbox2/notify.h" #include "sandboxed_api/sandbox2/policy.h" #include "sandboxed_api/sandbox2/result.h" +#include "sandboxed_api/util/statusor.h" namespace sandbox2 { diff --git a/sandboxed_api/sandbox2/util.cc b/sandboxed_api/sandbox2/util.cc index b760d26..24d4a36 100644 --- a/sandboxed_api/sandbox2/util.cc +++ b/sandboxed_api/sandbox2/util.cc @@ -183,8 +183,8 @@ bool CreateMemFd(int* fd, const char* name) { } sapi::StatusOr Communicate(const std::vector& argv, - const std::vector& envv, - std::string* output) { + const std::vector& envv, + std::string* output) { int cout_pipe[2]; posix_spawn_file_actions_t action; diff --git a/sandboxed_api/sandbox2/util.h b/sandboxed_api/sandbox2/util.h index e42bf72..87fe31c 100644 --- a/sandboxed_api/sandbox2/util.h +++ b/sandboxed_api/sandbox2/util.h @@ -63,8 +63,8 @@ bool CreateMemFd(int* fd, const char* name = "buffer_file"); // Executes a the program given by argv and the specified environment and // captures any output to stdout/stderr. sapi::StatusOr Communicate(const std::vector& argv, - const std::vector& envv, - std::string* output); + const std::vector& envv, + std::string* output); // Returns signal description. std::string GetSignalName(int signo); diff --git a/sandboxed_api/sandbox2/util/minielf.cc b/sandboxed_api/sandbox2/util/minielf.cc index dce033b..3eac979 100644 --- a/sandboxed_api/sandbox2/util/minielf.cc +++ b/sandboxed_api/sandbox2/util/minielf.cc @@ -219,7 +219,8 @@ absl::Status ElfParser::ReadFileHeader() { return absl::OkStatus(); } -sapi::StatusOr ElfParser::ReadSectionHeader(absl::string_view src) { +sapi::StatusOr ElfParser::ReadSectionHeader( + absl::string_view src) { if (src.size() < sizeof(Elf64_Shdr)) { return absl::FailedPreconditionError( absl::StrCat("invalid section header data: got ", src.size(), @@ -292,7 +293,8 @@ sapi::StatusOr ElfParser::ReadSectionContents( return rv; } -sapi::StatusOr ElfParser::ReadProgramHeader(absl::string_view src) { +sapi::StatusOr ElfParser::ReadProgramHeader( + absl::string_view src) { if (src.size() < sizeof(Elf64_Phdr)) { return absl::FailedPreconditionError( absl::StrCat("invalid program header data: got ", src.size(), @@ -512,7 +514,7 @@ sapi::StatusOr ElfParser::Parse(FILE* elf, uint32_t features) { } sapi::StatusOr ElfFile::ParseFromFile(const std::string& filename, - uint32_t features) { + uint32_t features) { std::unique_ptr elf{fopen(filename.c_str(), "r"), [](FILE* f) { fclose(f); }}; if (!elf) { diff --git a/sandboxed_api/sandbox2/util/minielf.h b/sandboxed_api/sandbox2/util/minielf.h index c33a604..247d271 100644 --- a/sandboxed_api/sandbox2/util/minielf.h +++ b/sandboxed_api/sandbox2/util/minielf.h @@ -34,7 +34,7 @@ class ElfFile { }; static sapi::StatusOr ParseFromFile(const std::string& filename, - uint32_t features); + uint32_t features); int64_t file_size() const { return file_size_; } const std::string& interpreter() const { return interpreter_; } diff --git a/sandboxed_api/tools/clang_generator/emitter.cc b/sandboxed_api/tools/clang_generator/emitter.cc index 78b9a9a..18fdcd9 100644 --- a/sandboxed_api/tools/clang_generator/emitter.cc +++ b/sandboxed_api/tools/clang_generator/emitter.cc @@ -46,10 +46,10 @@ constexpr absl::string_view kHeaderProlog = #include "absl/base/macros.h" #include "absl/status/status.h" -#include "sandboxed_api/util/statusor.h" #include "sandboxed_api/sandbox.h" #include "sandboxed_api/vars.h" #include "sandboxed_api/util/status_macros.h" +#include "sandboxed_api/util/statusor.h" )"; constexpr absl::string_view kHeaderEpilog = diff --git a/sandboxed_api/tools/clang_generator/emitter.h b/sandboxed_api/tools/clang_generator/emitter.h index ad9f5f8..42ba92d 100644 --- a/sandboxed_api/tools/clang_generator/emitter.h +++ b/sandboxed_api/tools/clang_generator/emitter.h @@ -18,12 +18,12 @@ #include #include "absl/status/status.h" -#include "sandboxed_api/util/statusor.h" #include "absl/strings/string_view.h" #include "clang/AST/Decl.h" #include "clang/AST/Type.h" #include "sandboxed_api/tools/clang_generator/generator.h" #include "sandboxed_api/tools/clang_generator/types.h" +#include "sandboxed_api/util/statusor.h" namespace sapi { diff --git a/sandboxed_api/tools/clang_generator/generator.cc b/sandboxed_api/tools/clang_generator/generator.cc index ffe2106..508877e 100644 --- a/sandboxed_api/tools/clang_generator/generator.cc +++ b/sandboxed_api/tools/clang_generator/generator.cc @@ -18,12 +18,12 @@ #include #include "absl/status/status.h" -#include "sandboxed_api/util/statusor.h" #include "clang/Format/Format.h" #include "sandboxed_api/sandbox2/util/fileops.h" #include "sandboxed_api/tools/clang_generator/diagnostics.h" #include "sandboxed_api/tools/clang_generator/emitter.h" #include "sandboxed_api/util/status_macros.h" +#include "sandboxed_api/util/statusor.h" namespace sapi { namespace { @@ -68,7 +68,7 @@ bool GeneratorASTVisitor::VisitFunctionDecl(clang::FunctionDecl* decl) { namespace internal { sapi::StatusOr ReformatGoogleStyle(const std::string& filename, - const std::string& code) { + const std::string& code) { // Configure code style based on Google style, but enforce pointer alignment clang::format::FormatStyle style = clang::format::getGoogleStyle(clang::format::FormatStyle::LK_Cpp); diff --git a/sandboxed_api/tools/clang_generator/generator.h b/sandboxed_api/tools/clang_generator/generator.h index 3b9680b..24e2658 100644 --- a/sandboxed_api/tools/clang_generator/generator.h +++ b/sandboxed_api/tools/clang_generator/generator.h @@ -20,13 +20,13 @@ #include "absl/container/flat_hash_set.h" #include "absl/memory/memory.h" #include "absl/status/status.h" -#include "sandboxed_api/util/statusor.h" #include "clang/AST/ASTConsumer.h" #include "clang/AST/RecursiveASTVisitor.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Frontend/FrontendAction.h" #include "clang/Tooling/Tooling.h" #include "sandboxed_api/tools/clang_generator/types.h" +#include "sandboxed_api/util/statusor.h" namespace sapi { @@ -67,7 +67,7 @@ class GeneratorASTVisitor namespace internal { sapi::StatusOr ReformatGoogleStyle(const std::string& filename, - const std::string& code); + const std::string& code); } // namespace internal diff --git a/sandboxed_api/tools/clang_generator/types.cc b/sandboxed_api/tools/clang_generator/types.cc index 321d1e2..4d91499 100644 --- a/sandboxed_api/tools/clang_generator/types.cc +++ b/sandboxed_api/tools/clang_generator/types.cc @@ -208,7 +208,7 @@ std::string MapQualTypeReturn(const clang::ASTContext& context, return "absl::Status"; } // Remove const qualifier like in MapQualType(). - return absl::StrCat("sapi::StatusOr<", + return absl::StrCat("::sapi::StatusOr<", MaybeRemoveConst(context, qual).getAsString(), ">"); } diff --git a/sandboxed_api/util/status.h b/sandboxed_api/util/status.h index d9962fd..6390bc0 100644 --- a/sandboxed_api/util/status.h +++ b/sandboxed_api/util/status.h @@ -12,6 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. +// This file and it's implementation provide a custom fork of +// util/task/status.h. This will become obsolete and will be replaced once +// Abseil releases absl::Status. + #ifndef THIRD_PARTY_SAPI_UTIL_STATUS_H_ #define THIRD_PARTY_SAPI_UTIL_STATUS_H_ From 8f21b0e931ed702876a5421bf2bbf13374a58987 Mon Sep 17 00:00:00 2001 From: doinachiroiu Date: Wed, 26 Aug 2020 11:23:33 +0000 Subject: [PATCH 40/82] Coding style update --- .../pffft/main_pffft_sandboxed.cc | 66 +++++++------------ 1 file changed, 23 insertions(+), 43 deletions(-) diff --git a/oss-internship-2020/pffft/main_pffft_sandboxed.cc b/oss-internship-2020/pffft/main_pffft_sandboxed.cc index b9a5a90..4da5abd 100644 --- a/oss-internship-2020/pffft/main_pffft_sandboxed.cc +++ b/oss-internship-2020/pffft/main_pffft_sandboxed.cc @@ -1,15 +1,14 @@ -#define GOOGLE_STRIP_LOG 1 - -#include #include -#include -#include #include #include #include #include #include +#include +#include +#include + #include "fftpack.h" #include "pffft_sapi.sapi.h" #include "sandboxed_api/util/flag.h" @@ -38,14 +37,12 @@ class pffftSapiSandbox : public pffftSandbox { } }; -double frand() { return rand() / (double)RAND_MAX; } - -double uclock_sec(void) { return (double)clock() / (double)CLOCKS_PER_SEC; } +double UclockSec(void) { return (double)clock() / (double)CLOCKS_PER_SEC; } int array_output_format = 0; -void show_output(const char* name, int N, int cplx, float flops, float t0, - float t1, int max_iter) { +void ShowOutput(const char* name, int N, int cplx, float flops, float t0, + float t1, int max_iter) { float mflops = flops / 1e6 / (t1 - t0 + 1e-16); if (array_output_format) { if (flops != -1) { @@ -85,39 +82,21 @@ int main(int argc, char* argv[]) { LOG(INFO) << "Initializing sandbox...\n"; pffftSapiSandbox sandbox; - sandbox.Init().IgnoreError(); + absl::Status init_status = sandbox.Init(); - LOG(INFO) << "Initialization: " << sandbox.Init().ToString().c_str() << "\n"; + LOG(INFO) << "Initialization: " << init_status.ToString().c_str() << "\n"; pffftApi api(&sandbox); - - int N, cplx; - - cplx = 0; + int cplx = 0; do { - for (i = 0; i < 23; i++) { - N = Nvalues[i]; - - int Nfloat = N * (cplx ? 2 : 1); + for (int N : Nvalues) { + const int Nfloat = N * (cplx ? 2 : 1); int Nbytes = Nfloat * sizeof(float); - int pass; - float ref[Nbytes], in[Nbytes], out[Nbytes], tmp[Nbytes], tmp2[Nbytes]; + float wrk[2 * Nfloat + 15 * sizeof(float)]; + sapi::v::Array wrk_(wrk, 2 * Nfloat + 15 * sizeof(float)); - sapi::v::Array ref_(ref, Nbytes); - sapi::v::Array in_(in, Nbytes); - sapi::v::Array out_(out, Nbytes); - sapi::v::Array tmp_(tmp, Nbytes); - sapi::v::Array tmp2_(tmp2, Nbytes); - - float wrk[2 * Nbytes + 15 * sizeof(float)]; - sapi::v::Array wrk_(wrk, 2 * Nbytes + 15 * sizeof(float)); - - float ref_max = 0; - int k; - - Nfloat = (cplx ? N * 2 : N); float X[Nbytes], Y[Nbytes], Z[Nbytes]; sapi::v::Array X_(X, Nbytes), Y_(Y, Nbytes), Z_(Z, Nbytes); @@ -127,7 +106,7 @@ int main(int argc, char* argv[]) { #ifdef __arm__ max_iter /= 4; #endif - int iter; + int iter, k; for (k = 0; k < Nfloat; ++k) { X[k] = 0; @@ -148,7 +127,7 @@ int main(int argc, char* argv[]) { } else { api.rffti(N, wrk_.PtrBoth()).IgnoreError(); } - t0 = uclock_sec(); + t0 = UclockSec(); for (iter = 0; iter < max_iter_; ++iter) { if (cplx) { @@ -159,11 +138,11 @@ int main(int argc, char* argv[]) { api.rfftb(N, X_.PtrBoth(), wrk_.PtrBoth()).IgnoreError(); } } - t1 = uclock_sec(); + t1 = UclockSec(); flops = (max_iter_ * 2) * ((cplx ? 5 : 2.5) * N * log((double)N) / M_LN2); - show_output("FFTPack", N, cplx, flops, t0, t1, max_iter_); + ShowOutput("FFTPack", N, cplx, flops, t0, t1, max_iter_); } /* @@ -173,12 +152,13 @@ int main(int argc, char* argv[]) { sapi::StatusOr s = api.pffft_new_setup(N, cplx ? PFFFT_COMPLEX : PFFFT_REAL); - LOG(INFO) << "Setup status is: " << s.status().ToString().c_str() << "\n"; + LOG(INFO) << "Setup status is: " << s.status().ToString().c_str() + << "\n"; if (s.ok()) { sapi::v::RemotePtr s_reg(s.value()); - t0 = uclock_sec(); + t0 = UclockSec(); for (iter = 0; iter < max_iter; ++iter) { api.pffft_transform(&s_reg, X_.PtrBoth(), Z_.PtrBoth(), Y_.PtrBoth(), PFFFT_FORWARD) @@ -188,12 +168,12 @@ int main(int argc, char* argv[]) { .IgnoreError(); } - t1 = uclock_sec(); + t1 = UclockSec(); api.pffft_destroy_setup(&s_reg).IgnoreError(); flops = (max_iter * 2) * ((cplx ? 5 : 2.5) * N * log((double)N) / M_LN2); - show_output("PFFFT", N, cplx, flops, t0, t1, max_iter); + ShowOutput("PFFFT", N, cplx, flops, t0, t1, max_iter); } LOG(INFO) << "N = " << N << " SUCCESSFULLY\n\n"; From 9f120598c170f84956a5cc8eacf4e22c0eb9b7cc Mon Sep 17 00:00:00 2001 From: Katarzyna Miernikiewicz Date: Wed, 26 Aug 2020 12:17:18 +0000 Subject: [PATCH 41/82] added requested changes --- .../openjpeg/examples/CMakeLists.txt | 2 +- .../openjpeg/examples/convert_helper.cc | 3 + .../openjpeg/examples/convert_helper.h | 3 - .../openjpeg/examples/decompress_example.cc | 91 ++++++------------- 4 files changed, 34 insertions(+), 65 deletions(-) diff --git a/oss-internship-2020/openjpeg/examples/CMakeLists.txt b/oss-internship-2020/openjpeg/examples/CMakeLists.txt index f85e392..3156472 100644 --- a/oss-internship-2020/openjpeg/examples/CMakeLists.txt +++ b/oss-internship-2020/openjpeg/examples/CMakeLists.txt @@ -13,7 +13,7 @@ # limitations under the License. # copy of one of the converting functions -# put here to omit the library's complex +# put here to omit the library's complex build logic add_library(convert_helper STATIC convert_helper.h convert_helper.cc diff --git a/oss-internship-2020/openjpeg/examples/convert_helper.cc b/oss-internship-2020/openjpeg/examples/convert_helper.cc index 6763d4b..7a80d9b 100644 --- a/oss-internship-2020/openjpeg/examples/convert_helper.cc +++ b/oss-internship-2020/openjpeg/examples/convert_helper.cc @@ -40,6 +40,9 @@ #include "convert_helper.h" +#define OPJ_TRUE 1 +#define OPJ_FALSE 0 + const char *opj_version(void) { return "2.3.1"; } static int are_comps_similar(opj_image_t *image) { diff --git a/oss-internship-2020/openjpeg/examples/convert_helper.h b/oss-internship-2020/openjpeg/examples/convert_helper.h index 517e212..b795261 100644 --- a/oss-internship-2020/openjpeg/examples/convert_helper.h +++ b/oss-internship-2020/openjpeg/examples/convert_helper.h @@ -3,9 +3,6 @@ #include "openjp2_sapi.sapi.h" -#define OPJ_TRUE 1 -#define OPJ_FALSE 0 - const char* opj_version(void); static int are_comps_similar(opj_image_t* image); int imagetopnm(opj_image_t* image, const char* outfile, int force_split); diff --git a/oss-internship-2020/openjpeg/examples/decompress_example.cc b/oss-internship-2020/openjpeg/examples/decompress_example.cc index 1c4e756..e710bce 100644 --- a/oss-internship-2020/openjpeg/examples/decompress_example.cc +++ b/oss-internship-2020/openjpeg/examples/decompress_example.cc @@ -27,8 +27,7 @@ class Openjp2SapiSandbox : public Openjp2Sandbox { public: - Openjp2SapiSandbox(const std::string in_file) - : in_file_(std::move(in_file)) {} + Openjp2SapiSandbox(std::string in_file) : in_file_(std::move(in_file)) {} std::unique_ptr ModifyPolicy( sandbox2::PolicyBuilder*) override { @@ -68,10 +67,7 @@ int main(int argc, char* argv[]) { // initialize sandbox Openjp2SapiSandbox sandbox(in_file); absl::Status status = sandbox.Init(); - if (!status.ok()) { - LOG(FATAL) << "sandbox initialization status: " << status; - return EXIT_FAILURE; - } + CHECK(status.ok()) << "sandbox initialization failed" << status; Openjp2Api api(&sandbox); sapi::v::ConstCStr in_file_v(in_file.c_str()); @@ -79,110 +75,83 @@ int main(int argc, char* argv[]) { // initialize library's main data-holders sapi::StatusOr stream = api.opj_stream_create_default_file_stream(in_file_v.PtrBefore(), 1); - if (!stream.ok()) { - LOG(FATAL) << "opj_stream initialization failed: " << stream.status(); - return EXIT_FAILURE; - } + CHECK(stream.ok()) << "opj_stream initialization failed: " << stream.status(); + sapi::v::RemotePtr stream_pointer(stream.value()); sapi::StatusOr codec = api.opj_create_decompress(OPJ_CODEC_JP2); - if (!codec.ok()) { - LOG(FATAL) << "opj_codec initialization failed: " << codec.status(); - return EXIT_FAILURE; - } + CHECK(codec.ok()) << "opj_codec initialization failed: " << stream.status(); sapi::v::RemotePtr codec_pointer(codec.value()); sapi::v::Struct parameters; status = api.opj_set_default_decoder_parameters(parameters.PtrBoth()); - if (!status.ok()) { - LOG(FATAL) << "parameters initialization failed: " << status; - return EXIT_FAILURE; - } + CHECK(status.ok()) << "parameters initialization failed" << status; sapi::StatusOr bool_status = api.opj_setup_decoder(&codec_pointer, parameters.PtrBefore()); - if (!bool_status.ok() || !bool_status.value()) { - LOG(FATAL) << "decoder setup failed"; - return EXIT_FAILURE; - } + CHECK(bool_status.ok() && bool_status.value()) << "decoder setup failed"; // start reading image from the input file sapi::v::GenericPtr image_pointer; bool_status = api.opj_read_header(&stream_pointer, &codec_pointer, image_pointer.PtrAfter()); - if (!bool_status.ok() || !bool_status.value()) { - LOG(FATAL) << "reading image header failed"; - return EXIT_FAILURE; - } + CHECK(bool_status.ok() && bool_status.value()) + << "reading image header failed"; sapi::v::Struct image; - image.SetRemote((void*)image_pointer.GetValue()); - if (!sandbox.TransferFromSandboxee(&image).ok()) { - LOG(FATAL) << "transfer from sandboxee failed"; - return EXIT_FAILURE; - } + image.SetRemote(reinterpret_cast(image_pointer.GetValue())); + CHECK(sandbox.TransferFromSandboxee(&image).ok()) + << "transfer from sandboxee failed"; - sapi::v::RemotePtr image_remote_pointer(image.GetRemote()); bool_status = - api.opj_decode(&codec_pointer, &stream_pointer, &image_remote_pointer); - if (!bool_status.ok() || !bool_status.value()) { - LOG(FATAL) << "decoding failed"; - return EXIT_FAILURE; - } + api.opj_decode(&codec_pointer, &stream_pointer, image.PtrAfter()); + CHECK(bool_status.ok() && bool_status.value()) << "decoding failed"; bool_status = api.opj_end_decompress(&codec_pointer, &stream_pointer); - if (!bool_status.ok() || !bool_status.value()) { - LOG(FATAL) << "ending decompress failed"; - return EXIT_FAILURE; - } + CHECK(bool_status.ok() && bool_status.value()) << "ending decompress failed"; int components = image.data().numcomps; // transfer the read data to the main process sapi::v::Array image_components(components); image_components.SetRemote(image.data().comps); - if (!sandbox.TransferFromSandboxee(&image_components).ok()) { - LOG(FATAL) << "transfer from sandboxee failed"; - return EXIT_FAILURE; - } + CHECK(sandbox.TransferFromSandboxee(&image_components).ok()) + << "transfer from sandboxee failed"; image.mutable_data()->comps = (opj_image_comp_t*)image_components.GetLocal(); - int width = (int)image.data().comps[0].w; - int height = (int)image.data().comps[0].h; + unsigned int width = reinterpret_cast(image.data().comps[0].w); + unsigned height = reinterpret_cast(image.data().comps[0].h); - std::vector> data; + std::vector> data(components); sapi::v::Array image_components_data(width * height); for (int i = 0; i < components; ++i) { image_components_data.SetRemote(image.data().comps[i].data); - if (!sandbox.TransferFromSandboxee(&image_components_data).ok()) { - LOG(FATAL) << "transfer from sandboxee failed"; - return EXIT_FAILURE; - } + CHECK(sandbox.TransferFromSandboxee(&image_components_data).ok()) + << "transfer from sandboxee failed"; + std::vector component_data( image_components_data.GetData(), image_components_data.GetData() + (width * height)); - data.push_back(component_data); - } - - for (int i = 0; i < components; ++i) { + data[i] = std::move(component_data); image_components[i].data = &data[i][0]; } // convert the image to the desired format and save it to the file - int error = imagetopnm((opj_image_t*)image.GetLocal(), argv[2], 0); - if (error) LOG(FATAL) << "image convert failed"; + int error = + imagetopnm(reinterpret_cast(image.GetLocal()), argv[2], 0); + CHECK(!error) << "image convert failed"; // cleanup status = api.opj_image_destroy(image.PtrNone()); - if (!status.ok()) LOG(FATAL) << "image destroy failed: " << status; + CHECK(status.ok()) << "image destroy failed" << status; status = api.opj_stream_destroy(&stream_pointer); - if (!status.ok()) LOG(FATAL) << "stream destroy failed: " << status; + CHECK(status.ok()) << "stream destroy failed" << status; status = api.opj_destroy_codec(&codec_pointer); - if (!status.ok()) LOG(FATAL) << "codec destroy failed: " << status; + CHECK(status.ok()) << "codec destroy failed" << status; return EXIT_SUCCESS; } From 139723d3b8ea803417ed1c654302d26991bbdbd0 Mon Sep 17 00:00:00 2001 From: doinachiroiu Date: Wed, 26 Aug 2020 14:18:31 +0000 Subject: [PATCH 42/82] Added LICENSE & coding changes required --- oss-internship-2020/pffft/CMakeLists.txt | 16 +++- oss-internship-2020/pffft/Makefile | 15 --- oss-internship-2020/pffft/README.md | 29 ++++-- .../{pffft_library_notes.txt => README.txt} | 0 .../pffft/main_pffft_sandboxed.cc | 93 ++++++++++--------- .../pffft/{main_pffft.c => test_pffft.c} | 0 6 files changed, 85 insertions(+), 68 deletions(-) delete mode 100644 oss-internship-2020/pffft/Makefile rename oss-internship-2020/pffft/{pffft_library_notes.txt => README.txt} (100%) rename oss-internship-2020/pffft/{main_pffft.c => test_pffft.c} (100%) diff --git a/oss-internship-2020/pffft/CMakeLists.txt b/oss-internship-2020/pffft/CMakeLists.txt index 6c7e776..442bb74 100644 --- a/oss-internship-2020/pffft/CMakeLists.txt +++ b/oss-internship-2020/pffft/CMakeLists.txt @@ -1,3 +1,17 @@ +# Copyright 2020 Google LLC +# +# 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. + cmake_minimum_required(VERSION 3.10) project(pffft CXX C) @@ -13,7 +27,7 @@ add_library(pffft STATIC ) add_executable(pffft_main - main_pffft.c + test_pffft.c ) target_link_libraries(pffft_main PRIVATE diff --git a/oss-internship-2020/pffft/Makefile b/oss-internship-2020/pffft/Makefile deleted file mode 100644 index 326fd90..0000000 --- a/oss-internship-2020/pffft/Makefile +++ /dev/null @@ -1,15 +0,0 @@ -CXXFLAGS ?= -std=c++17 - -pffft_main: test_pffft.o libpffft.a - $(CXX) -o $@ $(CXXFLAGS) $(LDFLAGS) $^ - -libpffft.a: pffft.o fftpack.o - ar rcs $@ $^ - -pffft.c: pffft.h - -fftpack.c: fftpack.h - -.PHONY: clean -clean: - rm -f *.o *.a pffft_main \ No newline at end of file diff --git a/oss-internship-2020/pffft/README.md b/oss-internship-2020/pffft/README.md index 8b8b2bd..6cef583 100644 --- a/oss-internship-2020/pffft/README.md +++ b/oss-internship-2020/pffft/README.md @@ -1,14 +1,23 @@ # Sandboxing PFFFT library -Builder: CMake +Build System: CMake OS: Linux +### Check out the PFFFT library & CMake set up +`mkdir -p build && cd build` + +`git clone https://bitbucket.org/jpommier/pffft.git` + +`cmake .. -G Ninja -DPFFFT_ROOT_DIR=$PWD` + +`ninja` + ### For testing: `cd build`, then `./pffft_sandboxed` ### For debug: -`SAPI_VLOG_LEVEL=1 ./pffft_sandboxed --v=100 ---sandbox2_danger_danger_permit_all_and_log ` +display custom info with +`./pffft_sandboxed --logtostderr` ## ***About the project*** *PFFFT library is concerned with 1D Fast-Fourier Transformations finding a @@ -38,22 +47,22 @@ it is taken into account while testing. In the end, the performance of PFFFT library it is outlined by the output.* #### CMake observations resume: - * linking pffft and fftpack (which contains necessary functions for pffft) - * set math library +* linking pffft and fftpack (which contains necessary functions for pffft) +* set math library #### Sandboxed main observations resume: - * containing two testing parts (fft / pffft benchmarks) - * showing the performance of the transformations implies +* containing two testing parts (fft / pffft benchmarks) +* showing the performance of the transformations implies testing them through various FFT dimenstions. Variable N, the input length, will take specific values meaning the number of points to which it is set the calculus (more details of mathematical purpose of N - https://en.wikipedia.org/wiki/Cooley%E2%80%93Tukey_FFT_algorithm). - * output shows speed depending on the input length +* output shows speed depending on the input length ### Bugs history - - [Solved] pffft benchmark bug: "Sandbox not active" + 1. [Solved] pffft benchmark bug: "Sandbox not active" N = 64, status OK, pffft_transform generates error N > 64, status not OK Problem on initialising sapi::StatusOr s; the memory that stays @@ -66,7 +75,7 @@ In the end, the performance of PFFFT library it is outlined by the output.* Solution: using "sapi::v::RemotePtr" instead of "sapi::v::GenericPtr" to access the memory of object s - - [Unresolved] compiling bug: "No space left on device" + 2. [Unresolved] compiling bug: "No space left on device" The building process creates some `embed` files that use lots of memory, trying to write them on /tmp. diff --git a/oss-internship-2020/pffft/pffft_library_notes.txt b/oss-internship-2020/pffft/README.txt similarity index 100% rename from oss-internship-2020/pffft/pffft_library_notes.txt rename to oss-internship-2020/pffft/README.txt diff --git a/oss-internship-2020/pffft/main_pffft_sandboxed.cc b/oss-internship-2020/pffft/main_pffft_sandboxed.cc index 4da5abd..8b7e6d1 100644 --- a/oss-internship-2020/pffft/main_pffft_sandboxed.cc +++ b/oss-internship-2020/pffft/main_pffft_sandboxed.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// 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. + #include #include #include @@ -17,7 +31,7 @@ ABSL_DECLARE_FLAG(string, sandbox2_danger_danger_permit_all); ABSL_DECLARE_FLAG(string, sandbox2_danger_danger_permit_all_and_log); -class pffftSapiSandbox : public pffftSandbox { +class PffftSapiSandbox : public pffftSandbox { public: std::unique_ptr ModifyPolicy( sandbox2::PolicyBuilder*) override { @@ -60,28 +74,26 @@ void ShowOutput(const char* name, int N, int cplx, float flops, float t0, } int main(int argc, char* argv[]) { - /* - * Initialize Google's logging library. - */ + + // Initialize Google's logging library. google::InitGoogleLogging(argv[0]); gflags::ParseCommandLineFlags(&argc, &argv, true); - /* - * Nvalues is a vector keeping the values by which iterates N, its value - * representing the input length. More concrete, N is the number of - * data points the caclulus is up to (determinating its accuracy). - * To show the performance of Fast-Fourier Transformations the program is - * testing for various values of N. - */ + + // Nvalues is a vector keeping the values by which iterates N, its value + // representing the input length. More concrete, N is the number of + // data points the caclulus is up to (determinating its accuracy). + // To show the performance of Fast-Fourier Transformations the program is + // testing for various values of N. int Nvalues[] = {64, 96, 128, 160, 192, 256, 384, 5 * 96, 512, 5 * 128, 3 * 256, 800, 1024, 2048, 2400, 4096, 8192, 9 * 1024, - 16384, 32768, 256 * 1024, 1024 * 1024, -1}; + 16384, 32768}; int i; LOG(INFO) << "Initializing sandbox...\n"; - pffftSapiSandbox sandbox; + PffftSapiSandbox sandbox; absl::Status init_status = sandbox.Init(); LOG(INFO) << "Initialization: " << init_status.ToString().c_str() << "\n"; @@ -112,13 +124,9 @@ int main(int argc, char* argv[]) { X[k] = 0; } - /* - * FFTPack benchmark - */ + // FFTPack benchmark { - /* - * SIMD_SZ == 4 (returning value of pffft_simd_size()) - */ + // SIMD_SZ == 4 (returning value of pffft_simd_size()) int max_iter_ = max_iter / 4; if (max_iter_ == 0) max_iter_ = 1; @@ -144,10 +152,8 @@ int main(int argc, char* argv[]) { (max_iter_ * 2) * ((cplx ? 5 : 2.5) * N * log((double)N) / M_LN2); ShowOutput("FFTPack", N, cplx, flops, t0, t1, max_iter_); } - - /* - * PFFFT benchmark - */ + + // PFFFT benchmark { sapi::StatusOr s = api.pffft_new_setup(N, cplx ? PFFFT_COMPLEX : PFFFT_REAL); @@ -155,27 +161,30 @@ int main(int argc, char* argv[]) { LOG(INFO) << "Setup status is: " << s.status().ToString().c_str() << "\n"; - if (s.ok()) { - sapi::v::RemotePtr s_reg(s.value()); - - t0 = UclockSec(); - for (iter = 0; iter < max_iter; ++iter) { - api.pffft_transform(&s_reg, X_.PtrBoth(), Z_.PtrBoth(), - Y_.PtrBoth(), PFFFT_FORWARD) - .IgnoreError(); - api.pffft_transform(&s_reg, X_.PtrBoth(), Z_.PtrBoth(), - Y_.PtrBoth(), PFFFT_FORWARD) - .IgnoreError(); - } - - t1 = UclockSec(); - api.pffft_destroy_setup(&s_reg).IgnoreError(); - - flops = - (max_iter * 2) * ((cplx ? 5 : 2.5) * N * log((double)N) / M_LN2); - ShowOutput("PFFFT", N, cplx, flops, t0, t1, max_iter); + if (!s.ok()) { + printf("Sandbox failed.\n"); + return 1; } + sapi::v::RemotePtr s_reg(s.value()); + + t0 = UclockSec(); + for (iter = 0; iter < max_iter; ++iter) { + api.pffft_transform(&s_reg, X_.PtrBoth(), Z_.PtrBoth(), + Y_.PtrBoth(), PFFFT_FORWARD) + .IgnoreError(); + api.pffft_transform(&s_reg, X_.PtrBoth(), Z_.PtrBoth(), + Y_.PtrBoth(), PFFFT_FORWARD) + .IgnoreError(); + } + + t1 = UclockSec(); + api.pffft_destroy_setup(&s_reg).IgnoreError(); + + flops = + (max_iter * 2) * ((cplx ? 5 : 2.5) * N * log((double)N) / M_LN2); + ShowOutput("PFFFT", N, cplx, flops, t0, t1, max_iter); + LOG(INFO) << "N = " << N << " SUCCESSFULLY\n\n"; } } diff --git a/oss-internship-2020/pffft/main_pffft.c b/oss-internship-2020/pffft/test_pffft.c similarity index 100% rename from oss-internship-2020/pffft/main_pffft.c rename to oss-internship-2020/pffft/test_pffft.c From b08726540a76c290e8c339e0f2b6bee65ddaf889 Mon Sep 17 00:00:00 2001 From: doinachiroiu Date: Wed, 26 Aug 2020 14:25:11 +0000 Subject: [PATCH 43/82] Added PFFFT submodule - master directory --- .gitmodules | 3 +++ oss-internship-2020/pffft/master | 1 + 2 files changed, 4 insertions(+) create mode 100644 .gitmodules create mode 160000 oss-internship-2020/pffft/master diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..793361c --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "oss-internship-2020/pffft/master"] + path = oss-internship-2020/pffft/master + url = https://bitbucket.org/jpommier/pffft/src/master/ diff --git a/oss-internship-2020/pffft/master b/oss-internship-2020/pffft/master new file mode 160000 index 0000000..74d7261 --- /dev/null +++ b/oss-internship-2020/pffft/master @@ -0,0 +1 @@ +Subproject commit 74d7261be17cf659d5930d4830609406bd7553e3 From c8d07aeaa59e5850d212644357af31a54f4787fb Mon Sep 17 00:00:00 2001 From: doinachiroiu Date: Wed, 26 Aug 2020 14:56:19 +0000 Subject: [PATCH 44/82] Changing paths for propertly usage of the submodule --- oss-internship-2020/pffft/CMakeLists.txt | 12 +- oss-internship-2020/pffft/README.md | 4 +- oss-internship-2020/pffft/README.txt | 416 --- oss-internship-2020/pffft/fftpack.c | 3112 ----------------- oss-internship-2020/pffft/fftpack.h | 799 ----- .../pffft/main_pffft_sandboxed.cc | 1 - oss-internship-2020/pffft/pffft.c | 1881 ---------- oss-internship-2020/pffft/pffft.h | 177 - oss-internship-2020/pffft/test_pffft.c | 419 --- 9 files changed, 8 insertions(+), 6813 deletions(-) delete mode 100644 oss-internship-2020/pffft/README.txt delete mode 100644 oss-internship-2020/pffft/fftpack.c delete mode 100644 oss-internship-2020/pffft/fftpack.h delete mode 100644 oss-internship-2020/pffft/pffft.c delete mode 100644 oss-internship-2020/pffft/pffft.h delete mode 100644 oss-internship-2020/pffft/test_pffft.c diff --git a/oss-internship-2020/pffft/CMakeLists.txt b/oss-internship-2020/pffft/CMakeLists.txt index 442bb74..c2a7bcd 100644 --- a/oss-internship-2020/pffft/CMakeLists.txt +++ b/oss-internship-2020/pffft/CMakeLists.txt @@ -20,14 +20,14 @@ set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED True) add_library(pffft STATIC - pffft.c - pffft.h - fftpack.c - fftpack.h + master/pffft.c + master/pffft.h + master/fftpack.c + master/fftpack.h ) add_executable(pffft_main - test_pffft.c + master/test_pffft.c ) target_link_libraries(pffft_main PRIVATE @@ -84,7 +84,7 @@ add_sapi_library(pffft_sapi sinti sint - INPUTS pffft.h fftpack.h + INPUTS master/pffft.h master/fftpack.h LIBRARY pffft LIBRARY_NAME pffft diff --git a/oss-internship-2020/pffft/README.md b/oss-internship-2020/pffft/README.md index 6cef583..2d2a9ca 100644 --- a/oss-internship-2020/pffft/README.md +++ b/oss-internship-2020/pffft/README.md @@ -4,9 +4,9 @@ Build System: CMake OS: Linux ### Check out the PFFFT library & CMake set up -`mkdir -p build && cd build` +`git submodule add https://bitbucket.org/jpommier/pffft.git` -`git clone https://bitbucket.org/jpommier/pffft.git` +`mkdir -p build && cd build` `cmake .. -G Ninja -DPFFFT_ROOT_DIR=$PWD` diff --git a/oss-internship-2020/pffft/README.txt b/oss-internship-2020/pffft/README.txt deleted file mode 100644 index ee20b42..0000000 --- a/oss-internship-2020/pffft/README.txt +++ /dev/null @@ -1,416 +0,0 @@ -PFFFT: a pretty fast FFT. - -TL;DR --- - -PFFFT does 1D Fast Fourier Transforms, of single precision real and -complex vectors. It tries do it fast, it tries to be correct, and it -tries to be small. Computations do take advantage of SSE1 instructions -on x86 cpus, Altivec on powerpc cpus, and NEON on ARM cpus. The -license is BSD-like. - - -Why does it exist: --- - -I was in search of a good performing FFT library , preferably very -small and with a very liberal license. - -When one says "fft library", FFTW ("Fastest Fourier Transform in the -West") is probably the first name that comes to mind -- I guess that -99% of open-source projects that need a FFT do use FFTW, and are happy -with it. However, it is quite a large library , which does everything -fft related (2d transforms, 3d transforms, other transformations such -as discrete cosine , or fast hartley). And it is licensed under the -GNU GPL , which means that it cannot be used in non open-source -products. - -An alternative to FFTW that is really small, is the venerable FFTPACK -v4, which is available on NETLIB. A more recent version (v5) exists, -but it is larger as it deals with multi-dimensional transforms. This -is a library that is written in FORTRAN 77, a language that is now -considered as a bit antiquated by many. FFTPACKv4 was written in 1985, -by Dr Paul Swarztrauber of NCAR, more than 25 years ago ! And despite -its age, benchmarks show it that it still a very good performing FFT -library, see for example the 1d single precision benchmarks here: -http://www.fftw.org/speed/opteron-2.2GHz-32bit/ . It is however not -competitive with the fastest ones, such as FFTW, Intel MKL, AMD ACML, -Apple vDSP. The reason for that is that those libraries do take -advantage of the SSE SIMD instructions available on Intel CPUs, -available since the days of the Pentium III. These instructions deal -with small vectors of 4 floats at a time, instead of a single float -for a traditionnal FPU, so when using these instructions one may expect -a 4-fold performance improvement. - -The idea was to take this fortran fftpack v4 code, translate to C, -modify it to deal with those SSE instructions, and check that the -final performance is not completely ridiculous when compared to other -SIMD FFT libraries. Translation to C was performed with f2c ( -http://www.netlib.org/f2c/ ). The resulting file was a bit edited in -order to remove the thousands of gotos that were introduced by -f2c. You will find the fftpack.h and fftpack.c sources in the -repository, this a complete translation of -http://www.netlib.org/fftpack/ , with the discrete cosine transform -and the test program. There is no license information in the netlib -repository, but it was confirmed to me by the fftpack v5 curators that -the same terms do apply to fftpack v4: -http://www.cisl.ucar.edu/css/software/fftpack5/ftpk.html . This is a -"BSD-like" license, it is compatible with proprietary projects. - -Adapting fftpack to deal with the SIMD 4-element vectors instead of -scalar single precision numbers was more complex than I originally -thought, especially with the real transforms, and I ended up writing -more code than I planned.. - - -The code: --- - -Only two files, in good old C, pffft.c and pffft.h . The API is very -very simple, just make sure that you read the comments in pffft.h. - - -Comparison with other FFTs: --- - -The idea was not to break speed records, but to get a decently fast -fft that is at least 50% as fast as the fastest FFT -- especially on -slowest computers . I'm more focused on getting the best performance -on slow cpus (Atom, Intel Core 1, old Athlons, ARM Cortex-A9...), than -on getting top performance on today fastest cpus. - -It can be used in a real-time context as the fft functions do not -perform any memory allocation -- that is why they accept a 'work' -array in their arguments. - -It is also a bit focused on performing 1D convolutions, that is why it -provides "unordered" FFTs , and a fourier domain convolution -operation. - - -Benchmark results (cpu tested: core i7 2600, core 2 quad, core 1 duo, atom N270, cortex-A9, cortex-A15, A8X) --- - -The benchmark shows the performance of various fft implementations measured in -MFlops, with the number of floating point operations being defined as 5Nlog2(N) -for a length N complex fft, and 2.5*Nlog2(N) for a real fft. -See http://www.fftw.org/speed/method.html for an explanation of these formulas. - -MacOS Lion, gcc 4.2, 64-bit, fftw 3.3 on a 3.4 GHz core i7 2600 - -Built with: - - gcc-4.2 -o test_pffft -arch x86_64 -O3 -Wall -W pffft.c test_pffft.c fftpack.c -L/usr/local/lib -I/usr/local/include/ -DHAVE_VECLIB -framework veclib -DHAVE_FFTW -lfftw3f - -| input len |real FFTPack| real vDSP | real FFTW | real PFFFT | |cplx FFTPack| cplx vDSP | cplx FFTW | cplx PFFFT | -|-----------+------------+------------+------------+------------| |------------+------------+------------+------------| -| 64 | 2816 | 8596 | 7329 | 8187 | | 2887 | 14898 | 14668 | 11108 | -| 96 | 3298 | n/a | 8378 | 7727 | | 3953 | n/a | 15680 | 10878 | -| 128 | 3507 | 11575 | 9266 | 10108 | | 4233 | 17598 | 16427 | 12000 | -| 160 | 3391 | n/a | 9838 | 10711 | | 4220 | n/a | 16653 | 11187 | -| 192 | 3919 | n/a | 9868 | 10956 | | 4297 | n/a | 15770 | 12540 | -| 256 | 4283 | 13179 | 10694 | 13128 | | 4545 | 19550 | 16350 | 13822 | -| 384 | 3136 | n/a | 10810 | 12061 | | 3600 | n/a | 16103 | 13240 | -| 480 | 3477 | n/a | 10632 | 12074 | | 3536 | n/a | 11630 | 12522 | -| 512 | 3783 | 15141 | 11267 | 13838 | | 3649 | 20002 | 16560 | 13580 | -| 640 | 3639 | n/a | 11164 | 13946 | | 3695 | n/a | 15416 | 13890 | -| 768 | 3800 | n/a | 11245 | 13495 | | 3590 | n/a | 15802 | 14552 | -| 800 | 3440 | n/a | 10499 | 13301 | | 3659 | n/a | 12056 | 13268 | -| 1024 | 3924 | 15605 | 11450 | 15339 | | 3769 | 20963 | 13941 | 15467 | -| 2048 | 4518 | 16195 | 11551 | 15532 | | 4258 | 20413 | 13723 | 15042 | -| 2400 | 4294 | n/a | 10685 | 13078 | | 4093 | n/a | 12777 | 13119 | -| 4096 | 4750 | 16596 | 11672 | 15817 | | 4157 | 19662 | 14316 | 14336 | -| 8192 | 3820 | 16227 | 11084 | 12555 | | 3691 | 18132 | 12102 | 13813 | -| 9216 | 3864 | n/a | 10254 | 12870 | | 3586 | n/a | 12119 | 13994 | -| 16384 | 3822 | 15123 | 10454 | 12822 | | 3613 | 16874 | 12370 | 13881 | -| 32768 | 4175 | 14512 | 10662 | 11095 | | 3881 | 14702 | 11619 | 11524 | -| 262144 | 3317 | 11429 | 6269 | 9517 | | 2810 | 11729 | 7757 | 10179 | -| 1048576 | 2913 | 10551 | 4730 | 5867 | | 2661 | 7881 | 3520 | 5350 | -|-----------+------------+------------+------------+------------| |------------+------------+------------+------------| - - -Debian 6, gcc 4.4.5, 64-bit, fftw 3.3.1 on a 3.4 GHz core i7 2600 - -Built with: -gcc -o test_pffft -DHAVE_FFTW -msse2 -O3 -Wall -W pffft.c test_pffft.c fftpack.c -L$HOME/local/lib -I$HOME/local/include/ -lfftw3f -lm - -| N (input length) | real FFTPack | real FFTW | real PFFFT | | cplx FFTPack | cplx FFTW | cplx PFFFT | -|------------------+--------------+--------------+--------------| |--------------+--------------+--------------| -| 64 | 3840 | 7680 | 8777 | | 4389 | 20480 | 11171 | -| 96 | 4214 | 9633 | 8429 | | 4816 | 22477 | 11238 | -| 128 | 3584 | 10240 | 10240 | | 5120 | 23893 | 11947 | -| 192 | 4854 | 11095 | 12945 | | 4854 | 22191 | 14121 | -| 256 | 4096 | 11703 | 16384 | | 5120 | 23406 | 13653 | -| 384 | 4395 | 14651 | 12558 | | 4884 | 19535 | 14651 | -| 512 | 5760 | 13166 | 15360 | | 4608 | 23040 | 15360 | -| 768 | 4907 | 14020 | 16357 | | 4461 | 19628 | 14020 | -| 1024 | 5120 | 14629 | 14629 | | 5120 | 20480 | 15754 | -| 2048 | 5632 | 14080 | 18773 | | 4693 | 12516 | 16091 | -| 4096 | 5120 | 13653 | 17554 | | 4726 | 7680 | 14456 | -| 8192 | 4160 | 7396 | 13312 | | 4437 | 14791 | 13312 | -| 9216 | 4210 | 6124 | 13473 | | 4491 | 7282 | 14970 | -| 16384 | 3976 | 11010 | 14313 | | 4210 | 11450 | 13631 | -| 32768 | 4260 | 10224 | 10954 | | 4260 | 6816 | 11797 | -| 262144 | 3736 | 6896 | 9961 | | 2359 | 8965 | 9437 | -| 1048576 | 2796 | 4534 | 6453 | | 1864 | 3078 | 5592 | -|------------------+--------------+--------------+--------------| |--------------+--------------+--------------| - - - -MacOS Snow Leopard, gcc 4.0, 32-bit, fftw 3.3 on a 1.83 GHz core 1 duo - -Built with: - - gcc -o test_pffft -DHAVE_FFTW -DHAVE_VECLIB -O3 -Wall -W pffft.c test_pffft.c fftpack.c -L/usr/local/lib -I/usr/local/include/ -lfftw3f -framework veclib - -| input len |real FFTPack| real vDSP | real FFTW | real PFFFT | |cplx FFTPack| cplx vDSP | cplx FFTW | cplx PFFFT | -|-----------+------------+------------+------------+------------| |------------+------------+------------+------------| -| 64 | 745 | 2145 | 1706 | 2028 | | 961 | 3356 | 3313 | 2300 | -| 96 | 877 | n/a | 1976 | 1978 | | 1059 | n/a | 3333 | 2233 | -| 128 | 951 | 2808 | 2213 | 2279 | | 1202 | 3803 | 3739 | 2494 | -| 192 | 1002 | n/a | 2456 | 2429 | | 1186 | n/a | 3701 | 2508 | -| 256 | 1065 | 3205 | 2641 | 2793 | | 1302 | 4013 | 3912 | 2663 | -| 384 | 845 | n/a | 2759 | 2499 | | 948 | n/a | 3729 | 2504 | -| 512 | 900 | 3476 | 2956 | 2759 | | 974 | 4057 | 3954 | 2645 | -| 768 | 910 | n/a | 2912 | 2737 | | 975 | n/a | 3837 | 2614 | -| 1024 | 936 | 3583 | 3107 | 3009 | | 1006 | 4124 | 3821 | 2697 | -| 2048 | 1057 | 3585 | 3091 | 2837 | | 1089 | 3889 | 3701 | 2513 | -| 4096 | 1083 | 3524 | 3092 | 2733 | | 1039 | 3617 | 3462 | 2364 | -| 8192 | 874 | 3252 | 2967 | 2363 | | 911 | 3106 | 2789 | 2302 | -| 9216 | 898 | n/a | 2420 | 2290 | | 865 | n/a | 2676 | 2204 | -| 16384 | 903 | 2892 | 2506 | 2421 | | 899 | 3026 | 2797 | 2289 | -| 32768 | 965 | 2837 | 2550 | 2358 | | 920 | 2922 | 2763 | 2240 | -| 262144 | 738 | 2422 | 1589 | 1708 | | 610 | 2038 | 1436 | 1091 | -| 1048576 | 528 | 1207 | 845 | 880 | | 606 | 1020 | 669 | 1036 | -|-----------+------------+------------+------------+------------| |------------+------------+------------+------------| - - - -Ubuntu 11.04, gcc 4.5, 32-bit, fftw 3.2 on a 2.66 core 2 quad - -Built with: -gcc -o test_pffft -DHAVE_FFTW -msse -mfpmath=sse -O3 -Wall -W pffft.c test_pffft.c fftpack.c -L/usr/local/lib -I/usr/local/include/ -lfftw3f -lm - -| input len |real FFTPack| real FFTW | real PFFFT | |cplx FFTPack| cplx FFTW | cplx PFFFT | -|-----------+------------+------------+------------| |------------+------------+------------| -| 64 | 1920 | 3614 | 5120 | | 2194 | 7680 | 6467 | -| 96 | 1873 | 3549 | 5187 | | 2107 | 8429 | 5863 | -| 128 | 2240 | 3773 | 5514 | | 2560 | 7964 | 6827 | -| 192 | 1765 | 4569 | 7767 | | 2284 | 9137 | 7061 | -| 256 | 2048 | 5461 | 7447 | | 2731 | 9638 | 7802 | -| 384 | 1998 | 5861 | 6762 | | 2313 | 9253 | 7644 | -| 512 | 2095 | 6144 | 7680 | | 2194 | 10240 | 7089 | -| 768 | 2230 | 5773 | 7549 | | 2045 | 10331 | 7010 | -| 1024 | 2133 | 6400 | 8533 | | 2133 | 10779 | 7877 | -| 2048 | 2011 | 7040 | 8665 | | 1942 | 10240 | 7768 | -| 4096 | 2194 | 6827 | 8777 | | 1755 | 9452 | 6827 | -| 8192 | 1849 | 6656 | 6656 | | 1752 | 7831 | 6827 | -| 9216 | 1871 | 5858 | 6416 | | 1643 | 6909 | 6266 | -| 16384 | 1883 | 6223 | 6506 | | 1664 | 7340 | 6982 | -| 32768 | 1826 | 6390 | 6667 | | 1631 | 7481 | 6971 | -| 262144 | 1546 | 4075 | 5977 | | 1299 | 3415 | 3551 | -| 1048576 | 1104 | 2071 | 1730 | | 1104 | 1149 | 1834 | -|-----------+------------+------------+------------| |------------+------------+------------| - - - -Ubuntu 11.04, gcc 4.5, 32-bit, fftw 3.3 on a 1.6 GHz Atom N270 - -Built with: -gcc -o test_pffft -DHAVE_FFTW -msse -mfpmath=sse -O3 -Wall -W pffft.c test_pffft.c fftpack.c -L/usr/local/lib -I/usr/local/include/ -lfftw3f -lm - -| N (input length) | real FFTPack | real FFTW | real PFFFT | | cplx FFTPack | cplx FFTW | cplx PFFFT | -|------------------+--------------+--------------+--------------| |--------------+--------------+--------------| -| 64 | 452 | 1041 | 1336 | | 549 | 2318 | 1781 | -| 96 | 444 | 1297 | 1297 | | 503 | 2408 | 1686 | -| 128 | 527 | 1525 | 1707 | | 543 | 2655 | 1886 | -| 192 | 498 | 1653 | 1849 | | 539 | 2678 | 1942 | -| 256 | 585 | 1862 | 2156 | | 594 | 2777 | 2244 | -| 384 | 499 | 1870 | 1998 | | 511 | 2586 | 1890 | -| 512 | 562 | 2095 | 2194 | | 542 | 2973 | 2194 | -| 768 | 545 | 2045 | 2133 | | 545 | 2365 | 2133 | -| 1024 | 595 | 2133 | 2438 | | 569 | 2695 | 2179 | -| 2048 | 587 | 2125 | 2347 | | 521 | 2230 | 1707 | -| 4096 | 495 | 1890 | 1834 | | 492 | 1876 | 1672 | -| 8192 | 469 | 1548 | 1729 | | 438 | 1740 | 1664 | -| 9216 | 468 | 1663 | 1663 | | 446 | 1585 | 1531 | -| 16384 | 453 | 1608 | 1767 | | 398 | 1476 | 1664 | -| 32768 | 456 | 1420 | 1503 | | 387 | 1388 | 1345 | -| 262144 | 309 | 385 | 726 | | 262 | 415 | 840 | -| 1048576 | 280 | 351 | 739 | | 261 | 313 | 797 | -|------------------+--------------+--------------+--------------| |--------------+--------------+--------------| - - - -Windows 7, visual c++ 2010 on a 1.6 GHz Atom N270 - -Built with: -cl /Ox -D_USE_MATH_DEFINES /arch:SSE test_pffft.c pffft.c fftpack.c - -(visual c++ is definitively not very good with SSE intrinsics...) - -| N (input length) | real FFTPack | real PFFFT | | cplx FFTPack | cplx PFFFT | -|------------------+--------------+--------------| |--------------+--------------| -| 64 | 173 | 1009 | | 174 | 1159 | -| 96 | 169 | 1029 | | 188 | 1201 | -| 128 | 195 | 1242 | | 191 | 1275 | -| 192 | 178 | 1312 | | 184 | 1276 | -| 256 | 196 | 1591 | | 186 | 1281 | -| 384 | 172 | 1409 | | 181 | 1281 | -| 512 | 187 | 1640 | | 181 | 1313 | -| 768 | 171 | 1614 | | 176 | 1258 | -| 1024 | 186 | 1812 | | 178 | 1223 | -| 2048 | 190 | 1707 | | 186 | 1099 | -| 4096 | 182 | 1446 | | 177 | 975 | -| 8192 | 175 | 1345 | | 169 | 1034 | -| 9216 | 165 | 1271 | | 168 | 1023 | -| 16384 | 166 | 1396 | | 165 | 949 | -| 32768 | 172 | 1311 | | 161 | 881 | -| 262144 | 136 | 632 | | 134 | 629 | -| 1048576 | 134 | 698 | | 127 | 623 | -|------------------+--------------+--------------| |--------------+--------------| - - - -Ubuntu 12.04, gcc-4.7.3, 32-bit, with fftw 3.3.3 (built with --enable-neon), on a 1.2GHz ARM Cortex A9 (Tegra 3) - -Built with: -gcc-4.7 -O3 -DHAVE_FFTW -march=armv7-a -mtune=cortex-a9 -mfloat-abi=hard -mfpu=neon -ffast-math test_pffft.c pffft.c -o test_pffft_arm fftpack.c -lm -I/usr/local/include/ -L/usr/local/lib/ -lfftw3f - -| input len |real FFTPack| real FFTW | real PFFFT | |cplx FFTPack| cplx FFTW | cplx PFFFT | -|-----------+------------+------------+------------| |------------+------------+------------| -| 64 | 549 | 452 | 731 | | 512 | 602 | 640 | -| 96 | 421 | 272 | 702 | | 496 | 571 | 602 | -| 128 | 498 | 512 | 815 | | 597 | 618 | 652 | -| 160 | 521 | 536 | 815 | | 586 | 669 | 625 | -| 192 | 539 | 571 | 883 | | 485 | 597 | 626 | -| 256 | 640 | 539 | 975 | | 569 | 611 | 671 | -| 384 | 499 | 610 | 879 | | 499 | 602 | 637 | -| 480 | 518 | 507 | 877 | | 496 | 661 | 616 | -| 512 | 524 | 591 | 1002 | | 549 | 678 | 668 | -| 640 | 542 | 612 | 955 | | 568 | 663 | 645 | -| 768 | 557 | 613 | 981 | | 491 | 663 | 598 | -| 800 | 514 | 353 | 882 | | 514 | 360 | 574 | -| 1024 | 640 | 640 | 1067 | | 492 | 683 | 602 | -| 2048 | 587 | 640 | 908 | | 486 | 640 | 552 | -| 2400 | 479 | 368 | 777 | | 422 | 376 | 518 | -| 4096 | 511 | 614 | 853 | | 426 | 640 | 534 | -| 8192 | 415 | 584 | 708 | | 386 | 622 | 516 | -| 9216 | 419 | 571 | 687 | | 364 | 586 | 506 | -| 16384 | 426 | 577 | 716 | | 398 | 606 | 530 | -| 32768 | 417 | 572 | 673 | | 399 | 572 | 468 | -| 262144 | 219 | 380 | 293 | | 255 | 431 | 343 | -| 1048576 | 202 | 274 | 237 | | 265 | 282 | 355 | -|-----------+------------+------------+------------| |------------+------------+------------| - -Same platform as above, but this time pffft and fftpack are built with clang 3.2: - -clang -O3 -DHAVE_FFTW -march=armv7-a -mtune=cortex-a9 -mfloat-abi=hard -mfpu=neon -ffast-math test_pffft.c pffft.c -o test_pffft_arm fftpack.c -lm -I/usr/local/include/ -L/usr/local/lib/ -lfftw3f - -| input len |real FFTPack| real FFTW | real PFFFT | |cplx FFTPack| cplx FFTW | cplx PFFFT | -|-----------+------------+------------+------------| |------------+------------+------------| -| 64 | 427 | 452 | 853 | | 427 | 602 | 1024 | -| 96 | 351 | 276 | 843 | | 337 | 571 | 963 | -| 128 | 373 | 512 | 996 | | 390 | 618 | 1054 | -| 160 | 426 | 536 | 987 | | 375 | 669 | 914 | -| 192 | 404 | 571 | 1079 | | 388 | 588 | 1079 | -| 256 | 465 | 539 | 1205 | | 445 | 602 | 1170 | -| 384 | 366 | 610 | 1099 | | 343 | 594 | 1099 | -| 480 | 356 | 507 | 1140 | | 335 | 651 | 931 | -| 512 | 411 | 591 | 1213 | | 384 | 649 | 1124 | -| 640 | 398 | 612 | 1193 | | 373 | 654 | 901 | -| 768 | 409 | 613 | 1227 | | 383 | 663 | 1044 | -| 800 | 411 | 348 | 1073 | | 353 | 358 | 809 | -| 1024 | 427 | 640 | 1280 | | 413 | 692 | 1004 | -| 2048 | 414 | 626 | 1126 | | 371 | 640 | 853 | -| 2400 | 399 | 373 | 898 | | 319 | 368 | 653 | -| 4096 | 404 | 602 | 1059 | | 357 | 633 | 778 | -| 8192 | 332 | 584 | 792 | | 308 | 616 | 716 | -| 9216 | 322 | 561 | 783 | | 299 | 586 | 687 | -| 16384 | 344 | 568 | 778 | | 314 | 617 | 745 | -| 32768 | 342 | 564 | 737 | | 314 | 552 | 629 | -| 262144 | 201 | 383 | 313 | | 227 | 435 | 413 | -| 1048576 | 187 | 262 | 251 | | 228 | 281 | 409 | -|-----------+------------+------------+------------| |------------+------------+------------| - -So it looks like, on ARM, gcc 4.7 is the best at scalar floating point -(the fftpack performance numbers are better with gcc), while clang is -the best with neon intrinsics (see how pffft perf has improved with -clang 3.2). - - -NVIDIA Jetson TK1 board, gcc-4.8.2. The cpu is a 2.3GHz cortex A15 (Tegra K1). - -Built with: -gcc -O3 -march=armv7-a -mtune=native -mfloat-abi=hard -mfpu=neon -ffast-math test_pffft.c pffft.c -o test_pffft_arm fftpack.c -lm - -| input len |real FFTPack| real PFFFT | |cplx FFTPack| cplx PFFFT | -|-----------+------------+------------| |------------+------------| -| 64 | 1735 | 3308 | | 1994 | 3744 | -| 96 | 1596 | 3448 | | 1987 | 3572 | -| 128 | 1807 | 4076 | | 2255 | 3960 | -| 160 | 1769 | 4083 | | 2071 | 3845 | -| 192 | 1990 | 4233 | | 2017 | 3939 | -| 256 | 2191 | 4882 | | 2254 | 4346 | -| 384 | 1878 | 4492 | | 2073 | 4012 | -| 480 | 1748 | 4398 | | 1923 | 3951 | -| 512 | 2030 | 5064 | | 2267 | 4195 | -| 640 | 1918 | 4756 | | 2094 | 4184 | -| 768 | 2099 | 4907 | | 2048 | 4297 | -| 800 | 1822 | 4555 | | 1880 | 4063 | -| 1024 | 2232 | 5355 | | 2187 | 4420 | -| 2048 | 2176 | 4983 | | 2027 | 3602 | -| 2400 | 1741 | 4256 | | 1710 | 3344 | -| 4096 | 1816 | 3914 | | 1851 | 3349 | -| 8192 | 1716 | 3481 | | 1700 | 3255 | -| 9216 | 1735 | 3589 | | 1653 | 3094 | -| 16384 | 1567 | 3483 | | 1637 | 3244 | -| 32768 | 1624 | 3240 | | 1655 | 3156 | -| 262144 | 1012 | 1898 | | 983 | 1503 | -| 1048576 | 876 | 1154 | | 868 | 1341 | -|-----------+------------+------------| |------------+------------| - -The performance on the tegra K1 is pretty impressive. I'm not -including the FFTW numbers as they as slightly below the scalar -fftpack numbers, so something must be wrong (however it seems to be -correctly configured and is using neon simd instructions). - -When using clang 3.4 the pffft version is even a bit faster, reaching -5.7 GFlops for real ffts of size 1024. - - -iPad Air 2 with iOS9, xcode 8.0, arm64. The cpu is an Apple A8X, supposedly running at 1.5GHz. - -| input len |real FFTPack| real vDSP | real PFFFT | |cplx FFTPack| cplx vDSP | cplx PFFFT | -|-----------+------------+------------+------------| |------------+------------+------------| -| 64 | 2517 | 7995 | 6086 | | 2725 | 13006 | 8495 | -| 96 | 2442 | n/a | 6691 | | 2256 | n/a | 7991 | -| 128 | 2664 | 10186 | 7877 | | 2575 | 15115 | 9115 | -| 160 | 2638 | n/a | 8283 | | 2682 | n/a | 8806 | -| 192 | 2903 | n/a | 9083 | | 2634 | n/a | 8980 | -| 256 | 3184 | 11452 | 10039 | | 3026 | 15410 | 10199 | -| 384 | 2665 | n/a | 10100 | | 2275 | n/a | 9247 | -| 480 | 2546 | n/a | 9863 | | 2341 | n/a | 8892 | -| 512 | 2832 | 12197 | 10989 | | 2547 | 16768 | 10154 | -| 640 | 2755 | n/a | 10461 | | 2569 | n/a | 9666 | -| 768 | 2998 | n/a | 11355 | | 2585 | n/a | 9813 | -| 800 | 2516 | n/a | 10332 | | 2433 | n/a | 9164 | -| 1024 | 3109 | 12965 | 12114 | | 2869 | 16448 | 10519 | -| 2048 | 3027 | 12996 | 12023 | | 2648 | 17304 | 10307 | -| 2400 | 2515 | n/a | 10372 | | 2355 | n/a | 8443 | -| 4096 | 3204 | 13603 | 12359 | | 2814 | 16570 | 9780 | -| 8192 | 2759 | 13422 | 10824 | | 2153 | 15652 | 7884 | -| 9216 | 2700 | n/a | 9938 | | 2241 | n/a | 7900 | -| 16384 | 2280 | 13057 | 7976 | | 593 | 4272 | 2534 | -| 32768 | 768 | 4269 | 2882 | | 606 | 4405 | 2604 | -| 262144 | 724 | 3527 | 2630 | | 534 | 2418 | 2157 | -| 1048576 | 674 | 1467 | 2135 | | 530 | 1621 | 2055 | -|-----------+------------+------------+------------| |------------+------------+------------| - -I double-checked to make sure I did not make a mistake in the time -measurements, as the numbers are much higher than what I initially -expected. They are in fact higher than the number I get on the 2.8GHz -Xeon of my 2008 mac pro.. (except for FFT lengths >= 32768 where -having a big cache is useful). A good surprise is also that the perf -is not too far from apple's vDSP (at least for the real FFT). - diff --git a/oss-internship-2020/pffft/fftpack.c b/oss-internship-2020/pffft/fftpack.c deleted file mode 100644 index b6375a8..0000000 --- a/oss-internship-2020/pffft/fftpack.c +++ /dev/null @@ -1,3112 +0,0 @@ -/* - compile with cc -DTESTING_FFTPACK fftpack.c in order to build the - test application. - - This is an f2c translation of the full fftpack sources as found on - http://www.netlib.org/fftpack/ The translated code has been - slightlty edited to remove the ugliest artefacts of the translation - (a hundred of wild GOTOs were wiped during that operation). - - The original fftpack file was written by Paul N. Swarztrauber - (Version 4, 1985), in fortran 77. - - FFTPACK license: - - http://www.cisl.ucar.edu/css/software/fftpack5/ftpk.html - - Copyright (c) 2004 the University Corporation for Atmospheric - Research ("UCAR"). All rights reserved. Developed by NCAR's - Computational and Information Systems Laboratory, UCAR, - www.cisl.ucar.edu. - - Redistribution and use of the Software in source and binary forms, - with or without modification, is permitted provided that the - following conditions are met: - - - Neither the names of NCAR's Computational and Information Systems - Laboratory, the University Corporation for Atmospheric Research, - nor the names of its sponsors or contributors may be used to - endorse or promote products derived from this Software without - specific prior written permission. - - - Redistributions of source code must retain the above copyright - notices, this list of conditions, and the disclaimer below. - - - Redistributions in binary form must reproduce the above copyright - notice, this list of conditions, and the disclaimer below in the - documentation and/or other materials provided with the - distribution. - - THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT - HOLDERS BE LIABLE FOR ANY CLAIM, INDIRECT, INCIDENTAL, SPECIAL, - EXEMPLARY, OR CONSEQUENTIAL DAMAGES OR OTHER LIABILITY, WHETHER IN AN - ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE - SOFTWARE. - - ChangeLog: - 2011/10/02: this is my first release of this file. -*/ - -#include "fftpack.h" -#include - -typedef fftpack_real real; -typedef fftpack_int integer; - -typedef struct f77complex { - real r, i; -} f77complex; - -#ifdef TESTING_FFTPACK -static real c_abs(f77complex *c) { return sqrt(c->r*c->r + c->i*c->i); } -static double dmax(double a, double b) { return a < b ? b : a; } -#endif - -/* translated by f2c (version 20061008), and slightly edited */ - -static void passfb(integer *nac, integer ido, integer ip, integer l1, integer idl1, - real *cc, real *c1, real *c2, real *ch, real *ch2, const real *wa, real fsign) -{ - /* System generated locals */ - integer ch_offset, cc_offset, - c1_offset, c2_offset, ch2_offset; - - /* Local variables */ - integer i, j, k, l, jc, lc, ik, idj, idl, inc, idp; - real wai, war; - integer ipp2, idij, idlj, idot, ipph; - - -#define c1_ref(a_1,a_2,a_3) c1[((a_3)*l1 + (a_2))*ido + a_1] -#define c2_ref(a_1,a_2) c2[(a_2)*idl1 + a_1] -#define cc_ref(a_1,a_2,a_3) cc[((a_3)*ip + (a_2))*ido + a_1] -#define ch_ref(a_1,a_2,a_3) ch[((a_3)*l1 + (a_2))*ido + a_1] -#define ch2_ref(a_1,a_2) ch2[(a_2)*idl1 + a_1] - - /* Parameter adjustments */ - ch_offset = 1 + ido * (1 + l1); - ch -= ch_offset; - c1_offset = 1 + ido * (1 + l1); - c1 -= c1_offset; - cc_offset = 1 + ido * (1 + ip); - cc -= cc_offset; - ch2_offset = 1 + idl1; - ch2 -= ch2_offset; - c2_offset = 1 + idl1; - c2 -= c2_offset; - --wa; - - /* Function Body */ - idot = ido / 2; - ipp2 = ip + 2; - ipph = (ip + 1) / 2; - idp = ip * ido; - - if (ido >= l1) { - for (j = 2; j <= ipph; ++j) { - jc = ipp2 - j; - for (k = 1; k <= l1; ++k) { - for (i = 1; i <= ido; ++i) { - ch_ref(i, k, j) = cc_ref(i, j, k) + cc_ref(i, jc, k); - ch_ref(i, k, jc) = cc_ref(i, j, k) - cc_ref(i, jc, k); - } - } - } - for (k = 1; k <= l1; ++k) { - for (i = 1; i <= ido; ++i) { - ch_ref(i, k, 1) = cc_ref(i, 1, k); - } - } - } else { - for (j = 2; j <= ipph; ++j) { - jc = ipp2 - j; - for (i = 1; i <= ido; ++i) { - for (k = 1; k <= l1; ++k) { - ch_ref(i, k, j) = cc_ref(i, j, k) + cc_ref(i, jc, k); - ch_ref(i, k, jc) = cc_ref(i, j, k) - cc_ref(i, jc, k); - } - } - } - for (i = 1; i <= ido; ++i) { - for (k = 1; k <= l1; ++k) { - ch_ref(i, k, 1) = cc_ref(i, 1, k); - } - } - } - idl = 2 - ido; - inc = 0; - for (l = 2; l <= ipph; ++l) { - lc = ipp2 - l; - idl += ido; - for (ik = 1; ik <= idl1; ++ik) { - c2_ref(ik, l) = ch2_ref(ik, 1) + wa[idl - 1] * ch2_ref(ik, 2); - c2_ref(ik, lc) = fsign*wa[idl] * ch2_ref(ik, ip); - } - idlj = idl; - inc += ido; - for (j = 3; j <= ipph; ++j) { - jc = ipp2 - j; - idlj += inc; - if (idlj > idp) { - idlj -= idp; - } - war = wa[idlj - 1]; - wai = wa[idlj]; - for (ik = 1; ik <= idl1; ++ik) { - c2_ref(ik, l) = c2_ref(ik, l) + war * ch2_ref(ik, j); - c2_ref(ik, lc) = c2_ref(ik, lc) + fsign*wai * ch2_ref(ik, jc); - } - } - } - for (j = 2; j <= ipph; ++j) { - for (ik = 1; ik <= idl1; ++ik) { - ch2_ref(ik, 1) = ch2_ref(ik, 1) + ch2_ref(ik, j); - } - } - for (j = 2; j <= ipph; ++j) { - jc = ipp2 - j; - for (ik = 2; ik <= idl1; ik += 2) { - ch2_ref(ik - 1, j) = c2_ref(ik - 1, j) - c2_ref(ik, jc); - ch2_ref(ik - 1, jc) = c2_ref(ik - 1, j) + c2_ref(ik, jc); - ch2_ref(ik, j) = c2_ref(ik, j) + c2_ref(ik - 1, jc); - ch2_ref(ik, jc) = c2_ref(ik, j) - c2_ref(ik - 1, jc); - } - } - *nac = 1; - if (ido == 2) { - return; - } - *nac = 0; - for (ik = 1; ik <= idl1; ++ik) { - c2_ref(ik, 1) = ch2_ref(ik, 1); - } - for (j = 2; j <= ip; ++j) { - for (k = 1; k <= l1; ++k) { - c1_ref(1, k, j) = ch_ref(1, k, j); - c1_ref(2, k, j) = ch_ref(2, k, j); - } - } - if (idot <= l1) { - idij = 0; - for (j = 2; j <= ip; ++j) { - idij += 2; - for (i = 4; i <= ido; i += 2) { - idij += 2; - for (k = 1; k <= l1; ++k) { - c1_ref(i - 1, k, j) = wa[idij - 1] * ch_ref(i - 1, k, j) - fsign*wa[idij] * ch_ref(i, k, j); - c1_ref(i, k, j) = wa[idij - 1] * ch_ref(i, k, j) + fsign*wa[idij] * ch_ref(i - 1, k, j); - } - } - } - return; - } - idj = 2 - ido; - for (j = 2; j <= ip; ++j) { - idj += ido; - for (k = 1; k <= l1; ++k) { - idij = idj; - for (i = 4; i <= ido; i += 2) { - idij += 2; - c1_ref(i - 1, k, j) = wa[idij - 1] * ch_ref(i - 1, k, j) - fsign*wa[idij] * ch_ref(i, k, j); - c1_ref(i, k, j) = wa[idij - 1] * ch_ref(i, k, j) + fsign*wa[idij] * ch_ref(i - 1, k, j); - } - } - } -} /* passb */ - -#undef ch2_ref -#undef ch_ref -#undef cc_ref -#undef c2_ref -#undef c1_ref - - -static void passb2(integer ido, integer l1, const real *cc, real *ch, const real *wa1) -{ - /* System generated locals */ - integer cc_offset, ch_offset; - - /* Local variables */ - integer i, k; - real ti2, tr2; - - -#define cc_ref(a_1,a_2,a_3) cc[((a_3)*2 + (a_2))*ido + a_1] -#define ch_ref(a_1,a_2,a_3) ch[((a_3)*l1 + (a_2))*ido + a_1] - - /* Parameter adjustments */ - ch_offset = 1 + ido * (1 + l1); - ch -= ch_offset; - cc_offset = 1 + ido * 3; - cc -= cc_offset; - --wa1; - - /* Function Body */ - if (ido <= 2) { - for (k = 1; k <= l1; ++k) { - ch_ref(1, k, 1) = cc_ref(1, 1, k) + cc_ref(1, 2, k); - ch_ref(1, k, 2) = cc_ref(1, 1, k) - cc_ref(1, 2, k); - ch_ref(2, k, 1) = cc_ref(2, 1, k) + cc_ref(2, 2, k); - ch_ref(2, k, 2) = cc_ref(2, 1, k) - cc_ref(2, 2, k); - } - return; - } - for (k = 1; k <= l1; ++k) { - for (i = 2; i <= ido; i += 2) { - ch_ref(i - 1, k, 1) = cc_ref(i - 1, 1, k) + cc_ref(i - 1, 2, k); - tr2 = cc_ref(i - 1, 1, k) - cc_ref(i - 1, 2, k); - ch_ref(i, k, 1) = cc_ref(i, 1, k) + cc_ref(i, 2, k); - ti2 = cc_ref(i, 1, k) - cc_ref(i, 2, k); - ch_ref(i, k, 2) = wa1[i - 1] * ti2 + wa1[i] * tr2; - ch_ref(i - 1, k, 2) = wa1[i - 1] * tr2 - wa1[i] * ti2; - } - } -} /* passb2 */ - -#undef ch_ref -#undef cc_ref - - -static void passb3(integer ido, integer l1, const real *cc, real *ch, const real *wa1, const real *wa2) -{ - static const real taur = -.5f; - static const real taui = .866025403784439f; - - /* System generated locals */ - integer cc_offset, ch_offset; - - /* Local variables */ - integer i, k; - real ci2, ci3, di2, di3, cr2, cr3, dr2, dr3, ti2, tr2; - - -#define cc_ref(a_1,a_2,a_3) cc[((a_3)*3 + (a_2))*ido + a_1] -#define ch_ref(a_1,a_2,a_3) ch[((a_3)*l1 + (a_2))*ido + a_1] - - /* Parameter adjustments */ - ch_offset = 1 + ido * (1 + l1); - ch -= ch_offset; - cc_offset = 1 + (ido << 2); - cc -= cc_offset; - --wa1; - --wa2; - - /* Function Body */ - if (ido == 2) { - for (k = 1; k <= l1; ++k) { - tr2 = cc_ref(1, 2, k) + cc_ref(1, 3, k); - cr2 = cc_ref(1, 1, k) + taur * tr2; - ch_ref(1, k, 1) = cc_ref(1, 1, k) + tr2; - ti2 = cc_ref(2, 2, k) + cc_ref(2, 3, k); - ci2 = cc_ref(2, 1, k) + taur * ti2; - ch_ref(2, k, 1) = cc_ref(2, 1, k) + ti2; - cr3 = taui * (cc_ref(1, 2, k) - cc_ref(1, 3, k)); - ci3 = taui * (cc_ref(2, 2, k) - cc_ref(2, 3, k)); - ch_ref(1, k, 2) = cr2 - ci3; - ch_ref(1, k, 3) = cr2 + ci3; - ch_ref(2, k, 2) = ci2 + cr3; - ch_ref(2, k, 3) = ci2 - cr3; - } - } else { - for (k = 1; k <= l1; ++k) { - for (i = 2; i <= ido; i += 2) { - tr2 = cc_ref(i - 1, 2, k) + cc_ref(i - 1, 3, k); - cr2 = cc_ref(i - 1, 1, k) + taur * tr2; - ch_ref(i - 1, k, 1) = cc_ref(i - 1, 1, k) + tr2; - ti2 = cc_ref(i, 2, k) + cc_ref(i, 3, k); - ci2 = cc_ref(i, 1, k) + taur * ti2; - ch_ref(i, k, 1) = cc_ref(i, 1, k) + ti2; - cr3 = taui * (cc_ref(i - 1, 2, k) - cc_ref(i - 1, 3, k)); - ci3 = taui * (cc_ref(i, 2, k) - cc_ref(i, 3, k)); - dr2 = cr2 - ci3; - dr3 = cr2 + ci3; - di2 = ci2 + cr3; - di3 = ci2 - cr3; - ch_ref(i, k, 2) = wa1[i - 1] * di2 + wa1[i] * dr2; - ch_ref(i - 1, k, 2) = wa1[i - 1] * dr2 - wa1[i] * di2; - ch_ref(i, k, 3) = wa2[i - 1] * di3 + wa2[i] * dr3; - ch_ref(i - 1, k, 3) = wa2[i - 1] * dr3 - wa2[i] * di3; - } - } - } -} /* passb3 */ - -#undef ch_ref -#undef cc_ref - - -static void passb4(integer ido, integer l1, const real *cc, real *ch, - const real *wa1, const real *wa2, const real *wa3) -{ - /* System generated locals */ - integer cc_offset, ch_offset; - - /* Local variables */ - integer i, k; - real ci2, ci3, ci4, cr2, cr3, cr4, ti1, ti2, ti3, ti4, tr1, tr2, tr3, tr4; - - -#define cc_ref(a_1,a_2,a_3) cc[((a_3)*4 + (a_2))*ido + a_1] -#define ch_ref(a_1,a_2,a_3) ch[((a_3)*l1 + (a_2))*ido + a_1] - - /* Parameter adjustments */ - ch_offset = 1 + ido * (1 + l1); - ch -= ch_offset; - cc_offset = 1 + ido * 5; - cc -= cc_offset; - --wa1; - --wa2; - --wa3; - - /* Function Body */ - if (ido == 2) { - for (k = 1; k <= l1; ++k) { - ti1 = cc_ref(2, 1, k) - cc_ref(2, 3, k); - ti2 = cc_ref(2, 1, k) + cc_ref(2, 3, k); - tr4 = cc_ref(2, 4, k) - cc_ref(2, 2, k); - ti3 = cc_ref(2, 2, k) + cc_ref(2, 4, k); - tr1 = cc_ref(1, 1, k) - cc_ref(1, 3, k); - tr2 = cc_ref(1, 1, k) + cc_ref(1, 3, k); - ti4 = cc_ref(1, 2, k) - cc_ref(1, 4, k); - tr3 = cc_ref(1, 2, k) + cc_ref(1, 4, k); - ch_ref(1, k, 1) = tr2 + tr3; - ch_ref(1, k, 3) = tr2 - tr3; - ch_ref(2, k, 1) = ti2 + ti3; - ch_ref(2, k, 3) = ti2 - ti3; - ch_ref(1, k, 2) = tr1 + tr4; - ch_ref(1, k, 4) = tr1 - tr4; - ch_ref(2, k, 2) = ti1 + ti4; - ch_ref(2, k, 4) = ti1 - ti4; - } - } else { - for (k = 1; k <= l1; ++k) { - for (i = 2; i <= ido; i += 2) { - ti1 = cc_ref(i, 1, k) - cc_ref(i, 3, k); - ti2 = cc_ref(i, 1, k) + cc_ref(i, 3, k); - ti3 = cc_ref(i, 2, k) + cc_ref(i, 4, k); - tr4 = cc_ref(i, 4, k) - cc_ref(i, 2, k); - tr1 = cc_ref(i - 1, 1, k) - cc_ref(i - 1, 3, k); - tr2 = cc_ref(i - 1, 1, k) + cc_ref(i - 1, 3, k); - ti4 = cc_ref(i - 1, 2, k) - cc_ref(i - 1, 4, k); - tr3 = cc_ref(i - 1, 2, k) + cc_ref(i - 1, 4, k); - ch_ref(i - 1, k, 1) = tr2 + tr3; - cr3 = tr2 - tr3; - ch_ref(i, k, 1) = ti2 + ti3; - ci3 = ti2 - ti3; - cr2 = tr1 + tr4; - cr4 = tr1 - tr4; - ci2 = ti1 + ti4; - ci4 = ti1 - ti4; - ch_ref(i - 1, k, 2) = wa1[i - 1] * cr2 - wa1[i] * ci2; - ch_ref(i, k, 2) = wa1[i - 1] * ci2 + wa1[i] * cr2; - ch_ref(i - 1, k, 3) = wa2[i - 1] * cr3 - wa2[i] * ci3; - ch_ref(i, k, 3) = wa2[i - 1] * ci3 + wa2[i] * cr3; - ch_ref(i - 1, k, 4) = wa3[i - 1] * cr4 - wa3[i] * ci4; - ch_ref(i, k, 4) = wa3[i - 1] * ci4 + wa3[i] * cr4; - } - } - } -} /* passb4 */ - -#undef ch_ref -#undef cc_ref - -/* passf5 and passb5 merged */ -static void passfb5(integer ido, integer l1, const real *cc, real *ch, - const real *wa1, const real *wa2, const real *wa3, const real *wa4, real fsign) -{ - const real tr11 = .309016994374947f; - const real ti11 = .951056516295154f*fsign; - const real tr12 = -.809016994374947f; - const real ti12 = .587785252292473f*fsign; - - /* System generated locals */ - integer cc_offset, ch_offset; - - /* Local variables */ - integer i, k; - real ci2, ci3, ci4, ci5, di3, di4, di5, di2, cr2, cr3, cr5, cr4, ti2, ti3, - ti4, ti5, dr3, dr4, dr5, dr2, tr2, tr3, tr4, tr5; - - -#define cc_ref(a_1,a_2,a_3) cc[((a_3)*5 + (a_2))*ido + a_1] -#define ch_ref(a_1,a_2,a_3) ch[((a_3)*l1 + (a_2))*ido + a_1] - - /* Parameter adjustments */ - ch_offset = 1 + ido * (1 + l1); - ch -= ch_offset; - cc_offset = 1 + ido * 6; - cc -= cc_offset; - --wa1; - --wa2; - --wa3; - --wa4; - - /* Function Body */ - if (ido == 2) { - for (k = 1; k <= l1; ++k) { - ti5 = cc_ref(2, 2, k) - cc_ref(2, 5, k); - ti2 = cc_ref(2, 2, k) + cc_ref(2, 5, k); - ti4 = cc_ref(2, 3, k) - cc_ref(2, 4, k); - ti3 = cc_ref(2, 3, k) + cc_ref(2, 4, k); - tr5 = cc_ref(1, 2, k) - cc_ref(1, 5, k); - tr2 = cc_ref(1, 2, k) + cc_ref(1, 5, k); - tr4 = cc_ref(1, 3, k) - cc_ref(1, 4, k); - tr3 = cc_ref(1, 3, k) + cc_ref(1, 4, k); - ch_ref(1, k, 1) = cc_ref(1, 1, k) + tr2 + tr3; - ch_ref(2, k, 1) = cc_ref(2, 1, k) + ti2 + ti3; - cr2 = cc_ref(1, 1, k) + tr11 * tr2 + tr12 * tr3; - ci2 = cc_ref(2, 1, k) + tr11 * ti2 + tr12 * ti3; - cr3 = cc_ref(1, 1, k) + tr12 * tr2 + tr11 * tr3; - ci3 = cc_ref(2, 1, k) + tr12 * ti2 + tr11 * ti3; - cr5 = ti11 * tr5 + ti12 * tr4; - ci5 = ti11 * ti5 + ti12 * ti4; - cr4 = ti12 * tr5 - ti11 * tr4; - ci4 = ti12 * ti5 - ti11 * ti4; - ch_ref(1, k, 2) = cr2 - ci5; - ch_ref(1, k, 5) = cr2 + ci5; - ch_ref(2, k, 2) = ci2 + cr5; - ch_ref(2, k, 3) = ci3 + cr4; - ch_ref(1, k, 3) = cr3 - ci4; - ch_ref(1, k, 4) = cr3 + ci4; - ch_ref(2, k, 4) = ci3 - cr4; - ch_ref(2, k, 5) = ci2 - cr5; - } - } else { - for (k = 1; k <= l1; ++k) { - for (i = 2; i <= ido; i += 2) { - ti5 = cc_ref(i, 2, k) - cc_ref(i, 5, k); - ti2 = cc_ref(i, 2, k) + cc_ref(i, 5, k); - ti4 = cc_ref(i, 3, k) - cc_ref(i, 4, k); - ti3 = cc_ref(i, 3, k) + cc_ref(i, 4, k); - tr5 = cc_ref(i - 1, 2, k) - cc_ref(i - 1, 5, k); - tr2 = cc_ref(i - 1, 2, k) + cc_ref(i - 1, 5, k); - tr4 = cc_ref(i - 1, 3, k) - cc_ref(i - 1, 4, k); - tr3 = cc_ref(i - 1, 3, k) + cc_ref(i - 1, 4, k); - ch_ref(i - 1, k, 1) = cc_ref(i - 1, 1, k) + tr2 + tr3; - ch_ref(i, k, 1) = cc_ref(i, 1, k) + ti2 + ti3; - cr2 = cc_ref(i - 1, 1, k) + tr11 * tr2 + tr12 * tr3; - ci2 = cc_ref(i, 1, k) + tr11 * ti2 + tr12 * ti3; - cr3 = cc_ref(i - 1, 1, k) + tr12 * tr2 + tr11 * tr3; - ci3 = cc_ref(i, 1, k) + tr12 * ti2 + tr11 * ti3; - cr5 = ti11 * tr5 + ti12 * tr4; - ci5 = ti11 * ti5 + ti12 * ti4; - cr4 = ti12 * tr5 - ti11 * tr4; - ci4 = ti12 * ti5 - ti11 * ti4; - dr3 = cr3 - ci4; - dr4 = cr3 + ci4; - di3 = ci3 + cr4; - di4 = ci3 - cr4; - dr5 = cr2 + ci5; - dr2 = cr2 - ci5; - di5 = ci2 - cr5; - di2 = ci2 + cr5; - ch_ref(i - 1, k, 2) = wa1[i - 1] * dr2 - fsign*wa1[i] * di2; - ch_ref(i, k, 2) = wa1[i - 1] * di2 + fsign*wa1[i] * dr2; - ch_ref(i - 1, k, 3) = wa2[i - 1] * dr3 - fsign*wa2[i] * di3; - ch_ref(i, k, 3) = wa2[i - 1] * di3 + fsign*wa2[i] * dr3; - ch_ref(i - 1, k, 4) = wa3[i - 1] * dr4 - fsign*wa3[i] * di4; - ch_ref(i, k, 4) = wa3[i - 1] * di4 + fsign*wa3[i] * dr4; - ch_ref(i - 1, k, 5) = wa4[i - 1] * dr5 - fsign*wa4[i] * di5; - ch_ref(i, k, 5) = wa4[i - 1] * di5 + fsign*wa4[i] * dr5; - } - } - } -} /* passb5 */ - -#undef ch_ref -#undef cc_ref - -static void passf2(integer ido, integer l1, const real *cc, real *ch, const real *wa1) -{ - /* System generated locals */ - integer cc_offset, ch_offset; - - /* Local variables */ - integer i, k; - real ti2, tr2; - - -#define cc_ref(a_1,a_2,a_3) cc[((a_3)*2 + (a_2))*ido + a_1] -#define ch_ref(a_1,a_2,a_3) ch[((a_3)*l1 + (a_2))*ido + a_1] - - /* Parameter adjustments */ - ch_offset = 1 + ido * (1 + l1); - ch -= ch_offset; - cc_offset = 1 + ido * 3; - cc -= cc_offset; - --wa1; - - /* Function Body */ - if (ido == 2) { - for (k = 1; k <= l1; ++k) { - ch_ref(1, k, 1) = cc_ref(1, 1, k) + cc_ref(1, 2, k); - ch_ref(1, k, 2) = cc_ref(1, 1, k) - cc_ref(1, 2, k); - ch_ref(2, k, 1) = cc_ref(2, 1, k) + cc_ref(2, 2, k); - ch_ref(2, k, 2) = cc_ref(2, 1, k) - cc_ref(2, 2, k); - } - } else { - for (k = 1; k <= l1; ++k) { - for (i = 2; i <= ido; i += 2) { - ch_ref(i - 1, k, 1) = cc_ref(i - 1, 1, k) + cc_ref(i - 1, 2, - k); - tr2 = cc_ref(i - 1, 1, k) - cc_ref(i - 1, 2, k); - ch_ref(i, k, 1) = cc_ref(i, 1, k) + cc_ref(i, 2, k); - ti2 = cc_ref(i, 1, k) - cc_ref(i, 2, k); - ch_ref(i, k, 2) = wa1[i - 1] * ti2 - wa1[i] * tr2; - ch_ref(i - 1, k, 2) = wa1[i - 1] * tr2 + wa1[i] * ti2; - } - } - } -} /* passf2 */ - -#undef ch_ref -#undef cc_ref - - -static void passf3(integer ido, integer l1, const real *cc, real *ch, - const real *wa1, const real *wa2) -{ - static const real taur = -.5f; - static const real taui = -.866025403784439f; - - /* System generated locals */ - integer cc_offset, ch_offset; - - /* Local variables */ - integer i, k; - real ci2, ci3, di2, di3, cr2, cr3, dr2, dr3, ti2, tr2; - - -#define cc_ref(a_1,a_2,a_3) cc[((a_3)*3 + (a_2))*ido + a_1] -#define ch_ref(a_1,a_2,a_3) ch[((a_3)*l1 + (a_2))*ido + a_1] - - /* Parameter adjustments */ - ch_offset = 1 + ido * (1 + l1); - ch -= ch_offset; - cc_offset = 1 + (ido << 2); - cc -= cc_offset; - --wa1; - --wa2; - - /* Function Body */ - if (ido == 2) { - for (k = 1; k <= l1; ++k) { - tr2 = cc_ref(1, 2, k) + cc_ref(1, 3, k); - cr2 = cc_ref(1, 1, k) + taur * tr2; - ch_ref(1, k, 1) = cc_ref(1, 1, k) + tr2; - ti2 = cc_ref(2, 2, k) + cc_ref(2, 3, k); - ci2 = cc_ref(2, 1, k) + taur * ti2; - ch_ref(2, k, 1) = cc_ref(2, 1, k) + ti2; - cr3 = taui * (cc_ref(1, 2, k) - cc_ref(1, 3, k)); - ci3 = taui * (cc_ref(2, 2, k) - cc_ref(2, 3, k)); - ch_ref(1, k, 2) = cr2 - ci3; - ch_ref(1, k, 3) = cr2 + ci3; - ch_ref(2, k, 2) = ci2 + cr3; - ch_ref(2, k, 3) = ci2 - cr3; - } - } else { - for (k = 1; k <= l1; ++k) { - for (i = 2; i <= ido; i += 2) { - tr2 = cc_ref(i - 1, 2, k) + cc_ref(i - 1, 3, k); - cr2 = cc_ref(i - 1, 1, k) + taur * tr2; - ch_ref(i - 1, k, 1) = cc_ref(i - 1, 1, k) + tr2; - ti2 = cc_ref(i, 2, k) + cc_ref(i, 3, k); - ci2 = cc_ref(i, 1, k) + taur * ti2; - ch_ref(i, k, 1) = cc_ref(i, 1, k) + ti2; - cr3 = taui * (cc_ref(i - 1, 2, k) - cc_ref(i - 1, 3, k)); - ci3 = taui * (cc_ref(i, 2, k) - cc_ref(i, 3, k)); - dr2 = cr2 - ci3; - dr3 = cr2 + ci3; - di2 = ci2 + cr3; - di3 = ci2 - cr3; - ch_ref(i, k, 2) = wa1[i - 1] * di2 - wa1[i] * dr2; - ch_ref(i - 1, k, 2) = wa1[i - 1] * dr2 + wa1[i] * di2; - ch_ref(i, k, 3) = wa2[i - 1] * di3 - wa2[i] * dr3; - ch_ref(i - 1, k, 3) = wa2[i - 1] * dr3 + wa2[i] * di3; - } - } - } -} /* passf3 */ - -#undef ch_ref -#undef cc_ref - - -static void passf4(integer ido, integer l1, const real *cc, real *ch, - const real *wa1, const real *wa2, const real *wa3) -{ - /* System generated locals */ - integer cc_offset, ch_offset; - - /* Local variables */ - integer i, k; - real ci2, ci3, ci4, cr2, cr3, cr4, ti1, ti2, ti3, ti4, tr1, tr2, tr3, tr4; - - -#define cc_ref(a_1,a_2,a_3) cc[((a_3)*4 + (a_2))*ido + a_1] -#define ch_ref(a_1,a_2,a_3) ch[((a_3)*l1 + (a_2))*ido + a_1] - - /* Parameter adjustments */ - ch_offset = 1 + ido * (1 + l1); - ch -= ch_offset; - cc_offset = 1 + ido * 5; - cc -= cc_offset; - --wa1; - --wa2; - --wa3; - - /* Function Body */ - if (ido == 2) { - for (k = 1; k <= l1; ++k) { - ti1 = cc_ref(2, 1, k) - cc_ref(2, 3, k); - ti2 = cc_ref(2, 1, k) + cc_ref(2, 3, k); - tr4 = cc_ref(2, 2, k) - cc_ref(2, 4, k); - ti3 = cc_ref(2, 2, k) + cc_ref(2, 4, k); - tr1 = cc_ref(1, 1, k) - cc_ref(1, 3, k); - tr2 = cc_ref(1, 1, k) + cc_ref(1, 3, k); - ti4 = cc_ref(1, 4, k) - cc_ref(1, 2, k); - tr3 = cc_ref(1, 2, k) + cc_ref(1, 4, k); - ch_ref(1, k, 1) = tr2 + tr3; - ch_ref(1, k, 3) = tr2 - tr3; - ch_ref(2, k, 1) = ti2 + ti3; - ch_ref(2, k, 3) = ti2 - ti3; - ch_ref(1, k, 2) = tr1 + tr4; - ch_ref(1, k, 4) = tr1 - tr4; - ch_ref(2, k, 2) = ti1 + ti4; - ch_ref(2, k, 4) = ti1 - ti4; - } - } else { - for (k = 1; k <= l1; ++k) { - for (i = 2; i <= ido; i += 2) { - ti1 = cc_ref(i, 1, k) - cc_ref(i, 3, k); - ti2 = cc_ref(i, 1, k) + cc_ref(i, 3, k); - ti3 = cc_ref(i, 2, k) + cc_ref(i, 4, k); - tr4 = cc_ref(i, 2, k) - cc_ref(i, 4, k); - tr1 = cc_ref(i - 1, 1, k) - cc_ref(i - 1, 3, k); - tr2 = cc_ref(i - 1, 1, k) + cc_ref(i - 1, 3, k); - ti4 = cc_ref(i - 1, 4, k) - cc_ref(i - 1, 2, k); - tr3 = cc_ref(i - 1, 2, k) + cc_ref(i - 1, 4, k); - ch_ref(i - 1, k, 1) = tr2 + tr3; - cr3 = tr2 - tr3; - ch_ref(i, k, 1) = ti2 + ti3; - ci3 = ti2 - ti3; - cr2 = tr1 + tr4; - cr4 = tr1 - tr4; - ci2 = ti1 + ti4; - ci4 = ti1 - ti4; - ch_ref(i - 1, k, 2) = wa1[i - 1] * cr2 + wa1[i] * ci2; - ch_ref(i, k, 2) = wa1[i - 1] * ci2 - wa1[i] * cr2; - ch_ref(i - 1, k, 3) = wa2[i - 1] * cr3 + wa2[i] * ci3; - ch_ref(i, k, 3) = wa2[i - 1] * ci3 - wa2[i] * cr3; - ch_ref(i - 1, k, 4) = wa3[i - 1] * cr4 + wa3[i] * ci4; - ch_ref(i, k, 4) = wa3[i - 1] * ci4 - wa3[i] * cr4; - } - } - } -} /* passf4 */ - -#undef ch_ref -#undef cc_ref - -static void radb2(integer ido, integer l1, const real *cc, real *ch, const real *wa1) -{ - /* System generated locals */ - integer cc_offset, ch_offset; - - /* Local variables */ - integer i, k, ic; - real ti2, tr2; - integer idp2; - - -#define cc_ref(a_1,a_2,a_3) cc[((a_3)*2 + (a_2))*ido + a_1] -#define ch_ref(a_1,a_2,a_3) ch[((a_3)*l1 + (a_2))*ido + a_1] - - /* Parameter adjustments */ - ch_offset = 1 + ido * (1 + l1); - ch -= ch_offset; - cc_offset = 1 + ido * 3; - cc -= cc_offset; - --wa1; - - /* Function Body */ - for (k = 1; k <= l1; ++k) { - ch_ref(1, k, 1) = cc_ref(1, 1, k) + cc_ref(ido, 2, k); - ch_ref(1, k, 2) = cc_ref(1, 1, k) - cc_ref(ido, 2, k); - } - if (ido < 2) return; - else if (ido != 2) { - idp2 = ido + 2; - for (k = 1; k <= l1; ++k) { - for (i = 3; i <= ido; i += 2) { - ic = idp2 - i; - ch_ref(i - 1, k, 1) = cc_ref(i - 1, 1, k) + cc_ref(ic - 1, 2, - k); - tr2 = cc_ref(i - 1, 1, k) - cc_ref(ic - 1, 2, k); - ch_ref(i, k, 1) = cc_ref(i, 1, k) - cc_ref(ic, 2, k); - ti2 = cc_ref(i, 1, k) + cc_ref(ic, 2, k); - ch_ref(i - 1, k, 2) = wa1[i - 2] * tr2 - wa1[i - 1] * ti2; - ch_ref(i, k, 2) = wa1[i - 2] * ti2 + wa1[i - 1] * tr2; - } - } - if (ido % 2 == 1) return; - } - for (k = 1; k <= l1; ++k) { - ch_ref(ido, k, 1) = cc_ref(ido, 1, k) + cc_ref(ido, 1, k); - ch_ref(ido, k, 2) = -(cc_ref(1, 2, k) + cc_ref(1, 2, k)); - } -} /* radb2 */ - -#undef ch_ref -#undef cc_ref - - -static void radb3(integer ido, integer l1, const real *cc, real *ch, - const real *wa1, const real *wa2) -{ - /* Initialized data */ - - static const real taur = -.5f; - static const real taui = .866025403784439f; - - /* System generated locals */ - integer cc_offset, ch_offset; - - /* Local variables */ - integer i, k, ic; - real ci2, ci3, di2, di3, cr2, cr3, dr2, dr3, ti2, tr2; - integer idp2; - - -#define cc_ref(a_1,a_2,a_3) cc[((a_3)*3 + (a_2))*ido + a_1] -#define ch_ref(a_1,a_2,a_3) ch[((a_3)*l1 + (a_2))*ido + a_1] - - /* Parameter adjustments */ - ch_offset = 1 + ido * (1 + l1); - ch -= ch_offset; - cc_offset = 1 + (ido << 2); - cc -= cc_offset; - --wa1; - --wa2; - - /* Function Body */ - for (k = 1; k <= l1; ++k) { - tr2 = cc_ref(ido, 2, k) + cc_ref(ido, 2, k); - cr2 = cc_ref(1, 1, k) + taur * tr2; - ch_ref(1, k, 1) = cc_ref(1, 1, k) + tr2; - ci3 = taui * (cc_ref(1, 3, k) + cc_ref(1, 3, k)); - ch_ref(1, k, 2) = cr2 - ci3; - ch_ref(1, k, 3) = cr2 + ci3; - } - if (ido == 1) { - return; - } - idp2 = ido + 2; - for (k = 1; k <= l1; ++k) { - for (i = 3; i <= ido; i += 2) { - ic = idp2 - i; - tr2 = cc_ref(i - 1, 3, k) + cc_ref(ic - 1, 2, k); - cr2 = cc_ref(i - 1, 1, k) + taur * tr2; - ch_ref(i - 1, k, 1) = cc_ref(i - 1, 1, k) + tr2; - ti2 = cc_ref(i, 3, k) - cc_ref(ic, 2, k); - ci2 = cc_ref(i, 1, k) + taur * ti2; - ch_ref(i, k, 1) = cc_ref(i, 1, k) + ti2; - cr3 = taui * (cc_ref(i - 1, 3, k) - cc_ref(ic - 1, 2, k)); - ci3 = taui * (cc_ref(i, 3, k) + cc_ref(ic, 2, k)); - dr2 = cr2 - ci3; - dr3 = cr2 + ci3; - di2 = ci2 + cr3; - di3 = ci2 - cr3; - ch_ref(i - 1, k, 2) = wa1[i - 2] * dr2 - wa1[i - 1] * di2; - ch_ref(i, k, 2) = wa1[i - 2] * di2 + wa1[i - 1] * dr2; - ch_ref(i - 1, k, 3) = wa2[i - 2] * dr3 - wa2[i - 1] * di3; - ch_ref(i, k, 3) = wa2[i - 2] * di3 + wa2[i - 1] * dr3; - } - } -} /* radb3 */ - -#undef ch_ref -#undef cc_ref - - -static void radb4(integer ido, integer l1, const real *cc, real *ch, - const real *wa1, const real *wa2, const real *wa3) -{ - /* Initialized data */ - - static const real sqrt2 = 1.414213562373095f; - - /* System generated locals */ - integer cc_offset, ch_offset; - - /* Local variables */ - integer i, k, ic; - real ci2, ci3, ci4, cr2, cr3, cr4, ti1, ti2, ti3, ti4, tr1, tr2, tr3, tr4; - integer idp2; - - -#define cc_ref(a_1,a_2,a_3) cc[((a_3)*4 + (a_2))*ido + a_1] -#define ch_ref(a_1,a_2,a_3) ch[((a_3)*l1 + (a_2))*ido + a_1] - - /* Parameter adjustments */ - ch_offset = 1 + ido * (1 + l1); - ch -= ch_offset; - cc_offset = 1 + ido * 5; - cc -= cc_offset; - --wa1; - --wa2; - --wa3; - - /* Function Body */ - for (k = 1; k <= l1; ++k) { - tr1 = cc_ref(1, 1, k) - cc_ref(ido, 4, k); - tr2 = cc_ref(1, 1, k) + cc_ref(ido, 4, k); - tr3 = cc_ref(ido, 2, k) + cc_ref(ido, 2, k); - tr4 = cc_ref(1, 3, k) + cc_ref(1, 3, k); - ch_ref(1, k, 1) = tr2 + tr3; - ch_ref(1, k, 2) = tr1 - tr4; - ch_ref(1, k, 3) = tr2 - tr3; - ch_ref(1, k, 4) = tr1 + tr4; - } - if (ido < 2) return; - if (ido != 2) { - idp2 = ido + 2; - for (k = 1; k <= l1; ++k) { - for (i = 3; i <= ido; i += 2) { - ic = idp2 - i; - ti1 = cc_ref(i, 1, k) + cc_ref(ic, 4, k); - ti2 = cc_ref(i, 1, k) - cc_ref(ic, 4, k); - ti3 = cc_ref(i, 3, k) - cc_ref(ic, 2, k); - tr4 = cc_ref(i, 3, k) + cc_ref(ic, 2, k); - tr1 = cc_ref(i - 1, 1, k) - cc_ref(ic - 1, 4, k); - tr2 = cc_ref(i - 1, 1, k) + cc_ref(ic - 1, 4, k); - ti4 = cc_ref(i - 1, 3, k) - cc_ref(ic - 1, 2, k); - tr3 = cc_ref(i - 1, 3, k) + cc_ref(ic - 1, 2, k); - ch_ref(i - 1, k, 1) = tr2 + tr3; - cr3 = tr2 - tr3; - ch_ref(i, k, 1) = ti2 + ti3; - ci3 = ti2 - ti3; - cr2 = tr1 - tr4; - cr4 = tr1 + tr4; - ci2 = ti1 + ti4; - ci4 = ti1 - ti4; - ch_ref(i - 1, k, 2) = wa1[i - 2] * cr2 - wa1[i - 1] * ci2; - ch_ref(i, k, 2) = wa1[i - 2] * ci2 + wa1[i - 1] * cr2; - ch_ref(i - 1, k, 3) = wa2[i - 2] * cr3 - wa2[i - 1] * ci3; - ch_ref(i, k, 3) = wa2[i - 2] * ci3 + wa2[i - 1] * cr3; - ch_ref(i - 1, k, 4) = wa3[i - 2] * cr4 - wa3[i - 1] * ci4; - ch_ref(i, k, 4) = wa3[i - 2] * ci4 + wa3[i - 1] * cr4; - } - } - if (ido % 2 == 1) return; - } - for (k = 1; k <= l1; ++k) { - ti1 = cc_ref(1, 2, k) + cc_ref(1, 4, k); - ti2 = cc_ref(1, 4, k) - cc_ref(1, 2, k); - tr1 = cc_ref(ido, 1, k) - cc_ref(ido, 3, k); - tr2 = cc_ref(ido, 1, k) + cc_ref(ido, 3, k); - ch_ref(ido, k, 1) = tr2 + tr2; - ch_ref(ido, k, 2) = sqrt2 * (tr1 - ti1); - ch_ref(ido, k, 3) = ti2 + ti2; - ch_ref(ido, k, 4) = -sqrt2 * (tr1 + ti1); - } -} /* radb4 */ - -#undef ch_ref -#undef cc_ref - - -static void radb5(integer ido, integer l1, const real *cc, real *ch, - const real *wa1, const real *wa2, const real *wa3, const real *wa4) -{ - /* Initialized data */ - - static const real tr11 = .309016994374947f; - static const real ti11 = .951056516295154f; - static const real tr12 = -.809016994374947f; - static const real ti12 = .587785252292473f; - - /* System generated locals */ - integer cc_offset, ch_offset; - - /* Local variables */ - integer i, k, ic; - real ci2, ci3, ci4, ci5, di3, di4, di5, di2, cr2, cr3, cr5, cr4, ti2, ti3, - ti4, ti5, dr3, dr4, dr5, dr2, tr2, tr3, tr4, tr5; - integer idp2; - - -#define cc_ref(a_1,a_2,a_3) cc[((a_3)*5 + (a_2))*ido + a_1] -#define ch_ref(a_1,a_2,a_3) ch[((a_3)*l1 + (a_2))*ido + a_1] - - /* Parameter adjustments */ - ch_offset = 1 + ido * (1 + l1); - ch -= ch_offset; - cc_offset = 1 + ido * 6; - cc -= cc_offset; - --wa1; - --wa2; - --wa3; - --wa4; - - /* Function Body */ - for (k = 1; k <= l1; ++k) { - ti5 = cc_ref(1, 3, k) + cc_ref(1, 3, k); - ti4 = cc_ref(1, 5, k) + cc_ref(1, 5, k); - tr2 = cc_ref(ido, 2, k) + cc_ref(ido, 2, k); - tr3 = cc_ref(ido, 4, k) + cc_ref(ido, 4, k); - ch_ref(1, k, 1) = cc_ref(1, 1, k) + tr2 + tr3; - cr2 = cc_ref(1, 1, k) + tr11 * tr2 + tr12 * tr3; - cr3 = cc_ref(1, 1, k) + tr12 * tr2 + tr11 * tr3; - ci5 = ti11 * ti5 + ti12 * ti4; - ci4 = ti12 * ti5 - ti11 * ti4; - ch_ref(1, k, 2) = cr2 - ci5; - ch_ref(1, k, 3) = cr3 - ci4; - ch_ref(1, k, 4) = cr3 + ci4; - ch_ref(1, k, 5) = cr2 + ci5; - } - if (ido == 1) { - return; - } - idp2 = ido + 2; - for (k = 1; k <= l1; ++k) { - for (i = 3; i <= ido; i += 2) { - ic = idp2 - i; - ti5 = cc_ref(i, 3, k) + cc_ref(ic, 2, k); - ti2 = cc_ref(i, 3, k) - cc_ref(ic, 2, k); - ti4 = cc_ref(i, 5, k) + cc_ref(ic, 4, k); - ti3 = cc_ref(i, 5, k) - cc_ref(ic, 4, k); - tr5 = cc_ref(i - 1, 3, k) - cc_ref(ic - 1, 2, k); - tr2 = cc_ref(i - 1, 3, k) + cc_ref(ic - 1, 2, k); - tr4 = cc_ref(i - 1, 5, k) - cc_ref(ic - 1, 4, k); - tr3 = cc_ref(i - 1, 5, k) + cc_ref(ic - 1, 4, k); - ch_ref(i - 1, k, 1) = cc_ref(i - 1, 1, k) + tr2 + tr3; - ch_ref(i, k, 1) = cc_ref(i, 1, k) + ti2 + ti3; - cr2 = cc_ref(i - 1, 1, k) + tr11 * tr2 + tr12 * tr3; - ci2 = cc_ref(i, 1, k) + tr11 * ti2 + tr12 * ti3; - cr3 = cc_ref(i - 1, 1, k) + tr12 * tr2 + tr11 * tr3; - ci3 = cc_ref(i, 1, k) + tr12 * ti2 + tr11 * ti3; - cr5 = ti11 * tr5 + ti12 * tr4; - ci5 = ti11 * ti5 + ti12 * ti4; - cr4 = ti12 * tr5 - ti11 * tr4; - ci4 = ti12 * ti5 - ti11 * ti4; - dr3 = cr3 - ci4; - dr4 = cr3 + ci4; - di3 = ci3 + cr4; - di4 = ci3 - cr4; - dr5 = cr2 + ci5; - dr2 = cr2 - ci5; - di5 = ci2 - cr5; - di2 = ci2 + cr5; - ch_ref(i - 1, k, 2) = wa1[i - 2] * dr2 - wa1[i - 1] * di2; - ch_ref(i, k, 2) = wa1[i - 2] * di2 + wa1[i - 1] * dr2; - ch_ref(i - 1, k, 3) = wa2[i - 2] * dr3 - wa2[i - 1] * di3; - ch_ref(i, k, 3) = wa2[i - 2] * di3 + wa2[i - 1] * dr3; - ch_ref(i - 1, k, 4) = wa3[i - 2] * dr4 - wa3[i - 1] * di4; - ch_ref(i, k, 4) = wa3[i - 2] * di4 + wa3[i - 1] * dr4; - ch_ref(i - 1, k, 5) = wa4[i - 2] * dr5 - wa4[i - 1] * di5; - ch_ref(i, k, 5) = wa4[i - 2] * di5 + wa4[i - 1] * dr5; - } - } -} /* radb5 */ - -#undef ch_ref -#undef cc_ref - - -static void radbg(integer ido, integer ip, integer l1, integer idl1, - const real *cc, real *c1, real *c2, real *ch, real *ch2, const real *wa) -{ - /* System generated locals */ - integer ch_offset, cc_offset, - c1_offset, c2_offset, ch2_offset; - - /* Local variables */ - integer i, j, k, l, j2, ic, jc, lc, ik, is; - real dc2, ai1, ai2, ar1, ar2, ds2; - integer nbd; - real dcp, arg, dsp, ar1h, ar2h; - integer idp2, ipp2, idij, ipph; - - -#define c1_ref(a_1,a_2,a_3) c1[((a_3)*l1 + (a_2))*ido + a_1] -#define c2_ref(a_1,a_2) c2[(a_2)*idl1 + a_1] -#define cc_ref(a_1,a_2,a_3) cc[((a_3)*ip + (a_2))*ido + a_1] -#define ch_ref(a_1,a_2,a_3) ch[((a_3)*l1 + (a_2))*ido + a_1] -#define ch2_ref(a_1,a_2) ch2[(a_2)*idl1 + a_1] - - /* Parameter adjustments */ - ch_offset = 1 + ido * (1 + l1); - ch -= ch_offset; - c1_offset = 1 + ido * (1 + l1); - c1 -= c1_offset; - cc_offset = 1 + ido * (1 + ip); - cc -= cc_offset; - ch2_offset = 1 + idl1; - ch2 -= ch2_offset; - c2_offset = 1 + idl1; - c2 -= c2_offset; - --wa; - - /* Function Body */ - arg = (2*M_PI) / (real) (ip); - dcp = cos(arg); - dsp = sin(arg); - idp2 = ido + 2; - nbd = (ido - 1) / 2; - ipp2 = ip + 2; - ipph = (ip + 1) / 2; - if (ido >= l1) { - for (k = 1; k <= l1; ++k) { - for (i = 1; i <= ido; ++i) { - ch_ref(i, k, 1) = cc_ref(i, 1, k); - } - } - } else { - for (i = 1; i <= ido; ++i) { - for (k = 1; k <= l1; ++k) { - ch_ref(i, k, 1) = cc_ref(i, 1, k); - } - } - } - for (j = 2; j <= ipph; ++j) { - jc = ipp2 - j; - j2 = j + j; - for (k = 1; k <= l1; ++k) { - ch_ref(1, k, j) = cc_ref(ido, j2 - 2, k) + cc_ref(ido, j2 - 2, k); - ch_ref(1, k, jc) = cc_ref(1, j2 - 1, k) + cc_ref(1, j2 - 1, k); - } - } - if (ido != 1) { - if (nbd >= l1) { - for (j = 2; j <= ipph; ++j) { - jc = ipp2 - j; - for (k = 1; k <= l1; ++k) { - for (i = 3; i <= ido; i += 2) { - ic = idp2 - i; - ch_ref(i - 1, k, j) = cc_ref(i - 1, (j << 1) - 1, k) + cc_ref(ic - 1, (j << 1) - 2, k); - ch_ref(i - 1, k, jc) = cc_ref(i - 1, (j << 1) - 1, k) - cc_ref(ic - 1, (j << 1) - 2, k); - ch_ref(i, k, j) = cc_ref(i, (j << 1) - 1, k) - cc_ref(ic, (j << 1) - 2, k); - ch_ref(i, k, jc) = cc_ref(i, (j << 1) - 1, k) + cc_ref(ic, (j << 1) - 2, k); - } - } - } - } else { - for (j = 2; j <= ipph; ++j) { - jc = ipp2 - j; - for (i = 3; i <= ido; i += 2) { - ic = idp2 - i; - for (k = 1; k <= l1; ++k) { - ch_ref(i - 1, k, j) = cc_ref(i - 1, (j << 1) - 1, k) + cc_ref(ic - 1, (j << 1) - 2, k); - ch_ref(i - 1, k, jc) = cc_ref(i - 1, (j << 1) - 1, k) - cc_ref(ic - 1, (j << 1) - 2, k); - ch_ref(i, k, j) = cc_ref(i, (j << 1) - 1, k) - cc_ref(ic, (j << 1) - 2, k); - ch_ref(i, k, jc) = cc_ref(i, (j << 1) - 1, k) + cc_ref(ic, (j << 1) - 2, k); - } - } - } - } - } - ar1 = 1.f; - ai1 = 0.f; - for (l = 2; l <= ipph; ++l) { - lc = ipp2 - l; - ar1h = dcp * ar1 - dsp * ai1; - ai1 = dcp * ai1 + dsp * ar1; - ar1 = ar1h; - for (ik = 1; ik <= idl1; ++ik) { - c2_ref(ik, l) = ch2_ref(ik, 1) + ar1 * ch2_ref(ik, 2); - c2_ref(ik, lc) = ai1 * ch2_ref(ik, ip); - } - dc2 = ar1; - ds2 = ai1; - ar2 = ar1; - ai2 = ai1; - for (j = 3; j <= ipph; ++j) { - jc = ipp2 - j; - ar2h = dc2 * ar2 - ds2 * ai2; - ai2 = dc2 * ai2 + ds2 * ar2; - ar2 = ar2h; - for (ik = 1; ik <= idl1; ++ik) { - c2_ref(ik, l) = c2_ref(ik, l) + ar2 * ch2_ref(ik, j); - c2_ref(ik, lc) = c2_ref(ik, lc) + ai2 * ch2_ref(ik, jc); - } - } - } - for (j = 2; j <= ipph; ++j) { - for (ik = 1; ik <= idl1; ++ik) { - ch2_ref(ik, 1) = ch2_ref(ik, 1) + ch2_ref(ik, j); - } - } - for (j = 2; j <= ipph; ++j) { - jc = ipp2 - j; - for (k = 1; k <= l1; ++k) { - ch_ref(1, k, j) = c1_ref(1, k, j) - c1_ref(1, k, jc); - ch_ref(1, k, jc) = c1_ref(1, k, j) + c1_ref(1, k, jc); - } - } - if (ido != 1) { - if (nbd >= l1) { - for (j = 2; j <= ipph; ++j) { - jc = ipp2 - j; - for (k = 1; k <= l1; ++k) { - for (i = 3; i <= ido; i += 2) { - ch_ref(i - 1, k, j) = c1_ref(i - 1, k, j) - c1_ref(i, k, jc); - ch_ref(i - 1, k, jc) = c1_ref(i - 1, k, j) + c1_ref(i, k, jc); - ch_ref(i, k, j) = c1_ref(i, k, j) + c1_ref(i - 1, k, jc); - ch_ref(i, k, jc) = c1_ref(i, k, j) - c1_ref(i - 1, k, jc); - } - } - } - } else { - for (j = 2; j <= ipph; ++j) { - jc = ipp2 - j; - for (i = 3; i <= ido; i += 2) { - for (k = 1; k <= l1; ++k) { - ch_ref(i - 1, k, j) = c1_ref(i - 1, k, j) - c1_ref(i, k, jc); - ch_ref(i - 1, k, jc) = c1_ref(i - 1, k, j) + c1_ref(i, k, jc); - ch_ref(i, k, j) = c1_ref(i, k, j) + c1_ref(i - 1, k, jc); - ch_ref(i, k, jc) = c1_ref(i, k, j) - c1_ref(i - 1, k, jc); - } - } - } - } - } - if (ido == 1) { - return; - } - for (ik = 1; ik <= idl1; ++ik) { - c2_ref(ik, 1) = ch2_ref(ik, 1); - } - for (j = 2; j <= ip; ++j) { - for (k = 1; k <= l1; ++k) { - c1_ref(1, k, j) = ch_ref(1, k, j); - } - } - if (nbd <= l1) { - is = -(ido); - for (j = 2; j <= ip; ++j) { - is += ido; - idij = is; - for (i = 3; i <= ido; i += 2) { - idij += 2; - for (k = 1; k <= l1; ++k) { - c1_ref(i - 1, k, j) = wa[idij - 1] * ch_ref(i - 1, k, j) - - wa[idij] * ch_ref(i, k, j); - c1_ref(i, k, j) = wa[idij - 1] * ch_ref(i, k, j) + wa[idij] * ch_ref(i - 1, k, j); - } - } - } - } else { - is = -(ido); - for (j = 2; j <= ip; ++j) { - is += ido; - for (k = 1; k <= l1; ++k) { - idij = is; - for (i = 3; i <= ido; i += 2) { - idij += 2; - c1_ref(i - 1, k, j) = wa[idij - 1] * ch_ref(i - 1, k, j) - - wa[idij] * ch_ref(i, k, j); - c1_ref(i, k, j) = wa[idij - 1] * ch_ref(i, k, j) + wa[idij] * ch_ref(i - 1, k, j); - } - } - } - } -} /* radbg */ - -#undef ch2_ref -#undef ch_ref -#undef cc_ref -#undef c2_ref -#undef c1_ref - - -static void radf2(integer ido, integer l1, const real *cc, real *ch, - const real *wa1) -{ - /* System generated locals */ - integer ch_offset, cc_offset; - - /* Local variables */ - integer i, k, ic; - real ti2, tr2; - integer idp2; - - -#define cc_ref(a_1,a_2,a_3) cc[((a_3)*l1 + (a_2))*ido + a_1] -#define ch_ref(a_1,a_2,a_3) ch[((a_3)*2 + (a_2))*ido + a_1] - - /* Parameter adjustments */ - ch_offset = 1 + ido * 3; - ch -= ch_offset; - cc_offset = 1 + ido * (1 + l1); - cc -= cc_offset; - --wa1; - - /* Function Body */ - for (k = 1; k <= l1; ++k) { - ch_ref(1, 1, k) = cc_ref(1, k, 1) + cc_ref(1, k, 2); - ch_ref(ido, 2, k) = cc_ref(1, k, 1) - cc_ref(1, k, 2); - } - if (ido < 2) return; - if (ido != 2) { - idp2 = ido + 2; - for (k = 1; k <= l1; ++k) { - for (i = 3; i <= ido; i += 2) { - ic = idp2 - i; - tr2 = wa1[i - 2] * cc_ref(i - 1, k, 2) + wa1[i - 1] * - cc_ref(i, k, 2); - ti2 = wa1[i - 2] * cc_ref(i, k, 2) - wa1[i - 1] * cc_ref( - i - 1, k, 2); - ch_ref(i, 1, k) = cc_ref(i, k, 1) + ti2; - ch_ref(ic, 2, k) = ti2 - cc_ref(i, k, 1); - ch_ref(i - 1, 1, k) = cc_ref(i - 1, k, 1) + tr2; - ch_ref(ic - 1, 2, k) = cc_ref(i - 1, k, 1) - tr2; - } - } - if (ido % 2 == 1) { - return; - } - } - for (k = 1; k <= l1; ++k) { - ch_ref(1, 2, k) = -cc_ref(ido, k, 2); - ch_ref(ido, 1, k) = cc_ref(ido, k, 1); - } -} /* radf2 */ - -#undef ch_ref -#undef cc_ref - - -static void radf3(integer ido, integer l1, const real *cc, real *ch, - const real *wa1, const real *wa2) -{ - static const real taur = -.5f; - static const real taui = .866025403784439f; - - /* System generated locals */ - integer ch_offset, cc_offset; - - /* Local variables */ - integer i, k, ic; - real ci2, di2, di3, cr2, dr2, dr3, ti2, ti3, tr2, tr3; - integer idp2; - - -#define cc_ref(a_1,a_2,a_3) cc[((a_3)*l1 + (a_2))*ido + a_1] -#define ch_ref(a_1,a_2,a_3) ch[((a_3)*3 + (a_2))*ido + a_1] - - /* Parameter adjustments */ - ch_offset = 1 + (ido << 2); - ch -= ch_offset; - cc_offset = 1 + ido * (1 + l1); - cc -= cc_offset; - --wa1; - --wa2; - - /* Function Body */ - for (k = 1; k <= l1; ++k) { - cr2 = cc_ref(1, k, 2) + cc_ref(1, k, 3); - ch_ref(1, 1, k) = cc_ref(1, k, 1) + cr2; - ch_ref(1, 3, k) = taui * (cc_ref(1, k, 3) - cc_ref(1, k, 2)); - ch_ref(ido, 2, k) = cc_ref(1, k, 1) + taur * cr2; - } - if (ido == 1) { - return; - } - idp2 = ido + 2; - for (k = 1; k <= l1; ++k) { - for (i = 3; i <= ido; i += 2) { - ic = idp2 - i; - dr2 = wa1[i - 2] * cc_ref(i - 1, k, 2) + wa1[i - 1] * - cc_ref(i, k, 2); - di2 = wa1[i - 2] * cc_ref(i, k, 2) - wa1[i - 1] * cc_ref( - i - 1, k, 2); - dr3 = wa2[i - 2] * cc_ref(i - 1, k, 3) + wa2[i - 1] * - cc_ref(i, k, 3); - di3 = wa2[i - 2] * cc_ref(i, k, 3) - wa2[i - 1] * cc_ref( - i - 1, k, 3); - cr2 = dr2 + dr3; - ci2 = di2 + di3; - ch_ref(i - 1, 1, k) = cc_ref(i - 1, k, 1) + cr2; - ch_ref(i, 1, k) = cc_ref(i, k, 1) + ci2; - tr2 = cc_ref(i - 1, k, 1) + taur * cr2; - ti2 = cc_ref(i, k, 1) + taur * ci2; - tr3 = taui * (di2 - di3); - ti3 = taui * (dr3 - dr2); - ch_ref(i - 1, 3, k) = tr2 + tr3; - ch_ref(ic - 1, 2, k) = tr2 - tr3; - ch_ref(i, 3, k) = ti2 + ti3; - ch_ref(ic, 2, k) = ti3 - ti2; - } - } -} /* radf3 */ - -#undef ch_ref -#undef cc_ref - - -static void radf4(integer ido, integer l1, const real *cc, real *ch, - const real *wa1, const real *wa2, const real *wa3) -{ - /* Initialized data */ - - static const real hsqt2 = .7071067811865475f; - - /* System generated locals */ - integer cc_offset, ch_offset; - - /* Local variables */ - integer i, k, ic; - real ci2, ci3, ci4, cr2, cr3, cr4, ti1, ti2, ti3, ti4, tr1, tr2, tr3, tr4; - integer idp2; - - -#define cc_ref(a_1,a_2,a_3) cc[((a_3)*l1 + (a_2))*ido + a_1] -#define ch_ref(a_1,a_2,a_3) ch[((a_3)*4 + (a_2))*ido + a_1] - - /* Parameter adjustments */ - ch_offset = 1 + ido * 5; - ch -= ch_offset; - cc_offset = 1 + ido * (1 + l1); - cc -= cc_offset; - --wa1; - --wa2; - --wa3; - - /* Function Body */ - for (k = 1; k <= l1; ++k) { - tr1 = cc_ref(1, k, 2) + cc_ref(1, k, 4); - tr2 = cc_ref(1, k, 1) + cc_ref(1, k, 3); - ch_ref(1, 1, k) = tr1 + tr2; - ch_ref(ido, 4, k) = tr2 - tr1; - ch_ref(ido, 2, k) = cc_ref(1, k, 1) - cc_ref(1, k, 3); - ch_ref(1, 3, k) = cc_ref(1, k, 4) - cc_ref(1, k, 2); - } - if (ido < 2) return; - if (ido != 2) { - idp2 = ido + 2; - for (k = 1; k <= l1; ++k) { - for (i = 3; i <= ido; i += 2) { - ic = idp2 - i; - cr2 = wa1[i - 2] * cc_ref(i - 1, k, 2) + wa1[i - 1] * - cc_ref(i, k, 2); - ci2 = wa1[i - 2] * cc_ref(i, k, 2) - wa1[i - 1] * cc_ref( - i - 1, k, 2); - cr3 = wa2[i - 2] * cc_ref(i - 1, k, 3) + wa2[i - 1] * - cc_ref(i, k, 3); - ci3 = wa2[i - 2] * cc_ref(i, k, 3) - wa2[i - 1] * cc_ref( - i - 1, k, 3); - cr4 = wa3[i - 2] * cc_ref(i - 1, k, 4) + wa3[i - 1] * - cc_ref(i, k, 4); - ci4 = wa3[i - 2] * cc_ref(i, k, 4) - wa3[i - 1] * cc_ref( - i - 1, k, 4); - tr1 = cr2 + cr4; - tr4 = cr4 - cr2; - ti1 = ci2 + ci4; - ti4 = ci2 - ci4; - ti2 = cc_ref(i, k, 1) + ci3; - ti3 = cc_ref(i, k, 1) - ci3; - tr2 = cc_ref(i - 1, k, 1) + cr3; - tr3 = cc_ref(i - 1, k, 1) - cr3; - ch_ref(i - 1, 1, k) = tr1 + tr2; - ch_ref(ic - 1, 4, k) = tr2 - tr1; - ch_ref(i, 1, k) = ti1 + ti2; - ch_ref(ic, 4, k) = ti1 - ti2; - ch_ref(i - 1, 3, k) = ti4 + tr3; - ch_ref(ic - 1, 2, k) = tr3 - ti4; - ch_ref(i, 3, k) = tr4 + ti3; - ch_ref(ic, 2, k) = tr4 - ti3; - } - } - if (ido % 2 == 1) { - return; - } - } - for (k = 1; k <= l1; ++k) { - ti1 = -hsqt2 * (cc_ref(ido, k, 2) + cc_ref(ido, k, 4)); - tr1 = hsqt2 * (cc_ref(ido, k, 2) - cc_ref(ido, k, 4)); - ch_ref(ido, 1, k) = tr1 + cc_ref(ido, k, 1); - ch_ref(ido, 3, k) = cc_ref(ido, k, 1) - tr1; - ch_ref(1, 2, k) = ti1 - cc_ref(ido, k, 3); - ch_ref(1, 4, k) = ti1 + cc_ref(ido, k, 3); - } -} /* radf4 */ - -#undef ch_ref -#undef cc_ref - - -static void radf5(integer ido, integer l1, const real *cc, real *ch, - const real *wa1, const real *wa2, const real *wa3, const real *wa4) -{ - /* Initialized data */ - - static const real tr11 = .309016994374947f; - static const real ti11 = .951056516295154f; - static const real tr12 = -.809016994374947f; - static const real ti12 = .587785252292473f; - - /* System generated locals */ - integer cc_offset, ch_offset; - - /* Local variables */ - integer i, k, ic; - real ci2, di2, ci4, ci5, di3, di4, di5, ci3, cr2, cr3, dr2, dr3, dr4, dr5, - cr5, cr4, ti2, ti3, ti5, ti4, tr2, tr3, tr4, tr5; - integer idp2; - - -#define cc_ref(a_1,a_2,a_3) cc[((a_3)*l1 + (a_2))*ido + a_1] -#define ch_ref(a_1,a_2,a_3) ch[((a_3)*5 + (a_2))*ido + a_1] - - /* Parameter adjustments */ - ch_offset = 1 + ido * 6; - ch -= ch_offset; - cc_offset = 1 + ido * (1 + l1); - cc -= cc_offset; - --wa1; - --wa2; - --wa3; - --wa4; - - /* Function Body */ - for (k = 1; k <= l1; ++k) { - cr2 = cc_ref(1, k, 5) + cc_ref(1, k, 2); - ci5 = cc_ref(1, k, 5) - cc_ref(1, k, 2); - cr3 = cc_ref(1, k, 4) + cc_ref(1, k, 3); - ci4 = cc_ref(1, k, 4) - cc_ref(1, k, 3); - ch_ref(1, 1, k) = cc_ref(1, k, 1) + cr2 + cr3; - ch_ref(ido, 2, k) = cc_ref(1, k, 1) + tr11 * cr2 + tr12 * cr3; - ch_ref(1, 3, k) = ti11 * ci5 + ti12 * ci4; - ch_ref(ido, 4, k) = cc_ref(1, k, 1) + tr12 * cr2 + tr11 * cr3; - ch_ref(1, 5, k) = ti12 * ci5 - ti11 * ci4; - } - if (ido == 1) { - return; - } - idp2 = ido + 2; - for (k = 1; k <= l1; ++k) { - for (i = 3; i <= ido; i += 2) { - ic = idp2 - i; - dr2 = wa1[i - 2] * cc_ref(i - 1, k, 2) + wa1[i - 1] * cc_ref(i, k, 2); - di2 = wa1[i - 2] * cc_ref(i, k, 2) - wa1[i - 1] * cc_ref(i - 1, k, 2); - dr3 = wa2[i - 2] * cc_ref(i - 1, k, 3) + wa2[i - 1] * cc_ref(i, k, 3); - di3 = wa2[i - 2] * cc_ref(i, k, 3) - wa2[i - 1] * cc_ref(i - 1, k, 3); - dr4 = wa3[i - 2] * cc_ref(i - 1, k, 4) + wa3[i - 1] * cc_ref(i, k, 4); - di4 = wa3[i - 2] * cc_ref(i, k, 4) - wa3[i - 1] * cc_ref(i - 1, k, 4); - dr5 = wa4[i - 2] * cc_ref(i - 1, k, 5) + wa4[i - 1] * cc_ref(i, k, 5); - di5 = wa4[i - 2] * cc_ref(i, k, 5) - wa4[i - 1] * cc_ref(i - 1, k, 5); - cr2 = dr2 + dr5; - ci5 = dr5 - dr2; - cr5 = di2 - di5; - ci2 = di2 + di5; - cr3 = dr3 + dr4; - ci4 = dr4 - dr3; - cr4 = di3 - di4; - ci3 = di3 + di4; - ch_ref(i - 1, 1, k) = cc_ref(i - 1, k, 1) + cr2 + cr3; - ch_ref(i, 1, k) = cc_ref(i, k, 1) + ci2 + ci3; - tr2 = cc_ref(i - 1, k, 1) + tr11 * cr2 + tr12 * cr3; - ti2 = cc_ref(i, k, 1) + tr11 * ci2 + tr12 * ci3; - tr3 = cc_ref(i - 1, k, 1) + tr12 * cr2 + tr11 * cr3; - ti3 = cc_ref(i, k, 1) + tr12 * ci2 + tr11 * ci3; - tr5 = ti11 * cr5 + ti12 * cr4; - ti5 = ti11 * ci5 + ti12 * ci4; - tr4 = ti12 * cr5 - ti11 * cr4; - ti4 = ti12 * ci5 - ti11 * ci4; - ch_ref(i - 1, 3, k) = tr2 + tr5; - ch_ref(ic - 1, 2, k) = tr2 - tr5; - ch_ref(i, 3, k) = ti2 + ti5; - ch_ref(ic, 2, k) = ti5 - ti2; - ch_ref(i - 1, 5, k) = tr3 + tr4; - ch_ref(ic - 1, 4, k) = tr3 - tr4; - ch_ref(i, 5, k) = ti3 + ti4; - ch_ref(ic, 4, k) = ti4 - ti3; - } - } -} /* radf5 */ - -#undef ch_ref -#undef cc_ref - - -static void radfg(integer ido, integer ip, integer l1, integer idl1, - real *cc, real *c1, real *c2, real *ch, real *ch2, const real *wa) -{ - /* System generated locals */ - integer ch_offset, cc_offset, - c1_offset, c2_offset, ch2_offset; - - /* Local variables */ - integer i, j, k, l, j2, ic, jc, lc, ik, is; - real dc2, ai1, ai2, ar1, ar2, ds2; - integer nbd; - real dcp, arg, dsp, ar1h, ar2h; - integer idp2, ipp2, idij, ipph; - - -#define c1_ref(a_1,a_2,a_3) c1[((a_3)*l1 + (a_2))*ido + a_1] -#define c2_ref(a_1,a_2) c2[(a_2)*idl1 + a_1] -#define cc_ref(a_1,a_2,a_3) cc[((a_3)*ip + (a_2))*ido + a_1] -#define ch_ref(a_1,a_2,a_3) ch[((a_3)*l1 + (a_2))*ido + a_1] -#define ch2_ref(a_1,a_2) ch2[(a_2)*idl1 + a_1] - - /* Parameter adjustments */ - ch_offset = 1 + ido * (1 + l1); - ch -= ch_offset; - c1_offset = 1 + ido * (1 + l1); - c1 -= c1_offset; - cc_offset = 1 + ido * (1 + ip); - cc -= cc_offset; - ch2_offset = 1 + idl1; - ch2 -= ch2_offset; - c2_offset = 1 + idl1; - c2 -= c2_offset; - --wa; - - /* Function Body */ - arg = (2*M_PI) / (real) (ip); - dcp = cos(arg); - dsp = sin(arg); - ipph = (ip + 1) / 2; - ipp2 = ip + 2; - idp2 = ido + 2; - nbd = (ido - 1) / 2; - if (ido == 1) { - for (ik = 1; ik <= idl1; ++ik) { - c2_ref(ik, 1) = ch2_ref(ik, 1); - } - } else { - for (ik = 1; ik <= idl1; ++ik) { - ch2_ref(ik, 1) = c2_ref(ik, 1); - } - for (j = 2; j <= ip; ++j) { - for (k = 1; k <= l1; ++k) { - ch_ref(1, k, j) = c1_ref(1, k, j); - } - } - if (nbd <= l1) { - is = -(ido); - for (j = 2; j <= ip; ++j) { - is += ido; - idij = is; - for (i = 3; i <= ido; i += 2) { - idij += 2; - for (k = 1; k <= l1; ++k) { - ch_ref(i - 1, k, j) = wa[idij - 1] * c1_ref(i - 1, k, j) - + wa[idij] * c1_ref(i, k, j); - ch_ref(i, k, j) = wa[idij - 1] * c1_ref(i, k, j) - wa[ - idij] * c1_ref(i - 1, k, j); - } - } - } - } else { - is = -(ido); - for (j = 2; j <= ip; ++j) { - is += ido; - for (k = 1; k <= l1; ++k) { - idij = is; - for (i = 3; i <= ido; i += 2) { - idij += 2; - ch_ref(i - 1, k, j) = wa[idij - 1] * c1_ref(i - 1, k, j) - + wa[idij] * c1_ref(i, k, j); - ch_ref(i, k, j) = wa[idij - 1] * c1_ref(i, k, j) - wa[ - idij] * c1_ref(i - 1, k, j); - } - } - } - } - if (nbd >= l1) { - for (j = 2; j <= ipph; ++j) { - jc = ipp2 - j; - for (k = 1; k <= l1; ++k) { - for (i = 3; i <= ido; i += 2) { - c1_ref(i - 1, k, j) = ch_ref(i - 1, k, j) + ch_ref(i - - 1, k, jc); - c1_ref(i - 1, k, jc) = ch_ref(i, k, j) - ch_ref(i, k, - jc); - c1_ref(i, k, j) = ch_ref(i, k, j) + ch_ref(i, k, jc); - c1_ref(i, k, jc) = ch_ref(i - 1, k, jc) - ch_ref(i - 1, - k, j); - } - } - } - } else { - for (j = 2; j <= ipph; ++j) { - jc = ipp2 - j; - for (i = 3; i <= ido; i += 2) { - for (k = 1; k <= l1; ++k) { - c1_ref(i - 1, k, j) = ch_ref(i - 1, k, j) + ch_ref(i - - 1, k, jc); - c1_ref(i - 1, k, jc) = ch_ref(i, k, j) - ch_ref(i, k, - jc); - c1_ref(i, k, j) = ch_ref(i, k, j) + ch_ref(i, k, jc); - c1_ref(i, k, jc) = ch_ref(i - 1, k, jc) - ch_ref(i - 1, - k, j); - } - } - } - } - } - for (j = 2; j <= ipph; ++j) { - jc = ipp2 - j; - for (k = 1; k <= l1; ++k) { - c1_ref(1, k, j) = ch_ref(1, k, j) + ch_ref(1, k, jc); - c1_ref(1, k, jc) = ch_ref(1, k, jc) - ch_ref(1, k, j); - } - } - - ar1 = 1.f; - ai1 = 0.f; - for (l = 2; l <= ipph; ++l) { - lc = ipp2 - l; - ar1h = dcp * ar1 - dsp * ai1; - ai1 = dcp * ai1 + dsp * ar1; - ar1 = ar1h; - for (ik = 1; ik <= idl1; ++ik) { - ch2_ref(ik, l) = c2_ref(ik, 1) + ar1 * c2_ref(ik, 2); - ch2_ref(ik, lc) = ai1 * c2_ref(ik, ip); - } - dc2 = ar1; - ds2 = ai1; - ar2 = ar1; - ai2 = ai1; - for (j = 3; j <= ipph; ++j) { - jc = ipp2 - j; - ar2h = dc2 * ar2 - ds2 * ai2; - ai2 = dc2 * ai2 + ds2 * ar2; - ar2 = ar2h; - for (ik = 1; ik <= idl1; ++ik) { - ch2_ref(ik, l) = ch2_ref(ik, l) + ar2 * c2_ref(ik, j); - ch2_ref(ik, lc) = ch2_ref(ik, lc) + ai2 * c2_ref(ik, jc); - } - } - } - for (j = 2; j <= ipph; ++j) { - for (ik = 1; ik <= idl1; ++ik) { - ch2_ref(ik, 1) = ch2_ref(ik, 1) + c2_ref(ik, j); - } - } - - if (ido >= l1) { - for (k = 1; k <= l1; ++k) { - for (i = 1; i <= ido; ++i) { - cc_ref(i, 1, k) = ch_ref(i, k, 1); - } - } - } else { - for (i = 1; i <= ido; ++i) { - for (k = 1; k <= l1; ++k) { - cc_ref(i, 1, k) = ch_ref(i, k, 1); - } - } - } - for (j = 2; j <= ipph; ++j) { - jc = ipp2 - j; - j2 = j + j; - for (k = 1; k <= l1; ++k) { - cc_ref(ido, j2 - 2, k) = ch_ref(1, k, j); - cc_ref(1, j2 - 1, k) = ch_ref(1, k, jc); - } - } - if (ido == 1) { - return; - } - if (nbd >= l1) { - for (j = 2; j <= ipph; ++j) { - jc = ipp2 - j; - j2 = j + j; - for (k = 1; k <= l1; ++k) { - for (i = 3; i <= ido; i += 2) { - ic = idp2 - i; - cc_ref(i - 1, j2 - 1, k) = ch_ref(i - 1, k, j) + ch_ref( - i - 1, k, jc); - cc_ref(ic - 1, j2 - 2, k) = ch_ref(i - 1, k, j) - ch_ref( - i - 1, k, jc); - cc_ref(i, j2 - 1, k) = ch_ref(i, k, j) + ch_ref(i, k, - jc); - cc_ref(ic, j2 - 2, k) = ch_ref(i, k, jc) - ch_ref(i, k, j) - ; - } - } - } - } else { - for (j = 2; j <= ipph; ++j) { - jc = ipp2 - j; - j2 = j + j; - for (i = 3; i <= ido; i += 2) { - ic = idp2 - i; - for (k = 1; k <= l1; ++k) { - cc_ref(i - 1, j2 - 1, k) = ch_ref(i - 1, k, j) + ch_ref( - i - 1, k, jc); - cc_ref(ic - 1, j2 - 2, k) = ch_ref(i - 1, k, j) - ch_ref( - i - 1, k, jc); - cc_ref(i, j2 - 1, k) = ch_ref(i, k, j) + ch_ref(i, k, - jc); - cc_ref(ic, j2 - 2, k) = ch_ref(i, k, jc) - ch_ref(i, k, j) - ; - } - } - } - } -} /* radfg */ - -#undef ch2_ref -#undef ch_ref -#undef cc_ref -#undef c2_ref -#undef c1_ref - - -static void cfftb1(integer n, real *c, real *ch, const real *wa, integer *ifac) -{ - integer i, k1, l1, l2, na, nf, ip, iw, ix2, ix3, ix4, nac, ido, - idl1, idot; - - /* Function Body */ - nf = ifac[1]; - na = 0; - l1 = 1; - iw = 0; - for (k1 = 1; k1 <= nf; ++k1) { - ip = ifac[k1 + 1]; - l2 = ip * l1; - ido = n / l2; - idot = ido + ido; - idl1 = idot * l1; - switch (ip) { - case 4: - ix2 = iw + idot; - ix3 = ix2 + idot; - passb4(idot, l1, na?ch:c, na?c:ch, &wa[iw], &wa[ix2], &wa[ix3]); - na = 1 - na; - break; - case 2: - passb2(idot, l1, na?ch:c, na?c:ch, &wa[iw]); - na = 1 - na; - break; - case 3: - ix2 = iw + idot; - passb3(idot, l1, na?ch:c, na?c:ch, &wa[iw], &wa[ix2]); - na = 1 - na; - break; - case 5: - ix2 = iw + idot; - ix3 = ix2 + idot; - ix4 = ix3 + idot; - passfb5(idot, l1, na?ch:c, na?c:ch, &wa[iw], &wa[ix2], &wa[ix3], &wa[ix4], +1); - na = 1 - na; - break; - default: - if (na == 0) { - passfb(&nac, idot, ip, l1, idl1, c, c, c, ch, ch, &wa[iw], +1); - } else { - passfb(&nac, idot, ip, l1, idl1, ch, ch, ch, c, c, &wa[iw], +1); - } - if (nac != 0) { - na = 1 - na; - } - break; - } - l1 = l2; - iw += (ip - 1) * idot; - } - if (na == 0) { - return; - } - for (i = 0; i < 2*n; ++i) { - c[i] = ch[i]; - } -} /* cfftb1 */ - -void cfftb(integer n, real *c, real *wsave) -{ - integer iw1, iw2; - - /* Parameter adjustments */ - --wsave; - --c; - - /* Function Body */ - if (n == 1) { - return; - } - iw1 = 2*n + 1; - iw2 = iw1 + 2*n; - cfftb1(n, &c[1], &wsave[1], &wsave[iw1], (int*)&wsave[iw2]); -} /* cfftb */ - -static void cfftf1(integer n, real *c, real *ch, const real *wa, integer *ifac) -{ - /* Local variables */ - integer i, k1, l1, l2, na, nf, ip, iw, ix2, ix3, ix4, nac, ido, - idl1, idot; - - /* Function Body */ - nf = ifac[1]; - na = 0; - l1 = 1; - iw = 0; - for (k1 = 1; k1 <= nf; ++k1) { - ip = ifac[k1 + 1]; - l2 = ip * l1; - ido = n / l2; - idot = ido + ido; - idl1 = idot * l1; - switch (ip) { - case 4: - ix2 = iw + idot; - ix3 = ix2 + idot; - passf4(idot, l1, na?ch:c, na?c:ch, &wa[iw], &wa[ix2], &wa[ix3]); - na = 1 - na; - break; - case 2: - passf2(idot, l1, na?ch:c, na?c:ch, &wa[iw]); - na = 1 - na; - break; - case 3: - ix2 = iw + idot; - passf3(idot, l1, na?ch:c, na?c:ch, &wa[iw], &wa[ix2]); - na = 1 - na; - break; - case 5: - ix2 = iw + idot; - ix3 = ix2 + idot; - ix4 = ix3 + idot; - passfb5(idot, l1, na?ch:c, na?c:ch, &wa[iw], &wa[ix2], &wa[ix3], &wa[ix4], -1); - na = 1 - na; - break; - default: - if (na == 0) { - passfb(&nac, idot, ip, l1, idl1, c, c, c, ch, ch, &wa[iw], -1); - } else { - passfb(&nac, idot, ip, l1, idl1, ch, ch, ch, c, c, &wa[iw], -1); - } - if (nac != 0) { - na = 1 - na; - } - break; - } - l1 = l2; - iw += (ip - 1)*idot; - } - if (na == 0) { - return; - } - for (i = 0; i < 2*n; ++i) { - c[i] = ch[i]; - } -} /* cfftf1 */ - -void cfftf(integer n, real *c, real *wsave) -{ - integer iw1, iw2; - - /* Parameter adjustments */ - --wsave; - --c; - - /* Function Body */ - if (n == 1) { - return; - } - iw1 = 2*n + 1; - iw2 = iw1 + 2*n; - cfftf1(n, &c[1], &wsave[1], &wsave[iw1], (int*)&wsave[iw2]); -} /* cfftf */ - -static int decompose(integer n, integer *ifac, integer ntryh[4]) { - integer ntry=0, nl = n, nf = 0, nq, nr, i, j = 0; - do { - if (j < 4) { - ntry = ntryh[j]; - } else { - ntry += 2; - } - ++j; - L104: - nq = nl / ntry; - nr = nl - ntry * nq; - if (nr != 0) continue; - ++nf; - ifac[nf + 2] = ntry; - nl = nq; - if (ntry == 2 && nf != 1) { - for (i = 2; i <= nf; ++i) { - integer ib = nf - i + 2; - ifac[ib + 2] = ifac[ib + 1]; - } - ifac[3] = 2; - } - if (nl != 1) { - goto L104; - } - } while (nl != 1); - ifac[1] = n; - ifac[2] = nf; - return nf; -} - -static void cffti1(integer n, real *wa, integer *ifac) -{ - static integer ntryh[4] = { 3,4,2,5 }; - - /* Local variables */ - integer i, j, i1, k1, l1, l2; - real fi; - integer ld, ii, nf, ip; - real arg; - integer ido, ipm; - real argh; - integer idot; - real argld; - - /* Parameter adjustments */ - --ifac; - --wa; - - nf = decompose(n, ifac, ntryh); - - argh = (2*M_PI) / (real) (n); - i = 2; - l1 = 1; - for (k1 = 1; k1 <= nf; ++k1) { - ip = ifac[k1 + 2]; - ld = 0; - l2 = l1 * ip; - ido = n / l2; - idot = ido + ido + 2; - ipm = ip - 1; - for (j = 1; j <= ipm; ++j) { - i1 = i; - wa[i - 1] = 1.f; - wa[i] = 0.f; - ld += l1; - fi = 0.f; - argld = (real) ld * argh; - for (ii = 4; ii <= idot; ii += 2) { - i += 2; - fi += 1.f; - arg = fi * argld; - wa[i - 1] = cos(arg); - wa[i] = sin(arg); - } - if (ip > 5) { - wa[i1 - 1] = wa[i - 1]; - wa[i1] = wa[i]; - }; - } - l1 = l2; - } -} /* cffti1 */ - -void cffti(integer n, real *wsave) -{ - integer iw1, iw2; - /* Parameter adjustments */ - --wsave; - - /* Function Body */ - if (n == 1) { - return; - } - iw1 = 2*n + 1; - iw2 = iw1 + 2*n; - cffti1(n, &wsave[iw1], (int*)&wsave[iw2]); - return; -} /* cffti */ - -static void rfftb1(integer n, real *c, real *ch, const real *wa, integer *ifac) -{ - /* Local variables */ - integer i, k1, l1, l2, na, nf, ip, iw, ix2, ix3, ix4, ido, idl1; - - /* Function Body */ - nf = ifac[1]; - na = 0; - l1 = 1; - iw = 0; - for (k1 = 1; k1 <= nf; ++k1) { - ip = ifac[k1 + 1]; - l2 = ip * l1; - ido = n / l2; - idl1 = ido * l1; - switch (ip) { - case 4: - ix2 = iw + ido; - ix3 = ix2 + ido; - radb4(ido, l1, na?ch:c, na?c:ch, &wa[iw], &wa[ix2], &wa[ix3]); - na = 1 - na; - break; - case 2: - radb2(ido, l1, na?ch:c, na?c:ch, &wa[iw]); - na = 1 - na; - break; - case 3: - ix2 = iw + ido; - radb3(ido, l1, na?ch:c, na?c:ch, &wa[iw], &wa[ix2]); - na = 1 - na; - break; - case 5: - ix2 = iw + ido; - ix3 = ix2 + ido; - ix4 = ix3 + ido; - radb5(ido, l1, na?ch:c, na?c:ch, &wa[iw], &wa[ix2], &wa[ix3], &wa[ix4]); - na = 1 - na; - break; - default: - if (na == 0) { - radbg(ido, ip, l1, idl1, c, c, c, ch, ch, &wa[iw]); - } else { - radbg(ido, ip, l1, idl1, ch, ch, ch, c, c, &wa[iw]); - } - if (ido == 1) { - na = 1 - na; - } - break; - } - l1 = l2; - iw += (ip - 1) * ido; - } - if (na == 0) { - return; - } - for (i = 0; i < n; ++i) { - c[i] = ch[i]; - } -} /* rfftb1 */ - -static void rfftf1(integer n, real *c, real *ch, const real *wa, integer *ifac) -{ - /* Local variables */ - integer i, k1, l1, l2, na, kh, nf, ip, iw, ix2, ix3, ix4, ido, idl1; - - /* Function Body */ - nf = ifac[1]; - na = 1; - l2 = n; - iw = n-1; - for (k1 = 1; k1 <= nf; ++k1) { - kh = nf - k1; - ip = ifac[kh + 2]; - l1 = l2 / ip; - ido = n / l2; - idl1 = ido * l1; - iw -= (ip - 1) * ido; - na = 1 - na; - switch (ip) { - case 4: - ix2 = iw + ido; - ix3 = ix2 + ido; - radf4(ido, l1, na ? ch : c, na ? c : ch, &wa[iw], &wa[ix2], &wa[ix3]); - break; - case 2: - radf2(ido, l1, na ? ch : c, na ? c : ch, &wa[iw]); - break; - case 3: - ix2 = iw + ido; - radf3(ido, l1, na ? ch : c, na ? c : ch, &wa[iw], &wa[ix2]); - break; - case 5: - ix2 = iw + ido; - ix3 = ix2 + ido; - ix4 = ix3 + ido; - radf5(ido, l1, na ? ch : c, na ? c : ch, &wa[iw], &wa[ix2], &wa[ix3], &wa[ix4]); - break; - default: - if (ido == 1) { - na = 1 - na; - } - if (na == 0) { - radfg(ido, ip, l1, idl1, c, c, c, ch, ch, &wa[iw]); - na = 1; - } else { - radfg(ido, ip, l1, idl1, ch, ch, ch, c, c, &wa[iw]); - na = 0; - } - break; - } - l2 = l1; - } - if (na == 1) { - return; - } - for (i = 0; i < n; ++i) { - c[i] = ch[i]; - } -} - -void rfftb(integer n, real *r, real *wsave) -{ - - /* Parameter adjustments */ - --wsave; - --r; - - /* Function Body */ - if (n == 1) { - return; - } - rfftb1(n, &r[1], &wsave[1], &wsave[n + 1], (int*)&wsave[(n << 1) + 1]); -} /* rfftb */ - -static void rffti1(integer n, real *wa, integer *ifac) -{ - static integer ntryh[4] = { 4,2,3,5 }; - - /* Local variables */ - integer i, j, k1, l1, l2; - real fi; - integer ld, ii, nf, ip, is; - real arg; - integer ido, ipm; - integer nfm1; - real argh; - real argld; - - /* Parameter adjustments */ - --ifac; - --wa; - - nf = decompose(n, ifac, ntryh); - - argh = (2*M_PI) / (real) (n); - is = 0; - nfm1 = nf - 1; - l1 = 1; - if (nfm1 == 0) { - return; - } - for (k1 = 1; k1 <= nfm1; ++k1) { - ip = ifac[k1 + 2]; - ld = 0; - l2 = l1 * ip; - ido = n / l2; - ipm = ip - 1; - for (j = 1; j <= ipm; ++j) { - ld += l1; - i = is; - argld = (real) ld * argh; - fi = 0.f; - for (ii = 3; ii <= ido; ii += 2) { - i += 2; - fi += 1.f; - arg = fi * argld; - wa[i - 1] = cos(arg); - wa[i] = sin(arg); - } - is += ido; - } - l1 = l2; - } -} /* rffti1 */ - -void rfftf(integer n, real *r, real *wsave) -{ - - /* Parameter adjustments */ - --wsave; - --r; - - /* Function Body */ - if (n == 1) { - return; - } - rfftf1(n, &r[1], &wsave[1], &wsave[n + 1], (int*)&wsave[(n << 1) + 1]); -} /* rfftf */ - -void rffti(integer n, real *wsave) -{ - /* Parameter adjustments */ - --wsave; - - /* Function Body */ - if (n == 1) { - return; - } - rffti1(n, &wsave[n + 1], (int*)&wsave[(n << 1) + 1]); - return; -} /* rffti */ - -static void cosqb1(integer n, real *x, real *w, real *xh) -{ - /* Local variables */ - integer i, k, kc, np2, ns2; - real xim1; - integer modn; - - /* Parameter adjustments */ - --xh; - --w; - --x; - - /* Function Body */ - ns2 = (n + 1) / 2; - np2 = n + 2; - for (i = 3; i <= n; i += 2) { - xim1 = x[i - 1] + x[i]; - x[i] -= x[i - 1]; - x[i - 1] = xim1; - } - x[1] += x[1]; - modn = n % 2; - if (modn == 0) { - x[n] += x[n]; - } - rfftb(n, &x[1], &xh[1]); - for (k = 2; k <= ns2; ++k) { - kc = np2 - k; - xh[k] = w[k - 1] * x[kc] + w[kc - 1] * x[k]; - xh[kc] = w[k - 1] * x[k] - w[kc - 1] * x[kc]; - } - if (modn == 0) { - x[ns2 + 1] = w[ns2] * (x[ns2 + 1] + x[ns2 + 1]); - } - for (k = 2; k <= ns2; ++k) { - kc = np2 - k; - x[k] = xh[k] + xh[kc]; - x[kc] = xh[k] - xh[kc]; - } - x[1] += x[1]; -} /* cosqb1 */ - -void cosqb(integer n, real *x, real *wsave) -{ - static const real tsqrt2 = 2.82842712474619f; - - /* Local variables */ - real x1; - - /* Parameter adjustments */ - --wsave; - --x; - - if (n < 2) { - x[1] *= 4.f; - } else if (n == 2) { - x1 = (x[1] + x[2]) * 4.f; - x[2] = tsqrt2 * (x[1] - x[2]); - x[1] = x1; - } else { - cosqb1(n, &x[1], &wsave[1], &wsave[n + 1]); - } -} /* cosqb */ - -static void cosqf1(integer n, real *x, real *w, real *xh) -{ - /* Local variables */ - integer i, k, kc, np2, ns2; - real xim1; - integer modn; - - /* Parameter adjustments */ - --xh; - --w; - --x; - - /* Function Body */ - ns2 = (n + 1) / 2; - np2 = n + 2; - for (k = 2; k <= ns2; ++k) { - kc = np2 - k; - xh[k] = x[k] + x[kc]; - xh[kc] = x[k] - x[kc]; - } - modn = n % 2; - if (modn == 0) { - xh[ns2 + 1] = x[ns2 + 1] + x[ns2 + 1]; - } - for (k = 2; k <= ns2; ++k) { - kc = np2 - k; - x[k] = w[k - 1] * xh[kc] + w[kc - 1] * xh[k]; - x[kc] = w[k - 1] * xh[k] - w[kc - 1] * xh[kc]; - } - if (modn == 0) { - x[ns2 + 1] = w[ns2] * xh[ns2 + 1]; - } - rfftf(n, &x[1], &xh[1]); - for (i = 3; i <= n; i += 2) { - xim1 = x[i - 1] - x[i]; - x[i] = x[i - 1] + x[i]; - x[i - 1] = xim1; - } -} /* cosqf1 */ - -void cosqf(integer n, real *x, real *wsave) -{ - static const real sqrt2 = 1.4142135623731f; - - /* Local variables */ - real tsqx; - - /* Parameter adjustments */ - --wsave; - --x; - - if (n == 2) { - tsqx = sqrt2 * x[2]; - x[2] = x[1] - tsqx; - x[1] += tsqx; - } else if (n > 2) { - cosqf1(n, &x[1], &wsave[1], &wsave[n + 1]); - } -} /* cosqf */ - -void cosqi(integer n, real *wsave) -{ - /* Local variables */ - integer k; - real fk, dt; - - /* Parameter adjustments */ - --wsave; - - dt = M_PI/2 / (real) (n); - fk = 0.f; - for (k = 1; k <= n; ++k) { - fk += 1.f; - wsave[k] = cos(fk * dt); - } - rffti(n, &wsave[n + 1]); -} /* cosqi */ - -void cost(integer n, real *x, real *wsave) -{ - /* Local variables */ - integer i, k; - real c1, t1, t2; - integer kc; - real xi; - integer nm1, np1; - real x1h; - integer ns2; - real tx2, x1p3, xim2; - integer modn; - - /* Parameter adjustments */ - --wsave; - --x; - - /* Function Body */ - nm1 = n - 1; - np1 = n + 1; - ns2 = n / 2; - if (n < 2) { - } else if (n == 2) { - x1h = x[1] + x[2]; - x[2] = x[1] - x[2]; - x[1] = x1h; - } else if (n == 3) { - x1p3 = x[1] + x[3]; - tx2 = x[2] + x[2]; - x[2] = x[1] - x[3]; - x[1] = x1p3 + tx2; - x[3] = x1p3 - tx2; - } else { - c1 = x[1] - x[n]; - x[1] += x[n]; - for (k = 2; k <= ns2; ++k) { - kc = np1 - k; - t1 = x[k] + x[kc]; - t2 = x[k] - x[kc]; - c1 += wsave[kc] * t2; - t2 = wsave[k] * t2; - x[k] = t1 - t2; - x[kc] = t1 + t2; - } - modn = n % 2; - if (modn != 0) { - x[ns2 + 1] += x[ns2 + 1]; - } - rfftf(nm1, &x[1], &wsave[n + 1]); - xim2 = x[2]; - x[2] = c1; - for (i = 4; i <= n; i += 2) { - xi = x[i]; - x[i] = x[i - 2] - x[i - 1]; - x[i - 1] = xim2; - xim2 = xi; - } - if (modn != 0) { - x[n] = xim2; - } - } -} /* cost */ - -void costi(integer n, real *wsave) -{ - /* Initialized data */ - - /* Local variables */ - integer k, kc; - real fk, dt; - integer nm1, np1, ns2; - - /* Parameter adjustments */ - --wsave; - - /* Function Body */ - if (n <= 3) { - return; - } - nm1 = n - 1; - np1 = n + 1; - ns2 = n / 2; - dt = M_PI / (real) nm1; - fk = 0.f; - for (k = 2; k <= ns2; ++k) { - kc = np1 - k; - fk += 1.f; - wsave[k] = sin(fk * dt) * 2.f; - wsave[kc] = cos(fk * dt) * 2.f; - } - rffti(nm1, &wsave[n + 1]); -} /* costi */ - -void sinqb(integer n, real *x, real *wsave) -{ - /* Local variables */ - integer k, kc, ns2; - real xhold; - - /* Parameter adjustments */ - --wsave; - --x; - - /* Function Body */ - if (n <= 1) { - x[1] *= 4.f; - return; - } - ns2 = n / 2; - for (k = 2; k <= n; k += 2) { - x[k] = -x[k]; - } - cosqb(n, &x[1], &wsave[1]); - for (k = 1; k <= ns2; ++k) { - kc = n - k; - xhold = x[k]; - x[k] = x[kc + 1]; - x[kc + 1] = xhold; - } -} /* sinqb */ - -void sinqf(integer n, real *x, real *wsave) -{ - /* Local variables */ - integer k, kc, ns2; - real xhold; - - /* Parameter adjustments */ - --wsave; - --x; - - /* Function Body */ - if (n == 1) { - return; - } - ns2 = n / 2; - for (k = 1; k <= ns2; ++k) { - kc = n - k; - xhold = x[k]; - x[k] = x[kc + 1]; - x[kc + 1] = xhold; - } - cosqf(n, &x[1], &wsave[1]); - for (k = 2; k <= n; k += 2) { - x[k] = -x[k]; - } -} /* sinqf */ - -void sinqi(integer n, real *wsave) -{ - - /* Parameter adjustments */ - --wsave; - - /* Function Body */ - cosqi(n, &wsave[1]); -} /* sinqi */ - -static void sint1(integer n, real *war, real *was, real *xh, real * - x, integer *ifac) -{ - /* Initialized data */ - - static const real sqrt3 = 1.73205080756888f; - - /* Local variables */ - integer i, k; - real t1, t2; - integer kc, np1, ns2, modn; - real xhold; - - /* Parameter adjustments */ - --ifac; - --x; - --xh; - --was; - --war; - - /* Function Body */ - for (i = 1; i <= n; ++i) { - xh[i] = war[i]; - war[i] = x[i]; - } - - if (n < 2) { - xh[1] += xh[1]; - } else if (n == 2) { - xhold = sqrt3 * (xh[1] + xh[2]); - xh[2] = sqrt3 * (xh[1] - xh[2]); - xh[1] = xhold; - } else { - np1 = n + 1; - ns2 = n / 2; - x[1] = 0.f; - for (k = 1; k <= ns2; ++k) { - kc = np1 - k; - t1 = xh[k] - xh[kc]; - t2 = was[k] * (xh[k] + xh[kc]); - x[k + 1] = t1 + t2; - x[kc + 1] = t2 - t1; - } - modn = n % 2; - if (modn != 0) { - x[ns2 + 2] = xh[ns2 + 1] * 4.f; - } - rfftf1(np1, &x[1], &xh[1], &war[1], &ifac[1]); - xh[1] = x[1] * .5f; - for (i = 3; i <= n; i += 2) { - xh[i - 1] = -x[i]; - xh[i] = xh[i - 2] + x[i - 1]; - } - if (modn == 0) { - xh[n] = -x[n + 1]; - } - } - for (i = 1; i <= n; ++i) { - x[i] = war[i]; - war[i] = xh[i]; - } -} /* sint1 */ - -void sinti(integer n, real *wsave) -{ - /* Local variables */ - integer k; - real dt; - integer np1, ns2; - - /* Parameter adjustments */ - --wsave; - - /* Function Body */ - if (n <= 1) { - return; - } - ns2 = n / 2; - np1 = n + 1; - dt = M_PI / (real) np1; - for (k = 1; k <= ns2; ++k) { - wsave[k] = sin(k * dt) * 2.f; - } - rffti(np1, &wsave[ns2 + 1]); -} /* sinti */ - -void sint(integer n, real *x, real *wsave) -{ - integer np1, iw1, iw2, iw3; - - /* Parameter adjustments */ - --wsave; - --x; - - /* Function Body */ - np1 = n + 1; - iw1 = n / 2 + 1; - iw2 = iw1 + np1; - iw3 = iw2 + np1; - sint1(n, &x[1], &wsave[1], &wsave[iw1], &wsave[iw2], (int*)&wsave[iw3]); -} /* sint */ - -#ifdef TESTING_FFTPACK -#include - -int main(void) -{ - static integer nd[] = { 120,91,54,49,32,28,24,8,4,3,2 }; - - /* System generated locals */ - real r1, r2, r3; - f77complex q1, q2, q3; - - /* Local variables */ - integer i, j, k, n; - real w[2000], x[200], y[200], cf, fn, dt; - f77complex cx[200], cy[200]; - real xh[200]; - integer nz, nm1, np1, ns2; - real arg, tfn; - real sum, arg1, arg2; - real sum1, sum2, dcfb; - integer modn; - real rftb, rftf; - real sqrt2; - real rftfb; - real costt, sintt, dcfftb, dcfftf, cosqfb, costfb; - real sinqfb; - real sintfb; - real cosqbt, cosqft, sinqbt, sinqft; - - - - /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ - - /* VERSION 4 APRIL 1985 */ - - /* A TEST DRIVER FOR */ - /* A PACKAGE OF FORTRAN SUBPROGRAMS FOR THE FAST FOURIER */ - /* TRANSFORM OF PERIODIC AND OTHER SYMMETRIC SEQUENCES */ - - /* BY */ - - /* PAUL N SWARZTRAUBER */ - - /* NATIONAL CENTER FOR ATMOSPHERIC RESEARCH BOULDER,COLORADO 80307 */ - - /* WHICH IS SPONSORED BY THE NATIONAL SCIENCE FOUNDATION */ - - /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ - - - /* THIS PROGRAM TESTS THE PACKAGE OF FAST FOURIER */ - /* TRANSFORMS FOR BOTH COMPLEX AND REAL PERIODIC SEQUENCES AND */ - /* CERTIAN OTHER SYMMETRIC SEQUENCES THAT ARE LISTED BELOW. */ - - /* 1. RFFTI INITIALIZE RFFTF AND RFFTB */ - /* 2. RFFTF FORWARD TRANSFORM OF A REAL PERIODIC SEQUENCE */ - /* 3. RFFTB BACKWARD TRANSFORM OF A REAL COEFFICIENT ARRAY */ - - /* 4. EZFFTI INITIALIZE EZFFTF AND EZFFTB */ - /* 5. EZFFTF A SIMPLIFIED REAL PERIODIC FORWARD TRANSFORM */ - /* 6. EZFFTB A SIMPLIFIED REAL PERIODIC BACKWARD TRANSFORM */ - - /* 7. SINTI INITIALIZE SINT */ - /* 8. SINT SINE TRANSFORM OF A REAL ODD SEQUENCE */ - - /* 9. COSTI INITIALIZE COST */ - /* 10. COST COSINE TRANSFORM OF A REAL EVEN SEQUENCE */ - - /* 11. SINQI INITIALIZE SINQF AND SINQB */ - /* 12. SINQF FORWARD SINE TRANSFORM WITH ODD WAVE NUMBERS */ - /* 13. SINQB UNNORMALIZED INVERSE OF SINQF */ - - /* 14. COSQI INITIALIZE COSQF AND COSQB */ - /* 15. COSQF FORWARD COSINE TRANSFORM WITH ODD WAVE NUMBERS */ - /* 16. COSQB UNNORMALIZED INVERSE OF COSQF */ - - /* 17. CFFTI INITIALIZE CFFTF AND CFFTB */ - /* 18. CFFTF FORWARD TRANSFORM OF A COMPLEX PERIODIC SEQUENCE */ - /* 19. CFFTB UNNORMALIZED INVERSE OF CFFTF */ - - - sqrt2 = sqrt(2.f); - int all_ok = 1; - for (nz = 1; nz <= (int)(sizeof nd/sizeof nd[0]); ++nz) { - n = nd[nz - 1]; - modn = n % 2; - fn = (real) n; - tfn = fn + fn; - np1 = n + 1; - nm1 = n - 1; - for (j = 1; j <= np1; ++j) { - x[j - 1] = sin((real) j * sqrt2); - y[j - 1] = x[j - 1]; - xh[j - 1] = x[j - 1]; - } - - /* TEST SUBROUTINES RFFTI,RFFTF AND RFFTB */ - - rffti(n, w); - dt = (2*M_PI) / fn; - ns2 = (n + 1) / 2; - if (ns2 < 2) { - goto L104; - } - for (k = 2; k <= ns2; ++k) { - sum1 = 0.f; - sum2 = 0.f; - arg = (real) (k - 1) * dt; - for (i = 1; i <= n; ++i) { - arg1 = (real) (i - 1) * arg; - sum1 += x[i - 1] * cos(arg1); - sum2 += x[i - 1] * sin(arg1); - } - y[(k << 1) - 3] = sum1; - y[(k << 1) - 2] = -sum2; - } - L104: - sum1 = 0.f; - sum2 = 0.f; - for (i = 1; i <= nm1; i += 2) { - sum1 += x[i - 1]; - sum2 += x[i]; - } - if (modn == 1) { - sum1 += x[n - 1]; - } - y[0] = sum1 + sum2; - if (modn == 0) { - y[n - 1] = sum1 - sum2; - } - rfftf(n, x, w); - rftf = 0.f; - for (i = 1; i <= n; ++i) { - /* Computing MAX */ - r2 = rftf, r3 = (r1 = x[i - 1] - y[i - 1], fabs(r1)); - rftf = dmax(r2,r3); - x[i - 1] = xh[i - 1]; - } - rftf /= fn; - for (i = 1; i <= n; ++i) { - sum = x[0] * .5f; - arg = (real) (i - 1) * dt; - if (ns2 < 2) { - goto L108; - } - for (k = 2; k <= ns2; ++k) { - arg1 = (real) (k - 1) * arg; - sum = sum + x[(k << 1) - 3] * cos(arg1) - x[(k << 1) - 2] * - sin(arg1); - } - L108: - if (modn == 0) { - sum += (real)pow(-1, i-1) * .5f * x[n - 1]; - } - y[i - 1] = sum + sum; - } - rfftb(n, x, w); - rftb = 0.f; - for (i = 1; i <= n; ++i) { - /* Computing MAX */ - r2 = rftb, r3 = (r1 = x[i - 1] - y[i - 1], fabs(r1)); - rftb = dmax(r2,r3); - x[i - 1] = xh[i - 1]; - y[i - 1] = xh[i - 1]; - } - rfftb(n, y, w); - rfftf(n, y, w); - cf = 1.f / fn; - rftfb = 0.f; - for (i = 1; i <= n; ++i) { - /* Computing MAX */ - r2 = rftfb, r3 = (r1 = cf * y[i - 1] - x[i - 1], fabs( - r1)); - rftfb = dmax(r2,r3); - } - - /* TEST SUBROUTINES SINTI AND SINT */ - - dt = M_PI / fn; - for (i = 1; i <= nm1; ++i) { - x[i - 1] = xh[i - 1]; - } - for (i = 1; i <= nm1; ++i) { - y[i - 1] = 0.f; - arg1 = (real) i * dt; - for (k = 1; k <= nm1; ++k) { - y[i - 1] += x[k - 1] * sin((real) k * arg1); - } - y[i - 1] += y[i - 1]; - } - sinti(nm1, w); - sint(nm1, x, w); - cf = .5f / fn; - sintt = 0.f; - for (i = 1; i <= nm1; ++i) { - /* Computing MAX */ - r2 = sintt, r3 = (r1 = x[i - 1] - y[i - 1], fabs(r1)); - sintt = dmax(r2,r3); - x[i - 1] = xh[i - 1]; - y[i - 1] = x[i - 1]; - } - sintt = cf * sintt; - sint(nm1, x, w); - sint(nm1, x, w); - sintfb = 0.f; - for (i = 1; i <= nm1; ++i) { - /* Computing MAX */ - r2 = sintfb, r3 = (r1 = cf * x[i - 1] - y[i - 1], fabs( - r1)); - sintfb = dmax(r2,r3); - } - - /* TEST SUBROUTINES COSTI AND COST */ - - for (i = 1; i <= np1; ++i) { - x[i - 1] = xh[i - 1]; - } - for (i = 1; i <= np1; ++i) { - y[i - 1] = (x[0] + (real) pow(-1, i+1) * x[n]) * .5f; - arg = (real) (i - 1) * dt; - for (k = 2; k <= n; ++k) { - y[i - 1] += x[k - 1] * cos((real) (k - 1) * arg); - } - y[i - 1] += y[i - 1]; - } - costi(np1, w); - cost(np1, x, w); - costt = 0.f; - for (i = 1; i <= np1; ++i) { - /* Computing MAX */ - r2 = costt, r3 = (r1 = x[i - 1] - y[i - 1], fabs(r1)); - costt = dmax(r2,r3); - x[i - 1] = xh[i - 1]; - y[i - 1] = xh[i - 1]; - } - costt = cf * costt; - cost(np1, x, w); - cost(np1, x, w); - costfb = 0.f; - for (i = 1; i <= np1; ++i) { - /* Computing MAX */ - r2 = costfb, r3 = (r1 = cf * x[i - 1] - y[i - 1], fabs( - r1)); - costfb = dmax(r2,r3); - } - - /* TEST SUBROUTINES SINQI,SINQF AND SINQB */ - - cf = .25f / fn; - for (i = 1; i <= n; ++i) { - y[i - 1] = xh[i - 1]; - } - dt = M_PI / (fn + fn); - for (i = 1; i <= n; ++i) { - x[i - 1] = 0.f; - arg = dt * (real) i; - for (k = 1; k <= n; ++k) { - x[i - 1] += y[k - 1] * sin((real) (k + k - 1) * arg); - } - x[i - 1] *= 4.f; - } - sinqi(n, w); - sinqb(n, y, w); - sinqbt = 0.f; - for (i = 1; i <= n; ++i) { - /* Computing MAX */ - r2 = sinqbt, r3 = (r1 = y[i - 1] - x[i - 1], fabs(r1)) - ; - sinqbt = dmax(r2,r3); - x[i - 1] = xh[i - 1]; - } - sinqbt = cf * sinqbt; - for (i = 1; i <= n; ++i) { - arg = (real) (i + i - 1) * dt; - y[i - 1] = (real) pow(-1, i+1) * .5f * x[n - 1]; - for (k = 1; k <= nm1; ++k) { - y[i - 1] += x[k - 1] * sin((real) k * arg); - } - y[i - 1] += y[i - 1]; - } - sinqf(n, x, w); - sinqft = 0.f; - for (i = 1; i <= n; ++i) { - /* Computing MAX */ - r2 = sinqft, r3 = (r1 = x[i - 1] - y[i - 1], fabs(r1)) - ; - sinqft = dmax(r2,r3); - y[i - 1] = xh[i - 1]; - x[i - 1] = xh[i - 1]; - } - sinqf(n, y, w); - sinqb(n, y, w); - sinqfb = 0.f; - for (i = 1; i <= n; ++i) { - /* Computing MAX */ - r2 = sinqfb, r3 = (r1 = cf * y[i - 1] - x[i - 1], fabs( - r1)); - sinqfb = dmax(r2,r3); - } - - /* TEST SUBROUTINES COSQI,COSQF AND COSQB */ - - for (i = 1; i <= n; ++i) { - y[i - 1] = xh[i - 1]; - } - for (i = 1; i <= n; ++i) { - x[i - 1] = 0.f; - arg = (real) (i - 1) * dt; - for (k = 1; k <= n; ++k) { - x[i - 1] += y[k - 1] * cos((real) (k + k - 1) * arg); - } - x[i - 1] *= 4.f; - } - cosqi(n, w); - cosqb(n, y, w); - cosqbt = 0.f; - for (i = 1; i <= n; ++i) { - /* Computing MAX */ - r2 = cosqbt, r3 = (r1 = x[i - 1] - y[i - 1], fabs(r1)) - ; - cosqbt = dmax(r2,r3); - x[i - 1] = xh[i - 1]; - } - cosqbt = cf * cosqbt; - for (i = 1; i <= n; ++i) { - y[i - 1] = x[0] * .5f; - arg = (real) (i + i - 1) * dt; - for (k = 2; k <= n; ++k) { - y[i - 1] += x[k - 1] * cos((real) (k - 1) * arg); - } - y[i - 1] += y[i - 1]; - } - cosqf(n, x, w); - cosqft = 0.f; - for (i = 1; i <= n; ++i) { - /* Computing MAX */ - r2 = cosqft, r3 = (r1 = y[i - 1] - x[i - 1], fabs(r1)) - ; - cosqft = dmax(r2,r3); - x[i - 1] = xh[i - 1]; - y[i - 1] = xh[i - 1]; - } - cosqft = cf * cosqft; - cosqb(n, x, w); - cosqf(n, x, w); - cosqfb = 0.f; - for (i = 1; i <= n; ++i) { - /* Computing MAX */ - r2 = cosqfb, r3 = (r1 = cf * x[i - 1] - y[i - 1], fabs(r1)); - cosqfb = dmax(r2,r3); - } - - /* TEST CFFTI,CFFTF,CFFTB */ - - for (i = 1; i <= n; ++i) { - r1 = cos(sqrt2 * (real) i); - r2 = sin(sqrt2 * (real) (i * i)); - q1.r = r1, q1.i = r2; - cx[i-1].r = q1.r, cx[i-1].i = q1.i; - } - dt = (2*M_PI) / fn; - for (i = 1; i <= n; ++i) { - arg1 = -((real) (i - 1)) * dt; - cy[i-1].r = 0.f, cy[i-1].i = 0.f; - for (k = 1; k <= n; ++k) { - arg2 = (real) (k - 1) * arg1; - r1 = cos(arg2); - r2 = sin(arg2); - q3.r = r1, q3.i = r2; - q2.r = q3.r * cx[k-1].r - q3.i * cx[k-1].i, q2.i = - q3.r * cx[k-1].i + q3.i * cx[k-1].r; - q1.r = cy[i-1].r + q2.r, q1.i = cy[i-1].i + q2.i; - cy[i-1].r = q1.r, cy[i-1].i = q1.i; - } - } - cffti(n, w); - cfftf(n, (real*)cx, w); - dcfftf = 0.f; - for (i = 1; i <= n; ++i) { - /* Computing MAX */ - q1.r = cx[i-1].r - cy[i-1].r, q1.i = cx[i-1].i - cy[i-1] - .i; - r1 = dcfftf, r2 = c_abs(&q1); - dcfftf = dmax(r1,r2); - q1.r = cx[i-1].r / fn, q1.i = cx[i-1].i / fn; - cx[i-1].r = q1.r, cx[i-1].i = q1.i; - } - dcfftf /= fn; - for (i = 1; i <= n; ++i) { - arg1 = (real) (i - 1) * dt; - cy[i-1].r = 0.f, cy[i-1].i = 0.f; - for (k = 1; k <= n; ++k) { - arg2 = (real) (k - 1) * arg1; - r1 = cos(arg2); - r2 = sin(arg2); - q3.r = r1, q3.i = r2; - q2.r = q3.r * cx[k-1].r - q3.i * cx[k-1].i, q2.i = - q3.r * cx[k-1].i + q3.i * cx[k-1].r; - q1.r = cy[i-1].r + q2.r, q1.i = cy[i-1].i + q2.i; - cy[i-1].r = q1.r, cy[i-1].i = q1.i; - } - } - cfftb(n, (real*)cx, w); - dcfftb = 0.f; - for (i = 1; i <= n; ++i) { - /* Computing MAX */ - q1.r = cx[i-1].r - cy[i-1].r, q1.i = cx[i-1].i - cy[i-1].i; - r1 = dcfftb, r2 = c_abs(&q1); - dcfftb = dmax(r1,r2); - cx[i-1].r = cy[i-1].r, cx[i-1].i = cy[i-1].i; - } - cf = 1.f / fn; - cfftf(n, (real*)cx, w); - cfftb(n, (real*)cx, w); - dcfb = 0.f; - for (i = 1; i <= n; ++i) { - /* Computing MAX */ - q2.r = cf * cx[i-1].r, q2.i = cf * cx[i-1].i; - q1.r = q2.r - cy[i-1].r, q1.i = q2.i - cy[i-1].i; - r1 = dcfb, r2 = c_abs(&q1); - dcfb = dmax(r1,r2); - } - printf("%d\tRFFTF %10.3g\tRFFTB %10.ge\tRFFTFB %10.3g", n, rftf, rftb, rftfb); - printf( "\tSINT %10.3g\tSINTFB %10.ge\tCOST %10.3g\n", sintt, sintfb, costt); - printf( "\tCOSTFB %10.3g\tSINQF %10.ge\tSINQB %10.3g", costfb, sinqft, sinqbt); - printf( "\tSINQFB %10.3g\tCOSQF %10.ge\tCOSQB %10.3g\n", sinqfb, cosqft, cosqbt); - printf( "\tCOSQFB %10.3g\t", cosqfb); - printf( "\tCFFTF %10.ge\tCFFTB %10.3g\n", dcfftf, dcfftb); - printf( "\tCFFTFB %10.3g\n", dcfb); - -#define CHECK(x) if (x > 1e-3) { printf(#x " failed: %g\n", x); all_ok = 0; } - CHECK(rftf); CHECK(rftb); CHECK(rftfb); CHECK(sintt); CHECK(sintfb); CHECK(costt); - CHECK(costfb); CHECK(sinqft); CHECK(sinqbt); CHECK(sinqfb); CHECK(cosqft); CHECK(cosqbt); - CHECK(cosqfb); CHECK(dcfftf); CHECK(dcfftb); - } - - if (all_ok) printf("Everything looks fine.\n"); - else printf("ERRORS WERE DETECTED.\n"); - /* - expected: - 120 RFFTF 2.786e-06 RFFTB 6.847e-04 RFFTFB 2.795e-07 SINT 1.312e-06 SINTFB 1.237e-06 COST 1.319e-06 - COSTFB 4.355e-06 SINQF 3.281e-04 SINQB 1.876e-06 SINQFB 2.198e-07 COSQF 6.199e-07 COSQB 2.193e-06 - COSQFB 2.300e-07 DEZF 5.573e-06 DEZB 1.363e-05 DEZFB 1.371e-06 CFFTF 5.590e-06 CFFTB 4.751e-05 - CFFTFB 4.215e-07 - 54 RFFTF 4.708e-07 RFFTB 3.052e-05 RFFTFB 3.439e-07 SINT 3.532e-07 SINTFB 4.145e-07 COST 3.002e-07 - COSTFB 6.343e-07 SINQF 4.959e-05 SINQB 4.415e-07 SINQFB 2.882e-07 COSQF 2.826e-07 COSQB 2.472e-07 - COSQFB 3.439e-07 DEZF 9.388e-07 DEZB 5.066e-06 DEZFB 5.960e-07 CFFTF 1.426e-06 CFFTB 9.482e-06 - CFFTFB 2.980e-07 - 49 RFFTF 4.476e-07 RFFTB 5.341e-05 RFFTFB 2.574e-07 SINT 9.196e-07 SINTFB 9.401e-07 COST 8.174e-07 - COSTFB 1.331e-06 SINQF 4.005e-05 SINQB 9.342e-07 SINQFB 3.057e-07 COSQF 2.530e-07 COSQB 6.228e-07 - COSQFB 4.826e-07 DEZF 9.071e-07 DEZB 4.590e-06 DEZFB 5.960e-07 CFFTF 2.095e-06 CFFTB 1.414e-05 - CFFTFB 7.398e-07 - 32 RFFTF 4.619e-07 RFFTB 2.861e-05 RFFTFB 1.192e-07 SINT 3.874e-07 SINTFB 4.172e-07 COST 4.172e-07 - COSTFB 1.699e-06 SINQF 2.551e-05 SINQB 6.407e-07 SINQFB 2.980e-07 COSQF 1.639e-07 COSQB 1.714e-07 - COSQFB 2.384e-07 DEZF 1.013e-06 DEZB 2.339e-06 DEZFB 7.749e-07 CFFTF 1.127e-06 CFFTB 6.744e-06 - CFFTFB 2.666e-07 - 4 RFFTF 1.490e-08 RFFTB 1.490e-07 RFFTFB 5.960e-08 SINT 7.451e-09 SINTFB 0.000e+00 COST 2.980e-08 - COSTFB 1.192e-07 SINQF 4.768e-07 SINQB 2.980e-08 SINQFB 5.960e-08 COSQF 2.608e-08 COSQB 5.960e-08 - COSQFB 1.192e-07 DEZF 2.980e-08 DEZB 5.960e-08 DEZFB 0.000e+00 CFFTF 6.664e-08 CFFTB 5.960e-08 - CFFTFB 6.144e-08 - 3 RFFTF 3.974e-08 RFFTB 1.192e-07 RFFTFB 3.303e-08 SINT 1.987e-08 SINTFB 1.069e-08 COST 4.967e-08 - COSTFB 5.721e-08 SINQF 8.941e-08 SINQB 2.980e-08 SINQFB 1.259e-07 COSQF 7.451e-09 COSQB 4.967e-08 - COSQFB 7.029e-08 DEZF 1.192e-07 DEZB 5.960e-08 DEZFB 5.960e-08 CFFTF 7.947e-08 CFFTB 8.429e-08 - CFFTFB 9.064e-08 - 2 RFFTF 0.000e+00 RFFTB 0.000e+00 RFFTFB 0.000e+00 SINT 0.000e+00 SINTFB 0.000e+00 COST 0.000e+00 - COSTFB 0.000e+00 SINQF 1.192e-07 SINQB 2.980e-08 SINQFB 5.960e-08 COSQF 7.451e-09 COSQB 1.490e-08 - COSQFB 0.000e+00 DEZF 0.000e+00 DEZB 0.000e+00 DEZFB 0.000e+00 CFFTF 0.000e+00 CFFTB 5.960e-08 - CFFTFB 5.960e-08 - Everything looks fine. - - */ - - return all_ok ? 0 : 1; -} -#endif //TESTING_FFTPACK diff --git a/oss-internship-2020/pffft/fftpack.h b/oss-internship-2020/pffft/fftpack.h deleted file mode 100644 index 5971b9f..0000000 --- a/oss-internship-2020/pffft/fftpack.h +++ /dev/null @@ -1,799 +0,0 @@ -/* - Interface for the f2c translation of fftpack as found on http://www.netlib.org/fftpack/ - - FFTPACK license: - - http://www.cisl.ucar.edu/css/software/fftpack5/ftpk.html - - Copyright (c) 2004 the University Corporation for Atmospheric - Research ("UCAR"). All rights reserved. Developed by NCAR's - Computational and Information Systems Laboratory, UCAR, - www.cisl.ucar.edu. - - Redistribution and use of the Software in source and binary forms, - with or without modification, is permitted provided that the - following conditions are met: - - - Neither the names of NCAR's Computational and Information Systems - Laboratory, the University Corporation for Atmospheric Research, - nor the names of its sponsors or contributors may be used to - endorse or promote products derived from this Software without - specific prior written permission. - - - Redistributions of source code must retain the above copyright - notices, this list of conditions, and the disclaimer below. - - - Redistributions in binary form must reproduce the above copyright - notice, this list of conditions, and the disclaimer below in the - documentation and/or other materials provided with the - distribution. - - THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT - HOLDERS BE LIABLE FOR ANY CLAIM, INDIRECT, INCIDENTAL, SPECIAL, - EXEMPLARY, OR CONSEQUENTIAL DAMAGES OR OTHER LIABILITY, WHETHER IN AN - ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE - SOFTWARE. - - ChangeLog: - 2011/10/02: this is my first release of this file. -*/ - -#ifndef FFTPACK_H -#define FFTPACK_H - -#ifdef __cplusplus -extern "C" { -#endif - -// just define FFTPACK_DOUBLE_PRECISION if you want to build it as a double precision fft - -#ifndef FFTPACK_DOUBLE_PRECISION - typedef float fftpack_real; - typedef int fftpack_int; -#else - typedef double fftpack_real; - typedef int fftpack_int; -#endif - - void cffti(fftpack_int n, fftpack_real *wsave); - - void cfftf(fftpack_int n, fftpack_real *c, fftpack_real *wsave); - - void cfftb(fftpack_int n, fftpack_real *c, fftpack_real *wsave); - - void rffti(fftpack_int n, fftpack_real *wsave); - void rfftf(fftpack_int n, fftpack_real *r, fftpack_real *wsave); - void rfftb(fftpack_int n, fftpack_real *r, fftpack_real *wsave); - - void cosqi(fftpack_int n, fftpack_real *wsave); - void cosqf(fftpack_int n, fftpack_real *x, fftpack_real *wsave); - void cosqb(fftpack_int n, fftpack_real *x, fftpack_real *wsave); - - void costi(fftpack_int n, fftpack_real *wsave); - void cost(fftpack_int n, fftpack_real *x, fftpack_real *wsave); - - void sinqi(fftpack_int n, fftpack_real *wsave); - void sinqb(fftpack_int n, fftpack_real *x, fftpack_real *wsave); - void sinqf(fftpack_int n, fftpack_real *x, fftpack_real *wsave); - - void sinti(fftpack_int n, fftpack_real *wsave); - void sint(fftpack_int n, fftpack_real *x, fftpack_real *wsave); - -#ifdef __cplusplus -} -#endif - -#endif /* FFTPACK_H */ - -/* - - FFTPACK - -* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - - version 4 april 1985 - - a package of fortran subprograms for the fast fourier - transform of periodic and other symmetric sequences - - by - - paul n swarztrauber - - national center for atmospheric research boulder,colorado 80307 - - which is sponsored by the national science foundation - -* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - - -this package consists of programs which perform fast fourier -transforms for both complex and real periodic sequences and -certain other symmetric sequences that are listed below. - -1. rffti initialize rfftf and rfftb -2. rfftf forward transform of a real periodic sequence -3. rfftb backward transform of a real coefficient array - -4. ezffti initialize ezfftf and ezfftb -5. ezfftf a simplified real periodic forward transform -6. ezfftb a simplified real periodic backward transform - -7. sinti initialize sint -8. sint sine transform of a real odd sequence - -9. costi initialize cost -10. cost cosine transform of a real even sequence - -11. sinqi initialize sinqf and sinqb -12. sinqf forward sine transform with odd wave numbers -13. sinqb unnormalized inverse of sinqf - -14. cosqi initialize cosqf and cosqb -15. cosqf forward cosine transform with odd wave numbers -16. cosqb unnormalized inverse of cosqf - -17. cffti initialize cfftf and cfftb -18. cfftf forward transform of a complex periodic sequence -19. cfftb unnormalized inverse of cfftf - - -****************************************************************** - -subroutine rffti(n,wsave) - - **************************************************************** - -subroutine rffti initializes the array wsave which is used in -both rfftf and rfftb. the prime factorization of n together with -a tabulation of the trigonometric functions are computed and -stored in wsave. - -input parameter - -n the length of the sequence to be transformed. - -output parameter - -wsave a work array which must be dimensioned at least 2*n+15. - the same work array can be used for both rfftf and rfftb - as long as n remains unchanged. different wsave arrays - are required for different values of n. the contents of - wsave must not be changed between calls of rfftf or rfftb. - -****************************************************************** - -subroutine rfftf(n,r,wsave) - -****************************************************************** - -subroutine rfftf computes the fourier coefficients of a real -perodic sequence (fourier analysis). the transform is defined -below at output parameter r. - -input parameters - -n the length of the array r to be transformed. the method - is most efficient when n is a product of small primes. - n may change so long as different work arrays are provided - -r a real array of length n which contains the sequence - to be transformed - -wsave a work array which must be dimensioned at least 2*n+15. - in the program that calls rfftf. the wsave array must be - initialized by calling subroutine rffti(n,wsave) and a - different wsave array must be used for each different - value of n. this initialization does not have to be - repeated so long as n remains unchanged thus subsequent - transforms can be obtained faster than the first. - the same wsave array can be used by rfftf and rfftb. - - -output parameters - -r r(1) = the sum from i=1 to i=n of r(i) - - if n is even set l =n/2 , if n is odd set l = (n+1)/2 - - then for k = 2,...,l - - r(2*k-2) = the sum from i = 1 to i = n of - - r(i)*cos((k-1)*(i-1)*2*pi/n) - - r(2*k-1) = the sum from i = 1 to i = n of - - -r(i)*sin((k-1)*(i-1)*2*pi/n) - - if n is even - - r(n) = the sum from i = 1 to i = n of - - (-1)**(i-1)*r(i) - - ***** note - this transform is unnormalized since a call of rfftf - followed by a call of rfftb will multiply the input - sequence by n. - -wsave contains results which must not be destroyed between - calls of rfftf or rfftb. - - -****************************************************************** - -subroutine rfftb(n,r,wsave) - -****************************************************************** - -subroutine rfftb computes the real perodic sequence from its -fourier coefficients (fourier synthesis). the transform is defined -below at output parameter r. - -input parameters - -n the length of the array r to be transformed. the method - is most efficient when n is a product of small primes. - n may change so long as different work arrays are provided - -r a real array of length n which contains the sequence - to be transformed - -wsave a work array which must be dimensioned at least 2*n+15. - in the program that calls rfftb. the wsave array must be - initialized by calling subroutine rffti(n,wsave) and a - different wsave array must be used for each different - value of n. this initialization does not have to be - repeated so long as n remains unchanged thus subsequent - transforms can be obtained faster than the first. - the same wsave array can be used by rfftf and rfftb. - - -output parameters - -r for n even and for i = 1,...,n - - r(i) = r(1)+(-1)**(i-1)*r(n) - - plus the sum from k=2 to k=n/2 of - - 2.*r(2*k-2)*cos((k-1)*(i-1)*2*pi/n) - - -2.*r(2*k-1)*sin((k-1)*(i-1)*2*pi/n) - - for n odd and for i = 1,...,n - - r(i) = r(1) plus the sum from k=2 to k=(n+1)/2 of - - 2.*r(2*k-2)*cos((k-1)*(i-1)*2*pi/n) - - -2.*r(2*k-1)*sin((k-1)*(i-1)*2*pi/n) - - ***** note - this transform is unnormalized since a call of rfftf - followed by a call of rfftb will multiply the input - sequence by n. - -wsave contains results which must not be destroyed between - calls of rfftb or rfftf. - -****************************************************************** - -subroutine sinti(n,wsave) - -****************************************************************** - -subroutine sinti initializes the array wsave which is used in -subroutine sint. the prime factorization of n together with -a tabulation of the trigonometric functions are computed and -stored in wsave. - -input parameter - -n the length of the sequence to be transformed. the method - is most efficient when n+1 is a product of small primes. - -output parameter - -wsave a work array with at least int(2.5*n+15) locations. - different wsave arrays are required for different values - of n. the contents of wsave must not be changed between - calls of sint. - -****************************************************************** - -subroutine sint(n,x,wsave) - -****************************************************************** - -subroutine sint computes the discrete fourier sine transform -of an odd sequence x(i). the transform is defined below at -output parameter x. - -sint is the unnormalized inverse of itself since a call of sint -followed by another call of sint will multiply the input sequence -x by 2*(n+1). - -the array wsave which is used by subroutine sint must be -initialized by calling subroutine sinti(n,wsave). - -input parameters - -n the length of the sequence to be transformed. the method - is most efficient when n+1 is the product of small primes. - -x an array which contains the sequence to be transformed - - -wsave a work array with dimension at least int(2.5*n+15) - in the program that calls sint. the wsave array must be - initialized by calling subroutine sinti(n,wsave) and a - different wsave array must be used for each different - value of n. this initialization does not have to be - repeated so long as n remains unchanged thus subsequent - transforms can be obtained faster than the first. - -output parameters - -x for i=1,...,n - - x(i)= the sum from k=1 to k=n - - 2*x(k)*sin(k*i*pi/(n+1)) - - a call of sint followed by another call of - sint will multiply the sequence x by 2*(n+1). - hence sint is the unnormalized inverse - of itself. - -wsave contains initialization calculations which must not be - destroyed between calls of sint. - -****************************************************************** - -subroutine costi(n,wsave) - -****************************************************************** - -subroutine costi initializes the array wsave which is used in -subroutine cost. the prime factorization of n together with -a tabulation of the trigonometric functions are computed and -stored in wsave. - -input parameter - -n the length of the sequence to be transformed. the method - is most efficient when n-1 is a product of small primes. - -output parameter - -wsave a work array which must be dimensioned at least 3*n+15. - different wsave arrays are required for different values - of n. the contents of wsave must not be changed between - calls of cost. - -****************************************************************** - -subroutine cost(n,x,wsave) - -****************************************************************** - -subroutine cost computes the discrete fourier cosine transform -of an even sequence x(i). the transform is defined below at output -parameter x. - -cost is the unnormalized inverse of itself since a call of cost -followed by another call of cost will multiply the input sequence -x by 2*(n-1). the transform is defined below at output parameter x - -the array wsave which is used by subroutine cost must be -initialized by calling subroutine costi(n,wsave). - -input parameters - -n the length of the sequence x. n must be greater than 1. - the method is most efficient when n-1 is a product of - small primes. - -x an array which contains the sequence to be transformed - -wsave a work array which must be dimensioned at least 3*n+15 - in the program that calls cost. the wsave array must be - initialized by calling subroutine costi(n,wsave) and a - different wsave array must be used for each different - value of n. this initialization does not have to be - repeated so long as n remains unchanged thus subsequent - transforms can be obtained faster than the first. - -output parameters - -x for i=1,...,n - - x(i) = x(1)+(-1)**(i-1)*x(n) - - + the sum from k=2 to k=n-1 - - 2*x(k)*cos((k-1)*(i-1)*pi/(n-1)) - - a call of cost followed by another call of - cost will multiply the sequence x by 2*(n-1) - hence cost is the unnormalized inverse - of itself. - -wsave contains initialization calculations which must not be - destroyed between calls of cost. - -****************************************************************** - -subroutine sinqi(n,wsave) - -****************************************************************** - -subroutine sinqi initializes the array wsave which is used in -both sinqf and sinqb. the prime factorization of n together with -a tabulation of the trigonometric functions are computed and -stored in wsave. - -input parameter - -n the length of the sequence to be transformed. the method - is most efficient when n is a product of small primes. - -output parameter - -wsave a work array which must be dimensioned at least 3*n+15. - the same work array can be used for both sinqf and sinqb - as long as n remains unchanged. different wsave arrays - are required for different values of n. the contents of - wsave must not be changed between calls of sinqf or sinqb. - -****************************************************************** - -subroutine sinqf(n,x,wsave) - -****************************************************************** - -subroutine sinqf computes the fast fourier transform of quarter -wave data. that is , sinqf computes the coefficients in a sine -series representation with only odd wave numbers. the transform -is defined below at output parameter x. - -sinqb is the unnormalized inverse of sinqf since a call of sinqf -followed by a call of sinqb will multiply the input sequence x -by 4*n. - -the array wsave which is used by subroutine sinqf must be -initialized by calling subroutine sinqi(n,wsave). - - -input parameters - -n the length of the array x to be transformed. the method - is most efficient when n is a product of small primes. - -x an array which contains the sequence to be transformed - -wsave a work array which must be dimensioned at least 3*n+15. - in the program that calls sinqf. the wsave array must be - initialized by calling subroutine sinqi(n,wsave) and a - different wsave array must be used for each different - value of n. this initialization does not have to be - repeated so long as n remains unchanged thus subsequent - transforms can be obtained faster than the first. - -output parameters - -x for i=1,...,n - - x(i) = (-1)**(i-1)*x(n) - - + the sum from k=1 to k=n-1 of - - 2*x(k)*sin((2*i-1)*k*pi/(2*n)) - - a call of sinqf followed by a call of - sinqb will multiply the sequence x by 4*n. - therefore sinqb is the unnormalized inverse - of sinqf. - -wsave contains initialization calculations which must not - be destroyed between calls of sinqf or sinqb. - -****************************************************************** - -subroutine sinqb(n,x,wsave) - -****************************************************************** - -subroutine sinqb computes the fast fourier transform of quarter -wave data. that is , sinqb computes a sequence from its -representation in terms of a sine series with odd wave numbers. -the transform is defined below at output parameter x. - -sinqf is the unnormalized inverse of sinqb since a call of sinqb -followed by a call of sinqf will multiply the input sequence x -by 4*n. - -the array wsave which is used by subroutine sinqb must be -initialized by calling subroutine sinqi(n,wsave). - - -input parameters - -n the length of the array x to be transformed. the method - is most efficient when n is a product of small primes. - -x an array which contains the sequence to be transformed - -wsave a work array which must be dimensioned at least 3*n+15. - in the program that calls sinqb. the wsave array must be - initialized by calling subroutine sinqi(n,wsave) and a - different wsave array must be used for each different - value of n. this initialization does not have to be - repeated so long as n remains unchanged thus subsequent - transforms can be obtained faster than the first. - -output parameters - -x for i=1,...,n - - x(i)= the sum from k=1 to k=n of - - 4*x(k)*sin((2k-1)*i*pi/(2*n)) - - a call of sinqb followed by a call of - sinqf will multiply the sequence x by 4*n. - therefore sinqf is the unnormalized inverse - of sinqb. - -wsave contains initialization calculations which must not - be destroyed between calls of sinqb or sinqf. - -****************************************************************** - -subroutine cosqi(n,wsave) - -****************************************************************** - -subroutine cosqi initializes the array wsave which is used in -both cosqf and cosqb. the prime factorization of n together with -a tabulation of the trigonometric functions are computed and -stored in wsave. - -input parameter - -n the length of the array to be transformed. the method - is most efficient when n is a product of small primes. - -output parameter - -wsave a work array which must be dimensioned at least 3*n+15. - the same work array can be used for both cosqf and cosqb - as long as n remains unchanged. different wsave arrays - are required for different values of n. the contents of - wsave must not be changed between calls of cosqf or cosqb. - -****************************************************************** - -subroutine cosqf(n,x,wsave) - -****************************************************************** - -subroutine cosqf computes the fast fourier transform of quarter -wave data. that is , cosqf computes the coefficients in a cosine -series representation with only odd wave numbers. the transform -is defined below at output parameter x - -cosqf is the unnormalized inverse of cosqb since a call of cosqf -followed by a call of cosqb will multiply the input sequence x -by 4*n. - -the array wsave which is used by subroutine cosqf must be -initialized by calling subroutine cosqi(n,wsave). - - -input parameters - -n the length of the array x to be transformed. the method - is most efficient when n is a product of small primes. - -x an array which contains the sequence to be transformed - -wsave a work array which must be dimensioned at least 3*n+15 - in the program that calls cosqf. the wsave array must be - initialized by calling subroutine cosqi(n,wsave) and a - different wsave array must be used for each different - value of n. this initialization does not have to be - repeated so long as n remains unchanged thus subsequent - transforms can be obtained faster than the first. - -output parameters - -x for i=1,...,n - - x(i) = x(1) plus the sum from k=2 to k=n of - - 2*x(k)*cos((2*i-1)*(k-1)*pi/(2*n)) - - a call of cosqf followed by a call of - cosqb will multiply the sequence x by 4*n. - therefore cosqb is the unnormalized inverse - of cosqf. - -wsave contains initialization calculations which must not - be destroyed between calls of cosqf or cosqb. - -****************************************************************** - -subroutine cosqb(n,x,wsave) - -****************************************************************** - -subroutine cosqb computes the fast fourier transform of quarter -wave data. that is , cosqb computes a sequence from its -representation in terms of a cosine series with odd wave numbers. -the transform is defined below at output parameter x. - -cosqb is the unnormalized inverse of cosqf since a call of cosqb -followed by a call of cosqf will multiply the input sequence x -by 4*n. - -the array wsave which is used by subroutine cosqb must be -initialized by calling subroutine cosqi(n,wsave). - - -input parameters - -n the length of the array x to be transformed. the method - is most efficient when n is a product of small primes. - -x an array which contains the sequence to be transformed - -wsave a work array that must be dimensioned at least 3*n+15 - in the program that calls cosqb. the wsave array must be - initialized by calling subroutine cosqi(n,wsave) and a - different wsave array must be used for each different - value of n. this initialization does not have to be - repeated so long as n remains unchanged thus subsequent - transforms can be obtained faster than the first. - -output parameters - -x for i=1,...,n - - x(i)= the sum from k=1 to k=n of - - 4*x(k)*cos((2*k-1)*(i-1)*pi/(2*n)) - - a call of cosqb followed by a call of - cosqf will multiply the sequence x by 4*n. - therefore cosqf is the unnormalized inverse - of cosqb. - -wsave contains initialization calculations which must not - be destroyed between calls of cosqb or cosqf. - -****************************************************************** - -subroutine cffti(n,wsave) - -****************************************************************** - -subroutine cffti initializes the array wsave which is used in -both cfftf and cfftb. the prime factorization of n together with -a tabulation of the trigonometric functions are computed and -stored in wsave. - -input parameter - -n the length of the sequence to be transformed - -output parameter - -wsave a work array which must be dimensioned at least 4*n+15 - the same work array can be used for both cfftf and cfftb - as long as n remains unchanged. different wsave arrays - are required for different values of n. the contents of - wsave must not be changed between calls of cfftf or cfftb. - -****************************************************************** - -subroutine cfftf(n,c,wsave) - -****************************************************************** - -subroutine cfftf computes the forward complex discrete fourier -transform (the fourier analysis). equivalently , cfftf computes -the fourier coefficients of a complex periodic sequence. -the transform is defined below at output parameter c. - -the transform is not normalized. to obtain a normalized transform -the output must be divided by n. otherwise a call of cfftf -followed by a call of cfftb will multiply the sequence by n. - -the array wsave which is used by subroutine cfftf must be -initialized by calling subroutine cffti(n,wsave). - -input parameters - - -n the length of the complex sequence c. the method is - more efficient when n is the product of small primes. n - -c a complex array of length n which contains the sequence - -wsave a real work array which must be dimensioned at least 4n+15 - in the program that calls cfftf. the wsave array must be - initialized by calling subroutine cffti(n,wsave) and a - different wsave array must be used for each different - value of n. this initialization does not have to be - repeated so long as n remains unchanged thus subsequent - transforms can be obtained faster than the first. - the same wsave array can be used by cfftf and cfftb. - -output parameters - -c for j=1,...,n - - c(j)=the sum from k=1,...,n of - - c(k)*exp(-i*(j-1)*(k-1)*2*pi/n) - - where i=sqrt(-1) - -wsave contains initialization calculations which must not be - destroyed between calls of subroutine cfftf or cfftb - -****************************************************************** - -subroutine cfftb(n,c,wsave) - -****************************************************************** - -subroutine cfftb computes the backward complex discrete fourier -transform (the fourier synthesis). equivalently , cfftb computes -a complex periodic sequence from its fourier coefficients. -the transform is defined below at output parameter c. - -a call of cfftf followed by a call of cfftb will multiply the -sequence by n. - -the array wsave which is used by subroutine cfftb must be -initialized by calling subroutine cffti(n,wsave). - -input parameters - - -n the length of the complex sequence c. the method is - more efficient when n is the product of small primes. - -c a complex array of length n which contains the sequence - -wsave a real work array which must be dimensioned at least 4n+15 - in the program that calls cfftb. the wsave array must be - initialized by calling subroutine cffti(n,wsave) and a - different wsave array must be used for each different - value of n. this initialization does not have to be - repeated so long as n remains unchanged thus subsequent - transforms can be obtained faster than the first. - the same wsave array can be used by cfftf and cfftb. - -output parameters - -c for j=1,...,n - - c(j)=the sum from k=1,...,n of - - c(k)*exp(i*(j-1)*(k-1)*2*pi/n) - - where i=sqrt(-1) - -wsave contains initialization calculations which must not be - destroyed between calls of subroutine cfftf or cfftb - -*/ diff --git a/oss-internship-2020/pffft/main_pffft_sandboxed.cc b/oss-internship-2020/pffft/main_pffft_sandboxed.cc index 8b7e6d1..10ea802 100644 --- a/oss-internship-2020/pffft/main_pffft_sandboxed.cc +++ b/oss-internship-2020/pffft/main_pffft_sandboxed.cc @@ -23,7 +23,6 @@ #include #include -#include "fftpack.h" #include "pffft_sapi.sapi.h" #include "sandboxed_api/util/flag.h" #include "sandboxed_api/vars.h" diff --git a/oss-internship-2020/pffft/pffft.c b/oss-internship-2020/pffft/pffft.c deleted file mode 100644 index 1686e15..0000000 --- a/oss-internship-2020/pffft/pffft.c +++ /dev/null @@ -1,1881 +0,0 @@ -/* Copyright (c) 2013 Julien Pommier ( pommier@modartt.com ) - - Based on original fortran 77 code from FFTPACKv4 from NETLIB - (http://www.netlib.org/fftpack), authored by Dr Paul Swarztrauber - of NCAR, in 1985. - - As confirmed by the NCAR fftpack software curators, the following - FFTPACKv5 license applies to FFTPACKv4 sources. My changes are - released under the same terms. - - FFTPACK license: - - http://www.cisl.ucar.edu/css/software/fftpack5/ftpk.html - - Copyright (c) 2004 the University Corporation for Atmospheric - Research ("UCAR"). All rights reserved. Developed by NCAR's - Computational and Information Systems Laboratory, UCAR, - www.cisl.ucar.edu. - - Redistribution and use of the Software in source and binary forms, - with or without modification, is permitted provided that the - following conditions are met: - - - Neither the names of NCAR's Computational and Information Systems - Laboratory, the University Corporation for Atmospheric Research, - nor the names of its sponsors or contributors may be used to - endorse or promote products derived from this Software without - specific prior written permission. - - - Redistributions of source code must retain the above copyright - notices, this list of conditions, and the disclaimer below. - - - Redistributions in binary form must reproduce the above copyright - notice, this list of conditions, and the disclaimer below in the - documentation and/or other materials provided with the - distribution. - - THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT - HOLDERS BE LIABLE FOR ANY CLAIM, INDIRECT, INCIDENTAL, SPECIAL, - EXEMPLARY, OR CONSEQUENTIAL DAMAGES OR OTHER LIABILITY, WHETHER IN AN - ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE - SOFTWARE. - - - PFFFT : a Pretty Fast FFT. - - This file is largerly based on the original FFTPACK implementation, modified in - order to take advantage of SIMD instructions of modern CPUs. -*/ - -/* - ChangeLog: - - 2011/10/02, version 1: This is the very first release of this file. -*/ - -#include "pffft.h" -#include -#include -#include -#include - -/* detect compiler flavour */ -#if defined(_MSC_VER) -# define COMPILER_MSVC -#elif defined(__GNUC__) -# define COMPILER_GCC -#endif - -#if defined(COMPILER_GCC) -# define ALWAYS_INLINE(return_type) inline return_type __attribute__ ((always_inline)) -# define NEVER_INLINE(return_type) return_type __attribute__ ((noinline)) -# define RESTRICT __restrict -# define VLA_ARRAY_ON_STACK(type__, varname__, size__) type__ varname__[size__]; -#elif defined(COMPILER_MSVC) -# define ALWAYS_INLINE(return_type) __forceinline return_type -# define NEVER_INLINE(return_type) __declspec(noinline) return_type -# define RESTRICT __restrict -# define VLA_ARRAY_ON_STACK(type__, varname__, size__) type__ *varname__ = (type__*)_alloca(size__ * sizeof(type__)) -#endif - - -/* - vector support macros: the rest of the code is independant of - SSE/Altivec/NEON -- adding support for other platforms with 4-element - vectors should be limited to these macros -*/ - - -// define PFFFT_SIMD_DISABLE if you want to use scalar code instead of simd code -//#define PFFFT_SIMD_DISABLE - -/* - Altivec support macros -*/ -#if !defined(PFFFT_SIMD_DISABLE) && (defined(__ppc__) || defined(__ppc64__)) -typedef vector float v4sf; -# define SIMD_SZ 4 -# define VZERO() ((vector float) vec_splat_u8(0)) -# define VMUL(a,b) vec_madd(a,b, VZERO()) -# define VADD(a,b) vec_add(a,b) -# define VMADD(a,b,c) vec_madd(a,b,c) -# define VSUB(a,b) vec_sub(a,b) -inline v4sf ld_ps1(const float *p) { v4sf v=vec_lde(0,p); return vec_splat(vec_perm(v, v, vec_lvsl(0, p)), 0); } -# define LD_PS1(p) ld_ps1(&p) -# define INTERLEAVE2(in1, in2, out1, out2) { v4sf tmp__ = vec_mergeh(in1, in2); out2 = vec_mergel(in1, in2); out1 = tmp__; } -# define UNINTERLEAVE2(in1, in2, out1, out2) { \ - vector unsigned char vperm1 = (vector unsigned char)(0,1,2,3,8,9,10,11,16,17,18,19,24,25,26,27); \ - vector unsigned char vperm2 = (vector unsigned char)(4,5,6,7,12,13,14,15,20,21,22,23,28,29,30,31); \ - v4sf tmp__ = vec_perm(in1, in2, vperm1); out2 = vec_perm(in1, in2, vperm2); out1 = tmp__; \ - } -# define VTRANSPOSE4(x0,x1,x2,x3) { \ - v4sf y0 = vec_mergeh(x0, x2); \ - v4sf y1 = vec_mergel(x0, x2); \ - v4sf y2 = vec_mergeh(x1, x3); \ - v4sf y3 = vec_mergel(x1, x3); \ - x0 = vec_mergeh(y0, y2); \ - x1 = vec_mergel(y0, y2); \ - x2 = vec_mergeh(y1, y3); \ - x3 = vec_mergel(y1, y3); \ - } -# define VSWAPHL(a,b) vec_perm(a,b, (vector unsigned char)(16,17,18,19,20,21,22,23,8,9,10,11,12,13,14,15)) -# define VALIGNED(ptr) ((((long)(ptr)) & 0xF) == 0) - -/* - SSE1 support macros -*/ -#elif !defined(PFFFT_SIMD_DISABLE) && (defined(__x86_64__) || defined(_M_X64) || defined(i386) || defined(_M_IX86)) - -#include -typedef __m128 v4sf; -# define SIMD_SZ 4 // 4 floats by simd vector -- this is pretty much hardcoded in the preprocess/finalize functions anyway so you will have to work if you want to enable AVX with its 256-bit vectors. -# define VZERO() _mm_setzero_ps() -# define VMUL(a,b) _mm_mul_ps(a,b) -# define VADD(a,b) _mm_add_ps(a,b) -# define VMADD(a,b,c) _mm_add_ps(_mm_mul_ps(a,b), c) -# define VSUB(a,b) _mm_sub_ps(a,b) -# define LD_PS1(p) _mm_set1_ps(p) -# define INTERLEAVE2(in1, in2, out1, out2) { v4sf tmp__ = _mm_unpacklo_ps(in1, in2); out2 = _mm_unpackhi_ps(in1, in2); out1 = tmp__; } -# define UNINTERLEAVE2(in1, in2, out1, out2) { v4sf tmp__ = _mm_shuffle_ps(in1, in2, _MM_SHUFFLE(2,0,2,0)); out2 = _mm_shuffle_ps(in1, in2, _MM_SHUFFLE(3,1,3,1)); out1 = tmp__; } -# define VTRANSPOSE4(x0,x1,x2,x3) _MM_TRANSPOSE4_PS(x0,x1,x2,x3) -# define VSWAPHL(a,b) _mm_shuffle_ps(b, a, _MM_SHUFFLE(3,2,1,0)) -# define VALIGNED(ptr) ((((long)(ptr)) & 0xF) == 0) - -/* - ARM NEON support macros -*/ -#elif !defined(PFFFT_SIMD_DISABLE) && (defined(__arm__) || defined(__aarch64__) || defined(__arm64__)) -# include -typedef float32x4_t v4sf; -# define SIMD_SZ 4 -# define VZERO() vdupq_n_f32(0) -# define VMUL(a,b) vmulq_f32(a,b) -# define VADD(a,b) vaddq_f32(a,b) -# define VMADD(a,b,c) vmlaq_f32(c,a,b) -# define VSUB(a,b) vsubq_f32(a,b) -# define LD_PS1(p) vld1q_dup_f32(&(p)) -# define INTERLEAVE2(in1, in2, out1, out2) { float32x4x2_t tmp__ = vzipq_f32(in1,in2); out1=tmp__.val[0]; out2=tmp__.val[1]; } -# define UNINTERLEAVE2(in1, in2, out1, out2) { float32x4x2_t tmp__ = vuzpq_f32(in1,in2); out1=tmp__.val[0]; out2=tmp__.val[1]; } -# define VTRANSPOSE4(x0,x1,x2,x3) { \ - float32x4x2_t t0_ = vzipq_f32(x0, x2); \ - float32x4x2_t t1_ = vzipq_f32(x1, x3); \ - float32x4x2_t u0_ = vzipq_f32(t0_.val[0], t1_.val[0]); \ - float32x4x2_t u1_ = vzipq_f32(t0_.val[1], t1_.val[1]); \ - x0 = u0_.val[0]; x1 = u0_.val[1]; x2 = u1_.val[0]; x3 = u1_.val[1]; \ - } -// marginally faster version -//# define VTRANSPOSE4(x0,x1,x2,x3) { asm("vtrn.32 %q0, %q1;\n vtrn.32 %q2,%q3\n vswp %f0,%e2\n vswp %f1,%e3" : "+w"(x0), "+w"(x1), "+w"(x2), "+w"(x3)::); } -# define VSWAPHL(a,b) vcombine_f32(vget_low_f32(b), vget_high_f32(a)) -# define VALIGNED(ptr) ((((long)(ptr)) & 0x3) == 0) -#else -# if !defined(PFFFT_SIMD_DISABLE) -# warning "building with simd disabled !\n"; -# define PFFFT_SIMD_DISABLE // fallback to scalar code -# endif -#endif - -// fallback mode for situations where SSE/Altivec are not available, use scalar mode instead -#ifdef PFFFT_SIMD_DISABLE -typedef float v4sf; -# define SIMD_SZ 1 -# define VZERO() 0.f -# define VMUL(a,b) ((a)*(b)) -# define VADD(a,b) ((a)+(b)) -# define VMADD(a,b,c) ((a)*(b)+(c)) -# define VSUB(a,b) ((a)-(b)) -# define LD_PS1(p) (p) -# define VALIGNED(ptr) ((((long)(ptr)) & 0x3) == 0) -#endif - -// shortcuts for complex multiplcations -#define VCPLXMUL(ar,ai,br,bi) { v4sf tmp; tmp=VMUL(ar,bi); ar=VMUL(ar,br); ar=VSUB(ar,VMUL(ai,bi)); ai=VMUL(ai,br); ai=VADD(ai,tmp); } -#define VCPLXMULCONJ(ar,ai,br,bi) { v4sf tmp; tmp=VMUL(ar,bi); ar=VMUL(ar,br); ar=VADD(ar,VMUL(ai,bi)); ai=VMUL(ai,br); ai=VSUB(ai,tmp); } -#ifndef SVMUL -// multiply a scalar with a vector -#define SVMUL(f,v) VMUL(LD_PS1(f),v) -#endif - -#if !defined(PFFFT_SIMD_DISABLE) -typedef union v4sf_union { - v4sf v; - float f[4]; -} v4sf_union; - -#include - -#define assertv4(v,f0,f1,f2,f3) assert(v.f[0] == (f0) && v.f[1] == (f1) && v.f[2] == (f2) && v.f[3] == (f3)) - -/* detect bugs with the vector support macros */ -void validate_pffft_simd() { - float f[16] = { 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 }; - v4sf_union a0, a1, a2, a3, t, u; - memcpy(a0.f, f, 4*sizeof(float)); - memcpy(a1.f, f+4, 4*sizeof(float)); - memcpy(a2.f, f+8, 4*sizeof(float)); - memcpy(a3.f, f+12, 4*sizeof(float)); - - t = a0; u = a1; t.v = VZERO(); - printf("VZERO=[%2g %2g %2g %2g]\n", t.f[0], t.f[1], t.f[2], t.f[3]); assertv4(t, 0, 0, 0, 0); - t.v = VADD(a1.v, a2.v); - printf("VADD(4:7,8:11)=[%2g %2g %2g %2g]\n", t.f[0], t.f[1], t.f[2], t.f[3]); assertv4(t, 12, 14, 16, 18); - t.v = VMUL(a1.v, a2.v); - printf("VMUL(4:7,8:11)=[%2g %2g %2g %2g]\n", t.f[0], t.f[1], t.f[2], t.f[3]); assertv4(t, 32, 45, 60, 77); - t.v = VMADD(a1.v, a2.v,a0.v); - printf("VMADD(4:7,8:11,0:3)=[%2g %2g %2g %2g]\n", t.f[0], t.f[1], t.f[2], t.f[3]); assertv4(t, 32, 46, 62, 80); - - INTERLEAVE2(a1.v,a2.v,t.v,u.v); - printf("INTERLEAVE2(4:7,8:11)=[%2g %2g %2g %2g] [%2g %2g %2g %2g]\n", t.f[0], t.f[1], t.f[2], t.f[3], u.f[0], u.f[1], u.f[2], u.f[3]); - assertv4(t, 4, 8, 5, 9); assertv4(u, 6, 10, 7, 11); - UNINTERLEAVE2(a1.v,a2.v,t.v,u.v); - printf("UNINTERLEAVE2(4:7,8:11)=[%2g %2g %2g %2g] [%2g %2g %2g %2g]\n", t.f[0], t.f[1], t.f[2], t.f[3], u.f[0], u.f[1], u.f[2], u.f[3]); - assertv4(t, 4, 6, 8, 10); assertv4(u, 5, 7, 9, 11); - - t.v=LD_PS1(f[15]); - printf("LD_PS1(15)=[%2g %2g %2g %2g]\n", t.f[0], t.f[1], t.f[2], t.f[3]); - assertv4(t, 15, 15, 15, 15); - t.v = VSWAPHL(a1.v, a2.v); - printf("VSWAPHL(4:7,8:11)=[%2g %2g %2g %2g]\n", t.f[0], t.f[1], t.f[2], t.f[3]); - assertv4(t, 8, 9, 6, 7); - VTRANSPOSE4(a0.v, a1.v, a2.v, a3.v); - printf("VTRANSPOSE4(0:3,4:7,8:11,12:15)=[%2g %2g %2g %2g] [%2g %2g %2g %2g] [%2g %2g %2g %2g] [%2g %2g %2g %2g]\n", - a0.f[0], a0.f[1], a0.f[2], a0.f[3], a1.f[0], a1.f[1], a1.f[2], a1.f[3], - a2.f[0], a2.f[1], a2.f[2], a2.f[3], a3.f[0], a3.f[1], a3.f[2], a3.f[3]); - assertv4(a0, 0, 4, 8, 12); assertv4(a1, 1, 5, 9, 13); assertv4(a2, 2, 6, 10, 14); assertv4(a3, 3, 7, 11, 15); -} -#endif //!PFFFT_SIMD_DISABLE - -/* SSE and co like 16-bytes aligned pointers */ -#define MALLOC_V4SF_ALIGNMENT 64 // with a 64-byte alignment, we are even aligned on L2 cache lines... -void *pffft_aligned_malloc(size_t nb_bytes) { - void *p, *p0 = malloc(nb_bytes + MALLOC_V4SF_ALIGNMENT); - if (!p0) return (void *) 0; - p = (void *) (((size_t) p0 + MALLOC_V4SF_ALIGNMENT) & (~((size_t) (MALLOC_V4SF_ALIGNMENT-1)))); - *((void **) p - 1) = p0; - return p; -} - -void pffft_aligned_free(void *p) { - if (p) free(*((void **) p - 1)); -} - -int pffft_simd_size() { return SIMD_SZ; } - -/* - passf2 and passb2 has been merged here, fsign = -1 for passf2, +1 for passb2 -*/ -static NEVER_INLINE(void) passf2_ps(int ido, int l1, const v4sf *cc, v4sf *ch, const float *wa1, float fsign) { - int k, i; - int l1ido = l1*ido; - if (ido <= 2) { - for (k=0; k < l1ido; k += ido, ch += ido, cc+= 2*ido) { - ch[0] = VADD(cc[0], cc[ido+0]); - ch[l1ido] = VSUB(cc[0], cc[ido+0]); - ch[1] = VADD(cc[1], cc[ido+1]); - ch[l1ido + 1] = VSUB(cc[1], cc[ido+1]); - } - } else { - for (k=0; k < l1ido; k += ido, ch += ido, cc += 2*ido) { - for (i=0; i 2); - for (k=0; k< l1ido; k += ido, cc+= 3*ido, ch +=ido) { - for (i=0; i 2); - for (k = 0; k < l1; ++k, cc += 5*ido, ch += ido) { - for (i = 0; i < ido-1; i += 2) { - ti5 = VSUB(cc_ref(i , 2), cc_ref(i , 5)); - ti2 = VADD(cc_ref(i , 2), cc_ref(i , 5)); - ti4 = VSUB(cc_ref(i , 3), cc_ref(i , 4)); - ti3 = VADD(cc_ref(i , 3), cc_ref(i , 4)); - tr5 = VSUB(cc_ref(i-1, 2), cc_ref(i-1, 5)); - tr2 = VADD(cc_ref(i-1, 2), cc_ref(i-1, 5)); - tr4 = VSUB(cc_ref(i-1, 3), cc_ref(i-1, 4)); - tr3 = VADD(cc_ref(i-1, 3), cc_ref(i-1, 4)); - ch_ref(i-1, 1) = VADD(cc_ref(i-1, 1), VADD(tr2, tr3)); - ch_ref(i , 1) = VADD(cc_ref(i , 1), VADD(ti2, ti3)); - cr2 = VADD(cc_ref(i-1, 1), VADD(SVMUL(tr11, tr2),SVMUL(tr12, tr3))); - ci2 = VADD(cc_ref(i , 1), VADD(SVMUL(tr11, ti2),SVMUL(tr12, ti3))); - cr3 = VADD(cc_ref(i-1, 1), VADD(SVMUL(tr12, tr2),SVMUL(tr11, tr3))); - ci3 = VADD(cc_ref(i , 1), VADD(SVMUL(tr12, ti2),SVMUL(tr11, ti3))); - cr5 = VADD(SVMUL(ti11, tr5), SVMUL(ti12, tr4)); - ci5 = VADD(SVMUL(ti11, ti5), SVMUL(ti12, ti4)); - cr4 = VSUB(SVMUL(ti12, tr5), SVMUL(ti11, tr4)); - ci4 = VSUB(SVMUL(ti12, ti5), SVMUL(ti11, ti4)); - dr3 = VSUB(cr3, ci4); - dr4 = VADD(cr3, ci4); - di3 = VADD(ci3, cr4); - di4 = VSUB(ci3, cr4); - dr5 = VADD(cr2, ci5); - dr2 = VSUB(cr2, ci5); - di5 = VSUB(ci2, cr5); - di2 = VADD(ci2, cr5); - wr1=wa1[i], wi1=fsign*wa1[i+1], wr2=wa2[i], wi2=fsign*wa2[i+1]; - wr3=wa3[i], wi3=fsign*wa3[i+1], wr4=wa4[i], wi4=fsign*wa4[i+1]; - VCPLXMUL(dr2, di2, LD_PS1(wr1), LD_PS1(wi1)); - ch_ref(i - 1, 2) = dr2; - ch_ref(i, 2) = di2; - VCPLXMUL(dr3, di3, LD_PS1(wr2), LD_PS1(wi2)); - ch_ref(i - 1, 3) = dr3; - ch_ref(i, 3) = di3; - VCPLXMUL(dr4, di4, LD_PS1(wr3), LD_PS1(wi3)); - ch_ref(i - 1, 4) = dr4; - ch_ref(i, 4) = di4; - VCPLXMUL(dr5, di5, LD_PS1(wr4), LD_PS1(wi4)); - ch_ref(i - 1, 5) = dr5; - ch_ref(i, 5) = di5; - } - } -#undef ch_ref -#undef cc_ref -} - -static NEVER_INLINE(void) radf2_ps(int ido, int l1, const v4sf * RESTRICT cc, v4sf * RESTRICT ch, const float *wa1) { - static const float minus_one = -1.f; - int i, k, l1ido = l1*ido; - for (k=0; k < l1ido; k += ido) { - v4sf a = cc[k], b = cc[k + l1ido]; - ch[2*k] = VADD(a, b); - ch[2*(k+ido)-1] = VSUB(a, b); - } - if (ido < 2) return; - if (ido != 2) { - for (k=0; k < l1ido; k += ido) { - for (i=2; i 5) { - wa[i1-1] = wa[i-1]; - wa[i1] = wa[i]; - } - } - l1 = l2; - } -} /* cffti1 */ - - -v4sf *cfftf1_ps(int n, const v4sf *input_readonly, v4sf *work1, v4sf *work2, const float *wa, const int *ifac, int isign) { - v4sf *in = (v4sf*)input_readonly; - v4sf *out = (in == work2 ? work1 : work2); - int nf = ifac[1], k1; - int l1 = 1; - int iw = 0; - assert(in != out && work1 != work2); - for (k1=2; k1<=nf+1; k1++) { - int ip = ifac[k1]; - int l2 = ip*l1; - int ido = n / l2; - int idot = ido + ido; - switch (ip) { - case 5: { - int ix2 = iw + idot; - int ix3 = ix2 + idot; - int ix4 = ix3 + idot; - passf5_ps(idot, l1, in, out, &wa[iw], &wa[ix2], &wa[ix3], &wa[ix4], isign); - } break; - case 4: { - int ix2 = iw + idot; - int ix3 = ix2 + idot; - passf4_ps(idot, l1, in, out, &wa[iw], &wa[ix2], &wa[ix3], isign); - } break; - case 2: { - passf2_ps(idot, l1, in, out, &wa[iw], isign); - } break; - case 3: { - int ix2 = iw + idot; - passf3_ps(idot, l1, in, out, &wa[iw], &wa[ix2], isign); - } break; - default: - assert(0); - } - l1 = l2; - iw += (ip - 1)*idot; - if (out == work2) { - out = work1; in = work2; - } else { - out = work2; in = work1; - } - } - - return in; /* this is in fact the output .. */ -} - - -struct PFFFT_Setup { - int N; - int Ncvec; // nb of complex simd vectors (N/4 if PFFFT_COMPLEX, N/8 if PFFFT_REAL) - int ifac[15]; - pffft_transform_t transform; - v4sf *data; // allocated room for twiddle coefs - float *e; // points into 'data' , N/4*3 elements - float *twiddle; // points into 'data', N/4 elements -}; - -PFFFT_Setup *pffft_new_setup(int N, pffft_transform_t transform) { - PFFFT_Setup *s = (PFFFT_Setup*)malloc(sizeof(PFFFT_Setup)); - int k, m; - /* unfortunately, the fft size must be a multiple of 16 for complex FFTs - and 32 for real FFTs -- a lot of stuff would need to be rewritten to - handle other cases (or maybe just switch to a scalar fft, I don't know..) */ - if (transform == PFFFT_REAL) { assert((N%(2*SIMD_SZ*SIMD_SZ))==0 && N>0); } - if (transform == PFFFT_COMPLEX) { assert((N%(SIMD_SZ*SIMD_SZ))==0 && N>0); } - //assert((N % 32) == 0); - s->N = N; - s->transform = transform; - /* nb of complex simd vectors */ - s->Ncvec = (transform == PFFFT_REAL ? N/2 : N)/SIMD_SZ; - s->data = (v4sf*)pffft_aligned_malloc(2*s->Ncvec * sizeof(v4sf)); - s->e = (float*)s->data; - s->twiddle = (float*)(s->data + (2*s->Ncvec*(SIMD_SZ-1))/SIMD_SZ); - - if (transform == PFFFT_REAL) { - for (k=0; k < s->Ncvec; ++k) { - int i = k/SIMD_SZ; - int j = k%SIMD_SZ; - for (m=0; m < SIMD_SZ-1; ++m) { - float A = -2*M_PI*(m+1)*k / N; - s->e[(2*(i*3 + m) + 0) * SIMD_SZ + j] = cos(A); - s->e[(2*(i*3 + m) + 1) * SIMD_SZ + j] = sin(A); - } - } - rffti1_ps(N/SIMD_SZ, s->twiddle, s->ifac); - } else { - for (k=0; k < s->Ncvec; ++k) { - int i = k/SIMD_SZ; - int j = k%SIMD_SZ; - for (m=0; m < SIMD_SZ-1; ++m) { - float A = -2*M_PI*(m+1)*k / N; - s->e[(2*(i*3 + m) + 0)*SIMD_SZ + j] = cos(A); - s->e[(2*(i*3 + m) + 1)*SIMD_SZ + j] = sin(A); - } - } - cffti1_ps(N/SIMD_SZ, s->twiddle, s->ifac); - } - - /* check that N is decomposable with allowed prime factors */ - for (k=0, m=1; k < s->ifac[1]; ++k) { m *= s->ifac[2+k]; } - if (m != N/SIMD_SZ) { - pffft_destroy_setup(s); s = 0; - } - - return s; -} - - -void pffft_destroy_setup(PFFFT_Setup *s) { - pffft_aligned_free(s->data); - free(s); -} - -#if !defined(PFFFT_SIMD_DISABLE) - -/* [0 0 1 2 3 4 5 6 7 8] -> [0 8 7 6 5 4 3 2 1] */ -static void reversed_copy(int N, const v4sf *in, int in_stride, v4sf *out) { - v4sf g0, g1; - int k; - INTERLEAVE2(in[0], in[1], g0, g1); in += in_stride; - - *--out = VSWAPHL(g0, g1); // [g0l, g0h], [g1l g1h] -> [g1l, g0h] - for (k=1; k < N; ++k) { - v4sf h0, h1; - INTERLEAVE2(in[0], in[1], h0, h1); in += in_stride; - *--out = VSWAPHL(g1, h0); - *--out = VSWAPHL(h0, h1); - g1 = h1; - } - *--out = VSWAPHL(g1, g0); -} - -static void unreversed_copy(int N, const v4sf *in, v4sf *out, int out_stride) { - v4sf g0, g1, h0, h1; - int k; - g0 = g1 = in[0]; ++in; - for (k=1; k < N; ++k) { - h0 = *in++; h1 = *in++; - g1 = VSWAPHL(g1, h0); - h0 = VSWAPHL(h0, h1); - UNINTERLEAVE2(h0, g1, out[0], out[1]); out += out_stride; - g1 = h1; - } - h0 = *in++; h1 = g0; - g1 = VSWAPHL(g1, h0); - h0 = VSWAPHL(h0, h1); - UNINTERLEAVE2(h0, g1, out[0], out[1]); -} - -void pffft_zreorder(PFFFT_Setup *setup, const float *in, float *out, pffft_direction_t direction) { - int k, N = setup->N, Ncvec = setup->Ncvec; - const v4sf *vin = (const v4sf*)in; - v4sf *vout = (v4sf*)out; - assert(in != out); - if (setup->transform == PFFFT_REAL) { - int k, dk = N/32; - if (direction == PFFFT_FORWARD) { - for (k=0; k < dk; ++k) { - INTERLEAVE2(vin[k*8 + 0], vin[k*8 + 1], vout[2*(0*dk + k) + 0], vout[2*(0*dk + k) + 1]); - INTERLEAVE2(vin[k*8 + 4], vin[k*8 + 5], vout[2*(2*dk + k) + 0], vout[2*(2*dk + k) + 1]); - } - reversed_copy(dk, vin+2, 8, (v4sf*)(out + N/2)); - reversed_copy(dk, vin+6, 8, (v4sf*)(out + N)); - } else { - for (k=0; k < dk; ++k) { - UNINTERLEAVE2(vin[2*(0*dk + k) + 0], vin[2*(0*dk + k) + 1], vout[k*8 + 0], vout[k*8 + 1]); - UNINTERLEAVE2(vin[2*(2*dk + k) + 0], vin[2*(2*dk + k) + 1], vout[k*8 + 4], vout[k*8 + 5]); - } - unreversed_copy(dk, (v4sf*)(in + N/4), (v4sf*)(out + N - 6*SIMD_SZ), -8); - unreversed_copy(dk, (v4sf*)(in + 3*N/4), (v4sf*)(out + N - 2*SIMD_SZ), -8); - } - } else { - if (direction == PFFFT_FORWARD) { - for (k=0; k < Ncvec; ++k) { - int kk = (k/4) + (k%4)*(Ncvec/4); - INTERLEAVE2(vin[k*2], vin[k*2+1], vout[kk*2], vout[kk*2+1]); - } - } else { - for (k=0; k < Ncvec; ++k) { - int kk = (k/4) + (k%4)*(Ncvec/4); - UNINTERLEAVE2(vin[kk*2], vin[kk*2+1], vout[k*2], vout[k*2+1]); - } - } - } -} - -void pffft_cplx_finalize(int Ncvec, const v4sf *in, v4sf *out, const v4sf *e) { - int k, dk = Ncvec/SIMD_SZ; // number of 4x4 matrix blocks - v4sf r0, i0, r1, i1, r2, i2, r3, i3; - v4sf sr0, dr0, sr1, dr1, si0, di0, si1, di1; - assert(in != out); - for (k=0; k < dk; ++k) { - r0 = in[8*k+0]; i0 = in[8*k+1]; - r1 = in[8*k+2]; i1 = in[8*k+3]; - r2 = in[8*k+4]; i2 = in[8*k+5]; - r3 = in[8*k+6]; i3 = in[8*k+7]; - VTRANSPOSE4(r0,r1,r2,r3); - VTRANSPOSE4(i0,i1,i2,i3); - VCPLXMUL(r1,i1,e[k*6+0],e[k*6+1]); - VCPLXMUL(r2,i2,e[k*6+2],e[k*6+3]); - VCPLXMUL(r3,i3,e[k*6+4],e[k*6+5]); - - sr0 = VADD(r0,r2); dr0 = VSUB(r0, r2); - sr1 = VADD(r1,r3); dr1 = VSUB(r1, r3); - si0 = VADD(i0,i2); di0 = VSUB(i0, i2); - si1 = VADD(i1,i3); di1 = VSUB(i1, i3); - - /* - transformation for each column is: - - [1 1 1 1 0 0 0 0] [r0] - [1 0 -1 0 0 -1 0 1] [r1] - [1 -1 1 -1 0 0 0 0] [r2] - [1 0 -1 0 0 1 0 -1] [r3] - [0 0 0 0 1 1 1 1] * [i0] - [0 1 0 -1 1 0 -1 0] [i1] - [0 0 0 0 1 -1 1 -1] [i2] - [0 -1 0 1 1 0 -1 0] [i3] - */ - - r0 = VADD(sr0, sr1); i0 = VADD(si0, si1); - r1 = VADD(dr0, di1); i1 = VSUB(di0, dr1); - r2 = VSUB(sr0, sr1); i2 = VSUB(si0, si1); - r3 = VSUB(dr0, di1); i3 = VADD(di0, dr1); - - *out++ = r0; *out++ = i0; *out++ = r1; *out++ = i1; - *out++ = r2; *out++ = i2; *out++ = r3; *out++ = i3; - } -} - -void pffft_cplx_preprocess(int Ncvec, const v4sf *in, v4sf *out, const v4sf *e) { - int k, dk = Ncvec/SIMD_SZ; // number of 4x4 matrix blocks - v4sf r0, i0, r1, i1, r2, i2, r3, i3; - v4sf sr0, dr0, sr1, dr1, si0, di0, si1, di1; - assert(in != out); - for (k=0; k < dk; ++k) { - r0 = in[8*k+0]; i0 = in[8*k+1]; - r1 = in[8*k+2]; i1 = in[8*k+3]; - r2 = in[8*k+4]; i2 = in[8*k+5]; - r3 = in[8*k+6]; i3 = in[8*k+7]; - - sr0 = VADD(r0,r2); dr0 = VSUB(r0, r2); - sr1 = VADD(r1,r3); dr1 = VSUB(r1, r3); - si0 = VADD(i0,i2); di0 = VSUB(i0, i2); - si1 = VADD(i1,i3); di1 = VSUB(i1, i3); - - r0 = VADD(sr0, sr1); i0 = VADD(si0, si1); - r1 = VSUB(dr0, di1); i1 = VADD(di0, dr1); - r2 = VSUB(sr0, sr1); i2 = VSUB(si0, si1); - r3 = VADD(dr0, di1); i3 = VSUB(di0, dr1); - - VCPLXMULCONJ(r1,i1,e[k*6+0],e[k*6+1]); - VCPLXMULCONJ(r2,i2,e[k*6+2],e[k*6+3]); - VCPLXMULCONJ(r3,i3,e[k*6+4],e[k*6+5]); - - VTRANSPOSE4(r0,r1,r2,r3); - VTRANSPOSE4(i0,i1,i2,i3); - - *out++ = r0; *out++ = i0; *out++ = r1; *out++ = i1; - *out++ = r2; *out++ = i2; *out++ = r3; *out++ = i3; - } -} - - -static ALWAYS_INLINE(void) pffft_real_finalize_4x4(const v4sf *in0, const v4sf *in1, const v4sf *in, - const v4sf *e, v4sf *out) { - v4sf r0, i0, r1, i1, r2, i2, r3, i3; - v4sf sr0, dr0, sr1, dr1, si0, di0, si1, di1; - r0 = *in0; i0 = *in1; - r1 = *in++; i1 = *in++; r2 = *in++; i2 = *in++; r3 = *in++; i3 = *in++; - VTRANSPOSE4(r0,r1,r2,r3); - VTRANSPOSE4(i0,i1,i2,i3); - - /* - transformation for each column is: - - [1 1 1 1 0 0 0 0] [r0] - [1 0 -1 0 0 -1 0 1] [r1] - [1 0 -1 0 0 1 0 -1] [r2] - [1 -1 1 -1 0 0 0 0] [r3] - [0 0 0 0 1 1 1 1] * [i0] - [0 -1 0 1 -1 0 1 0] [i1] - [0 -1 0 1 1 0 -1 0] [i2] - [0 0 0 0 -1 1 -1 1] [i3] - */ - - //cerr << "matrix initial, before e , REAL:\n 1: " << r0 << "\n 1: " << r1 << "\n 1: " << r2 << "\n 1: " << r3 << "\n"; - //cerr << "matrix initial, before e, IMAG :\n 1: " << i0 << "\n 1: " << i1 << "\n 1: " << i2 << "\n 1: " << i3 << "\n"; - - VCPLXMUL(r1,i1,e[0],e[1]); - VCPLXMUL(r2,i2,e[2],e[3]); - VCPLXMUL(r3,i3,e[4],e[5]); - - //cerr << "matrix initial, real part:\n 1: " << r0 << "\n 1: " << r1 << "\n 1: " << r2 << "\n 1: " << r3 << "\n"; - //cerr << "matrix initial, imag part:\n 1: " << i0 << "\n 1: " << i1 << "\n 1: " << i2 << "\n 1: " << i3 << "\n"; - - sr0 = VADD(r0,r2); dr0 = VSUB(r0,r2); - sr1 = VADD(r1,r3); dr1 = VSUB(r3,r1); - si0 = VADD(i0,i2); di0 = VSUB(i0,i2); - si1 = VADD(i1,i3); di1 = VSUB(i3,i1); - - r0 = VADD(sr0, sr1); - r3 = VSUB(sr0, sr1); - i0 = VADD(si0, si1); - i3 = VSUB(si1, si0); - r1 = VADD(dr0, di1); - r2 = VSUB(dr0, di1); - i1 = VSUB(dr1, di0); - i2 = VADD(dr1, di0); - - *out++ = r0; - *out++ = i0; - *out++ = r1; - *out++ = i1; - *out++ = r2; - *out++ = i2; - *out++ = r3; - *out++ = i3; - -} - -static NEVER_INLINE(void) pffft_real_finalize(int Ncvec, const v4sf *in, v4sf *out, const v4sf *e) { - int k, dk = Ncvec/SIMD_SZ; // number of 4x4 matrix blocks - /* fftpack order is f0r f1r f1i f2r f2i ... f(n-1)r f(n-1)i f(n)r */ - - v4sf_union cr, ci, *uout = (v4sf_union*)out; - v4sf save = in[7], zero=VZERO(); - float xr0, xi0, xr1, xi1, xr2, xi2, xr3, xi3; - static const float s = M_SQRT2/2; - - cr.v = in[0]; ci.v = in[Ncvec*2-1]; - assert(in != out); - pffft_real_finalize_4x4(&zero, &zero, in+1, e, out); - - /* - [cr0 cr1 cr2 cr3 ci0 ci1 ci2 ci3] - - [Xr(1)] ] [1 1 1 1 0 0 0 0] - [Xr(N/4) ] [0 0 0 0 1 s 0 -s] - [Xr(N/2) ] [1 0 -1 0 0 0 0 0] - [Xr(3N/4)] [0 0 0 0 1 -s 0 s] - [Xi(1) ] [1 -1 1 -1 0 0 0 0] - [Xi(N/4) ] [0 0 0 0 0 -s -1 -s] - [Xi(N/2) ] [0 -1 0 1 0 0 0 0] - [Xi(3N/4)] [0 0 0 0 0 -s 1 -s] - */ - - xr0=(cr.f[0]+cr.f[2]) + (cr.f[1]+cr.f[3]); uout[0].f[0] = xr0; - xi0=(cr.f[0]+cr.f[2]) - (cr.f[1]+cr.f[3]); uout[1].f[0] = xi0; - xr2=(cr.f[0]-cr.f[2]); uout[4].f[0] = xr2; - xi2=(cr.f[3]-cr.f[1]); uout[5].f[0] = xi2; - xr1= ci.f[0] + s*(ci.f[1]-ci.f[3]); uout[2].f[0] = xr1; - xi1=-ci.f[2] - s*(ci.f[1]+ci.f[3]); uout[3].f[0] = xi1; - xr3= ci.f[0] - s*(ci.f[1]-ci.f[3]); uout[6].f[0] = xr3; - xi3= ci.f[2] - s*(ci.f[1]+ci.f[3]); uout[7].f[0] = xi3; - - for (k=1; k < dk; ++k) { - v4sf save_next = in[8*k+7]; - pffft_real_finalize_4x4(&save, &in[8*k+0], in + 8*k+1, - e + k*6, out + k*8); - save = save_next; - } - -} - -static ALWAYS_INLINE(void) pffft_real_preprocess_4x4(const v4sf *in, - const v4sf *e, v4sf *out, int first) { - v4sf r0=in[0], i0=in[1], r1=in[2], i1=in[3], r2=in[4], i2=in[5], r3=in[6], i3=in[7]; - /* - transformation for each column is: - - [1 1 1 1 0 0 0 0] [r0] - [1 0 0 -1 0 -1 -1 0] [r1] - [1 -1 -1 1 0 0 0 0] [r2] - [1 0 0 -1 0 1 1 0] [r3] - [0 0 0 0 1 -1 1 -1] * [i0] - [0 -1 1 0 1 0 0 1] [i1] - [0 0 0 0 1 1 -1 -1] [i2] - [0 1 -1 0 1 0 0 1] [i3] - */ - - v4sf sr0 = VADD(r0,r3), dr0 = VSUB(r0,r3); - v4sf sr1 = VADD(r1,r2), dr1 = VSUB(r1,r2); - v4sf si0 = VADD(i0,i3), di0 = VSUB(i0,i3); - v4sf si1 = VADD(i1,i2), di1 = VSUB(i1,i2); - - r0 = VADD(sr0, sr1); - r2 = VSUB(sr0, sr1); - r1 = VSUB(dr0, si1); - r3 = VADD(dr0, si1); - i0 = VSUB(di0, di1); - i2 = VADD(di0, di1); - i1 = VSUB(si0, dr1); - i3 = VADD(si0, dr1); - - VCPLXMULCONJ(r1,i1,e[0],e[1]); - VCPLXMULCONJ(r2,i2,e[2],e[3]); - VCPLXMULCONJ(r3,i3,e[4],e[5]); - - VTRANSPOSE4(r0,r1,r2,r3); - VTRANSPOSE4(i0,i1,i2,i3); - - if (!first) { - *out++ = r0; - *out++ = i0; - } - *out++ = r1; - *out++ = i1; - *out++ = r2; - *out++ = i2; - *out++ = r3; - *out++ = i3; -} - -static NEVER_INLINE(void) pffft_real_preprocess(int Ncvec, const v4sf *in, v4sf *out, const v4sf *e) { - int k, dk = Ncvec/SIMD_SZ; // number of 4x4 matrix blocks - /* fftpack order is f0r f1r f1i f2r f2i ... f(n-1)r f(n-1)i f(n)r */ - - v4sf_union Xr, Xi, *uout = (v4sf_union*)out; - float cr0, ci0, cr1, ci1, cr2, ci2, cr3, ci3; - static const float s = M_SQRT2; - assert(in != out); - for (k=0; k < 4; ++k) { - Xr.f[k] = ((float*)in)[8*k]; - Xi.f[k] = ((float*)in)[8*k+4]; - } - - pffft_real_preprocess_4x4(in, e, out+1, 1); // will write only 6 values - - /* - [Xr0 Xr1 Xr2 Xr3 Xi0 Xi1 Xi2 Xi3] - - [cr0] [1 0 2 0 1 0 0 0] - [cr1] [1 0 0 0 -1 0 -2 0] - [cr2] [1 0 -2 0 1 0 0 0] - [cr3] [1 0 0 0 -1 0 2 0] - [ci0] [0 2 0 2 0 0 0 0] - [ci1] [0 s 0 -s 0 -s 0 -s] - [ci2] [0 0 0 0 0 -2 0 2] - [ci3] [0 -s 0 s 0 -s 0 -s] - */ - for (k=1; k < dk; ++k) { - pffft_real_preprocess_4x4(in+8*k, e + k*6, out-1+k*8, 0); - } - - cr0=(Xr.f[0]+Xi.f[0]) + 2*Xr.f[2]; uout[0].f[0] = cr0; - cr1=(Xr.f[0]-Xi.f[0]) - 2*Xi.f[2]; uout[0].f[1] = cr1; - cr2=(Xr.f[0]+Xi.f[0]) - 2*Xr.f[2]; uout[0].f[2] = cr2; - cr3=(Xr.f[0]-Xi.f[0]) + 2*Xi.f[2]; uout[0].f[3] = cr3; - ci0= 2*(Xr.f[1]+Xr.f[3]); uout[2*Ncvec-1].f[0] = ci0; - ci1= s*(Xr.f[1]-Xr.f[3]) - s*(Xi.f[1]+Xi.f[3]); uout[2*Ncvec-1].f[1] = ci1; - ci2= 2*(Xi.f[3]-Xi.f[1]); uout[2*Ncvec-1].f[2] = ci2; - ci3=-s*(Xr.f[1]-Xr.f[3]) - s*(Xi.f[1]+Xi.f[3]); uout[2*Ncvec-1].f[3] = ci3; -} - - -void pffft_transform_internal(PFFFT_Setup *setup, const float *finput, float *foutput, v4sf *scratch, - pffft_direction_t direction, int ordered) { - int k, Ncvec = setup->Ncvec; - int nf_odd = (setup->ifac[1] & 1); - - // temporary buffer is allocated on the stack if the scratch pointer is NULL - int stack_allocate = (scratch == 0 ? Ncvec*2 : 1); - VLA_ARRAY_ON_STACK(v4sf, scratch_on_stack, stack_allocate); - - const v4sf *vinput = (const v4sf*)finput; - v4sf *voutput = (v4sf*)foutput; - v4sf *buff[2] = { voutput, scratch ? scratch : scratch_on_stack }; - int ib = (nf_odd ^ ordered ? 1 : 0); - - assert(VALIGNED(finput) && VALIGNED(foutput)); - - //assert(finput != foutput); - if (direction == PFFFT_FORWARD) { - ib = !ib; - if (setup->transform == PFFFT_REAL) { - ib = (rfftf1_ps(Ncvec*2, vinput, buff[ib], buff[!ib], - setup->twiddle, &setup->ifac[0]) == buff[0] ? 0 : 1); - pffft_real_finalize(Ncvec, buff[ib], buff[!ib], (v4sf*)setup->e); - } else { - v4sf *tmp = buff[ib]; - for (k=0; k < Ncvec; ++k) { - UNINTERLEAVE2(vinput[k*2], vinput[k*2+1], tmp[k*2], tmp[k*2+1]); - } - ib = (cfftf1_ps(Ncvec, buff[ib], buff[!ib], buff[ib], - setup->twiddle, &setup->ifac[0], -1) == buff[0] ? 0 : 1); - pffft_cplx_finalize(Ncvec, buff[ib], buff[!ib], (v4sf*)setup->e); - } - if (ordered) { - pffft_zreorder(setup, (float*)buff[!ib], (float*)buff[ib], PFFFT_FORWARD); - } else ib = !ib; - } else { - if (vinput == buff[ib]) { - ib = !ib; // may happen when finput == foutput - } - if (ordered) { - pffft_zreorder(setup, (float*)vinput, (float*)buff[ib], PFFFT_BACKWARD); - vinput = buff[ib]; ib = !ib; - } - if (setup->transform == PFFFT_REAL) { - pffft_real_preprocess(Ncvec, vinput, buff[ib], (v4sf*)setup->e); - ib = (rfftb1_ps(Ncvec*2, buff[ib], buff[0], buff[1], - setup->twiddle, &setup->ifac[0]) == buff[0] ? 0 : 1); - } else { - pffft_cplx_preprocess(Ncvec, vinput, buff[ib], (v4sf*)setup->e); - ib = (cfftf1_ps(Ncvec, buff[ib], buff[0], buff[1], - setup->twiddle, &setup->ifac[0], +1) == buff[0] ? 0 : 1); - for (k=0; k < Ncvec; ++k) { - INTERLEAVE2(buff[ib][k*2], buff[ib][k*2+1], buff[ib][k*2], buff[ib][k*2+1]); - } - } - } - - if (buff[ib] != voutput) { - /* extra copy required -- this situation should only happen when finput == foutput */ - assert(finput==foutput); - for (k=0; k < Ncvec; ++k) { - v4sf a = buff[ib][2*k], b = buff[ib][2*k+1]; - voutput[2*k] = a; voutput[2*k+1] = b; - } - ib = !ib; - } - assert(buff[ib] == voutput); -} - -void pffft_zconvolve_accumulate(PFFFT_Setup *s, const float *a, const float *b, float *ab, float scaling) { - int Ncvec = s->Ncvec; - const v4sf * RESTRICT va = (const v4sf*)a; - const v4sf * RESTRICT vb = (const v4sf*)b; - v4sf * RESTRICT vab = (v4sf*)ab; - -#ifdef __arm__ - __builtin_prefetch(va); - __builtin_prefetch(vb); - __builtin_prefetch(vab); - __builtin_prefetch(va+2); - __builtin_prefetch(vb+2); - __builtin_prefetch(vab+2); - __builtin_prefetch(va+4); - __builtin_prefetch(vb+4); - __builtin_prefetch(vab+4); - __builtin_prefetch(va+6); - __builtin_prefetch(vb+6); - __builtin_prefetch(vab+6); -# ifndef __clang__ -# define ZCONVOLVE_USING_INLINE_NEON_ASM -# endif -#endif - - float ar, ai, br, bi, abr, abi; -#ifndef ZCONVOLVE_USING_INLINE_ASM - v4sf vscal = LD_PS1(scaling); - int i; -#endif - - assert(VALIGNED(a) && VALIGNED(b) && VALIGNED(ab)); - ar = ((v4sf_union*)va)[0].f[0]; - ai = ((v4sf_union*)va)[1].f[0]; - br = ((v4sf_union*)vb)[0].f[0]; - bi = ((v4sf_union*)vb)[1].f[0]; - abr = ((v4sf_union*)vab)[0].f[0]; - abi = ((v4sf_union*)vab)[1].f[0]; - -#ifdef ZCONVOLVE_USING_INLINE_ASM // inline asm version, unfortunately miscompiled by clang 3.2, at least on ubuntu.. so this will be restricted to gcc - const float *a_ = a, *b_ = b; float *ab_ = ab; - int N = Ncvec; - asm volatile("mov r8, %2 \n" - "vdup.f32 q15, %4 \n" - "1: \n" - "pld [%0,#64] \n" - "pld [%1,#64] \n" - "pld [%2,#64] \n" - "pld [%0,#96] \n" - "pld [%1,#96] \n" - "pld [%2,#96] \n" - "vld1.f32 {q0,q1}, [%0,:128]! \n" - "vld1.f32 {q4,q5}, [%1,:128]! \n" - "vld1.f32 {q2,q3}, [%0,:128]! \n" - "vld1.f32 {q6,q7}, [%1,:128]! \n" - "vld1.f32 {q8,q9}, [r8,:128]! \n" - - "vmul.f32 q10, q0, q4 \n" - "vmul.f32 q11, q0, q5 \n" - "vmul.f32 q12, q2, q6 \n" - "vmul.f32 q13, q2, q7 \n" - "vmls.f32 q10, q1, q5 \n" - "vmla.f32 q11, q1, q4 \n" - "vld1.f32 {q0,q1}, [r8,:128]! \n" - "vmls.f32 q12, q3, q7 \n" - "vmla.f32 q13, q3, q6 \n" - "vmla.f32 q8, q10, q15 \n" - "vmla.f32 q9, q11, q15 \n" - "vmla.f32 q0, q12, q15 \n" - "vmla.f32 q1, q13, q15 \n" - "vst1.f32 {q8,q9},[%2,:128]! \n" - "vst1.f32 {q0,q1},[%2,:128]! \n" - "subs %3, #2 \n" - "bne 1b \n" - : "+r"(a_), "+r"(b_), "+r"(ab_), "+r"(N) : "r"(scaling) : "r8", "q0","q1","q2","q3","q4","q5","q6","q7","q8","q9", "q10","q11","q12","q13","q15","memory"); -#else // default routine, works fine for non-arm cpus with current compilers - for (i=0; i < Ncvec; i += 2) { - v4sf ar, ai, br, bi; - ar = va[2*i+0]; ai = va[2*i+1]; - br = vb[2*i+0]; bi = vb[2*i+1]; - VCPLXMUL(ar, ai, br, bi); - vab[2*i+0] = VMADD(ar, vscal, vab[2*i+0]); - vab[2*i+1] = VMADD(ai, vscal, vab[2*i+1]); - ar = va[2*i+2]; ai = va[2*i+3]; - br = vb[2*i+2]; bi = vb[2*i+3]; - VCPLXMUL(ar, ai, br, bi); - vab[2*i+2] = VMADD(ar, vscal, vab[2*i+2]); - vab[2*i+3] = VMADD(ai, vscal, vab[2*i+3]); - } -#endif - if (s->transform == PFFFT_REAL) { - ((v4sf_union*)vab)[0].f[0] = abr + ar*br*scaling; - ((v4sf_union*)vab)[1].f[0] = abi + ai*bi*scaling; - } -} - - -#else // defined(PFFFT_SIMD_DISABLE) - -// standard routine using scalar floats, without SIMD stuff. - -#define pffft_zreorder_nosimd pffft_zreorder -void pffft_zreorder_nosimd(PFFFT_Setup *setup, const float *in, float *out, pffft_direction_t direction) { - int k, N = setup->N; - if (setup->transform == PFFFT_COMPLEX) { - for (k=0; k < 2*N; ++k) out[k] = in[k]; - return; - } - else if (direction == PFFFT_FORWARD) { - float x_N = in[N-1]; - for (k=N-1; k > 1; --k) out[k] = in[k-1]; - out[0] = in[0]; - out[1] = x_N; - } else { - float x_N = in[1]; - for (k=1; k < N-1; ++k) out[k] = in[k+1]; - out[0] = in[0]; - out[N-1] = x_N; - } -} - -#define pffft_transform_internal_nosimd pffft_transform_internal -void pffft_transform_internal_nosimd(PFFFT_Setup *setup, const float *input, float *output, float *scratch, - pffft_direction_t direction, int ordered) { - int Ncvec = setup->Ncvec; - int nf_odd = (setup->ifac[1] & 1); - - // temporary buffer is allocated on the stack if the scratch pointer is NULL - int stack_allocate = (scratch == 0 ? Ncvec*2 : 1); - VLA_ARRAY_ON_STACK(v4sf, scratch_on_stack, stack_allocate); - float *buff[2]; - int ib; - if (scratch == 0) scratch = scratch_on_stack; - buff[0] = output; buff[1] = scratch; - - if (setup->transform == PFFFT_COMPLEX) ordered = 0; // it is always ordered. - ib = (nf_odd ^ ordered ? 1 : 0); - - if (direction == PFFFT_FORWARD) { - if (setup->transform == PFFFT_REAL) { - ib = (rfftf1_ps(Ncvec*2, input, buff[ib], buff[!ib], - setup->twiddle, &setup->ifac[0]) == buff[0] ? 0 : 1); - } else { - ib = (cfftf1_ps(Ncvec, input, buff[ib], buff[!ib], - setup->twiddle, &setup->ifac[0], -1) == buff[0] ? 0 : 1); - } - if (ordered) { - pffft_zreorder(setup, buff[ib], buff[!ib], PFFFT_FORWARD); ib = !ib; - } - } else { - if (input == buff[ib]) { - ib = !ib; // may happen when finput == foutput - } - if (ordered) { - pffft_zreorder(setup, input, buff[!ib], PFFFT_BACKWARD); - input = buff[!ib]; - } - if (setup->transform == PFFFT_REAL) { - ib = (rfftb1_ps(Ncvec*2, input, buff[ib], buff[!ib], - setup->twiddle, &setup->ifac[0]) == buff[0] ? 0 : 1); - } else { - ib = (cfftf1_ps(Ncvec, input, buff[ib], buff[!ib], - setup->twiddle, &setup->ifac[0], +1) == buff[0] ? 0 : 1); - } - } - if (buff[ib] != output) { - int k; - // extra copy required -- this situation should happens only when finput == foutput - assert(input==output); - for (k=0; k < Ncvec; ++k) { - float a = buff[ib][2*k], b = buff[ib][2*k+1]; - output[2*k] = a; output[2*k+1] = b; - } - ib = !ib; - } - assert(buff[ib] == output); -} - -#define pffft_zconvolve_accumulate_nosimd pffft_zconvolve_accumulate -void pffft_zconvolve_accumulate_nosimd(PFFFT_Setup *s, const float *a, const float *b, - float *ab, float scaling) { - int i, Ncvec = s->Ncvec; - - if (s->transform == PFFFT_REAL) { - // take care of the fftpack ordering - ab[0] += a[0]*b[0]*scaling; - ab[2*Ncvec-1] += a[2*Ncvec-1]*b[2*Ncvec-1]*scaling; - ++ab; ++a; ++b; --Ncvec; - } - for (i=0; i < Ncvec; ++i) { - float ar, ai, br, bi; - ar = a[2*i+0]; ai = a[2*i+1]; - br = b[2*i+0]; bi = b[2*i+1]; - VCPLXMUL(ar, ai, br, bi); - ab[2*i+0] += ar*scaling; - ab[2*i+1] += ai*scaling; - } -} - -#endif // defined(PFFFT_SIMD_DISABLE) - -void pffft_transform(PFFFT_Setup *setup, const float *input, float *output, float *work, pffft_direction_t direction) { - pffft_transform_internal(setup, input, output, (v4sf*)work, direction, 0); -} - -void pffft_transform_ordered(PFFFT_Setup *setup, const float *input, float *output, float *work, pffft_direction_t direction) { - pffft_transform_internal(setup, input, output, (v4sf*)work, direction, 1); -} diff --git a/oss-internship-2020/pffft/pffft.h b/oss-internship-2020/pffft/pffft.h deleted file mode 100644 index 2bfa7b3..0000000 --- a/oss-internship-2020/pffft/pffft.h +++ /dev/null @@ -1,177 +0,0 @@ -/* Copyright (c) 2013 Julien Pommier ( pommier@modartt.com ) - - Based on original fortran 77 code from FFTPACKv4 from NETLIB, - authored by Dr Paul Swarztrauber of NCAR, in 1985. - - As confirmed by the NCAR fftpack software curators, the following - FFTPACKv5 license applies to FFTPACKv4 sources. My changes are - released under the same terms. - - FFTPACK license: - - http://www.cisl.ucar.edu/css/software/fftpack5/ftpk.html - - Copyright (c) 2004 the University Corporation for Atmospheric - Research ("UCAR"). All rights reserved. Developed by NCAR's - Computational and Information Systems Laboratory, UCAR, - www.cisl.ucar.edu. - - Redistribution and use of the Software in source and binary forms, - with or without modification, is permitted provided that the - following conditions are met: - - - Neither the names of NCAR's Computational and Information Systems - Laboratory, the University Corporation for Atmospheric Research, - nor the names of its sponsors or contributors may be used to - endorse or promote products derived from this Software without - specific prior written permission. - - - Redistributions of source code must retain the above copyright - notices, this list of conditions, and the disclaimer below. - - - Redistributions in binary form must reproduce the above copyright - notice, this list of conditions, and the disclaimer below in the - documentation and/or other materials provided with the - distribution. - - THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT - HOLDERS BE LIABLE FOR ANY CLAIM, INDIRECT, INCIDENTAL, SPECIAL, - EXEMPLARY, OR CONSEQUENTIAL DAMAGES OR OTHER LIABILITY, WHETHER IN AN - ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE - SOFTWARE. -*/ - -/* - PFFFT : a Pretty Fast FFT. - - This is basically an adaptation of the single precision fftpack - (v4) as found on netlib taking advantage of SIMD instruction found - on cpus such as intel x86 (SSE1), powerpc (Altivec), and arm (NEON). - - For architectures where no SIMD instruction is available, the code - falls back to a scalar version. - - Restrictions: - - - 1D transforms only, with 32-bit single precision. - - - supports only transforms for inputs of length N of the form - N=(2^a)*(3^b)*(5^c), a >= 5, b >=0, c >= 0 (32, 48, 64, 96, 128, - 144, 160, etc are all acceptable lengths). Performance is best for - 128<=N<=8192. - - - all (float*) pointers in the functions below are expected to - have an "simd-compatible" alignment, that is 16 bytes on x86 and - powerpc CPUs. - - You can allocate such buffers with the functions - pffft_aligned_malloc / pffft_aligned_free (or with stuff like - posix_memalign..) - -*/ - -#ifndef PFFFT_H -#define PFFFT_H - -#include // for size_t - -#ifdef __cplusplus -extern "C" { -#endif - - /* opaque struct holding internal stuff (precomputed twiddle factors) - this struct can be shared by many threads as it contains only - read-only data. - */ - typedef struct PFFFT_Setup PFFFT_Setup; - - /* direction of the transform */ - typedef enum { PFFFT_FORWARD, PFFFT_BACKWARD } pffft_direction_t; - - /* type of transform */ - typedef enum { PFFFT_REAL, PFFFT_COMPLEX } pffft_transform_t; - - /* - prepare for performing transforms of size N -- the returned - PFFFT_Setup structure is read-only so it can safely be shared by - multiple concurrent threads. - */ - PFFFT_Setup *pffft_new_setup(int N, pffft_transform_t transform); - void pffft_destroy_setup(PFFFT_Setup *); - /* - Perform a Fourier transform , The z-domain data is stored in the - most efficient order for transforming it back, or using it for - convolution. If you need to have its content sorted in the - "usual" way, that is as an array of interleaved complex numbers, - either use pffft_transform_ordered , or call pffft_zreorder after - the forward fft, and before the backward fft. - - Transforms are not scaled: PFFFT_BACKWARD(PFFFT_FORWARD(x)) = N*x. - Typically you will want to scale the backward transform by 1/N. - - The 'work' pointer should point to an area of N (2*N for complex - fft) floats, properly aligned. If 'work' is NULL, then stack will - be used instead (this is probably the best strategy for small - FFTs, say for N < 16384). - - input and output may alias. - */ - void pffft_transform(PFFFT_Setup *setup, const float *input, float *output, float *work, pffft_direction_t direction); - - /* - Similar to pffft_transform, but makes sure that the output is - ordered as expected (interleaved complex numbers). This is - similar to calling pffft_transform and then pffft_zreorder. - - input and output may alias. - */ - void pffft_transform_ordered(PFFFT_Setup *setup, const float *input, float *output, float *work, pffft_direction_t direction); - - /* - call pffft_zreorder(.., PFFFT_FORWARD) after pffft_transform(..., - PFFFT_FORWARD) if you want to have the frequency components in - the correct "canonical" order, as interleaved complex numbers. - - (for real transforms, both 0-frequency and half frequency - components, which are real, are assembled in the first entry as - F(0)+i*F(n/2+1). Note that the original fftpack did place - F(n/2+1) at the end of the arrays). - - input and output should not alias. - */ - void pffft_zreorder(PFFFT_Setup *setup, const float *input, float *output, pffft_direction_t direction); - - /* - Perform a multiplication of the frequency components of dft_a and - dft_b and accumulate them into dft_ab. The arrays should have - been obtained with pffft_transform(.., PFFFT_FORWARD) and should - *not* have been reordered with pffft_zreorder (otherwise just - perform the operation yourself as the dft coefs are stored as - interleaved complex numbers). - - the operation performed is: dft_ab += (dft_a * fdt_b)*scaling - - The dft_a, dft_b and dft_ab pointers may alias. - */ - void pffft_zconvolve_accumulate(PFFFT_Setup *setup, const float *dft_a, const float *dft_b, float *dft_ab, float scaling); - - /* - the float buffers must have the correct alignment (16-byte boundary - on intel and powerpc). This function may be used to obtain such - correctly aligned buffers. - */ - void *pffft_aligned_malloc(size_t nb_bytes); - void pffft_aligned_free(void *); - - /* return 4 or 1 wether support SSE/Altivec instructions was enable when building pffft.c */ - int pffft_simd_size(); - -#ifdef __cplusplus -} -#endif - -#endif // PFFFT_H diff --git a/oss-internship-2020/pffft/test_pffft.c b/oss-internship-2020/pffft/test_pffft.c deleted file mode 100644 index a5d20c2..0000000 --- a/oss-internship-2020/pffft/test_pffft.c +++ /dev/null @@ -1,419 +0,0 @@ -/* - Copyright (c) 2013 Julien Pommier. - - Small test & bench for PFFFT, comparing its performance with the scalar FFTPACK, FFTW, and Apple vDSP - - How to build: - - on linux, with fftw3: - gcc -o test_pffft -DHAVE_FFTW -msse -mfpmath=sse -O3 -Wall -W pffft.c test_pffft.c fftpack.c -L/usr/local/lib -I/usr/local/include/ -lfftw3f -lm - - on macos, without fftw3: - clang -o test_pffft -DHAVE_VECLIB -O3 -Wall -W pffft.c test_pffft.c fftpack.c -L/usr/local/lib -I/usr/local/include/ -framework Accelerate - - on macos, with fftw3: - clang -o test_pffft -DHAVE_FFTW -DHAVE_VECLIB -O3 -Wall -W pffft.c test_pffft.c fftpack.c -L/usr/local/lib -I/usr/local/include/ -lfftw3f -framework Accelerate - - on windows, with visual c++: - cl /Ox -D_USE_MATH_DEFINES /arch:SSE test_pffft.c pffft.c fftpack.c - - build without SIMD instructions: - gcc -o test_pffft -DPFFFT_SIMD_DISABLE -O3 -Wall -W pffft.c test_pffft.c fftpack.c -lm - - */ - -#include "pffft.h" -#include "fftpack.h" - -#include -#include -#include -#include -#include -#include - -#ifdef HAVE_SYS_TIMES -# include -# include -#endif - -#ifdef HAVE_VECLIB -# include -#endif - -#ifdef HAVE_FFTW -# include -#endif - -#define MAX(x,y) ((x)>(y)?(x):(y)) - -double frand() { - return rand()/(double)RAND_MAX; -} - -#if defined(HAVE_SYS_TIMES) - inline double uclock_sec(void) { - static double ttclk = 0.; - if (ttclk == 0.) ttclk = sysconf(_SC_CLK_TCK); - struct tms t; return ((double)times(&t)) / ttclk; - } -# else - double uclock_sec(void) -{ return (double)clock()/(double)CLOCKS_PER_SEC; } -#endif - - -/* compare results with the regular fftpack */ -void pffft_validate_N(int N, int cplx) { - int Nfloat = N*(cplx?2:1); - int Nbytes = Nfloat * sizeof(float); - float *ref, *in, *out, *tmp, *tmp2; - PFFFT_Setup *s = pffft_new_setup(N, cplx ? PFFFT_COMPLEX : PFFFT_REAL); - int pass; - - if (!s) { printf("Skipping N=%d, not supported\n", N); return; } - ref = pffft_aligned_malloc(Nbytes); - in = pffft_aligned_malloc(Nbytes); - out = pffft_aligned_malloc(Nbytes); - tmp = pffft_aligned_malloc(Nbytes); - tmp2 = pffft_aligned_malloc(Nbytes); - - for (pass=0; pass < 2; ++pass) { - float ref_max = 0; - int k; - //printf("N=%d pass=%d cplx=%d\n", N, pass, cplx); - // compute reference solution with FFTPACK - if (pass == 0) { - float *wrk = malloc(2*Nbytes+15*sizeof(float)); - for (k=0; k < Nfloat; ++k) { - ref[k] = in[k] = frand()*2-1; - out[k] = 1e30; - } - if (!cplx) { - rffti(N, wrk); - rfftf(N, ref, wrk); - // use our ordering for real ffts instead of the one of fftpack - { - float refN=ref[N-1]; - for (k=N-2; k >= 1; --k) ref[k+1] = ref[k]; - ref[1] = refN; - } - } else { - cffti(N, wrk); - cfftf(N, ref, wrk); - } - free(wrk); - } - - for (k = 0; k < Nfloat; ++k) ref_max = MAX(ref_max, fabs(ref[k])); - - - // pass 0 : non canonical ordering of transform coefficients - if (pass == 0) { - // test forward transform, with different input / output - pffft_transform(s, in, tmp, 0, PFFFT_FORWARD); - memcpy(tmp2, tmp, Nbytes); - memcpy(tmp, in, Nbytes); - pffft_transform(s, tmp, tmp, 0, PFFFT_FORWARD); - for (k = 0; k < Nfloat; ++k) { - assert(tmp2[k] == tmp[k]); - } - - // test reordering - pffft_zreorder(s, tmp, out, PFFFT_FORWARD); - pffft_zreorder(s, out, tmp, PFFFT_BACKWARD); - for (k = 0; k < Nfloat; ++k) { - assert(tmp2[k] == tmp[k]); - } - pffft_zreorder(s, tmp, out, PFFFT_FORWARD); - } else { - // pass 1 : canonical ordering of transform coeffs. - pffft_transform_ordered(s, in, tmp, 0, PFFFT_FORWARD); - memcpy(tmp2, tmp, Nbytes); - memcpy(tmp, in, Nbytes); - pffft_transform_ordered(s, tmp, tmp, 0, PFFFT_FORWARD); - for (k = 0; k < Nfloat; ++k) { - assert(tmp2[k] == tmp[k]); - } - memcpy(out, tmp, Nbytes); - } - - { - for (k=0; k < Nfloat; ++k) { - if (!(fabs(ref[k] - out[k]) < 1e-3*ref_max)) { - printf("%s forward PFFFT mismatch found for N=%d\n", (cplx?"CPLX":"REAL"), N); - exit(1); - } - } - - if (pass == 0) pffft_transform(s, tmp, out, 0, PFFFT_BACKWARD); - else pffft_transform_ordered(s, tmp, out, 0, PFFFT_BACKWARD); - memcpy(tmp2, out, Nbytes); - memcpy(out, tmp, Nbytes); - if (pass == 0) pffft_transform(s, out, out, 0, PFFFT_BACKWARD); - else pffft_transform_ordered(s, out, out, 0, PFFFT_BACKWARD); - for (k = 0; k < Nfloat; ++k) { - assert(tmp2[k] == out[k]); - out[k] *= 1.f/N; - } - for (k = 0; k < Nfloat; ++k) { - if (fabs(in[k] - out[k]) > 1e-3 * ref_max) { - printf("pass=%d, %s IFFFT does not match for N=%d\n", pass, (cplx?"CPLX":"REAL"), N); break; - exit(1); - } - } - } - - // quick test of the circular convolution in fft domain - { - float conv_err = 0, conv_max = 0; - - pffft_zreorder(s, ref, tmp, PFFFT_FORWARD); - memset(out, 0, Nbytes); - pffft_zconvolve_accumulate(s, ref, ref, out, 1.0); - pffft_zreorder(s, out, tmp2, PFFFT_FORWARD); - - for (k=0; k < Nfloat; k += 2) { - float ar = tmp[k], ai=tmp[k+1]; - if (cplx || k > 0) { - tmp[k] = ar*ar - ai*ai; - tmp[k+1] = 2*ar*ai; - } else { - tmp[0] = ar*ar; - tmp[1] = ai*ai; - } - } - - for (k=0; k < Nfloat; ++k) { - float d = fabs(tmp[k] - tmp2[k]), e = fabs(tmp[k]); - if (d > conv_err) conv_err = d; - if (e > conv_max) conv_max = e; - } - if (conv_err > 1e-5*conv_max) { - printf("zconvolve error ? %g %g\n", conv_err, conv_max); exit(1); - } - } - - } - - printf("%s PFFFT is OK for N=%d\n", (cplx?"CPLX":"REAL"), N); fflush(stdout); - - pffft_destroy_setup(s); - pffft_aligned_free(ref); - pffft_aligned_free(in); - pffft_aligned_free(out); - pffft_aligned_free(tmp); - pffft_aligned_free(tmp2); -} - -void pffft_validate(int cplx) { - static int Ntest[] = { 16, 32, 64, 96, 128, 160, 192, 256, 288, 384, 5*96, 512, 576, 5*128, 800, 864, 1024, 2048, 2592, 4000, 4096, 12000, 36864, 0}; - int k; - for (k = 0; Ntest[k]; ++k) { - int N = Ntest[k]; - if (N == 16 && !cplx) continue; - pffft_validate_N(N, cplx); - } -} - -int array_output_format = 0; - -void show_output(const char *name, int N, int cplx, float flops, float t0, float t1, int max_iter) { - float mflops = flops/1e6/(t1 - t0 + 1e-16); - if (array_output_format) { - if (flops != -1) { - printf("|%9.0f ", mflops); - } else printf("| n/a "); - } else { - if (flops != -1) { - printf("N=%5d, %s %16s : %6.0f MFlops [t=%6.0f ns, %d runs]\n", N, (cplx?"CPLX":"REAL"), name, mflops, (t1-t0)/2/max_iter * 1e9, max_iter); - } - } - fflush(stdout); -} - -void benchmark_ffts(int N, int cplx) { - int Nfloat = (cplx ? N*2 : N); - int Nbytes = Nfloat * sizeof(float); - float *X = pffft_aligned_malloc(Nbytes), *Y = pffft_aligned_malloc(Nbytes), *Z = pffft_aligned_malloc(Nbytes); - - double t0, t1, flops; - - int k; - int max_iter = 5120000/N*4; -#ifdef __arm__ - max_iter /= 4; -#endif - int iter; - - for (k = 0; k < Nfloat; ++k) { - X[k] = 0; //sqrtf(k+1); - } - - // FFTPack benchmark - { - float *wrk = malloc(2*Nbytes + 15*sizeof(float)); - int max_iter_ = max_iter/pffft_simd_size(); if (max_iter_ == 0) max_iter_ = 1; - if (cplx) cffti(N, wrk); - else rffti(N, wrk); - t0 = uclock_sec(); - - for (iter = 0; iter < max_iter_; ++iter) { - if (cplx) { - cfftf(N, X, wrk); - cfftb(N, X, wrk); - } else { - rfftf(N, X, wrk); - rfftb(N, X, wrk); - } - } - t1 = uclock_sec(); - free(wrk); - - flops = (max_iter_*2) * ((cplx ? 5 : 2.5)*N*log((double)N)/M_LN2); // see http://www.fftw.org/speed/method.html - show_output("FFTPack", N, cplx, flops, t0, t1, max_iter_); - } - -#ifdef HAVE_VECLIB - int log2N = (int)(log(N)/log(2) + 0.5f); - if (N == (1< 1 && strcmp(argv[1], "--array-format") == 0) { - array_output_format = 1; - } - -#ifndef PFFFT_SIMD_DISABLE - validate_pffft_simd(); -#endif - pffft_validate(1); - pffft_validate(0); - if (!array_output_format) { - for (i=0; Nvalues[i] > 0; ++i) { - benchmark_ffts(Nvalues[i], 0 /* real fft */); - } - for (i=0; Nvalues[i] > 0; ++i) { - benchmark_ffts(Nvalues[i], 1 /* cplx fft */); - } - } else { - printf("| input len "); - printf("|real FFTPack"); -#ifdef HAVE_VECLIB - printf("| real vDSP "); -#endif -#ifdef HAVE_FFTW - printf("| real FFTW "); -#endif - printf("| real PFFFT | "); - - printf("|cplx FFTPack"); -#ifdef HAVE_VECLIB - printf("| cplx vDSP "); -#endif -#ifdef HAVE_FFTW - printf("| cplx FFTW "); -#endif - printf("| cplx PFFFT |\n"); - for (i=0; Nvalues[i] > 0; ++i) { - printf("|%9d ", Nvalues[i]); - benchmark_ffts(Nvalues[i], 0); - printf("| "); - benchmark_ffts(Nvalues[i], 1); - printf("|\n"); - } - printf(" (numbers are given in MFlops)\n"); - } - - - return 0; -} From 5ae99706e20cbd579416661a28726754c5e77bfd Mon Sep 17 00:00:00 2001 From: doinachiroiu Date: Wed, 26 Aug 2020 15:03:25 +0000 Subject: [PATCH 45/82] Added PFFFT_ROOT_DIR --- oss-internship-2020/pffft/CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/oss-internship-2020/pffft/CMakeLists.txt b/oss-internship-2020/pffft/CMakeLists.txt index c2a7bcd..41d7520 100644 --- a/oss-internship-2020/pffft/CMakeLists.txt +++ b/oss-internship-2020/pffft/CMakeLists.txt @@ -19,6 +19,8 @@ project(pffft CXX C) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED True) +set(PFFFT_ROOT_DIR https://bitbucket.org/jpommier/pffft.git) + add_library(pffft STATIC master/pffft.c master/pffft.h From 88844fc95819ce71156c56f55c19f09d18b3bfa3 Mon Sep 17 00:00:00 2001 From: Katarzyna Miernikiewicz Date: Wed, 26 Aug 2020 15:13:37 +0000 Subject: [PATCH 46/82] changed README.md --- oss-internship-2020/openjpeg/CMakeLists.txt | 4 ++-- oss-internship-2020/openjpeg/README.md | 23 ++++++++++++++++++++- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/oss-internship-2020/openjpeg/CMakeLists.txt b/oss-internship-2020/openjpeg/CMakeLists.txt index fdc7a9a..3e7d203 100644 --- a/oss-internship-2020/openjpeg/CMakeLists.txt +++ b/oss-internship-2020/openjpeg/CMakeLists.txt @@ -20,10 +20,10 @@ set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED True) # To override lib option -- else SAPI won't work -SET(BUILD_SHARED_LIBS OFF CACHE BOOL "Build OpenJPEG shared library and link executables against it." ) +set(BUILD_SHARED_LIBS OFF CACHE BOOL "Build OpenJPEG shared library and link executables against it." FORCE) add_subdirectory(openjpeg) -set(SAPI_ROOT "" CACHE PATH "Path to the Sandboxed API source tree") +set(SAPI_ROOT "../.." CACHE PATH "Path to the Sandboxed API source tree") set(SAPI_ENABLE_EXAMPLES OFF CACHE BOOL "") set(SAPI_ENABLE_TESTS OFF CACHE BOOL "") set(EXECUTABLE_OUTPUT_PATH "" CACHE PATH "" FORCE) diff --git a/oss-internship-2020/openjpeg/README.md b/oss-internship-2020/openjpeg/README.md index 50df415..4c17082 100644 --- a/oss-internship-2020/openjpeg/README.md +++ b/oss-internship-2020/openjpeg/README.md @@ -2,7 +2,28 @@ This library provides sandboxed version of the [OpenJPEG](https://github.com/uclouvain/openjpeg) library. - ## Examples The examples are sandboxed and simplified version of the main tools provided by the OpenJPEG library, namely (for now) `opj_decompress` from [here](https://github.com/uclouvain/openjpeg/blob/master/src/bin/jp2/opj_decompress.c). + +In `decompress_example.cc` the library's sandboxed API is used to convert the _.jp2_ to _.pnm_ image format. + +## Build + +To build this example, after cloning the whole Sandbox API project, you also need to run + +``` +git submodule update --init --recursive +``` +anywhere in the project tree in order to clone the `openjpeg` submodule. +Then in the `sandboxed-api/oss-internship-2020/openjpeg` run +``` +mkdir build && cd build +cmake -G Ninja +ninja +``` +To run `decompress_sandboxed`: +``` +cd examples +./decompress_sandboxed absolute/path/to/the/file.jp2 absolute/path/to/the/file.pnm +``` From 4f80af5d1c95044d9e0da35ed1534747700adf7a Mon Sep 17 00:00:00 2001 From: Katarzyna Miernikiewicz Date: Thu, 27 Aug 2020 08:38:57 +0000 Subject: [PATCH 47/82] minor changes --- oss-internship-2020/openjpeg/README.md | 2 +- oss-internship-2020/openjpeg/examples/decompress_example.cc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/oss-internship-2020/openjpeg/README.md b/oss-internship-2020/openjpeg/README.md index 4c17082..5e700c6 100644 --- a/oss-internship-2020/openjpeg/README.md +++ b/oss-internship-2020/openjpeg/README.md @@ -1,4 +1,4 @@ -# OpenJPEG +# OpenJPEG Sandboxed API This library provides sandboxed version of the [OpenJPEG](https://github.com/uclouvain/openjpeg) library. diff --git a/oss-internship-2020/openjpeg/examples/decompress_example.cc b/oss-internship-2020/openjpeg/examples/decompress_example.cc index e710bce..ba41c25 100644 --- a/oss-internship-2020/openjpeg/examples/decompress_example.cc +++ b/oss-internship-2020/openjpeg/examples/decompress_example.cc @@ -27,7 +27,7 @@ class Openjp2SapiSandbox : public Openjp2Sandbox { public: - Openjp2SapiSandbox(std::string in_file) : in_file_(std::move(in_file)) {} + explicit Openjp2SapiSandbox(std::string in_file) : in_file_(std::move(in_file)) {} std::unique_ptr ModifyPolicy( sandbox2::PolicyBuilder*) override { From 1b0fbc89bbcf073b90c6c024a0fe922095bd8350 Mon Sep 17 00:00:00 2001 From: Katarzyna Miernikiewicz Date: Thu, 27 Aug 2020 08:39:59 +0000 Subject: [PATCH 48/82] style fix --- oss-internship-2020/openjpeg/examples/decompress_example.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/oss-internship-2020/openjpeg/examples/decompress_example.cc b/oss-internship-2020/openjpeg/examples/decompress_example.cc index ba41c25..8124f2b 100644 --- a/oss-internship-2020/openjpeg/examples/decompress_example.cc +++ b/oss-internship-2020/openjpeg/examples/decompress_example.cc @@ -27,7 +27,8 @@ class Openjp2SapiSandbox : public Openjp2Sandbox { public: - explicit Openjp2SapiSandbox(std::string in_file) : in_file_(std::move(in_file)) {} + explicit Openjp2SapiSandbox(std::string in_file) + : in_file_(std::move(in_file)) {} std::unique_ptr ModifyPolicy( sandbox2::PolicyBuilder*) override { From 2c50bea22c7c53c8a6183df1fcddf2f051e83e72 Mon Sep 17 00:00:00 2001 From: Katarzyna Miernikiewicz Date: Thu, 27 Aug 2020 08:46:12 +0000 Subject: [PATCH 49/82] more style changes --- .../openjpeg/examples/decompress_example.cc | 45 +++++++++---------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/oss-internship-2020/openjpeg/examples/decompress_example.cc b/oss-internship-2020/openjpeg/examples/decompress_example.cc index 8124f2b..bc01222 100644 --- a/oss-internship-2020/openjpeg/examples/decompress_example.cc +++ b/oss-internship-2020/openjpeg/examples/decompress_example.cc @@ -58,66 +58,65 @@ int main(int argc, char* argv[]) { google::InitGoogleLogging(argv[0]); if (argc != 3) { - std::cerr << "usage: " << basename(argv[0]) << " absolute/path/to/INPUT.jp2" + std::cerr << "Usage: " << basename(argv[0]) << " absolute/path/to/INPUT.jp2" << " absolute/path/to/OUTPUT.pnm\n"; return EXIT_FAILURE; } std::string in_file(argv[1]); - // initialize sandbox + // Initialize sandbox. Openjp2SapiSandbox sandbox(in_file); absl::Status status = sandbox.Init(); - CHECK(status.ok()) << "sandbox initialization failed" << status; + CHECK(status.ok()) << "Sandbox initialization failed " << status; Openjp2Api api(&sandbox); sapi::v::ConstCStr in_file_v(in_file.c_str()); - // initialize library's main data-holders + // Initialize library's main data-holders. sapi::StatusOr stream = api.opj_stream_create_default_file_stream(in_file_v.PtrBefore(), 1); - CHECK(stream.ok()) << "opj_stream initialization failed: " << stream.status(); - + CHECK(stream.ok()) << "Stream initialization failed: " << stream.status(); sapi::v::RemotePtr stream_pointer(stream.value()); sapi::StatusOr codec = api.opj_create_decompress(OPJ_CODEC_JP2); - CHECK(codec.ok()) << "opj_codec initialization failed: " << stream.status(); + CHECK(codec.ok()) << "Codec initialization failed: " << stream.status(); sapi::v::RemotePtr codec_pointer(codec.value()); sapi::v::Struct parameters; status = api.opj_set_default_decoder_parameters(parameters.PtrBoth()); - CHECK(status.ok()) << "parameters initialization failed" << status; + CHECK(status.ok()) << "Parameters initialization failed " << status; sapi::StatusOr bool_status = api.opj_setup_decoder(&codec_pointer, parameters.PtrBefore()); - CHECK(bool_status.ok() && bool_status.value()) << "decoder setup failed"; + CHECK(bool_status.ok() && bool_status.value()) << "Decoder setup failed"; - // start reading image from the input file + // Start reading image from the input file. sapi::v::GenericPtr image_pointer; bool_status = api.opj_read_header(&stream_pointer, &codec_pointer, image_pointer.PtrAfter()); CHECK(bool_status.ok() && bool_status.value()) - << "reading image header failed"; + << "Reading image header failed"; sapi::v::Struct image; image.SetRemote(reinterpret_cast(image_pointer.GetValue())); CHECK(sandbox.TransferFromSandboxee(&image).ok()) - << "transfer from sandboxee failed"; + << "Transfer from sandboxee failed"; bool_status = api.opj_decode(&codec_pointer, &stream_pointer, image.PtrAfter()); - CHECK(bool_status.ok() && bool_status.value()) << "decoding failed"; + CHECK(bool_status.ok() && bool_status.value()) << "Decoding failed"; bool_status = api.opj_end_decompress(&codec_pointer, &stream_pointer); - CHECK(bool_status.ok() && bool_status.value()) << "ending decompress failed"; + CHECK(bool_status.ok() && bool_status.value()) << "Ending decompress failed"; int components = image.data().numcomps; - // transfer the read data to the main process + // Transfer the read data to the main process. sapi::v::Array image_components(components); image_components.SetRemote(image.data().comps); CHECK(sandbox.TransferFromSandboxee(&image_components).ok()) - << "transfer from sandboxee failed"; + << "Transfer from sandboxee failed"; image.mutable_data()->comps = (opj_image_comp_t*)image_components.GetLocal(); @@ -130,7 +129,7 @@ int main(int argc, char* argv[]) { for (int i = 0; i < components; ++i) { image_components_data.SetRemote(image.data().comps[i].data); CHECK(sandbox.TransferFromSandboxee(&image_components_data).ok()) - << "transfer from sandboxee failed"; + << "Transfer from sandboxee failed"; std::vector component_data( image_components_data.GetData(), @@ -139,20 +138,20 @@ int main(int argc, char* argv[]) { image_components[i].data = &data[i][0]; } - // convert the image to the desired format and save it to the file + // Convert the image to the desired format and save it to the file. int error = imagetopnm(reinterpret_cast(image.GetLocal()), argv[2], 0); - CHECK(!error) << "image convert failed"; + CHECK(!error) << "Image convert failed"; - // cleanup + // Clean up. status = api.opj_image_destroy(image.PtrNone()); - CHECK(status.ok()) << "image destroy failed" << status; + CHECK(status.ok()) << "Image destroy failed " << status; status = api.opj_stream_destroy(&stream_pointer); - CHECK(status.ok()) << "stream destroy failed" << status; + CHECK(status.ok()) << "Stream destroy failed " << status; status = api.opj_destroy_codec(&codec_pointer); - CHECK(status.ok()) << "codec destroy failed" << status; + CHECK(status.ok()) << "Codec destroy failed " << status; return EXIT_SUCCESS; } From bb623d4c0b25baa8ba2fa0d91a0110f045fd0399 Mon Sep 17 00:00:00 2001 From: doinachiroiu Date: Thu, 27 Aug 2020 12:54:57 +0000 Subject: [PATCH 50/82] Required changes resolved --- oss-internship-2020/pffft/CMakeLists.txt | 2 +- oss-internship-2020/pffft/README.md | 36 +++--- .../pffft/main_pffft_sandboxed.cc | 108 ++++++++++-------- 3 files changed, 79 insertions(+), 67 deletions(-) diff --git a/oss-internship-2020/pffft/CMakeLists.txt b/oss-internship-2020/pffft/CMakeLists.txt index 41d7520..be687cd 100644 --- a/oss-internship-2020/pffft/CMakeLists.txt +++ b/oss-internship-2020/pffft/CMakeLists.txt @@ -88,7 +88,7 @@ add_sapi_library(pffft_sapi INPUTS master/pffft.h master/fftpack.h LIBRARY pffft - LIBRARY_NAME pffft + LIBRARY_NAME Pffft NAMESPACE "" ) diff --git a/oss-internship-2020/pffft/README.md b/oss-internship-2020/pffft/README.md index 2d2a9ca..eaadb20 100644 --- a/oss-internship-2020/pffft/README.md +++ b/oss-internship-2020/pffft/README.md @@ -4,7 +4,9 @@ Build System: CMake OS: Linux ### Check out the PFFFT library & CMake set up -`git submodule add https://bitbucket.org/jpommier/pffft.git` +`git clone https://github.com/doinachiroiu/sandboxed-api/tree/master/oss-internship-2020/pffft` + +`git submodule update --init --recursive` `mkdir -p build && cd build` @@ -22,7 +24,7 @@ display custom info with ## ***About the project*** *PFFFT library is concerned with 1D Fast-Fourier Transformations finding a compromise between accuracy and speed. It deals with real and complex -vectors, both cases being illustrated in the testing part (`main_pffft.c` +vectors, both cases being illustrated in the testing part (`test_pffft.c` for initially and original version, `main_pffft_sandboxed.cc` for our currently implemented sandboxed version). The original files can be found at: https://bitbucket.org/jpommier/pffft/src.* @@ -62,21 +64,23 @@ In the end, the performance of PFFFT library it is outlined by the output.* ### Bugs history - 1. [Solved] pffft benchmark bug: "Sandbox not active" - N = 64, status OK, pffft_transform generates error - N > 64, status not OK - Problem on initialising sapi::StatusOr s; the memory that stays - for s is not the same with the address passed in pffft_transform function. - (sapi::v::GenericPtr - to be changed) +1. [Solved] pffft benchmark bug: "Sandbox not active" + + N = 64, status OK, pffft_transform generates error + N > 64, status not OK + Problem on initialising sapi::StatusOr s; the memory that stays + for s is not the same with the address passed in pffft_transform function. + (sapi::v::GenericPtr - to be changed) - Temporary solution: change the generated files to accept - uintptr_t instead of PFFFT_Setup + Temporary solution: change the generated files to accept + uintptr_t instead of PFFFT_Setup - Solution: using "sapi::v::RemotePtr" instead of "sapi::v::GenericPtr" - to access the memory of object s + Solution: using "sapi::v::RemotePtr" instead of "sapi::v::GenericPtr" + to access the memory of object s - 2. [Unresolved] compiling bug: "No space left on device" - The building process creates some `embed` files that use lots of - memory, trying to write them on /tmp. +2. [Unresolved] compiling bug: "No space left on device" + + The building process creates some `embed` files that use lots of + memory, trying to write them on /tmp. - Temporary solution: clean /tmp directory by `sudo rm -rf /tmp/*`. \ No newline at end of file + Temporary solution: clean /tmp directory by `sudo rm -rf /tmp/*`. \ No newline at end of file diff --git a/oss-internship-2020/pffft/main_pffft_sandboxed.cc b/oss-internship-2020/pffft/main_pffft_sandboxed.cc index 10ea802..f6abf05 100644 --- a/oss-internship-2020/pffft/main_pffft_sandboxed.cc +++ b/oss-internship-2020/pffft/main_pffft_sandboxed.cc @@ -30,10 +30,10 @@ ABSL_DECLARE_FLAG(string, sandbox2_danger_danger_permit_all); ABSL_DECLARE_FLAG(string, sandbox2_danger_danger_permit_all_and_log); -class PffftSapiSandbox : public pffftSandbox { +class PffftSapiSandbox : public PffftSandbox { public: std::unique_ptr ModifyPolicy( - sandbox2::PolicyBuilder*) override { + sandbox2::PolicyBuilder*) { return sandbox2::PolicyBuilder() .AllowStaticStartup() .AllowOpen() @@ -50,11 +50,11 @@ class PffftSapiSandbox : public pffftSandbox { } }; -double UclockSec(void) { return (double)clock() / (double)CLOCKS_PER_SEC; } +double UclockSec() { return static_cast(clock()) / CLOCKS_PER_SEC; } int array_output_format = 0; -void ShowOutput(const char* name, int N, int cplx, float flops, float t0, +void ShowOutput(const char* name, int n, int cplx, float flops, float t0, float t1, int max_iter) { float mflops = flops / 1e6 / (t1 - t0 + 1e-16); if (array_output_format) { @@ -64,7 +64,7 @@ void ShowOutput(const char* name, int N, int cplx, float flops, float t0, printf("| n/a "); } else { if (flops != -1) { - printf("N=%5d, %s %16s : %6.0f MFlops [t=%6.0f ns, %d runs]\n", N, + printf("n=%5d, %s %16s : %6.0f MFlops [t=%6.0f ns, %d runs]\n", n, (cplx ? "CPLX" : "REAL"), name, mflops, (t1 - t0) / 2 / max_iter * 1e9, max_iter); } @@ -72,55 +72,64 @@ void ShowOutput(const char* name, int N, int cplx, float flops, float t0, fflush(stdout); } +absl::Status PffftMain() { + PffftSapiSandbox sandbox; + SAPI_RETURN_IF_ERROR(sandbox.Init()); + + return absl::OkStatus(); +} + int main(int argc, char* argv[]) { - // Initialize Google's logging library. google::InitGoogleLogging(argv[0]); gflags::ParseCommandLineFlags(&argc, &argv, true); - // Nvalues is a vector keeping the values by which iterates N, its value - // representing the input length. More concrete, N is the number of + // kTransformSizes is a vector keeping the values by which iterates n, its value + // representing the input length. More concrete, n is the number of // data points the caclulus is up to (determinating its accuracy). // To show the performance of Fast-Fourier Transformations the program is - // testing for various values of N. - int Nvalues[] = {64, 96, 128, 160, 192, 256, + // testing for various values of n. + constexpr int kTransformSizes[] = {64, 96, 128, 160, 192, 256, 384, 5 * 96, 512, 5 * 128, 3 * 256, 800, 1024, 2048, 2400, 4096, 8192, 9 * 1024, 16384, 32768}; - int i; LOG(INFO) << "Initializing sandbox...\n"; PffftSapiSandbox sandbox; absl::Status init_status = sandbox.Init(); - LOG(INFO) << "Initialization: " << init_status.ToString().c_str() << "\n"; + if (absl::Status status = PffftMain(); !status.ok()) { + LOG(ERROR) << "Initialization failed: " << status.ToString(); + return EXIT_FAILURE; + } - pffftApi api(&sandbox); + LOG(INFO) << "Initialization: " << init_status.ToString(); + + PffftApi api(&sandbox); int cplx = 0; do { - for (int N : Nvalues) { - const int Nfloat = N * (cplx ? 2 : 1); - int Nbytes = Nfloat * sizeof(float); + for (int n : kTransformSizes) { + const int n_float = n * (cplx ? 2 : 1); + int n_bytes = n_float * sizeof(float); - float wrk[2 * Nfloat + 15 * sizeof(float)]; - sapi::v::Array wrk_(wrk, 2 * Nfloat + 15 * sizeof(float)); + std::vector work(2 * n_float + 15, 0.0); + sapi::v::Array work_array(&work[0], work.size()); - float X[Nbytes], Y[Nbytes], Z[Nbytes]; - sapi::v::Array X_(X, Nbytes), Y_(Y, Nbytes), Z_(Z, Nbytes); + float x[n_bytes], y[n_bytes], z[n_bytes]; + sapi::v::Array x_array(x, n_bytes), y_array(y, n_bytes), z_array(z, n_bytes); - double t0, t1, flops; + double t0; + double t1; + double flops; - int max_iter = 5120000 / N * 4; -#ifdef __arm__ - max_iter /= 4; -#endif - int iter, k; + int k; + int max_iter = 5120000 / n * 4; - for (k = 0; k < Nfloat; ++k) { - X[k] = 0; + for (k = 0; k < n_float; ++k) { + x[k] = 0; } // FFTPack benchmark @@ -130,50 +139,49 @@ int main(int argc, char* argv[]) { if (max_iter_ == 0) max_iter_ = 1; if (cplx) { - api.cffti(N, wrk_.PtrBoth()).IgnoreError(); + api.cffti(n, work_array.PtrBoth()).IgnoreError(); } else { - api.rffti(N, wrk_.PtrBoth()).IgnoreError(); + api.rffti(n, work_array.PtrBoth()).IgnoreError(); } t0 = UclockSec(); - for (iter = 0; iter < max_iter_; ++iter) { + for (int iter = 0; iter < max_iter_; ++iter) { if (cplx) { - api.cfftf(N, X_.PtrBoth(), wrk_.PtrBoth()).IgnoreError(); - api.cfftb(N, X_.PtrBoth(), wrk_.PtrBoth()).IgnoreError(); + api.cfftf(n, x_array.PtrBoth(), work_array.PtrBoth()).IgnoreError(); + api.cfftb(n, x_array.PtrBoth(), work_array.PtrBoth()).IgnoreError(); } else { - api.rfftf(N, X_.PtrBoth(), wrk_.PtrBoth()).IgnoreError(); - api.rfftb(N, X_.PtrBoth(), wrk_.PtrBoth()).IgnoreError(); + api.rfftf(n, x_array.PtrBoth(), work_array.PtrBoth()).IgnoreError(); + api.rfftb(n, x_array.PtrBoth(), work_array.PtrBoth()).IgnoreError(); } } t1 = UclockSec(); flops = - (max_iter_ * 2) * ((cplx ? 5 : 2.5) * N * log((double)N) / M_LN2); - ShowOutput("FFTPack", N, cplx, flops, t0, t1, max_iter_); + (max_iter_ * 2) * ((cplx ? 5 : 2.5) * n * log((double)n) / M_LN2); + ShowOutput("FFTPack", n, cplx, flops, t0, t1, max_iter_); } // PFFFT benchmark { sapi::StatusOr s = - api.pffft_new_setup(N, cplx ? PFFFT_COMPLEX : PFFFT_REAL); + api.pffft_new_setup(n, cplx ? PFFFT_COMPLEX : PFFFT_REAL); - LOG(INFO) << "Setup status is: " << s.status().ToString().c_str() - << "\n"; + LOG(INFO) << "Setup status is: " << s.status().ToString(); if (!s.ok()) { printf("Sandbox failed.\n"); - return 1; + return EXIT_FAILURE; } sapi::v::RemotePtr s_reg(s.value()); t0 = UclockSec(); - for (iter = 0; iter < max_iter; ++iter) { - api.pffft_transform(&s_reg, X_.PtrBoth(), Z_.PtrBoth(), - Y_.PtrBoth(), PFFFT_FORWARD) + for (int iter = 0; iter < max_iter; ++iter) { + api.pffft_transform(&s_reg, x_array.PtrBoth(), z_array.PtrBoth(), + y_array.PtrBoth(), PFFFT_FORWARD) .IgnoreError(); - api.pffft_transform(&s_reg, X_.PtrBoth(), Z_.PtrBoth(), - Y_.PtrBoth(), PFFFT_FORWARD) + api.pffft_transform(&s_reg, x_array.PtrBoth(), z_array.PtrBoth(), + y_array.PtrBoth(), PFFFT_FORWARD) .IgnoreError(); } @@ -181,15 +189,15 @@ int main(int argc, char* argv[]) { api.pffft_destroy_setup(&s_reg).IgnoreError(); flops = - (max_iter * 2) * ((cplx ? 5 : 2.5) * N * log((double)N) / M_LN2); - ShowOutput("PFFFT", N, cplx, flops, t0, t1, max_iter); + (max_iter * 2) * ((cplx ? 5 : 2.5) * n * log((double)n) / M_LN2); + ShowOutput("PFFFT", n, cplx, flops, t0, t1, max_iter); - LOG(INFO) << "N = " << N << " SUCCESSFULLY\n\n"; + LOG(INFO) << "n = " << n << " SUCCESSFULLY"; } } cplx = !cplx; } while (cplx); - return 0; + return EXIT_SUCCESS; } \ No newline at end of file From 4336c005d3b6505db883a34da40ceb03843d0a6e Mon Sep 17 00:00:00 2001 From: doinachiroiu Date: Thu, 27 Aug 2020 16:46:59 +0000 Subject: [PATCH 51/82] Added output format flag and made other required changes --- oss-internship-2020/pffft/README.md | 8 +- .../pffft/main_pffft_sandboxed.cc | 118 ++++++++++-------- 2 files changed, 72 insertions(+), 54 deletions(-) diff --git a/oss-internship-2020/pffft/README.md b/oss-internship-2020/pffft/README.md index eaadb20..cf04563 100644 --- a/oss-internship-2020/pffft/README.md +++ b/oss-internship-2020/pffft/README.md @@ -46,7 +46,11 @@ the series that calculate the result of transformation. It is also important to mention that the `cplx` variable stands for a boolean value that tells the type of transformation (0 for REAL and 1 for COMPLEX) and it is taken into account while testing. -In the end, the performance of PFFFT library it is outlined by the output.* +In the end, the performance of PFFFT library it is outlined by the output. +There are two output formats available, from which you can choose through +from which you can choose through `--output_format=` command-line flag. +Without using this type of argument when running, the output format is set +by default.* #### CMake observations resume: * linking pffft and fftpack (which contains necessary functions for pffft) @@ -60,6 +64,8 @@ In the end, the performance of PFFFT library it is outlined by the output.* meaning the number of points to which it is set the calculus (more details of mathematical purpose of N - https://en.wikipedia.org/wiki/Cooley%E2%80%93Tukey_FFT_algorithm). * output shows speed depending on the input length +* use `--output_format=0` or `--output_format=1` arguments to choose between output formats. + `0` is for a detailed output, while `1` is only displaying each transformation process speed. diff --git a/oss-internship-2020/pffft/main_pffft_sandboxed.cc b/oss-internship-2020/pffft/main_pffft_sandboxed.cc index f6abf05..fc14319 100644 --- a/oss-internship-2020/pffft/main_pffft_sandboxed.cc +++ b/oss-internship-2020/pffft/main_pffft_sandboxed.cc @@ -12,16 +12,17 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include #include -#include -#include #include #include -#include #include #include #include +#include +#include +#include #include "pffft_sapi.sapi.h" #include "sandboxed_api/util/flag.h" @@ -32,8 +33,7 @@ ABSL_DECLARE_FLAG(string, sandbox2_danger_danger_permit_all_and_log); class PffftSapiSandbox : public PffftSandbox { public: - std::unique_ptr ModifyPolicy( - sandbox2::PolicyBuilder*) { + std::unique_ptr ModifyPolicy(sandbox2::PolicyBuilder*) { return sandbox2::PolicyBuilder() .AllowStaticStartup() .AllowOpen() @@ -50,14 +50,27 @@ class PffftSapiSandbox : public PffftSandbox { } }; -double UclockSec() { return static_cast(clock()) / CLOCKS_PER_SEC; } +// output_format flag determines whether the output shows information in detail +// or not. By default, the flag is set as 0, meaning an elaborate display +// (see ShowOutput method). +static bool ValidateFlag(const char* flagname, int32_t value) { + if (value >= 0 && value < 32768) { + return true; + } -int array_output_format = 0; + LOG(ERROR) << "Invalid value for --" << flagname << "."; + return false; +} + +DEFINE_int32(output_format, 0, "Value to specific the output format."); +DEFINE_validator(output_format, &ValidateFlag); + +double UclockSec() { return static_cast(clock()) / CLOCKS_PER_SEC; } void ShowOutput(const char* name, int n, int cplx, float flops, float t0, float t1, int max_iter) { float mflops = flops / 1e6 / (t1 - t0 + 1e-16); - if (array_output_format) { + if (FLAGS_output_format) { if (flops != -1) { printf("|%9.0f ", mflops); } else @@ -76,40 +89,18 @@ absl::Status PffftMain() { PffftSapiSandbox sandbox; SAPI_RETURN_IF_ERROR(sandbox.Init()); - return absl::OkStatus(); -} - -int main(int argc, char* argv[]) { - // Initialize Google's logging library. - google::InitGoogleLogging(argv[0]); - - gflags::ParseCommandLineFlags(&argc, &argv, true); - - // kTransformSizes is a vector keeping the values by which iterates n, its value - // representing the input length. More concrete, n is the number of - // data points the caclulus is up to (determinating its accuracy). - // To show the performance of Fast-Fourier Transformations the program is - // testing for various values of n. - constexpr int kTransformSizes[] = {64, 96, 128, 160, 192, 256, - 384, 5 * 96, 512, 5 * 128, 3 * 256, 800, - 1024, 2048, 2400, 4096, 8192, 9 * 1024, - 16384, 32768}; - - LOG(INFO) << "Initializing sandbox...\n"; - - PffftSapiSandbox sandbox; - absl::Status init_status = sandbox.Init(); - - if (absl::Status status = PffftMain(); !status.ok()) { - LOG(ERROR) << "Initialization failed: " << status.ToString(); - return EXIT_FAILURE; - } - - LOG(INFO) << "Initialization: " << init_status.ToString(); - PffftApi api(&sandbox); int cplx = 0; + // kTransformSizes is a vector keeping the values by which iterates n, its + // value representing the input length. More concrete, n is the number of data + // points the caclulus is up to (determinating its accuracy). To show the + // performance of Fast-Fourier Transformations the program is testing for + // various values of n. + constexpr int kTransformSizes[] = { + 64, 96, 128, 160, 192, 256, 384, 5 * 96, 512, 5 * 128, + 3 * 256, 800, 1024, 2048, 2400, 4096, 8192, 9 * 1024, 16384, 32768}; + do { for (int n : kTransformSizes) { const int n_float = n * (cplx ? 2 : 1); @@ -118,26 +109,31 @@ int main(int argc, char* argv[]) { std::vector work(2 * n_float + 15, 0.0); sapi::v::Array work_array(&work[0], work.size()); - float x[n_bytes], y[n_bytes], z[n_bytes]; - sapi::v::Array x_array(x, n_bytes), y_array(y, n_bytes), z_array(z, n_bytes); + std::vector x(n_bytes, 0.0); + sapi::v::Array x_array(&x[0], x.size()); + + std::vector y(n_bytes, 0.0); + sapi::v::Array y_array(&y[0], y.size()); + + std::vector z(n_bytes, 0.0); + sapi::v::Array z_array(&z[0], z.size()); double t0; double t1; double flops; - int k; int max_iter = 5120000 / n * 4; - for (k = 0; k < n_float; ++k) { + for (int k = 0; k < n_float; ++k) { x[k] = 0; } // FFTPack benchmark { // SIMD_SZ == 4 (returning value of pffft_simd_size()) - int max_iter_ = max_iter / 4; + int simd_size_iter = max_iter / 4; - if (max_iter_ == 0) max_iter_ = 1; + if (simd_size_iter == 0) simd_size_iter = 1; if (cplx) { api.cffti(n, work_array.PtrBoth()).IgnoreError(); } else { @@ -145,7 +141,7 @@ int main(int argc, char* argv[]) { } t0 = UclockSec(); - for (int iter = 0; iter < max_iter_; ++iter) { + for (int iter = 0; iter < simd_size_iter; ++iter) { if (cplx) { api.cfftf(n, x_array.PtrBoth(), work_array.PtrBoth()).IgnoreError(); api.cfftb(n, x_array.PtrBoth(), work_array.PtrBoth()).IgnoreError(); @@ -156,11 +152,11 @@ int main(int argc, char* argv[]) { } t1 = UclockSec(); - flops = - (max_iter_ * 2) * ((cplx ? 5 : 2.5) * n * log((double)n) / M_LN2); - ShowOutput("FFTPack", n, cplx, flops, t0, t1, max_iter_); + flops = (simd_size_iter * 2) * + ((cplx ? 5 : 2.5) * n * log((double)n) / M_LN2); + ShowOutput("FFTPack", n, cplx, flops, t0, t1, simd_size_iter); } - + // PFFFT benchmark { sapi::StatusOr s = @@ -170,7 +166,7 @@ int main(int argc, char* argv[]) { if (!s.ok()) { printf("Sandbox failed.\n"); - return EXIT_FAILURE; + return s.status(); } sapi::v::RemotePtr s_reg(s.value()); @@ -188,8 +184,8 @@ int main(int argc, char* argv[]) { t1 = UclockSec(); api.pffft_destroy_setup(&s_reg).IgnoreError(); - flops = - (max_iter * 2) * ((cplx ? 5 : 2.5) * n * log((double)n) / M_LN2); + flops = (max_iter * 2) * ((cplx ? 5 : 2.5) * static_cast(n) * + log((double)n) / M_LN2); ShowOutput("PFFFT", n, cplx, flops, t0, t1, max_iter); LOG(INFO) << "n = " << n << " SUCCESSFULLY"; @@ -199,5 +195,21 @@ int main(int argc, char* argv[]) { cplx = !cplx; } while (cplx); + return absl::OkStatus(); +} + +int main(int argc, char* argv[]) { + // Initialize Google's logging library. + google::InitGoogleLogging(argv[0]); + + gflags::ParseCommandLineFlags(&argc, &argv, true); + + LOG(INFO) << "Initializing sandbox...\n"; + + if (absl::Status status = PffftMain(); !status.ok()) { + LOG(ERROR) << "Initialization failed: " << status.ToString(); + return EXIT_FAILURE; + } + return EXIT_SUCCESS; } \ No newline at end of file From 60b3b5057ce5c32eebd9c47600c71f8a31245c8e Mon Sep 17 00:00:00 2001 From: doinachiroiu Date: Thu, 27 Aug 2020 16:49:09 +0000 Subject: [PATCH 52/82] Small README correction --- oss-internship-2020/pffft/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/oss-internship-2020/pffft/README.md b/oss-internship-2020/pffft/README.md index cf04563..4f2338a 100644 --- a/oss-internship-2020/pffft/README.md +++ b/oss-internship-2020/pffft/README.md @@ -47,8 +47,8 @@ important to mention that the `cplx` variable stands for a boolean value that tells the type of transformation (0 for REAL and 1 for COMPLEX) and it is taken into account while testing. In the end, the performance of PFFFT library it is outlined by the output. -There are two output formats available, from which you can choose through -from which you can choose through `--output_format=` command-line flag. +There are two output formats available, from which you can choose through +`--output_format=` command-line flag. Without using this type of argument when running, the output format is set by default.* From b2351ec6395c7fc0ebe6a8b90d6becc0ed12bd8b Mon Sep 17 00:00:00 2001 From: doinachiroiu Date: Thu, 27 Aug 2020 16:55:55 +0000 Subject: [PATCH 53/82] Small coding style corrections --- oss-internship-2020/pffft/README.md | 2 +- .../pffft/main_pffft_sandboxed.cc | 27 +++++++++---------- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/oss-internship-2020/pffft/README.md b/oss-internship-2020/pffft/README.md index 4f2338a..d94a083 100644 --- a/oss-internship-2020/pffft/README.md +++ b/oss-internship-2020/pffft/README.md @@ -43,7 +43,7 @@ transformations and print the speed for each value and type of transformation. More specifically, the input length is the target for accuracy (named as `N`) and it stands for the number of data points from the series that calculate the result of transformation. It is also -important to mention that the `cplx` variable stands for a boolean value +important to mention that the `complex` variable stands for a boolean value that tells the type of transformation (0 for REAL and 1 for COMPLEX) and it is taken into account while testing. In the end, the performance of PFFFT library it is outlined by the output. diff --git a/oss-internship-2020/pffft/main_pffft_sandboxed.cc b/oss-internship-2020/pffft/main_pffft_sandboxed.cc index fc14319..33116ed 100644 --- a/oss-internship-2020/pffft/main_pffft_sandboxed.cc +++ b/oss-internship-2020/pffft/main_pffft_sandboxed.cc @@ -67,7 +67,7 @@ DEFINE_validator(output_format, &ValidateFlag); double UclockSec() { return static_cast(clock()) / CLOCKS_PER_SEC; } -void ShowOutput(const char* name, int n, int cplx, float flops, float t0, +void ShowOutput(const char* name, int n, int complex, float flops, float t0, float t1, int max_iter) { float mflops = flops / 1e6 / (t1 - t0 + 1e-16); if (FLAGS_output_format) { @@ -78,7 +78,7 @@ void ShowOutput(const char* name, int n, int cplx, float flops, float t0, } else { if (flops != -1) { printf("n=%5d, %s %16s : %6.0f MFlops [t=%6.0f ns, %d runs]\n", n, - (cplx ? "CPLX" : "REAL"), name, mflops, + (complex ? "CPLX" : "REAL"), name, mflops, (t1 - t0) / 2 / max_iter * 1e9, max_iter); } } @@ -90,7 +90,6 @@ absl::Status PffftMain() { SAPI_RETURN_IF_ERROR(sandbox.Init()); PffftApi api(&sandbox); - int cplx = 0; // kTransformSizes is a vector keeping the values by which iterates n, its // value representing the input length. More concrete, n is the number of data @@ -101,9 +100,9 @@ absl::Status PffftMain() { 64, 96, 128, 160, 192, 256, 384, 5 * 96, 512, 5 * 128, 3 * 256, 800, 1024, 2048, 2400, 4096, 8192, 9 * 1024, 16384, 32768}; - do { + for (int complex : {0, 1}) { for (int n : kTransformSizes) { - const int n_float = n * (cplx ? 2 : 1); + const int n_float = n * (complex ? 2 : 1); int n_bytes = n_float * sizeof(float); std::vector work(2 * n_float + 15, 0.0); @@ -134,7 +133,7 @@ absl::Status PffftMain() { int simd_size_iter = max_iter / 4; if (simd_size_iter == 0) simd_size_iter = 1; - if (cplx) { + if (complex) { api.cffti(n, work_array.PtrBoth()).IgnoreError(); } else { api.rffti(n, work_array.PtrBoth()).IgnoreError(); @@ -142,7 +141,7 @@ absl::Status PffftMain() { t0 = UclockSec(); for (int iter = 0; iter < simd_size_iter; ++iter) { - if (cplx) { + if (complex) { api.cfftf(n, x_array.PtrBoth(), work_array.PtrBoth()).IgnoreError(); api.cfftb(n, x_array.PtrBoth(), work_array.PtrBoth()).IgnoreError(); } else { @@ -153,14 +152,14 @@ absl::Status PffftMain() { t1 = UclockSec(); flops = (simd_size_iter * 2) * - ((cplx ? 5 : 2.5) * n * log((double)n) / M_LN2); - ShowOutput("FFTPack", n, cplx, flops, t0, t1, simd_size_iter); + ((complex ? 5 : 2.5) * n * log((double)n) / M_LN2); + ShowOutput("FFTPack", n, complex, flops, t0, t1, simd_size_iter); } // PFFFT benchmark { sapi::StatusOr s = - api.pffft_new_setup(n, cplx ? PFFFT_COMPLEX : PFFFT_REAL); + api.pffft_new_setup(n, complex ? PFFFT_COMPLEX : PFFFT_REAL); LOG(INFO) << "Setup status is: " << s.status().ToString(); @@ -184,16 +183,14 @@ absl::Status PffftMain() { t1 = UclockSec(); api.pffft_destroy_setup(&s_reg).IgnoreError(); - flops = (max_iter * 2) * ((cplx ? 5 : 2.5) * static_cast(n) * + flops = (max_iter * 2) * ((complex ? 5 : 2.5) * static_cast(n) * log((double)n) / M_LN2); - ShowOutput("PFFFT", n, cplx, flops, t0, t1, max_iter); + ShowOutput("PFFFT", n, complex, flops, t0, t1, max_iter); LOG(INFO) << "n = " << n << " SUCCESSFULLY"; } } - - cplx = !cplx; - } while (cplx); + } return absl::OkStatus(); } From 905f86a0ab64a78ca05e038b7bd553ca5fd1b85e Mon Sep 17 00:00:00 2001 From: doinachiroiu Date: Thu, 27 Aug 2020 16:59:58 +0000 Subject: [PATCH 54/82] Small README update regarding variables names --- oss-internship-2020/pffft/README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/oss-internship-2020/pffft/README.md b/oss-internship-2020/pffft/README.md index d94a083..a380ff4 100644 --- a/oss-internship-2020/pffft/README.md +++ b/oss-internship-2020/pffft/README.md @@ -41,7 +41,7 @@ Regarding the testing of the methods, one main is doing this job by iterating through a set of values, that represents the accuracy of transformations and print the speed for each value and type of transformation. More specifically, the input length is the target for -accuracy (named as `N`) and it stands for the number of data points from +accuracy (named as `n`) and it stands for the number of data points from the series that calculate the result of transformation. It is also important to mention that the `complex` variable stands for a boolean value that tells the type of transformation (0 for REAL and 1 for COMPLEX) and @@ -60,9 +60,9 @@ by default.* * containing two testing parts (fft / pffft benchmarks) * showing the performance of the transformations implies testing them through various FFT dimenstions. - Variable N, the input length, will take specific values + Variable n, the input length, will take specific values meaning the number of points to which it is set the calculus - (more details of mathematical purpose of N - https://en.wikipedia.org/wiki/Cooley%E2%80%93Tukey_FFT_algorithm). + (more details of mathematical purpose of n - https://en.wikipedia.org/wiki/Cooley%E2%80%93Tukey_FFT_algorithm). * output shows speed depending on the input length * use `--output_format=0` or `--output_format=1` arguments to choose between output formats. `0` is for a detailed output, while `1` is only displaying each transformation process speed. @@ -72,8 +72,8 @@ by default.* ### Bugs history 1. [Solved] pffft benchmark bug: "Sandbox not active" - N = 64, status OK, pffft_transform generates error - N > 64, status not OK + n = 64, status OK, pffft_transform generates error + n > 64, status not OK Problem on initialising sapi::StatusOr s; the memory that stays for s is not the same with the address passed in pffft_transform function. (sapi::v::GenericPtr - to be changed) From c53f2a900f37ab15ccda9f193dcd79215a728dfc Mon Sep 17 00:00:00 2001 From: Wiktor Garbacz Date: Fri, 28 Aug 2020 04:49:15 -0700 Subject: [PATCH 55/82] Automated rollback of commit e7a195ce42a1c3dbcdd1f78c954a4583b85fc789. PiperOrigin-RevId: 328918626 Change-Id: Iabe93ec7062ea6e750e4185e2b0b672a37111ee7 --- sandboxed_api/proto_helper.h | 2 +- sandboxed_api/rpcchannel.h | 2 +- sandboxed_api/sandbox.cc | 2 +- sandboxed_api/sandbox.h | 5 +++-- sandboxed_api/sandbox2/comms.cc | 2 +- sandboxed_api/sandbox2/examples/network_proxy/BUILD.bazel | 1 + .../sandbox2/examples/network_proxy/networkproxy_bin.cc | 4 ++-- sandboxed_api/sandbox2/forkserver.cc | 2 +- sandboxed_api/sandbox2/mounts.cc | 2 +- sandboxed_api/sandbox2/network_proxy/BUILD.bazel | 2 ++ sandboxed_api/sandbox2/network_proxy/client.cc | 2 +- sandboxed_api/sandbox2/network_proxy/client.h | 2 +- sandboxed_api/sandbox2/network_proxy/filtering.cc | 3 ++- sandboxed_api/sandbox2/network_proxy/filtering.h | 2 +- sandboxed_api/sandbox2/policybuilder.h | 2 +- sandboxed_api/sandbox2/sandbox2.h | 2 +- sandboxed_api/sandbox2/util.cc | 4 ++-- sandboxed_api/sandbox2/util.h | 4 ++-- sandboxed_api/sandbox2/util/minielf.cc | 8 +++----- sandboxed_api/sandbox2/util/minielf.h | 2 +- sandboxed_api/tools/clang_generator/emitter.cc | 2 +- sandboxed_api/tools/clang_generator/emitter.h | 2 +- sandboxed_api/tools/clang_generator/generator.cc | 4 ++-- sandboxed_api/tools/clang_generator/generator.h | 4 ++-- sandboxed_api/tools/clang_generator/types.cc | 2 +- sandboxed_api/util/status.h | 4 ---- 26 files changed, 36 insertions(+), 37 deletions(-) diff --git a/sandboxed_api/proto_helper.h b/sandboxed_api/proto_helper.h index 53ac26d..b00a73e 100644 --- a/sandboxed_api/proto_helper.h +++ b/sandboxed_api/proto_helper.h @@ -22,8 +22,8 @@ #include #include "absl/status/status.h" -#include "sandboxed_api/proto_arg.pb.h" #include "sandboxed_api/util/statusor.h" +#include "sandboxed_api/proto_arg.pb.h" namespace sapi { diff --git a/sandboxed_api/rpcchannel.h b/sandboxed_api/rpcchannel.h index ed50326..c54ef8e 100644 --- a/sandboxed_api/rpcchannel.h +++ b/sandboxed_api/rpcchannel.h @@ -18,11 +18,11 @@ #include #include "absl/status/status.h" +#include "sandboxed_api/util/statusor.h" #include "absl/synchronization/mutex.h" #include "sandboxed_api/call.h" #include "sandboxed_api/sandbox2/comms.h" #include "sandboxed_api/var_type.h" -#include "sandboxed_api/util/statusor.h" namespace sapi { diff --git a/sandboxed_api/sandbox.cc b/sandboxed_api/sandbox.cc index b29646b..18239a4 100644 --- a/sandboxed_api/sandbox.cc +++ b/sandboxed_api/sandbox.cc @@ -392,7 +392,7 @@ absl::Status Sandbox::TransferFromSandboxee(v::Var* var) { } sapi::StatusOr Sandbox::GetCString(const v::RemotePtr& str, - uint64_t max_length) { + uint64_t max_length) { if (!is_active()) { return absl::UnavailableError("Sandbox not active"); } diff --git a/sandboxed_api/sandbox.h b/sandboxed_api/sandbox.h index d8f37ef..8b4ee6a 100644 --- a/sandboxed_api/sandbox.h +++ b/sandboxed_api/sandbox.h @@ -102,8 +102,9 @@ class Sandbox { absl::Status TransferToSandboxee(v::Var* var); absl::Status TransferFromSandboxee(v::Var* var); - sapi::StatusOr GetCString( - const v::RemotePtr& str, uint64_t max_length = 10ULL << 20 /* 10 MiB*/ + sapi::StatusOr GetCString(const v::RemotePtr& str, + uint64_t max_length = 10ULL + << 20 /* 10 MiB*/ ); // Waits until the sandbox terminated and returns the result. diff --git a/sandboxed_api/sandbox2/comms.cc b/sandboxed_api/sandbox2/comms.cc index 6bb891c..3e61013 100644 --- a/sandboxed_api/sandbox2/comms.cc +++ b/sandboxed_api/sandbox2/comms.cc @@ -36,6 +36,7 @@ #include "google/protobuf/message.h" #include "absl/memory/memory.h" #include "absl/status/status.h" +#include "sandboxed_api/util/statusor.h" #include "absl/strings/str_cat.h" #include "absl/strings/str_format.h" #include "absl/synchronization/mutex.h" @@ -44,7 +45,6 @@ #include "sandboxed_api/util/raw_logging.h" #include "sandboxed_api/util/status.h" #include "sandboxed_api/util/status_macros.h" -#include "sandboxed_api/util/statusor.h" #ifdef MEMORY_SANITIZER #include "base/dynamic_annotations.h" diff --git a/sandboxed_api/sandbox2/examples/network_proxy/BUILD.bazel b/sandboxed_api/sandbox2/examples/network_proxy/BUILD.bazel index 133cb88..480c078 100644 --- a/sandboxed_api/sandbox2/examples/network_proxy/BUILD.bazel +++ b/sandboxed_api/sandbox2/examples/network_proxy/BUILD.bazel @@ -49,6 +49,7 @@ cc_binary( "//sandboxed_api/util:flags", "//sandboxed_api/util:status", "//sandboxed_api/util:statusor", + "@com_google_absl//absl/status", "@com_google_absl//absl/strings:str_format", ], ) diff --git a/sandboxed_api/sandbox2/examples/network_proxy/networkproxy_bin.cc b/sandboxed_api/sandbox2/examples/network_proxy/networkproxy_bin.cc index c22947d..b355522 100644 --- a/sandboxed_api/sandbox2/examples/network_proxy/networkproxy_bin.cc +++ b/sandboxed_api/sandbox2/examples/network_proxy/networkproxy_bin.cc @@ -12,15 +12,15 @@ #include #include "sandboxed_api/util/flag.h" +#include "absl/status/status.h" +#include "sandboxed_api/util/statusor.h" #include "absl/strings/str_format.h" #include "sandboxed_api/sandbox2/client.h" #include "sandboxed_api/sandbox2/comms.h" #include "sandboxed_api/sandbox2/network_proxy/client.h" #include "sandboxed_api/sandbox2/util/fileops.h" #include "sandboxed_api/sandbox2/util/strerror.h" -#include "sandboxed_api/util/status.h" #include "sandboxed_api/util/status_macros.h" -#include "sandboxed_api/util/statusor.h" ABSL_FLAG(bool, connect_with_handler, true, "Connect using automatic mode."); diff --git a/sandboxed_api/sandbox2/forkserver.cc b/sandboxed_api/sandbox2/forkserver.cc index 2125927..d0b637c 100644 --- a/sandboxed_api/sandbox2/forkserver.cc +++ b/sandboxed_api/sandbox2/forkserver.cc @@ -36,6 +36,7 @@ #include "absl/memory/memory.h" #include "absl/status/status.h" +#include "sandboxed_api/util/statusor.h" #include "absl/strings/match.h" #include "absl/strings/str_cat.h" #include "absl/strings/str_format.h" @@ -55,7 +56,6 @@ #include "sandboxed_api/sandbox2/util/fileops.h" #include "sandboxed_api/sandbox2/util/strerror.h" #include "sandboxed_api/util/raw_logging.h" -#include "sandboxed_api/util/statusor.h" namespace { // "Moves" the old FD to the new FD number. diff --git a/sandboxed_api/sandbox2/mounts.cc b/sandboxed_api/sandbox2/mounts.cc index 2f734e2..e1c0198 100644 --- a/sandboxed_api/sandbox2/mounts.cc +++ b/sandboxed_api/sandbox2/mounts.cc @@ -27,6 +27,7 @@ #include "google/protobuf/util/message_differencer.h" #include "absl/container/flat_hash_set.h" +#include "sandboxed_api/util/statusor.h" #include "absl/strings/ascii.h" #include "absl/strings/match.h" #include "absl/strings/str_cat.h" @@ -39,7 +40,6 @@ #include "sandboxed_api/sandbox2/util/strerror.h" #include "sandboxed_api/util/raw_logging.h" #include "sandboxed_api/util/status_macros.h" -#include "sandboxed_api/util/statusor.h" namespace sandbox2 { namespace { diff --git a/sandboxed_api/sandbox2/network_proxy/BUILD.bazel b/sandboxed_api/sandbox2/network_proxy/BUILD.bazel index 3a3a9d6..5935390 100644 --- a/sandboxed_api/sandbox2/network_proxy/BUILD.bazel +++ b/sandboxed_api/sandbox2/network_proxy/BUILD.bazel @@ -46,6 +46,7 @@ cc_library( "//sandboxed_api/sandbox2/util:strerror", "//sandboxed_api/util:status", "@com_google_absl//absl/memory", + "@com_google_absl//absl/status", "@com_google_absl//absl/strings", "@com_google_absl//absl/synchronization", "@com_google_glog//:glog", @@ -62,6 +63,7 @@ cc_library( "//sandboxed_api/sandbox2/util:strerror", "//sandboxed_api/util:status", "//sandboxed_api/util:statusor", + "@com_google_absl//absl/status", "@com_google_absl//absl/strings", "@com_google_glog//:glog", ], diff --git a/sandboxed_api/sandbox2/network_proxy/client.cc b/sandboxed_api/sandbox2/network_proxy/client.cc index 586d201..e528041 100644 --- a/sandboxed_api/sandbox2/network_proxy/client.cc +++ b/sandboxed_api/sandbox2/network_proxy/client.cc @@ -25,9 +25,9 @@ #include #include "absl/memory/memory.h" +#include "absl/status/status.h" #include "absl/strings/str_cat.h" #include "sandboxed_api/sandbox2/util/strerror.h" -#include "sandboxed_api/util/status.h" #include "sandboxed_api/util/status_macros.h" namespace sandbox2 { diff --git a/sandboxed_api/sandbox2/network_proxy/client.h b/sandboxed_api/sandbox2/network_proxy/client.h index e7af4dc..7318993 100644 --- a/sandboxed_api/sandbox2/network_proxy/client.h +++ b/sandboxed_api/sandbox2/network_proxy/client.h @@ -17,9 +17,9 @@ #include +#include "absl/status/status.h" #include "absl/synchronization/mutex.h" #include "sandboxed_api/sandbox2/comms.h" -#include "sandboxed_api/util/status.h" namespace sandbox2 { diff --git a/sandboxed_api/sandbox2/network_proxy/filtering.cc b/sandboxed_api/sandbox2/network_proxy/filtering.cc index d6389d8..fc93e6c 100644 --- a/sandboxed_api/sandbox2/network_proxy/filtering.cc +++ b/sandboxed_api/sandbox2/network_proxy/filtering.cc @@ -17,11 +17,12 @@ #include #include +#include "absl/status/status.h" +#include "sandboxed_api/util/statusor.h" #include "absl/strings/numbers.h" #include "absl/strings/str_cat.h" #include "absl/strings/str_split.h" #include "sandboxed_api/sandbox2/util/strerror.h" -#include "sandboxed_api/util/status.h" #include "sandboxed_api/util/status_macros.h" namespace sandbox2 { diff --git a/sandboxed_api/sandbox2/network_proxy/filtering.h b/sandboxed_api/sandbox2/network_proxy/filtering.h index fb6714a..c0a235b 100644 --- a/sandboxed_api/sandbox2/network_proxy/filtering.h +++ b/sandboxed_api/sandbox2/network_proxy/filtering.h @@ -19,8 +19,8 @@ #include -#include "sandboxed_api/sandbox2/comms.h" #include "sandboxed_api/util/statusor.h" +#include "sandboxed_api/sandbox2/comms.h" namespace sandbox2 { diff --git a/sandboxed_api/sandbox2/policybuilder.h b/sandboxed_api/sandbox2/policybuilder.h index f05023c..07095ba 100644 --- a/sandboxed_api/sandbox2/policybuilder.h +++ b/sandboxed_api/sandbox2/policybuilder.h @@ -29,11 +29,11 @@ #include #include "absl/base/macros.h" #include "absl/memory/memory.h" +#include "sandboxed_api/util/statusor.h" #include "absl/strings/string_view.h" #include "sandboxed_api/sandbox2/mounts.h" #include "sandboxed_api/sandbox2/network_proxy/filtering.h" #include "sandboxed_api/sandbox2/policy.h" -#include "sandboxed_api/util/statusor.h" struct bpf_labels; diff --git a/sandboxed_api/sandbox2/sandbox2.h b/sandboxed_api/sandbox2/sandbox2.h index 9527ff2..633297f 100644 --- a/sandboxed_api/sandbox2/sandbox2.h +++ b/sandboxed_api/sandbox2/sandbox2.h @@ -26,6 +26,7 @@ #include #include "absl/base/macros.h" #include "absl/memory/memory.h" +#include "sandboxed_api/util/statusor.h" #include "sandboxed_api/sandbox2/comms.h" #include "sandboxed_api/sandbox2/executor.h" #include "sandboxed_api/sandbox2/ipc.h" @@ -33,7 +34,6 @@ #include "sandboxed_api/sandbox2/notify.h" #include "sandboxed_api/sandbox2/policy.h" #include "sandboxed_api/sandbox2/result.h" -#include "sandboxed_api/util/statusor.h" namespace sandbox2 { diff --git a/sandboxed_api/sandbox2/util.cc b/sandboxed_api/sandbox2/util.cc index 24d4a36..b760d26 100644 --- a/sandboxed_api/sandbox2/util.cc +++ b/sandboxed_api/sandbox2/util.cc @@ -183,8 +183,8 @@ bool CreateMemFd(int* fd, const char* name) { } sapi::StatusOr Communicate(const std::vector& argv, - const std::vector& envv, - std::string* output) { + const std::vector& envv, + std::string* output) { int cout_pipe[2]; posix_spawn_file_actions_t action; diff --git a/sandboxed_api/sandbox2/util.h b/sandboxed_api/sandbox2/util.h index 87fe31c..e42bf72 100644 --- a/sandboxed_api/sandbox2/util.h +++ b/sandboxed_api/sandbox2/util.h @@ -63,8 +63,8 @@ bool CreateMemFd(int* fd, const char* name = "buffer_file"); // Executes a the program given by argv and the specified environment and // captures any output to stdout/stderr. sapi::StatusOr Communicate(const std::vector& argv, - const std::vector& envv, - std::string* output); + const std::vector& envv, + std::string* output); // Returns signal description. std::string GetSignalName(int signo); diff --git a/sandboxed_api/sandbox2/util/minielf.cc b/sandboxed_api/sandbox2/util/minielf.cc index 3eac979..dce033b 100644 --- a/sandboxed_api/sandbox2/util/minielf.cc +++ b/sandboxed_api/sandbox2/util/minielf.cc @@ -219,8 +219,7 @@ absl::Status ElfParser::ReadFileHeader() { return absl::OkStatus(); } -sapi::StatusOr ElfParser::ReadSectionHeader( - absl::string_view src) { +sapi::StatusOr ElfParser::ReadSectionHeader(absl::string_view src) { if (src.size() < sizeof(Elf64_Shdr)) { return absl::FailedPreconditionError( absl::StrCat("invalid section header data: got ", src.size(), @@ -293,8 +292,7 @@ sapi::StatusOr ElfParser::ReadSectionContents( return rv; } -sapi::StatusOr ElfParser::ReadProgramHeader( - absl::string_view src) { +sapi::StatusOr ElfParser::ReadProgramHeader(absl::string_view src) { if (src.size() < sizeof(Elf64_Phdr)) { return absl::FailedPreconditionError( absl::StrCat("invalid program header data: got ", src.size(), @@ -514,7 +512,7 @@ sapi::StatusOr ElfParser::Parse(FILE* elf, uint32_t features) { } sapi::StatusOr ElfFile::ParseFromFile(const std::string& filename, - uint32_t features) { + uint32_t features) { std::unique_ptr elf{fopen(filename.c_str(), "r"), [](FILE* f) { fclose(f); }}; if (!elf) { diff --git a/sandboxed_api/sandbox2/util/minielf.h b/sandboxed_api/sandbox2/util/minielf.h index 247d271..c33a604 100644 --- a/sandboxed_api/sandbox2/util/minielf.h +++ b/sandboxed_api/sandbox2/util/minielf.h @@ -34,7 +34,7 @@ class ElfFile { }; static sapi::StatusOr ParseFromFile(const std::string& filename, - uint32_t features); + uint32_t features); int64_t file_size() const { return file_size_; } const std::string& interpreter() const { return interpreter_; } diff --git a/sandboxed_api/tools/clang_generator/emitter.cc b/sandboxed_api/tools/clang_generator/emitter.cc index 18fdcd9..78b9a9a 100644 --- a/sandboxed_api/tools/clang_generator/emitter.cc +++ b/sandboxed_api/tools/clang_generator/emitter.cc @@ -46,10 +46,10 @@ constexpr absl::string_view kHeaderProlog = #include "absl/base/macros.h" #include "absl/status/status.h" +#include "sandboxed_api/util/statusor.h" #include "sandboxed_api/sandbox.h" #include "sandboxed_api/vars.h" #include "sandboxed_api/util/status_macros.h" -#include "sandboxed_api/util/statusor.h" )"; constexpr absl::string_view kHeaderEpilog = diff --git a/sandboxed_api/tools/clang_generator/emitter.h b/sandboxed_api/tools/clang_generator/emitter.h index 42ba92d..ad9f5f8 100644 --- a/sandboxed_api/tools/clang_generator/emitter.h +++ b/sandboxed_api/tools/clang_generator/emitter.h @@ -18,12 +18,12 @@ #include #include "absl/status/status.h" +#include "sandboxed_api/util/statusor.h" #include "absl/strings/string_view.h" #include "clang/AST/Decl.h" #include "clang/AST/Type.h" #include "sandboxed_api/tools/clang_generator/generator.h" #include "sandboxed_api/tools/clang_generator/types.h" -#include "sandboxed_api/util/statusor.h" namespace sapi { diff --git a/sandboxed_api/tools/clang_generator/generator.cc b/sandboxed_api/tools/clang_generator/generator.cc index 508877e..ffe2106 100644 --- a/sandboxed_api/tools/clang_generator/generator.cc +++ b/sandboxed_api/tools/clang_generator/generator.cc @@ -18,12 +18,12 @@ #include #include "absl/status/status.h" +#include "sandboxed_api/util/statusor.h" #include "clang/Format/Format.h" #include "sandboxed_api/sandbox2/util/fileops.h" #include "sandboxed_api/tools/clang_generator/diagnostics.h" #include "sandboxed_api/tools/clang_generator/emitter.h" #include "sandboxed_api/util/status_macros.h" -#include "sandboxed_api/util/statusor.h" namespace sapi { namespace { @@ -68,7 +68,7 @@ bool GeneratorASTVisitor::VisitFunctionDecl(clang::FunctionDecl* decl) { namespace internal { sapi::StatusOr ReformatGoogleStyle(const std::string& filename, - const std::string& code) { + const std::string& code) { // Configure code style based on Google style, but enforce pointer alignment clang::format::FormatStyle style = clang::format::getGoogleStyle(clang::format::FormatStyle::LK_Cpp); diff --git a/sandboxed_api/tools/clang_generator/generator.h b/sandboxed_api/tools/clang_generator/generator.h index 24e2658..3b9680b 100644 --- a/sandboxed_api/tools/clang_generator/generator.h +++ b/sandboxed_api/tools/clang_generator/generator.h @@ -20,13 +20,13 @@ #include "absl/container/flat_hash_set.h" #include "absl/memory/memory.h" #include "absl/status/status.h" +#include "sandboxed_api/util/statusor.h" #include "clang/AST/ASTConsumer.h" #include "clang/AST/RecursiveASTVisitor.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Frontend/FrontendAction.h" #include "clang/Tooling/Tooling.h" #include "sandboxed_api/tools/clang_generator/types.h" -#include "sandboxed_api/util/statusor.h" namespace sapi { @@ -67,7 +67,7 @@ class GeneratorASTVisitor namespace internal { sapi::StatusOr ReformatGoogleStyle(const std::string& filename, - const std::string& code); + const std::string& code); } // namespace internal diff --git a/sandboxed_api/tools/clang_generator/types.cc b/sandboxed_api/tools/clang_generator/types.cc index 4d91499..321d1e2 100644 --- a/sandboxed_api/tools/clang_generator/types.cc +++ b/sandboxed_api/tools/clang_generator/types.cc @@ -208,7 +208,7 @@ std::string MapQualTypeReturn(const clang::ASTContext& context, return "absl::Status"; } // Remove const qualifier like in MapQualType(). - return absl::StrCat("::sapi::StatusOr<", + return absl::StrCat("sapi::StatusOr<", MaybeRemoveConst(context, qual).getAsString(), ">"); } diff --git a/sandboxed_api/util/status.h b/sandboxed_api/util/status.h index 6390bc0..d9962fd 100644 --- a/sandboxed_api/util/status.h +++ b/sandboxed_api/util/status.h @@ -12,10 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -// This file and it's implementation provide a custom fork of -// util/task/status.h. This will become obsolete and will be replaced once -// Abseil releases absl::Status. - #ifndef THIRD_PARTY_SAPI_UTIL_STATUS_H_ #define THIRD_PARTY_SAPI_UTIL_STATUS_H_ From 28089de3532c1b7f17c137ae644103c74342d7e4 Mon Sep 17 00:00:00 2001 From: Katarzyna Miernikiewicz Date: Fri, 28 Aug 2020 15:27:23 +0000 Subject: [PATCH 56/82] workaround to avoid manually copying library files --- oss-internship-2020/openjpeg/.gitignore | 2 + .../openjpeg/examples/CMakeLists.txt | 41 +- .../openjpeg/examples/convert.patch | 2871 +++++++++++++++++ .../openjpeg/examples/convert_h.patch | 135 + .../openjpeg/examples/convert_helper.cc | 345 -- .../openjpeg/examples/convert_helper.h | 8 - .../openjpeg/examples/decompress_example.cc | 3 +- 7 files changed, 3040 insertions(+), 365 deletions(-) create mode 100644 oss-internship-2020/openjpeg/examples/convert.patch create mode 100644 oss-internship-2020/openjpeg/examples/convert_h.patch delete mode 100644 oss-internship-2020/openjpeg/examples/convert_helper.cc delete mode 100644 oss-internship-2020/openjpeg/examples/convert_helper.h diff --git a/oss-internship-2020/openjpeg/.gitignore b/oss-internship-2020/openjpeg/.gitignore index 567609b..cccbfce 100644 --- a/oss-internship-2020/openjpeg/.gitignore +++ b/oss-internship-2020/openjpeg/.gitignore @@ -1 +1,3 @@ build/ +examples/convert_lib.cc +examples/convert_lib.h diff --git a/oss-internship-2020/openjpeg/examples/CMakeLists.txt b/oss-internship-2020/openjpeg/examples/CMakeLists.txt index 3156472..77b4290 100644 --- a/oss-internship-2020/openjpeg/examples/CMakeLists.txt +++ b/oss-internship-2020/openjpeg/examples/CMakeLists.txt @@ -12,24 +12,45 @@ # See the License for the specific language governing permissions and # limitations under the License. -# copy of one of the converting functions -# put here to omit the library's complex build logic +add_custom_target(patching_reverse_before + COMMAND cd ${PROJECT_SOURCE_DIR}/openjpeg/src/bin/jp2/ && patch -R --no-backup-if-mismatch -r /dev/null convert.c < ${PROJECT_SOURCE_DIR}/examples/convert.patch > /dev/null || (exit 0) + COMMAND cd ${PROJECT_SOURCE_DIR}/openjpeg/src/bin/jp2/ && patch -R --no-backup-if-mismatch -r /dev/null convert.h < ${PROJECT_SOURCE_DIR}/examples/convert_h.patch > /dev/null || (exit 0) + ) + +add_custom_command( + DEPENDS opj_decompress opj_compress opj_dump + OUTPUT ${PROJECT_SOURCE_DIR}/examples/convert_lib.cc ${PROJECT_SOURCE_DIR}/examples/convert_lib.h + COMMAND cd ${PROJECT_SOURCE_DIR}/openjpeg/src/bin/jp2/ && patch convert.c < ${PROJECT_SOURCE_DIR}/examples/convert.patch > /dev/null && cp convert.c ${PROJECT_SOURCE_DIR}/examples/convert_lib.cc + COMMAND cd ${PROJECT_SOURCE_DIR}/openjpeg/src/bin/jp2/ && patch convert.h < ${PROJECT_SOURCE_DIR}/examples/convert_h.patch > /dev/null && cp convert.h ${PROJECT_SOURCE_DIR}/examples/convert_lib.h + ) + add_library(convert_helper STATIC - convert_helper.h - convert_helper.cc + ${PROJECT_SOURCE_DIR}/examples/convert_lib.cc + ${PROJECT_SOURCE_DIR}/examples/convert_lib.h ) +add_dependencies(opj_decompress patching_reverse_before) +add_dependencies(opj_compress patching_reverse_before) +add_dependencies(opj_dump patching_reverse_before) + add_executable(decompress_sandboxed - decompress_example.cc + decompress_example.cc ) target_link_libraries(decompress_sandboxed PRIVATE - convert_helper - openjp2_sapi - sapi::sapi + convert_helper + openjp2_sapi + sapi::sapi ) target_link_libraries(convert_helper PRIVATE - openjp2_sapi - sapi::sapi + openjp2_sapi + sapi::sapi ) + +add_custom_target(patching_reverse ALL + COMMAND cd ${PROJECT_SOURCE_DIR}/openjpeg/src/bin/jp2/ && patch -R convert.c < ${PROJECT_SOURCE_DIR}/examples/convert.patch > /dev/null + COMMAND cd ${PROJECT_SOURCE_DIR}/openjpeg/src/bin/jp2/ && patch -R convert.h < ${PROJECT_SOURCE_DIR}/examples/convert_h.patch > /dev/null + ) + +add_dependencies(patching_reverse decompress_sandboxed) diff --git a/oss-internship-2020/openjpeg/examples/convert.patch b/oss-internship-2020/openjpeg/examples/convert.patch new file mode 100644 index 0000000..416c6b7 --- /dev/null +++ b/oss-internship-2020/openjpeg/examples/convert.patch @@ -0,0 +1,2871 @@ +--- convert.c 2020-08-27 15:46:32.911584344 +0000 ++++ ../../../../examples/convert_helper.cc 2020-08-27 15:10:52.695231032 +0000 +@@ -35,2605 +35,311 @@ + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +-#include "opj_apps_config.h" + +-#include +-#include +-#include +-#include +-#include +- +-#include "openjpeg.h" +-#include "convert.h" +- +-/* +- * Get logarithm of an integer and round downwards. +- * +- * log2(a) +- */ +-static int int_floorlog2(int a) +-{ +- int l; +- for (l = 0; a > 1; l++) { +- a >>= 1; +- } +- return l; +-} +- +-/* Component precision scaling */ +-void clip_component(opj_image_comp_t* component, OPJ_UINT32 precision) +-{ +- OPJ_SIZE_T i; +- OPJ_SIZE_T len; +- OPJ_UINT32 umax = (OPJ_UINT32)((OPJ_INT32) - 1); +- +- len = (OPJ_SIZE_T)component->w * (OPJ_SIZE_T)component->h; +- if (precision < 32) { +- umax = (1U << precision) - 1U; +- } +- +- if (component->sgnd) { +- OPJ_INT32* l_data = component->data; +- OPJ_INT32 max = (OPJ_INT32)(umax / 2U); +- OPJ_INT32 min = -max - 1; +- for (i = 0; i < len; ++i) { +- if (l_data[i] > max) { +- l_data[i] = max; +- } else if (l_data[i] < min) { +- l_data[i] = min; +- } +- } +- } else { +- OPJ_UINT32* l_data = (OPJ_UINT32*)component->data; +- for (i = 0; i < len; ++i) { +- if (l_data[i] > umax) { +- l_data[i] = umax; +- } +- } +- } +- component->prec = precision; +-} ++// copies of a few library tools + +-/* Component precision scaling */ +-static void scale_component_up(opj_image_comp_t* component, +- OPJ_UINT32 precision) +-{ +- OPJ_SIZE_T i, len; +- +- len = (OPJ_SIZE_T)component->w * (OPJ_SIZE_T)component->h; +- if (component->sgnd) { +- OPJ_INT64 newMax = (OPJ_INT64)(1U << (precision - 1)); +- OPJ_INT64 oldMax = (OPJ_INT64)(1U << (component->prec - 1)); +- OPJ_INT32* l_data = component->data; +- for (i = 0; i < len; ++i) { +- l_data[i] = (OPJ_INT32)(((OPJ_INT64)l_data[i] * newMax) / oldMax); +- } +- } else { +- OPJ_UINT64 newMax = (OPJ_UINT64)((1U << precision) - 1U); +- OPJ_UINT64 oldMax = (OPJ_UINT64)((1U << component->prec) - 1U); +- OPJ_UINT32* l_data = (OPJ_UINT32*)component->data; +- for (i = 0; i < len; ++i) { +- l_data[i] = (OPJ_UINT32)(((OPJ_UINT64)l_data[i] * newMax) / oldMax); +- } +- } +- component->prec = precision; +- component->bpp = precision; +-} +-void scale_component(opj_image_comp_t* component, OPJ_UINT32 precision) +-{ +- int shift; +- OPJ_SIZE_T i, len; +- +- if (component->prec == precision) { +- return; +- } +- if (component->prec < precision) { +- scale_component_up(component, precision); +- return; +- } +- shift = (int)(component->prec - precision); +- len = (OPJ_SIZE_T)component->w * (OPJ_SIZE_T)component->h; +- if (component->sgnd) { +- OPJ_INT32* l_data = component->data; +- for (i = 0; i < len; ++i) { +- l_data[i] >>= shift; +- } +- } else { +- OPJ_UINT32* l_data = (OPJ_UINT32*)component->data; +- for (i = 0; i < len; ++i) { +- l_data[i] >>= shift; +- } +- } +- component->bpp = precision; +- component->prec = precision; +-} +- +- +-/* planar / interleaved conversions */ +-/* used by PNG/TIFF */ +-static void convert_32s_C1P1(const OPJ_INT32* pSrc, OPJ_INT32* const* pDst, +- OPJ_SIZE_T length) +-{ +- memcpy(pDst[0], pSrc, length * sizeof(OPJ_INT32)); +-} +-static void convert_32s_C2P2(const OPJ_INT32* pSrc, OPJ_INT32* const* pDst, +- OPJ_SIZE_T length) +-{ +- OPJ_SIZE_T i; +- OPJ_INT32* pDst0 = pDst[0]; +- OPJ_INT32* pDst1 = pDst[1]; +- +- for (i = 0; i < length; i++) { +- pDst0[i] = pSrc[2 * i + 0]; +- pDst1[i] = pSrc[2 * i + 1]; +- } +-} +-static void convert_32s_C3P3(const OPJ_INT32* pSrc, OPJ_INT32* const* pDst, +- OPJ_SIZE_T length) +-{ +- OPJ_SIZE_T i; +- OPJ_INT32* pDst0 = pDst[0]; +- OPJ_INT32* pDst1 = pDst[1]; +- OPJ_INT32* pDst2 = pDst[2]; +- +- for (i = 0; i < length; i++) { +- pDst0[i] = pSrc[3 * i + 0]; +- pDst1[i] = pSrc[3 * i + 1]; +- pDst2[i] = pSrc[3 * i + 2]; +- } +-} +-static void convert_32s_C4P4(const OPJ_INT32* pSrc, OPJ_INT32* const* pDst, +- OPJ_SIZE_T length) +-{ +- OPJ_SIZE_T i; +- OPJ_INT32* pDst0 = pDst[0]; +- OPJ_INT32* pDst1 = pDst[1]; +- OPJ_INT32* pDst2 = pDst[2]; +- OPJ_INT32* pDst3 = pDst[3]; +- +- for (i = 0; i < length; i++) { +- pDst0[i] = pSrc[4 * i + 0]; +- pDst1[i] = pSrc[4 * i + 1]; +- pDst2[i] = pSrc[4 * i + 2]; +- pDst3[i] = pSrc[4 * i + 3]; +- } +-} +-const convert_32s_CXPX convert_32s_CXPX_LUT[5] = { +- NULL, +- convert_32s_C1P1, +- convert_32s_C2P2, +- convert_32s_C3P3, +- convert_32s_C4P4 +-}; +- +-static void convert_32s_P1C1(OPJ_INT32 const* const* pSrc, OPJ_INT32* pDst, +- OPJ_SIZE_T length, OPJ_INT32 adjust) +-{ +- OPJ_SIZE_T i; +- const OPJ_INT32* pSrc0 = pSrc[0]; ++#include "convert_lib.h" + +- for (i = 0; i < length; i++) { +- pDst[i] = pSrc0[i] + adjust; +- } +-} +-static void convert_32s_P2C2(OPJ_INT32 const* const* pSrc, OPJ_INT32* pDst, +- OPJ_SIZE_T length, OPJ_INT32 adjust) +-{ +- OPJ_SIZE_T i; +- const OPJ_INT32* pSrc0 = pSrc[0]; +- const OPJ_INT32* pSrc1 = pSrc[1]; +- +- for (i = 0; i < length; i++) { +- pDst[2 * i + 0] = pSrc0[i] + adjust; +- pDst[2 * i + 1] = pSrc1[i] + adjust; +- } +-} +-static void convert_32s_P3C3(OPJ_INT32 const* const* pSrc, OPJ_INT32* pDst, +- OPJ_SIZE_T length, OPJ_INT32 adjust) +-{ +- OPJ_SIZE_T i; +- const OPJ_INT32* pSrc0 = pSrc[0]; +- const OPJ_INT32* pSrc1 = pSrc[1]; +- const OPJ_INT32* pSrc2 = pSrc[2]; +- +- for (i = 0; i < length; i++) { +- pDst[3 * i + 0] = pSrc0[i] + adjust; +- pDst[3 * i + 1] = pSrc1[i] + adjust; +- pDst[3 * i + 2] = pSrc2[i] + adjust; +- } +-} +-static void convert_32s_P4C4(OPJ_INT32 const* const* pSrc, OPJ_INT32* pDst, +- OPJ_SIZE_T length, OPJ_INT32 adjust) +-{ +- OPJ_SIZE_T i; +- const OPJ_INT32* pSrc0 = pSrc[0]; +- const OPJ_INT32* pSrc1 = pSrc[1]; +- const OPJ_INT32* pSrc2 = pSrc[2]; +- const OPJ_INT32* pSrc3 = pSrc[3]; +- +- for (i = 0; i < length; i++) { +- pDst[4 * i + 0] = pSrc0[i] + adjust; +- pDst[4 * i + 1] = pSrc1[i] + adjust; +- pDst[4 * i + 2] = pSrc2[i] + adjust; +- pDst[4 * i + 3] = pSrc3[i] + adjust; +- } +-} +-const convert_32s_PXCX convert_32s_PXCX_LUT[5] = { +- NULL, +- convert_32s_P1C1, +- convert_32s_P2C2, +- convert_32s_P3C3, +- convert_32s_P4C4 +-}; +- +-/* bit depth conversions */ +-/* used by PNG/TIFF up to 8bpp */ +-static void convert_1u32s_C1R(const OPJ_BYTE* pSrc, OPJ_INT32* pDst, +- OPJ_SIZE_T length) +-{ +- OPJ_SIZE_T i; +- for (i = 0; i < (length & ~(OPJ_SIZE_T)7U); i += 8U) { +- OPJ_UINT32 val = *pSrc++; +- pDst[i + 0] = (OPJ_INT32)(val >> 7); +- pDst[i + 1] = (OPJ_INT32)((val >> 6) & 0x1U); +- pDst[i + 2] = (OPJ_INT32)((val >> 5) & 0x1U); +- pDst[i + 3] = (OPJ_INT32)((val >> 4) & 0x1U); +- pDst[i + 4] = (OPJ_INT32)((val >> 3) & 0x1U); +- pDst[i + 5] = (OPJ_INT32)((val >> 2) & 0x1U); +- pDst[i + 6] = (OPJ_INT32)((val >> 1) & 0x1U); +- pDst[i + 7] = (OPJ_INT32)(val & 0x1U); +- } +- if (length & 7U) { +- OPJ_UINT32 val = *pSrc++; +- length = length & 7U; +- pDst[i + 0] = (OPJ_INT32)(val >> 7); +- +- if (length > 1U) { +- pDst[i + 1] = (OPJ_INT32)((val >> 6) & 0x1U); +- if (length > 2U) { +- pDst[i + 2] = (OPJ_INT32)((val >> 5) & 0x1U); +- if (length > 3U) { +- pDst[i + 3] = (OPJ_INT32)((val >> 4) & 0x1U); +- if (length > 4U) { +- pDst[i + 4] = (OPJ_INT32)((val >> 3) & 0x1U); +- if (length > 5U) { +- pDst[i + 5] = (OPJ_INT32)((val >> 2) & 0x1U); +- if (length > 6U) { +- pDst[i + 6] = (OPJ_INT32)((val >> 1) & 0x1U); +- } +- } +- } +- } +- } +- } +- } +-} +-static void convert_2u32s_C1R(const OPJ_BYTE* pSrc, OPJ_INT32* pDst, +- OPJ_SIZE_T length) +-{ +- OPJ_SIZE_T i; +- for (i = 0; i < (length & ~(OPJ_SIZE_T)3U); i += 4U) { +- OPJ_UINT32 val = *pSrc++; +- pDst[i + 0] = (OPJ_INT32)(val >> 6); +- pDst[i + 1] = (OPJ_INT32)((val >> 4) & 0x3U); +- pDst[i + 2] = (OPJ_INT32)((val >> 2) & 0x3U); +- pDst[i + 3] = (OPJ_INT32)(val & 0x3U); +- } +- if (length & 3U) { +- OPJ_UINT32 val = *pSrc++; +- length = length & 3U; +- pDst[i + 0] = (OPJ_INT32)(val >> 6); +- +- if (length > 1U) { +- pDst[i + 1] = (OPJ_INT32)((val >> 4) & 0x3U); +- if (length > 2U) { +- pDst[i + 2] = (OPJ_INT32)((val >> 2) & 0x3U); ++#define OPJ_TRUE 1 ++#define OPJ_FALSE 0 + +- } +- } +- } +-} +-static void convert_4u32s_C1R(const OPJ_BYTE* pSrc, OPJ_INT32* pDst, +- OPJ_SIZE_T length) +-{ +- OPJ_SIZE_T i; +- for (i = 0; i < (length & ~(OPJ_SIZE_T)1U); i += 2U) { +- OPJ_UINT32 val = *pSrc++; +- pDst[i + 0] = (OPJ_INT32)(val >> 4); +- pDst[i + 1] = (OPJ_INT32)(val & 0xFU); +- } +- if (length & 1U) { +- OPJ_UINT8 val = *pSrc++; +- pDst[i + 0] = (OPJ_INT32)(val >> 4); +- } +-} +-static void convert_6u32s_C1R(const OPJ_BYTE* pSrc, OPJ_INT32* pDst, +- OPJ_SIZE_T length) +-{ +- OPJ_SIZE_T i; +- for (i = 0; i < (length & ~(OPJ_SIZE_T)3U); i += 4U) { +- OPJ_UINT32 val0 = *pSrc++; +- OPJ_UINT32 val1 = *pSrc++; +- OPJ_UINT32 val2 = *pSrc++; +- pDst[i + 0] = (OPJ_INT32)(val0 >> 2); +- pDst[i + 1] = (OPJ_INT32)(((val0 & 0x3U) << 4) | (val1 >> 4)); +- pDst[i + 2] = (OPJ_INT32)(((val1 & 0xFU) << 2) | (val2 >> 6)); +- pDst[i + 3] = (OPJ_INT32)(val2 & 0x3FU); ++const char *opj_version(void) { return "2.3.1"; } + +- } +- if (length & 3U) { +- OPJ_UINT32 val0 = *pSrc++; +- length = length & 3U; +- pDst[i + 0] = (OPJ_INT32)(val0 >> 2); +- +- if (length > 1U) { +- OPJ_UINT32 val1 = *pSrc++; +- pDst[i + 1] = (OPJ_INT32)(((val0 & 0x3U) << 4) | (val1 >> 4)); +- if (length > 2U) { +- OPJ_UINT32 val2 = *pSrc++; +- pDst[i + 2] = (OPJ_INT32)(((val1 & 0xFU) << 2) | (val2 >> 6)); +- } +- } +- } +-} +-static void convert_8u32s_C1R(const OPJ_BYTE* pSrc, OPJ_INT32* pDst, +- OPJ_SIZE_T length) +-{ +- OPJ_SIZE_T i; +- for (i = 0; i < length; i++) { +- pDst[i] = pSrc[i]; +- } +-} +-const convert_XXx32s_C1R convert_XXu32s_C1R_LUT[9] = { +- NULL, +- convert_1u32s_C1R, +- convert_2u32s_C1R, +- NULL, +- convert_4u32s_C1R, +- NULL, +- convert_6u32s_C1R, +- NULL, +- convert_8u32s_C1R +-}; +- +- +-static void convert_32s1u_C1R(const OPJ_INT32* pSrc, OPJ_BYTE* pDst, +- OPJ_SIZE_T length) +-{ +- OPJ_SIZE_T i; +- for (i = 0; i < (length & ~(OPJ_SIZE_T)7U); i += 8U) { +- OPJ_UINT32 src0 = (OPJ_UINT32)pSrc[i + 0]; +- OPJ_UINT32 src1 = (OPJ_UINT32)pSrc[i + 1]; +- OPJ_UINT32 src2 = (OPJ_UINT32)pSrc[i + 2]; +- OPJ_UINT32 src3 = (OPJ_UINT32)pSrc[i + 3]; +- OPJ_UINT32 src4 = (OPJ_UINT32)pSrc[i + 4]; +- OPJ_UINT32 src5 = (OPJ_UINT32)pSrc[i + 5]; +- OPJ_UINT32 src6 = (OPJ_UINT32)pSrc[i + 6]; +- OPJ_UINT32 src7 = (OPJ_UINT32)pSrc[i + 7]; +- +- *pDst++ = (OPJ_BYTE)((src0 << 7) | (src1 << 6) | (src2 << 5) | (src3 << 4) | +- (src4 << 3) | (src5 << 2) | (src6 << 1) | src7); +- } +- +- if (length & 7U) { +- OPJ_UINT32 src0 = (OPJ_UINT32)pSrc[i + 0]; +- OPJ_UINT32 src1 = 0U; +- OPJ_UINT32 src2 = 0U; +- OPJ_UINT32 src3 = 0U; +- OPJ_UINT32 src4 = 0U; +- OPJ_UINT32 src5 = 0U; +- OPJ_UINT32 src6 = 0U; +- length = length & 7U; +- +- if (length > 1U) { +- src1 = (OPJ_UINT32)pSrc[i + 1]; +- if (length > 2U) { +- src2 = (OPJ_UINT32)pSrc[i + 2]; +- if (length > 3U) { +- src3 = (OPJ_UINT32)pSrc[i + 3]; +- if (length > 4U) { +- src4 = (OPJ_UINT32)pSrc[i + 4]; +- if (length > 5U) { +- src5 = (OPJ_UINT32)pSrc[i + 5]; +- if (length > 6U) { +- src6 = (OPJ_UINT32)pSrc[i + 6]; +- } +- } +- } +- } +- } +- } +- *pDst++ = (OPJ_BYTE)((src0 << 7) | (src1 << 6) | (src2 << 5) | (src3 << 4) | +- (src4 << 3) | (src5 << 2) | (src6 << 1)); +- } +-} +- +-static void convert_32s2u_C1R(const OPJ_INT32* pSrc, OPJ_BYTE* pDst, +- OPJ_SIZE_T length) +-{ +- OPJ_SIZE_T i; +- for (i = 0; i < (length & ~(OPJ_SIZE_T)3U); i += 4U) { +- OPJ_UINT32 src0 = (OPJ_UINT32)pSrc[i + 0]; +- OPJ_UINT32 src1 = (OPJ_UINT32)pSrc[i + 1]; +- OPJ_UINT32 src2 = (OPJ_UINT32)pSrc[i + 2]; +- OPJ_UINT32 src3 = (OPJ_UINT32)pSrc[i + 3]; +- +- *pDst++ = (OPJ_BYTE)((src0 << 6) | (src1 << 4) | (src2 << 2) | src3); +- } +- +- if (length & 3U) { +- OPJ_UINT32 src0 = (OPJ_UINT32)pSrc[i + 0]; +- OPJ_UINT32 src1 = 0U; +- OPJ_UINT32 src2 = 0U; +- length = length & 3U; +- +- if (length > 1U) { +- src1 = (OPJ_UINT32)pSrc[i + 1]; +- if (length > 2U) { +- src2 = (OPJ_UINT32)pSrc[i + 2]; +- } +- } +- *pDst++ = (OPJ_BYTE)((src0 << 6) | (src1 << 4) | (src2 << 2)); +- } +-} +- +-static void convert_32s4u_C1R(const OPJ_INT32* pSrc, OPJ_BYTE* pDst, +- OPJ_SIZE_T length) +-{ +- OPJ_SIZE_T i; +- for (i = 0; i < (length & ~(OPJ_SIZE_T)1U); i += 2U) { +- OPJ_UINT32 src0 = (OPJ_UINT32)pSrc[i + 0]; +- OPJ_UINT32 src1 = (OPJ_UINT32)pSrc[i + 1]; +- +- *pDst++ = (OPJ_BYTE)((src0 << 4) | src1); +- } +- +- if (length & 1U) { +- OPJ_UINT32 src0 = (OPJ_UINT32)pSrc[i + 0]; +- *pDst++ = (OPJ_BYTE)((src0 << 4)); +- } +-} +- +-static void convert_32s6u_C1R(const OPJ_INT32* pSrc, OPJ_BYTE* pDst, +- OPJ_SIZE_T length) +-{ +- OPJ_SIZE_T i; +- for (i = 0; i < (length & ~(OPJ_SIZE_T)3U); i += 4U) { +- OPJ_UINT32 src0 = (OPJ_UINT32)pSrc[i + 0]; +- OPJ_UINT32 src1 = (OPJ_UINT32)pSrc[i + 1]; +- OPJ_UINT32 src2 = (OPJ_UINT32)pSrc[i + 2]; +- OPJ_UINT32 src3 = (OPJ_UINT32)pSrc[i + 3]; +- +- *pDst++ = (OPJ_BYTE)((src0 << 2) | (src1 >> 4)); +- *pDst++ = (OPJ_BYTE)(((src1 & 0xFU) << 4) | (src2 >> 2)); +- *pDst++ = (OPJ_BYTE)(((src2 & 0x3U) << 6) | src3); +- } +- +- if (length & 3U) { +- OPJ_UINT32 src0 = (OPJ_UINT32)pSrc[i + 0]; +- OPJ_UINT32 src1 = 0U; +- OPJ_UINT32 src2 = 0U; +- length = length & 3U; +- +- if (length > 1U) { +- src1 = (OPJ_UINT32)pSrc[i + 1]; +- if (length > 2U) { +- src2 = (OPJ_UINT32)pSrc[i + 2]; +- } +- } +- *pDst++ = (OPJ_BYTE)((src0 << 2) | (src1 >> 4)); +- if (length > 1U) { +- *pDst++ = (OPJ_BYTE)(((src1 & 0xFU) << 4) | (src2 >> 2)); +- if (length > 2U) { +- *pDst++ = (OPJ_BYTE)(((src2 & 0x3U) << 6)); +- } +- } +- } +-} +-static void convert_32s8u_C1R(const OPJ_INT32* pSrc, OPJ_BYTE* pDst, +- OPJ_SIZE_T length) +-{ +- OPJ_SIZE_T i; +- for (i = 0; i < length; ++i) { +- pDst[i] = (OPJ_BYTE)pSrc[i]; +- } +-} +-const convert_32sXXx_C1R convert_32sXXu_C1R_LUT[9] = { +- NULL, +- convert_32s1u_C1R, +- convert_32s2u_C1R, +- NULL, +- convert_32s4u_C1R, +- NULL, +- convert_32s6u_C1R, +- NULL, +- convert_32s8u_C1R +-}; +- +-/* -->> -->> -->> -->> +- +- TGA IMAGE FORMAT +- +- <<-- <<-- <<-- <<-- */ +- +-#ifdef INFORMATION_ONLY +-/* TGA header definition. */ +-struct tga_header { +- unsigned char id_length; /* Image id field length */ +- unsigned char colour_map_type; /* Colour map type */ +- unsigned char image_type; /* Image type */ +- /* +- ** Colour map specification +- */ +- unsigned short colour_map_index; /* First entry index */ +- unsigned short colour_map_length; /* Colour map length */ +- unsigned char colour_map_entry_size; /* Colour map entry size */ +- /* +- ** Image specification +- */ +- unsigned short x_origin; /* x origin of image */ +- unsigned short y_origin; /* u origin of image */ +- unsigned short image_width; /* Image width */ +- unsigned short image_height; /* Image height */ +- unsigned char pixel_depth; /* Pixel depth */ +- unsigned char image_desc; /* Image descriptor */ +-}; +-#endif /* INFORMATION_ONLY */ +- +-/* Returns a ushort from a little-endian serialized value */ +-static unsigned short get_tga_ushort(const unsigned char *data) +-{ +- return (unsigned short)(data[0] | (data[1] << 8)); +-} +- +-#define TGA_HEADER_SIZE 18 +- +-static int tga_readheader(FILE *fp, unsigned int *bits_per_pixel, +- unsigned int *width, unsigned int *height, int *flip_image) +-{ +- int palette_size; +- unsigned char tga[TGA_HEADER_SIZE]; +- unsigned char id_len, /*cmap_type,*/ image_type; +- unsigned char pixel_depth, image_desc; +- unsigned short /*cmap_index,*/ cmap_len, cmap_entry_size; +- unsigned short /*x_origin, y_origin,*/ image_w, image_h; +- +- if (!bits_per_pixel || !width || !height || !flip_image) { +- return 0; +- } +- +- if (fread(tga, TGA_HEADER_SIZE, 1, fp) != 1) { +- fprintf(stderr, +- "\nError: fread return a number of element different from the expected.\n"); +- return 0 ; +- } +- id_len = tga[0]; +- /*cmap_type = tga[1];*/ +- image_type = tga[2]; +- /*cmap_index = get_tga_ushort(&tga[3]);*/ +- cmap_len = get_tga_ushort(&tga[5]); +- cmap_entry_size = tga[7]; +- +- +-#if 0 +- x_origin = get_tga_ushort(&tga[8]); +- y_origin = get_tga_ushort(&tga[10]); +-#endif +- image_w = get_tga_ushort(&tga[12]); +- image_h = get_tga_ushort(&tga[14]); +- pixel_depth = tga[16]; +- image_desc = tga[17]; +- +- *bits_per_pixel = (unsigned int)pixel_depth; +- *width = (unsigned int)image_w; +- *height = (unsigned int)image_h; +- +- /* Ignore tga identifier, if present ... */ +- if (id_len) { +- unsigned char *id = (unsigned char *) malloc(id_len); +- if (id == 0) { +- fprintf(stderr, "tga_readheader: memory out\n"); +- return 0; +- } +- if (!fread(id, id_len, 1, fp)) { +- fprintf(stderr, +- "\nError: fread return a number of element different from the expected.\n"); +- free(id); +- return 0 ; +- } +- free(id); +- } +- +- /* Test for compressed formats ... not yet supported ... +- // Note :- 9 - RLE encoded palettized. +- // 10 - RLE encoded RGB. */ +- if (image_type > 8) { +- fprintf(stderr, "Sorry, compressed tga files are not currently supported.\n"); +- return 0 ; +- } +- +- *flip_image = !(image_desc & 32); +- +- /* Palettized formats are not yet supported, skip over the palette, if present ... */ +- palette_size = cmap_len * (cmap_entry_size / 8); +- +- if (palette_size > 0) { +- fprintf(stderr, "File contains a palette - not yet supported."); +- fseek(fp, palette_size, SEEK_CUR); +- } ++static int are_comps_similar(opj_image_t *image) { ++ unsigned int i; ++ for (i = 1; i < image->numcomps; i++) { ++ if (image->comps[0].dx != image->comps[i].dx || ++ image->comps[0].dy != image->comps[i].dy || ++ (i <= 2 && (image->comps[0].prec != image->comps[i].prec || ++ image->comps[0].sgnd != image->comps[i].sgnd))) { ++ return OPJ_FALSE; ++ } ++ } ++ return OPJ_TRUE; ++} ++ ++int imagetopnm(opj_image_t *image, const char *outfile, int force_split) { ++ int *red, *green, *blue, *alpha; ++ int wr, hr, max; ++ int i; ++ unsigned int compno, ncomp; ++ int adjustR, adjustG, adjustB, adjustA; ++ int fails, two, want_gray, has_alpha, triple; ++ int prec, v; ++ FILE *fdest = NULL; ++ const char *tmp = outfile; ++ char *destname; ++ ++ alpha = NULL; ++ ++ if ((prec = (int)image->comps[0].prec) > 16) { ++ fprintf(stderr, ++ "%s:%d:imagetopnm\n\tprecision %d is larger than 16" ++ "\n\t: refused.\n", ++ __FILE__, __LINE__, prec); + return 1; +-} +- +-#ifdef OPJ_BIG_ENDIAN +- +-static INLINE OPJ_UINT16 swap16(OPJ_UINT16 x) +-{ +- return (OPJ_UINT16)(((x & 0x00ffU) << 8) | ((x & 0xff00U) >> 8)); +-} +- +-#endif ++ } + +-static int tga_writeheader(FILE *fp, int bits_per_pixel, int width, int height, +- OPJ_BOOL flip_image) +-{ +- OPJ_UINT16 image_w, image_h, us0; +- unsigned char uc0, image_type; +- unsigned char pixel_depth, image_desc; +- +- if (!bits_per_pixel || !width || !height) { +- return 0; +- } +- +- pixel_depth = 0; +- +- if (bits_per_pixel < 256) { +- pixel_depth = (unsigned char)bits_per_pixel; +- } else { +- fprintf(stderr, "ERROR: Wrong bits per pixel inside tga_header"); +- return 0; +- } +- uc0 = 0; +- +- if (fwrite(&uc0, 1, 1, fp) != 1) { +- goto fails; /* id_length */ +- } +- if (fwrite(&uc0, 1, 1, fp) != 1) { +- goto fails; /* colour_map_type */ +- } +- +- image_type = 2; /* Uncompressed. */ +- if (fwrite(&image_type, 1, 1, fp) != 1) { +- goto fails; +- } +- +- us0 = 0; +- if (fwrite(&us0, 2, 1, fp) != 1) { +- goto fails; /* colour_map_index */ +- } +- if (fwrite(&us0, 2, 1, fp) != 1) { +- goto fails; /* colour_map_length */ +- } +- if (fwrite(&uc0, 1, 1, fp) != 1) { +- goto fails; /* colour_map_entry_size */ +- } +- +- if (fwrite(&us0, 2, 1, fp) != 1) { +- goto fails; /* x_origin */ +- } +- if (fwrite(&us0, 2, 1, fp) != 1) { +- goto fails; /* y_origin */ +- } +- +- image_w = (unsigned short)width; +- image_h = (unsigned short) height; +- +-#ifndef OPJ_BIG_ENDIAN +- if (fwrite(&image_w, 2, 1, fp) != 1) { +- goto fails; +- } +- if (fwrite(&image_h, 2, 1, fp) != 1) { +- goto fails; +- } +-#else +- image_w = swap16(image_w); +- image_h = swap16(image_h); +- if (fwrite(&image_w, 2, 1, fp) != 1) { +- goto fails; +- } +- if (fwrite(&image_h, 2, 1, fp) != 1) { +- goto fails; +- } +-#endif +- +- if (fwrite(&pixel_depth, 1, 1, fp) != 1) { +- goto fails; +- } +- +- image_desc = 8; /* 8 bits per component. */ +- +- if (flip_image) { +- image_desc |= 32; +- } +- if (fwrite(&image_desc, 1, 1, fp) != 1) { +- goto fails; +- } +- +- return 1; +- +-fails: +- fputs("\nwrite_tgaheader: write ERROR\n", stderr); +- return 0; +-} +- +-opj_image_t* tgatoimage(const char *filename, opj_cparameters_t *parameters) +-{ +- FILE *f; +- opj_image_t *image; +- unsigned int image_width, image_height, pixel_bit_depth; +- unsigned int x, y; +- int flip_image = 0; +- opj_image_cmptparm_t cmptparm[4]; /* maximum 4 components */ +- int numcomps; +- OPJ_COLOR_SPACE color_space; +- OPJ_BOOL mono ; +- OPJ_BOOL save_alpha; +- int subsampling_dx, subsampling_dy; +- int i; +- +- f = fopen(filename, "rb"); +- if (!f) { +- fprintf(stderr, "Failed to open %s for reading !!\n", filename); +- return 0; +- } +- +- if (!tga_readheader(f, &pixel_bit_depth, &image_width, &image_height, +- &flip_image)) { +- fclose(f); +- return NULL; +- } +- +- /* We currently only support 24 & 32 bit tga's ... */ +- if (!((pixel_bit_depth == 24) || (pixel_bit_depth == 32))) { +- fclose(f); +- return NULL; +- } +- +- /* initialize image components */ +- memset(&cmptparm[0], 0, 4 * sizeof(opj_image_cmptparm_t)); +- +- mono = (pixel_bit_depth == 8) || +- (pixel_bit_depth == 16); /* Mono with & without alpha. */ +- save_alpha = (pixel_bit_depth == 16) || +- (pixel_bit_depth == 32); /* Mono with alpha, or RGB with alpha */ +- +- if (mono) { +- color_space = OPJ_CLRSPC_GRAY; +- numcomps = save_alpha ? 2 : 1; +- } else { +- numcomps = save_alpha ? 4 : 3; +- color_space = OPJ_CLRSPC_SRGB; +- } +- +- /* If the declared file size is > 10 MB, check that the file is big */ +- /* enough to avoid excessive memory allocations */ +- if (image_height != 0 && +- image_width > 10000000U / image_height / (OPJ_UINT32)numcomps) { +- char ch; +- OPJ_UINT64 expected_file_size = +- (OPJ_UINT64)image_width * image_height * (OPJ_UINT32)numcomps; +- long curpos = ftell(f); +- if (expected_file_size > (OPJ_UINT64)INT_MAX) { +- expected_file_size = (OPJ_UINT64)INT_MAX; +- } +- fseek(f, (long)expected_file_size - 1, SEEK_SET); +- if (fread(&ch, 1, 1, f) != 1) { +- fclose(f); +- return NULL; +- } +- fseek(f, curpos, SEEK_SET); +- } +- +- subsampling_dx = parameters->subsampling_dx; +- subsampling_dy = parameters->subsampling_dy; +- +- for (i = 0; i < numcomps; i++) { +- cmptparm[i].prec = 8; +- cmptparm[i].bpp = 8; +- cmptparm[i].sgnd = 0; +- cmptparm[i].dx = (OPJ_UINT32)subsampling_dx; +- cmptparm[i].dy = (OPJ_UINT32)subsampling_dy; +- cmptparm[i].w = image_width; +- cmptparm[i].h = image_height; +- } +- +- /* create the image */ +- image = opj_image_create((OPJ_UINT32)numcomps, &cmptparm[0], color_space); +- +- if (!image) { +- fclose(f); +- return NULL; +- } +- +- +- /* set image offset and reference grid */ +- image->x0 = (OPJ_UINT32)parameters->image_offset_x0; +- image->y0 = (OPJ_UINT32)parameters->image_offset_y0; +- image->x1 = !image->x0 ? (OPJ_UINT32)(image_width - 1) * +- (OPJ_UINT32)subsampling_dx + 1 : image->x0 + (OPJ_UINT32)(image_width - 1) * +- (OPJ_UINT32)subsampling_dx + 1; +- image->y1 = !image->y0 ? (OPJ_UINT32)(image_height - 1) * +- (OPJ_UINT32)subsampling_dy + 1 : image->y0 + (OPJ_UINT32)(image_height - 1) * +- (OPJ_UINT32)subsampling_dy + 1; +- +- /* set image data */ +- for (y = 0; y < image_height; y++) { +- int index; +- +- if (flip_image) { +- index = (int)((image_height - y - 1) * image_width); +- } else { +- index = (int)(y * image_width); +- } +- +- if (numcomps == 3) { +- for (x = 0; x < image_width; x++) { +- unsigned char r, g, b; +- +- if (!fread(&b, 1, 1, f)) { +- fprintf(stderr, +- "\nError: fread return a number of element different from the expected.\n"); +- opj_image_destroy(image); +- fclose(f); +- return NULL; +- } +- if (!fread(&g, 1, 1, f)) { +- fprintf(stderr, +- "\nError: fread return a number of element different from the expected.\n"); +- opj_image_destroy(image); +- fclose(f); +- return NULL; +- } +- if (!fread(&r, 1, 1, f)) { +- fprintf(stderr, +- "\nError: fread return a number of element different from the expected.\n"); +- opj_image_destroy(image); +- fclose(f); +- return NULL; +- } +- +- image->comps[0].data[index] = r; +- image->comps[1].data[index] = g; +- image->comps[2].data[index] = b; +- index++; +- } +- } else if (numcomps == 4) { +- for (x = 0; x < image_width; x++) { +- unsigned char r, g, b, a; +- if (!fread(&b, 1, 1, f)) { +- fprintf(stderr, +- "\nError: fread return a number of element different from the expected.\n"); +- opj_image_destroy(image); +- fclose(f); +- return NULL; +- } +- if (!fread(&g, 1, 1, f)) { +- fprintf(stderr, +- "\nError: fread return a number of element different from the expected.\n"); +- opj_image_destroy(image); +- fclose(f); +- return NULL; +- } +- if (!fread(&r, 1, 1, f)) { +- fprintf(stderr, +- "\nError: fread return a number of element different from the expected.\n"); +- opj_image_destroy(image); +- fclose(f); +- return NULL; +- } +- if (!fread(&a, 1, 1, f)) { +- fprintf(stderr, +- "\nError: fread return a number of element different from the expected.\n"); +- opj_image_destroy(image); +- fclose(f); +- return NULL; +- } +- +- image->comps[0].data[index] = r; +- image->comps[1].data[index] = g; +- image->comps[2].data[index] = b; +- image->comps[3].data[index] = a; +- index++; +- } +- } else { +- fprintf(stderr, "Currently unsupported bit depth : %s\n", filename); +- } +- } +- fclose(f); +- return image; +-} +- +-int imagetotga(opj_image_t * image, const char *outfile) +-{ +- int width, height, bpp, x, y; +- OPJ_BOOL write_alpha; +- unsigned int i; +- int adjustR, adjustG = 0, adjustB = 0, fails; +- unsigned int alpha_channel; +- float r, g, b, a; +- unsigned char value; +- float scale; +- FILE *fdest; +- size_t res; +- fails = 1; ++ two = has_alpha = 0; ++ fails = 1; ++ ncomp = image->numcomps; ++ ++ while (*tmp) { ++ ++tmp; ++ } ++ tmp -= 2; ++ want_gray = (*tmp == 'g' || *tmp == 'G'); ++ ncomp = image->numcomps; ++ ++ if (want_gray) { ++ ncomp = 1; ++ } + ++ if ((force_split == 0) && ncomp >= 2 && are_comps_similar(image)) { + fdest = fopen(outfile, "wb"); ++ + if (!fdest) { +- fprintf(stderr, "ERROR -> failed to open %s for writing\n", outfile); +- return 1; ++ fprintf(stderr, "ERROR -> failed to open %s for writing\n", outfile); ++ return fails; + } +- +- for (i = 0; i < image->numcomps - 1; i++) { +- if ((image->comps[0].dx != image->comps[i + 1].dx) +- || (image->comps[0].dy != image->comps[i + 1].dy) +- || (image->comps[0].prec != image->comps[i + 1].prec) +- || (image->comps[0].sgnd != image->comps[i + 1].sgnd)) { +- fclose(fdest); +- fprintf(stderr, +- "Unable to create a tga file with such J2K image charateristics.\n"); +- return 1; +- } ++ two = (prec > 8); ++ triple = (ncomp > 2); ++ wr = (int)image->comps[0].w; ++ hr = (int)image->comps[0].h; ++ max = (1 << prec) - 1; ++ has_alpha = (ncomp == 4 || ncomp == 2); ++ ++ red = image->comps[0].data; ++ if (red == NULL) { ++ fprintf(stderr, "imagetopnm: planes[%d] == NULL.\n", 0); ++ fprintf(stderr, "\tAborting\n"); ++ fclose(fdest); ++ return fails; ++ } ++ ++ if (triple) { ++ green = image->comps[1].data; ++ blue = image->comps[2].data; ++ for (i = 1; i <= 2; i++) { ++ if (image->comps[i].data == NULL) { ++ fprintf(stderr, "imagetopnm: planes[%d] == NULL.\n", i); ++ fprintf(stderr, "\tAborting\n"); ++ fclose(fdest); ++ return fails; ++ } ++ } ++ } else { ++ green = blue = NULL; ++ } ++ ++ if (has_alpha) { ++ const char *tt = (triple ? "RGB_ALPHA" : "GRAYSCALE_ALPHA"); ++ ++ fprintf(fdest, ++ "P7\n# OpenJPEG-%s\nWIDTH %d\nHEIGHT %d\nDEPTH %u\n" ++ "MAXVAL %d\nTUPLTYPE %s\nENDHDR\n", ++ opj_version(), wr, hr, ncomp, max, tt); ++ alpha = image->comps[ncomp - 1].data; ++ adjustA = (image->comps[ncomp - 1].sgnd ++ ? 1 << (image->comps[ncomp - 1].prec - 1) ++ : 0); ++ } else { ++ fprintf(fdest, "P6\n# OpenJPEG-%s\n%d %d\n%d\n", opj_version(), wr, hr, ++ max); ++ adjustA = 0; + } +- +- width = (int)image->comps[0].w; +- height = (int)image->comps[0].h; +- +- /* Mono with alpha, or RGB with alpha. */ +- write_alpha = (image->numcomps == 2) || (image->numcomps == 4); +- +- /* Write TGA header */ +- bpp = write_alpha ? 32 : 24; +- +- if (!tga_writeheader(fdest, bpp, width, height, OPJ_TRUE)) { +- goto fin; +- } +- +- alpha_channel = image->numcomps - 1; +- +- scale = 255.0f / (float)((1 << image->comps[0].prec) - 1); +- + adjustR = (image->comps[0].sgnd ? 1 << (image->comps[0].prec - 1) : 0); +- if (image->numcomps >= 3) { +- adjustG = (image->comps[1].sgnd ? 1 << (image->comps[1].prec - 1) : 0); +- adjustB = (image->comps[2].sgnd ? 1 << (image->comps[2].prec - 1) : 0); +- } +- +- for (y = 0; y < height; y++) { +- unsigned int index = (unsigned int)(y * width); +- +- for (x = 0; x < width; x++, index++) { +- r = (float)(image->comps[0].data[index] + adjustR); + +- if (image->numcomps > 2) { +- g = (float)(image->comps[1].data[index] + adjustG); +- b = (float)(image->comps[2].data[index] + adjustB); +- } else { +- /* Greyscale ... */ +- g = r; +- b = r; +- } +- +- /* TGA format writes BGR ... */ +- if (b > 255.) { +- b = 255.; +- } else if (b < 0.) { +- b = 0.; +- } +- value = (unsigned char)(b * scale); +- res = fwrite(&value, 1, 1, fdest); +- +- if (res < 1) { +- fprintf(stderr, "failed to write 1 byte for %s\n", outfile); +- goto fin; +- } +- if (g > 255.) { +- g = 255.; +- } else if (g < 0.) { +- g = 0.; +- } +- value = (unsigned char)(g * scale); +- res = fwrite(&value, 1, 1, fdest); +- +- if (res < 1) { +- fprintf(stderr, "failed to write 1 byte for %s\n", outfile); +- goto fin; +- } +- if (r > 255.) { +- r = 255.; +- } else if (r < 0.) { +- r = 0.; +- } +- value = (unsigned char)(r * scale); +- res = fwrite(&value, 1, 1, fdest); +- +- if (res < 1) { +- fprintf(stderr, "failed to write 1 byte for %s\n", outfile); +- goto fin; +- } +- +- if (write_alpha) { +- a = (float)(image->comps[alpha_channel].data[index]); +- if (a > 255.) { +- a = 255.; +- } else if (a < 0.) { +- a = 0.; +- } +- value = (unsigned char)(a * scale); +- res = fwrite(&value, 1, 1, fdest); +- +- if (res < 1) { +- fprintf(stderr, "failed to write 1 byte for %s\n", outfile); +- goto fin; +- } +- } +- } +- } +- fails = 0; +-fin: +- fclose(fdest); +- +- return fails; +-} +- +-/* -->> -->> -->> -->> +- +-PGX IMAGE FORMAT +- +-<<-- <<-- <<-- <<-- */ +- +- +-static unsigned char readuchar(FILE * f) +-{ +- unsigned char c1; +- if (!fread(&c1, 1, 1, f)) { +- fprintf(stderr, +- "\nError: fread return a number of element different from the expected.\n"); +- return 0; +- } +- return c1; +-} +- +-static unsigned short readushort(FILE * f, int bigendian) +-{ +- unsigned char c1, c2; +- if (!fread(&c1, 1, 1, f)) { +- fprintf(stderr, +- "\nError: fread return a number of element different from the expected.\n"); +- return 0; +- } +- if (!fread(&c2, 1, 1, f)) { +- fprintf(stderr, +- "\nError: fread return a number of element different from the expected.\n"); +- return 0; +- } +- if (bigendian) { +- return (unsigned short)((c1 << 8) + c2); +- } else { +- return (unsigned short)((c2 << 8) + c1); +- } +-} +- +-static unsigned int readuint(FILE * f, int bigendian) +-{ +- unsigned char c1, c2, c3, c4; +- if (!fread(&c1, 1, 1, f)) { +- fprintf(stderr, +- "\nError: fread return a number of element different from the expected.\n"); +- return 0; +- } +- if (!fread(&c2, 1, 1, f)) { +- fprintf(stderr, +- "\nError: fread return a number of element different from the expected.\n"); +- return 0; +- } +- if (!fread(&c3, 1, 1, f)) { +- fprintf(stderr, +- "\nError: fread return a number of element different from the expected.\n"); +- return 0; +- } +- if (!fread(&c4, 1, 1, f)) { +- fprintf(stderr, +- "\nError: fread return a number of element different from the expected.\n"); +- return 0; +- } +- if (bigendian) { +- return (unsigned int)(c1 << 24) + (unsigned int)(c2 << 16) + (unsigned int)( +- c3 << 8) + c4; ++ if (triple) { ++ adjustG = (image->comps[1].sgnd ? 1 << (image->comps[1].prec - 1) : 0); ++ adjustB = (image->comps[2].sgnd ? 1 << (image->comps[2].prec - 1) : 0); + } else { +- return (unsigned int)(c4 << 24) + (unsigned int)(c3 << 16) + (unsigned int)( +- c2 << 8) + c1; +- } +-} +- +-opj_image_t* pgxtoimage(const char *filename, opj_cparameters_t *parameters) +-{ +- FILE *f = NULL; +- int w, h, prec; +- int i, numcomps, max; +- OPJ_COLOR_SPACE color_space; +- opj_image_cmptparm_t cmptparm; /* maximum of 1 component */ +- opj_image_t * image = NULL; +- int adjustS, ushift, dshift, force8; +- OPJ_UINT64 expected_file_size; +- +- char endian1, endian2, sign; +- char signtmp[32]; +- +- char temp[32]; +- int bigendian; +- opj_image_comp_t *comp = NULL; +- +- numcomps = 1; +- color_space = OPJ_CLRSPC_GRAY; +- +- memset(&cmptparm, 0, sizeof(opj_image_cmptparm_t)); +- +- max = 0; +- +- f = fopen(filename, "rb"); +- if (!f) { +- fprintf(stderr, "Failed to open %s for reading !\n", filename); +- return NULL; ++ adjustG = adjustB = 0; + } + +- fseek(f, 0, SEEK_SET); +- if (fscanf(f, "PG%31[ \t]%c%c%31[ \t+-]%d%31[ \t]%d%31[ \t]%d", temp, &endian1, +- &endian2, signtmp, &prec, temp, &w, temp, &h) != 9) { +- fclose(f); +- fprintf(stderr, +- "ERROR: Failed to read the right number of element from the fscanf() function!\n"); +- return NULL; +- } +- +- i = 0; +- sign = '+'; +- while (signtmp[i] != '\0') { +- if (signtmp[i] == '-') { +- sign = '-'; ++ for (i = 0; i < wr * hr; ++i) { ++ if (two) { ++ v = *red + adjustR; ++ ++red; ++ if (v > 65535) { ++ v = 65535; ++ } else if (v < 0) { ++ v = 0; + } +- i++; +- } + +- fgetc(f); +- if (endian1 == 'M' && endian2 == 'L') { +- bigendian = 1; +- } else if (endian2 == 'M' && endian1 == 'L') { +- bigendian = 0; +- } else { +- fclose(f); +- fprintf(stderr, "Bad pgx header, please check input file\n"); +- return NULL; +- } +- +- if (w < 1 || h < 1 || prec < 1 || prec > 31) { +- fclose(f); +- fprintf(stderr, "Bad pgx header, please check input file\n"); +- return NULL; +- } +- +- expected_file_size = +- (OPJ_UINT64)w * (OPJ_UINT64)h * (prec > 16 ? 4 : prec > 8 ? 2 : 1); +- if (expected_file_size > 10000000U) { +- char ch; +- long curpos = ftell(f); +- if (expected_file_size > (OPJ_UINT64)INT_MAX) { +- expected_file_size = (OPJ_UINT64)INT_MAX; +- } +- fseek(f, (long)expected_file_size - 1, SEEK_SET); +- if (fread(&ch, 1, 1, f) != 1) { +- fprintf(stderr, "File too short\n"); +- fclose(f); +- return NULL; +- } +- fseek(f, curpos, SEEK_SET); +- } +- +- /* initialize image component */ +- +- cmptparm.x0 = (OPJ_UINT32)parameters->image_offset_x0; +- cmptparm.y0 = (OPJ_UINT32)parameters->image_offset_y0; +- cmptparm.w = !cmptparm.x0 ? (OPJ_UINT32)((w - 1) * parameters->subsampling_dx + +- 1) : cmptparm.x0 + (OPJ_UINT32)(w - 1) * (OPJ_UINT32)parameters->subsampling_dx +- + 1; +- cmptparm.h = !cmptparm.y0 ? (OPJ_UINT32)((h - 1) * parameters->subsampling_dy + +- 1) : cmptparm.y0 + (OPJ_UINT32)(h - 1) * (OPJ_UINT32)parameters->subsampling_dy +- + 1; +- +- if (sign == '-') { +- cmptparm.sgnd = 1; +- } else { +- cmptparm.sgnd = 0; +- } +- if (prec < 8) { +- force8 = 1; +- ushift = 8 - prec; +- dshift = prec - ushift; +- if (cmptparm.sgnd) { +- adjustS = (1 << (prec - 1)); +- } else { +- adjustS = 0; +- } +- cmptparm.sgnd = 0; +- prec = 8; +- } else { +- ushift = dshift = force8 = adjustS = 0; +- } +- +- cmptparm.prec = (OPJ_UINT32)prec; +- cmptparm.bpp = (OPJ_UINT32)prec; +- cmptparm.dx = (OPJ_UINT32)parameters->subsampling_dx; +- cmptparm.dy = (OPJ_UINT32)parameters->subsampling_dy; +- +- /* create the image */ +- image = opj_image_create((OPJ_UINT32)numcomps, &cmptparm, color_space); +- if (!image) { +- fclose(f); +- return NULL; +- } +- /* set image offset and reference grid */ +- image->x0 = cmptparm.x0; +- image->y0 = cmptparm.x0; +- image->x1 = cmptparm.w; +- image->y1 = cmptparm.h; +- +- /* set image data */ +- +- comp = &image->comps[0]; +- +- for (i = 0; i < w * h; i++) { +- int v; +- if (force8) { +- v = readuchar(f) + adjustS; +- v = (v << ushift) + (v >> dshift); +- comp->data[i] = (unsigned char)v; +- +- if (v > max) { +- max = v; +- } +- +- continue; +- } +- if (comp->prec == 8) { +- if (!comp->sgnd) { +- v = readuchar(f); +- } else { +- v = (char) readuchar(f); +- } +- } else if (comp->prec <= 16) { +- if (!comp->sgnd) { +- v = readushort(f, bigendian); +- } else { +- v = (short) readushort(f, bigendian); +- } +- } else { +- if (!comp->sgnd) { +- v = (int)readuint(f, bigendian); +- } else { +- v = (int) readuint(f, bigendian); +- } +- } +- if (v > max) { +- max = v; +- } +- comp->data[i] = v; +- } +- fclose(f); +- comp->bpp = (OPJ_UINT32)int_floorlog2(max) + 1; +- +- return image; +-} +- +-#define CLAMP(x,a,b) ((x) < (a) ? (a) : ((x) > (b) ? (b) : (x))) +- +-static INLINE int clamp(const int value, const int prec, const int sgnd) +-{ +- if (sgnd) { +- if (prec <= 8) { +- return CLAMP(value, -128, 127); +- } else if (prec <= 16) { +- return CLAMP(value, -32768, 32767); +- } else { +- return CLAMP(value, -2147483647 - 1, 2147483647); +- } +- } else { +- if (prec <= 8) { +- return CLAMP(value, 0, 255); +- } else if (prec <= 16) { +- return CLAMP(value, 0, 65535); +- } else { +- return value; /*CLAMP(value,0,4294967295);*/ +- } +- } +-} +- +-int imagetopgx(opj_image_t * image, const char *outfile) +-{ +- int w, h; +- int i, j, fails = 1; +- unsigned int compno; +- FILE *fdest = NULL; +- +- for (compno = 0; compno < image->numcomps; compno++) { +- opj_image_comp_t *comp = &image->comps[compno]; +- char bname[256]; /* buffer for name */ +- char *name = bname; /* pointer */ +- int nbytes = 0; +- size_t res; +- const size_t olen = strlen(outfile); +- const size_t dotpos = olen - 4; +- const size_t total = dotpos + 1 + 1 + 4; /* '-' + '[1-3]' + '.pgx' */ +- +- if (outfile[dotpos] != '.') { +- /* `pgx` was recognized but there is no dot at expected position */ +- fprintf(stderr, "ERROR -> Impossible happen."); +- goto fin; +- } +- if (total > 256) { +- name = (char*)malloc(total + 1); +- if (name == NULL) { +- fprintf(stderr, "imagetopgx: memory out\n"); +- goto fin; +- } +- } +- strncpy(name, outfile, dotpos); +- sprintf(name + dotpos, "_%u.pgx", compno); +- fdest = fopen(name, "wb"); +- /* don't need name anymore */ +- +- if (!fdest) { +- +- fprintf(stderr, "ERROR -> failed to open %s for writing\n", name); +- if (total > 256) { +- free(name); +- } +- goto fin; +- } +- +- w = (int)image->comps[compno].w; +- h = (int)image->comps[compno].h; +- +- fprintf(fdest, "PG ML %c %d %d %d\n", comp->sgnd ? '-' : '+', comp->prec, +- w, h); +- +- if (comp->prec <= 8) { +- nbytes = 1; +- } else if (comp->prec <= 16) { +- nbytes = 2; +- } else { +- nbytes = 4; +- } +- +- if (nbytes == 1) { +- unsigned char* line_buffer = malloc((size_t)w); +- if (line_buffer == NULL) { +- fprintf(stderr, "Out of memory"); +- if (total > 256) { +- free(name); +- } +- goto fin; +- } +- for (j = 0; j < h; j++) { +- if (comp->prec == 8 && comp->sgnd == 0) { +- for (i = 0; i < w; i++) { +- line_buffer[i] = (unsigned char)CLAMP(image->comps[compno].data[j * w + i], 0, +- 255); +- } +- } else { +- for (i = 0; i < w; i++) { +- line_buffer[i] = (unsigned char) +- clamp(image->comps[compno].data[j * w + i], +- (int)comp->prec, (int)comp->sgnd); +- } +- } +- res = fwrite(line_buffer, 1, (size_t)w, fdest); +- if (res != (size_t)w) { +- fprintf(stderr, "failed to write %d bytes for %s\n", w, name); +- if (total > 256) { +- free(name); +- } +- free(line_buffer); +- goto fin; +- } +- } +- free(line_buffer); +- } else { +- +- for (i = 0; i < w * h; i++) { +- /* FIXME: clamp func is being called within a loop */ +- const int val = clamp(image->comps[compno].data[i], +- (int)comp->prec, (int)comp->sgnd); +- +- for (j = nbytes - 1; j >= 0; j--) { +- int v = (int)(val >> (j * 8)); +- unsigned char byte = (unsigned char)v; +- res = fwrite(&byte, 1, 1, fdest); +- +- if (res < 1) { +- fprintf(stderr, "failed to write 1 byte for %s\n", name); +- if (total > 256) { +- free(name); +- } +- goto fin; +- } +- } +- } +- } +- +- if (total > 256) { +- free(name); +- } +- fclose(fdest); +- fdest = NULL; +- } +- fails = 0; +-fin: +- if (fdest) { +- fclose(fdest); +- } +- +- return fails; +-} +- +-/* -->> -->> -->> -->> +- +-PNM IMAGE FORMAT +- +-<<-- <<-- <<-- <<-- */ +- +-struct pnm_header { +- int width, height, maxval, depth, format; +- char rgb, rgba, gray, graya, bw; +- char ok; +-}; +- +-static char *skip_white(char *s) +-{ +- if (s != NULL) { +- while (*s) { +- if (*s == '\n' || *s == '\r') { +- return NULL; +- } +- if (isspace(*s)) { +- ++s; +- continue; +- } +- return s; +- } +- } +- return NULL; +-} +- +-static char *skip_int(char *start, int *out_n) +-{ +- char *s; +- char c; +- +- *out_n = 0; +- +- s = skip_white(start); +- if (s == NULL) { +- return NULL; +- } +- start = s; +- +- while (*s) { +- if (!isdigit(*s)) { +- break; +- } +- ++s; +- } +- c = *s; +- *s = 0; +- *out_n = atoi(start); +- *s = c; +- return s; +-} +- +-static char *skip_idf(char *start, char out_idf[256]) +-{ +- char *s; +- char c; +- +- s = skip_white(start); +- if (s == NULL) { +- return NULL; +- } +- start = s; +- +- while (*s) { +- if (isalpha(*s) || *s == '_') { +- ++s; +- continue; +- } +- break; +- } +- c = *s; +- *s = 0; +- strncpy(out_idf, start, 255); +- *s = c; +- return s; +-} +- +-static void read_pnm_header(FILE *reader, struct pnm_header *ph) +-{ +- int format, end, ttype; +- char idf[256], type[256]; +- char line[256]; +- +- if (fgets(line, 250, reader) == NULL) { +- fprintf(stderr, "\nWARNING: fgets return a NULL value"); +- return; +- } +- +- if (line[0] != 'P') { +- fprintf(stderr, "read_pnm_header:PNM:magic P missing\n"); +- return; +- } +- format = atoi(line + 1); +- if (format < 1 || format > 7) { +- fprintf(stderr, "read_pnm_header:magic format %d invalid\n", format); +- return; +- } +- ph->format = format; +- ttype = end = 0; +- +- while (fgets(line, 250, reader)) { +- char *s; +- int allow_null = 0; +- +- if (*line == '#') { +- continue; +- } +- +- s = line; +- +- if (format == 7) { +- s = skip_idf(s, idf); +- +- if (s == NULL || *s == 0) { +- return; +- } +- +- if (strcmp(idf, "ENDHDR") == 0) { +- end = 1; +- break; +- } +- if (strcmp(idf, "WIDTH") == 0) { +- s = skip_int(s, &ph->width); +- if (s == NULL || *s == 0) { +- return; +- } +- +- continue; +- } +- if (strcmp(idf, "HEIGHT") == 0) { +- s = skip_int(s, &ph->height); +- if (s == NULL || *s == 0) { +- return; +- } +- +- continue; +- } +- if (strcmp(idf, "DEPTH") == 0) { +- s = skip_int(s, &ph->depth); +- if (s == NULL || *s == 0) { +- return; +- } +- +- continue; +- } +- if (strcmp(idf, "MAXVAL") == 0) { +- s = skip_int(s, &ph->maxval); +- if (s == NULL || *s == 0) { +- return; +- } +- +- continue; +- } +- if (strcmp(idf, "TUPLTYPE") == 0) { +- s = skip_idf(s, type); +- if (s == NULL || *s == 0) { +- return; +- } +- +- if (strcmp(type, "BLACKANDWHITE") == 0) { +- ph->bw = 1; +- ttype = 1; +- continue; +- } +- if (strcmp(type, "GRAYSCALE") == 0) { +- ph->gray = 1; +- ttype = 1; +- continue; +- } +- if (strcmp(type, "GRAYSCALE_ALPHA") == 0) { +- ph->graya = 1; +- ttype = 1; +- continue; +- } +- if (strcmp(type, "RGB") == 0) { +- ph->rgb = 1; +- ttype = 1; +- continue; +- } +- if (strcmp(type, "RGB_ALPHA") == 0) { +- ph->rgba = 1; +- ttype = 1; +- continue; +- } +- fprintf(stderr, "read_pnm_header:unknown P7 TUPLTYPE %s\n", type); +- return; +- } +- fprintf(stderr, "read_pnm_header:unknown P7 idf %s\n", idf); +- return; +- } /* if(format == 7) */ +- +- /* Here format is in range [1,6] */ +- if (ph->width == 0) { +- s = skip_int(s, &ph->width); +- if ((s == NULL) || (*s == 0) || (ph->width < 1)) { +- return; +- } +- allow_null = 1; +- } +- if (ph->height == 0) { +- s = skip_int(s, &ph->height); +- if ((s == NULL) && allow_null) { +- continue; +- } +- if ((s == NULL) || (*s == 0) || (ph->height < 1)) { +- return; +- } +- if (format == 1 || format == 4) { +- break; +- } +- allow_null = 1; +- } +- /* here, format is in P2, P3, P5, P6 */ +- s = skip_int(s, &ph->maxval); +- if ((s == NULL) && allow_null) { +- continue; +- } +- if ((s == NULL) || (*s == 0)) { +- return; +- } +- break; +- }/* while(fgets( ) */ +- if (format == 2 || format == 3 || format > 4) { +- if (ph->maxval < 1 || ph->maxval > 65535) { +- return; +- } +- } +- if (ph->width < 1 || ph->height < 1) { +- return; +- } +- +- if (format == 7) { +- if (!end) { +- fprintf(stderr, "read_pnm_header:P7 without ENDHDR\n"); +- return; +- } +- if (ph->depth < 1 || ph->depth > 4) { +- return; +- } +- +- if (ttype) { +- ph->ok = 1; +- } +- } else { +- ph->ok = 1; +- if (format == 1 || format == 4) { +- ph->maxval = 255; +- } +- } +-} +- +-static int has_prec(int val) +-{ +- if (val < 2) { +- return 1; +- } +- if (val < 4) { +- return 2; +- } +- if (val < 8) { +- return 3; +- } +- if (val < 16) { +- return 4; +- } +- if (val < 32) { +- return 5; +- } +- if (val < 64) { +- return 6; +- } +- if (val < 128) { +- return 7; +- } +- if (val < 256) { +- return 8; +- } +- if (val < 512) { +- return 9; +- } +- if (val < 1024) { +- return 10; +- } +- if (val < 2048) { +- return 11; +- } +- if (val < 4096) { +- return 12; +- } +- if (val < 8192) { +- return 13; +- } +- if (val < 16384) { +- return 14; +- } +- if (val < 32768) { +- return 15; +- } +- return 16; +-} +- +-opj_image_t* pnmtoimage(const char *filename, opj_cparameters_t *parameters) +-{ +- int subsampling_dx = parameters->subsampling_dx; +- int subsampling_dy = parameters->subsampling_dy; +- +- FILE *fp = NULL; +- int i, compno, numcomps, w, h, prec, format; +- OPJ_COLOR_SPACE color_space; +- opj_image_cmptparm_t cmptparm[4]; /* RGBA: max. 4 components */ +- opj_image_t * image = NULL; +- struct pnm_header header_info; +- +- if ((fp = fopen(filename, "rb")) == NULL) { +- fprintf(stderr, "pnmtoimage:Failed to open %s for reading!\n", filename); +- return NULL; +- } +- memset(&header_info, 0, sizeof(struct pnm_header)); +- +- read_pnm_header(fp, &header_info); +- +- if (!header_info.ok) { +- fclose(fp); +- return NULL; +- } +- +- if (header_info.width == 0 +- || header_info.height == 0 +- || (header_info.format == 7 && header_info.depth == 0)) { +- fclose(fp); +- return NULL; +- } +- +- /* This limitation could be removed by making sure to use size_t below */ +- if (header_info.height != 0 && +- header_info.width > INT_MAX / header_info.height) { +- fprintf(stderr, "pnmtoimage:Image %dx%d too big!\n", +- header_info.width, header_info.height); +- fclose(fp); +- return NULL; +- } +- +- format = header_info.format; +- +- switch (format) { +- case 1: /* ascii bitmap */ +- case 4: /* raw bitmap */ +- numcomps = 1; +- break; +- +- case 2: /* ascii greymap */ +- case 5: /* raw greymap */ +- numcomps = 1; +- break; +- +- case 3: /* ascii pixmap */ +- case 6: /* raw pixmap */ +- numcomps = 3; +- break; +- +- case 7: /* arbitrary map */ +- numcomps = header_info.depth; +- break; +- +- default: +- fclose(fp); +- return NULL; +- } +- if (numcomps < 3) { +- color_space = OPJ_CLRSPC_GRAY; /* GRAY, GRAYA */ +- } else { +- color_space = OPJ_CLRSPC_SRGB; /* RGB, RGBA */ +- } +- +- prec = has_prec(header_info.maxval); +- +- if (prec < 8) { +- prec = 8; +- } +- +- w = header_info.width; +- h = header_info.height; +- subsampling_dx = parameters->subsampling_dx; +- subsampling_dy = parameters->subsampling_dy; +- +- memset(&cmptparm[0], 0, (size_t)numcomps * sizeof(opj_image_cmptparm_t)); +- +- for (i = 0; i < numcomps; i++) { +- cmptparm[i].prec = (OPJ_UINT32)prec; +- cmptparm[i].bpp = (OPJ_UINT32)prec; +- cmptparm[i].sgnd = 0; +- cmptparm[i].dx = (OPJ_UINT32)subsampling_dx; +- cmptparm[i].dy = (OPJ_UINT32)subsampling_dy; +- cmptparm[i].w = (OPJ_UINT32)w; +- cmptparm[i].h = (OPJ_UINT32)h; +- } +- image = opj_image_create((OPJ_UINT32)numcomps, &cmptparm[0], color_space); +- +- if (!image) { +- fclose(fp); +- return NULL; +- } +- +- /* set image offset and reference grid */ +- image->x0 = (OPJ_UINT32)parameters->image_offset_x0; +- image->y0 = (OPJ_UINT32)parameters->image_offset_y0; +- image->x1 = (OPJ_UINT32)(parameters->image_offset_x0 + (w - 1) * subsampling_dx +- + 1); +- image->y1 = (OPJ_UINT32)(parameters->image_offset_y0 + (h - 1) * subsampling_dy +- + 1); +- +- if ((format == 2) || (format == 3)) { /* ascii pixmap */ +- unsigned int index; +- +- for (i = 0; i < w * h; i++) { +- for (compno = 0; compno < numcomps; compno++) { +- index = 0; +- if (fscanf(fp, "%u", &index) != 1) { +- fprintf(stderr, "Missing data. Quitting.\n"); +- opj_image_destroy(image); +- fclose(fp); +- return NULL; +- } +- +- image->comps[compno].data[i] = (OPJ_INT32)(index * 255) / header_info.maxval; +- } +- } +- } else if ((format == 5) +- || (format == 6) +- || ((format == 7) +- && (header_info.gray || header_info.graya +- || header_info.rgb || header_info.rgba))) { /* binary pixmap */ +- unsigned char c0, c1, one; +- +- one = (prec < 9); +- +- for (i = 0; i < w * h; i++) { +- for (compno = 0; compno < numcomps; compno++) { +- if (!fread(&c0, 1, 1, fp)) { +- fprintf(stderr, "Missing data. Quitting.\n"); +- opj_image_destroy(image); +- fclose(fp); +- return NULL; +- } +- if (one) { +- image->comps[compno].data[i] = c0; +- } else { +- if (!fread(&c1, 1, 1, fp)) { +- fprintf(stderr, "Missing data. Quitting.\n"); +- opj_image_destroy(image); +- fclose(fp); +- return NULL; +- } +- /* netpbm: */ +- image->comps[compno].data[i] = ((c0 << 8) | c1); +- } +- } +- } +- } else if (format == 1) { /* ascii bitmap */ +- for (i = 0; i < w * h; i++) { +- unsigned int index; +- +- if (fscanf(fp, "%u", &index) != 1) { +- fprintf(stderr, "Missing data. Quitting.\n"); +- opj_image_destroy(image); +- fclose(fp); +- return NULL; +- } +- +- image->comps[0].data[i] = (index ? 0 : 255); +- } +- } else if (format == 4) { +- int x, y, bit; +- int uc; +- +- i = 0; +- for (y = 0; y < h; ++y) { +- bit = -1; +- uc = 0; +- +- for (x = 0; x < w; ++x) { +- if (bit == -1) { +- bit = 7; +- uc = getc(fp); +- if (uc == EOF) { +- fprintf(stderr, "Missing data. Quitting.\n"); +- opj_image_destroy(image); +- fclose(fp); +- return NULL; +- } +- } +- image->comps[0].data[i] = ((((unsigned char)uc >> bit) & 1) ? 0 : 255); +- --bit; +- ++i; +- } +- } +- } else if ((format == 7 && header_info.bw)) { /*MONO*/ +- unsigned char uc; +- +- for (i = 0; i < w * h; ++i) { +- if (!fread(&uc, 1, 1, fp)) { +- fprintf(stderr, "Missing data. Quitting.\n"); +- opj_image_destroy(image); +- fclose(fp); +- return NULL; +- } +- image->comps[0].data[i] = (uc & 1) ? 0 : 255; +- } +- } +- fclose(fp); +- +- return image; +-}/* pnmtoimage() */ +- +-static int are_comps_similar(opj_image_t * image) +-{ +- unsigned int i; +- for (i = 1; i < image->numcomps; i++) { +- if (image->comps[0].dx != image->comps[i].dx || +- image->comps[0].dy != image->comps[i].dy || +- (i <= 2 && +- (image->comps[0].prec != image->comps[i].prec || +- image->comps[0].sgnd != image->comps[i].sgnd))) { +- return OPJ_FALSE; +- } +- } +- return OPJ_TRUE; +-} +- +- +-int imagetopnm(opj_image_t * image, const char *outfile, int force_split) +-{ +- int *red, *green, *blue, *alpha; +- int wr, hr, max; +- int i; +- unsigned int compno, ncomp; +- int adjustR, adjustG, adjustB, adjustA; +- int fails, two, want_gray, has_alpha, triple; +- int prec, v; +- FILE *fdest = NULL; +- const char *tmp = outfile; +- char *destname; +- +- alpha = NULL; +- +- if ((prec = (int)image->comps[0].prec) > 16) { +- fprintf(stderr, "%s:%d:imagetopnm\n\tprecision %d is larger than 16" +- "\n\t: refused.\n", __FILE__, __LINE__, prec); +- return 1; +- } +- two = has_alpha = 0; +- fails = 1; +- ncomp = image->numcomps; +- +- while (*tmp) { +- ++tmp; +- } +- tmp -= 2; +- want_gray = (*tmp == 'g' || *tmp == 'G'); +- ncomp = image->numcomps; +- +- if (want_gray) { +- ncomp = 1; +- } +- +- if ((force_split == 0) && ncomp >= 2 && +- are_comps_similar(image)) { +- fdest = fopen(outfile, "wb"); +- +- if (!fdest) { +- fprintf(stderr, "ERROR -> failed to open %s for writing\n", outfile); +- return fails; +- } +- two = (prec > 8); +- triple = (ncomp > 2); +- wr = (int)image->comps[0].w; +- hr = (int)image->comps[0].h; +- max = (1 << prec) - 1; +- has_alpha = (ncomp == 4 || ncomp == 2); +- +- red = image->comps[0].data; +- if (red == NULL) { +- fprintf(stderr, +- "imagetopnm: planes[%d] == NULL.\n", 0); +- fprintf(stderr, "\tAborting\n"); +- fclose(fdest); +- return fails; +- } +- +- if (triple) { +- green = image->comps[1].data; +- blue = image->comps[2].data; +- for (i = 1; i <= 2; i++) { +- if (image->comps[i].data == NULL) { +- fprintf(stderr, +- "imagetopnm: planes[%d] == NULL.\n", i); +- fprintf(stderr, "\tAborting\n"); +- fclose(fdest); +- return fails; +- } +- } +- } else { +- green = blue = NULL; +- } +- +- if (has_alpha) { +- const char *tt = (triple ? "RGB_ALPHA" : "GRAYSCALE_ALPHA"); +- +- fprintf(fdest, "P7\n# OpenJPEG-%s\nWIDTH %d\nHEIGHT %d\nDEPTH %u\n" +- "MAXVAL %d\nTUPLTYPE %s\nENDHDR\n", opj_version(), +- wr, hr, ncomp, max, tt); +- alpha = image->comps[ncomp - 1].data; +- adjustA = (image->comps[ncomp - 1].sgnd ? +- 1 << (image->comps[ncomp - 1].prec - 1) : 0); +- } else { +- fprintf(fdest, "P6\n# OpenJPEG-%s\n%d %d\n%d\n", +- opj_version(), wr, hr, max); +- adjustA = 0; +- } +- adjustR = (image->comps[0].sgnd ? 1 << (image->comps[0].prec - 1) : 0); ++ /* netpbm: */ ++ fprintf(fdest, "%c%c", (unsigned char)(v >> 8), (unsigned char)v); + + if (triple) { +- adjustG = (image->comps[1].sgnd ? 1 << (image->comps[1].prec - 1) : 0); +- adjustB = (image->comps[2].sgnd ? 1 << (image->comps[2].prec - 1) : 0); +- } else { +- adjustG = adjustB = 0; +- } +- +- for (i = 0; i < wr * hr; ++i) { +- if (two) { +- v = *red + adjustR; +- ++red; +- if (v > 65535) { +- v = 65535; +- } else if (v < 0) { +- v = 0; +- } +- +- /* netpbm: */ +- fprintf(fdest, "%c%c", (unsigned char)(v >> 8), (unsigned char)v); +- +- if (triple) { +- v = *green + adjustG; +- ++green; +- if (v > 65535) { +- v = 65535; +- } else if (v < 0) { +- v = 0; +- } +- +- /* netpbm: */ +- fprintf(fdest, "%c%c", (unsigned char)(v >> 8), (unsigned char)v); +- +- v = *blue + adjustB; +- ++blue; +- if (v > 65535) { +- v = 65535; +- } else if (v < 0) { +- v = 0; +- } +- +- /* netpbm: */ +- fprintf(fdest, "%c%c", (unsigned char)(v >> 8), (unsigned char)v); +- +- }/* if(triple) */ +- +- if (has_alpha) { +- v = *alpha + adjustA; +- ++alpha; +- if (v > 65535) { +- v = 65535; +- } else if (v < 0) { +- v = 0; +- } +- +- /* netpbm: */ +- fprintf(fdest, "%c%c", (unsigned char)(v >> 8), (unsigned char)v); +- } +- continue; +- +- } /* if(two) */ +- +- /* prec <= 8: */ +- v = *red++; +- if (v > 255) { +- v = 255; +- } else if (v < 0) { +- v = 0; +- } +- +- fprintf(fdest, "%c", (unsigned char)v); +- if (triple) { +- v = *green++; +- if (v > 255) { +- v = 255; +- } else if (v < 0) { +- v = 0; +- } +- +- fprintf(fdest, "%c", (unsigned char)v); +- v = *blue++; +- if (v > 255) { +- v = 255; +- } else if (v < 0) { +- v = 0; +- } +- +- fprintf(fdest, "%c", (unsigned char)v); +- } +- if (has_alpha) { +- v = *alpha++; +- if (v > 255) { +- v = 255; +- } else if (v < 0) { +- v = 0; +- } +- +- fprintf(fdest, "%c", (unsigned char)v); +- } +- } /* for(i */ ++ v = *green + adjustG; ++ ++green; ++ if (v > 65535) { ++ v = 65535; ++ } else if (v < 0) { ++ v = 0; ++ } ++ ++ /* netpbm: */ ++ fprintf(fdest, "%c%c", (unsigned char)(v >> 8), (unsigned char)v); ++ ++ v = *blue + adjustB; ++ ++blue; ++ if (v > 65535) { ++ v = 65535; ++ } else if (v < 0) { ++ v = 0; ++ } + +- fclose(fdest); +- return 0; +- } ++ /* netpbm: */ ++ fprintf(fdest, "%c%c", (unsigned char)(v >> 8), (unsigned char)v); + +- /* YUV or MONO: */ ++ } /* if(triple) */ + +- if (image->numcomps > ncomp) { +- fprintf(stderr, "WARNING -> [PGM file] Only the first component\n"); +- fprintf(stderr, " is written to the file\n"); +- } +- destname = (char*)malloc(strlen(outfile) + 8); +- if (destname == NULL) { +- fprintf(stderr, "imagetopnm: memory out\n"); +- return 1; +- } +- for (compno = 0; compno < ncomp; compno++) { +- if (ncomp > 1) { +- /*sprintf(destname, "%d.%s", compno, outfile);*/ +- const size_t olen = strlen(outfile); +- const size_t dotpos = olen - 4; +- +- strncpy(destname, outfile, dotpos); +- sprintf(destname + dotpos, "_%u.pgm", compno); +- } else { +- sprintf(destname, "%s", outfile); +- } +- +- fdest = fopen(destname, "wb"); +- if (!fdest) { +- fprintf(stderr, "ERROR -> failed to open %s for writing\n", destname); +- free(destname); +- return 1; +- } +- wr = (int)image->comps[compno].w; +- hr = (int)image->comps[compno].h; +- prec = (int)image->comps[compno].prec; +- max = (1 << prec) - 1; +- +- fprintf(fdest, "P5\n#OpenJPEG-%s\n%d %d\n%d\n", +- opj_version(), wr, hr, max); +- +- red = image->comps[compno].data; +- if (!red) { +- fclose(fdest); +- continue; +- } +- +- adjustR = +- (image->comps[compno].sgnd ? 1 << (image->comps[compno].prec - 1) : 0); +- +- if (prec > 8) { +- for (i = 0; i < wr * hr; i++) { +- v = *red + adjustR; +- ++red; +- if (v > 65535) { +- v = 65535; +- } else if (v < 0) { +- v = 0; +- } +- +- /* netpbm: */ +- fprintf(fdest, "%c%c", (unsigned char)(v >> 8), (unsigned char)v); +- +- if (has_alpha) { +- v = *alpha++; +- if (v > 65535) { +- v = 65535; +- } else if (v < 0) { +- v = 0; +- } +- +- /* netpbm: */ +- fprintf(fdest, "%c%c", (unsigned char)(v >> 8), (unsigned char)v); +- } +- }/* for(i */ +- } else { /* prec <= 8 */ +- for (i = 0; i < wr * hr; ++i) { +- v = *red + adjustR; +- ++red; +- if (v > 255) { +- v = 255; +- } else if (v < 0) { +- v = 0; +- } +- +- fprintf(fdest, "%c", (unsigned char)v); +- } +- } +- fclose(fdest); +- } /* for (compno */ +- free(destname); ++ if (has_alpha) { ++ v = *alpha + adjustA; ++ ++alpha; ++ if (v > 65535) { ++ v = 65535; ++ } else if (v < 0) { ++ v = 0; ++ } ++ ++ /* netpbm: */ ++ fprintf(fdest, "%c%c", (unsigned char)(v >> 8), (unsigned char)v); ++ } ++ continue; ++ ++ } /* if(two) */ ++ ++ /* prec <= 8: */ ++ v = *red++; ++ if (v > 255) { ++ v = 255; ++ } else if (v < 0) { ++ v = 0; ++ } ++ ++ fprintf(fdest, "%c", (unsigned char)v); ++ if (triple) { ++ v = *green++; ++ if (v > 255) { ++ v = 255; ++ } else if (v < 0) { ++ v = 0; ++ } ++ ++ fprintf(fdest, "%c", (unsigned char)v); ++ v = *blue++; ++ if (v > 255) { ++ v = 255; ++ } else if (v < 0) { ++ v = 0; ++ } ++ ++ fprintf(fdest, "%c", (unsigned char)v); ++ } ++ if (has_alpha) { ++ v = *alpha++; ++ if (v > 255) { ++ v = 255; ++ } else if (v < 0) { ++ v = 0; ++ } ++ ++ fprintf(fdest, "%c", (unsigned char)v); ++ } ++ } /* for(i */ + ++ fclose(fdest); + return 0; +-}/* imagetopnm() */ +- +-/* -->> -->> -->> -->> ++ } + +- RAW IMAGE FORMAT ++ /* YUV or MONO: */ + +- <<-- <<-- <<-- <<-- */ +-static opj_image_t* rawtoimage_common(const char *filename, +- opj_cparameters_t *parameters, raw_cparameters_t *raw_cp, OPJ_BOOL big_endian) +-{ +- int subsampling_dx = parameters->subsampling_dx; +- int subsampling_dy = parameters->subsampling_dy; +- +- FILE *f = NULL; +- int i, compno, numcomps, w, h; +- OPJ_COLOR_SPACE color_space; +- opj_image_cmptparm_t *cmptparm; +- opj_image_t * image = NULL; +- unsigned short ch; +- +- if ((!(raw_cp->rawWidth & raw_cp->rawHeight & raw_cp->rawComp & +- raw_cp->rawBitDepth)) == 0) { +- fprintf(stderr, "\nError: invalid raw image parameters\n"); +- fprintf(stderr, "Please use the Format option -F:\n"); +- fprintf(stderr, +- "-F ,,,,{s,u}@x:...:x\n"); +- fprintf(stderr, +- "If subsampling is omitted, 1x1 is assumed for all components\n"); +- fprintf(stderr, +- "Example: -i image.raw -o image.j2k -F 512,512,3,8,u@1x1:2x2:2x2\n"); +- fprintf(stderr, " for raw 512x512 image with 4:2:0 subsampling\n"); +- fprintf(stderr, "Aborting.\n"); +- return NULL; +- } +- +- f = fopen(filename, "rb"); +- if (!f) { +- fprintf(stderr, "Failed to open %s for reading !!\n", filename); +- fprintf(stderr, "Aborting\n"); +- return NULL; +- } +- numcomps = raw_cp->rawComp; ++ if (image->numcomps > ncomp) { ++ fprintf(stderr, "WARNING -> [PGM file] Only the first component\n"); ++ fprintf(stderr, " is written to the file\n"); ++ } ++ destname = (char *)malloc(strlen(outfile) + 8); ++ if (destname == NULL) { ++ fprintf(stderr, "imagetopnm: memory out\n"); ++ return 1; ++ } ++ for (compno = 0; compno < ncomp; compno++) { ++ if (ncomp > 1) { ++ /*sprintf(destname, "%d.%s", compno, outfile);*/ ++ const size_t olen = strlen(outfile); ++ const size_t dotpos = olen - 4; + +- /* FIXME ADE at this point, tcp_mct has not been properly set in calling function */ +- if (numcomps == 1) { +- color_space = OPJ_CLRSPC_GRAY; +- } else if ((numcomps >= 3) && (parameters->tcp_mct == 0)) { +- color_space = OPJ_CLRSPC_SYCC; +- } else if ((numcomps >= 3) && (parameters->tcp_mct != 2)) { +- color_space = OPJ_CLRSPC_SRGB; ++ strncpy(destname, outfile, dotpos); ++ sprintf(destname + dotpos, "_%u.pgm", compno); + } else { +- color_space = OPJ_CLRSPC_UNKNOWN; +- } +- w = raw_cp->rawWidth; +- h = raw_cp->rawHeight; +- cmptparm = (opj_image_cmptparm_t*) calloc((OPJ_UINT32)numcomps, +- sizeof(opj_image_cmptparm_t)); +- if (!cmptparm) { +- fprintf(stderr, "Failed to allocate image components parameters !!\n"); +- fprintf(stderr, "Aborting\n"); +- fclose(f); +- return NULL; +- } +- /* initialize image components */ +- for (i = 0; i < numcomps; i++) { +- cmptparm[i].prec = (OPJ_UINT32)raw_cp->rawBitDepth; +- cmptparm[i].bpp = (OPJ_UINT32)raw_cp->rawBitDepth; +- cmptparm[i].sgnd = (OPJ_UINT32)raw_cp->rawSigned; +- cmptparm[i].dx = (OPJ_UINT32)(subsampling_dx * raw_cp->rawComps[i].dx); +- cmptparm[i].dy = (OPJ_UINT32)(subsampling_dy * raw_cp->rawComps[i].dy); +- cmptparm[i].w = (OPJ_UINT32)w; +- cmptparm[i].h = (OPJ_UINT32)h; +- } +- /* create the image */ +- image = opj_image_create((OPJ_UINT32)numcomps, &cmptparm[0], color_space); +- free(cmptparm); +- if (!image) { +- fclose(f); +- return NULL; +- } +- /* set image offset and reference grid */ +- image->x0 = (OPJ_UINT32)parameters->image_offset_x0; +- image->y0 = (OPJ_UINT32)parameters->image_offset_y0; +- image->x1 = (OPJ_UINT32)parameters->image_offset_x0 + (OPJ_UINT32)(w - 1) * +- (OPJ_UINT32)subsampling_dx + 1; +- image->y1 = (OPJ_UINT32)parameters->image_offset_y0 + (OPJ_UINT32)(h - 1) * +- (OPJ_UINT32)subsampling_dy + 1; +- +- if (raw_cp->rawBitDepth <= 8) { +- unsigned char value = 0; +- for (compno = 0; compno < numcomps; compno++) { +- int nloop = (w * h) / (raw_cp->rawComps[compno].dx * +- raw_cp->rawComps[compno].dy); +- for (i = 0; i < nloop; i++) { +- if (!fread(&value, 1, 1, f)) { +- fprintf(stderr, "Error reading raw file. End of file probably reached.\n"); +- opj_image_destroy(image); +- fclose(f); +- return NULL; +- } +- image->comps[compno].data[i] = raw_cp->rawSigned ? (char)value : value; +- } +- } +- } else if (raw_cp->rawBitDepth <= 16) { +- unsigned short value; +- for (compno = 0; compno < numcomps; compno++) { +- int nloop = (w * h) / (raw_cp->rawComps[compno].dx * +- raw_cp->rawComps[compno].dy); +- for (i = 0; i < nloop; i++) { +- unsigned char temp1; +- unsigned char temp2; +- if (!fread(&temp1, 1, 1, f)) { +- fprintf(stderr, "Error reading raw file. End of file probably reached.\n"); +- opj_image_destroy(image); +- fclose(f); +- return NULL; +- } +- if (!fread(&temp2, 1, 1, f)) { +- fprintf(stderr, "Error reading raw file. End of file probably reached.\n"); +- opj_image_destroy(image); +- fclose(f); +- return NULL; +- } +- if (big_endian) { +- value = (unsigned short)((temp1 << 8) + temp2); +- } else { +- value = (unsigned short)((temp2 << 8) + temp1); +- } +- image->comps[compno].data[i] = raw_cp->rawSigned ? (short)value : value; +- } +- } +- } else { +- fprintf(stderr, +- "OpenJPEG cannot encode raw components with bit depth higher than 16 bits.\n"); +- opj_image_destroy(image); +- fclose(f); +- return NULL; ++ sprintf(destname, "%s", outfile); + } + +- if (fread(&ch, 1, 1, f)) { +- fprintf(stderr, "Warning. End of raw file not reached... processing anyway\n"); ++ fdest = fopen(destname, "wb"); ++ if (!fdest) { ++ fprintf(stderr, "ERROR -> failed to open %s for writing\n", destname); ++ free(destname); ++ return 1; + } +- fclose(f); ++ wr = (int)image->comps[compno].w; ++ hr = (int)image->comps[compno].h; ++ prec = (int)image->comps[compno].prec; ++ max = (1 << prec) - 1; + +- return image; +-} ++ fprintf(fdest, "P5\n#OpenJPEG-%s\n%d %d\n%d\n", opj_version(), wr, hr, max); + +-opj_image_t* rawltoimage(const char *filename, opj_cparameters_t *parameters, +- raw_cparameters_t *raw_cp) +-{ +- return rawtoimage_common(filename, parameters, raw_cp, OPJ_FALSE); +-} +- +-opj_image_t* rawtoimage(const char *filename, opj_cparameters_t *parameters, +- raw_cparameters_t *raw_cp) +-{ +- return rawtoimage_common(filename, parameters, raw_cp, OPJ_TRUE); +-} ++ red = image->comps[compno].data; + +-static int imagetoraw_common(opj_image_t * image, const char *outfile, +- OPJ_BOOL big_endian) +-{ +- FILE *rawFile = NULL; +- size_t res; +- unsigned int compno, numcomps; +- int w, h, fails; +- int line, row, curr, mask; +- int *ptr; +- unsigned char uc; +- (void)big_endian; +- +- if ((image->numcomps * image->x1 * image->y1) == 0) { +- fprintf(stderr, "\nError: invalid raw image parameters\n"); +- return 1; ++ if (!red) { ++ fclose(fdest); ++ continue; + } + +- numcomps = image->numcomps; ++ adjustR = ++ (image->comps[compno].sgnd ? 1 << (image->comps[compno].prec - 1) : 0); + +- if (numcomps > 4) { +- numcomps = 4; +- } +- +- for (compno = 1; compno < numcomps; ++compno) { +- if (image->comps[0].dx != image->comps[compno].dx) { +- break; +- } +- if (image->comps[0].dy != image->comps[compno].dy) { +- break; ++ if (prec > 8) { ++ for (i = 0; i < wr * hr; i++) { ++ v = *red + adjustR; ++ ++red; ++ if (v > 65535) { ++ v = 65535; ++ } else if (v < 0) { ++ v = 0; + } +- if (image->comps[0].prec != image->comps[compno].prec) { +- break; +- } +- if (image->comps[0].sgnd != image->comps[compno].sgnd) { +- break; +- } +- } +- if (compno != numcomps) { +- fprintf(stderr, +- "imagetoraw_common: All components shall have the same subsampling, same bit depth, same sign.\n"); +- fprintf(stderr, "\tAborting\n"); +- return 1; +- } + +- rawFile = fopen(outfile, "wb"); +- if (!rawFile) { +- fprintf(stderr, "Failed to open %s for writing !!\n", outfile); +- return 1; +- } +- +- fails = 1; +- fprintf(stdout, "Raw image characteristics: %d components\n", image->numcomps); ++ /* netpbm: */ ++ fprintf(fdest, "%c%c", (unsigned char)(v >> 8), (unsigned char)v); + +- for (compno = 0; compno < image->numcomps; compno++) { +- fprintf(stdout, "Component %u characteristics: %dx%dx%d %s\n", compno, +- image->comps[compno].w, +- image->comps[compno].h, image->comps[compno].prec, +- image->comps[compno].sgnd == 1 ? "signed" : "unsigned"); +- +- w = (int)image->comps[compno].w; +- h = (int)image->comps[compno].h; +- +- if (image->comps[compno].prec <= 8) { +- if (image->comps[compno].sgnd == 1) { +- mask = (1 << image->comps[compno].prec) - 1; +- ptr = image->comps[compno].data; +- for (line = 0; line < h; line++) { +- for (row = 0; row < w; row++) { +- curr = *ptr; +- if (curr > 127) { +- curr = 127; +- } else if (curr < -128) { +- curr = -128; +- } +- uc = (unsigned char)(curr & mask); +- res = fwrite(&uc, 1, 1, rawFile); +- if (res < 1) { +- fprintf(stderr, "failed to write 1 byte for %s\n", outfile); +- goto fin; +- } +- ptr++; +- } +- } +- } else if (image->comps[compno].sgnd == 0) { +- mask = (1 << image->comps[compno].prec) - 1; +- ptr = image->comps[compno].data; +- for (line = 0; line < h; line++) { +- for (row = 0; row < w; row++) { +- curr = *ptr; +- if (curr > 255) { +- curr = 255; +- } else if (curr < 0) { +- curr = 0; +- } +- uc = (unsigned char)(curr & mask); +- res = fwrite(&uc, 1, 1, rawFile); +- if (res < 1) { +- fprintf(stderr, "failed to write 1 byte for %s\n", outfile); +- goto fin; +- } +- ptr++; +- } +- } +- } +- } else if (image->comps[compno].prec <= 16) { +- if (image->comps[compno].sgnd == 1) { +- union { +- signed short val; +- signed char vals[2]; +- } uc16; +- mask = (1 << image->comps[compno].prec) - 1; +- ptr = image->comps[compno].data; +- for (line = 0; line < h; line++) { +- for (row = 0; row < w; row++) { +- curr = *ptr; +- if (curr > 32767) { +- curr = 32767; +- } else if (curr < -32768) { +- curr = -32768; +- } +- uc16.val = (signed short)(curr & mask); +- res = fwrite(uc16.vals, 1, 2, rawFile); +- if (res < 2) { +- fprintf(stderr, "failed to write 2 byte for %s\n", outfile); +- goto fin; +- } +- ptr++; +- } +- } +- } else if (image->comps[compno].sgnd == 0) { +- union { +- unsigned short val; +- unsigned char vals[2]; +- } uc16; +- mask = (1 << image->comps[compno].prec) - 1; +- ptr = image->comps[compno].data; +- for (line = 0; line < h; line++) { +- for (row = 0; row < w; row++) { +- curr = *ptr; +- if (curr > 65535) { +- curr = 65535; +- } else if (curr < 0) { +- curr = 0; +- } +- uc16.val = (unsigned short)(curr & mask); +- res = fwrite(uc16.vals, 1, 2, rawFile); +- if (res < 2) { +- fprintf(stderr, "failed to write 2 byte for %s\n", outfile); +- goto fin; +- } +- ptr++; +- } +- } +- } +- } else if (image->comps[compno].prec <= 32) { +- fprintf(stderr, "More than 16 bits per component not handled yet\n"); +- goto fin; +- } else { +- fprintf(stderr, "Error: invalid precision: %d\n", image->comps[compno].prec); +- goto fin; ++ if (has_alpha) { ++ v = *alpha++; ++ if (v > 65535) { ++ v = 65535; ++ } else if (v < 0) { ++ v = 0; ++ } ++ ++ /* netpbm: */ ++ fprintf(fdest, "%c%c", (unsigned char)(v >> 8), (unsigned char)v); ++ } ++ } /* for(i */ ++ } else { /* prec <= 8 */ ++ for (i = 0; i < wr * hr; ++i) { ++ v = *red + adjustR; ++ ++red; ++ if (v > 255) { ++ v = 255; ++ } else if (v < 0) { ++ v = 0; + } ++ fprintf(fdest, "%c", (unsigned char)v); ++ } + } +- fails = 0; +-fin: +- fclose(rawFile); +- return fails; +-} +- +-int imagetoraw(opj_image_t * image, const char *outfile) +-{ +- return imagetoraw_common(image, outfile, OPJ_TRUE); +-} +- +-int imagetorawl(opj_image_t * image, const char *outfile) +-{ +- return imagetoraw_common(image, outfile, OPJ_FALSE); +-} ++ fclose(fdest); ++ } /* for (compno */ ++ free(destname); + ++ return 0; ++} /* imagetopnm() */ diff --git a/oss-internship-2020/openjpeg/examples/convert_h.patch b/oss-internship-2020/openjpeg/examples/convert_h.patch new file mode 100644 index 0000000..efeafc6 --- /dev/null +++ b/oss-internship-2020/openjpeg/examples/convert_h.patch @@ -0,0 +1,135 @@ +--- convert.h 2020-08-27 15:46:45.028628305 +0000 ++++ ../../../../examples/convert_helper.h 2020-08-27 14:26:02.155455250 +0000 +@@ -1,126 +1,8 @@ +-/* +- * The copyright in this software is being made available under the 2-clauses +- * BSD License, included below. This software may be subject to other third +- * party and contributor rights, including patent rights, and no such rights +- * are granted under this license. +- * +- * Copyright (c) 2002-2014, Universite catholique de Louvain (UCL), Belgium +- * Copyright (c) 2002-2014, Professor Benoit Macq +- * Copyright (c) 2001-2003, David Janssens +- * Copyright (c) 2002-2003, Yannick Verschueren +- * Copyright (c) 2003-2007, Francois-Olivier Devaux +- * Copyright (c) 2003-2014, Antonin Descampe +- * Copyright (c) 2005, Herve Drolon, FreeImage Team +- * All rights reserved. +- * +- * Redistribution and use in source and binary forms, with or without +- * modification, are permitted provided that the following conditions +- * are met: +- * 1. Redistributions of source code must retain the above copyright +- * notice, this list of conditions and the following disclaimer. +- * 2. Redistributions in binary form must reproduce the above copyright +- * notice, this list of conditions and the following disclaimer in the +- * documentation and/or other materials provided with the distribution. +- * +- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS `AS IS' +- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +- * POSSIBILITY OF SUCH DAMAGE. +- */ +-#ifndef __J2K_CONVERT_H +-#define __J2K_CONVERT_H ++// imagetopnm and the two functions it calls internaly are copied from the ++// library's tools; from openjpeg/src/bin/jp2/convert.c + +-/**@name RAW component encoding parameters */ +-/*@{*/ +-typedef struct raw_comp_cparameters { +- /** subsampling in X direction */ +- int dx; +- /** subsampling in Y direction */ +- int dy; +- /*@}*/ +-} raw_comp_cparameters_t; +- +-/**@name RAW image encoding parameters */ +-/*@{*/ +-typedef struct raw_cparameters { +- /** width of the raw image */ +- int rawWidth; +- /** height of the raw image */ +- int rawHeight; +- /** number of components of the raw image */ +- int rawComp; +- /** bit depth of the raw image */ +- int rawBitDepth; +- /** signed/unsigned raw image */ +- OPJ_BOOL rawSigned; +- /** raw components parameters */ +- raw_comp_cparameters_t *rawComps; +- /*@}*/ +-} raw_cparameters_t; +- +-/* Component precision clipping */ +-void clip_component(opj_image_comp_t* component, OPJ_UINT32 precision); +-/* Component precision scaling */ +-void scale_component(opj_image_comp_t* component, OPJ_UINT32 precision); +- +-/* planar / interleaved conversions */ +-typedef void (* convert_32s_CXPX)(const OPJ_INT32* pSrc, OPJ_INT32* const* pDst, +- OPJ_SIZE_T length); +-extern const convert_32s_CXPX convert_32s_CXPX_LUT[5]; +-typedef void (* convert_32s_PXCX)(OPJ_INT32 const* const* pSrc, OPJ_INT32* pDst, +- OPJ_SIZE_T length, OPJ_INT32 adjust); +-extern const convert_32s_PXCX convert_32s_PXCX_LUT[5]; +-/* bit depth conversions */ +-typedef void (* convert_XXx32s_C1R)(const OPJ_BYTE* pSrc, OPJ_INT32* pDst, +- OPJ_SIZE_T length); +-extern const convert_XXx32s_C1R convert_XXu32s_C1R_LUT[9]; /* up to 8bpp */ +-typedef void (* convert_32sXXx_C1R)(const OPJ_INT32* pSrc, OPJ_BYTE* pDst, +- OPJ_SIZE_T length); +-extern const convert_32sXXx_C1R convert_32sXXu_C1R_LUT[9]; /* up to 8bpp */ +- +- +-/* TGA conversion */ +-opj_image_t* tgatoimage(const char *filename, opj_cparameters_t *parameters); +-int imagetotga(opj_image_t * image, const char *outfile); +- +-/* BMP conversion */ +-opj_image_t* bmptoimage(const char *filename, opj_cparameters_t *parameters); +-int imagetobmp(opj_image_t *image, const char *outfile); +- +-/* TIFF conversion*/ +-opj_image_t* tiftoimage(const char *filename, opj_cparameters_t *parameters); +-int imagetotif(opj_image_t *image, const char *outfile); +-/** +-Load a single image component encoded in PGX file format +-@param filename Name of the PGX file to load +-@param parameters *List ?* +-@return Returns a greyscale image if successful, returns NULL otherwise +-*/ +-opj_image_t* pgxtoimage(const char *filename, opj_cparameters_t *parameters); +-int imagetopgx(opj_image_t *image, const char *outfile); +- +-opj_image_t* pnmtoimage(const char *filename, opj_cparameters_t *parameters); +-int imagetopnm(opj_image_t *image, const char *outfile, int force_split); +- +-/* RAW conversion */ +-int imagetoraw(opj_image_t * image, const char *outfile); +-int imagetorawl(opj_image_t * image, const char *outfile); +-opj_image_t* rawtoimage(const char *filename, opj_cparameters_t *parameters, +- raw_cparameters_t *raw_cp); +-opj_image_t* rawltoimage(const char *filename, opj_cparameters_t *parameters, +- raw_cparameters_t *raw_cp); +- +-/* PNG conversion*/ +-extern int imagetopng(opj_image_t *image, const char *write_idf); +-extern opj_image_t* pngtoimage(const char *filename, +- opj_cparameters_t *parameters); +- +-#endif /* __J2K_CONVERT_H */ ++#include "openjp2_sapi.sapi.h" + ++const char* opj_version(void); ++static int are_comps_similar(opj_image_t* image); ++int imagetopnm(opj_image_t* image, const char* outfile, int force_split); diff --git a/oss-internship-2020/openjpeg/examples/convert_helper.cc b/oss-internship-2020/openjpeg/examples/convert_helper.cc deleted file mode 100644 index 7a80d9b..0000000 --- a/oss-internship-2020/openjpeg/examples/convert_helper.cc +++ /dev/null @@ -1,345 +0,0 @@ -/* - * The copyright in this software is being made available under the 2-clauses - * BSD License, included below. This software may be subject to other third - * party and contributor rights, including patent rights, and no such rights - * are granted under this license. - * - * Copyright (c) 2002-2014, Universite catholique de Louvain (UCL), Belgium - * Copyright (c) 2002-2014, Professor Benoit Macq - * Copyright (c) 2001-2003, David Janssens - * Copyright (c) 2002-2003, Yannick Verschueren - * Copyright (c) 2003-2007, Francois-Olivier Devaux - * Copyright (c) 2003-2014, Antonin Descampe - * Copyright (c) 2005, Herve Drolon, FreeImage Team - * Copyright (c) 2006-2007, Parvatha Elangovan - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS `AS IS' - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -// copies of a few library tools - -#include "convert_helper.h" - -#define OPJ_TRUE 1 -#define OPJ_FALSE 0 - -const char *opj_version(void) { return "2.3.1"; } - -static int are_comps_similar(opj_image_t *image) { - unsigned int i; - for (i = 1; i < image->numcomps; i++) { - if (image->comps[0].dx != image->comps[i].dx || - image->comps[0].dy != image->comps[i].dy || - (i <= 2 && (image->comps[0].prec != image->comps[i].prec || - image->comps[0].sgnd != image->comps[i].sgnd))) { - return OPJ_FALSE; - } - } - return OPJ_TRUE; -} - -int imagetopnm(opj_image_t *image, const char *outfile, int force_split) { - int *red, *green, *blue, *alpha; - int wr, hr, max; - int i; - unsigned int compno, ncomp; - int adjustR, adjustG, adjustB, adjustA; - int fails, two, want_gray, has_alpha, triple; - int prec, v; - FILE *fdest = NULL; - const char *tmp = outfile; - char *destname; - - alpha = NULL; - - if ((prec = (int)image->comps[0].prec) > 16) { - fprintf(stderr, - "%s:%d:imagetopnm\n\tprecision %d is larger than 16" - "\n\t: refused.\n", - __FILE__, __LINE__, prec); - return 1; - } - - two = has_alpha = 0; - fails = 1; - ncomp = image->numcomps; - - while (*tmp) { - ++tmp; - } - tmp -= 2; - want_gray = (*tmp == 'g' || *tmp == 'G'); - ncomp = image->numcomps; - - if (want_gray) { - ncomp = 1; - } - - if ((force_split == 0) && ncomp >= 2 && are_comps_similar(image)) { - fdest = fopen(outfile, "wb"); - - if (!fdest) { - fprintf(stderr, "ERROR -> failed to open %s for writing\n", outfile); - return fails; - } - two = (prec > 8); - triple = (ncomp > 2); - wr = (int)image->comps[0].w; - hr = (int)image->comps[0].h; - max = (1 << prec) - 1; - has_alpha = (ncomp == 4 || ncomp == 2); - - red = image->comps[0].data; - if (red == NULL) { - fprintf(stderr, "imagetopnm: planes[%d] == NULL.\n", 0); - fprintf(stderr, "\tAborting\n"); - fclose(fdest); - return fails; - } - - if (triple) { - green = image->comps[1].data; - blue = image->comps[2].data; - for (i = 1; i <= 2; i++) { - if (image->comps[i].data == NULL) { - fprintf(stderr, "imagetopnm: planes[%d] == NULL.\n", i); - fprintf(stderr, "\tAborting\n"); - fclose(fdest); - return fails; - } - } - } else { - green = blue = NULL; - } - - if (has_alpha) { - const char *tt = (triple ? "RGB_ALPHA" : "GRAYSCALE_ALPHA"); - - fprintf(fdest, - "P7\n# OpenJPEG-%s\nWIDTH %d\nHEIGHT %d\nDEPTH %u\n" - "MAXVAL %d\nTUPLTYPE %s\nENDHDR\n", - opj_version(), wr, hr, ncomp, max, tt); - alpha = image->comps[ncomp - 1].data; - adjustA = (image->comps[ncomp - 1].sgnd - ? 1 << (image->comps[ncomp - 1].prec - 1) - : 0); - } else { - fprintf(fdest, "P6\n# OpenJPEG-%s\n%d %d\n%d\n", opj_version(), wr, hr, - max); - adjustA = 0; - } - adjustR = (image->comps[0].sgnd ? 1 << (image->comps[0].prec - 1) : 0); - - if (triple) { - adjustG = (image->comps[1].sgnd ? 1 << (image->comps[1].prec - 1) : 0); - adjustB = (image->comps[2].sgnd ? 1 << (image->comps[2].prec - 1) : 0); - } else { - adjustG = adjustB = 0; - } - - for (i = 0; i < wr * hr; ++i) { - if (two) { - v = *red + adjustR; - ++red; - if (v > 65535) { - v = 65535; - } else if (v < 0) { - v = 0; - } - - /* netpbm: */ - fprintf(fdest, "%c%c", (unsigned char)(v >> 8), (unsigned char)v); - - if (triple) { - v = *green + adjustG; - ++green; - if (v > 65535) { - v = 65535; - } else if (v < 0) { - v = 0; - } - - /* netpbm: */ - fprintf(fdest, "%c%c", (unsigned char)(v >> 8), (unsigned char)v); - - v = *blue + adjustB; - ++blue; - if (v > 65535) { - v = 65535; - } else if (v < 0) { - v = 0; - } - - /* netpbm: */ - fprintf(fdest, "%c%c", (unsigned char)(v >> 8), (unsigned char)v); - - } /* if(triple) */ - - if (has_alpha) { - v = *alpha + adjustA; - ++alpha; - if (v > 65535) { - v = 65535; - } else if (v < 0) { - v = 0; - } - - /* netpbm: */ - fprintf(fdest, "%c%c", (unsigned char)(v >> 8), (unsigned char)v); - } - continue; - - } /* if(two) */ - - /* prec <= 8: */ - v = *red++; - if (v > 255) { - v = 255; - } else if (v < 0) { - v = 0; - } - - fprintf(fdest, "%c", (unsigned char)v); - if (triple) { - v = *green++; - if (v > 255) { - v = 255; - } else if (v < 0) { - v = 0; - } - - fprintf(fdest, "%c", (unsigned char)v); - v = *blue++; - if (v > 255) { - v = 255; - } else if (v < 0) { - v = 0; - } - - fprintf(fdest, "%c", (unsigned char)v); - } - if (has_alpha) { - v = *alpha++; - if (v > 255) { - v = 255; - } else if (v < 0) { - v = 0; - } - - fprintf(fdest, "%c", (unsigned char)v); - } - } /* for(i */ - - fclose(fdest); - return 0; - } - - /* YUV or MONO: */ - - if (image->numcomps > ncomp) { - fprintf(stderr, "WARNING -> [PGM file] Only the first component\n"); - fprintf(stderr, " is written to the file\n"); - } - destname = (char *)malloc(strlen(outfile) + 8); - if (destname == NULL) { - fprintf(stderr, "imagetopnm: memory out\n"); - return 1; - } - for (compno = 0; compno < ncomp; compno++) { - if (ncomp > 1) { - /*sprintf(destname, "%d.%s", compno, outfile);*/ - const size_t olen = strlen(outfile); - const size_t dotpos = olen - 4; - - strncpy(destname, outfile, dotpos); - sprintf(destname + dotpos, "_%u.pgm", compno); - } else { - sprintf(destname, "%s", outfile); - } - - fdest = fopen(destname, "wb"); - if (!fdest) { - fprintf(stderr, "ERROR -> failed to open %s for writing\n", destname); - free(destname); - return 1; - } - wr = (int)image->comps[compno].w; - hr = (int)image->comps[compno].h; - prec = (int)image->comps[compno].prec; - max = (1 << prec) - 1; - - fprintf(fdest, "P5\n#OpenJPEG-%s\n%d %d\n%d\n", opj_version(), wr, hr, max); - - red = image->comps[compno].data; - - if (!red) { - fclose(fdest); - continue; - } - - adjustR = - (image->comps[compno].sgnd ? 1 << (image->comps[compno].prec - 1) : 0); - - if (prec > 8) { - for (i = 0; i < wr * hr; i++) { - v = *red + adjustR; - ++red; - if (v > 65535) { - v = 65535; - } else if (v < 0) { - v = 0; - } - - /* netpbm: */ - fprintf(fdest, "%c%c", (unsigned char)(v >> 8), (unsigned char)v); - - if (has_alpha) { - v = *alpha++; - if (v > 65535) { - v = 65535; - } else if (v < 0) { - v = 0; - } - - /* netpbm: */ - fprintf(fdest, "%c%c", (unsigned char)(v >> 8), (unsigned char)v); - } - } /* for(i */ - } else { /* prec <= 8 */ - for (i = 0; i < wr * hr; ++i) { - v = *red + adjustR; - ++red; - if (v > 255) { - v = 255; - } else if (v < 0) { - v = 0; - } - fprintf(fdest, "%c", (unsigned char)v); - } - } - fclose(fdest); - } /* for (compno */ - free(destname); - - return 0; -} /* imagetopnm() */ diff --git a/oss-internship-2020/openjpeg/examples/convert_helper.h b/oss-internship-2020/openjpeg/examples/convert_helper.h deleted file mode 100644 index b795261..0000000 --- a/oss-internship-2020/openjpeg/examples/convert_helper.h +++ /dev/null @@ -1,8 +0,0 @@ -// imagetopnm and the two functions it calls internaly are copied from the -// library's tools; from openjpeg/src/bin/jp2/convert.c - -#include "openjp2_sapi.sapi.h" - -const char* opj_version(void); -static int are_comps_similar(opj_image_t* image); -int imagetopnm(opj_image_t* image, const char* outfile, int force_split); diff --git a/oss-internship-2020/openjpeg/examples/decompress_example.cc b/oss-internship-2020/openjpeg/examples/decompress_example.cc index bc01222..9b3db0e 100644 --- a/oss-internship-2020/openjpeg/examples/decompress_example.cc +++ b/oss-internship-2020/openjpeg/examples/decompress_example.cc @@ -21,8 +21,7 @@ #include #include -#include "absl/algorithm/container.h" -#include "convert_helper.h" +#include "convert_lib.h" #include "openjp2_sapi.sapi.h" class Openjp2SapiSandbox : public Openjp2Sandbox { From cfac8eb2d9abfbec77d622542e6da0e1bd4ba5ab Mon Sep 17 00:00:00 2001 From: Sandboxed API Team Date: Mon, 31 Aug 2020 00:13:27 -0700 Subject: [PATCH 57/82] Internal cleanup migrating StatusOr. PiperOrigin-RevId: 329250595 Change-Id: I0447d8154a57b1132981b116f02b4d5bceedfd4c --- sandboxed_api/examples/zlib/BUILD.bazel | 5 +++-- sandboxed_api/examples/zlib/main_zlib.cc | 1 + sandboxed_api/rpcchannel.cc | 1 + sandboxed_api/tools/clang_generator/emitter.cc | 1 + 4 files changed, 6 insertions(+), 2 deletions(-) diff --git a/sandboxed_api/examples/zlib/BUILD.bazel b/sandboxed_api/examples/zlib/BUILD.bazel index d318e48..3dc1c75 100644 --- a/sandboxed_api/examples/zlib/BUILD.bazel +++ b/sandboxed_api/examples/zlib/BUILD.bazel @@ -14,11 +14,11 @@ # Description: Sandboxed API reimplementation of zlib's zpipe.c example. -licenses(["notice"]) - load("//sandboxed_api/bazel:build_defs.bzl", "sapi_platform_copts") load("//sandboxed_api/bazel:sapi.bzl", "sapi_library") +licenses(["notice"]) + sapi_library( name = "zlib-sapi", srcs = [], @@ -42,6 +42,7 @@ cc_binary( ":zlib-sapi_embed", "//sandboxed_api:vars", "//sandboxed_api/util:flags", + "//sandboxed_api/util:statusor", "@com_google_absl//absl/base:core_headers", ], ) diff --git a/sandboxed_api/examples/zlib/main_zlib.cc b/sandboxed_api/examples/zlib/main_zlib.cc index 969ae33..7d94c10 100644 --- a/sandboxed_api/examples/zlib/main_zlib.cc +++ b/sandboxed_api/examples/zlib/main_zlib.cc @@ -20,6 +20,7 @@ #include #include "absl/base/macros.h" #include "sandboxed_api/util/flag.h" +#include "sandboxed_api/util/statusor.h" #include "sandboxed_api/examples/zlib/zlib-sapi.sapi.h" #include "sandboxed_api/examples/zlib/zlib-sapi_embed.h" #include "sandboxed_api/vars.h" diff --git a/sandboxed_api/rpcchannel.cc b/sandboxed_api/rpcchannel.cc index 72a9250..1aad74d 100644 --- a/sandboxed_api/rpcchannel.cc +++ b/sandboxed_api/rpcchannel.cc @@ -15,6 +15,7 @@ #include "sandboxed_api/rpcchannel.h" #include +#include "sandboxed_api/util/statusor.h" #include "absl/strings/str_cat.h" #include "absl/synchronization/mutex.h" #include "sandboxed_api/call.h" diff --git a/sandboxed_api/tools/clang_generator/emitter.cc b/sandboxed_api/tools/clang_generator/emitter.cc index 78b9a9a..5f30953 100644 --- a/sandboxed_api/tools/clang_generator/emitter.cc +++ b/sandboxed_api/tools/clang_generator/emitter.cc @@ -15,6 +15,7 @@ #include "sandboxed_api/tools/clang_generator/emitter.h" #include "absl/random/random.h" +#include "sandboxed_api/util/statusor.h" #include "absl/strings/ascii.h" #include "absl/strings/escaping.h" #include "absl/strings/match.h" From 9803d0549fac835093d7a743c217d5fe1362fb35 Mon Sep 17 00:00:00 2001 From: Bohdan Tyshchenko Date: Mon, 31 Aug 2020 02:19:00 -0700 Subject: [PATCH 58/82] Changed README, Bazel deps and different parts of code according to the review --- oss-internship-2020/guetzli/BUILD.bazel | 2 +- oss-internship-2020/guetzli/README.md | 10 ++-- oss-internship-2020/guetzli/WORKSPACE | 31 +++++----- .../guetzli/external/butteraugli.BUILD | 4 +- .../guetzli/external/guetzli.BUILD | 4 +- .../guetzli/external/jpeg.BUILD | 1 - .../guetzli/external/png.BUILD | 2 +- .../guetzli/external/zlib.BUILD | 40 +++++++++---- .../guetzli/guetzli_entry_points.cc | 12 ++-- oss-internship-2020/guetzli/guetzli_sandbox.h | 6 +- .../guetzli/guetzli_sandboxed.cc | 1 + .../guetzli/guetzli_transaction.cc | 40 +++++-------- .../guetzli/guetzli_transaction.h | 16 +++-- oss-internship-2020/guetzli/tests/BUILD.bazel | 10 ++-- .../guetzli/tests/guetzli_sapi_test.cc | 22 +++---- .../guetzli/tests/guetzli_transaction_test.cc | 58 +++++++++---------- 16 files changed, 132 insertions(+), 127 deletions(-) diff --git a/oss-internship-2020/guetzli/BUILD.bazel b/oss-internship-2020/guetzli/BUILD.bazel index 88a6da2..c1c40b2 100644 --- a/oss-internship-2020/guetzli/BUILD.bazel +++ b/oss-internship-2020/guetzli/BUILD.bazel @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -licenses(["unencumbered"]) # code authored by Google +licenses(["notice"]) load( "@com_google_sandboxed_api//sandboxed_api/bazel:sapi.bzl", diff --git a/oss-internship-2020/guetzli/README.md b/oss-internship-2020/guetzli/README.md index 97b3bc3..5fa8412 100644 --- a/oss-internship-2020/guetzli/README.md +++ b/oss-internship-2020/guetzli/README.md @@ -1,11 +1,11 @@ -# Guetzli Sandboxed +# Guetzli Sandbox This is an example implementation of a sandbox for the [Guetzli](https://github.com/google/guetzli) library using [Sandboxed API](https://github.com/google/sandboxed-api). Please read Guetzli's [documentation](https://github.com/google/guetzli#introduction) to learn more about it. ## Implementation details -Because Guetzli provides C++ API and SAPI requires functions to be `extern "C"` a wrapper library has been written for the compatibility. SAPI provides a Transaction class, which is a convenient way to create a wrapper for your sandboxed API that handles internal errors. Original Guetzli has command-line utility to encode images, so fully compatible utility that uses sandboxed Guetzli is provided. +Because Guetzli provides a C++ API and SAPI requires functions to be `extern "C"`, a wrapper library has been written for the compatibility. SAPI provides a Transaction class, which is a convenient way to create a wrapper for your sandboxed API that handles internal errors. The original Guetzli has a command-line utility to encode images, so a fully compatible utility that uses sandboxed Guetzli is provided. -Wrapper around Guetzli uses file descriptors to pass data to the sandbox. This approach restricts the sandbox from using open() syscall and also helps to prevent making copies of data, because you need to synchronize it between processes. +The wrapper around Guetzli uses file descriptors to pass data to the sandbox. This approach restricts the sandbox from using the `open()` syscall and also helps to prevent making copies of data, because you need to synchronize it between processes. ## Build Guetzli Sandboxed Right now Sandboxed API support only Linux systems, so you need one to build it. Guetzli sandboxed uses [Bazel](https://bazel.build/) as a build system so you need to [install it](https://docs.bazel.build/versions/3.4.0/install.html) before building. @@ -13,7 +13,7 @@ Right now Sandboxed API support only Linux systems, so you need one to build it. To build Guetzli sandboxed encoding utility you can use this command: `bazel build //:guetzli_sandboxed` -Than you can use it in this way: +Then you can use it in this way: ``` guetzli_sandboxed [--quality Q] [--verbose] original.png output.jpg guetzli_sandboxed [--quality Q] [--verbose] original.jpg output.jpg @@ -25,7 +25,7 @@ There are two different sets of unit tests which demonstrate how to use differen * `tests/guetzli_sapi_test.cc` - example usage of Guetzli sandboxed API. * `tests/guetzli_transaction_test.cc` - example usage of Guetzli transaction. -To run tests use following command: +To run tests use the following command: `bazel test ...` Also, there is an example of custom security policy for your sandbox in diff --git a/oss-internship-2020/guetzli/WORKSPACE b/oss-internship-2020/guetzli/WORKSPACE index 7f2b667..c952173 100644 --- a/oss-internship-2020/guetzli/WORKSPACE +++ b/oss-internship-2020/guetzli/WORKSPACE @@ -71,14 +71,6 @@ http_archive( url = "http://github.com/glennrp/libpng/archive/v1.2.57.zip", ) -http_archive( - name = "zlib_archive", - build_file = "zlib.BUILD", - sha256 = "8d7e9f698ce48787b6e1c67e6bff79e487303e66077e25cb9784ac8835978017", - strip_prefix = "zlib-1.2.10", - url = "http://zlib.net/fossils/zlib-1.2.10.tar.gz", -) - http_archive( name = "jpeg_archive", url = "http://www.ijg.org/files/jpegsrc.v9b.tar.gz", @@ -87,9 +79,22 @@ http_archive( build_file = "jpeg.BUILD", ) -maybe( - git_repository, - name = "googletest", - remote = "https://github.com/google/googletest", - tag = "release-1.10.0", +http_archive( + name = "net_zlib", + build_file = "zlib.BUILD", + sha256 = "c3e5e9fdd5004dcb542feda5ee4f0ff0744628baf8ed2dd5d66f8ca1197cb1a1", # 2020-04-23 + strip_prefix = "zlib-1.2.11", + urls = [ + "https://mirror.bazel.build/zlib.net/zlib-1.2.11.tar.gz", + "https://www.zlib.net/zlib-1.2.11.tar.gz", + ], ) + +# GoogleTest/GoogleMock +maybe( + http_archive, + name = "com_google_googletest", + sha256 = "a6ab7c7d6fd4dd727f6012b5d85d71a73d3aa1274f529ecd4ad84eb9ec4ff767", # 2020-04-16 + strip_prefix = "googletest-dcc92d0ab6c4ce022162a23566d44f673251eee4", + urls = ["https://github.com/google/googletest/archive/dcc92d0ab6c4ce022162a23566d44f673251eee4.zip"], +) \ No newline at end of file diff --git a/oss-internship-2020/guetzli/external/butteraugli.BUILD b/oss-internship-2020/guetzli/external/butteraugli.BUILD index 9058593..d61eb35 100644 --- a/oss-internship-2020/guetzli/external/butteraugli.BUILD +++ b/oss-internship-2020/guetzli/external/butteraugli.BUILD @@ -12,10 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. -licenses(["unencumbered"]) # code authored by Google +licenses(["notice"]) cc_library( - name = "butteraugli_lib", + name = "butteraugli", srcs = [ "butteraugli/butteraugli.cc", "butteraugli/butteraugli.h", diff --git a/oss-internship-2020/guetzli/external/guetzli.BUILD b/oss-internship-2020/guetzli/external/guetzli.BUILD index d2d4f32..454ec62 100644 --- a/oss-internship-2020/guetzli/external/guetzli.BUILD +++ b/oss-internship-2020/guetzli/external/guetzli.BUILD @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -licenses(["unencumbered"]) # code authored by Google +licenses(["notice"]) cc_library( name = "guetzli_lib", @@ -27,6 +27,6 @@ cc_library( copts = [ "-Wno-sign-compare" ], visibility= [ "//visibility:public" ], deps = [ - "@butteraugli//:butteraugli_lib", + "@butteraugli//:butteraugli", ], ) diff --git a/oss-internship-2020/guetzli/external/jpeg.BUILD b/oss-internship-2020/guetzli/external/jpeg.BUILD index 71eb87b..92c9ddc 100644 --- a/oss-internship-2020/guetzli/external/jpeg.BUILD +++ b/oss-internship-2020/guetzli/external/jpeg.BUILD @@ -1,4 +1,3 @@ - # Description: # The Independent JPEG Group's JPEG runtime library. diff --git a/oss-internship-2020/guetzli/external/png.BUILD b/oss-internship-2020/guetzli/external/png.BUILD index 9ff982b..4b061f5 100644 --- a/oss-internship-2020/guetzli/external/png.BUILD +++ b/oss-internship-2020/guetzli/external/png.BUILD @@ -29,5 +29,5 @@ cc_library( includes = ["."], linkopts = ["-lm"], visibility = ["//visibility:public"], - deps = ["@zlib_archive//:zlib"], + deps = ["@net_zlib//:zlib"], ) diff --git a/oss-internship-2020/guetzli/external/zlib.BUILD b/oss-internship-2020/guetzli/external/zlib.BUILD index edb77fd..ec4fff0 100644 --- a/oss-internship-2020/guetzli/external/zlib.BUILD +++ b/oss-internship-2020/guetzli/external/zlib.BUILD @@ -1,6 +1,18 @@ -package(default_visibility = ["//visibility:public"]) +# Copyright 2019 Google LLC +# +# 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. -licenses(["notice"]) # BSD/MIT-like license (for zlib) +licenses(["notice"]) cc_library( name = "zlib", @@ -8,29 +20,37 @@ cc_library( "adler32.c", "compress.c", "crc32.c", - "crc32.h", "deflate.c", - "deflate.h", "gzclose.c", - "gzguts.h", "gzlib.c", "gzread.c", "gzwrite.c", "infback.c", "inffast.c", - "inffast.h", - "inffixed.h", "inflate.c", "inflate.h", "inftrees.c", - "inftrees.h", "trees.c", "trees.h", "uncompr.c", - "zconf.h", "zutil.c", - "zutil.h", ], hdrs = ["zlib.h"], + # Use -Dverbose=-1 to turn off zlib's trace logging. (#3280) + copts = [ + "-w", + "-Dverbose=-1", + ], includes = ["."], + textual_hdrs = [ + "crc32.h", + "deflate.h", + "gzguts.h", + "inffast.h", + "inffixed.h", + "inftrees.h", + "zconf.h", + "zutil.h", + ], + visibility = ["//visibility:public"], ) diff --git a/oss-internship-2020/guetzli/guetzli_entry_points.cc b/oss-internship-2020/guetzli/guetzli_entry_points.cc index 7368928..ea8dbcd 100644 --- a/oss-internship-2020/guetzli/guetzli_entry_points.cc +++ b/oss-internship-2020/guetzli/guetzli_entry_points.cc @@ -75,8 +75,8 @@ sapi::StatusOr ReadFromFd(int fd) { } sapi::StatusOr PrepareDataForProcessing( - const ProcessingParams* processing_params) { - sapi::StatusOr input = ReadFromFd(processing_params->remote_fd); + const ProcessingParams& processing_params) { + sapi::StatusOr input = ReadFromFd(processing_params.remote_fd); if (!input.ok()) { return input.status(); @@ -84,11 +84,11 @@ sapi::StatusOr PrepareDataForProcessing( guetzli::Params guetzli_params; guetzli_params.butteraugli_target = static_cast( - guetzli::ButteraugliScoreForQuality(processing_params->quality)); + guetzli::ButteraugliScoreForQuality(processing_params.quality)); guetzli::ProcessStats stats; - if (processing_params->verbose) { + if (processing_params.verbose) { stats.debug_output_file = stderr; } @@ -234,7 +234,7 @@ bool CheckMemoryLimitExceeded(int memlimit_mb, int xsize, int ysize) { extern "C" bool ProcessJpeg(const ProcessingParams* processing_params, sapi::LenValStruct* output) { - auto processing_data = PrepareDataForProcessing(processing_params); + auto processing_data = PrepareDataForProcessing(*processing_params); if (!processing_data.ok()) { std::cerr << processing_data.status().ToString() << std::endl; @@ -269,7 +269,7 @@ extern "C" bool ProcessJpeg(const ProcessingParams* processing_params, extern "C" bool ProcessRgb(const ProcessingParams* processing_params, sapi::LenValStruct* output) { - auto processing_data = PrepareDataForProcessing(processing_params); + auto processing_data = PrepareDataForProcessing(*processing_params); if (!processing_data.ok()) { std::cerr << processing_data.status().ToString() << std::endl; diff --git a/oss-internship-2020/guetzli/guetzli_sandbox.h b/oss-internship-2020/guetzli/guetzli_sandbox.h index d1da1f8..712b012 100644 --- a/oss-internship-2020/guetzli/guetzli_sandbox.h +++ b/oss-internship-2020/guetzli/guetzli_sandbox.h @@ -19,8 +19,7 @@ #include "guetzli_sapi.sapi.h" -namespace guetzli { -namespace sandbox { +namespace guetzli::sandbox { class GuetzliSapiSandbox : public GuetzliSandbox { public: @@ -43,7 +42,6 @@ class GuetzliSapiSandbox : public GuetzliSandbox { } }; -} // namespace sandbox -} // namespace guetzli +} // namespace guetzli::sandbox #endif // GUETZLI_SANDBOXED_GUETZLI_SANDBOX_H_ diff --git a/oss-internship-2020/guetzli/guetzli_sandboxed.cc b/oss-internship-2020/guetzli/guetzli_sandboxed.cc index 6628f3e..6c53bf3 100644 --- a/oss-internship-2020/guetzli/guetzli_sandboxed.cc +++ b/oss-internship-2020/guetzli/guetzli_sandboxed.cc @@ -19,6 +19,7 @@ #include #include +#include #include "sandboxed_api/sandbox2/util/fileops.h" #include "sandboxed_api/util/statusor.h" diff --git a/oss-internship-2020/guetzli/guetzli_transaction.cc b/oss-internship-2020/guetzli/guetzli_transaction.cc index 8fd1db4..deaa3e9 100644 --- a/oss-internship-2020/guetzli/guetzli_transaction.cc +++ b/oss-internship-2020/guetzli/guetzli_transaction.cc @@ -22,8 +22,7 @@ #include #include -namespace guetzli { -namespace sandbox { +namespace guetzli::sandbox { absl::Status GuetzliTransaction::Main() { sapi::v::Fd in_fd(open(params_.in_file, O_RDONLY)); @@ -53,22 +52,17 @@ absl::Status GuetzliTransaction::Main() { }; auto result = image_type_ == ImageType::kJpeg ? - api.ProcessJpeg(processing_params.PtrBefore(), output.PtrBoth()) : - api.ProcessRgb(processing_params.PtrBefore(), output.PtrBoth()); + api.ProcessJpeg(processing_params.PtrBefore(), output.PtrBefore()) : + api.ProcessRgb(processing_params.PtrBefore(), output.PtrBefore()); if (!result.value_or(false)) { - std::stringstream error_stream; - error_stream << "Error processing " - << (image_type_ == ImageType::kJpeg ? "jpeg" : "rgb") << " data" - << std::endl; - return absl::FailedPreconditionError( - error_stream.str() + absl::StrCat("Error processing ", + (image_type_ == ImageType::kJpeg ? "jpeg" : "rgb"), " data") ); } sapi::v::Fd out_fd(open(".", O_TMPFILE | O_RDWR, S_IRUSR | S_IWUSR)); - if (out_fd.GetValue() < 0) { return absl::FailedPreconditionError( "Error creating temp output file" @@ -83,7 +77,7 @@ absl::Status GuetzliTransaction::Main() { } auto write_result = api.WriteDataToFd(out_fd.GetRemoteFd(), - output.PtrBefore()); + output.PtrNone()); if (!write_result.value_or(false)) { return absl::FailedPreconditionError( @@ -99,20 +93,19 @@ absl::Status GuetzliTransaction::Main() { absl::Status GuetzliTransaction::LinkOutFile(int out_fd) const { if (access(params_.out_file, F_OK) != -1) { if (remove(params_.out_file) < 0) { - std::stringstream error; - error << "Error deleting existing output file: " << params_.out_file; - return absl::FailedPreconditionError(error.str()); + return absl::FailedPreconditionError( + absl::StrCat("Error deleting existing output file: ", params_.out_file) + ); } - } + } - std::stringstream path; - path << "/proc/self/fd/" << out_fd; + std::string path = absl::StrCat("/proc/self/fd/", out_fd); - if (linkat(AT_FDCWD, path.str().c_str(), AT_FDCWD, params_.out_file, + if (linkat(AT_FDCWD, path.c_str(), AT_FDCWD, params_.out_file, AT_SYMLINK_FOLLOW) < 0) { - std::stringstream error; - error << "Error linking: " << params_.out_file; - return absl::FailedPreconditionError(error.str()); + return absl::FailedPreconditionError( + absl::StrCat("Error linking: ", params_.out_file) + ); } return absl::OkStatus(); @@ -140,5 +133,4 @@ sapi::StatusOr GuetzliTransaction::GetImageTypeFromFd(int fd) const { ImageType::kPng : ImageType::kJpeg; } -} // namespace sandbox -} // namespace guetzli +} // namespace guetzli::sandbox diff --git a/oss-internship-2020/guetzli/guetzli_transaction.h b/oss-internship-2020/guetzli/guetzli_transaction.h index c9c0d7d..4e5b25c 100644 --- a/oss-internship-2020/guetzli/guetzli_transaction.h +++ b/oss-internship-2020/guetzli/guetzli_transaction.h @@ -22,8 +22,7 @@ #include "guetzli_sandbox.h" -namespace guetzli { -namespace sandbox { +namespace guetzli::sandbox { enum class ImageType { kJpeg, @@ -42,12 +41,12 @@ struct TransactionParams { // Create a new one for each processing operation class GuetzliTransaction : public sapi::Transaction { public: - GuetzliTransaction(TransactionParams params, int retry_count = 0) - : sapi::Transaction(std::make_unique()) - , params_(std::move(params)) + explicit GuetzliTransaction(TransactionParams params, int retry_count = 0) + : sapi::Transaction(std::make_unique()), + params_(std::move(params)) { - sapi::Transaction::set_retry_count(retry_count); - sapi::Transaction::SetTimeLimit(0); // Infinite time limit + set_retry_count(retry_count); + SetTimeLimit(absl::InfiniteDuration()); } private: @@ -60,7 +59,6 @@ class GuetzliTransaction : public sapi::Transaction { ImageType image_type_ = ImageType::kJpeg; }; -} // namespace sandbox -} // namespace guetzli +} // namespace guetzli::sandbox #endif // GUETZLI_SANDBOXED_GUETZLI_TRANSACTION_H_ diff --git a/oss-internship-2020/guetzli/tests/BUILD.bazel b/oss-internship-2020/guetzli/tests/BUILD.bazel index a59237e..36f618d 100644 --- a/oss-internship-2020/guetzli/tests/BUILD.bazel +++ b/oss-internship-2020/guetzli/tests/BUILD.bazel @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -licenses(["unencumbered"]) # code authored by Google +licenses(["notice"]) cc_test( name = "transaction_tests", @@ -20,10 +20,10 @@ cc_test( visibility=["//visibility:public"], deps = [ "//:guetzli_sapi", - "@googletest//:gtest_main" + "@com_google_googletest//:gtest_main", ], size = "large", - data = glob(["testdata/*"]) + data = glob(["testdata/*"]), ) cc_test( @@ -32,8 +32,8 @@ cc_test( visibility=["//visibility:public"], deps = [ "//:guetzli_sapi", - "@googletest//:gtest_main" + "@com_google_googletest//:gtest_main", ], size = "large", - data = glob(["testdata/*"]) + data = glob(["testdata/*"]), ) diff --git a/oss-internship-2020/guetzli/tests/guetzli_sapi_test.cc b/oss-internship-2020/guetzli/tests/guetzli_sapi_test.cc index 66f2ef6..433c4a1 100644 --- a/oss-internship-2020/guetzli/tests/guetzli_sapi_test.cc +++ b/oss-internship-2020/guetzli/tests/guetzli_sapi_test.cc @@ -28,24 +28,22 @@ #include "guetzli_sandbox.h" -namespace guetzli { -namespace sandbox { -namespace tests { +namespace guetzli::sandbox::tests { namespace { -constexpr const char* kInPngFilename = "bees.png"; -constexpr const char* kInJpegFilename = "nature.jpg"; -constexpr const char* kPngReferenceFilename = "bees_reference.jpg"; -constexpr const char* kJpegReferenceFIlename = "nature_reference.jpg"; +constexpr absl::string_view kInPngFilename = "bees.png"; +constexpr absl::string_view kInJpegFilename = "nature.jpg"; +constexpr absl::string_view kPngReferenceFilename = "bees_reference.jpg"; +constexpr absl::string_view kJpegReferenceFIlename = "nature_reference.jpg"; constexpr int kDefaultQualityTarget = 95; constexpr int kDefaultMemlimitMb = 6000; -constexpr const char* kRelativePathToTestdata = +constexpr absl::string_view kRelativePathToTestdata = "/guetzli_sandboxed/tests/testdata/"; -std::string GetPathToInputFile(const char* filename) { +std::string GetPathToInputFile(absl::string_view filename) { return absl::StrCat(getenv("TEST_SRCDIR"), kRelativePathToTestdata, filename); } @@ -67,7 +65,7 @@ class GuetzliSapiTest : public ::testing::Test { protected: void SetUp() override { sandbox_ = std::make_unique(); - sandbox_->Init().IgnoreError(); + ASSERT_EQ(sandbox_->Init(), absl::OkStatus()); api_ = std::make_unique(sandbox_.get()); } @@ -129,6 +127,4 @@ TEST_F(GuetzliSapiTest, ProcessJpeg) { << "Processed data doesn't match reference output"; } -} // namespace tests -} // namespace sandbox -} // namespace guetzli +} // namespace guetzli::sandbox::tests diff --git a/oss-internship-2020/guetzli/tests/guetzli_transaction_test.cc b/oss-internship-2020/guetzli/tests/guetzli_transaction_test.cc index 4b33b61..875b328 100644 --- a/oss-internship-2020/guetzli/tests/guetzli_transaction_test.cc +++ b/oss-internship-2020/guetzli/tests/guetzli_transaction_test.cc @@ -26,18 +26,16 @@ #include "guetzli_transaction.h" -namespace guetzli { -namespace sandbox { -namespace tests { +namespace guetzli::sandbox::tests { namespace { -constexpr const char* kInPngFilename = "bees.png"; -constexpr const char* kInJpegFilename = "nature.jpg"; -constexpr const char* kOutJpegFilename = "out_jpeg.jpg"; -constexpr const char* kOutPngFilename = "out_png.png"; -constexpr const char* kPngReferenceFilename = "bees_reference.jpg"; -constexpr const char* kJpegReferenceFIlename = "nature_reference.jpg"; +constexpr absl::string_view kInPngFilename = "bees.png"; +constexpr absl::string_view kInJpegFilename = "nature.jpg"; +constexpr absl::string_view kOutJpegFilename = "out_jpeg.jpg"; +constexpr absl::string_view kOutPngFilename = "out_png.png"; +constexpr absl::string_view kPngReferenceFilename = "bees_reference.jpg"; +constexpr absl::string_view kJpegReferenceFIlename = "nature_reference.jpg"; constexpr int kPngExpectedSize = 38'625; constexpr int kJpegExpectedSize = 10'816; @@ -45,10 +43,10 @@ constexpr int kJpegExpectedSize = 10'816; constexpr int kDefaultQualityTarget = 95; constexpr int kDefaultMemlimitMb = 6000; -constexpr const char* kRelativePathToTestdata = +constexpr absl::string_view kRelativePathToTestdata = "/guetzli_sandboxed/tests/testdata/"; -std::string GetPathToFile(const char* filename) { +std::string GetPathToFile(absl::string_view filename) { return absl::StrCat(getenv("TEST_SRCDIR"), kRelativePathToTestdata, filename); } @@ -99,26 +97,26 @@ TEST(GuetzliTransactionTest, TestTransactionJpg) { }; { GuetzliTransaction transaction(std::move(params)); - auto result = transaction.Run(); + absl::Status result = transaction.Run(); ASSERT_TRUE(result.ok()) << result.ToString(); } - auto reference_data = ReadFromFile(GetPathToFile(kJpegReferenceFIlename)); + std::string reference_data = ReadFromFile( + GetPathToFile(kJpegReferenceFIlename)); FileRemover file_remover(out_path.c_str()); ASSERT_TRUE(file_remover.get() != -1) << "Error opening output file"; - auto output_size = lseek(file_remover.get(), 0, SEEK_END); + off_t output_size = lseek(file_remover.get(), 0, SEEK_END); ASSERT_EQ(reference_data.size(), output_size) << "Different sizes of reference and returned data"; ASSERT_EQ(lseek(file_remover.get(), 0, SEEK_SET), 0) << "Error repositioning out file"; - std::unique_ptr buf(new char[output_size]); - auto status = read(file_remover.get(), buf.get(), output_size); + std::string output; + output.resize(output_size); + ssize_t status = read(file_remover.get(), output.data(), output_size); ASSERT_EQ(status, output_size) << "Error reading data from temp output file"; - ASSERT_TRUE( - std::equal(buf.get(), buf.get() + output_size, reference_data.begin())) - << "Returned data doesn't match reference"; + ASSERT_EQ(output, reference_data) << "Returned data doesn't match reference"; } TEST(GuetzliTransactionTest, TestTransactionPng) { @@ -134,28 +132,26 @@ TEST(GuetzliTransactionTest, TestTransactionPng) { }; { GuetzliTransaction transaction(std::move(params)); - auto result = transaction.Run(); + absl::Status result = transaction.Run(); ASSERT_TRUE(result.ok()) << result.ToString(); } - auto reference_data = ReadFromFile(GetPathToFile(kPngReferenceFilename)); + std::string reference_data = ReadFromFile( + GetPathToFile(kPngReferenceFilename)); FileRemover file_remover(out_path.c_str()); ASSERT_TRUE(file_remover.get() != -1) << "Error opening output file"; - auto output_size = lseek(file_remover.get(), 0, SEEK_END); + off_t output_size = lseek(file_remover.get(), 0, SEEK_END); ASSERT_EQ(reference_data.size(), output_size) << "Different sizes of reference and returned data"; ASSERT_EQ(lseek(file_remover.get(), 0, SEEK_SET), 0) << "Error repositioning out file"; - - std::unique_ptr buf(new char[output_size]); - auto status = read(file_remover.get(), buf.get(), output_size); + + std::string output; + output.resize(output_size); + ssize_t status = read(file_remover.get(), output.data(), output_size); ASSERT_EQ(status, output_size) << "Error reading data from temp output file"; - ASSERT_TRUE( - std::equal(buf.get(), buf.get() + output_size, reference_data.begin())) - << "Returned data doesn't match refernce"; + ASSERT_EQ(output, reference_data) << "Returned data doesn't match refernce"; } -} // namespace tests -} // namespace sandbox -} // namespace guetzli +} // namespace guetzli::sandbox::tests From 531850afc219947ccc4fd3d5208b0049b55a42c6 Mon Sep 17 00:00:00 2001 From: doinachiroiu Date: Mon, 31 Aug 2020 11:17:15 +0000 Subject: [PATCH 59/82] Required changes resolved --- oss-internship-2020/pffft/README.md | 2 +- oss-internship-2020/pffft/main_pffft_sandboxed.cc | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/oss-internship-2020/pffft/README.md b/oss-internship-2020/pffft/README.md index a380ff4..194c6b6 100644 --- a/oss-internship-2020/pffft/README.md +++ b/oss-internship-2020/pffft/README.md @@ -4,7 +4,7 @@ Build System: CMake OS: Linux ### Check out the PFFFT library & CMake set up -`git clone https://github.com/doinachiroiu/sandboxed-api/tree/master/oss-internship-2020/pffft` +`git checkout -b master` `git submodule update --init --recursive` diff --git a/oss-internship-2020/pffft/main_pffft_sandboxed.cc b/oss-internship-2020/pffft/main_pffft_sandboxed.cc index 33116ed..ea42a26 100644 --- a/oss-internship-2020/pffft/main_pffft_sandboxed.cc +++ b/oss-internship-2020/pffft/main_pffft_sandboxed.cc @@ -86,6 +86,8 @@ void ShowOutput(const char* name, int n, int complex, float flops, float t0, } absl::Status PffftMain() { + LOG(INFO) << "Initializing sandbox...\n"; + PffftSapiSandbox sandbox; SAPI_RETURN_IF_ERROR(sandbox.Init()); @@ -201,8 +203,6 @@ int main(int argc, char* argv[]) { gflags::ParseCommandLineFlags(&argc, &argv, true); - LOG(INFO) << "Initializing sandbox...\n"; - if (absl::Status status = PffftMain(); !status.ok()) { LOG(ERROR) << "Initialization failed: " << status.ToString(); return EXIT_FAILURE; From 7e22952c424536dd950ccf32e42a9abc6ce0a6f7 Mon Sep 17 00:00:00 2001 From: doinachiroiu Date: Mon, 31 Aug 2020 11:48:19 +0000 Subject: [PATCH 60/82] Setup error handling modified --- .../pffft/main_pffft_sandboxed.cc | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/oss-internship-2020/pffft/main_pffft_sandboxed.cc b/oss-internship-2020/pffft/main_pffft_sandboxed.cc index ea42a26..51e271d 100644 --- a/oss-internship-2020/pffft/main_pffft_sandboxed.cc +++ b/oss-internship-2020/pffft/main_pffft_sandboxed.cc @@ -160,17 +160,11 @@ absl::Status PffftMain() { // PFFFT benchmark { - sapi::StatusOr s = - api.pffft_new_setup(n, complex ? PFFFT_COMPLEX : PFFFT_REAL); + SAPI_ASSIGN_OR_RETURN( + PFFFT_Setup *s, + api.pffft_new_setup(n, complex ? PFFFT_COMPLEX : PFFFT_REAL)); - LOG(INFO) << "Setup status is: " << s.status().ToString(); - - if (!s.ok()) { - printf("Sandbox failed.\n"); - return s.status(); - } - - sapi::v::RemotePtr s_reg(s.value()); + sapi::v::RemotePtr s_reg(s); t0 = UclockSec(); for (int iter = 0; iter < max_iter; ++iter) { @@ -192,7 +186,7 @@ absl::Status PffftMain() { LOG(INFO) << "n = " << n << " SUCCESSFULLY"; } } - } + } return absl::OkStatus(); } From 1c833d6f25ae4763eb1e4fb4401f25d2bdc6ef40 Mon Sep 17 00:00:00 2001 From: Sandboxed API Team Date: Mon, 31 Aug 2020 08:13:00 -0700 Subject: [PATCH 61/82] Internal cleanup migrating StatusOr. PiperOrigin-RevId: 329304527 Change-Id: Id6c141272df54c4e165829d690f9f5b2e9ee90cc --- sandboxed_api/sandbox2/BUILD.bazel | 1 + sandboxed_api/sandbox2/buffer.cc | 1 + sandboxed_api/sandbox2/network_proxy/BUILD.bazel | 1 + sandboxed_api/sandbox2/network_proxy/server.cc | 1 + sandboxed_api/sandbox2/policybuilder.cc | 2 ++ sandboxed_api/sandbox2/policybuilder_test.cc | 1 + sandboxed_api/sandbox2/sandbox2.cc | 1 + sandboxed_api/sandbox2/util/maps_parser.cc | 1 + sandboxed_api/sandbox2/util/temp_file.cc | 1 + 9 files changed, 10 insertions(+) diff --git a/sandboxed_api/sandbox2/BUILD.bazel b/sandboxed_api/sandbox2/BUILD.bazel index 09dbab4..ad415c2 100644 --- a/sandboxed_api/sandbox2/BUILD.bazel +++ b/sandboxed_api/sandbox2/BUILD.bazel @@ -803,6 +803,7 @@ cc_test( ":testing", "//sandboxed_api/sandbox2/util:bpf_helper", "//sandboxed_api/util:status_matchers", + "//sandboxed_api/util:statusor", "@com_google_absl//absl/memory", "@com_google_absl//absl/status", "@com_google_absl//absl/strings", diff --git a/sandboxed_api/sandbox2/buffer.cc b/sandboxed_api/sandbox2/buffer.cc index ea5f808..9c39339 100644 --- a/sandboxed_api/sandbox2/buffer.cc +++ b/sandboxed_api/sandbox2/buffer.cc @@ -21,6 +21,7 @@ #include #include "absl/memory/memory.h" +#include "sandboxed_api/util/statusor.h" #include "absl/strings/str_cat.h" #include "sandboxed_api/sandbox2/util.h" #include "sandboxed_api/sandbox2/util/strerror.h" diff --git a/sandboxed_api/sandbox2/network_proxy/BUILD.bazel b/sandboxed_api/sandbox2/network_proxy/BUILD.bazel index 5935390..d489a53 100644 --- a/sandboxed_api/sandbox2/network_proxy/BUILD.bazel +++ b/sandboxed_api/sandbox2/network_proxy/BUILD.bazel @@ -29,6 +29,7 @@ cc_library( ":filtering", "//sandboxed_api/sandbox2:comms", "//sandboxed_api/sandbox2/util:fileops", + "//sandboxed_api/util:statusor", "@com_google_absl//absl/memory", "@com_google_absl//absl/strings", "@com_google_glog//:glog", diff --git a/sandboxed_api/sandbox2/network_proxy/server.cc b/sandboxed_api/sandbox2/network_proxy/server.cc index c183812..41f6f7e 100644 --- a/sandboxed_api/sandbox2/network_proxy/server.cc +++ b/sandboxed_api/sandbox2/network_proxy/server.cc @@ -26,6 +26,7 @@ #include #include "absl/memory/memory.h" +#include "sandboxed_api/util/statusor.h" #include "sandboxed_api/sandbox2/util/fileops.h" namespace sandbox2 { diff --git a/sandboxed_api/sandbox2/policybuilder.cc b/sandboxed_api/sandbox2/policybuilder.cc index 9eec379..d185631 100644 --- a/sandboxed_api/sandbox2/policybuilder.cc +++ b/sandboxed_api/sandbox2/policybuilder.cc @@ -15,6 +15,7 @@ #include "sandboxed_api/sandbox2/policybuilder.h" #include // For TCGETS + #if defined(__x86_64__) #include #endif @@ -34,6 +35,7 @@ #include #include "absl/memory/memory.h" +#include "sandboxed_api/util/statusor.h" #include "absl/strings/escaping.h" #include "absl/strings/match.h" #include "sandboxed_api/sandbox2/namespace.h" diff --git a/sandboxed_api/sandbox2/policybuilder_test.cc b/sandboxed_api/sandbox2/policybuilder_test.cc index a86befd..ca13614 100644 --- a/sandboxed_api/sandbox2/policybuilder_test.cc +++ b/sandboxed_api/sandbox2/policybuilder_test.cc @@ -24,6 +24,7 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" #include "absl/memory/memory.h" +#include "sandboxed_api/util/statusor.h" #include "absl/strings/match.h" #include "absl/strings/str_cat.h" #include "absl/strings/str_split.h" diff --git a/sandboxed_api/sandbox2/sandbox2.cc b/sandboxed_api/sandbox2/sandbox2.cc index 559bceb..86c06dc 100644 --- a/sandboxed_api/sandbox2/sandbox2.cc +++ b/sandboxed_api/sandbox2/sandbox2.cc @@ -21,6 +21,7 @@ #include #include "absl/memory/memory.h" +#include "sandboxed_api/util/statusor.h" #include "absl/time/time.h" #include "sandboxed_api/sandbox2/monitor.h" #include "sandboxed_api/sandbox2/result.h" diff --git a/sandboxed_api/sandbox2/util/maps_parser.cc b/sandboxed_api/sandbox2/util/maps_parser.cc index d3040cc..cf0955f 100644 --- a/sandboxed_api/sandbox2/util/maps_parser.cc +++ b/sandboxed_api/sandbox2/util/maps_parser.cc @@ -15,6 +15,7 @@ #include "sandboxed_api/sandbox2/util/maps_parser.h" #include "absl/status/status.h" +#include "sandboxed_api/util/statusor.h" #include "absl/strings/str_split.h" namespace sandbox2 { diff --git a/sandboxed_api/sandbox2/util/temp_file.cc b/sandboxed_api/sandbox2/util/temp_file.cc index 9caac66..602a18c 100644 --- a/sandboxed_api/sandbox2/util/temp_file.cc +++ b/sandboxed_api/sandbox2/util/temp_file.cc @@ -22,6 +22,7 @@ #include #include +#include "sandboxed_api/util/statusor.h" #include "absl/strings/str_cat.h" #include "sandboxed_api/sandbox2/util/fileops.h" #include "sandboxed_api/sandbox2/util/strerror.h" From 490bc569ed0fb0a27dfd726150b5220bc7125800 Mon Sep 17 00:00:00 2001 From: Katarzyna Miernikiewicz Date: Tue, 1 Sep 2020 11:06:12 +0000 Subject: [PATCH 62/82] changed copying files to patching --- oss-internship-2020/openjpeg/.gitignore | 2 - .../openjpeg/examples/CMakeLists.txt | 38 ++++++++----------- .../openjpeg/examples/convert.patch | 6 +-- .../openjpeg/examples/convert_h.patch | 6 +-- .../openjpeg/examples/decompress_example.cc | 2 +- 5 files changed, 22 insertions(+), 32 deletions(-) diff --git a/oss-internship-2020/openjpeg/.gitignore b/oss-internship-2020/openjpeg/.gitignore index cccbfce..567609b 100644 --- a/oss-internship-2020/openjpeg/.gitignore +++ b/oss-internship-2020/openjpeg/.gitignore @@ -1,3 +1 @@ build/ -examples/convert_lib.cc -examples/convert_lib.h diff --git a/oss-internship-2020/openjpeg/examples/CMakeLists.txt b/oss-internship-2020/openjpeg/examples/CMakeLists.txt index 77b4290..ca11b74 100644 --- a/oss-internship-2020/openjpeg/examples/CMakeLists.txt +++ b/oss-internship-2020/openjpeg/examples/CMakeLists.txt @@ -12,26 +12,25 @@ # See the License for the specific language governing permissions and # limitations under the License. -add_custom_target(patching_reverse_before - COMMAND cd ${PROJECT_SOURCE_DIR}/openjpeg/src/bin/jp2/ && patch -R --no-backup-if-mismatch -r /dev/null convert.c < ${PROJECT_SOURCE_DIR}/examples/convert.patch > /dev/null || (exit 0) - COMMAND cd ${PROJECT_SOURCE_DIR}/openjpeg/src/bin/jp2/ && patch -R --no-backup-if-mismatch -r /dev/null convert.h < ${PROJECT_SOURCE_DIR}/examples/convert_h.patch > /dev/null || (exit 0) - ) +# we need to use modified versions of some of the library tools + +file(MAKE_DIRECTORY "${PROJECT_BINARY_DIR}/gen_files") +file(COPY "${PROJECT_SOURCE_DIR}/openjpeg/src/bin/jp2/convert.c" DESTINATION "${PROJECT_BINARY_DIR}/gen_files/") +file(COPY "${PROJECT_SOURCE_DIR}/openjpeg/src/bin/jp2/convert.h" DESTINATION "${PROJECT_BINARY_DIR}/gen_files/") +file(COPY "${PROJECT_SOURCE_DIR}/examples/convert.patch" DESTINATION "${PROJECT_BINARY_DIR}/gen_files/") +file(COPY "${PROJECT_SOURCE_DIR}/examples/convert_h.patch" DESTINATION "${PROJECT_BINARY_DIR}/gen_files/") add_custom_command( - DEPENDS opj_decompress opj_compress opj_dump - OUTPUT ${PROJECT_SOURCE_DIR}/examples/convert_lib.cc ${PROJECT_SOURCE_DIR}/examples/convert_lib.h - COMMAND cd ${PROJECT_SOURCE_DIR}/openjpeg/src/bin/jp2/ && patch convert.c < ${PROJECT_SOURCE_DIR}/examples/convert.patch > /dev/null && cp convert.c ${PROJECT_SOURCE_DIR}/examples/convert_lib.cc - COMMAND cd ${PROJECT_SOURCE_DIR}/openjpeg/src/bin/jp2/ && patch convert.h < ${PROJECT_SOURCE_DIR}/examples/convert_h.patch > /dev/null && cp convert.h ${PROJECT_SOURCE_DIR}/examples/convert_lib.h - ) - -add_library(convert_helper STATIC - ${PROJECT_SOURCE_DIR}/examples/convert_lib.cc - ${PROJECT_SOURCE_DIR}/examples/convert_lib.h + OUTPUT ${PROJECT_BINARY_DIR}/gen_files/convert.cc ${PROJECT_BINARY_DIR}/gen_files/convert.h + COMMAND cd ${PROJECT_BINARY_DIR}/gen_files && patch < ${PROJECT_SOURCE_DIR}/examples/convert.patch > /dev/null + COMMAND cd ${PROJECT_BINARY_DIR}/gen_files && patch < ${PROJECT_SOURCE_DIR}/examples/convert_h.patch > /dev/null + COMMAND mv ${PROJECT_BINARY_DIR}/gen_files/convert.c ${PROJECT_BINARY_DIR}/gen_files/convert.cc ) -add_dependencies(opj_decompress patching_reverse_before) -add_dependencies(opj_compress patching_reverse_before) -add_dependencies(opj_dump patching_reverse_before) +add_library(convert_helper STATIC + ${PROJECT_BINARY_DIR}/gen_files/convert.cc + ${PROJECT_BINARY_DIR}/gen_files/convert.h +) add_executable(decompress_sandboxed decompress_example.cc @@ -47,10 +46,3 @@ target_link_libraries(convert_helper PRIVATE openjp2_sapi sapi::sapi ) - -add_custom_target(patching_reverse ALL - COMMAND cd ${PROJECT_SOURCE_DIR}/openjpeg/src/bin/jp2/ && patch -R convert.c < ${PROJECT_SOURCE_DIR}/examples/convert.patch > /dev/null - COMMAND cd ${PROJECT_SOURCE_DIR}/openjpeg/src/bin/jp2/ && patch -R convert.h < ${PROJECT_SOURCE_DIR}/examples/convert_h.patch > /dev/null - ) - -add_dependencies(patching_reverse decompress_sandboxed) diff --git a/oss-internship-2020/openjpeg/examples/convert.patch b/oss-internship-2020/openjpeg/examples/convert.patch index 416c6b7..15bc1a3 100644 --- a/oss-internship-2020/openjpeg/examples/convert.patch +++ b/oss-internship-2020/openjpeg/examples/convert.patch @@ -1,5 +1,5 @@ --- convert.c 2020-08-27 15:46:32.911584344 +0000 -+++ ../../../../examples/convert_helper.cc 2020-08-27 15:10:52.695231032 +0000 ++++ convert_helper.cc 2020-08-27 15:10:52.695231032 +0000 @@ -35,2605 +35,311 @@ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. @@ -62,7 +62,7 @@ - } - component->prec = precision; -} -+// copies of a few library tools ++// patched versions of a few library tools -/* Component precision scaling */ -static void scale_component_up(opj_image_comp_t* component, @@ -181,7 +181,7 @@ -{ - OPJ_SIZE_T i; - const OPJ_INT32* pSrc0 = pSrc[0]; -+#include "convert_lib.h" ++#include "convert.h" - for (i = 0; i < length; i++) { - pDst[i] = pSrc0[i] + adjust; diff --git a/oss-internship-2020/openjpeg/examples/convert_h.patch b/oss-internship-2020/openjpeg/examples/convert_h.patch index efeafc6..53f729c 100644 --- a/oss-internship-2020/openjpeg/examples/convert_h.patch +++ b/oss-internship-2020/openjpeg/examples/convert_h.patch @@ -1,5 +1,5 @@ --- convert.h 2020-08-27 15:46:45.028628305 +0000 -+++ ../../../../examples/convert_helper.h 2020-08-27 14:26:02.155455250 +0000 ++++ convert_helper.h 2020-08-27 14:26:02.155455250 +0000 @@ -1,126 +1,8 @@ -/* - * The copyright in this software is being made available under the 2-clauses @@ -39,8 +39,8 @@ - */ -#ifndef __J2K_CONVERT_H -#define __J2K_CONVERT_H -+// imagetopnm and the two functions it calls internaly are copied from the -+// library's tools; from openjpeg/src/bin/jp2/convert.c ++// imagetopnm and the two functions it calls internaly are patched ++// versions of the library's tools; from openjpeg/src/bin/jp2/convert.c -/**@name RAW component encoding parameters */ -/*@{*/ diff --git a/oss-internship-2020/openjpeg/examples/decompress_example.cc b/oss-internship-2020/openjpeg/examples/decompress_example.cc index 9b3db0e..68bddf8 100644 --- a/oss-internship-2020/openjpeg/examples/decompress_example.cc +++ b/oss-internship-2020/openjpeg/examples/decompress_example.cc @@ -21,7 +21,7 @@ #include #include -#include "convert_lib.h" +#include "gen_files/convert.h" #include "openjp2_sapi.sapi.h" class Openjp2SapiSandbox : public Openjp2Sandbox { From 23da55c19a903fca32252e8bda68a4d3d0fa1377 Mon Sep 17 00:00:00 2001 From: Sandboxed API Team Date: Wed, 2 Sep 2020 08:46:48 -0700 Subject: [PATCH 63/82] Internal BUILD refactoring PiperOrigin-RevId: 329720214 Change-Id: I25fbb94dea17db3bdca6438d17508fa304d9706f --- sandboxed_api/BUILD.bazel | 2 +- sandboxed_api/examples/zlib/BUILD.bazel | 2 +- sandboxed_api/examples/zlib/main_zlib.cc | 4 +- sandboxed_api/proto_helper.h | 6 +- sandboxed_api/rpcchannel.cc | 6 +- sandboxed_api/rpcchannel.h | 6 +- sandboxed_api/sandbox.cc | 2 +- sandboxed_api/sandbox.h | 2 +- sandboxed_api/sandbox2/BUILD.bazel | 16 +- sandboxed_api/sandbox2/buffer.cc | 6 +- sandboxed_api/sandbox2/buffer.h | 6 +- sandboxed_api/sandbox2/comms.cc | 2 +- .../examples/network_proxy/BUILD.bazel | 2 +- .../network_proxy/networkproxy_bin.cc | 8 +- sandboxed_api/sandbox2/forkserver.cc | 4 +- sandboxed_api/sandbox2/mounts.cc | 4 +- .../sandbox2/network_proxy/BUILD.bazel | 4 +- .../sandbox2/network_proxy/filtering.cc | 8 +- .../sandbox2/network_proxy/filtering.h | 4 +- .../sandbox2/network_proxy/server.cc | 4 +- sandboxed_api/sandbox2/policybuilder.cc | 8 +- sandboxed_api/sandbox2/policybuilder.h | 8 +- sandboxed_api/sandbox2/policybuilder_test.cc | 4 +- sandboxed_api/sandbox2/sandbox2.cc | 4 +- sandboxed_api/sandbox2/sandbox2.h | 4 +- sandboxed_api/sandbox2/util.cc | 4 +- sandboxed_api/sandbox2/util.h | 6 +- sandboxed_api/sandbox2/util/BUILD.bazel | 6 +- sandboxed_api/sandbox2/util/maps_parser.cc | 4 +- sandboxed_api/sandbox2/util/maps_parser.h | 4 +- sandboxed_api/sandbox2/util/minielf.cc | 22 +- sandboxed_api/sandbox2/util/minielf.h | 4 +- sandboxed_api/sandbox2/util/temp_file.cc | 8 +- sandboxed_api/sandbox2/util/temp_file.h | 8 +- .../tools/clang_generator/emitter.cc | 8 +- sandboxed_api/tools/clang_generator/emitter.h | 4 +- .../tools/clang_generator/generator.cc | 4 +- .../tools/clang_generator/generator.h | 4 +- sandboxed_api/tools/clang_generator/types.cc | 2 +- sandboxed_api/tools/clang_generator/types.h | 2 +- sandboxed_api/tools/generator2/code.py | 4 +- .../tools/generator2/code_test_util.py | 16 +- sandboxed_api/util/statusor_test.cc | 407 ------------------ sandboxed_api/var_proto.h | 4 +- 44 files changed, 120 insertions(+), 527 deletions(-) delete mode 100644 sandboxed_api/util/statusor_test.cc diff --git a/sandboxed_api/BUILD.bazel b/sandboxed_api/BUILD.bazel index fbe40b3..59ed83a 100644 --- a/sandboxed_api/BUILD.bazel +++ b/sandboxed_api/BUILD.bazel @@ -147,10 +147,10 @@ cc_library( ":var_type", "//sandboxed_api/sandbox2:comms", "//sandboxed_api/util:status", - "//sandboxed_api/util:statusor", "@com_google_absl//absl/base:core_headers", "@com_google_absl//absl/memory", "@com_google_absl//absl/status", + "@com_google_absl//absl/status:statusor", "@com_google_absl//absl/strings", "@com_google_absl//absl/strings:str_format", "@com_google_absl//absl/synchronization", diff --git a/sandboxed_api/examples/zlib/BUILD.bazel b/sandboxed_api/examples/zlib/BUILD.bazel index 3dc1c75..3716280 100644 --- a/sandboxed_api/examples/zlib/BUILD.bazel +++ b/sandboxed_api/examples/zlib/BUILD.bazel @@ -42,7 +42,7 @@ cc_binary( ":zlib-sapi_embed", "//sandboxed_api:vars", "//sandboxed_api/util:flags", - "//sandboxed_api/util:statusor", "@com_google_absl//absl/base:core_headers", + "@com_google_absl//absl/status:statusor", ], ) diff --git a/sandboxed_api/examples/zlib/main_zlib.cc b/sandboxed_api/examples/zlib/main_zlib.cc index 7d94c10..e6e2b2a 100644 --- a/sandboxed_api/examples/zlib/main_zlib.cc +++ b/sandboxed_api/examples/zlib/main_zlib.cc @@ -20,7 +20,7 @@ #include #include "absl/base/macros.h" #include "sandboxed_api/util/flag.h" -#include "sandboxed_api/util/statusor.h" +#include "absl/status/statusor.h" #include "sandboxed_api/examples/zlib/zlib-sapi.sapi.h" #include "sandboxed_api/examples/zlib/zlib-sapi_embed.h" #include "sandboxed_api/vars.h" @@ -48,7 +48,7 @@ int main(int argc, char** argv) { << status.message(); } - sapi::StatusOr ret; + absl::StatusOr ret; int flush; unsigned have; sapi::v::Struct strm; diff --git a/sandboxed_api/proto_helper.h b/sandboxed_api/proto_helper.h index b00a73e..63c4294 100644 --- a/sandboxed_api/proto_helper.h +++ b/sandboxed_api/proto_helper.h @@ -22,13 +22,13 @@ #include #include "absl/status/status.h" -#include "sandboxed_api/util/statusor.h" +#include "absl/status/statusor.h" #include "sandboxed_api/proto_arg.pb.h" namespace sapi { template -sapi::StatusOr> SerializeProto(const T& proto) { +absl::StatusOr> SerializeProto(const T& proto) { static_assert(std::is_base_of::value, "Template argument must be a proto message"); // Wrap protobuf in a envelope so that we know the name of the protobuf @@ -46,7 +46,7 @@ sapi::StatusOr> SerializeProto(const T& proto) { } template -sapi::StatusOr DeserializeProto(const char* data, size_t len) { +absl::StatusOr DeserializeProto(const char* data, size_t len) { static_assert(std::is_base_of::value, "Template argument must be a proto message"); ProtoArg envelope; diff --git a/sandboxed_api/rpcchannel.cc b/sandboxed_api/rpcchannel.cc index 1aad74d..66e1b2b 100644 --- a/sandboxed_api/rpcchannel.cc +++ b/sandboxed_api/rpcchannel.cc @@ -15,7 +15,7 @@ #include "sandboxed_api/rpcchannel.h" #include -#include "sandboxed_api/util/statusor.h" +#include "absl/status/statusor.h" #include "absl/strings/str_cat.h" #include "absl/synchronization/mutex.h" #include "sandboxed_api/call.h" @@ -36,7 +36,7 @@ absl::Status RPCChannel::Call(const FuncCall& call, uint32_t tag, FuncRet* ret, return absl::OkStatus(); } -sapi::StatusOr RPCChannel::Return(v::Type exp_type) { +absl::StatusOr RPCChannel::Return(v::Type exp_type) { uint32_t tag; uint64_t len; FuncRet ret; @@ -203,7 +203,7 @@ absl::Status RPCChannel::Close(int remote_fd) { return absl::OkStatus(); } -sapi::StatusOr RPCChannel::Strlen(void* str) { +absl::StatusOr RPCChannel::Strlen(void* str) { absl::MutexLock lock(&mutex_); if (!comms_->SendTLV(comms::kMsgStrlen, sizeof(str), reinterpret_cast(&str))) { diff --git a/sandboxed_api/rpcchannel.h b/sandboxed_api/rpcchannel.h index c54ef8e..4fec23b 100644 --- a/sandboxed_api/rpcchannel.h +++ b/sandboxed_api/rpcchannel.h @@ -18,7 +18,7 @@ #include #include "absl/status/status.h" -#include "sandboxed_api/util/statusor.h" +#include "absl/status/statusor.h" #include "absl/synchronization/mutex.h" #include "sandboxed_api/call.h" #include "sandboxed_api/sandbox2/comms.h" @@ -61,13 +61,13 @@ class RPCChannel { absl::Status Close(int remote_fd); // Returns length of a null-terminated c-style string (invokes strlen). - sapi::StatusOr Strlen(void* str); + absl::StatusOr Strlen(void* str); sandbox2::Comms* comms() const { return comms_; } private: // Receives the result after a call. - sapi::StatusOr Return(v::Type exp_type); + absl::StatusOr Return(v::Type exp_type); sandbox2::Comms* comms_; // Owned by sandbox2; absl::Mutex mutex_; diff --git a/sandboxed_api/sandbox.cc b/sandboxed_api/sandbox.cc index 18239a4..b18a1b0 100644 --- a/sandboxed_api/sandbox.cc +++ b/sandboxed_api/sandbox.cc @@ -391,7 +391,7 @@ absl::Status Sandbox::TransferFromSandboxee(v::Var* var) { return var->TransferFromSandboxee(GetRpcChannel(), pid()); } -sapi::StatusOr Sandbox::GetCString(const v::RemotePtr& str, +absl::StatusOr Sandbox::GetCString(const v::RemotePtr& str, uint64_t max_length) { if (!is_active()) { return absl::UnavailableError("Sandbox not active"); diff --git a/sandboxed_api/sandbox.h b/sandboxed_api/sandbox.h index 8b4ee6a..5d69eb0 100644 --- a/sandboxed_api/sandbox.h +++ b/sandboxed_api/sandbox.h @@ -102,7 +102,7 @@ class Sandbox { absl::Status TransferToSandboxee(v::Var* var); absl::Status TransferFromSandboxee(v::Var* var); - sapi::StatusOr GetCString(const v::RemotePtr& str, + absl::StatusOr GetCString(const v::RemotePtr& str, uint64_t max_length = 10ULL << 20 /* 10 MiB*/ ); diff --git a/sandboxed_api/sandbox2/BUILD.bazel b/sandboxed_api/sandbox2/BUILD.bazel index ad415c2..2bf7542 100644 --- a/sandboxed_api/sandbox2/BUILD.bazel +++ b/sandboxed_api/sandbox2/BUILD.bazel @@ -88,9 +88,9 @@ cc_library( ":regs", ":syscall", ":util", - "//sandboxed_api/util:statusor", "@com_google_absl//absl/memory", "@com_google_absl//absl/status", + "@com_google_absl//absl/status:statusor", "@com_google_absl//absl/strings", ], ) @@ -300,12 +300,12 @@ cc_library( "//sandboxed_api/util:flags", "//sandboxed_api/util:raw_logging", "//sandboxed_api/util:status", - "//sandboxed_api/util:statusor", "@com_google_absl//absl/base:core_headers", "@com_google_absl//absl/container:flat_hash_map", "@com_google_absl//absl/container:flat_hash_set", "@com_google_absl//absl/memory", "@com_google_absl//absl/status", + "@com_google_absl//absl/status:statusor", "@com_google_absl//absl/strings", "@com_google_absl//absl/strings:str_format", "@com_google_absl//absl/synchronization", @@ -374,9 +374,9 @@ cc_library( "//sandboxed_api/sandbox2/util:fileops", "//sandboxed_api/sandbox2/util:strerror", "//sandboxed_api/util:raw_logging", - "//sandboxed_api/util:statusor", "@com_google_absl//absl/memory", "@com_google_absl//absl/status", + "@com_google_absl//absl/status:statusor", "@com_google_absl//absl/strings", "@com_google_absl//absl/strings:str_format", "@org_kernel_libcap//:libcap", @@ -411,10 +411,10 @@ cc_library( "//sandboxed_api/sandbox2/util:strerror", "//sandboxed_api/util:raw_logging", "//sandboxed_api/util:status", - "//sandboxed_api/util:statusor", "@com_google_absl//absl/base:core_headers", "@com_google_absl//absl/container:flat_hash_set", "@com_google_absl//absl/status", + "@com_google_absl//absl/status:statusor", "@com_google_absl//absl/strings", "@com_google_protobuf//:protobuf", ], @@ -509,8 +509,8 @@ cc_library( "//sandboxed_api/sandbox2/util:fileops", "//sandboxed_api/sandbox2/util:strerror", "//sandboxed_api/util:raw_logging", - "//sandboxed_api/util:statusor", "@com_google_absl//absl/base:core_headers", + "@com_google_absl//absl/status:statusor", "@com_google_absl//absl/strings", "@com_google_absl//absl/strings:str_format", ], @@ -526,9 +526,9 @@ cc_library( ":util", "//sandboxed_api/sandbox2/util:strerror", "//sandboxed_api/util:status", - "//sandboxed_api/util:statusor", "@com_google_absl//absl/base:core_headers", "@com_google_absl//absl/memory", + "@com_google_absl//absl/status:statusor", "@com_google_absl//absl/strings", ], ) @@ -573,10 +573,10 @@ cc_library( "//sandboxed_api/util:raw_logging", "//sandboxed_api/util:status", "//sandboxed_api/util:status_proto", - "//sandboxed_api/util:statusor", "@com_google_absl//absl/base:core_headers", "@com_google_absl//absl/memory", "@com_google_absl//absl/status", + "@com_google_absl//absl/status:statusor", "@com_google_absl//absl/strings", "@com_google_absl//absl/strings:str_format", "@com_google_absl//absl/synchronization", @@ -803,9 +803,9 @@ cc_test( ":testing", "//sandboxed_api/sandbox2/util:bpf_helper", "//sandboxed_api/util:status_matchers", - "//sandboxed_api/util:statusor", "@com_google_absl//absl/memory", "@com_google_absl//absl/status", + "@com_google_absl//absl/status:statusor", "@com_google_absl//absl/strings", "@com_google_glog//:glog", "@com_google_googletest//:gtest_main", diff --git a/sandboxed_api/sandbox2/buffer.cc b/sandboxed_api/sandbox2/buffer.cc index 9c39339..074618d 100644 --- a/sandboxed_api/sandbox2/buffer.cc +++ b/sandboxed_api/sandbox2/buffer.cc @@ -21,7 +21,7 @@ #include #include "absl/memory/memory.h" -#include "sandboxed_api/util/statusor.h" +#include "absl/status/statusor.h" #include "absl/strings/str_cat.h" #include "sandboxed_api/sandbox2/util.h" #include "sandboxed_api/sandbox2/util/strerror.h" @@ -29,7 +29,7 @@ namespace sandbox2 { // Creates a new Buffer that is backed by the specified file descriptor. -sapi::StatusOr> Buffer::CreateFromFd(int fd) { +absl::StatusOr> Buffer::CreateFromFd(int fd) { auto buffer = absl::WrapUnique(new Buffer{}); struct stat stat_buf; @@ -54,7 +54,7 @@ sapi::StatusOr> Buffer::CreateFromFd(int fd) { // Creates a new Buffer of the specified size, backed by a temporary file that // will be immediately deleted. -sapi::StatusOr> Buffer::CreateWithSize(int64_t size) { +absl::StatusOr> Buffer::CreateWithSize(int64_t size) { int fd; if (!util::CreateMemFd(&fd)) { return absl::InternalError("Could not create buffer temp file"); diff --git a/sandboxed_api/sandbox2/buffer.h b/sandboxed_api/sandbox2/buffer.h index 72f042d..d613bcd 100644 --- a/sandboxed_api/sandbox2/buffer.h +++ b/sandboxed_api/sandbox2/buffer.h @@ -19,7 +19,7 @@ #include #include -#include "sandboxed_api/util/statusor.h" +#include "absl/status/statusor.h" namespace sandbox2 { @@ -37,11 +37,11 @@ class Buffer final { // Creates a new Buffer that is backed by the specified file descriptor. // The Buffer takes ownership of the descriptor and will close it when // destroyed. - static sapi::StatusOr> CreateFromFd(int fd); + static absl::StatusOr> CreateFromFd(int fd); // Creates a new Buffer of the specified size, backed by a temporary file that // will be immediately deleted. - static sapi::StatusOr> CreateWithSize(int64_t size); + static absl::StatusOr> CreateWithSize(int64_t size); // Returns a pointer to the buffer, which is read/write. uint8_t* data() const { return buf_; } diff --git a/sandboxed_api/sandbox2/comms.cc b/sandboxed_api/sandbox2/comms.cc index 3e61013..8a357b6 100644 --- a/sandboxed_api/sandbox2/comms.cc +++ b/sandboxed_api/sandbox2/comms.cc @@ -36,7 +36,7 @@ #include "google/protobuf/message.h" #include "absl/memory/memory.h" #include "absl/status/status.h" -#include "sandboxed_api/util/statusor.h" +#include "absl/status/statusor.h" #include "absl/strings/str_cat.h" #include "absl/strings/str_format.h" #include "absl/synchronization/mutex.h" diff --git a/sandboxed_api/sandbox2/examples/network_proxy/BUILD.bazel b/sandboxed_api/sandbox2/examples/network_proxy/BUILD.bazel index 480c078..fb07dd5 100644 --- a/sandboxed_api/sandbox2/examples/network_proxy/BUILD.bazel +++ b/sandboxed_api/sandbox2/examples/network_proxy/BUILD.bazel @@ -48,8 +48,8 @@ cc_binary( "//sandboxed_api/sandbox2/util:strerror", "//sandboxed_api/util:flags", "//sandboxed_api/util:status", - "//sandboxed_api/util:statusor", "@com_google_absl//absl/status", + "@com_google_absl//absl/status:statusor", "@com_google_absl//absl/strings:str_format", ], ) diff --git a/sandboxed_api/sandbox2/examples/network_proxy/networkproxy_bin.cc b/sandboxed_api/sandbox2/examples/network_proxy/networkproxy_bin.cc index b355522..84eb715 100644 --- a/sandboxed_api/sandbox2/examples/network_proxy/networkproxy_bin.cc +++ b/sandboxed_api/sandbox2/examples/network_proxy/networkproxy_bin.cc @@ -13,7 +13,7 @@ #include "sandboxed_api/util/flag.h" #include "absl/status/status.h" -#include "sandboxed_api/util/statusor.h" +#include "absl/status/statusor.h" #include "absl/strings/str_format.h" #include "sandboxed_api/sandbox2/client.h" #include "sandboxed_api/sandbox2/comms.h" @@ -58,7 +58,7 @@ absl::Status CommunicationTest(int sock) { return absl::OkStatus(); } -sapi::StatusOr CreateAddres(int port) { +absl::StatusOr CreateAddres(int port) { static struct sockaddr_in6 saddr {}; saddr.sin6_family = AF_INET6; saddr.sin6_port = htons(port); @@ -86,7 +86,7 @@ absl::Status ConnectWithHandler(int s, const struct sockaddr_in6& saddr) { return absl::OkStatus(); } -sapi::StatusOr ConnectToServer(int port) { +absl::StatusOr ConnectToServer(int port) { SAPI_ASSIGN_OR_RETURN(struct sockaddr_in6 saddr, CreateAddres(port)); sandbox2::file_util::fileops::FDCloser s(socket(AF_INET6, SOCK_STREAM, 0)); @@ -134,7 +134,7 @@ int main(int argc, char** argv) { return 2; } - sapi::StatusOr sock_s = ConnectToServer(port); + absl::StatusOr sock_s = ConnectToServer(port); if (!sock_s.ok()) { LOG(ERROR) << sock_s.status().message(); return 3; diff --git a/sandboxed_api/sandbox2/forkserver.cc b/sandboxed_api/sandbox2/forkserver.cc index d0b637c..dc6a283 100644 --- a/sandboxed_api/sandbox2/forkserver.cc +++ b/sandboxed_api/sandbox2/forkserver.cc @@ -36,7 +36,7 @@ #include "absl/memory/memory.h" #include "absl/status/status.h" -#include "sandboxed_api/util/statusor.h" +#include "absl/status/statusor.h" #include "absl/strings/match.h" #include "absl/strings/str_cat.h" #include "absl/strings/str_format.h" @@ -142,7 +142,7 @@ absl::Status SendPid(int signaling_fd) { return absl::OkStatus(); } -sapi::StatusOr ReceivePid(int signaling_fd) { +absl::StatusOr ReceivePid(int signaling_fd) { union { struct cmsghdr cmh; char ctrl[CMSG_SPACE(sizeof(struct ucred))]; diff --git a/sandboxed_api/sandbox2/mounts.cc b/sandboxed_api/sandbox2/mounts.cc index e1c0198..7e800ea 100644 --- a/sandboxed_api/sandbox2/mounts.cc +++ b/sandboxed_api/sandbox2/mounts.cc @@ -27,7 +27,7 @@ #include "google/protobuf/util/message_differencer.h" #include "absl/container/flat_hash_set.h" -#include "sandboxed_api/util/statusor.h" +#include "absl/status/statusor.h" #include "absl/strings/ascii.h" #include "absl/strings/match.h" #include "absl/strings/str_cat.h" @@ -97,7 +97,7 @@ absl::string_view GetOutsidePath(const MountTree::Node& node) { } } -sapi::StatusOr ExistingPathInsideDir( +absl::StatusOr ExistingPathInsideDir( absl::string_view dir_path, absl::string_view relative_path) { auto path = file::CleanPath(file::JoinPath(dir_path, relative_path)); if (file_util::fileops::StripBasename(path) != dir_path) { diff --git a/sandboxed_api/sandbox2/network_proxy/BUILD.bazel b/sandboxed_api/sandbox2/network_proxy/BUILD.bazel index d489a53..f099939 100644 --- a/sandboxed_api/sandbox2/network_proxy/BUILD.bazel +++ b/sandboxed_api/sandbox2/network_proxy/BUILD.bazel @@ -29,8 +29,8 @@ cc_library( ":filtering", "//sandboxed_api/sandbox2:comms", "//sandboxed_api/sandbox2/util:fileops", - "//sandboxed_api/util:statusor", "@com_google_absl//absl/memory", + "@com_google_absl//absl/status:statusor", "@com_google_absl//absl/strings", "@com_google_glog//:glog", ], @@ -63,8 +63,8 @@ cc_library( "//sandboxed_api/sandbox2:comms", "//sandboxed_api/sandbox2/util:strerror", "//sandboxed_api/util:status", - "//sandboxed_api/util:statusor", "@com_google_absl//absl/status", + "@com_google_absl//absl/status:statusor", "@com_google_absl//absl/strings", "@com_google_glog//:glog", ], diff --git a/sandboxed_api/sandbox2/network_proxy/filtering.cc b/sandboxed_api/sandbox2/network_proxy/filtering.cc index fc93e6c..aac47df 100644 --- a/sandboxed_api/sandbox2/network_proxy/filtering.cc +++ b/sandboxed_api/sandbox2/network_proxy/filtering.cc @@ -18,7 +18,7 @@ #include #include "absl/status/status.h" -#include "sandboxed_api/util/statusor.h" +#include "absl/status/statusor.h" #include "absl/strings/numbers.h" #include "absl/strings/str_cat.h" #include "absl/strings/str_split.h" @@ -27,7 +27,7 @@ namespace sandbox2 { -static sapi::StatusOr Addr6ToString( +static absl::StatusOr Addr6ToString( const struct sockaddr_in6* saddr) { char addr[INET6_ADDRSTRLEN]; int port = htons(saddr->sin6_port); @@ -39,7 +39,7 @@ static sapi::StatusOr Addr6ToString( } // Converts sockaddr_in structure into a string IPv4 representation. -static sapi::StatusOr Addr4ToString( +static absl::StatusOr Addr4ToString( const struct sockaddr_in* saddr) { char addr[INET_ADDRSTRLEN]; int port = htons(saddr->sin_port); @@ -51,7 +51,7 @@ static sapi::StatusOr Addr4ToString( } // Converts sockaddr_in6 structure into a string IPv6 representation. -sapi::StatusOr AddrToString(const struct sockaddr* saddr) { +absl::StatusOr AddrToString(const struct sockaddr* saddr) { switch (saddr->sa_family) { case AF_INET: return Addr4ToString(reinterpret_cast(saddr)); diff --git a/sandboxed_api/sandbox2/network_proxy/filtering.h b/sandboxed_api/sandbox2/network_proxy/filtering.h index c0a235b..b830d8e 100644 --- a/sandboxed_api/sandbox2/network_proxy/filtering.h +++ b/sandboxed_api/sandbox2/network_proxy/filtering.h @@ -19,14 +19,14 @@ #include -#include "sandboxed_api/util/statusor.h" +#include "absl/status/statusor.h" #include "sandboxed_api/sandbox2/comms.h" namespace sandbox2 { // Converts sockaddr_in or sockaddr_in6 structure into a string // representation. -sapi::StatusOr AddrToString(const struct sockaddr* saddr); +absl::StatusOr AddrToString(const struct sockaddr* saddr); struct IPv4 { in_addr_t ip; diff --git a/sandboxed_api/sandbox2/network_proxy/server.cc b/sandboxed_api/sandbox2/network_proxy/server.cc index 41f6f7e..6ba1ba7 100644 --- a/sandboxed_api/sandbox2/network_proxy/server.cc +++ b/sandboxed_api/sandbox2/network_proxy/server.cc @@ -26,7 +26,7 @@ #include #include "absl/memory/memory.h" -#include "sandboxed_api/util/statusor.h" +#include "absl/status/statusor.h" #include "sandboxed_api/sandbox2/util/fileops.h" namespace sandbox2 { @@ -105,7 +105,7 @@ void NetworkProxyServer::NotifySuccess() { } void NetworkProxyServer::NotifyViolation(const struct sockaddr* saddr) { - if (sapi::StatusOr result = AddrToString(saddr); result.ok()) { + if (absl::StatusOr result = AddrToString(saddr); result.ok()) { violation_msg_ = std::move(result).value(); } else { violation_msg_ = std::string(result.status().message()); diff --git a/sandboxed_api/sandbox2/policybuilder.cc b/sandboxed_api/sandbox2/policybuilder.cc index d185631..dbae2d9 100644 --- a/sandboxed_api/sandbox2/policybuilder.cc +++ b/sandboxed_api/sandbox2/policybuilder.cc @@ -35,7 +35,7 @@ #include #include "absl/memory/memory.h" -#include "sandboxed_api/util/statusor.h" +#include "absl/status/statusor.h" #include "absl/strings/escaping.h" #include "absl/strings/match.h" #include "sandboxed_api/sandbox2/namespace.h" @@ -658,7 +658,7 @@ PolicyBuilder& PolicyBuilder::DangerDefaultAllowAll() { return *this; } -sapi::StatusOr PolicyBuilder::ValidateAbsolutePath( +absl::StatusOr PolicyBuilder::ValidateAbsolutePath( absl::string_view path) { if (!file::IsAbsolutePath(path)) { return absl::InvalidArgumentError( @@ -667,7 +667,7 @@ sapi::StatusOr PolicyBuilder::ValidateAbsolutePath( return ValidatePath(path); } -sapi::StatusOr PolicyBuilder::ValidatePath( +absl::StatusOr PolicyBuilder::ValidatePath( absl::string_view path) { std::string fixed_path = file::CleanPath(path); if (fixed_path != path) { @@ -688,7 +688,7 @@ std::vector PolicyBuilder::ResolveBpfFunc(BpfFunc f) { return policy; } -sapi::StatusOr> PolicyBuilder::TryBuild() { +absl::StatusOr> PolicyBuilder::TryBuild() { auto output = absl::WrapUnique(new Policy()); if (!last_status_.ok()) { diff --git a/sandboxed_api/sandbox2/policybuilder.h b/sandboxed_api/sandbox2/policybuilder.h index 07095ba..17e95ff 100644 --- a/sandboxed_api/sandbox2/policybuilder.h +++ b/sandboxed_api/sandbox2/policybuilder.h @@ -29,7 +29,7 @@ #include #include "absl/base/macros.h" #include "absl/memory/memory.h" -#include "sandboxed_api/util/statusor.h" +#include "absl/status/statusor.h" #include "absl/strings/string_view.h" #include "sandboxed_api/sandbox2/mounts.h" #include "sandboxed_api/sandbox2/network_proxy/filtering.h" @@ -390,7 +390,7 @@ class PolicyBuilder final { // Builds the policy returning a unique_ptr to it. This should only be called // once. - sapi::StatusOr> TryBuild(); + absl::StatusOr> TryBuild(); // Builds the policy returning a unique_ptr to it. This should only be called // once. @@ -532,9 +532,9 @@ class PolicyBuilder final { std::vector ResolveBpfFunc(BpfFunc f); - static sapi::StatusOr ValidateAbsolutePath( + static absl::StatusOr ValidateAbsolutePath( absl::string_view path); - static sapi::StatusOr ValidatePath(absl::string_view path); + static absl::StatusOr ValidatePath(absl::string_view path); void StoreDescription(PolicyBuilderDescription* pb_description); diff --git a/sandboxed_api/sandbox2/policybuilder_test.cc b/sandboxed_api/sandbox2/policybuilder_test.cc index ca13614..b5387d9 100644 --- a/sandboxed_api/sandbox2/policybuilder_test.cc +++ b/sandboxed_api/sandbox2/policybuilder_test.cc @@ -24,7 +24,7 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" #include "absl/memory/memory.h" -#include "sandboxed_api/util/statusor.h" +#include "absl/status/statusor.h" #include "absl/strings/match.h" #include "absl/strings/str_cat.h" #include "absl/strings/str_split.h" @@ -58,7 +58,7 @@ class PolicyBuilderPeer { int policy_size() const { return builder_->user_policy_.size(); } - static sapi::StatusOr ValidateAbsolutePath( + static absl::StatusOr ValidateAbsolutePath( absl::string_view path) { return PolicyBuilder::ValidateAbsolutePath(path); } diff --git a/sandboxed_api/sandbox2/sandbox2.cc b/sandboxed_api/sandbox2/sandbox2.cc index 86c06dc..d0c1e22 100644 --- a/sandboxed_api/sandbox2/sandbox2.cc +++ b/sandboxed_api/sandbox2/sandbox2.cc @@ -21,7 +21,7 @@ #include #include "absl/memory/memory.h" -#include "sandboxed_api/util/statusor.h" +#include "absl/status/statusor.h" #include "absl/time/time.h" #include "sandboxed_api/sandbox2/monitor.h" #include "sandboxed_api/sandbox2/result.h" @@ -34,7 +34,7 @@ Sandbox2::~Sandbox2() { } } -sapi::StatusOr Sandbox2::AwaitResultWithTimeout( +absl::StatusOr Sandbox2::AwaitResultWithTimeout( absl::Duration timeout) { CHECK(monitor_ != nullptr) << "Sandbox was not launched yet"; CHECK(monitor_thread_ != nullptr) << "Sandbox was already waited on"; diff --git a/sandboxed_api/sandbox2/sandbox2.h b/sandboxed_api/sandbox2/sandbox2.h index 633297f..62dd4fe 100644 --- a/sandboxed_api/sandbox2/sandbox2.h +++ b/sandboxed_api/sandbox2/sandbox2.h @@ -26,7 +26,7 @@ #include #include "absl/base/macros.h" #include "absl/memory/memory.h" -#include "sandboxed_api/util/statusor.h" +#include "absl/status/statusor.h" #include "sandboxed_api/sandbox2/comms.h" #include "sandboxed_api/sandbox2/executor.h" #include "sandboxed_api/sandbox2/ipc.h" @@ -76,7 +76,7 @@ class Sandbox2 final { // Waits for sandbox execution to finish within the timeout. // Returns execution result or a DeadlineExceededError if the sandboxee does // not finish in time. - sapi::StatusOr AwaitResultWithTimeout(absl::Duration timeout); + absl::StatusOr AwaitResultWithTimeout(absl::Duration timeout); // Requests termination of the sandboxee. // Sandbox should still waited with AwaitResult(), as it may finish for other diff --git a/sandboxed_api/sandbox2/util.cc b/sandboxed_api/sandbox2/util.cc index b760d26..55f7aa6 100644 --- a/sandboxed_api/sandbox2/util.cc +++ b/sandboxed_api/sandbox2/util.cc @@ -182,7 +182,7 @@ bool CreateMemFd(int* fd, const char* name) { return true; } -sapi::StatusOr Communicate(const std::vector& argv, +absl::StatusOr Communicate(const std::vector& argv, const std::vector& envv, std::string* output) { int cout_pipe[2]; @@ -280,7 +280,7 @@ std::string GetRlimitName(int resource) { } } -sapi::StatusOr ReadCPathFromPid(pid_t pid, uintptr_t ptr) { +absl::StatusOr ReadCPathFromPid(pid_t pid, uintptr_t ptr) { std::string path(PATH_MAX, '\0'); iovec local_iov[] = {{&path[0], path.size()}}; diff --git a/sandboxed_api/sandbox2/util.h b/sandboxed_api/sandbox2/util.h index e42bf72..a925b02 100644 --- a/sandboxed_api/sandbox2/util.h +++ b/sandboxed_api/sandbox2/util.h @@ -25,7 +25,7 @@ #include #include "absl/base/macros.h" -#include "sandboxed_api/util/statusor.h" +#include "absl/status/statusor.h" namespace sandbox2 { namespace util { @@ -62,7 +62,7 @@ bool CreateMemFd(int* fd, const char* name = "buffer_file"); // Executes a the program given by argv and the specified environment and // captures any output to stdout/stderr. -sapi::StatusOr Communicate(const std::vector& argv, +absl::StatusOr Communicate(const std::vector& argv, const std::vector& envv, std::string* output); @@ -74,7 +74,7 @@ std::string GetRlimitName(int resource); // Reads a path string (NUL-terminated, shorter than PATH_MAX) from another // process memory -sapi::StatusOr ReadCPathFromPid(pid_t pid, uintptr_t ptr); +absl::StatusOr ReadCPathFromPid(pid_t pid, uintptr_t ptr); } // namespace util } // namespace sandbox2 diff --git a/sandboxed_api/sandbox2/util/BUILD.bazel b/sandboxed_api/sandbox2/util/BUILD.bazel index d48d9a4..924e22e 100644 --- a/sandboxed_api/sandbox2/util/BUILD.bazel +++ b/sandboxed_api/sandbox2/util/BUILD.bazel @@ -139,9 +139,9 @@ cc_library( "//sandboxed_api/sandbox2:util", "//sandboxed_api/util:raw_logging", "//sandboxed_api/util:status", - "//sandboxed_api/util:statusor", "@com_google_absl//absl/base:endian", "@com_google_absl//absl/status", + "@com_google_absl//absl/status:statusor", "@com_google_absl//absl/strings", ], ) @@ -173,8 +173,8 @@ cc_library( deps = [ ":fileops", ":strerror", - "//sandboxed_api/util:statusor", "@com_google_absl//absl/status", + "@com_google_absl//absl/status:statusor", "@com_google_absl//absl/strings", ], ) @@ -199,8 +199,8 @@ cc_library( hdrs = ["maps_parser.h"], copts = sapi_platform_copts(), deps = [ - "//sandboxed_api/util:statusor", "@com_google_absl//absl/status", + "@com_google_absl//absl/status:statusor", "@com_google_absl//absl/strings", ], ) diff --git a/sandboxed_api/sandbox2/util/maps_parser.cc b/sandboxed_api/sandbox2/util/maps_parser.cc index cf0955f..6687989 100644 --- a/sandboxed_api/sandbox2/util/maps_parser.cc +++ b/sandboxed_api/sandbox2/util/maps_parser.cc @@ -15,12 +15,12 @@ #include "sandboxed_api/sandbox2/util/maps_parser.h" #include "absl/status/status.h" -#include "sandboxed_api/util/statusor.h" +#include "absl/status/statusor.h" #include "absl/strings/str_split.h" namespace sandbox2 { -sapi::StatusOr> ParseProcMaps( +absl::StatusOr> ParseProcMaps( const std::string& contents) { // Note: The format string // https://github.com/torvalds/linux/blob/v4.14/fs/proc/task_mmu.c#L289 diff --git a/sandboxed_api/sandbox2/util/maps_parser.h b/sandboxed_api/sandbox2/util/maps_parser.h index 862d3e7..ea0a419 100644 --- a/sandboxed_api/sandbox2/util/maps_parser.h +++ b/sandboxed_api/sandbox2/util/maps_parser.h @@ -19,7 +19,7 @@ #include #include -#include "sandboxed_api/util/statusor.h" +#include "absl/status/statusor.h" namespace sandbox2 { @@ -37,7 +37,7 @@ struct MapsEntry { std::string path; }; -sapi::StatusOr> ParseProcMaps( +absl::StatusOr> ParseProcMaps( const std::string& contents); } // namespace sandbox2 diff --git a/sandboxed_api/sandbox2/util/minielf.cc b/sandboxed_api/sandbox2/util/minielf.cc index dce033b..737dc97 100644 --- a/sandboxed_api/sandbox2/util/minielf.cc +++ b/sandboxed_api/sandbox2/util/minielf.cc @@ -95,7 +95,7 @@ class ElfParser { static constexpr size_t kMaxInterpreterSize = 1000; ElfParser() = default; - sapi::StatusOr Parse(FILE* elf, uint32_t features); + absl::StatusOr Parse(FILE* elf, uint32_t features); private: // Endianess support functions @@ -133,16 +133,16 @@ class ElfParser { // Reads elf header. absl::Status ReadFileHeader(); // Reads a single elf program header. - sapi::StatusOr ReadProgramHeader(absl::string_view src); + absl::StatusOr ReadProgramHeader(absl::string_view src); // Reads all elf program headers. absl::Status ReadProgramHeaders(); // Reads a single elf section header. - sapi::StatusOr ReadSectionHeader(absl::string_view src); + absl::StatusOr ReadSectionHeader(absl::string_view src); // Reads all elf section headers. absl::Status ReadSectionHeaders(); // Reads contents of an elf section. - sapi::StatusOr ReadSectionContents(int idx); - sapi::StatusOr ReadSectionContents( + absl::StatusOr ReadSectionContents(int idx); + absl::StatusOr ReadSectionContents( const Elf64_Shdr& section_header); // Reads all symbols from symtab section. absl::Status ReadSymbolsFromSymtab(const Elf64_Shdr& symtab); @@ -219,7 +219,7 @@ absl::Status ElfParser::ReadFileHeader() { return absl::OkStatus(); } -sapi::StatusOr ElfParser::ReadSectionHeader(absl::string_view src) { +absl::StatusOr ElfParser::ReadSectionHeader(absl::string_view src) { if (src.size() < sizeof(Elf64_Shdr)) { return absl::FailedPreconditionError( absl::StrCat("invalid section header data: got ", src.size(), @@ -266,7 +266,7 @@ absl::Status ElfParser::ReadSectionHeaders() { return absl::OkStatus(); } -sapi::StatusOr ElfParser::ReadSectionContents(int idx) { +absl::StatusOr ElfParser::ReadSectionContents(int idx) { if (idx < 0 || idx >= section_headers_.size()) { return absl::FailedPreconditionError( absl::StrCat("invalid section header index: ", idx)); @@ -274,7 +274,7 @@ sapi::StatusOr ElfParser::ReadSectionContents(int idx) { return ReadSectionContents(section_headers_.at(idx)); } -sapi::StatusOr ElfParser::ReadSectionContents( +absl::StatusOr ElfParser::ReadSectionContents( const Elf64_Shdr& section_header) { auto offset = section_header.sh_offset; if (offset > file_size_) { @@ -292,7 +292,7 @@ sapi::StatusOr ElfParser::ReadSectionContents( return rv; } -sapi::StatusOr ElfParser::ReadProgramHeader(absl::string_view src) { +absl::StatusOr ElfParser::ReadProgramHeader(absl::string_view src) { if (src.size() < sizeof(Elf64_Phdr)) { return absl::FailedPreconditionError( absl::StrCat("invalid program header data: got ", src.size(), @@ -454,7 +454,7 @@ absl::Status ElfParser::ReadImportedLibrariesFromDynamic( return absl::OkStatus(); } -sapi::StatusOr ElfParser::Parse(FILE* elf, uint32_t features) { +absl::StatusOr ElfParser::Parse(FILE* elf, uint32_t features) { elf_ = elf; // Basic sanity check. if (features & ~(ElfFile::kAll)) { @@ -511,7 +511,7 @@ sapi::StatusOr ElfParser::Parse(FILE* elf, uint32_t features) { return std::move(result_); } -sapi::StatusOr ElfFile::ParseFromFile(const std::string& filename, +absl::StatusOr ElfFile::ParseFromFile(const std::string& filename, uint32_t features) { std::unique_ptr elf{fopen(filename.c_str(), "r"), [](FILE* f) { fclose(f); }}; diff --git a/sandboxed_api/sandbox2/util/minielf.h b/sandboxed_api/sandbox2/util/minielf.h index c33a604..027e21c 100644 --- a/sandboxed_api/sandbox2/util/minielf.h +++ b/sandboxed_api/sandbox2/util/minielf.h @@ -20,7 +20,7 @@ #include #include -#include "sandboxed_api/util/statusor.h" +#include "absl/status/statusor.h" namespace sandbox2 { @@ -33,7 +33,7 @@ class ElfFile { std::string name; }; - static sapi::StatusOr ParseFromFile(const std::string& filename, + static absl::StatusOr ParseFromFile(const std::string& filename, uint32_t features); int64_t file_size() const { return file_size_; } diff --git a/sandboxed_api/sandbox2/util/temp_file.cc b/sandboxed_api/sandbox2/util/temp_file.cc index 602a18c..09a5a1b 100644 --- a/sandboxed_api/sandbox2/util/temp_file.cc +++ b/sandboxed_api/sandbox2/util/temp_file.cc @@ -22,7 +22,7 @@ #include #include -#include "sandboxed_api/util/statusor.h" +#include "absl/status/statusor.h" #include "absl/strings/str_cat.h" #include "sandboxed_api/sandbox2/util/fileops.h" #include "sandboxed_api/sandbox2/util/strerror.h" @@ -33,7 +33,7 @@ namespace { constexpr absl::string_view kMktempSuffix = "XXXXXX"; } // namespace -sapi::StatusOr> CreateNamedTempFile( +absl::StatusOr> CreateNamedTempFile( absl::string_view prefix) { std::string name_template = absl::StrCat(prefix, kMktempSuffix); int fd = mkstemp(&name_template[0]); @@ -43,7 +43,7 @@ sapi::StatusOr> CreateNamedTempFile( return std::pair{std::move(name_template), fd}; } -sapi::StatusOr CreateNamedTempFileAndClose( +absl::StatusOr CreateNamedTempFileAndClose( absl::string_view prefix) { auto result_or = CreateNamedTempFile(prefix); if (result_or.ok()) { @@ -56,7 +56,7 @@ sapi::StatusOr CreateNamedTempFileAndClose( return result_or.status(); } -sapi::StatusOr CreateTempDir(absl::string_view prefix) { +absl::StatusOr CreateTempDir(absl::string_view prefix) { std::string name_template = absl::StrCat(prefix, kMktempSuffix); if (mkdtemp(&name_template[0]) == nullptr) { return absl::UnknownError(absl::StrCat("mkdtemp():", StrError(errno))); diff --git a/sandboxed_api/sandbox2/util/temp_file.h b/sandboxed_api/sandbox2/util/temp_file.h index ae8f986..8324a61 100644 --- a/sandboxed_api/sandbox2/util/temp_file.h +++ b/sandboxed_api/sandbox2/util/temp_file.h @@ -17,24 +17,24 @@ #include -#include "sandboxed_api/util/statusor.h" +#include "absl/status/statusor.h" namespace sandbox2 { // Creates a temporary file under a path starting with prefix. File is not // unlinked and its path is returned together with an open fd. -sapi::StatusOr> CreateNamedTempFile( +absl::StatusOr> CreateNamedTempFile( absl::string_view prefix); // Creates a temporary file under a path starting with prefix. File is not // unlinked and its path is returned. FD of the created file is closed just // after creation. -sapi::StatusOr CreateNamedTempFileAndClose( +absl::StatusOr CreateNamedTempFileAndClose( absl::string_view prefix); // Creates a temporary directory under a path starting with prefix. // Returns the path of the created directory. -sapi::StatusOr CreateTempDir(absl::string_view prefix); +absl::StatusOr CreateTempDir(absl::string_view prefix); } // namespace sandbox2 diff --git a/sandboxed_api/tools/clang_generator/emitter.cc b/sandboxed_api/tools/clang_generator/emitter.cc index 5f30953..03bb5e3 100644 --- a/sandboxed_api/tools/clang_generator/emitter.cc +++ b/sandboxed_api/tools/clang_generator/emitter.cc @@ -15,7 +15,7 @@ #include "sandboxed_api/tools/clang_generator/emitter.h" #include "absl/random/random.h" -#include "sandboxed_api/util/statusor.h" +#include "absl/status/statusor.h" #include "absl/strings/ascii.h" #include "absl/strings/escaping.h" #include "absl/strings/match.h" @@ -47,7 +47,7 @@ constexpr absl::string_view kHeaderProlog = #include "absl/base/macros.h" #include "absl/status/status.h" -#include "sandboxed_api/util/statusor.h" +#include "absl/status/statusor.h" #include "sandboxed_api/sandbox.h" #include "sandboxed_api/vars.h" #include "sandboxed_api/util/status_macros.h" @@ -197,7 +197,7 @@ std::string PrintFunctionPrototype(const clang::FunctionDecl* decl) { return out; } -sapi::StatusOr EmitFunction(const clang::FunctionDecl* decl) { +absl::StatusOr EmitFunction(const clang::FunctionDecl* decl) { std::string out; absl::StrAppend(&out, "\n// ", PrintFunctionPrototype(decl), "\n"); const std::string function_name = decl->getNameAsString(); @@ -249,7 +249,7 @@ sapi::StatusOr EmitFunction(const clang::FunctionDecl* decl) { return out; } -sapi::StatusOr EmitHeader( +absl::StatusOr EmitHeader( std::vector functions, const QualTypeSet& types, const GeneratorOptions& options) { std::string out; diff --git a/sandboxed_api/tools/clang_generator/emitter.h b/sandboxed_api/tools/clang_generator/emitter.h index ad9f5f8..78e1f12 100644 --- a/sandboxed_api/tools/clang_generator/emitter.h +++ b/sandboxed_api/tools/clang_generator/emitter.h @@ -18,7 +18,7 @@ #include #include "absl/status/status.h" -#include "sandboxed_api/util/statusor.h" +#include "absl/status/statusor.h" #include "absl/strings/string_view.h" #include "clang/AST/Decl.h" #include "clang/AST/Type.h" @@ -36,7 +36,7 @@ namespace sapi { std::string GetIncludeGuard(absl::string_view filename); // Outputs a formatted header for a list of functions and their related types. -sapi::StatusOr EmitHeader( +absl::StatusOr EmitHeader( std::vector functions, const QualTypeSet& types, const GeneratorOptions& options); diff --git a/sandboxed_api/tools/clang_generator/generator.cc b/sandboxed_api/tools/clang_generator/generator.cc index ffe2106..90504eb 100644 --- a/sandboxed_api/tools/clang_generator/generator.cc +++ b/sandboxed_api/tools/clang_generator/generator.cc @@ -18,7 +18,7 @@ #include #include "absl/status/status.h" -#include "sandboxed_api/util/statusor.h" +#include "absl/status/statusor.h" #include "clang/Format/Format.h" #include "sandboxed_api/sandbox2/util/fileops.h" #include "sandboxed_api/tools/clang_generator/diagnostics.h" @@ -67,7 +67,7 @@ bool GeneratorASTVisitor::VisitFunctionDecl(clang::FunctionDecl* decl) { namespace internal { -sapi::StatusOr ReformatGoogleStyle(const std::string& filename, +absl::StatusOr ReformatGoogleStyle(const std::string& filename, const std::string& code) { // Configure code style based on Google style, but enforce pointer alignment clang::format::FormatStyle style = diff --git a/sandboxed_api/tools/clang_generator/generator.h b/sandboxed_api/tools/clang_generator/generator.h index 3b9680b..8732a26 100644 --- a/sandboxed_api/tools/clang_generator/generator.h +++ b/sandboxed_api/tools/clang_generator/generator.h @@ -20,7 +20,7 @@ #include "absl/container/flat_hash_set.h" #include "absl/memory/memory.h" #include "absl/status/status.h" -#include "sandboxed_api/util/statusor.h" +#include "absl/status/statusor.h" #include "clang/AST/ASTConsumer.h" #include "clang/AST/RecursiveASTVisitor.h" #include "clang/Frontend/CompilerInstance.h" @@ -66,7 +66,7 @@ class GeneratorASTVisitor namespace internal { -sapi::StatusOr ReformatGoogleStyle(const std::string& filename, +absl::StatusOr ReformatGoogleStyle(const std::string& filename, const std::string& code); } // namespace internal diff --git a/sandboxed_api/tools/clang_generator/types.cc b/sandboxed_api/tools/clang_generator/types.cc index 321d1e2..8efb4ef 100644 --- a/sandboxed_api/tools/clang_generator/types.cc +++ b/sandboxed_api/tools/clang_generator/types.cc @@ -208,7 +208,7 @@ std::string MapQualTypeReturn(const clang::ASTContext& context, return "absl::Status"; } // Remove const qualifier like in MapQualType(). - return absl::StrCat("sapi::StatusOr<", + return absl::StrCat("absl::StatusOr<", MaybeRemoveConst(context, qual).getAsString(), ">"); } diff --git a/sandboxed_api/tools/clang_generator/types.h b/sandboxed_api/tools/clang_generator/types.h index 82a4478..b4d5946 100644 --- a/sandboxed_api/tools/clang_generator/types.h +++ b/sandboxed_api/tools/clang_generator/types.h @@ -68,7 +68,7 @@ std::string MapQualTypeParameter(const clang::ASTContext& context, // Maps a qualified type used as a function return type to a type name // compatible with the generated Sandboxed API. Uses MapQualTypeParameter() and -// wraps the type in a sapi::StatusOr<> if qual is non-void. Otherwise returns +// wraps the type in a absl::StatusOr<> if qual is non-void. Otherwise returns // absl::Status. std::string MapQualTypeReturn(const clang::ASTContext& context, clang::QualType qual); diff --git a/sandboxed_api/tools/generator2/code.py b/sandboxed_api/tools/generator2/code.py index fb31101..84cff80 100644 --- a/sandboxed_api/tools/generator2/code.py +++ b/sandboxed_api/tools/generator2/code.py @@ -461,7 +461,7 @@ class ReturnType(ArgumentType): """Class representing function return type. Attributes: - return_type: sapi::StatusOr where T is original return type, or + return_type: absl::StatusOr where T is original return type, or absl::Status for functions returning void """ @@ -474,7 +474,7 @@ class ReturnType(ArgumentType): """Returns function return type prepared from the type.""" # TODO(szwl): const ptrs do not play well with SAPI C++ API... spelling = self._clang_type.spelling.replace('const', '') - return_type = 'sapi::StatusOr<{}>'.format(spelling) + return_type = 'absl::StatusOr<{}>'.format(spelling) return_type = 'absl::Status' if self.is_void() else return_type return return_type diff --git a/sandboxed_api/tools/generator2/code_test_util.py b/sandboxed_api/tools/generator2/code_test_util.py index 1fd5f9d..82ee3a6 100644 --- a/sandboxed_api/tools/generator2/code_test_util.py +++ b/sandboxed_api/tools/generator2/code_test_util.py @@ -32,7 +32,7 @@ class TestApi { ::sapi::Sandbox* sandbox() const { return sandbox_; } // int function_a(int, int) - sapi::StatusOr function_a(int x, int y) { + absl::StatusOr function_a(int x, int y) { ::sapi::v::Int ret; ::sapi::v::Int x_((x)); ::sapi::v::Int y_((y)); @@ -42,7 +42,7 @@ class TestApi { } // int types_1(bool, unsigned char, char, unsigned short, short) - sapi::StatusOr types_1(bool a0, unsigned char a1, char a2, unsigned short a3, short a4) { + absl::StatusOr types_1(bool a0, unsigned char a1, char a2, unsigned short a3, short a4) { ::sapi::v::Int ret; ::sapi::v::Bool a0_((a0)); ::sapi::v::UChar a1_((a1)); @@ -55,7 +55,7 @@ class TestApi { } // int types_2(int, unsigned int, long, unsigned long) - sapi::StatusOr types_2(int a0, unsigned int a1, long a2, unsigned long a3) { + absl::StatusOr types_2(int a0, unsigned int a1, long a2, unsigned long a3) { ::sapi::v::Int ret; ::sapi::v::Int a0_((a0)); ::sapi::v::UInt a1_((a1)); @@ -67,7 +67,7 @@ class TestApi { } // int types_3(long long, unsigned long long, float, double) - sapi::StatusOr types_3(long long a0, unsigned long long a1, float a2, double a3) { + absl::StatusOr types_3(long long a0, unsigned long long a1, float a2, double a3) { ::sapi::v::Int ret; ::sapi::v::LLong a0_((a0)); ::sapi::v::ULLong a1_((a1)); @@ -79,7 +79,7 @@ class TestApi { } // int types_4(signed char, short, int, long) - sapi::StatusOr types_4(signed char a0, short a1, int a2, long a3) { + absl::StatusOr types_4(signed char a0, short a1, int a2, long a3) { ::sapi::v::Int ret; ::sapi::v::SChar a0_((a0)); ::sapi::v::Short a1_((a1)); @@ -91,7 +91,7 @@ class TestApi { } // int types_5(long long, long double) - sapi::StatusOr types_5(long long a0, long double a1) { + absl::StatusOr types_5(long long a0, long double a1) { ::sapi::v::Int ret; ::sapi::v::LLong a0_((a0)); ::sapi::v::Reg a1_((a1)); @@ -136,7 +136,7 @@ class TestApi { ::sapi::Sandbox* sandbox() const { return sandbox_; } // uint function(uintp) - sapi::StatusOr function(::sapi::v::Ptr* a) { + absl::StatusOr function(::sapi::v::Ptr* a) { ::sapi::v::UInt ret; SAPI_RETURN_IF_ERROR(sandbox_->Call("function", &ret, a)); @@ -173,7 +173,7 @@ class TestApi { ::sapi::Sandbox* sandbox() const { return sandbox_; } // ProcessStatus ProcessDatapoint(ProcessStatus) - sapi::StatusOr ProcessDatapoint(ProcessStatus status) { + absl::StatusOr ProcessDatapoint(ProcessStatus status) { ::sapi::v::IntBase ret; ::sapi::v::IntBase status_((status)); diff --git a/sandboxed_api/util/statusor_test.cc b/sandboxed_api/util/statusor_test.cc deleted file mode 100644 index ba80ce1..0000000 --- a/sandboxed_api/util/statusor_test.cc +++ /dev/null @@ -1,407 +0,0 @@ -// Copyright 2019 Google LLC -// -// 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. - -// This file is a custom fork of the version in Asylo. This will become obsolete -// and will be replaced once Abseil releases absl::Status. - -#include "sandboxed_api/util/statusor.h" - -#include -#include -#include - -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include "sandboxed_api/util/status_matchers.h" - -using ::testing::Eq; -using ::testing::IsFalse; -using ::testing::Not; -using ::testing::Pointee; - -namespace sapi { -namespace { - -constexpr auto kErrorCode = absl::StatusCode::kInvalidArgument; -constexpr char kErrorMessage[] = "Invalid argument"; - -const int kIntElement = 47; -constexpr char kStringElement[] = "47 is 42, corrected for inflation"; - -// A data type without a default constructor. -struct Foo { - int bar; - std::string baz; - - explicit Foo(int value) : bar(value), baz(kStringElement) {} -}; - -// A data type with dynamically-allocated data. -struct HeapAllocatedObject { - int* value; - - HeapAllocatedObject() { - value = new int; - *value = kIntElement; - } - - HeapAllocatedObject(const HeapAllocatedObject& other) { *this = other; } - - HeapAllocatedObject& operator=(const HeapAllocatedObject& other) { - value = new int; - *value = *other.value; - return *this; - } - - HeapAllocatedObject(HeapAllocatedObject&& other) { *this = std::move(other); } - - HeapAllocatedObject& operator=(HeapAllocatedObject&& other) { - value = other.value; - other.value = nullptr; - return *this; - } - - ~HeapAllocatedObject() { delete value; } -}; - -// Constructs a Foo. -struct FooCtor { - using value_type = Foo; - - Foo operator()() { return Foo(kIntElement); } -}; - -// Constructs a HeapAllocatedObject. -struct HeapAllocatedObjectCtor { - using value_type = HeapAllocatedObject; - - HeapAllocatedObject operator()() { return HeapAllocatedObject(); } -}; - -// Constructs an integer. -struct IntCtor { - using value_type = int; - - int operator()() { return kIntElement; } -}; - -// Constructs a string. -struct StringCtor { - using value_type = std::string; - - std::string operator()() { return std::string(kStringElement); } -}; - -// Constructs a vector of strings. -struct StringVectorCtor { - using value_type = std::vector; - - std::vector operator()() { - return {kStringElement, kErrorMessage}; - } -}; - -bool operator==(const Foo& lhs, const Foo& rhs) { - return (lhs.bar == rhs.bar) && (lhs.baz == rhs.baz); -} - -bool operator==(const HeapAllocatedObject& lhs, - const HeapAllocatedObject& rhs) { - return *lhs.value == *rhs.value; -} - -// Returns an rvalue reference to the StatusOr object pointed to by -// |statusor|. -template -StatusOr&& MoveStatusOr(StatusOr* statusor) { - return std::move(*statusor); -} - -// A test fixture is required for typed tests. -template -class StatusOrTest : public ::testing::Test {}; - -using TestTypes = ::testing::Types; -TYPED_TEST_SUITE(StatusOrTest, TestTypes); - -// Verify that the default constructor for StatusOr constructs an object with a -// non-ok status. -TYPED_TEST(StatusOrTest, ConstructorDefault) { - StatusOr statusor; - EXPECT_THAT(statusor.ok(), IsFalse()); - EXPECT_THAT(statusor.status().code(), Eq(absl::StatusCode::kUnknown)); -} - -// Verify that StatusOr can be constructed from a Status object. -TYPED_TEST(StatusOrTest, ConstructorStatus) { - StatusOr statusor( - absl::Status(kErrorCode, kErrorMessage)); - - EXPECT_THAT(statusor.ok(), IsFalse()); - EXPECT_THAT(statusor.status().ok(), IsFalse()); - EXPECT_THAT(statusor.status(), Eq(absl::Status(kErrorCode, kErrorMessage))); -} - -// Verify that StatusOr can be constructed from an object of its element type. -TYPED_TEST(StatusOrTest, ConstructorElementConstReference) { - auto value = TypeParam()(); - StatusOr statusor{value}; - - ASSERT_THAT(statusor, IsOk()); - ASSERT_THAT(statusor.status(), IsOk()); - EXPECT_THAT(statusor.ValueOrDie(), Eq(value)); -} - -// Verify that StatusOr can be constructed from an rvalue reference of an object -// of its element type. -TYPED_TEST(StatusOrTest, ConstructorElementRValue) { - auto value = TypeParam()(); - auto value_copy(value); - StatusOr statusor(std::move(value)); - - ASSERT_THAT(statusor, IsOk()); - ASSERT_THAT(statusor.status(), IsOk()); - - // Compare to a copy of the original value, since the original was moved. - EXPECT_THAT(statusor.ValueOrDie(), Eq(value_copy)); -} - -// Verify that StatusOr can be copy-constructed from a StatusOr with a non-ok -// status. -TYPED_TEST(StatusOrTest, CopyConstructorNonOkStatus) { - StatusOr statusor1 = - absl::Status(kErrorCode, kErrorMessage); - StatusOr statusor2(statusor1); - - EXPECT_THAT(statusor1.ok(), Eq(statusor2.ok())); - EXPECT_THAT(statusor1.status(), Eq(statusor2.status())); -} - -// Verify that StatusOr can be copy-constructed from a StatusOr with an ok -// status. -TYPED_TEST(StatusOrTest, CopyConstructorOkStatus) { - StatusOr statusor1{TypeParam()()}; - StatusOr statusor2{statusor1}; - - EXPECT_THAT(statusor1.ok(), Eq(statusor2.ok())); - ASSERT_THAT(statusor2, IsOk()); - EXPECT_THAT(statusor1.ValueOrDie(), Eq(statusor2.ValueOrDie())); -} - -// Verify that copy-assignment of a StatusOr with a non-ok is working as -// expected. -TYPED_TEST(StatusOrTest, CopyAssignmentNonOkStatus) { - StatusOr statusor1{ - absl::Status(kErrorCode, kErrorMessage)}; - StatusOr statusor2{TypeParam()()}; - - // Invoke the copy-assignment operator. - statusor2 = statusor1; - EXPECT_THAT(statusor1.ok(), Eq(statusor2.ok())); - EXPECT_THAT(statusor1.status(), Eq(statusor2.status())); -} - -// Verify that copy-assignment of a StatusOr with an ok status is working as -// expected. -TYPED_TEST(StatusOrTest, CopyAssignmentOkStatus) { - StatusOr statusor1{TypeParam()()}; - StatusOr statusor2{ - absl::Status(kErrorCode, kErrorMessage)}; - - // Invoke the copy-assignment operator. - statusor2 = statusor1; - EXPECT_THAT(statusor1.ok(), Eq(statusor2.ok())); - ASSERT_THAT(statusor2, IsOk()); - EXPECT_THAT(statusor1.ValueOrDie(), Eq(statusor2.ValueOrDie())); -} - -// Verify that StatusOr can be move-constructed from a StatusOr with a non-ok -// status. -TYPED_TEST(StatusOrTest, MoveConstructorNonOkStatus) { - absl::Status status(kErrorCode, kErrorMessage); - StatusOr statusor1(status); - StatusOr statusor2(std::move(statusor1)); - - // Verify that the status of the donor object was updated. - EXPECT_THAT(statusor1.ok(), IsFalse()); // NOLINT - EXPECT_THAT(statusor1.status(), StatusIs(absl::StatusCode::kInternal)); - - // Verify that the destination object contains the status previously held by - // the donor. - EXPECT_THAT(statusor2.ok(), IsFalse()); - EXPECT_THAT(statusor2.status(), Eq(status)); -} - -// Verify that StatusOr can be move-constructed from a StatusOr with an ok -// status. -TYPED_TEST(StatusOrTest, MoveConstructorOkStatus) { - auto value = TypeParam()(); - StatusOr statusor1(value); - StatusOr statusor2(std::move(statusor1)); - - // The destination object should possess the value previously held by the - // donor. - ASSERT_THAT(statusor2, IsOk()); - EXPECT_THAT(statusor2.ValueOrDie(), Eq(value)); -} - -// Verify that move-assignment from a StatusOr with a non-ok status is working -// as expected. -TYPED_TEST(StatusOrTest, MoveAssignmentOperatorNonOkStatus) { - absl::Status status(kErrorCode, kErrorMessage); - StatusOr statusor1(status); - StatusOr statusor2{TypeParam()()}; - - // Invoke the move-assignment operator. - statusor2 = std::move(statusor1); - - // Verify that the status of the donor object was updated. - EXPECT_THAT(statusor1.ok(), IsFalse()); // NOLINT - EXPECT_THAT(statusor1.status(), StatusIs(absl::StatusCode::kInternal)); - - // Verify that the destination object contains the status previously held by - // the donor. - EXPECT_THAT(statusor2.ok(), IsFalse()); - EXPECT_THAT(statusor2.status(), Eq(status)); -} - -// Verify that move-assignment from a StatusOr with an ok status is working as -// expected. -TYPED_TEST(StatusOrTest, MoveAssignmentOperatorOkStatus) { - auto value = TypeParam()(); - StatusOr statusor1(value); - StatusOr statusor2( - absl::Status(kErrorCode, kErrorMessage)); - - // Invoke the move-assignment operator. - statusor2 = std::move(statusor1); - - // The destination object should possess the value previously held by the - // donor. - ASSERT_THAT(statusor2, IsOk()); - EXPECT_THAT(statusor2.ValueOrDie(), Eq(value)); -} - -// Verify that the sapi::IsOk() gMock matcher works with StatusOr. -TYPED_TEST(StatusOrTest, IsOkMatcher) { - auto value = TypeParam()(); - StatusOr statusor(value); - - EXPECT_THAT(statusor, IsOk()); - - statusor = StatusOr( - absl::Status(kErrorCode, kErrorMessage)); - EXPECT_THAT(statusor, Not(IsOk())); -} - -// Tests for move-only types. These tests use std::unique_ptr<> as the -// test type, since it is valuable to support this type in the Asylo infra. -// These tests are not part of the typed test suite for the following reasons: -// * std::unique_ptr<> cannot be used as a type in tests that expect -// the test type to support copy operations. -// * std::unique_ptr<> provides an equality operator that checks equality of -// the underlying ptr. Consequently, it is difficult to generalize existing -// tests that verify ValueOrDie() functionality using equality comparisons. - -// Verify that a StatusOr object can be constructed from a move-only type. -TEST(StatusOrTest, InitializationMoveOnlyType) { - auto* str = new std::string(kStringElement); - std::unique_ptr value(str); - StatusOr> statusor(std::move(value)); - - ASSERT_THAT(statusor, IsOk()); - EXPECT_THAT(statusor.ValueOrDie().get(), Eq(str)); -} - -// Verify that a StatusOr object can be move-constructed from a move-only type. -TEST(StatusOrTest, MoveConstructorMoveOnlyType) { - auto* str = new std::string(kStringElement); - std::unique_ptr value(str); - StatusOr> statusor1(std::move(value)); - StatusOr> statusor2(std::move(statusor1)); - - // The destination object should possess the value previously held by the - // donor. - ASSERT_THAT(statusor2, IsOk()); - EXPECT_THAT(statusor2.ValueOrDie().get(), Eq(str)); -} - -// Verify that a StatusOr object can be move-assigned to from a StatusOr object -// containing a move-only type. -TEST(StatusOrTest, MoveAssignmentMoveOnlyType) { - auto* str = new std::string(kStringElement); - std::unique_ptr value(str); - StatusOr> statusor1(std::move(value)); - StatusOr> statusor2( - absl::Status(kErrorCode, kErrorMessage)); - - // Invoke the move-assignment operator. - statusor2 = std::move(statusor1); - - // The destination object should possess the value previously held by the - // donor. - ASSERT_THAT(statusor2, IsOk()); - EXPECT_THAT(statusor2.ValueOrDie().get(), Eq(str)); -} - -// Verify that a value can be moved out of a StatusOr object via ValueOrDie(). -TEST(StatusOrTest, ValueOrDieMovedValue) { - auto* str = new std::string(kStringElement); - std::unique_ptr value(str); - StatusOr> statusor(std::move(value)); - - std::unique_ptr moved_value = std::move(statusor).ValueOrDie(); - EXPECT_THAT(moved_value.get(), Eq(str)); - EXPECT_THAT(*moved_value, Eq(kStringElement)); -} - -TEST(StatusOrTest, MapToStatusOrUniquePtr) { - // A reduced version of a problematic type found in the wild. All of the - // operations below should compile. - using MapType = std::map>>; - - MapType a; - - // Move-construction - MapType b(std::move(a)); - - // Move-assignment - a = std::move(b); -} - -TEST(StatusOrTest, ValueOrOk) { - const StatusOr status_or = 0; - EXPECT_EQ(status_or.value_or(-1), 0); -} - -TEST(StatusOrTest, ValueOrDefault) { - const StatusOr status_or = absl::CancelledError(); - EXPECT_EQ(status_or.value_or(-1), -1); -} - -TEST(StatusOrTest, MoveOnlyValueOrOk) { - EXPECT_THAT(StatusOr>(absl::make_unique(0)) - .value_or(absl::make_unique(-1)), - Pointee(0)); -} - -TEST(StatusOr, MoveOnlyValueOrDefault) { - EXPECT_THAT(StatusOr>(absl::CancelledError()) - .value_or(absl::make_unique(-1)), - Pointee(-1)); -} - -} // namespace -} // namespace sapi diff --git a/sandboxed_api/var_proto.h b/sandboxed_api/var_proto.h index 4d1dac2..ffa8128 100644 --- a/sandboxed_api/var_proto.h +++ b/sandboxed_api/var_proto.h @@ -41,7 +41,7 @@ class Proto : public Pointable, public Var { explicit Proto(const T& proto) : wrapped_var_(SerializeProto(proto).value()) {} - static sapi::StatusOr> FromMessage(const T& proto) { + static absl::StatusOr> FromMessage(const T& proto) { SAPI_ASSIGN_OR_RETURN(std::vector len_val, SerializeProto(proto)); return Proto(len_val); } @@ -59,7 +59,7 @@ class Proto : public Pointable, public Var { void* GetLocal() const override { return wrapped_var_.GetLocal(); } // Returns a copy of the stored protobuf object. - sapi::StatusOr GetMessage() const { + absl::StatusOr GetMessage() const { return DeserializeProto( reinterpret_cast(wrapped_var_.GetData()), wrapped_var_.GetDataSize()); From fdf0483ca0fe65fcc03a96924decb79dbc70a1d8 Mon Sep 17 00:00:00 2001 From: Christian Blichmann Date: Thu, 3 Sep 2020 07:40:09 -0700 Subject: [PATCH 64/82] Migrate to open-source `absl::StatusOr<>` This removes our own fork of `absl::StatusOr<>`. Sandboxed API still includes a custom matcher for Googletest, as that is not open source yet. For compatibility, the `statusor.h` header is still retained and now aliases `sapi::StatusOr<>` to `absl::StatusOr<>`. PiperOrigin-RevId: 329916309 Change-Id: I0544b73a9e312dce499bc4128c28457e04ab9929 --- cmake/abseil/CMakeLists.txt.in | 2 +- sandboxed_api/CMakeLists.txt | 9 +- sandboxed_api/bazel/sapi_deps.bzl | 6 +- sandboxed_api/examples/zlib/CMakeLists.txt | 2 +- sandboxed_api/sandbox2/CMakeLists.txt | 17 +- .../examples/network_proxy/BUILD.bazel | 2 +- .../examples/network_proxy/CMakeLists.txt | 5 +- .../network_proxy/networkproxy_bin.cc | 1 + .../sandbox2/network_proxy/CMakeLists.txt | 14 +- sandboxed_api/sandbox2/testcases/BUILD.bazel | 7 +- sandboxed_api/sandbox2/util/CMakeLists.txt | 5 +- sandboxed_api/sandbox2/util_test.cc | 4 +- sandboxed_api/util/BUILD.bazel | 30 +-- sandboxed_api/util/CMakeLists.txt | 22 +- sandboxed_api/util/status_macros.h | 2 +- sandboxed_api/util/status_macros_test.cc | 27 +-- sandboxed_api/util/status_matchers.h | 2 +- sandboxed_api/util/statusor.h | 209 +----------------- 18 files changed, 81 insertions(+), 285 deletions(-) diff --git a/cmake/abseil/CMakeLists.txt.in b/cmake/abseil/CMakeLists.txt.in index 2484f6e..01aacba 100644 --- a/cmake/abseil/CMakeLists.txt.in +++ b/cmake/abseil/CMakeLists.txt.in @@ -18,7 +18,7 @@ project(absl-download NONE) include(ExternalProject) ExternalProject_Add(absl GIT_REPOSITORY https://github.com/abseil/abseil-cpp - GIT_TAG 6e18c7115df9b7ca0987cc346b1b1d4b3cc829b3 # 2020-04-28 + GIT_TAG 0e9921b75a0fdd639a504ec8443fc1fe801becd7 # 2020-09-02 SOURCE_DIR "${CMAKE_BINARY_DIR}/absl-src" BINARY_DIR "${CMAKE_BINARY_DIR}/absl-build" CONFIGURE_COMMAND "" diff --git a/sandboxed_api/CMakeLists.txt b/sandboxed_api/CMakeLists.txt index 0f4ca85..c2b6d2f 100644 --- a/sandboxed_api/CMakeLists.txt +++ b/sandboxed_api/CMakeLists.txt @@ -43,6 +43,8 @@ add_library(sapi_embed_file STATIC add_library(sapi::embed_file ALIAS sapi_embed_file) target_link_libraries(sapi_embed_file PRIVATE absl::flat_hash_map + absl::status + absl::statusor absl::strings absl::synchronization glog::glog @@ -65,6 +67,8 @@ add_library(sapi::sapi ALIAS sapi_sapi) target_link_libraries(sapi_sapi PRIVATE absl::flat_hash_map absl::memory + absl::status + absl::statusor absl::str_format absl::strings absl::synchronization @@ -76,7 +80,6 @@ target_link_libraries(sapi_sapi sandbox2::strerror sandbox2::util sapi::embed_file - sapi::status sapi::vars PUBLIC absl::core_headers sandbox2::client @@ -137,6 +140,8 @@ add_library(sapi_vars STATIC add_library(sapi::vars ALIAS sapi_vars) target_link_libraries(sapi_vars PRIVATE absl::core_headers + absl::status + absl::statusor absl::str_format absl::strings absl::synchronization @@ -147,7 +152,6 @@ target_link_libraries(sapi_vars PRIVATE sapi::lenval_core sapi::proto_arg_proto sapi::status - sapi::statusor sapi::var_type ) @@ -178,6 +182,7 @@ if(SAPI_ENABLE_TESTS) ) target_link_libraries(sapi_test PRIVATE absl::memory + absl::status benchmark sapi::sapi sapi::status diff --git a/sandboxed_api/bazel/sapi_deps.bzl b/sandboxed_api/bazel/sapi_deps.bzl index ade3211..ceff2f4 100644 --- a/sandboxed_api/bazel/sapi_deps.bzl +++ b/sandboxed_api/bazel/sapi_deps.bzl @@ -34,9 +34,9 @@ def sapi_deps(): maybe( http_archive, name = "com_google_absl", - sha256 = "6668ada01192e2b95b42bb3668cfa5282c047de5176f5e567028e12f8bfb8aef", # 2020-04-28 - strip_prefix = "abseil-cpp-6e18c7115df9b7ca0987cc346b1b1d4b3cc829b3", - urls = ["https://github.com/abseil/abseil-cpp/archive/6e18c7115df9b7ca0987cc346b1b1d4b3cc829b3.zip"], + sha256 = "8061df0ebbd3f599bcd3f5e57fb8003564d50a9b6a81a7f968fb0196b952365d", # 2020-09-02 + strip_prefix = "abseil-cpp-0e9921b75a0fdd639a504ec8443fc1fe801becd7", + urls = ["https://github.com/abseil/abseil-cpp/archive/0e9921b75a0fdd639a504ec8443fc1fe801becd7.zip"], ) maybe( http_archive, diff --git a/sandboxed_api/examples/zlib/CMakeLists.txt b/sandboxed_api/examples/zlib/CMakeLists.txt index 561b6d3..5750c7d 100644 --- a/sandboxed_api/examples/zlib/CMakeLists.txt +++ b/sandboxed_api/examples/zlib/CMakeLists.txt @@ -32,11 +32,11 @@ add_executable(main_zlib main_zlib.cc ) target_link_libraries(main_zlib PRIVATE + absl::status sapi::base glog::glog sapi::flags sapi::sapi sapi::status - sapi::statusor sapi::zlib_sapi ) diff --git a/sandboxed_api/sandbox2/CMakeLists.txt b/sandboxed_api/sandbox2/CMakeLists.txt index f5919ec..e70356f 100644 --- a/sandboxed_api/sandbox2/CMakeLists.txt +++ b/sandboxed_api/sandbox2/CMakeLists.txt @@ -271,6 +271,8 @@ target_link_libraries(sandbox2_sandbox2 absl::flat_hash_set absl::memory absl::optional + absl::status + absl::statusor absl::str_format absl::strings absl::synchronization @@ -300,7 +302,6 @@ target_link_libraries(sandbox2_sandbox2 sandbox2::util sandbox2::violation_proto sapi::base - sapi::statusor PUBLIC sapi::flags sapi::status sandbox2::logsink @@ -351,6 +352,8 @@ add_library(sandbox2_forkserver STATIC add_library(sandbox2::forkserver ALIAS sandbox2_forkserver) target_link_libraries(sandbox2_forkserver PRIVATE absl::memory + absl::status + absl::statusor absl::str_format absl::strings libcap::libcap @@ -369,7 +372,6 @@ target_link_libraries(sandbox2_forkserver PRIVATE sandbox2::util sapi::base sapi::raw_logging - sapi::statusor ) # sandboxed_api/sandbox2:fork_client @@ -397,6 +399,7 @@ target_link_libraries(sandbox2_mounts PRIVATE absl::core_headers absl::flat_hash_set absl::status + absl::statusor absl::str_format absl::strings protobuf::libprotobuf @@ -408,7 +411,6 @@ target_link_libraries(sandbox2_mounts PRIVATE sapi::base sapi::raw_logging sapi::status - sapi::statusor ) # sandboxed_api/sandbox2:namespace @@ -463,8 +465,8 @@ target_link_libraries(sandbox2_util sandbox2::strerror sapi::base sapi::raw_logging - sapi::statusor PUBLIC absl::status + absl::statusor ) target_compile_options(sandbox2_util PRIVATE # The default is 16384, however we need to do a clone with a @@ -482,12 +484,13 @@ add_library(sandbox2::buffer ALIAS sandbox2_buffer) target_link_libraries(sandbox2_buffer PRIVATE absl::core_headers absl::memory + absl::status + absl::statusor absl::strings sandbox2::strerror sandbox2::util sapi::base sapi::status - sapi::statusor ) # sandboxed_api/sandbox2:forkserver_proto @@ -527,6 +530,8 @@ add_library(sandbox2_comms STATIC add_library(sandbox2::comms ALIAS sandbox2_comms) target_link_libraries(sandbox2_comms PRIVATE absl::memory + absl::status + absl::statusor absl::str_format absl::strings sandbox2::strerror @@ -534,7 +539,6 @@ target_link_libraries(sandbox2_comms sapi::base sapi::raw_logging sapi::status_proto - sapi::statusor PUBLIC absl::core_headers absl::status absl::synchronization @@ -832,6 +836,7 @@ if(SAPI_ENABLE_TESTS) ) target_link_libraries(stack_trace_test PRIVATE absl::memory + absl::status absl::strings sandbox2::bpf_helper sandbox2::fileops diff --git a/sandboxed_api/sandbox2/examples/network_proxy/BUILD.bazel b/sandboxed_api/sandbox2/examples/network_proxy/BUILD.bazel index fb07dd5..ccbefc5 100644 --- a/sandboxed_api/sandbox2/examples/network_proxy/BUILD.bazel +++ b/sandboxed_api/sandbox2/examples/network_proxy/BUILD.bazel @@ -27,7 +27,6 @@ cc_binary( deps = [ "//sandboxed_api/sandbox2", "//sandboxed_api/sandbox2:comms", - "//sandboxed_api/sandbox2/network_proxy:filtering", "//sandboxed_api/sandbox2/util:bpf_helper", "//sandboxed_api/sandbox2/util:fileops", "//sandboxed_api/sandbox2/util:runfiles", @@ -50,6 +49,7 @@ cc_binary( "//sandboxed_api/util:status", "@com_google_absl//absl/status", "@com_google_absl//absl/status:statusor", + "@com_google_absl//absl/strings", "@com_google_absl//absl/strings:str_format", ], ) diff --git a/sandboxed_api/sandbox2/examples/network_proxy/CMakeLists.txt b/sandboxed_api/sandbox2/examples/network_proxy/CMakeLists.txt index 084c8ed..3df9f21 100644 --- a/sandboxed_api/sandbox2/examples/network_proxy/CMakeLists.txt +++ b/sandboxed_api/sandbox2/examples/network_proxy/CMakeLists.txt @@ -31,13 +31,14 @@ target_link_libraries(sandbox2_networkproxy_sandbox PRIVATE sapi::flags ) - # sandboxed_api/sandbox2/examples/networkproxy:networkproxy_bin add_executable(sandbox2_networkproxy_bin networkproxy_bin.cc ) add_executable(sandbox2::networkproxy_bin ALIAS sandbox2_networkproxy_bin) target_link_libraries(sandbox2_networkproxy_bin PRIVATE + absl::status + absl::statusor absl::str_format glog::glog gflags::gflags @@ -48,6 +49,4 @@ target_link_libraries(sandbox2_networkproxy_bin PRIVATE sapi::base sapi::flags sapi::status - sapi::statusor ) - diff --git a/sandboxed_api/sandbox2/examples/network_proxy/networkproxy_bin.cc b/sandboxed_api/sandbox2/examples/network_proxy/networkproxy_bin.cc index 84eb715..d5d01f0 100644 --- a/sandboxed_api/sandbox2/examples/network_proxy/networkproxy_bin.cc +++ b/sandboxed_api/sandbox2/examples/network_proxy/networkproxy_bin.cc @@ -14,6 +14,7 @@ #include "sandboxed_api/util/flag.h" #include "absl/status/status.h" #include "absl/status/statusor.h" +#include "absl/strings/str_cat.h" #include "absl/strings/str_format.h" #include "sandboxed_api/sandbox2/client.h" #include "sandboxed_api/sandbox2/comms.h" diff --git a/sandboxed_api/sandbox2/network_proxy/CMakeLists.txt b/sandboxed_api/sandbox2/network_proxy/CMakeLists.txt index 8ae6c40..c23d917 100644 --- a/sandboxed_api/sandbox2/network_proxy/CMakeLists.txt +++ b/sandboxed_api/sandbox2/network_proxy/CMakeLists.txt @@ -33,14 +33,14 @@ add_library(sandbox2_network_proxy_filtering STATIC filtering.h ) add_library(sandbox2::network_proxy_filtering ALIAS sandbox2_network_proxy_filtering) -target_link_libraries(sandbox2_network_proxy_filtering PRIVATE - absl::memory - glog::glog - sandbox2::comms - sandbox2::fileops - sapi::base +target_link_libraries(sandbox2_network_proxy_filtering + PRIVATE absl::memory + absl::status + glog::glog + sandbox2::comms + sandbox2::fileops + sapi::base PUBLIC sapi::status - sapi::statusor ) # sandboxed_api/sandbox2/network_proxy:client diff --git a/sandboxed_api/sandbox2/testcases/BUILD.bazel b/sandboxed_api/sandbox2/testcases/BUILD.bazel index 80a1441..1983e4d 100644 --- a/sandboxed_api/sandbox2/testcases/BUILD.bazel +++ b/sandboxed_api/sandbox2/testcases/BUILD.bazel @@ -41,7 +41,12 @@ STATIC_LINKOPTS = [ ] # TODO(https://github.com/bazelbuild/bazel/issues/8672): Remove this workaround -EXTRA_FULLY_STATIC_LINKOPTS = ["-l:libstdc++.a"] +# Change is scheduled for Bazel 4.0. Specifying +# `--incompatible_linkopts_to_linklibs` also works +EXTRA_FULLY_STATIC_LINKOPTS = [ + "-l:libstdc++.a", + "-l:libm.a", +] cc_binary( name = "abort", diff --git a/sandboxed_api/sandbox2/util/CMakeLists.txt b/sandboxed_api/sandbox2/util/CMakeLists.txt index 1e13da4..46af750 100644 --- a/sandboxed_api/sandbox2/util/CMakeLists.txt +++ b/sandboxed_api/sandbox2/util/CMakeLists.txt @@ -80,7 +80,6 @@ target_link_libraries(sandbox2_util_minielf PRIVATE sandbox2::util sapi::base sapi::raw_logging - sapi::statusor ) # sandboxed_api/sandbox2/util:temp_file @@ -95,7 +94,7 @@ target_link_libraries(sandbox2_util_temp_file sandbox2::strerror sapi::base PUBLIC absl::status - sapi::statusor + absl::statusor ) # sandboxed_api/sandbox2/util:maps_parser @@ -106,9 +105,9 @@ add_library(sandbox2_util_maps_parser STATIC add_library(sandbox2::maps_parser ALIAS sandbox2_util_maps_parser) target_link_libraries(sandbox2_util_maps_parser PRIVATE absl::status + absl::statusor absl::strings sapi::base - sapi::statusor ) # sandboxed_api/sandbox2/util:runfiles diff --git a/sandboxed_api/sandbox2/util_test.cc b/sandboxed_api/sandbox2/util_test.cc index 55de298..3fa21ff 100644 --- a/sandboxed_api/sandbox2/util_test.cc +++ b/sandboxed_api/sandbox2/util_test.cc @@ -23,8 +23,8 @@ #include "sandboxed_api/sandbox2/testing.h" #include "sandboxed_api/sandbox2/util/path.h" -using testing::Gt; -using testing::IsTrue; +using ::testing::Gt; +using ::testing::IsTrue; namespace sandbox2 { namespace util { diff --git a/sandboxed_api/util/BUILD.bazel b/sandboxed_api/util/BUILD.bazel index 6f21685..d0f453c 100644 --- a/sandboxed_api/util/BUILD.bazel +++ b/sandboxed_api/util/BUILD.bazel @@ -45,20 +45,15 @@ cc_library( cc_library( name = "statusor", hdrs = ["statusor.h"], - copts = sapi_platform_copts(), - visibility = ["//visibility:public"], + deprecation = "Migrate to `absl::StatusOr`", deps = [ - ":raw_logging", - ":status", "@com_google_absl//absl/base:core_headers", - "@com_google_absl//absl/base:log_severity", - "@com_google_absl//absl/status", - "@com_google_absl//absl/types:variant", + "@com_google_absl//absl/status:statusor", ], ) -# gMock matchers for absl::Status and sapi::StatusOr and a gUnit printer -# extension for sapi::StatusOr. +# gMock matchers for absl::Status and absl::StatusOr and a gUnit printer +# extension. Adapted from the version in Asylo. cc_library( name = "status_matchers", testonly = 1, @@ -67,8 +62,8 @@ cc_library( visibility = ["//visibility:public"], deps = [ ":status", - ":statusor", "@com_google_absl//absl/status", + "@com_google_absl//absl/status:statusor", "@com_google_absl//absl/types:optional", "@com_google_googletest//:gtest", ], @@ -87,19 +82,6 @@ cc_test( ], ) -# Tests for the StatusOr template class. -cc_test( - name = "statusor_test", - srcs = ["statusor_test.cc"], - copts = sapi_platform_copts(), - deps = [ - ":status", - ":status_matchers", - ":statusor", - "@com_google_googletest//:gtest_main", - ], -) - # Tests for the Status macros. cc_test( name = "status_macros_test", @@ -108,9 +90,9 @@ cc_test( deps = [ ":status", ":status_matchers", - ":statusor", "@com_google_absl//absl/memory", "@com_google_absl//absl/status", + "@com_google_absl//absl/status:statusor", "@com_google_absl//absl/strings", "@com_google_googletest//:gtest_main", ], diff --git a/sandboxed_api/util/CMakeLists.txt b/sandboxed_api/util/CMakeLists.txt index 4bd6d25..9564188 100644 --- a/sandboxed_api/util/CMakeLists.txt +++ b/sandboxed_api/util/CMakeLists.txt @@ -48,11 +48,10 @@ add_library(sapi_util_statusor STATIC ) add_library(sapi::statusor ALIAS sapi_util_statusor) target_link_libraries(sapi_util_statusor PRIVATE - absl::base absl::core_headers - absl::variant - sapi::raw_logging - sapi::status + absl::status + absl::statusor + sapi::base ) # sandboxed_api/util:flag @@ -90,7 +89,6 @@ if(SAPI_ENABLE_TESTS) sapi::base PUBLIC absl::status sapi::status - sapi::statusor ) # sandboxed_api/util:status_test @@ -98,20 +96,24 @@ if(SAPI_ENABLE_TESTS) status_test.cc ) target_link_libraries(status_test PRIVATE + sapi::status sapi::status_matchers sapi::test_main + absl::status absl::type_traits ) gtest_discover_tests(status_test) - # sandboxed_api/util:statusor_test - add_executable(statusor_test - statusor_test.cc + # sandboxed_api/util:status_macros_test + add_executable(status_macros_test + status_macros_test.cc ) - target_link_libraries(statusor_test PRIVATE + target_link_libraries(status_macros_test PRIVATE sapi::status_matchers sapi::test_main + absl::status + absl::statusor absl::type_traits ) - gtest_discover_tests(statusor_test) + gtest_discover_tests(status_macros_test) endif() diff --git a/sandboxed_api/util/status_macros.h b/sandboxed_api/util/status_macros.h index 8c7c011..d22215b 100644 --- a/sandboxed_api/util/status_macros.h +++ b/sandboxed_api/util/status_macros.h @@ -42,6 +42,6 @@ if (ABSL_PREDICT_FALSE(!statusor.ok())) { \ return statusor.status(); \ } \ - lhs = std::move(statusor).ValueOrDie(); + lhs = std::move(statusor).value(); #endif // THIRD_PARTY_SAPI_UTIL_STATUS_MACROS_H_ diff --git a/sandboxed_api/util/status_macros_test.cc b/sandboxed_api/util/status_macros_test.cc index f74c83c..647149b 100644 --- a/sandboxed_api/util/status_macros_test.cc +++ b/sandboxed_api/util/status_macros_test.cc @@ -20,10 +20,10 @@ #include "gtest/gtest.h" #include "absl/memory/memory.h" #include "absl/status/status.h" +#include "absl/status/statusor.h" #include "absl/strings/str_cat.h" #include "sandboxed_api/util/status.h" #include "sandboxed_api/util/status_matchers.h" -#include "sandboxed_api/util/statusor.h" namespace sapi { namespace { @@ -52,17 +52,17 @@ TEST(ReturnIfError, ReturnsOnErrorFromLambda) { TEST(AssignOrReturn, AssignsMultipleVariablesInSequence) { auto func = []() -> absl::Status { int value1; - SAPI_ASSIGN_OR_RETURN(value1, StatusOr(1)); + SAPI_ASSIGN_OR_RETURN(value1, absl::StatusOr(1)); EXPECT_EQ(1, value1); int value2; - SAPI_ASSIGN_OR_RETURN(value2, StatusOr(2)); + SAPI_ASSIGN_OR_RETURN(value2, absl::StatusOr(2)); EXPECT_EQ(2, value2); int value3; - SAPI_ASSIGN_OR_RETURN(value3, StatusOr(3)); + SAPI_ASSIGN_OR_RETURN(value3, absl::StatusOr(3)); EXPECT_EQ(3, value3); int value4; SAPI_ASSIGN_OR_RETURN(value4, - StatusOr(absl::UnknownError("EXPECTED"))); + absl::StatusOr(absl::UnknownError("EXPECTED"))); return absl::UnknownError(absl::StrCat("ERROR: assigned value ", value4)); }; @@ -72,11 +72,12 @@ TEST(AssignOrReturn, AssignsMultipleVariablesInSequence) { TEST(AssignOrReturn, AssignsRepeatedlyToSingleVariable) { auto func = []() -> absl::Status { int value = 1; - SAPI_ASSIGN_OR_RETURN(value, StatusOr(2)); + SAPI_ASSIGN_OR_RETURN(value, absl::StatusOr(2)); EXPECT_EQ(2, value); - SAPI_ASSIGN_OR_RETURN(value, StatusOr(3)); + SAPI_ASSIGN_OR_RETURN(value, absl::StatusOr(3)); EXPECT_EQ(3, value); - SAPI_ASSIGN_OR_RETURN(value, StatusOr(absl::UnknownError("EXPECTED"))); + SAPI_ASSIGN_OR_RETURN(value, + absl::StatusOr(absl::UnknownError("EXPECTED"))); return absl::UnknownError("ERROR"); }; @@ -87,7 +88,7 @@ TEST(AssignOrReturn, MovesUniquePtr) { auto func = []() -> absl::Status { std::unique_ptr ptr; SAPI_ASSIGN_OR_RETURN( - ptr, StatusOr>(absl::make_unique(1))); + ptr, absl::StatusOr>(absl::make_unique(1))); EXPECT_EQ(*ptr, 1); return absl::UnknownError("EXPECTED"); }; @@ -98,8 +99,8 @@ TEST(AssignOrReturn, MovesUniquePtr) { TEST(AssignOrReturn, DoesNotAssignUniquePtrOnErrorStatus) { auto func = []() -> absl::Status { std::unique_ptr ptr; - SAPI_ASSIGN_OR_RETURN( - ptr, StatusOr>(absl::UnknownError("EXPECTED"))); + SAPI_ASSIGN_OR_RETURN(ptr, absl::StatusOr>( + absl::UnknownError("EXPECTED"))); EXPECT_EQ(ptr, nullptr); return absl::OkStatus(); }; @@ -111,10 +112,10 @@ TEST(AssignOrReturn, MovesUniquePtrRepeatedlyToSingleVariable) { auto func = []() -> absl::Status { std::unique_ptr ptr; SAPI_ASSIGN_OR_RETURN( - ptr, StatusOr>(absl::make_unique(1))); + ptr, absl::StatusOr>(absl::make_unique(1))); EXPECT_EQ(*ptr, 1); SAPI_ASSIGN_OR_RETURN( - ptr, StatusOr>(absl::make_unique(2))); + ptr, absl::StatusOr>(absl::make_unique(2))); EXPECT_EQ(*ptr, 2); return absl::UnknownError("EXPECTED"); }; diff --git a/sandboxed_api/util/status_matchers.h b/sandboxed_api/util/status_matchers.h index c7ed2e5..f181fc6 100644 --- a/sandboxed_api/util/status_matchers.h +++ b/sandboxed_api/util/status_matchers.h @@ -19,9 +19,9 @@ #include "gmock/gmock.h" #include "absl/status/status.h" +#include "absl/status/statusor.h" #include "absl/types/optional.h" #include "sandboxed_api/util/status_macros.h" -#include "sandboxed_api/util/statusor.h" #define SAPI_ASSERT_OK_AND_ASSIGN(lhs, rexpr) \ SAPI_ASSERT_OK_AND_ASSIGN_IMPL( \ diff --git a/sandboxed_api/util/statusor.h b/sandboxed_api/util/statusor.h index f1f5be3..51bd488 100644 --- a/sandboxed_api/util/statusor.h +++ b/sandboxed_api/util/statusor.h @@ -12,220 +12,17 @@ // See the License for the specific language governing permissions and // limitations under the License. -// This file and it's implementation provide a custom fork of -// util/task/statusor.h. This will become obsolete and will be replaced once -// Abseil releases absl::Status. - #ifndef THIRD_PARTY_SAPI_UTIL_STATUSOR_H_ #define THIRD_PARTY_SAPI_UTIL_STATUSOR_H_ -#include -#include - -#include "absl/base/internal/raw_logging.h" #include "absl/base/attributes.h" -#include "absl/base/log_severity.h" -#include "absl/status/status.h" -#include "absl/types/variant.h" -#include "sandboxed_api/util/raw_logging.h" +#include "absl/status/statusor.h" namespace sapi { template -class ABSL_MUST_USE_RESULT StatusOr { - template - friend class StatusOr; - - public: - using element_type = T; - - explicit StatusOr() : variant_(absl::UnknownError("")) {} - - StatusOr(const StatusOr&) = default; - StatusOr& operator=(const StatusOr&) = default; - - StatusOr(StatusOr&&) = default; - StatusOr& operator=(StatusOr&&) = default; - - // Not implemented: - // template StatusOr(const StatusOr& other) - // template StatusOr(StatusOr&& other) - - template - StatusOr& operator=(const StatusOr& other) { - if (other.ok()) { - variant_ = other.value(); - } else { - variant_ = other.status(); - } - return *this; - } - - template - StatusOr& operator=(StatusOr&& other) { - if (other.ok()) { - variant_ = std::move(other).value(); - } else { - variant_ = std::move(other).status(); - } - return *this; - } - - StatusOr(const T& value) : variant_(value) {} - - StatusOr(const absl::Status& status) : variant_(status) { EnsureNotOk(); } - - // Not implemented: - // template StatusOr& operator=(U&& value) - - StatusOr(T&& value) : variant_(std::move(value)) {} - - StatusOr(absl::Status&& value) : variant_(std::move(value)) {} - - StatusOr& operator=(absl::Status&& status) { - variant_ = std::move(status); - EnsureNotOk(); - } - - template - explicit StatusOr(absl::in_place_t, Args&&... args) - : StatusOr(T(std::forward(args)...)) {} - - template - explicit StatusOr(absl::in_place_t, std::initializer_list ilist, - Args&&... args) - : StatusOr(ilist, U(std::forward(args)...)) {} - - explicit operator bool() const { return ok(); } - - ABSL_MUST_USE_RESULT bool ok() const { - return absl::holds_alternative(variant_); - } - - const absl::Status& status() const& { - static const auto* ok_status = new absl::Status(); - return ok() ? *ok_status : absl::get(variant_); - } - - absl::Status status() && { - return ok() ? absl::OkStatus() - : std::move(absl::get(variant_)); - } - - const T& value() const& { - EnsureOk(); - return absl::get(variant_); - } - - T& value() & { - EnsureOk(); - return absl::get(variant_); - } - - const T&& value() const&& { - EnsureOk(); - return absl::get(std::move(variant_)); - } - - T&& value() && { - EnsureOk(); - return absl::get(std::move(variant_)); - } - - const T& ValueOrDie() const& { - EnsureOk(); - return absl::get(variant_); - } - - T& ValueOrDie() & { - EnsureOk(); - return absl::get(variant_); - } - - T&& ValueOrDie() && { - EnsureOk(); - return absl::get(std::move(variant_)); - } - - const T& operator*() const& { - EnsureOk(); - return absl::get(variant_); - } - - T& operator*() & { - EnsureOk(); - return absl::get(variant_); - } - - const T&& operator*() const&& { - EnsureOk(); - return absl::get(std::move(variant_)); - } - - T&& operator*() && { - EnsureOk(); - return absl::get(std::move(variant_)); - } - - const T* operator->() const { - EnsureOk(); - return &absl::get(variant_); - } - - T* operator->() { - EnsureOk(); - return &absl::get(variant_); - } - - template - T value_or(U&& default_value) const& { - if (ok()) { - return absl::get(variant_); - } - return std::forward(default_value); - } - - template - T value_or(U&& default_value) && { - if (ok()) { - return absl::get(std::move(variant_)); - } - return std::forward(default_value); - } - - void IgnoreError() const { /* no-op */ - } - - template - T& emplace(Args&&... args) { - return variant_.template emplace(std::forward(args)...); - } - - template - T& emplace(std::initializer_list ilist, Args&&... args) { - return variant_.template emplace(ilist, std::forward(args)...); - } - - private: - void EnsureOk() const { - if (!ok()) { - // GoogleTest needs this exact error message for death tests to work. - SAPI_RAW_LOG(FATAL, - "Attempting to fetch value instead of handling error %s", - status().message()); - } - } - - void EnsureNotOk() const { - if (ok()) { - SAPI_RAW_LOG( - FATAL, - "An OK status is not a valid constructor argument to StatusOr"); - } - } - - absl::variant variant_; -}; +using StatusOr ABSL_DEPRECATED("Use absl::StatusOr instead") = + absl::StatusOr; } // namespace sapi From 1869fe515ff110f5b3b12f72ff0e5d9afd04a314 Mon Sep 17 00:00:00 2001 From: doinachiroiu Date: Thu, 3 Sep 2020 14:59:54 +0000 Subject: [PATCH 65/82] Required changes resolved --- oss-internship-2020/pffft/CMakeLists.txt | 3 -- oss-internship-2020/pffft/README.md | 15 ++++---- .../pffft/main_pffft_sandboxed.cc | 36 ++++++++++--------- 3 files changed, 26 insertions(+), 28 deletions(-) diff --git a/oss-internship-2020/pffft/CMakeLists.txt b/oss-internship-2020/pffft/CMakeLists.txt index be687cd..b172009 100644 --- a/oss-internship-2020/pffft/CMakeLists.txt +++ b/oss-internship-2020/pffft/CMakeLists.txt @@ -19,8 +19,6 @@ project(pffft CXX C) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED True) -set(PFFFT_ROOT_DIR https://bitbucket.org/jpommier/pffft.git) - add_library(pffft STATIC master/pffft.c master/pffft.h @@ -45,7 +43,6 @@ endif() target_link_libraries(pffft PUBLIC ${MATH_LIBS}) - # Adding dependencies set(SAPI_ROOT "../.." CACHE PATH "Path to the Sandboxed API source tree") # Then configure: diff --git a/oss-internship-2020/pffft/README.md b/oss-internship-2020/pffft/README.md index 194c6b6..2e023c2 100644 --- a/oss-internship-2020/pffft/README.md +++ b/oss-internship-2020/pffft/README.md @@ -4,16 +4,13 @@ Build System: CMake OS: Linux ### Check out the PFFFT library & CMake set up -`git checkout -b master` - -`git submodule update --init --recursive` - -`mkdir -p build && cd build` - -`cmake .. -G Ninja -DPFFFT_ROOT_DIR=$PWD` - -`ninja` +``` +git submodule update --init --recursive +mkdir -p build && cd build +cmake .. -G Ninja -DPFFFT_ROOT_DIR=$PWD +ninjas +``` ### For testing: `cd build`, then `./pffft_sandboxed` diff --git a/oss-internship-2020/pffft/main_pffft_sandboxed.cc b/oss-internship-2020/pffft/main_pffft_sandboxed.cc index 51e271d..f922275 100644 --- a/oss-internship-2020/pffft/main_pffft_sandboxed.cc +++ b/oss-internship-2020/pffft/main_pffft_sandboxed.cc @@ -136,51 +136,55 @@ absl::Status PffftMain() { if (simd_size_iter == 0) simd_size_iter = 1; if (complex) { - api.cffti(n, work_array.PtrBoth()).IgnoreError(); + SAPI_RETURN_IF_ERROR(api.cffti(n, work_array.PtrBoth())) } else { - api.rffti(n, work_array.PtrBoth()).IgnoreError(); + SAPI_RETURN_IF_ERROR(api.rffti(n, work_array.PtrBoth())); } t0 = UclockSec(); for (int iter = 0; iter < simd_size_iter; ++iter) { if (complex) { - api.cfftf(n, x_array.PtrBoth(), work_array.PtrBoth()).IgnoreError(); - api.cfftb(n, x_array.PtrBoth(), work_array.PtrBoth()).IgnoreError(); + SAPI_RETURN_IF_ERROR( + api.cfftf(n, x_array.PtrBoth(), work_array.PtrBoth())); + SAPI_RETURN_IF_ERROR( + api.cfftb(n, x_array.PtrBoth(), work_array.PtrBoth())); } else { - api.rfftf(n, x_array.PtrBoth(), work_array.PtrBoth()).IgnoreError(); - api.rfftb(n, x_array.PtrBoth(), work_array.PtrBoth()).IgnoreError(); + SAPI_RETURN_IF_ERROR( + api.rfftf(n, x_array.PtrBoth(), work_array.PtrBoth())); + SAPI_RETURN_IF_ERROR( + api.rfftb(n, x_array.PtrBoth(), work_array.PtrBoth())); } } t1 = UclockSec(); flops = (simd_size_iter * 2) * - ((complex ? 5 : 2.5) * n * log((double)n) / M_LN2); + ((complex ? 5 : 2.5) * static_cast(n) * log(static_cast(n)) / M_LN2); ShowOutput("FFTPack", n, complex, flops, t0, t1, simd_size_iter); } // PFFFT benchmark { SAPI_ASSIGN_OR_RETURN( - PFFFT_Setup *s, + PFFFT_Setup * s, api.pffft_new_setup(n, complex ? PFFFT_COMPLEX : PFFFT_REAL)); sapi::v::RemotePtr s_reg(s); t0 = UclockSec(); for (int iter = 0; iter < max_iter; ++iter) { - api.pffft_transform(&s_reg, x_array.PtrBoth(), z_array.PtrBoth(), - y_array.PtrBoth(), PFFFT_FORWARD) - .IgnoreError(); - api.pffft_transform(&s_reg, x_array.PtrBoth(), z_array.PtrBoth(), - y_array.PtrBoth(), PFFFT_FORWARD) - .IgnoreError(); + SAPI_RETURN_IF_ERROR( + api.pffft_transform(&s_reg, x_array.PtrBoth(), z_array.PtrBoth(), + y_array.PtrBoth(), PFFFT_FORWARD)); + SAPI_RETURN_IF_ERROR( + api.pffft_transform(&s_reg, x_array.PtrBoth(), z_array.PtrBoth(), + y_array.PtrBoth(), PFFFT_FORWARD)); } t1 = UclockSec(); - api.pffft_destroy_setup(&s_reg).IgnoreError(); + SAPI_RETURN_IF_ERROR(api.pffft_destroy_setup(&s_reg)); flops = (max_iter * 2) * ((complex ? 5 : 2.5) * static_cast(n) * - log((double)n) / M_LN2); + log(static_cast(n)) / M_LN2); ShowOutput("PFFFT", n, complex, flops, t0, t1, max_iter); LOG(INFO) << "n = " << n << " SUCCESSFULLY"; From 776e34502a3e58dac0c61f7cf7f9a328bd63d658 Mon Sep 17 00:00:00 2001 From: Sandboxed API Team Date: Tue, 8 Sep 2020 12:23:59 -0700 Subject: [PATCH 66/82] Internal cleanup migrating StatusOr. PiperOrigin-RevId: 330561315 Change-Id: Ie8d8857e7fa5819be3358b26425790ede97c99f8 --- sandboxed_api/tools/clang_generator/types.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sandboxed_api/tools/clang_generator/types.h b/sandboxed_api/tools/clang_generator/types.h index b4d5946..7947df3 100644 --- a/sandboxed_api/tools/clang_generator/types.h +++ b/sandboxed_api/tools/clang_generator/types.h @@ -68,7 +68,7 @@ std::string MapQualTypeParameter(const clang::ASTContext& context, // Maps a qualified type used as a function return type to a type name // compatible with the generated Sandboxed API. Uses MapQualTypeParameter() and -// wraps the type in a absl::StatusOr<> if qual is non-void. Otherwise returns +// wraps the type in an absl::StatusOr<> if qual is non-void. Otherwise returns // absl::Status. std::string MapQualTypeReturn(const clang::ASTContext& context, clang::QualType qual); From 1f8e88586b46fea3252264369147564164b46cb4 Mon Sep 17 00:00:00 2001 From: Kevin Hamacher Date: Wed, 9 Sep 2020 02:12:05 -0700 Subject: [PATCH 67/82] Log details when executor fails to open the sandboxee binary PiperOrigin-RevId: 330680717 Change-Id: I4ec855861196177321783dc94f2e05a28e84d512 --- sandboxed_api/sandbox2/executor.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sandboxed_api/sandbox2/executor.cc b/sandboxed_api/sandbox2/executor.cc index 6170a9d..f2afb3a 100644 --- a/sandboxed_api/sandbox2/executor.cc +++ b/sandboxed_api/sandbox2/executor.cc @@ -86,7 +86,7 @@ pid_t Executor::StartSubProcess(int32_t clone_flags, const Namespace* ns, if (!path_.empty()) { exec_fd_ = open(path_.c_str(), O_PATH); if (exec_fd_ < 0) { - LOG(ERROR) << "Could not open file " << path_; + PLOG(ERROR) << "Could not open file " << path_; return -1; } } From 6a1e4b881c2c24f65e74f5c71b7a11b2632b8278 Mon Sep 17 00:00:00 2001 From: Christian Blichmann Date: Thu, 10 Sep 2020 05:47:32 -0700 Subject: [PATCH 68/82] Introduce config header to centralize CPU architecture checks This allows us to remove some uses of macros. Related changes: - Make it clear that we support hosting sandboxed binaries from 64-bit processes only. CPU architectures are x86-64 and POWER64 (little endian). - Introduced CPU architecture macros, abstracting away compiler specifics PiperOrigin-RevId: 330918134 Change-Id: Ife7ad5f14723eec9f68055127b0583b8aecd38dd --- sandboxed_api/sandbox2/BUILD.bazel | 19 +++++ sandboxed_api/sandbox2/CMakeLists.txt | 21 +++++ sandboxed_api/sandbox2/buffer_test.cc | 7 +- sandboxed_api/sandbox2/config.h | 83 +++++++++++++++++++ sandboxed_api/sandbox2/limits_test.cc | 1 + sandboxed_api/sandbox2/monitor.cc | 3 +- sandboxed_api/sandbox2/mounts.cc | 21 +++-- sandboxed_api/sandbox2/namespace_test.cc | 1 + .../sandbox2/network_proxy/BUILD.bazel | 1 + .../sandbox2/network_proxy/CMakeLists.txt | 1 + .../sandbox2/network_proxy/client.cc | 12 +-- sandboxed_api/sandbox2/policy.cc | 16 ++-- sandboxed_api/sandbox2/policy_test.cc | 9 +- sandboxed_api/sandbox2/policybuilder.cc | 22 ++--- sandboxed_api/sandbox2/policybuilder.h | 6 +- sandboxed_api/sandbox2/regs.cc | 69 ++++++++------- sandboxed_api/sandbox2/regs.h | 13 ++- sandboxed_api/sandbox2/result.h | 5 +- sandboxed_api/sandbox2/sandbox2_test.cc | 1 + sandboxed_api/sandbox2/syscall.cc | 47 +++++------ sandboxed_api/sandbox2/syscall.h | 32 +++---- sandboxed_api/sandbox2/syscall_defs.cc | 23 ++--- sandboxed_api/sandbox2/syscall_defs.h | 3 +- sandboxed_api/sandbox2/syscall_test.cc | 5 +- sandboxed_api/sandbox2/testcases/BUILD.bazel | 1 + .../sandbox2/testcases/CMakeLists.txt | 1 + sandboxed_api/sandbox2/testcases/policy.cc | 10 ++- sandboxed_api/sandbox2/util.cc | 14 ++-- 28 files changed, 282 insertions(+), 165 deletions(-) create mode 100644 sandboxed_api/sandbox2/config.h diff --git a/sandboxed_api/sandbox2/BUILD.bazel b/sandboxed_api/sandbox2/BUILD.bazel index 2bf7542..839a7e0 100644 --- a/sandboxed_api/sandbox2/BUILD.bazel +++ b/sandboxed_api/sandbox2/BUILD.bazel @@ -26,6 +26,13 @@ licenses(["notice"]) # Apache 2.0 exports_files(["testdata/hostname"]) +cc_library( + name = "config", + hdrs = ["config.h"], + copts = sapi_platform_copts(), + deps = ["@com_google_absl//absl/base:config"], +) + cc_library( name = "bpfdisassembler", srcs = ["bpfdisassembler.cc"], @@ -40,6 +47,7 @@ cc_library( hdrs = ["regs.h"], copts = sapi_platform_copts(), deps = [ + ":config", ":syscall", ":violation_cc_proto", "//sandboxed_api/sandbox2/util:strerror", @@ -60,6 +68,7 @@ cc_library( copts = sapi_platform_copts(), visibility = ["//visibility:public"], deps = [ + ":config", ":util", "@com_google_absl//absl/strings", "@com_google_absl//absl/strings:str_format", @@ -73,6 +82,7 @@ cc_test( srcs = ["syscall_test.cc"], copts = sapi_platform_copts(), deps = [ + ":config", ":syscall", "@com_google_absl//absl/strings", "@com_google_googletest//:gtest_main", @@ -85,6 +95,7 @@ cc_library( hdrs = ["result.h"], copts = sapi_platform_copts(), deps = [ + ":config", ":regs", ":syscall", ":util", @@ -272,6 +283,7 @@ cc_library( deps = [ ":client", ":comms", + ":config", ":executor", ":fork_client", ":forkserver_cc_proto", @@ -404,6 +416,7 @@ cc_library( hdrs = ["mounts.h"], copts = sapi_platform_copts(), deps = [ + ":config", ":mounttree_cc_proto", "//sandboxed_api/sandbox2/util:file_base", "//sandboxed_api/sandbox2/util:fileops", @@ -468,6 +481,7 @@ cc_test( ], deps = [ ":comms", + ":config", ":namespace", ":sandbox2", ":testing", @@ -505,6 +519,7 @@ cc_library( copts = sapi_platform_copts(), visibility = ["//visibility:public"], deps = [ + ":config", "//sandboxed_api/sandbox2/util:file_base", "//sandboxed_api/sandbox2/util:fileops", "//sandboxed_api/sandbox2/util:strerror", @@ -541,6 +556,7 @@ cc_test( deps = [ ":buffer", ":comms", + ":config", ":sandbox2", ":testing", "//sandboxed_api/util:status_matchers", @@ -629,6 +645,7 @@ cc_test( copts = sapi_platform_copts(), data = ["//sandboxed_api/sandbox2/testcases:limits"], deps = [ + ":config", ":limits", ":sandbox2", ":testing", @@ -671,6 +688,7 @@ cc_test( "//sandboxed_api/sandbox2/testcases:policy", ], deps = [ + ":config", ":limits", ":regs", ":sandbox2", @@ -695,6 +713,7 @@ cc_test( ], tags = ["local"], deps = [ + ":config", ":sandbox2", ":testing", "//sandboxed_api/sandbox2/util:bpf_helper", diff --git a/sandboxed_api/sandbox2/CMakeLists.txt b/sandboxed_api/sandbox2/CMakeLists.txt index e70356f..e6a746b 100644 --- a/sandboxed_api/sandbox2/CMakeLists.txt +++ b/sandboxed_api/sandbox2/CMakeLists.txt @@ -17,6 +17,16 @@ add_subdirectory(unwind) add_subdirectory(util) add_subdirectory(network_proxy) +# sandboxed_api/sandbox2:config +add_library(sandbox2_config STATIC + config.h +) +add_library(sandbox2::config ALIAS sandbox2_config) +target_link_libraries(sandbox2_config PRIVATE + absl::config + sapi::base +) + # sandboxed_api/sandbox2:bpfdisassembler add_library(sandbox2_bpfdisassembler STATIC bpfdisassembler.cc @@ -37,6 +47,7 @@ add_library(sandbox2::regs ALIAS sandbox2_regs) target_link_libraries(sandbox2_regs PRIVATE absl::core_headers absl::strings + sandbox2::config sandbox2::strerror sandbox2::syscall sandbox2::violation_proto @@ -72,6 +83,7 @@ target_link_libraries(sandbox2_result PRIVATE absl::base absl::memory absl::strings + sandbox2::config sandbox2::regs sandbox2::syscall sandbox2::util @@ -280,6 +292,7 @@ target_link_libraries(sandbox2_sandbox2 sandbox2::bpf_helper sandbox2::client sandbox2::comms + sandbox2::config sandbox2::executor sandbox2::file_base sandbox2::fileops @@ -403,6 +416,7 @@ target_link_libraries(sandbox2_mounts PRIVATE absl::str_format absl::strings protobuf::libprotobuf + sandbox2::config sandbox2::file_base sandbox2::fileops sandbox2::minielf @@ -460,6 +474,7 @@ target_link_libraries(sandbox2_util PRIVATE absl::core_headers absl::str_format absl::strings + sandbox2::config sandbox2::file_base sandbox2::fileops sandbox2::strerror @@ -570,6 +585,7 @@ if(SAPI_ENABLE_TESTS) ) target_link_libraries(syscall_test PRIVATE absl::strings + sandbox2::config sandbox2::syscall sapi::test_main ) @@ -608,6 +624,7 @@ if(SAPI_ENABLE_TESTS) absl::memory absl::strings sandbox2::comms + sandbox2::config sandbox2::fileops sandbox2::namespace sandbox2::sandbox2 @@ -632,6 +649,7 @@ if(SAPI_ENABLE_TESTS) absl::memory sandbox2::buffer sandbox2::comms + sandbox2::config sandbox2::sandbox2 sandbox2::testing sapi::status_matchers @@ -706,6 +724,7 @@ if(SAPI_ENABLE_TESTS) target_link_libraries(limits_test PRIVATE absl::memory sandbox2::bpf_helper + sandbox2::config sandbox2::limits sandbox2::sandbox2 sandbox2::testing @@ -755,6 +774,7 @@ if(SAPI_ENABLE_TESTS) absl::memory absl::strings sandbox2::bpf_helper + sandbox2::config sandbox2::limits sandbox2::regs sandbox2::sandbox2 @@ -780,6 +800,7 @@ if(SAPI_ENABLE_TESTS) absl::memory absl::strings sandbox2::bpf_helper + sandbox2::config sandbox2::sandbox2 sandbox2::testing sapi::status_matchers diff --git a/sandboxed_api/sandbox2/buffer_test.cc b/sandboxed_api/sandbox2/buffer_test.cc index 78d8cf9..876be31 100644 --- a/sandboxed_api/sandbox2/buffer_test.cc +++ b/sandboxed_api/sandbox2/buffer_test.cc @@ -29,6 +29,7 @@ #include "gtest/gtest.h" #include "absl/memory/memory.h" #include "sandboxed_api/sandbox2/comms.h" +#include "sandboxed_api/sandbox2/config.h" #include "sandboxed_api/sandbox2/executor.h" #include "sandboxed_api/sandbox2/ipc.h" #include "sandboxed_api/sandbox2/policy.h" @@ -90,12 +91,6 @@ std::unique_ptr BufferTestcasePolicy() { .BlockSyscallWithErrno(__NR_access, ENOENT) .BuildOrDie(); -#if defined(__powerpc64__) - - s2p->AllowUnsafeMmapFiles(); - s2p->AllowUnsafeMmapShared(); -#endif /* defined(__powerpc64__) */ - return s2p; } diff --git a/sandboxed_api/sandbox2/config.h b/sandboxed_api/sandbox2/config.h new file mode 100644 index 0000000..ba0fec5 --- /dev/null +++ b/sandboxed_api/sandbox2/config.h @@ -0,0 +1,83 @@ +// Copyright 2020 Google LLC +// +// 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. + +#ifndef SANDBOXED_API_SANDBOX2_CONFIG_H_ +#define SANDBOXED_API_SANDBOX2_CONFIG_H_ + +#include + +#include "absl/base/config.h" + +// GCC/Clang define __x86_64__, Visual Studio uses _M_X64 +#if defined(__x86_64__) || defined(_M_X64) +#define SAPI_X86_64 1 + +// Check various spellings for 64-bit POWER. Not checking for Visual Studio, as +// it does not support 64-bit POWER. +#elif (defined(__PPC64__) || defined(__powerpc64__) || defined(__ppc64__)) && \ + defined(ABSL_IS_LITTLE_ENDIAN) +#define SAPI_PPC64_LE 1 + +// Spellings for AArch64 +#elif defined(__aarch64__) || defined(_M_ARM64) +#define SAPI_ARM64 1 +#endif + +namespace sandbox2 { + +namespace cpu { + +// CPU architectures known to Sandbox2 +enum Architecture : uint16_t { + // Linux: Use a magic value, so it can be easily spotted in the seccomp-bpf + // bytecode decompilation stream. Must be < (1<<15), as/ that's the size of + // data which can be returned by BPF. + kUnknown = 0xCAF0, + kX8664, + kX86, + kPPC64LE, + kArm64, +}; + +} // namespace cpu + +namespace host_cpu { + +// Returns the current host CPU architecture if supported. If not supported, +// returns cpu::kUnknown. +constexpr cpu::Architecture Architecture() { +#if defined(SAPI_X86_64) + return cpu::kX8664; +#elif defined(SAPI_PPC64_LE) + return cpu::kPPC64LE; +#else + return cpu::kUnknown; +#endif +} + +constexpr bool IsX8664() { return Architecture() == cpu::kX8664; } + +constexpr bool IsPPC64LE() { return Architecture() == cpu::kPPC64LE; } + +constexpr bool IsArm64() { return Architecture() == cpu::kArm64; } + +} // namespace host_cpu + +static_assert(host_cpu::Architecture() != cpu::kUnknown, + "Host CPU architecture is not supported: One of x86-64 or " + "POWER64 (little endian) is required."); + +} // namespace sandbox2 + +#endif // SANDBOXED_API_SANDBOX2_CONFIG_H_ diff --git a/sandboxed_api/sandbox2/limits_test.cc b/sandboxed_api/sandbox2/limits_test.cc index 1f3386a..35d9bba 100644 --- a/sandboxed_api/sandbox2/limits_test.cc +++ b/sandboxed_api/sandbox2/limits_test.cc @@ -23,6 +23,7 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" #include "absl/memory/memory.h" +#include "sandboxed_api/sandbox2/config.h" #include "sandboxed_api/sandbox2/executor.h" #include "sandboxed_api/sandbox2/policy.h" #include "sandboxed_api/sandbox2/policybuilder.h" diff --git a/sandboxed_api/sandbox2/monitor.cc b/sandboxed_api/sandbox2/monitor.cc index 10e0710..d15c1af 100644 --- a/sandboxed_api/sandbox2/monitor.cc +++ b/sandboxed_api/sandbox2/monitor.cc @@ -49,6 +49,7 @@ #include "absl/time/time.h" #include "sandboxed_api/sandbox2/client.h" #include "sandboxed_api/sandbox2/comms.h" +#include "sandboxed_api/sandbox2/config.h" #include "sandboxed_api/sandbox2/executor.h" #include "sandboxed_api/sandbox2/limits.h" #include "sandboxed_api/sandbox2/mounts.h" @@ -761,7 +762,7 @@ void Monitor::LogSyscallViolation(const Syscall& syscall) const { void Monitor::EventPtraceSeccomp(pid_t pid, int event_msg) { // If the seccomp-policy is using RET_TRACE, we request that it returns the // syscall architecture identifier in the SECCOMP_RET_DATA. - const auto syscall_arch = static_cast(event_msg); + const auto syscall_arch = static_cast(event_msg); Regs regs(pid); auto status = regs.Fetch(); if (!status.ok()) { diff --git a/sandboxed_api/sandbox2/mounts.cc b/sandboxed_api/sandbox2/mounts.cc index 7e800ea..d722e6f 100644 --- a/sandboxed_api/sandbox2/mounts.cc +++ b/sandboxed_api/sandbox2/mounts.cc @@ -34,6 +34,7 @@ #include "absl/strings/str_join.h" #include "absl/strings/str_split.h" #include "absl/strings/string_view.h" +#include "sandboxed_api/sandbox2/config.h" #include "sandboxed_api/sandbox2/util/fileops.h" #include "sandboxed_api/sandbox2/util/minielf.h" #include "sandboxed_api/sandbox2/util/path.h" @@ -132,15 +133,19 @@ std::string ResolveLibraryPath(absl::string_view lib_name, return ""; } +constexpr absl::string_view GetPlatformCPUName() { + switch (host_cpu::Architecture()) { + case cpu::kX8664: + return "x86_64"; + case cpu::kPPC64LE: + return "ppc64"; + default: + return "unknown"; + } +} + std::string GetPlatform(absl::string_view interpreter) { -#if defined(__x86_64__) - constexpr absl::string_view kCpuPlatform = "x86_64"; -#elif defined(__powerpc64__) - constexpr absl::string_view kCpuPlatform = "ppc64"; -#else - constexpr absl::string_view kCpuPlatform = "unknown"; -#endif - return absl::StrCat(kCpuPlatform, "-linux-gnu"); + return absl::StrCat(GetPlatformCPUName(), "-linux-gnu"); } } // namespace diff --git a/sandboxed_api/sandbox2/namespace_test.cc b/sandboxed_api/sandbox2/namespace_test.cc index 89da97b..2bedf16 100644 --- a/sandboxed_api/sandbox2/namespace_test.cc +++ b/sandboxed_api/sandbox2/namespace_test.cc @@ -28,6 +28,7 @@ #include "absl/strings/numbers.h" #include "absl/strings/str_cat.h" #include "sandboxed_api/sandbox2/comms.h" +#include "sandboxed_api/sandbox2/config.h" #include "sandboxed_api/sandbox2/executor.h" #include "sandboxed_api/sandbox2/policy.h" #include "sandboxed_api/sandbox2/policybuilder.h" diff --git a/sandboxed_api/sandbox2/network_proxy/BUILD.bazel b/sandboxed_api/sandbox2/network_proxy/BUILD.bazel index f099939..ac58976 100644 --- a/sandboxed_api/sandbox2/network_proxy/BUILD.bazel +++ b/sandboxed_api/sandbox2/network_proxy/BUILD.bazel @@ -44,6 +44,7 @@ cc_library( visibility = ["//visibility:public"], deps = [ "//sandboxed_api/sandbox2:comms", + "//sandboxed_api/sandbox2:config", "//sandboxed_api/sandbox2/util:strerror", "//sandboxed_api/util:status", "@com_google_absl//absl/memory", diff --git a/sandboxed_api/sandbox2/network_proxy/CMakeLists.txt b/sandboxed_api/sandbox2/network_proxy/CMakeLists.txt index c23d917..781cb9d 100644 --- a/sandboxed_api/sandbox2/network_proxy/CMakeLists.txt +++ b/sandboxed_api/sandbox2/network_proxy/CMakeLists.txt @@ -54,6 +54,7 @@ target_link_libraries(sandbox2_network_proxy_client PRIVATE absl::synchronization glog::glog sandbox2::comms + sandbox2::config sandbox2::strerror sapi::base sapi::status diff --git a/sandboxed_api/sandbox2/network_proxy/client.cc b/sandboxed_api/sandbox2/network_proxy/client.cc index e528041..51b1260 100644 --- a/sandboxed_api/sandbox2/network_proxy/client.cc +++ b/sandboxed_api/sandbox2/network_proxy/client.cc @@ -27,6 +27,7 @@ #include "absl/memory/memory.h" #include "absl/status/status.h" #include "absl/strings/str_cat.h" +#include "sandboxed_api/sandbox2/config.h" #include "sandboxed_api/sandbox2/util/strerror.h" #include "sandboxed_api/util/status_macros.h" @@ -36,14 +37,13 @@ namespace sandbox2 { constexpr int SYS_SECCOMP = 1; #endif -#if defined(__x86_64__) +#if defined(SAPI_X86_64) constexpr int kRegResult = REG_RAX; constexpr int kRegSyscall = REG_RAX; constexpr int kRegArg0 = REG_RDI; constexpr int kRegArg1 = REG_RSI; constexpr int kRegArg2 = REG_RDX; -#endif -#if defined(__powerpc64__) +#elif defined(SAPI_PPC64_LE) constexpr int kRegResult = 3; constexpr int kRegSyscall = 0; constexpr int kRegArg0 = 3; @@ -161,9 +161,9 @@ void NetworkProxyHandler::ProcessSeccompTrap(int nr, siginfo_t* info, } if (!ctx) return; -#if defined(__x86_64__) +#if defined(SAPI_X86_64) auto* registers = ctx->uc_mcontext.gregs; -#elif defined(__powerpc64__) +#elif defined(SAPI_PPC64_LE) auto* registers = ctx->uc_mcontext.gp_regs; using ppc_gpreg_t = std::decay::type; #endif @@ -178,7 +178,7 @@ void NetworkProxyHandler::ProcessSeccompTrap(int nr, siginfo_t* info, sockfd = static_cast(registers[kRegArg0]); addr = reinterpret_cast(registers[kRegArg1]); addrlen = static_cast(registers[kRegArg2]); -#if defined(__powerpc64__) +#if defined(SAPI_PPC64_LE) } else if (syscall == __NR_socketcall && static_cast(registers[kRegArg0]) == SYS_CONNECT) { ppc_gpreg_t* args = reinterpret_cast(registers[kRegArg1]); diff --git a/sandboxed_api/sandbox2/policy.cc b/sandboxed_api/sandbox2/policy.cc index e631c0a..4e3fe03 100644 --- a/sandboxed_api/sandbox2/policy.cc +++ b/sandboxed_api/sandbox2/policy.cc @@ -85,10 +85,10 @@ std::vector Policy::GetDefaultPolicy() const { // If compiled arch is different than the runtime one, inform the Monitor. LOAD_ARCH, JEQ32(Syscall::GetHostAuditArch(), JUMP(&l, past_arch_check_l)), - JEQ32(AUDIT_ARCH_X86_64, TRACE(Syscall::kX86_64)), - JEQ32(AUDIT_ARCH_I386, TRACE(Syscall::kX86_32)), - JEQ32(AUDIT_ARCH_PPC64LE, TRACE(Syscall::kPPC_64)), - TRACE(Syscall::kUnknown), + JEQ32(AUDIT_ARCH_X86_64, TRACE(cpu::kX8664)), + JEQ32(AUDIT_ARCH_I386, TRACE(cpu::kX86)), + JEQ32(AUDIT_ARCH_PPC64LE, TRACE(cpu::kPPC64LE)), + TRACE(cpu::kUnknown), LABEL(&l, past_arch_check_l), // After the policy is uploaded, forkserver will execve the sandboxee. We @@ -130,10 +130,10 @@ std::vector Policy::GetDefaultPolicy() const { std::vector Policy::GetTrackingPolicy() const { return { LOAD_ARCH, - JEQ32(AUDIT_ARCH_X86_64, TRACE(Syscall::kX86_64)), - JEQ32(AUDIT_ARCH_I386, TRACE(Syscall::kX86_32)), - JEQ32(AUDIT_ARCH_PPC64LE, TRACE(Syscall::kPPC_64)), - TRACE(Syscall::kUnknown), + JEQ32(AUDIT_ARCH_X86_64, TRACE(cpu::kX8664)), + JEQ32(AUDIT_ARCH_I386, TRACE(cpu::kX86)), + JEQ32(AUDIT_ARCH_PPC64LE, TRACE(cpu::kPPC64LE)), + TRACE(cpu::kUnknown), }; } diff --git a/sandboxed_api/sandbox2/policy_test.cc b/sandboxed_api/sandbox2/policy_test.cc index 809ecd9..226b450 100644 --- a/sandboxed_api/sandbox2/policy_test.cc +++ b/sandboxed_api/sandbox2/policy_test.cc @@ -25,6 +25,7 @@ #include "gtest/gtest.h" #include "absl/memory/memory.h" #include "absl/strings/string_view.h" +#include "sandboxed_api/sandbox2/config.h" #include "sandboxed_api/sandbox2/executor.h" #include "sandboxed_api/sandbox2/limits.h" #include "sandboxed_api/sandbox2/policybuilder.h" @@ -56,7 +57,7 @@ std::unique_ptr PolicyTestcasePolicy() { .BuildOrDie(); } -#if defined(__x86_64__) +#ifdef SAPI_X86_64 // Test that 32-bit syscalls from 64-bit are disallowed. TEST(PolicyTest, AMD64Syscall32PolicyAllowed) { SKIP_SANITIZERS_AND_COVERAGE; @@ -72,7 +73,7 @@ TEST(PolicyTest, AMD64Syscall32PolicyAllowed) { ASSERT_THAT(result.final_status(), Eq(Result::VIOLATION)); EXPECT_THAT(result.reason_code(), Eq(1)); // __NR_exit in 32-bit - EXPECT_THAT(result.GetSyscallArch(), Eq(Syscall::kX86_32)); + EXPECT_THAT(result.GetSyscallArch(), Eq(cpu::kX86)); } // Test that 32-bit syscalls from 64-bit for FS checks are disallowed. @@ -90,9 +91,9 @@ TEST(PolicyTest, AMD64Syscall32FsAllowed) { ASSERT_THAT(result.final_status(), Eq(Result::VIOLATION)); EXPECT_THAT(result.reason_code(), Eq(33)); // __NR_access in 32-bit - EXPECT_THAT(result.GetSyscallArch(), Eq(Syscall::kX86_32)); + EXPECT_THAT(result.GetSyscallArch(), Eq(cpu::kX86)); } -#endif // defined(__x86_64__) +#endif // Test that ptrace(2) is disallowed. TEST(PolicyTest, PtraceDisallowed) { diff --git a/sandboxed_api/sandbox2/policybuilder.cc b/sandboxed_api/sandbox2/policybuilder.cc index dbae2d9..a560190 100644 --- a/sandboxed_api/sandbox2/policybuilder.cc +++ b/sandboxed_api/sandbox2/policybuilder.cc @@ -15,14 +15,7 @@ #include "sandboxed_api/sandbox2/policybuilder.h" #include // For TCGETS - -#if defined(__x86_64__) -#include -#endif -#if defined(__powerpc64__) -#include // On PPC, TCGETS macro needs termios -#endif -#include // For the fcntl flags +#include // For the fcntl flags #include #include // For SYS_CONNECT #include // For GRND_NONBLOCK @@ -38,11 +31,18 @@ #include "absl/status/statusor.h" #include "absl/strings/escaping.h" #include "absl/strings/match.h" +#include "sandboxed_api/sandbox2/config.h" #include "sandboxed_api/sandbox2/namespace.h" #include "sandboxed_api/sandbox2/util/bpf_helper.h" #include "sandboxed_api/sandbox2/util/path.h" #include "sandboxed_api/util/status_macros.h" +#if defined(SAPI_X86_64) +#include +#elif defined(SAPI_PPC64_LE) +#include // On PPC, TCGETS macro needs termios +#endif + namespace sandbox2 { namespace { @@ -512,7 +512,7 @@ PolicyBuilder& PolicyBuilder::AllowStaticStartup() { JEQ32(SIG_UNBLOCK, ALLOW), }); -#if defined(__x86_64__) +#ifdef SAPI_X86_64 // The second argument is a pointer. AddPolicyOnSyscall(__NR_arch_prctl, { ARG_32(0), @@ -901,7 +901,7 @@ PolicyBuilder& PolicyBuilder::AddNetworkProxyPolicy() { LABEL(&labels, getsockopt_end), }; }); -#if defined(__powerpc64__) +#ifdef SAPI_PPC64_LE AddPolicyOnSyscall(__NR_socketcall, { ARG_32(0), JEQ32(SYS_SOCKET, ALLOW), @@ -927,7 +927,7 @@ PolicyBuilder& PolicyBuilder::AddNetworkProxyHandlerPolicy() { }); AddPolicyOnSyscall(__NR_connect, {TRAP(0)}); -#if defined(__powerpc64__) +#ifdef SAPI_PPC64_LE AddPolicyOnSyscall(__NR_socketcall, { ARG_32(0), JEQ32(SYS_CONNECT, TRAP(0)), diff --git a/sandboxed_api/sandbox2/policybuilder.h b/sandboxed_api/sandbox2/policybuilder.h index 17e95ff..5a5fd97 100644 --- a/sandboxed_api/sandbox2/policybuilder.h +++ b/sandboxed_api/sandbox2/policybuilder.h @@ -39,8 +39,6 @@ struct bpf_labels; namespace sandbox2 { -constexpr char kDefaultHostname[] = "sandbox2"; - // PolicyBuilder is a helper class to simplify creation of policies. The builder // uses fluent interface for convenience and increased readability of policies. // @@ -91,6 +89,8 @@ constexpr char kDefaultHostname[] = "sandbox2"; // For a more complicated example, see examples/persistent/persistent_sandbox.cc class PolicyBuilder final { public: + static constexpr absl::string_view kDefaultHostname = "sandbox2"; + using BpfInitializer = std::initializer_list; using BpfFunc = const std::function(bpf_labels&)>&; using SyscallInitializer = std::initializer_list; @@ -542,7 +542,7 @@ class PolicyBuilder final { bool use_namespaces_ = true; bool requires_namespaces_ = false; bool allow_unrestricted_networking_ = false; - std::string hostname_ = kDefaultHostname; + std::string hostname_ = std::string(kDefaultHostname); bool collect_stacktrace_on_violation_ = true; bool collect_stacktrace_on_signal_ = true; diff --git a/sandboxed_api/sandbox2/regs.cc b/sandboxed_api/sandbox2/regs.cc index 6dfcd14..07f8e87 100644 --- a/sandboxed_api/sandbox2/regs.cc +++ b/sandboxed_api/sandbox2/regs.cc @@ -24,64 +24,69 @@ #include #include "absl/strings/str_cat.h" +#include "sandboxed_api/sandbox2/config.h" #include "sandboxed_api/sandbox2/util/strerror.h" namespace sandbox2 { absl::Status Regs::Fetch() { -#if defined(__powerpc64__) - iovec pt_iov = {&user_regs_, sizeof(user_regs_)}; - - if (ptrace(PTRACE_GETREGSET, pid_, NT_PRSTATUS, &pt_iov) == -1L) { - return absl::InternalError(absl::StrCat( - "ptrace(PTRACE_GETREGSET, pid=", pid_, ") failed: ", StrError(errno))); - } - if (pt_iov.iov_len != sizeof(user_regs_)) { - return absl::InternalError(absl::StrCat( - "ptrace(PTRACE_GETREGSET, pid=", pid_, - ") size returned: ", pt_iov.iov_len, - " different than sizeof(user_regs_): ", sizeof(user_regs_))); - } -#else +#ifdef SAPI_X86_64 if (ptrace(PTRACE_GETREGS, pid_, 0, &user_regs_) == -1L) { return absl::InternalError(absl::StrCat("ptrace(PTRACE_GETREGS, pid=", pid_, ") failed: ", StrError(errno))); } #endif + if constexpr (host_cpu::IsPPC64LE()) { + iovec pt_iov = {&user_regs_, sizeof(user_regs_)}; + + if (ptrace(PTRACE_GETREGSET, pid_, NT_PRSTATUS, &pt_iov) == -1L) { + return absl::InternalError( + absl::StrCat("ptrace(PTRACE_GETREGSET, pid=", pid_, + ") failed: ", StrError(errno))); + } + if (pt_iov.iov_len != sizeof(user_regs_)) { + return absl::InternalError(absl::StrCat( + "ptrace(PTRACE_GETREGSET, pid=", pid_, + ") size returned: ", pt_iov.iov_len, + " different than sizeof(user_regs_): ", sizeof(user_regs_))); + } + } return absl::OkStatus(); } absl::Status Regs::Store() { -#if defined(__powerpc64__) - iovec pt_iov = {&user_regs_, sizeof(user_regs_)}; - - if (ptrace(PTRACE_SETREGSET, pid_, NT_PRSTATUS, &pt_iov) == -1L) { - return absl::InternalError(absl::StrCat( - "ptrace(PTRACE_SETREGSET, pid=", pid_, ") failed: ", StrError(errno))); - } -#else +#ifdef SAPI_X86_64 if (ptrace(PTRACE_SETREGS, pid_, 0, &user_regs_) == -1) { return absl::InternalError(absl::StrCat("ptrace(PTRACE_SETREGS, pid=", pid_, ") failed: ", StrError(errno))); } #endif + if constexpr (host_cpu::IsPPC64LE()) { + iovec pt_iov = {&user_regs_, sizeof(user_regs_)}; + + if (ptrace(PTRACE_SETREGSET, pid_, NT_PRSTATUS, &pt_iov) == -1L) { + return absl::InternalError( + absl::StrCat("ptrace(PTRACE_SETREGSET, pid=", pid_, + ") failed: ", StrError(errno))); + } + } return absl::OkStatus(); } absl::Status Regs::SkipSyscallReturnValue(uint64_t value) { -#if defined(__x86_64__) +#if defined(SAPI_X86_64) user_regs_.orig_rax = -1; user_regs_.rax = value; -#elif defined(__powerpc64__) +#elif defined(SAPI_PPC64_LE) user_regs_.gpr[0] = -1; user_regs_.gpr[3] = value; #endif return Store(); } -Syscall Regs::ToSyscall(Syscall::CpuArch syscall_arch) const { -#if defined(__x86_64__) - if (ABSL_PREDICT_TRUE(syscall_arch == Syscall::kX86_64)) { +Syscall Regs::ToSyscall(cpu::Architecture syscall_arch) const { +#if defined(SAPI_X86_64) + if (ABSL_PREDICT_TRUE(syscall_arch == cpu::kX8664)) { auto syscall = user_regs_.orig_rax; Syscall::Args args = {user_regs_.rdi, user_regs_.rsi, user_regs_.rdx, user_regs_.r10, user_regs_.r8, user_regs_.r9}; @@ -89,7 +94,7 @@ Syscall Regs::ToSyscall(Syscall::CpuArch syscall_arch) const { auto ip = user_regs_.rip; return Syscall(syscall_arch, syscall, args, pid_, sp, ip); } - if (syscall_arch == Syscall::kX86_32) { + if (syscall_arch == cpu::kX86) { auto syscall = user_regs_.orig_rax & 0xFFFFFFFF; Syscall::Args args = { user_regs_.rbx & 0xFFFFFFFF, user_regs_.rcx & 0xFFFFFFFF, @@ -99,8 +104,8 @@ Syscall Regs::ToSyscall(Syscall::CpuArch syscall_arch) const { auto ip = user_regs_.rip & 0xFFFFFFFF; return Syscall(syscall_arch, syscall, args, pid_, sp, ip); } -#elif defined(__powerpc64__) - if (ABSL_PREDICT_TRUE(syscall_arch == Syscall::kPPC_64)) { +#elif defined(SAPI_PPC64_LE) + if (ABSL_PREDICT_TRUE(syscall_arch == cpu::kPPC64LE)) { auto syscall = user_regs_.gpr[0]; Syscall::Args args = {user_regs_.orig_gpr3, user_regs_.gpr[4], user_regs_.gpr[5], user_regs_.gpr[6], @@ -114,7 +119,7 @@ Syscall Regs::ToSyscall(Syscall::CpuArch syscall_arch) const { } void Regs::StoreRegisterValuesInProtobuf(RegisterValues* values) const { -#if defined(__x86_64__) +#if defined(SAPI_X86_64) RegisterX8664* regs = values->mutable_register_x86_64(); regs->set_r15(user_regs_.r15); regs->set_r14(user_regs_.r14); @@ -143,7 +148,7 @@ void Regs::StoreRegisterValuesInProtobuf(RegisterValues* values) const { regs->set_es(user_regs_.es); regs->set_fs(user_regs_.fs); regs->set_gs(user_regs_.gs); -#elif defined(__powerpc64__) +#elif defined(SAPI_PPC64_LE) RegisterPowerpc64* regs = values->mutable_register_powerpc64(); for (int i = 0; i < ABSL_ARRAYSIZE(user_regs_.gpr); ++i) { regs->add_gpr(user_regs_.gpr[i]); diff --git a/sandboxed_api/sandbox2/regs.h b/sandboxed_api/sandbox2/regs.h index f1149a9..661c8b6 100644 --- a/sandboxed_api/sandbox2/regs.h +++ b/sandboxed_api/sandbox2/regs.h @@ -24,6 +24,7 @@ #include #include "absl/status/status.h" +#include "sandboxed_api/sandbox2/config.h" #include "sandboxed_api/sandbox2/syscall.h" #include "sandboxed_api/sandbox2/violation.pb.h" @@ -33,10 +34,6 @@ namespace sandbox2 { // assumes the process is already attached. class Regs { public: -#if !defined(__x86_64__) && !defined(__powerpc64__) - static_assert(false, "No support for the current CPU architecture"); -#endif - explicit Regs(pid_t pid) : pid_(pid) {} // Copies register values from the process @@ -49,7 +46,7 @@ class Regs { absl::Status SkipSyscallReturnValue(uint64_t value); // Converts raw register values obtained on syscall entry to syscall info - Syscall ToSyscall(Syscall::CpuArch syscall_arch) const; + Syscall ToSyscall(cpu::Architecture syscall_arch) const; pid_t pid() const { return pid_; } @@ -60,7 +57,7 @@ class Regs { friend class StackTracePeer; struct PtraceRegisters { -#if defined(__x86_64__) +#if defined(SAPI_X86_64) uint64_t r15; uint64_t r14; uint64_t r13; @@ -88,7 +85,7 @@ class Regs { uint64_t es; uint64_t fs; uint64_t gs; -#elif defined(__powerpc64__) +#elif defined(SAPI_PPC64_LE) uint64_t gpr[32]; uint64_t nip; uint64_t msr; @@ -108,6 +105,8 @@ class Regs { uint64_t zero1; uint64_t zero2; uint64_t zero3; +#else + static_assert(false, "Host CPU architecture not supported, see config.h"); #endif }; diff --git a/sandboxed_api/sandbox2/result.h b/sandboxed_api/sandbox2/result.h index f381ccb..d2d9c5a 100644 --- a/sandboxed_api/sandbox2/result.h +++ b/sandboxed_api/sandbox2/result.h @@ -28,6 +28,7 @@ #include "absl/memory/memory.h" #include "absl/status/status.h" +#include "sandboxed_api/sandbox2/config.h" #include "sandboxed_api/sandbox2/regs.h" #include "sandboxed_api/sandbox2/syscall.h" @@ -131,8 +132,8 @@ class Result { // Returns the current syscall architecture. // Client architecture when final_status_ == VIOLATION, might be different // from the host architecture (32-bit vs 64-bit syscalls). - Syscall::CpuArch GetSyscallArch() const { - return syscall_ ? syscall_->arch() : Syscall::kUnknown; + cpu::Architecture GetSyscallArch() const { + return syscall_ ? syscall_->arch() : cpu::kUnknown; } const std::vector stack_trace() { return stack_trace_; } diff --git a/sandboxed_api/sandbox2/sandbox2_test.cc b/sandboxed_api/sandbox2/sandbox2_test.cc index edf3d6c..df94c1f 100644 --- a/sandboxed_api/sandbox2/sandbox2_test.cc +++ b/sandboxed_api/sandbox2/sandbox2_test.cc @@ -26,6 +26,7 @@ #include "gtest/gtest.h" #include "absl/memory/memory.h" #include "absl/strings/str_cat.h" +#include "sandboxed_api/sandbox2/config.h" #include "sandboxed_api/sandbox2/executor.h" #include "sandboxed_api/sandbox2/policy.h" #include "sandboxed_api/sandbox2/policybuilder.h" diff --git a/sandboxed_api/sandbox2/syscall.cc b/sandboxed_api/sandbox2/syscall.cc index 2b3ffad..7e06717 100644 --- a/sandboxed_api/sandbox2/syscall.cc +++ b/sandboxed_api/sandbox2/syscall.cc @@ -12,12 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -// Implementation of the sandbox2::Syscall class. - #include "sandboxed_api/sandbox2/syscall.h" #include #include + #include #include #include @@ -26,6 +25,7 @@ #include #include "absl/strings/str_format.h" #include "absl/strings/str_join.h" +#include "sandboxed_api/sandbox2/config.h" #include "sandboxed_api/sandbox2/syscall_defs.h" #ifndef AUDIT_ARCH_PPC64LE @@ -34,13 +34,13 @@ namespace sandbox2 { -std::string Syscall::GetArchDescription(CpuArch arch) { +std::string Syscall::GetArchDescription(cpu::Architecture arch) { switch (arch) { - case kX86_64: + case cpu::kX8664: return "[X86-64]"; - case kX86_32: + case cpu::kX86: return "[X86-32]"; - case kPPC_64: + case cpu::kPPC64LE: return "[PPC-64]"; default: LOG(ERROR) << "Unknown CPU architecture: " << arch; @@ -48,32 +48,25 @@ std::string Syscall::GetArchDescription(CpuArch arch) { } } -Syscall::CpuArch Syscall::GetHostArch() { -#if defined(__x86_64__) - return kX86_64; -#elif defined(__i386__) - return kX86_32; -#elif defined(__powerpc64__) - return kPPC_64; -#endif -} - uint32_t Syscall::GetHostAuditArch() { -#if defined(__x86_64__) - return AUDIT_ARCH_X86_64; -#elif defined(__i386__) - return AUDIT_ARCH_I386; -#elif defined(__powerpc64__) - return AUDIT_ARCH_PPC64LE; -#endif + switch (host_cpu::Architecture()) { + case cpu::kX8664: + return AUDIT_ARCH_X86_64; + case cpu::kPPC64LE: + return AUDIT_ARCH_PPC64LE; + default: + // The static_assert() in config.h should prevent us from ever getting + // here. + return 0; // Not reached + } } std::string Syscall::GetName() const { - absl::string_view name = SyscallTable::get(arch_).GetName(nr_); - if (name.empty()) { - return absl::StrFormat("UNKNOWN[%d/0x%x]", nr_, nr_); + if (absl::string_view name = SyscallTable::get(arch_).GetName(nr_); + !name.empty()) { + return std::string(name); } - return std::string(name); + return absl::StrFormat("UNKNOWN[%d/0x%x]", nr_, nr_); } std::vector Syscall::GetArgumentsDescription() const { diff --git a/sandboxed_api/sandbox2/syscall.h b/sandboxed_api/sandbox2/syscall.h index 50f8dbf..132a505 100644 --- a/sandboxed_api/sandbox2/syscall.h +++ b/sandboxed_api/sandbox2/syscall.h @@ -13,7 +13,7 @@ // limitations under the License. // The sandbox2::Syscalls class defines mostly static helper methods which -// are used to analyze status of the ptraced process +// are used to analyze the status of the sandboxed process. #ifndef SANDBOXED_API_SANDBOX2_SYSCALL_H__ #define SANDBOXED_API_SANDBOX2_SYSCALL_H__ @@ -26,40 +26,34 @@ #include #include +#include "sandboxed_api/sandbox2/config.h" + namespace sandbox2 { class Syscall { public: - // Supported CPU architectures. - // Linux: Use a magic value, so it can be easily spotted in the seccomp-bpf - // bytecode decompilation stream. Must be < (1<<15), as/ that's the size of - // data which can be returned by BPF. - enum CpuArch { - kUnknown = 0xCAF0, - kX86_64, - kX86_32, - kPPC_64, - }; // Maximum number of syscall arguments static constexpr size_t kMaxArgs = 6; using Args = std::array; // Returns the host architecture, according to CpuArch. - static CpuArch GetHostArch(); + static constexpr cpu::Architecture GetHostArch() { + return host_cpu::Architecture(); + } // Returns the host architecture, according to . static uint32_t GetHostAuditArch(); // Returns a description of the architecture. - static std::string GetArchDescription(CpuArch arch); + static std::string GetArchDescription(cpu::Architecture arch); Syscall() = default; - Syscall(CpuArch arch, uint64_t nr, Args args = {}) + Syscall(cpu::Architecture arch, uint64_t nr, Args args = {}) : arch_(arch), nr_(nr), args_(args) {} pid_t pid() const { return pid_; } uint64_t nr() const { return nr_; } - CpuArch arch() const { return arch_; } + cpu::Architecture arch() const { return arch_; } const Args& args() const { return args_; } uint64_t stack_pointer() const { return sp_; } uint64_t instruction_pointer() const { return ip_; } @@ -72,12 +66,12 @@ class Syscall { private: friend class Regs; - Syscall(pid_t pid) : pid_(pid) {} - Syscall(CpuArch arch, uint64_t nr, Args args, pid_t pid, uint64_t sp, - uint64_t ip) + explicit Syscall(pid_t pid) : pid_(pid) {} + Syscall(cpu::Architecture arch, uint64_t nr, Args args, pid_t pid, + uint64_t sp, uint64_t ip) : arch_(arch), nr_(nr), args_(args), pid_(pid), sp_(sp), ip_(ip) {} - CpuArch arch_ = kUnknown; + cpu::Architecture arch_ = cpu::kUnknown; uint64_t nr_ = -1; Args args_ = {}; pid_t pid_ = -1; diff --git a/sandboxed_api/sandbox2/syscall_defs.cc b/sandboxed_api/sandbox2/syscall_defs.cc index 1a8c94f..410d054 100644 --- a/sandboxed_api/sandbox2/syscall_defs.cc +++ b/sandboxed_api/sandbox2/syscall_defs.cc @@ -7,6 +7,7 @@ #include "absl/strings/escaping.h" #include "absl/strings/str_cat.h" #include "absl/strings/str_format.h" +#include "sandboxed_api/sandbox2/config.h" #include "sandboxed_api/sandbox2/util.h" namespace sandbox2 { @@ -128,7 +129,6 @@ std::vector SyscallTable::GetArgumentsDescription( #define SYSCALLS_UNUSED00_99(prefix) \ SYSCALLS_UNUSED00_49(prefix), SYSCALLS_UNUSED50_99(prefix) -#if defined(__x86_64__) // Syscall description table for Linux x86_64 constexpr SyscallTable::Entry kSyscallDataX8664[] = { MakeEntry("read", kInt, kHex, kInt), // 0 @@ -824,12 +824,10 @@ constexpr SyscallTable::Entry kSyscallDataX8632[] = { MakeEntry("bpf", kHex, kHex, kHex, kHex, kHex, kHex), // 357 }; -#elif defined(__powerpc64__) - // http://lxr.free-electrons.com/source/arch/powerpc/include/uapi/asm/unistd.h // Note: PPC64 syscalls can have up to 7 register arguments, but nobody is // using the 7th argument - probably for x64 compatibility reasons. -constexpr SyscallTable::Entry kSyscallDataPPC64[] = { +constexpr SyscallTable::Entry kSyscallDataPPC64LE[] = { MakeEntry("restart_syscall", kGen, kGen, kGen, kGen, kGen, kGen), // 0 MakeEntry("exit", kInt, kGen, kGen, kGen, kGen, kGen), // 1 MakeEntry("fork", kGen, kGen, kGen, kGen, kGen, kGen), // 2 @@ -1218,25 +1216,20 @@ constexpr SyscallTable::Entry kSyscallDataPPC64[] = { MakeEntry("pwritev2", kHex, kHex, kHex, kHex, kHex, kHex), // 381 }; -#endif - #undef SYSCALLS_UNUSED00_99 #undef SYSCALLS_UNUSED50_99 #undef SYSCALLS_UNUSED00_49 #undef SYSCALLS_UNUSED0_9 #undef SYSCALLS_UNUSED -SyscallTable SyscallTable::get(Syscall::CpuArch arch) { - switch (arch) { -#if defined(__x86_64__) - case Syscall::kX86_64: +SyscallTable SyscallTable::get(cpu::Architecture arch) { + switch (host_cpu::Architecture()) { + case cpu::kX8664: return SyscallTable(kSyscallDataX8664); - case Syscall::kX86_32: + case cpu::kX86: return SyscallTable(kSyscallDataX8632); -#elif defined(__powerpc64__) - case Syscall::kPPC_64: - return SyscallTable(kSyscallDataPPC64); -#endif + case cpu::kPPC64LE: + return SyscallTable(kSyscallDataPPC64LE); default: return SyscallTable(); } diff --git a/sandboxed_api/sandbox2/syscall_defs.h b/sandboxed_api/sandbox2/syscall_defs.h index bef3d19..168ed1d 100644 --- a/sandboxed_api/sandbox2/syscall_defs.h +++ b/sandboxed_api/sandbox2/syscall_defs.h @@ -9,6 +9,7 @@ #include "absl/strings/string_view.h" #include "absl/types/span.h" +#include "sandboxed_api/sandbox2/config.h" #include "sandboxed_api/sandbox2/syscall.h" namespace sandbox2 { @@ -23,7 +24,7 @@ class SyscallTable { struct Entry; // Returns the syscall table for the architecture. - static SyscallTable get(Syscall::CpuArch arch); + static SyscallTable get(cpu::Architecture arch); int size() { return data_.size(); } diff --git a/sandboxed_api/sandbox2/syscall_test.cc b/sandboxed_api/sandbox2/syscall_test.cc index 1cb7228..2d13257 100644 --- a/sandboxed_api/sandbox2/syscall_test.cc +++ b/sandboxed_api/sandbox2/syscall_test.cc @@ -19,6 +19,7 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" #include "absl/strings/str_cat.h" +#include "sandboxed_api/sandbox2/config.h" using ::testing::Eq; using ::testing::StartsWith; @@ -45,7 +46,7 @@ TEST(SyscallTest, Basic) { EXPECT_THAT(arg_desc[2], Eq("0x5 [5]")); EXPECT_THAT( syscall.GetDescription(), - Eq(absl::StrCat(Syscall::GetArchDescription(Syscall::GetHostArch()), + Eq(absl::StrCat(Syscall::GetArchDescription(host_cpu::Architecture()), " read [", __NR_read, "](0x1 [1], 0xbadbeef, 0x5 [5]) IP: 0, STACK: 0"))); } @@ -53,7 +54,7 @@ TEST(SyscallTest, Basic) { TEST(SyscallTest, Empty) { Syscall syscall; - EXPECT_THAT(syscall.arch(), Eq(Syscall::kUnknown)); + EXPECT_THAT(syscall.arch(), Eq(cpu::kUnknown)); EXPECT_THAT(syscall.GetName(), StartsWith("UNKNOWN")); EXPECT_THAT(syscall.GetArgumentsDescription().size(), Eq(Syscall::kMaxArgs)); } diff --git a/sandboxed_api/sandbox2/testcases/BUILD.bazel b/sandboxed_api/sandbox2/testcases/BUILD.bazel index 1983e4d..307db47 100644 --- a/sandboxed_api/sandbox2/testcases/BUILD.bazel +++ b/sandboxed_api/sandbox2/testcases/BUILD.bazel @@ -178,6 +178,7 @@ cc_binary( "fully_static_link", # link libc statically ], linkstatic = 1, # prefer static libraries + deps = ["//sandboxed_api/sandbox2:config"], ) # security: disable=cc-static-no-pie diff --git a/sandboxed_api/sandbox2/testcases/CMakeLists.txt b/sandboxed_api/sandbox2/testcases/CMakeLists.txt index 4af43ba..c57ffc5 100644 --- a/sandboxed_api/sandbox2/testcases/CMakeLists.txt +++ b/sandboxed_api/sandbox2/testcases/CMakeLists.txt @@ -159,6 +159,7 @@ set_target_properties(policy PROPERTIES ) target_link_libraries(policy PRIVATE sapi::base + sandbox2::config ${_sandbox2_fully_static_linkopts} ) diff --git a/sandboxed_api/sandbox2/testcases/policy.cc b/sandboxed_api/sandbox2/testcases/policy.cc index e1f851e..ae598cf 100644 --- a/sandboxed_api/sandbox2/testcases/policy.cc +++ b/sandboxed_api/sandbox2/testcases/policy.cc @@ -23,7 +23,9 @@ #include #include -#if defined(__x86_64__) +#include "sandboxed_api/sandbox2/config.h" + +#ifdef SAPI_X86_64 void TestAMD64SyscallMismatch() { int64_t result; @@ -53,7 +55,7 @@ void TestAMD64SyscallMismatchFs() { : "rax", "rbx", "rcx"); exit(-result); } -#endif // defined(__x86_64__) +#endif void TestPtrace() { ptrace(PTRACE_SEIZE, getppid(), 0, 0); @@ -97,14 +99,14 @@ int main(int argc, char** argv) { int testno = atoi(argv[1]); // NOLINT switch (testno) { -#if defined(__x86_64__) +#ifdef SAPI_X86_64 case 1: TestAMD64SyscallMismatch(); break; case 2: TestAMD64SyscallMismatchFs(); break; -#endif // defined(__x86_64__) +#endif case 3: TestPtrace(); break; diff --git a/sandboxed_api/sandbox2/util.cc b/sandboxed_api/sandbox2/util.cc index 55f7aa6..3e97abc 100644 --- a/sandboxed_api/sandbox2/util.cc +++ b/sandboxed_api/sandbox2/util.cc @@ -39,13 +39,13 @@ #include "absl/strings/str_format.h" #include "absl/strings/string_view.h" #include "absl/strings/strip.h" +#include "sandboxed_api/sandbox2/config.h" #include "sandboxed_api/sandbox2/util/fileops.h" #include "sandboxed_api/sandbox2/util/path.h" #include "sandboxed_api/sandbox2/util/strerror.h" #include "sandboxed_api/util/raw_logging.h" -namespace sandbox2 { -namespace util { +namespace sandbox2::util { void CharPtrArrToVecString(char* const* arr, std::vector* vec) { for (int i = 0; arr[i]; ++i) { @@ -126,13 +126,10 @@ ABSL_ATTRIBUTE_NO_SANITIZE_ADDRESS ABSL_ATTRIBUTE_NOINLINE pid_t CloneAndJump(int flags, jmp_buf* env_ptr) { uint8_t stack_buf[PTHREAD_STACK_MIN] ABSL_CACHELINE_ALIGNED; -#if defined(__x86_64__) || defined(__x86__) || defined(__i386__) || \ - defined(__powerpc64__) + static_assert(host_cpu::IsX8664() || host_cpu::IsPPC64LE(), + "Host CPU architecture not supported, see config.h"); // Stack grows down. void* stack = stack_buf + sizeof(stack_buf); -#else -#error "Architecture is not supported" -#endif int r; { r = clone(&ChildFunc, stack, flags, env_ptr, nullptr, nullptr, nullptr); @@ -321,5 +318,4 @@ absl::StatusOr ReadCPathFromPid(pid_t pid, uintptr_t ptr) { return path; } -} // namespace util -} // namespace sandbox2 +} // namespace sandbox2::util From c19949eb7b66d4ec872014dd7baccac5c7942c84 Mon Sep 17 00:00:00 2001 From: Christian Blichmann Date: Fri, 11 Sep 2020 03:13:46 -0700 Subject: [PATCH 69/82] Use inclusive language PiperOrigin-RevId: 331116936 Change-Id: I7084b24440a1c78c0d70030da900330f0b8d954f --- sandboxed_api/sandbox2/comms.cc | 2 +- sandboxed_api/sandbox2/mounts.cc | 2 +- sandboxed_api/sandbox2/violation.proto | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/sandboxed_api/sandbox2/comms.cc b/sandboxed_api/sandbox2/comms.cc index 8a357b6..a4845c8 100644 --- a/sandboxed_api/sandbox2/comms.cc +++ b/sandboxed_api/sandbox2/comms.cc @@ -347,7 +347,7 @@ bool Comms::RecvFD(int* fd) { const auto op = [&msg](int fd) -> ssize_t { PotentiallyBlockingRegion region; - // Use syscall, otherwise we would need to whitelist socketcall() on PPC. + // Use syscall, otherwise we would need to allow socketcall() on PPC. return TEMP_FAILURE_RETRY( util::Syscall(__NR_recvmsg, fd, reinterpret_cast(&msg), 0)); }; diff --git a/sandboxed_api/sandbox2/mounts.cc b/sandboxed_api/sandbox2/mounts.cc index d722e6f..24afdc6 100644 --- a/sandboxed_api/sandbox2/mounts.cc +++ b/sandboxed_api/sandbox2/mounts.cc @@ -501,7 +501,7 @@ std::string MountFlagsToString(uint64_t flags) { SAPI_MAP(MS_POSIXACL), SAPI_MAP(MS_UNBINDABLE), SAPI_MAP(MS_PRIVATE), - SAPI_MAP(MS_SLAVE), + SAPI_MAP(MS_SLAVE), // Inclusive language: system constant SAPI_MAP(MS_SHARED), SAPI_MAP(MS_RELATIME), SAPI_MAP(MS_KERNMOUNT), diff --git a/sandboxed_api/sandbox2/violation.proto b/sandboxed_api/sandbox2/violation.proto index acd8a6f..57a3b3d 100644 --- a/sandboxed_api/sandbox2/violation.proto +++ b/sandboxed_api/sandbox2/violation.proto @@ -105,10 +105,10 @@ message SyscallDescription { } message FsDescription { - repeated string file_whitelist = 1; - repeated string symlink_whitelist = 2; + repeated string file_allowlist = 1; + repeated string symlink_allowlist = 2; repeated string file_greylist = 3; - repeated string file_blacklist = 4; + repeated string file_denylist = 4; } message PolicyBuilderDescription { @@ -125,7 +125,7 @@ message NamespaceDescription { message PolicyDescription { bytes user_bpf_policy = 1; reserved 2 to 5; - // This requires additional fields. (e.g. whitelisted syscall #s) + // This requires additional fields. (e.g. allowed syscall numbers) PolicyBuilderDescription policy_builder_description = 6; // namespace From 21f7373e76ff909ea6e1a87a2f3aa0d7344095eb Mon Sep 17 00:00:00 2001 From: Christian Blichmann Date: Fri, 11 Sep 2020 06:33:57 -0700 Subject: [PATCH 70/82] Initial changes to support AArch64 This is a work in progress: - Syscall tables need work - Only tested on real hardware using one of our test hosts As a drive-by, this change also enables the open source version to function on POWER. Another side-effect of this change is that the default policies no longer check for different host architectures at runtime. On x86_64, we do not need to check for PPC or AArch64 specifice and vice versa. PiperOrigin-RevId: 331137472 Change-Id: Ic6d6be5cbe61d83dbe13d5a0be036871754b2eb8 --- sandboxed_api/sandbox.cc | 2 + sandboxed_api/sandbox2/buffer_test.cc | 7 + sandboxed_api/sandbox2/config.h | 6 +- .../examples/static/static_sandbox.cc | 2 + sandboxed_api/sandbox2/mounts.cc | 4 + .../sandbox2/network_proxy/client.cc | 27 +- sandboxed_api/sandbox2/notify_test.cc | 4 + sandboxed_api/sandbox2/policy.cc | 79 ++--- sandboxed_api/sandbox2/policy_test.cc | 17 ++ sandboxed_api/sandbox2/policybuilder.cc | 7 + sandboxed_api/sandbox2/policybuilder_test.cc | 2 +- sandboxed_api/sandbox2/regs.cc | 66 +++- sandboxed_api/sandbox2/regs.h | 8 + sandboxed_api/sandbox2/stack_trace.cc | 4 + sandboxed_api/sandbox2/syscall.cc | 4 + sandboxed_api/sandbox2/syscall_defs.cc | 287 ++++++++++++++++++ sandboxed_api/sandbox2/util.cc | 5 +- sandboxed_api/sandbox2/violation.proto | 4 +- 18 files changed, 477 insertions(+), 58 deletions(-) diff --git a/sandboxed_api/sandbox.cc b/sandboxed_api/sandbox.cc index b18a1b0..8c9da3d 100644 --- a/sandboxed_api/sandbox.cc +++ b/sandboxed_api/sandbox.cc @@ -84,7 +84,9 @@ void InitDefaultPolicyBuilder(sandbox2::PolicyBuilder* builder) { __NR_kill, __NR_tgkill, __NR_tkill, +#ifdef __NR_readlink __NR_readlink, +#endif #ifdef __NR_arch_prctl // x86-64 only __NR_arch_prctl, #endif diff --git a/sandboxed_api/sandbox2/buffer_test.cc b/sandboxed_api/sandbox2/buffer_test.cc index 876be31..2b72a8b 100644 --- a/sandboxed_api/sandbox2/buffer_test.cc +++ b/sandboxed_api/sandbox2/buffer_test.cc @@ -84,11 +84,18 @@ std::unique_ptr BufferTestcasePolicy() { .AllowSyscall(__NR_lseek) .AllowSyscall(__NR_close) .BlockSyscallWithErrno(__NR_prlimit64, EPERM) +#ifdef __NR_open .BlockSyscallWithErrno(__NR_open, ENOENT) +#endif .BlockSyscallWithErrno(__NR_openat, ENOENT) +#ifdef __NR_access // On Debian, even static binaries check existence of // /etc/ld.so.nohwcap. .BlockSyscallWithErrno(__NR_access, ENOENT) +#endif +#ifdef __NR_faccessat + .BlockSyscallWithErrno(__NR_faccessat, ENOENT) +#endif .BuildOrDie(); return s2p; diff --git a/sandboxed_api/sandbox2/config.h b/sandboxed_api/sandbox2/config.h index ba0fec5..209cd82 100644 --- a/sandboxed_api/sandbox2/config.h +++ b/sandboxed_api/sandbox2/config.h @@ -61,6 +61,8 @@ constexpr cpu::Architecture Architecture() { return cpu::kX8664; #elif defined(SAPI_PPC64_LE) return cpu::kPPC64LE; +#elif defined(SAPI_ARM64) + return cpu::kArm64; #else return cpu::kUnknown; #endif @@ -75,8 +77,8 @@ constexpr bool IsArm64() { return Architecture() == cpu::kArm64; } } // namespace host_cpu static_assert(host_cpu::Architecture() != cpu::kUnknown, - "Host CPU architecture is not supported: One of x86-64 or " - "POWER64 (little endian) is required."); + "Host CPU architecture is not supported: One of x86-64, POWER64 " + "(little endian) or AArch64 is required."); } // namespace sandbox2 diff --git a/sandboxed_api/sandbox2/examples/static/static_sandbox.cc b/sandboxed_api/sandbox2/examples/static/static_sandbox.cc index 44f794c..7290b72 100644 --- a/sandboxed_api/sandbox2/examples/static/static_sandbox.cc +++ b/sandboxed_api/sandbox2/examples/static/static_sandbox.cc @@ -52,8 +52,10 @@ std::unique_ptr GetPolicy() { // Allow the getpid() syscall. .AllowSyscall(__NR_getpid) +#ifdef __NR_access // On Debian, even static binaries check existence of /etc/ld.so.nohwcap. .BlockSyscallWithErrno(__NR_access, ENOENT) +#endif // Examples for AddPolicyOnSyscall: .AddPolicyOnSyscall(__NR_write, diff --git a/sandboxed_api/sandbox2/mounts.cc b/sandboxed_api/sandbox2/mounts.cc index 24afdc6..4c71d52 100644 --- a/sandboxed_api/sandbox2/mounts.cc +++ b/sandboxed_api/sandbox2/mounts.cc @@ -113,6 +113,8 @@ absl::StatusOr ExistingPathInsideDir( absl::Status ValidateInterpreter(absl::string_view interpreter) { const absl::flat_hash_set allowed_interpreters = { "/lib64/ld-linux-x86-64.so.2", + "/lib64/ld64.so.2", // PPC64 + "/lib/ld-linux-aarch64.so.1", // AArch64 }; if (!allowed_interpreters.contains(interpreter)) { @@ -139,6 +141,8 @@ constexpr absl::string_view GetPlatformCPUName() { return "x86_64"; case cpu::kPPC64LE: return "ppc64"; + case cpu::kArm64: + return "aarch64"; default: return "unknown"; } diff --git a/sandboxed_api/sandbox2/network_proxy/client.cc b/sandboxed_api/sandbox2/network_proxy/client.cc index 51b1260..3dde8c1 100644 --- a/sandboxed_api/sandbox2/network_proxy/client.cc +++ b/sandboxed_api/sandbox2/network_proxy/client.cc @@ -49,10 +49,14 @@ constexpr int kRegSyscall = 0; constexpr int kRegArg0 = 3; constexpr int kRegArg1 = 4; constexpr int kRegArg2 = 5; +#elif defined(SAPI_ARM64) +constexpr int kRegResult = 0; +constexpr int kRegSyscall = 8; +constexpr int kRegArg0 = 0; +constexpr int kRegArg1 = 1; +constexpr int kRegArg2 = 2; #endif -constexpr char NetworkProxyClient::kFDName[]; - int NetworkProxyClient::ConnectHandler(int sockfd, const struct sockaddr* addr, socklen_t addrlen) { absl::Status status = Connect(sockfd, addr, addrlen); @@ -154,20 +158,22 @@ void NetworkProxyHandler::InvokeOldAct(int nr, siginfo_t* info, void NetworkProxyHandler::ProcessSeccompTrap(int nr, siginfo_t* info, void* void_context) { - ucontext_t* ctx = (ucontext_t*)(void_context); if (info->si_code != SYS_SECCOMP) { InvokeOldAct(nr, info, void_context); return; } - if (!ctx) return; + auto* ctx = static_cast(void_context); + if (!ctx) { + return; + } #if defined(SAPI_X86_64) auto* registers = ctx->uc_mcontext.gregs; #elif defined(SAPI_PPC64_LE) auto* registers = ctx->uc_mcontext.gp_regs; - using ppc_gpreg_t = std::decay::type; +#elif defined(SAPI_ARM64) + auto* registers = ctx->uc_mcontext.regs; #endif - int syscall = registers[kRegSyscall]; int sockfd; @@ -181,11 +187,10 @@ void NetworkProxyHandler::ProcessSeccompTrap(int nr, siginfo_t* info, #if defined(SAPI_PPC64_LE) } else if (syscall == __NR_socketcall && static_cast(registers[kRegArg0]) == SYS_CONNECT) { - ppc_gpreg_t* args = reinterpret_cast(registers[kRegArg1]); - - sockfd = static_cast(args[0]); - addr = reinterpret_cast(args[1]); - addrlen = static_cast(args[2]); + auto* connect_args = reinterpret_cast(registers[kRegArg1]); + sockfd = static_cast(connect_args[0]); + addr = reinterpret_cast(connect_args[1]); + addrlen = static_cast(connect_args[2]); #endif } else { InvokeOldAct(nr, info, void_context); diff --git a/sandboxed_api/sandbox2/notify_test.cc b/sandboxed_api/sandbox2/notify_test.cc index 77fc67f..8e0bc38 100644 --- a/sandboxed_api/sandbox2/notify_test.cc +++ b/sandboxed_api/sandbox2/notify_test.cc @@ -49,8 +49,12 @@ std::unique_ptr NotifyTestcasePolicy() { .AllowWrite() .AllowSyscall(__NR_close) .AddPolicyOnSyscall(__NR_personality, {SANDBOX2_TRACE}) +#ifdef __NR_open .BlockSyscallWithErrno(__NR_open, ENOENT) +#endif +#ifdef __NR_access .BlockSyscallWithErrno(__NR_access, ENOENT) +#endif .BlockSyscallWithErrno(__NR_openat, ENOENT) .BlockSyscallWithErrno(__NR_prlimit64, EPERM) .BuildOrDie(); diff --git a/sandboxed_api/sandbox2/policy.cc b/sandboxed_api/sandbox2/policy.cc index 4e3fe03..398fbfe 100644 --- a/sandboxed_api/sandbox2/policy.cc +++ b/sandboxed_api/sandbox2/policy.cc @@ -82,41 +82,41 @@ std::vector Policy::GetDefaultPolicy() const { bpf_labels l = {0}; std::vector policy = { - // If compiled arch is different than the runtime one, inform the Monitor. - LOAD_ARCH, - JEQ32(Syscall::GetHostAuditArch(), JUMP(&l, past_arch_check_l)), - JEQ32(AUDIT_ARCH_X86_64, TRACE(cpu::kX8664)), - JEQ32(AUDIT_ARCH_I386, TRACE(cpu::kX86)), - JEQ32(AUDIT_ARCH_PPC64LE, TRACE(cpu::kPPC64LE)), - TRACE(cpu::kUnknown), - LABEL(&l, past_arch_check_l), + // If compiled arch is different than the runtime one, inform the Monitor. + LOAD_ARCH, + JEQ32(Syscall::GetHostAuditArch(), JUMP(&l, past_arch_check_l)), +#if defined(SAPI_X86_64) + JEQ32(AUDIT_ARCH_I386, TRACE(cpu::kX86)), // 32-bit sandboxee +#endif + TRACE(cpu::kUnknown), + LABEL(&l, past_arch_check_l), - // After the policy is uploaded, forkserver will execve the sandboxee. We - // need to allow this execve but not others. Since BPF does not have - // state, we need to inform the Monitor to decide, and for that we use a - // magic value in syscall args 5. Note that this value is not supposed to - // be secret, but just an optimization so that the monitor is not - // triggered on every call to execveat. - LOAD_SYSCALL_NR, - JNE32(__NR_execveat, JUMP(&l, past_execveat_l)), - ARG_32(4), - JNE32(AT_EMPTY_PATH, JUMP(&l, past_execveat_l)), - ARG_32(5), - JNE32(internal::kExecveMagic, JUMP(&l, past_execveat_l)), - SANDBOX2_TRACE, - LABEL(&l, past_execveat_l), + // After the policy is uploaded, forkserver will execve the sandboxee. We + // need to allow this execve but not others. Since BPF does not have + // state, we need to inform the Monitor to decide, and for that we use a + // magic value in syscall args 5. Note that this value is not supposed to + // be secret, but just an optimization so that the monitor is not + // triggered on every call to execveat. + LOAD_SYSCALL_NR, + JNE32(__NR_execveat, JUMP(&l, past_execveat_l)), + ARG_32(4), + JNE32(AT_EMPTY_PATH, JUMP(&l, past_execveat_l)), + ARG_32(5), + JNE32(internal::kExecveMagic, JUMP(&l, past_execveat_l)), + SANDBOX2_TRACE, + LABEL(&l, past_execveat_l), - // Forbid some syscalls because unsafe or too risky. - LOAD_SYSCALL_NR, - JEQ32(__NR_ptrace, DENY), - JEQ32(__NR_bpf, DENY), + // Forbid some syscalls because unsafe or too risky. + LOAD_SYSCALL_NR, + JEQ32(__NR_ptrace, DENY), + JEQ32(__NR_bpf, DENY), - // Disallow clone with CLONE_UNTRACED flag. - JNE32(__NR_clone, JUMP(&l, past_clone_untraced_l)), - // Regardless of arch, we only care about the lower 32-bits of the flags. - ARG_32(0), - JA32(CLONE_UNTRACED, DENY), - LABEL(&l, past_clone_untraced_l), + // Disallow clone with CLONE_UNTRACED flag. + JNE32(__NR_clone, JUMP(&l, past_clone_untraced_l)), + // Regardless of arch, we only care about the lower 32-bits of the flags. + ARG_32(0), + JA32(CLONE_UNTRACED, DENY), + LABEL(&l, past_clone_untraced_l), }; if (bpf_resolve_jumps(&l, policy.data(), policy.size()) != 0) { @@ -129,11 +129,16 @@ std::vector Policy::GetDefaultPolicy() const { std::vector Policy::GetTrackingPolicy() const { return { - LOAD_ARCH, - JEQ32(AUDIT_ARCH_X86_64, TRACE(cpu::kX8664)), - JEQ32(AUDIT_ARCH_I386, TRACE(cpu::kX86)), - JEQ32(AUDIT_ARCH_PPC64LE, TRACE(cpu::kPPC64LE)), - TRACE(cpu::kUnknown), + LOAD_ARCH, +#if defined(SAPI_X86_64) + JEQ32(AUDIT_ARCH_X86_64, TRACE(cpu::kX8664)), + JEQ32(AUDIT_ARCH_I386, TRACE(cpu::kX86)), +#elif defined(SAPI_PPC64_LE) + JEQ32(AUDIT_ARCH_PPC64LE, TRACE(cpu::kPPC64LE)), +#elif defined(SAPI_ARM64) + JEQ32(AUDIT_ARCH_AARCH64, TRACE(cpu::kArm64)), +#endif + TRACE(cpu::kUnknown), }; } diff --git a/sandboxed_api/sandbox2/policy_test.cc b/sandboxed_api/sandbox2/policy_test.cc index 226b450..a7cf852 100644 --- a/sandboxed_api/sandbox2/policy_test.cc +++ b/sandboxed_api/sandbox2/policy_test.cc @@ -50,9 +50,16 @@ std::unique_ptr PolicyTestcasePolicy() { .AllowSyscall(__NR_close) .AllowSyscall(__NR_getppid) .AllowTCGETS() +#ifdef __NR_open .BlockSyscallWithErrno(__NR_open, ENOENT) +#endif .BlockSyscallWithErrno(__NR_openat, ENOENT) +#ifdef __NR_access .BlockSyscallWithErrno(__NR_access, ENOENT) +#endif +#ifdef __NR_faccessat + .BlockSyscallWithErrno(__NR_faccessat, ENOENT) +#endif .BlockSyscallWithErrno(__NR_prlimit64, EPERM) .BuildOrDie(); } @@ -162,7 +169,9 @@ std::unique_ptr MinimalTestcasePolicy() { .AllowStaticStartup() .AllowExit() .BlockSyscallWithErrno(__NR_prlimit64, EPERM) +#ifdef __NR_access .BlockSyscallWithErrno(__NR_access, ENOENT) +#endif .BuildOrDie(); } @@ -197,8 +206,10 @@ TEST(MinimalTest, MinimalSharedBinaryWorks) { .AllowOpen() .AllowExit() .AllowMmap() +#ifdef __NR_access // New glibc accesses /etc/ld.so.preload .BlockSyscallWithErrno(__NR_access, ENOENT) +#endif .BlockSyscallWithErrno(__NR_prlimit64, EPERM) .AddLibrariesForBinary(path) .BuildOrDie(); @@ -223,7 +234,9 @@ TEST(MallocTest, SystemMallocWorks) { .AllowSystemMalloc() .AllowExit() .BlockSyscallWithErrno(__NR_prlimit64, EPERM) +#ifdef __NR_access .BlockSyscallWithErrno(__NR_access, ENOENT) +#endif .BuildOrDie(); Sandbox2 s2(std::move(executor), std::move(policy)); @@ -247,7 +260,9 @@ TEST(MultipleSyscalls, AddPolicyOnSyscallsWorks) { auto policy = PolicyBuilder() +#ifdef __NR_open .BlockSyscallWithErrno(__NR_open, ENOENT) +#endif .BlockSyscallWithErrno(__NR_openat, ENOENT) .AllowStaticStartup() .AllowTcMalloc() @@ -258,7 +273,9 @@ TEST(MultipleSyscalls, AddPolicyOnSyscallsWorks) { .AddPolicyOnSyscalls({__NR_read, __NR_write}, {ERRNO(43)}) .AddPolicyOnSyscall(__NR_umask, {DENY}) .BlockSyscallWithErrno(__NR_prlimit64, EPERM) +#ifdef __NR_access .BlockSyscallWithErrno(__NR_access, ENOENT) +#endif .BuildOrDie(); Sandbox2 s2(std::move(executor), std::move(policy)); diff --git a/sandboxed_api/sandbox2/policybuilder.cc b/sandboxed_api/sandbox2/policybuilder.cc index a560190..c0bfd41 100644 --- a/sandboxed_api/sandbox2/policybuilder.cc +++ b/sandboxed_api/sandbox2/policybuilder.cc @@ -520,7 +520,12 @@ PolicyBuilder& PolicyBuilder::AllowStaticStartup() { }); #endif + if constexpr (host_cpu::IsArm64()) { + BlockSyscallWithErrno(__NR_readlinkat, ENOENT); + } +#ifdef __NR_readlink BlockSyscallWithErrno(__NR_readlink, ENOENT); +#endif return *this; } @@ -881,7 +886,9 @@ PolicyBuilder& PolicyBuilder::AddNetworkProxyPolicy() { AllowFutexOp(FUTEX_WAIT); AllowFutexOp(FUTEX_WAIT_BITSET); AllowSyscalls({ +#ifdef __NR_dup2 __NR_dup2, +#endif __NR_recvmsg, __NR_close, __NR_gettid, diff --git a/sandboxed_api/sandbox2/policybuilder_test.cc b/sandboxed_api/sandbox2/policybuilder_test.cc index b5387d9..652a827 100644 --- a/sandboxed_api/sandbox2/policybuilder_test.cc +++ b/sandboxed_api/sandbox2/policybuilder_test.cc @@ -101,7 +101,7 @@ TEST_F(PolicyBuilderTest, Testpolicy_size) { builder.AllowSystemMalloc(); assert_increased(); builder.AllowSyscall(__NR_munmap); assert_same(); builder.BlockSyscallWithErrno(__NR_munmap, 1); assert_same(); - builder.BlockSyscallWithErrno(__NR_open, 1); + builder.BlockSyscallWithErrno(__NR_openat, 1); assert_increased(); builder.AllowTCGETS(); assert_increased(); diff --git a/sandboxed_api/sandbox2/regs.cc b/sandboxed_api/sandbox2/regs.cc index 07f8e87..7a4c214 100644 --- a/sandboxed_api/sandbox2/regs.cc +++ b/sandboxed_api/sandbox2/regs.cc @@ -23,12 +23,18 @@ #include +#include "absl/base/macros.h" +#include "absl/status/status.h" #include "absl/strings/str_cat.h" #include "sandboxed_api/sandbox2/config.h" #include "sandboxed_api/sandbox2/util/strerror.h" namespace sandbox2 { +#ifndef NT_ARM_SYSTEM_CALL +#define NT_ARM_SYSTEM_CALL 0x404 +#endif + absl::Status Regs::Fetch() { #ifdef SAPI_X86_64 if (ptrace(PTRACE_GETREGS, pid_, 0, &user_regs_) == -1L) { @@ -36,7 +42,7 @@ absl::Status Regs::Fetch() { ") failed: ", StrError(errno))); } #endif - if constexpr (host_cpu::IsPPC64LE()) { + if constexpr (host_cpu::IsPPC64LE() || host_cpu::IsArm64()) { iovec pt_iov = {&user_regs_, sizeof(user_regs_)}; if (ptrace(PTRACE_GETREGSET, pid_, NT_PRSTATUS, &pt_iov) == -1L) { @@ -50,6 +56,24 @@ absl::Status Regs::Fetch() { ") size returned: ", pt_iov.iov_len, " different than sizeof(user_regs_): ", sizeof(user_regs_))); } + + // On AArch64, we are not done yet. Read the syscall number. + if constexpr (host_cpu::IsArm64()) { + iovec sys_iov = {&syscall_number_, sizeof(syscall_number_)}; + + if (ptrace(PTRACE_GETREGSET, pid_, NT_ARM_SYSTEM_CALL, &sys_iov) == -1L) { + return absl::InternalError( + absl::StrCat("ptrace(PTRACE_GETREGSET, pid=", pid_, + ", NT_ARM_SYSTEM_CALL) failed: ", StrError(errno))); + } + if (sys_iov.iov_len != sizeof(syscall_number_)) { + return absl::InternalError(absl::StrCat( + "ptrace(PTRACE_GETREGSET, pid=", pid_, + ", NT_ARM_SYSTEM_CALL) size returned: ", sys_iov.iov_len, + " different than sizeof(syscall_number_): ", + sizeof(syscall_number_))); + } + } } return absl::OkStatus(); } @@ -61,7 +85,7 @@ absl::Status Regs::Store() { ") failed: ", StrError(errno))); } #endif - if constexpr (host_cpu::IsPPC64LE()) { + if constexpr (host_cpu::IsPPC64LE() || host_cpu::IsArm64()) { iovec pt_iov = {&user_regs_, sizeof(user_regs_)}; if (ptrace(PTRACE_SETREGSET, pid_, NT_PRSTATUS, &pt_iov) == -1L) { @@ -69,6 +93,17 @@ absl::Status Regs::Store() { absl::StrCat("ptrace(PTRACE_SETREGSET, pid=", pid_, ") failed: ", StrError(errno))); } + + // Store syscall number on AArch64. + if constexpr (host_cpu::IsArm64()) { + iovec sys_iov = {&syscall_number_, sizeof(syscall_number_)}; + + if (ptrace(PTRACE_SETREGSET, pid_, NT_ARM_SYSTEM_CALL, &sys_iov) == -1L) { + return absl::InternalError( + absl::StrCat("ptrace(PTRACE_SETREGSET, pid=", pid_, + ", NT_ARM_SYSTEM_CALL) failed: ", StrError(errno))); + } + } } return absl::OkStatus(); } @@ -80,6 +115,9 @@ absl::Status Regs::SkipSyscallReturnValue(uint64_t value) { #elif defined(SAPI_PPC64_LE) user_regs_.gpr[0] = -1; user_regs_.gpr[3] = value; +#elif defined(SAPI_ARM64) + user_regs_.regs[0] = -1; + syscall_number_ = value; #endif return Store(); } @@ -114,6 +152,22 @@ Syscall Regs::ToSyscall(cpu::Architecture syscall_arch) const { auto ip = user_regs_.nip; return Syscall(syscall_arch, syscall, args, pid_, sp, ip); } +#elif defined(SAPI_ARM64) + if (ABSL_PREDICT_TRUE(syscall_arch == cpu::kArm64)) { + Syscall::Args args = { + // First argument should be orig_x0, which is not available to ptrace on + // AArch64 (see + // https://undo.io/resources/arm64-vs-arm32-whats-different-linux-programmers/), + // as it will have been overwritten. For our use case, though, using + // regs[0] is fine, as we are always called on syscall entry and never + // on exit. + user_regs_.regs[0], user_regs_.regs[1], user_regs_.regs[2], + user_regs_.regs[3], user_regs_.regs[4], user_regs_.regs[5], + }; + auto sp = user_regs_.sp; + auto ip = user_regs_.pc; + return Syscall(syscall_arch, syscall_number_, args, pid_, sp, ip); + } #endif return Syscall(pid_); } @@ -169,6 +223,14 @@ void Regs::StoreRegisterValuesInProtobuf(RegisterValues* values) const { regs->set_zero1(user_regs_.zero1); regs->set_zero2(user_regs_.zero2); regs->set_zero3(user_regs_.zero3); +#elif defined(SAPI_ARM64) + RegisterAarch64* regs = values->mutable_register_aarch64(); + for (int i = 0; i < ABSL_ARRAYSIZE(user_regs_.regs); ++i) { + regs->add_regs(user_regs_.regs[i]); + } + regs->set_sp(user_regs_.sp); + regs->set_pc(user_regs_.pc); + regs->set_pstate(user_regs_.pstate); #endif } diff --git a/sandboxed_api/sandbox2/regs.h b/sandboxed_api/sandbox2/regs.h index 661c8b6..cce7022 100644 --- a/sandboxed_api/sandbox2/regs.h +++ b/sandboxed_api/sandbox2/regs.h @@ -105,6 +105,11 @@ class Regs { uint64_t zero1; uint64_t zero2; uint64_t zero3; +#elif defined(SAPI_ARM64) + uint64_t regs[31]; + uint64_t sp; + uint64_t pc; + uint64_t pstate; #else static_assert(false, "Host CPU architecture not supported, see config.h"); #endif @@ -115,6 +120,9 @@ class Regs { // Registers fetched with ptrace(PR_GETREGS/GETREGSET, pid). PtraceRegisters user_regs_ = {}; + + // On AArch64, obtaining the syscall number needs a specific call to ptrace() + int syscall_number_ = 0; }; } // namespace sandbox2 diff --git a/sandboxed_api/sandbox2/stack_trace.cc b/sandboxed_api/sandbox2/stack_trace.cc index 7df7952..868b89e 100644 --- a/sandboxed_api/sandbox2/stack_trace.cc +++ b/sandboxed_api/sandbox2/stack_trace.cc @@ -31,6 +31,7 @@ #include "absl/strings/strip.h" #include "libcap/include/sys/capability.h" #include "sandboxed_api/sandbox2/comms.h" +#include "sandboxed_api/sandbox2/config.h" #include "sandboxed_api/sandbox2/executor.h" #include "sandboxed_api/sandbox2/ipc.h" #include "sandboxed_api/sandbox2/limits.h" @@ -270,6 +271,9 @@ bool StackTracePeer::LaunchLibunwindSandbox(const Regs* regs, } std::vector GetStackTrace(const Regs* regs, const Mounts& mounts) { + if constexpr (host_cpu::IsArm64()) { + return {"[Stack traces unavailable]"}; + } if (absl::GetFlag(FLAGS_sandbox_disable_all_stack_traces)) { return {"[Stacktraces disabled]"}; } diff --git a/sandboxed_api/sandbox2/syscall.cc b/sandboxed_api/sandbox2/syscall.cc index 7e06717..980320a 100644 --- a/sandboxed_api/sandbox2/syscall.cc +++ b/sandboxed_api/sandbox2/syscall.cc @@ -42,6 +42,8 @@ std::string Syscall::GetArchDescription(cpu::Architecture arch) { return "[X86-32]"; case cpu::kPPC64LE: return "[PPC-64]"; + case cpu::kArm64: + return "[Arm-64]"; default: LOG(ERROR) << "Unknown CPU architecture: " << arch; return absl::StrFormat("[UNKNOWN_ARCH:%d]", arch); @@ -54,6 +56,8 @@ uint32_t Syscall::GetHostAuditArch() { return AUDIT_ARCH_X86_64; case cpu::kPPC64LE: return AUDIT_ARCH_PPC64LE; + case cpu::kArm64: + return AUDIT_ARCH_AARCH64; default: // The static_assert() in config.h should prevent us from ever getting // here. diff --git a/sandboxed_api/sandbox2/syscall_defs.cc b/sandboxed_api/sandbox2/syscall_defs.cc index 410d054..b79f6c7 100644 --- a/sandboxed_api/sandbox2/syscall_defs.cc +++ b/sandboxed_api/sandbox2/syscall_defs.cc @@ -1216,6 +1216,291 @@ constexpr SyscallTable::Entry kSyscallDataPPC64LE[] = { MakeEntry("pwritev2", kHex, kHex, kHex, kHex, kHex, kHex), // 381 }; +// TODO(cblichmann): Confirm the entries in this list. +// https://github.com/torvalds/linux/blob/v5.8/include/uapi/asm-generic/unistd.h +constexpr SyscallTable::Entry kSyscallDataArm64[] = { + MakeEntry("io_setup", UnknownArguments()), // 0 + MakeEntry("io_destroy", UnknownArguments()), // 1 + MakeEntry("io_submit", UnknownArguments()), // 2 + MakeEntry("io_cancel", UnknownArguments()), // 3 + MakeEntry("io_getevents", UnknownArguments()), // 4 + MakeEntry("setxattr", kPath, kString, kGen, kInt, kHex, kGen), // 5 + MakeEntry("lsetxattr", kPath, kString, kGen, kInt, kHex, kGen), // 6 + MakeEntry("fsetxattr", UnknownArguments()), // 7 + MakeEntry("getxattr", kPath, kString, kGen, kInt, kGen, kGen), // 8 + MakeEntry("lgetxattr", kPath, kString, kGen, kInt, kGen, kGen), // 9 + MakeEntry("fgetxattr", UnknownArguments()), // 10 + MakeEntry("listxattr", kPath, kGen, kInt, kGen, kGen, kGen), // 11 + MakeEntry("llistxattr", kPath, kGen, kInt, kGen, kGen, kGen), // 12 + MakeEntry("flistxattr", UnknownArguments()), // 13 + MakeEntry("removexattr", kPath, kString, kGen, kGen, kGen, kGen), // 14 + MakeEntry("lremovexattr", UnknownArguments()), // 15 + MakeEntry("fremovexattr", UnknownArguments()), // 16 + MakeEntry("getcwd", UnknownArguments()), // 17 + MakeEntry("lookup_dcookie", UnknownArguments()), // 18 + MakeEntry("eventfd2", UnknownArguments()), // 19 + MakeEntry("epoll_create1", UnknownArguments()), // 20 + MakeEntry("epoll_ctl", UnknownArguments()), // 21 + MakeEntry("epoll_pwait", UnknownArguments()), // 22 + MakeEntry("dup", UnknownArguments()), // 23 + MakeEntry("dup3", UnknownArguments()), // 24 + MakeEntry("fcntl", UnknownArguments()), // 25 + MakeEntry("inotify_init1", UnknownArguments()), // 26 + MakeEntry("inotify_add_watch", UnknownArguments()), // 27 + MakeEntry("inotify_rm_watch", UnknownArguments()), // 28 + MakeEntry("ioctl", UnknownArguments()), // 29 + MakeEntry("ioprio_set", UnknownArguments()), // 30 + MakeEntry("ioprio_get", UnknownArguments()), // 31 + MakeEntry("flock", UnknownArguments()), // 32 + MakeEntry("mknodat", kGen, kPath, kGen, kGen, kGen, kGen), // 33 + MakeEntry("mkdirat", kGen, kPath, kGen, kGen, kGen, kGen), // 34 + MakeEntry("unlinkat", kGen, kPath, kGen, kGen, kGen, kGen), // 35 + MakeEntry("symlinkat", kPath, kGen, kPath, kGen, kGen, kGen), // 36 + MakeEntry("linkat", kGen, kPath, kGen, kPath, kGen, kGen), // 37 + MakeEntry("renameat", kGen, kPath, kGen, kPath, kGen, kGen), // 38 + MakeEntry("umount2", kPath, kHex, kGen, kGen, kGen, kGen), // 39 + MakeEntry("mount", kPath, kPath, kString, kHex, kGen, kGen), // 40 + MakeEntry("pivot_root", kPath, kPath, kGen, kGen, kGen, kGen), // 41 + MakeEntry("nfsservctl", UnknownArguments()), // 42 + MakeEntry("statfs", kPath, kGen, kGen, kGen, kGen, kGen), // 43 + MakeEntry("fstatfs", UnknownArguments()), // 44 + MakeEntry("truncate", kPath, kInt, kGen, kGen, kGen, kGen), // 45 + MakeEntry("ftruncate", UnknownArguments()), // 46 + MakeEntry("fallocate", UnknownArguments()), // 47 + MakeEntry("faccessat", kGen, kPath, kGen, kGen, kGen, kGen), // 48 + MakeEntry("chdir", kPath, kGen, kGen, kGen, kGen, kGen), // 49 + MakeEntry("fchdir", UnknownArguments()), // 50 + MakeEntry("chroot", kPath, kGen, kGen, kGen, kGen, kGen), // 51 + MakeEntry("fchmod", UnknownArguments()), // 52 + MakeEntry("fchmodat", kGen, kPath, kGen, kGen, kGen, kGen), // 53 + MakeEntry("fchownat", kGen, kPath, kGen, kGen, kGen, kGen), // 54 + MakeEntry("fchown", UnknownArguments()), // 55 + MakeEntry("openat", kGen, kPath, kOct, kHex, kGen, kGen), // 56 + MakeEntry("close", kInt, kGen, kGen, kGen, kGen, kGen), // 57 + MakeEntry("vhangup", UnknownArguments()), // 58 + MakeEntry("pipe2", UnknownArguments()), // 59 + MakeEntry("quotactl", kInt, kPath, kInt, kGen, kGen, kGen), // 60 + MakeEntry("getdents64", UnknownArguments()), // 61 + MakeEntry("lseek", UnknownArguments()), // 62 + MakeEntry("read", kInt, kHex, kInt, kGen, kGen, kGen), // 63 + MakeEntry("write", kInt, kHex, kInt, kGen, kGen, kGen), // 64 + MakeEntry("readv", UnknownArguments()), // 65 + MakeEntry("writev", UnknownArguments()), // 66 + MakeEntry("pread64", UnknownArguments()), // 67 + MakeEntry("pwrite64", UnknownArguments()), // 68 + MakeEntry("preadv", UnknownArguments()), // 69 + MakeEntry("pwritev", UnknownArguments()), // 70 + MakeEntry("sendfile", UnknownArguments()), // 71 + MakeEntry("pselect6", UnknownArguments()), // 72 + MakeEntry("ppoll", UnknownArguments()), // 73 + MakeEntry("signalfd4", UnknownArguments()), // 74 + MakeEntry("vmsplice", UnknownArguments()), // 75 + MakeEntry("splice", UnknownArguments()), // 76 + MakeEntry("tee", UnknownArguments()), // 77 + MakeEntry("readlinkat", kGen, kPath, kGen, kGen, kGen, kGen), // 78 + MakeEntry("newfstatat", kGen, kPath, kGen, kGen, kGen, kGen), // 79 + MakeEntry("fstat", kInt, kHex, kGen, kGen, kGen, kGen), // 80 + MakeEntry("sync", UnknownArguments()), // 81 + MakeEntry("fsync", UnknownArguments()), // 82 + MakeEntry("fdatasync", UnknownArguments()), // 83 + MakeEntry("sync_file_range", UnknownArguments()), // 84 + MakeEntry("timerfd_create", UnknownArguments()), // 85 + MakeEntry("timerfd_settime", UnknownArguments()), // 86 + MakeEntry("timerfd_gettime", UnknownArguments()), // 87 + MakeEntry("utimensat", UnknownArguments()), // 88 + MakeEntry("acct", kPath, kGen, kGen, kGen, kGen, kGen), // 89 + MakeEntry("capget", UnknownArguments()), // 90 + MakeEntry("capset", UnknownArguments()), // 91 + MakeEntry("personality", UnknownArguments()), // 92 + MakeEntry("exit", kInt, kGen, kGen, kGen, kGen, kGen), // 93 + MakeEntry("exit_group", kInt, kGen, kGen, kGen, kGen, kGen), // 94 + MakeEntry("waitid", UnknownArguments()), // 95 + MakeEntry("set_tid_address", kHex, kGen, kGen, kGen, kGen, kGen), // 96 + MakeEntry("unshare", UnknownArguments()), // 97 + MakeEntry("futex", UnknownArguments()), // 98 + MakeEntry("set_robust_list", UnknownArguments()), // 99 + MakeEntry("get_robust_list", UnknownArguments()), // 100 + MakeEntry("nanosleep", kHex, kHex, kGen, kGen, kGen, kGen), // 101 + MakeEntry("getitimer", UnknownArguments()), // 102 + MakeEntry("setitimer", UnknownArguments()), // 103 + MakeEntry("kexec_load", UnknownArguments()), // 104 + MakeEntry("init_module", UnknownArguments()), // 105 + MakeEntry("delete_module", UnknownArguments()), // 106 + MakeEntry("timer_create", UnknownArguments()), // 107 + MakeEntry("timer_gettime", UnknownArguments()), // 108 + MakeEntry("timer_getoverrun", UnknownArguments()), // 109 + MakeEntry("timer_settime", UnknownArguments()), // 110 + MakeEntry("timer_delete", UnknownArguments()), // 111 + MakeEntry("clock_settime", UnknownArguments()), // 112 + MakeEntry("clock_gettime", UnknownArguments()), // 113 + MakeEntry("clock_getres", UnknownArguments()), // 114 + MakeEntry("clock_nanosleep", UnknownArguments()), // 115 + MakeEntry("syslog", UnknownArguments()), // 116 + MakeEntry("ptrace", UnknownArguments()), // 117 + MakeEntry("sched_setparam", UnknownArguments()), // 118 + MakeEntry("sched_setscheduler", UnknownArguments()), // 119 + MakeEntry("sched_getscheduler", UnknownArguments()), // 120 + MakeEntry("sched_getparam", UnknownArguments()), // 121 + MakeEntry("sched_setaffinity", UnknownArguments()), // 122 + MakeEntry("sched_getaffinity", UnknownArguments()), // 123 + MakeEntry("sched_yield", UnknownArguments()), // 124 + MakeEntry("sched_get_priority_max", UnknownArguments()), // 125 + MakeEntry("sched_get_priority_min", UnknownArguments()), // 126 + MakeEntry("sched_rr_get_interval", UnknownArguments()), // 127 + MakeEntry("restart_syscall", UnknownArguments()), // 128 + MakeEntry("kill", kInt, kSignal, kGen, kGen, kGen, kGen), // 129 + MakeEntry("tkill", kInt, kSignal, kGen, kGen, kGen, kGen), // 130 + MakeEntry("tgkill", kInt, kInt, kSignal, kGen, kGen, kGen), // 131 + MakeEntry("sigaltstack", UnknownArguments()), // 132 + MakeEntry("rt_sigsuspend", UnknownArguments()), // 133 + MakeEntry("rt_sigaction", kSignal, kHex, kHex, kInt, kGen, kGen), // 134 + MakeEntry("rt_sigprocmask", UnknownArguments()), // 135 + MakeEntry("rt_sigpending", UnknownArguments()), // 136 + MakeEntry("rt_sigtimedwait", UnknownArguments()), // 137 + MakeEntry("rt_sigqueueinfo", UnknownArguments()), // 138 + MakeEntry("rt_sigreturn", UnknownArguments()), // 139 + MakeEntry("setpriority", UnknownArguments()), // 140 + MakeEntry("getpriority", UnknownArguments()), // 141 + MakeEntry("reboot", UnknownArguments()), // 142 + MakeEntry("setregid", UnknownArguments()), // 143 + MakeEntry("setgid", UnknownArguments()), // 144 + MakeEntry("setreuid", UnknownArguments()), // 145 + MakeEntry("setuid", UnknownArguments()), // 146 + MakeEntry("setresuid", UnknownArguments()), // 147 + MakeEntry("getresuid", UnknownArguments()), // 148 + MakeEntry("setresgid", UnknownArguments()), // 149 + MakeEntry("getresgid", UnknownArguments()), // 150 + MakeEntry("setfsuid", UnknownArguments()), // 151 + MakeEntry("setfsgid", UnknownArguments()), // 152 + MakeEntry("times", UnknownArguments()), // 153 + MakeEntry("setpgid", UnknownArguments()), // 154 + MakeEntry("getpgid", UnknownArguments()), // 155 + MakeEntry("getsid", UnknownArguments()), // 156 + MakeEntry("setsid", UnknownArguments()), // 157 + MakeEntry("getgroups", UnknownArguments()), // 158 + MakeEntry("setgroups", UnknownArguments()), // 159 + MakeEntry("uname", UnknownArguments()), // 160 + MakeEntry("sethostname", UnknownArguments()), // 161 + MakeEntry("setdomainname", UnknownArguments()), // 162 + MakeEntry("getrlimit", UnknownArguments()), // 163 + MakeEntry("setrlimit", UnknownArguments()), // 164 + MakeEntry("getrusage", UnknownArguments()), // 165 + MakeEntry("umask", kHex, kGen, kGen, kGen, kGen, kGen), // 166 + MakeEntry("prctl", kInt, kHex, kHex, kHex, kHex, kGen), // 167 + MakeEntry("getcpu", kHex, kHex, kHex, kGen, kGen, kGen), // 168 + MakeEntry("gettimeofday", kHex, kHex, kGen, kGen, kGen, kGen), // 169 + MakeEntry("settimeofday", kHex, kHex, kGen, kGen, kGen, kGen), // 170 + MakeEntry("adjtimex", UnknownArguments()), // 171 + MakeEntry("getpid", UnknownArguments()), // 172 + MakeEntry("getppid", UnknownArguments()), // 173 + MakeEntry("getuid", UnknownArguments()), // 174 + MakeEntry("geteuid", UnknownArguments()), // 175 + MakeEntry("getgid", UnknownArguments()), // 176 + MakeEntry("getegid", UnknownArguments()), // 177 + MakeEntry("gettid", UnknownArguments()), // 178 + MakeEntry("sysinfo", UnknownArguments()), // 179 + MakeEntry("mq_open", UnknownArguments()), // 180 + MakeEntry("mq_unlink", UnknownArguments()), // 181 + MakeEntry("mq_timedsend", UnknownArguments()), // 182 + MakeEntry("mq_timedreceive", UnknownArguments()), // 183 + MakeEntry("mq_notify", UnknownArguments()), // 184 + MakeEntry("mq_getsetattr", UnknownArguments()), // 185 + MakeEntry("msgget", UnknownArguments()), // 186 + MakeEntry("msgctl", UnknownArguments()), // 187 + MakeEntry("msgrcv", UnknownArguments()), // 188 + MakeEntry("msgsnd", UnknownArguments()), // 189 + MakeEntry("semget", UnknownArguments()), // 190 + MakeEntry("semctl", UnknownArguments()), // 191 + MakeEntry("semtimedop", UnknownArguments()), // 192 + MakeEntry("semop", UnknownArguments()), // 193 + MakeEntry("shmget", UnknownArguments()), // 194 + MakeEntry("shmctl", UnknownArguments()), // 195 + MakeEntry("shmat", UnknownArguments()), // 196 + MakeEntry("shmdt", UnknownArguments()), // 197 + MakeEntry("socket", kAddressFamily, kInt, kInt, kGen, kGen, kGen), // 198 + MakeEntry("socketpair", UnknownArguments()), // 199 + MakeEntry("bind", UnknownArguments()), // 200 + MakeEntry("listen", UnknownArguments()), // 201 + MakeEntry("accept", UnknownArguments()), // 202 + MakeEntry("connect", kInt, kSockaddr, kInt, kGen, kGen, kGen), // 203 + MakeEntry("getsockname", UnknownArguments()), // 204 + MakeEntry("getpeername", UnknownArguments()), // 205 + MakeEntry("sendto", kInt, kGen, kInt, kHex, kSockaddr, kInt), // 206 + MakeEntry("recvfrom", UnknownArguments()), // 207 + MakeEntry("setsockopt", UnknownArguments()), // 208 + MakeEntry("getsockopt", UnknownArguments()), // 209 + MakeEntry("shutdown", UnknownArguments()), // 210 + MakeEntry("sendmsg", kInt, kSockmsghdr, kHex, kGen, kGen, kGen), // 211 + MakeEntry("recvmsg", UnknownArguments()), // 212 + MakeEntry("readahead", UnknownArguments()), // 213 + MakeEntry("brk", kHex, kGen, kGen, kGen, kGen, kGen), // 214 + MakeEntry("munmap", kHex, kHex, kGen, kGen, kGen, kGen), // 215 + MakeEntry("mremap", UnknownArguments()), // 216 + MakeEntry("add_key", UnknownArguments()), // 217 + MakeEntry("request_key", UnknownArguments()), // 218 + MakeEntry("keyctl", UnknownArguments()), // 219 + MakeEntry("clone", kCloneFlag, kHex, kHex, kHex, kHex, kGen), // 220 + MakeEntry("execve", kPath, kHex, kHex, kGen, kGen, kGen), // 221 + MakeEntry("mmap", kHex, kInt, kHex, kHex, kInt, kInt), // 222 + MakeEntry("fadvise64", UnknownArguments()), // 223 + MakeEntry("swapon", kPath, kHex, kGen, kGen, kGen, kGen), // 224 + MakeEntry("swapoff", kPath, kGen, kGen, kGen, kGen, kGen), // 225 + MakeEntry("mprotect", kHex, kHex, kHex, kGen, kGen, kGen), // 226 + MakeEntry("msync", UnknownArguments()), // 227 + MakeEntry("mlock", UnknownArguments()), // 228 + MakeEntry("munlock", UnknownArguments()), // 229 + MakeEntry("mlockall", UnknownArguments()), // 230 + MakeEntry("munlockall", UnknownArguments()), // 231 + MakeEntry("mincore", UnknownArguments()), // 232 + MakeEntry("madvise", UnknownArguments()), // 233 + MakeEntry("remap_file_pages", UnknownArguments()), // 234 + MakeEntry("mbind", UnknownArguments()), // 235 + MakeEntry("get_mempolicy", UnknownArguments()), // 236 + MakeEntry("set_mempolicy", UnknownArguments()), // 237 + MakeEntry("migrate_pages", UnknownArguments()), // 238 + MakeEntry("move_pages", UnknownArguments()), // 239 + MakeEntry("rt_tgsigqueueinfo", UnknownArguments()), // 240 + MakeEntry("perf_event_open", UnknownArguments()), // 241 + MakeEntry("accept4", UnknownArguments()), // 242 + MakeEntry("recvmmsg", kInt, kHex, kHex, kHex, kGen, kGen), // 243 + SYSCALLS_UNUSED("UNUSED244"), // 244 + SYSCALLS_UNUSED("UNUSED245"), // 245 + SYSCALLS_UNUSED("UNUSED246"), // 246 + SYSCALLS_UNUSED("UNUSED247"), // 247 + SYSCALLS_UNUSED("UNUSED248"), // 248 + SYSCALLS_UNUSED("UNUSED249"), // 249 + SYSCALLS_UNUSED("UNUSED250"), // 250 + SYSCALLS_UNUSED("UNUSED251"), // 251 + SYSCALLS_UNUSED("UNUSED252"), // 252 + SYSCALLS_UNUSED("UNUSED253"), // 253 + SYSCALLS_UNUSED("UNUSED254"), // 254 + SYSCALLS_UNUSED("UNUSED255"), // 255 + SYSCALLS_UNUSED("UNUSED256"), // 256 + SYSCALLS_UNUSED("UNUSED257"), // 257 + SYSCALLS_UNUSED("UNUSED258"), // 258 + SYSCALLS_UNUSED("UNUSED259"), // 259 + MakeEntry("wait4", kInt, kHex, kHex, kHex, kGen, kGen), // 260 + MakeEntry("prlimit64", kInt, kInt, kHex, kHex, kGen, kGen), // 261 + MakeEntry("fanotify_init", kHex, kHex, kInt, kGen, kGen, kGen), // 262 + MakeEntry("fanotify_mark", kInt, kHex, kInt, kPath, kGen, kGen), // 263 + MakeEntry("name_to_handle_at", kInt, kGen, kHex, kHex, kHex, kGen), // 264 + MakeEntry("open_by_handle_at", kInt, kHex, kHex, kGen, kGen, kGen), // 265 + MakeEntry("clock_adjtime", kInt, kHex, kGen, kGen, kGen, kGen), // 266 + MakeEntry("syncfs", kInt, kGen, kGen, kGen, kGen, kGen), // 267 + MakeEntry("setns", kInt, kHex, kGen, kGen, kGen, kGen), // 268 + MakeEntry("sendmmsg", kInt, kHex, kInt, kHex, kGen, kGen), // 269 + MakeEntry("process_vm_readv", kInt, kHex, kInt, kHex, kInt, kInt), // 270 + MakeEntry("process_vm_writev", kInt, kHex, kInt, kHex, kInt, kInt), // 271 + MakeEntry("kcmp", kInt, kInt, kInt, kHex, kHex, kGen), // 272 + MakeEntry("finit_module", kInt, kPath, kHex, kGen, kGen, kGen), // 273 + MakeEntry("sched_setattr", UnknownArguments()), // 274 + MakeEntry("sched_getattr", UnknownArguments()), // 275 + MakeEntry("renameat2", kGen, kPath, kGen, kPath, kGen, kGen), // 276 + MakeEntry("seccomp", UnknownArguments()), // 277 + MakeEntry("getrandom", UnknownArguments()), // 278 + MakeEntry("memfd_create", UnknownArguments()), // 279 +}; + #undef SYSCALLS_UNUSED00_99 #undef SYSCALLS_UNUSED50_99 #undef SYSCALLS_UNUSED00_49 @@ -1230,6 +1515,8 @@ SyscallTable SyscallTable::get(cpu::Architecture arch) { return SyscallTable(kSyscallDataX8632); case cpu::kPPC64LE: return SyscallTable(kSyscallDataPPC64LE); + case cpu::kArm64: + return SyscallTable(kSyscallDataArm64); default: return SyscallTable(); } diff --git a/sandboxed_api/sandbox2/util.cc b/sandboxed_api/sandbox2/util.cc index 3e97abc..c3d90ba 100644 --- a/sandboxed_api/sandbox2/util.cc +++ b/sandboxed_api/sandbox2/util.cc @@ -126,8 +126,9 @@ ABSL_ATTRIBUTE_NO_SANITIZE_ADDRESS ABSL_ATTRIBUTE_NOINLINE pid_t CloneAndJump(int flags, jmp_buf* env_ptr) { uint8_t stack_buf[PTHREAD_STACK_MIN] ABSL_CACHELINE_ALIGNED; - static_assert(host_cpu::IsX8664() || host_cpu::IsPPC64LE(), - "Host CPU architecture not supported, see config.h"); + static_assert( + host_cpu::IsX8664() || host_cpu::IsPPC64LE() || host_cpu::IsArm64(), + "Host CPU architecture not supported, see config.h"); // Stack grows down. void* stack = stack_buf + sizeof(stack_buf); int r; diff --git a/sandboxed_api/sandbox2/violation.proto b/sandboxed_api/sandbox2/violation.proto index 57a3b3d..235cf6a 100644 --- a/sandboxed_api/sandbox2/violation.proto +++ b/sandboxed_api/sandbox2/violation.proto @@ -25,7 +25,6 @@ enum PBViolationType { SYSCALL_ARCHITECTURE_MISMATCH = 3; } -// X86_64 not allowed (naming convention...) message RegisterX8664 { uint64 r15 = 1; uint64 r14 = 2; @@ -77,7 +76,6 @@ message RegisterPowerpc64 { uint64 zero3 = 17; } -// Deprecated. message RegisterAarch64 { repeated uint64 regs = 1; uint64 sp = 2; @@ -90,7 +88,7 @@ message RegisterValues { oneof register_values { RegisterX8664 register_x86_64 = 2; RegisterPowerpc64 register_powerpc64 = 3; - RegisterAarch64 register_aarch64 = 4; // Deprecated. + RegisterAarch64 register_aarch64 = 4; } } From 71e6237cf70eda03758f9791f60a53fac4762576 Mon Sep 17 00:00:00 2001 From: Katarzyna Miernikiewicz Date: Fri, 11 Sep 2020 16:35:52 +0000 Subject: [PATCH 71/82] cast changes --- .../openjpeg/examples/decompress_example.cc | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/oss-internship-2020/openjpeg/examples/decompress_example.cc b/oss-internship-2020/openjpeg/examples/decompress_example.cc index 68bddf8..f2e21a4 100644 --- a/oss-internship-2020/openjpeg/examples/decompress_example.cc +++ b/oss-internship-2020/openjpeg/examples/decompress_example.cc @@ -117,10 +117,11 @@ int main(int argc, char* argv[]) { CHECK(sandbox.TransferFromSandboxee(&image_components).ok()) << "Transfer from sandboxee failed"; - image.mutable_data()->comps = (opj_image_comp_t*)image_components.GetLocal(); + image.mutable_data()->comps = + static_cast(image_components.GetLocal()); - unsigned int width = reinterpret_cast(image.data().comps[0].w); - unsigned height = reinterpret_cast(image.data().comps[0].h); + unsigned int width = static_cast(image.data().comps[0].w); + unsigned int height = static_cast(image.data().comps[0].h); std::vector> data(components); sapi::v::Array image_components_data(width * height); @@ -139,7 +140,7 @@ int main(int argc, char* argv[]) { // Convert the image to the desired format and save it to the file. int error = - imagetopnm(reinterpret_cast(image.GetLocal()), argv[2], 0); + imagetopnm(static_cast(image.GetLocal()), argv[2], 0); CHECK(!error) << "Image convert failed"; // Clean up. From e8a15ea151078cf83477afc67aab465485f7d57b Mon Sep 17 00:00:00 2001 From: Bohdan Date: Sun, 13 Sep 2020 18:20:10 +0300 Subject: [PATCH 72/82] Moved tests to root folder, removed unused headers --- oss-internship-2020/guetzli/BUILD.bazel | 24 +++++++++++ oss-internship-2020/guetzli/guetzli_sandbox.h | 2 +- .../guetzli/guetzli_sandboxed.cc | 6 --- .../guetzli/{tests => }/guetzli_sapi_test.cc | 6 +-- .../{tests => }/guetzli_transaction_test.cc | 2 +- .../guetzli/{tests => }/testdata/bees.png | Bin .../{tests => }/testdata/bees_reference.jpg | Bin .../guetzli/{tests => }/testdata/nature.jpg | Bin .../{tests => }/testdata/nature_reference.jpg | Bin oss-internship-2020/guetzli/tests/BUILD.bazel | 39 ------------------ 10 files changed, 29 insertions(+), 50 deletions(-) rename oss-internship-2020/guetzli/{tests => }/guetzli_sapi_test.cc (95%) rename oss-internship-2020/guetzli/{tests => }/guetzli_transaction_test.cc (99%) rename oss-internship-2020/guetzli/{tests => }/testdata/bees.png (100%) rename oss-internship-2020/guetzli/{tests => }/testdata/bees_reference.jpg (100%) rename oss-internship-2020/guetzli/{tests => }/testdata/nature.jpg (100%) rename oss-internship-2020/guetzli/{tests => }/testdata/nature_reference.jpg (100%) delete mode 100644 oss-internship-2020/guetzli/tests/BUILD.bazel diff --git a/oss-internship-2020/guetzli/BUILD.bazel b/oss-internship-2020/guetzli/BUILD.bazel index c1c40b2..d28eca9 100644 --- a/oss-internship-2020/guetzli/BUILD.bazel +++ b/oss-internship-2020/guetzli/BUILD.bazel @@ -54,3 +54,27 @@ cc_binary( ":guetzli_sapi", ], ) + +cc_test( + name = "transaction_tests", + srcs = ["guetzli_transaction_test.cc"], + visibility=["//visibility:public"], + deps = [ + "//:guetzli_sapi", + "@com_google_googletest//:gtest_main", + ], + size = "large", + data = glob(["testdata/*"]), +) + +cc_test( + name = "sapi_lib_tests", + srcs = ["guetzli_sapi_test.cc"], + visibility=["//visibility:public"], + deps = [ + "//:guetzli_sapi", + "@com_google_googletest//:gtest_main", + ], + size = "large", + data = glob(["testdata/*"]), +) diff --git a/oss-internship-2020/guetzli/guetzli_sandbox.h b/oss-internship-2020/guetzli/guetzli_sandbox.h index 712b012..4793d24 100644 --- a/oss-internship-2020/guetzli/guetzli_sandbox.h +++ b/oss-internship-2020/guetzli/guetzli_sandbox.h @@ -22,7 +22,7 @@ namespace guetzli::sandbox { class GuetzliSapiSandbox : public GuetzliSandbox { - public: + public: std::unique_ptr ModifyPolicy( sandbox2::PolicyBuilder*) override { diff --git a/oss-internship-2020/guetzli/guetzli_sandboxed.cc b/oss-internship-2020/guetzli/guetzli_sandboxed.cc index 6c53bf3..ec5723c 100644 --- a/oss-internship-2020/guetzli/guetzli_sandboxed.cc +++ b/oss-internship-2020/guetzli/guetzli_sandboxed.cc @@ -12,12 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include -#include -#include -#include -#include - #include #include diff --git a/oss-internship-2020/guetzli/tests/guetzli_sapi_test.cc b/oss-internship-2020/guetzli/guetzli_sapi_test.cc similarity index 95% rename from oss-internship-2020/guetzli/tests/guetzli_sapi_test.cc rename to oss-internship-2020/guetzli/guetzli_sapi_test.cc index 433c4a1..03cb587 100644 --- a/oss-internship-2020/guetzli/tests/guetzli_sapi_test.cc +++ b/oss-internship-2020/guetzli/guetzli_sapi_test.cc @@ -41,7 +41,7 @@ constexpr int kDefaultQualityTarget = 95; constexpr int kDefaultMemlimitMb = 6000; constexpr absl::string_view kRelativePathToTestdata = - "/guetzli_sandboxed/tests/testdata/"; + "/guetzli_sandboxed/testdata/"; std::string GetPathToInputFile(absl::string_view filename) { return absl::StrCat(getenv("TEST_SRCDIR"), kRelativePathToTestdata, filename); @@ -88,7 +88,7 @@ TEST_F(GuetzliSapiTest, ProcessRGB) { kDefaultMemlimitMb }; sapi::v::LenVal output(0); - auto processing_result = api_->ProcessRgb(processing_params.PtrBefore(), + sapi::StatusOr processing_result = api_->ProcessRgb(processing_params.PtrBefore(), output.PtrBoth()); ASSERT_TRUE(processing_result.value_or(false)) << "Error processing rgb data"; std::string reference_data = @@ -115,7 +115,7 @@ TEST_F(GuetzliSapiTest, ProcessJpeg) { kDefaultMemlimitMb }; sapi::v::LenVal output(0); - auto processing_result = api_->ProcessJpeg(processing_params.PtrBefore(), + sapi::StatusOr processing_result = api_->ProcessJpeg(processing_params.PtrBefore(), output.PtrBoth()); ASSERT_TRUE(processing_result.value_or(false)) << "Error processing jpg data"; std::string reference_data = diff --git a/oss-internship-2020/guetzli/tests/guetzli_transaction_test.cc b/oss-internship-2020/guetzli/guetzli_transaction_test.cc similarity index 99% rename from oss-internship-2020/guetzli/tests/guetzli_transaction_test.cc rename to oss-internship-2020/guetzli/guetzli_transaction_test.cc index 875b328..f723c5c 100644 --- a/oss-internship-2020/guetzli/tests/guetzli_transaction_test.cc +++ b/oss-internship-2020/guetzli/guetzli_transaction_test.cc @@ -44,7 +44,7 @@ constexpr int kDefaultQualityTarget = 95; constexpr int kDefaultMemlimitMb = 6000; constexpr absl::string_view kRelativePathToTestdata = - "/guetzli_sandboxed/tests/testdata/"; + "/guetzli_sandboxed/testdata/"; std::string GetPathToFile(absl::string_view filename) { return absl::StrCat(getenv("TEST_SRCDIR"), kRelativePathToTestdata, filename); diff --git a/oss-internship-2020/guetzli/tests/testdata/bees.png b/oss-internship-2020/guetzli/testdata/bees.png similarity index 100% rename from oss-internship-2020/guetzli/tests/testdata/bees.png rename to oss-internship-2020/guetzli/testdata/bees.png diff --git a/oss-internship-2020/guetzli/tests/testdata/bees_reference.jpg b/oss-internship-2020/guetzli/testdata/bees_reference.jpg similarity index 100% rename from oss-internship-2020/guetzli/tests/testdata/bees_reference.jpg rename to oss-internship-2020/guetzli/testdata/bees_reference.jpg diff --git a/oss-internship-2020/guetzli/tests/testdata/nature.jpg b/oss-internship-2020/guetzli/testdata/nature.jpg similarity index 100% rename from oss-internship-2020/guetzli/tests/testdata/nature.jpg rename to oss-internship-2020/guetzli/testdata/nature.jpg diff --git a/oss-internship-2020/guetzli/tests/testdata/nature_reference.jpg b/oss-internship-2020/guetzli/testdata/nature_reference.jpg similarity index 100% rename from oss-internship-2020/guetzli/tests/testdata/nature_reference.jpg rename to oss-internship-2020/guetzli/testdata/nature_reference.jpg diff --git a/oss-internship-2020/guetzli/tests/BUILD.bazel b/oss-internship-2020/guetzli/tests/BUILD.bazel deleted file mode 100644 index 36f618d..0000000 --- a/oss-internship-2020/guetzli/tests/BUILD.bazel +++ /dev/null @@ -1,39 +0,0 @@ -# Copyright 2020 Google LLC -# -# 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. - -licenses(["notice"]) - -cc_test( - name = "transaction_tests", - srcs = ["guetzli_transaction_test.cc"], - visibility=["//visibility:public"], - deps = [ - "//:guetzli_sapi", - "@com_google_googletest//:gtest_main", - ], - size = "large", - data = glob(["testdata/*"]), -) - -cc_test( - name = "sapi_lib_tests", - srcs = ["guetzli_sapi_test.cc"], - visibility=["//visibility:public"], - deps = [ - "//:guetzli_sapi", - "@com_google_googletest//:gtest_main", - ], - size = "large", - data = glob(["testdata/*"]), -) From ed0086eb66f49f54fd3a8502fb781554131ff3a9 Mon Sep 17 00:00:00 2001 From: Christian Blichmann Date: Mon, 14 Sep 2020 01:18:09 -0700 Subject: [PATCH 73/82] Fix dynamic binary startup on PPC and newer glibc (> 2.19) This allows the `_llseek` syscall when it is defined. PiperOrigin-RevId: 331498182 Change-Id: I2760b264e3a82000b38d278a9c280501a3dbc724 --- sandboxed_api/sandbox2/policybuilder.cc | 6 +++++- sandboxed_api/sandbox2/stack_trace.cc | 3 +++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/sandboxed_api/sandbox2/policybuilder.cc b/sandboxed_api/sandbox2/policybuilder.cc index c0bfd41..678677d 100644 --- a/sandboxed_api/sandbox2/policybuilder.cc +++ b/sandboxed_api/sandbox2/policybuilder.cc @@ -533,7 +533,11 @@ PolicyBuilder& PolicyBuilder::AllowStaticStartup() { PolicyBuilder& PolicyBuilder::AllowDynamicStartup() { AllowRead(); AllowStat(); - AllowSyscalls({__NR_lseek, __NR_close, __NR_munmap}); + AllowSyscalls({__NR_lseek, +#ifdef __NR__llseek + __NR__llseek, // Newer glibc on PPC +#endif + __NR_close, __NR_munmap}); AddPolicyOnSyscall(__NR_mprotect, { ARG_32(2), JEQ32(PROT_READ, ALLOW), diff --git a/sandboxed_api/sandbox2/stack_trace.cc b/sandboxed_api/sandbox2/stack_trace.cc index 868b89e..ef494b1 100644 --- a/sandboxed_api/sandbox2/stack_trace.cc +++ b/sandboxed_api/sandbox2/stack_trace.cc @@ -86,6 +86,9 @@ std::unique_ptr StackTracePeer::GetPolicy(pid_t target_pid, // libunwind .AllowSyscall(__NR_fstat) .AllowSyscall(__NR_lseek) +#ifdef __NR__llseek + .AllowSyscall(__NR__llseek) // Newer glibc on PPC +#endif .AllowSyscall(__NR_mincore) .AllowSyscall(__NR_mprotect) .AllowSyscall(__NR_munmap) From a68b851c2c353dc00cc87482dc33da6b601984bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Szaw=C5=82owski?= Date: Wed, 16 Sep 2020 06:00:24 -0700 Subject: [PATCH 74/82] Added TypeKind.CHAR_U handling in the generator. PiperOrigin-RevId: 331988119 Change-Id: I8301c5041c32da185202ed34292e6a2988ecff46 --- sandboxed_api/tools/generator2/code.py | 1 + 1 file changed, 1 insertion(+) diff --git a/sandboxed_api/tools/generator2/code.py b/sandboxed_api/tools/generator2/code.py index 84cff80..f483d5e 100644 --- a/sandboxed_api/tools/generator2/code.py +++ b/sandboxed_api/tools/generator2/code.py @@ -84,6 +84,7 @@ def _stringify_tokens(tokens, separator='\n'): TYPE_MAPPING = { cindex.TypeKind.VOID: '::sapi::v::Void', cindex.TypeKind.CHAR_S: '::sapi::v::Char', + cindex.TypeKind.CHAR_U: '::sapi::v::UChar', cindex.TypeKind.INT: '::sapi::v::Int', cindex.TypeKind.UINT: '::sapi::v::UInt', cindex.TypeKind.LONG: '::sapi::v::Long', From 13c28403a64751c9d3aa905d5151cf6af53758f6 Mon Sep 17 00:00:00 2001 From: Christian Blichmann Date: Thu, 17 Sep 2020 03:07:47 -0700 Subject: [PATCH 75/82] Implement system include detection for CMake build The Bazel build already queries the current toolchain for its system include directories. This change brings feature parity and is necessary for systems with unusual include locations. PiperOrigin-RevId: 332195812 Change-Id: Ie81d614d21e90b4bd9edf2084ef80bf0d85dd750 --- cmake/SapiBuildDefs.cmake | 10 ++++++++-- sandboxed_api/bazel/sapi.bzl | 5 ++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/cmake/SapiBuildDefs.cmake b/cmake/SapiBuildDefs.cmake index 5e75e59..62583ec 100644 --- a/cmake/SapiBuildDefs.cmake +++ b/cmake/SapiBuildDefs.cmake @@ -132,7 +132,6 @@ function(add_sapi_library) set(_sapi_embed_dir "${CMAKE_CURRENT_BINARY_DIR}") set(_sapi_embed_name "${_sapi_NAME}") endif() - # TODO(cblichmann): Implement sapi_isystem if(SAPI_ENABLE_GENERATOR) add_custom_command( OUTPUT "${_sapi_gen_header}" @@ -149,9 +148,14 @@ function(add_sapi_library) VERBATIM ) else() + set(_sapi_isystem "${_sapi_NAME}.isystem") list_join(_sapi_full_inputs "," _sapi_full_inputs) add_custom_command( - OUTPUT "${_sapi_gen_header}" + OUTPUT "${_sapi_gen_header}" "${_sapi_isystem}" + COMMAND sh -c + "${CMAKE_CXX_COMPILER} -E -x c++ -v /dev/null 2>&1 | \ + awk '/> search starts here:/{f=1;next}/^End of search/{f=0}f{print $1}' \ + > \"${_sapi_isystem}\"" COMMAND "${SAPI_PYTHON3_EXECUTABLE}" -B "${SAPI_SOURCE_DIR}/sandboxed_api/tools/generator2/sapi_generator.py" "--sapi_name=${_sapi_LIBRARY_NAME}" @@ -160,8 +164,10 @@ function(add_sapi_library) "--sapi_embed_name=${_sapi_embed_name}" "--sapi_functions=${_sapi_funcs}" "--sapi_ns=${_sapi_NAMESPACE}" + "--sapi_isystem=${_sapi_isystem}" "--sapi_in=${_sapi_full_inputs}" COMMENT "Generating interface" + VERBATIM ) endif() diff --git a/sandboxed_api/bazel/sapi.bzl b/sandboxed_api/bazel/sapi.bzl index 11f26cc..211d5a6 100644 --- a/sandboxed_api/bazel/sapi.bzl +++ b/sandboxed_api/bazel/sapi.bzl @@ -254,9 +254,8 @@ def sapi_library( native.genrule( name = name + ".isystem", outs = [name + ".isystem.list"], - cmd = """echo | - $(CC) -E -x c++ - -v 2>&1 | - awk '/> search starts here:/{flag=1;next}/End of search/{flag=0}flag' > $@ + cmd = """$(CC) -E -x c++ -v /dev/null 2>&1 | + awk '/> search starts here:/{f=1;next}/^End of search/{f=0}f{print $$1}' > $@ """, toolchains = ["@bazel_tools//tools/cpp:current_cc_toolchain"], ) From 45c208b19a0ebf1ceca2b07f301129200544440d Mon Sep 17 00:00:00 2001 From: Christian Blichmann Date: Fri, 18 Sep 2020 02:22:19 -0700 Subject: [PATCH 76/82] Update `.clang-format` to prefer `&` and `*` to be close to the type PiperOrigin-RevId: 332412318 Change-Id: I4cd6d295ef68292c9412722829729142578c4c9f --- .clang-format | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.clang-format b/.clang-format index 06ea346..dd860a0 100644 --- a/.clang-format +++ b/.clang-format @@ -1,4 +1,6 @@ --- -Language: Cpp -BasedOnStyle: Google +Language: Cpp +BasedOnStyle: Google +DerivePointerAlignment: false +PointerAlignment: Left ... From 9ffa5afba6f65a86172169abdfe3288e0cad29a9 Mon Sep 17 00:00:00 2001 From: Wiktor Garbacz Date: Fri, 18 Sep 2020 02:22:54 -0700 Subject: [PATCH 77/82] Remove unnecessary TLV struct Drive-by: Zero-copy RecvString PiperOrigin-RevId: 332412385 Change-Id: I169ffa78f016ec2d55c1a3677ea97beed095123c --- sandboxed_api/sandbox2/comms.cc | 50 ++++++++++++++++----------------- sandboxed_api/sandbox2/comms.h | 14 ++++----- 2 files changed, 30 insertions(+), 34 deletions(-) diff --git a/sandboxed_api/sandbox2/comms.cc b/sandboxed_api/sandbox2/comms.cc index a4845c8..5195a9d 100644 --- a/sandboxed_api/sandbox2/comms.cc +++ b/sandboxed_api/sandbox2/comms.cc @@ -265,17 +265,16 @@ bool Comms::SendTLV(uint32_t tag, uint64_t length, const uint8_t* bytes) { } bool Comms::RecvString(std::string* v) { - TLV tlv; - if (!RecvTLV(&tlv)) { + uint32_t tag; + if (!RecvTLV(&tag, v)) { return false; } - if (tlv.tag != kTagString) { + if (tag != kTagString) { SAPI_RAW_LOG(ERROR, "Expected (kTagString == 0x%x), got: 0x%x", kTagString, - tlv.tag); + tag); return false; } - v->assign(reinterpret_cast(tlv.value.data()), tlv.value.size()); return true; } @@ -285,16 +284,16 @@ bool Comms::SendString(const std::string& v) { } bool Comms::RecvBytes(std::vector* buffer) { - TLV tlv; - if (!RecvTLV(&tlv)) { + uint32_t tag; + if (!RecvTLV(&tag, buffer)) { return false; } - if (tlv.tag != kTagBytes) { + if (tag != kTagBytes) { + buffer->clear(); SAPI_RAW_LOG(ERROR, "Expected (kTagBytes == 0x%x), got: 0x%u", kTagBytes, - tlv.tag); + tag); return false; } - buffer->swap(tlv.value); return true; } @@ -462,8 +461,9 @@ bool Comms::SendFD(int fd) { } bool Comms::RecvProtoBuf(google::protobuf::Message* message) { - TLV tlv; - if (!RecvTLV(&tlv)) { + uint32_t tag; + std::vector bytes; + if (!RecvTLV(&tag, &bytes)) { if (IsConnected()) { SAPI_RAW_PLOG(ERROR, "RecvProtoBuf failed for (%s)", socket_name_); } else { @@ -473,11 +473,11 @@ bool Comms::RecvProtoBuf(google::protobuf::Message* message) { return false; } - if (tlv.tag != kTagProto2) { - SAPI_RAW_LOG(ERROR, "Expected tag: 0x%x, got: 0x%u", kTagProto2, tlv.tag); + if (tag != kTagProto2) { + SAPI_RAW_LOG(ERROR, "Expected tag: 0x%x, got: 0x%u", kTagProto2, tag); return false; } - return message->ParseFromArray(tlv.value.data(), tlv.value.size()); + return message->ParseFromArray(bytes.data(), bytes.size()); } bool Comms::SendProtoBuf(const google::protobuf::Message& message) { @@ -599,18 +599,16 @@ bool Comms::RecvTL(uint32_t* tag, uint64_t* length) { return true; } -bool Comms::RecvTLV(TLV* tlv) { - absl::MutexLock lock(&tlv_recv_transmission_mutex_); - uint64_t length; - if (!RecvTL(&tlv->tag, &length)) { - return false; - } - - tlv->value.resize(length); - return length == 0 || Recv(tlv->value.data(), length); +bool Comms::RecvTLV(uint32_t* tag, std::vector* value) { + return RecvTLVGeneric(tag, value); } -bool Comms::RecvTLV(uint32_t* tag, std::vector* value) { +bool Comms::RecvTLV(uint32_t* tag, std::string* value) { + return RecvTLVGeneric(tag, value); +} + +template +bool Comms::RecvTLVGeneric(uint32_t* tag, T* value) { absl::MutexLock lock(&tlv_recv_transmission_mutex_); uint64_t length; if (!RecvTL(tag, &length)) { @@ -618,7 +616,7 @@ bool Comms::RecvTLV(uint32_t* tag, std::vector* value) { } value->resize(length); - return length == 0 || Recv(value->data(), length); + return length == 0 || Recv(reinterpret_cast(value->data()), length); } bool Comms::RecvTLV(uint32_t* tag, uint64_t* length, void* buffer, diff --git a/sandboxed_api/sandbox2/comms.h b/sandboxed_api/sandbox2/comms.h index c4ab84e..7863288 100644 --- a/sandboxed_api/sandbox2/comms.h +++ b/sandboxed_api/sandbox2/comms.h @@ -109,6 +109,9 @@ class Comms { // Receive a TLV structure, the memory for the value will be allocated // by std::vector. bool RecvTLV(uint32_t* tag, std::vector* value); + // Receive a TLV structure, the memory for the value will be allocated + // by std::string. + bool RecvTLV(uint32_t* tag, std::string* value); // Receives a TLV value into a specified buffer without allocating memory. bool RecvTLV(uint32_t* tag, uint64_t* length, void* buffer, uint64_t buffer_size); @@ -174,12 +177,6 @@ class Comms { // State of the channel (enum), socket will have to be connected later on. State state_ = State::kUnconnected; - // TLV structure used to pass messages around. - struct TLV { - uint32_t tag; - std::vector value; - }; - // Special struct for passing credentials or FDs. Different from the one above // as it inlines the value. This is important as the data is transmitted using // sendmsg/recvmsg instead of send/recv. @@ -201,8 +198,9 @@ class Comms { bool RecvTL(uint32_t* tag, uint64_t* length) ABSL_EXCLUSIVE_LOCKS_REQUIRED(tlv_recv_transmission_mutex_); - // Receives whole TLV structure, allocates memory for the data. - bool RecvTLV(TLV* tlv); + // T has to be a ContiguousContainer + template + bool RecvTLVGeneric(uint32_t* tag, T* value); // Receives arbitrary integers. bool RecvInt(void* buffer, uint64_t len, uint32_t tag); From 08a956a415d6c90d1349c416693c2f79ab521a0c Mon Sep 17 00:00:00 2001 From: Wiktor Garbacz Date: Fri, 18 Sep 2020 06:48:30 -0700 Subject: [PATCH 78/82] Use opaque void* instead of uint8_t* in Comms PiperOrigin-RevId: 332441641 Change-Id: I09902e98726a0bd57b47d3454ddcb6ef05021d56 --- sandboxed_api/rpcchannel.cc | 33 +++++++++++---------------------- sandboxed_api/sandbox2/comms.cc | 17 +++++++++-------- sandboxed_api/sandbox2/comms.h | 8 ++++---- 3 files changed, 24 insertions(+), 34 deletions(-) diff --git a/sandboxed_api/rpcchannel.cc b/sandboxed_api/rpcchannel.cc index 66e1b2b..8b19547 100644 --- a/sandboxed_api/rpcchannel.cc +++ b/sandboxed_api/rpcchannel.cc @@ -27,8 +27,7 @@ namespace sapi { absl::Status RPCChannel::Call(const FuncCall& call, uint32_t tag, FuncRet* ret, v::Type exp_type) { absl::MutexLock lock(&mutex_); - if (!comms_->SendTLV(tag, sizeof(call), - reinterpret_cast(&call))) { + if (!comms_->SendTLV(tag, sizeof(call), &call)) { return absl::UnavailableError("Sending TLV value failed"); } SAPI_ASSIGN_OR_RETURN(auto fret, Return(exp_type)); @@ -67,9 +66,7 @@ absl::StatusOr RPCChannel::Return(v::Type exp_type) { absl::Status RPCChannel::Allocate(size_t size, void** addr) { absl::MutexLock lock(&mutex_); - uint64_t sz = size; - if (!comms_->SendTLV(comms::kMsgAllocate, sizeof(sz), - reinterpret_cast(&sz))) { + if (!comms_->SendTLV(comms::kMsgAllocate, sizeof(size), &size)) { return absl::UnavailableError("Sending TLV value failed"); } @@ -86,7 +83,7 @@ absl::Status RPCChannel::Reallocate(void* old_addr, size_t size, req.size = size; if (!comms_->SendTLV(comms::kMsgReallocate, sizeof(comms::ReallocRequest), - reinterpret_cast(&req))) { + &req)) { return absl::UnavailableError("Sending TLV value failed"); } @@ -104,8 +101,7 @@ absl::Status RPCChannel::Reallocate(void* old_addr, size_t size, absl::Status RPCChannel::Free(void* addr) { absl::MutexLock lock(&mutex_); uint64_t remote = reinterpret_cast(addr); - if (!comms_->SendTLV(comms::kMsgFree, sizeof(remote), - reinterpret_cast(&remote))) { + if (!comms_->SendTLV(comms::kMsgFree, sizeof(remote), &remote)) { return absl::UnavailableError("Sending TLV value failed"); } @@ -118,8 +114,7 @@ absl::Status RPCChannel::Free(void* addr) { absl::Status RPCChannel::Symbol(const char* symname, void** addr) { absl::MutexLock lock(&mutex_); - if (!comms_->SendTLV(comms::kMsgSymbol, strlen(symname) + 1, - reinterpret_cast(symname))) { + if (!comms_->SendTLV(comms::kMsgSymbol, strlen(symname) + 1, symname)) { return absl::UnavailableError("Sending TLV value failed"); } @@ -137,9 +132,8 @@ absl::Status RPCChannel::Exit() { // Try the RPC exit sequence. But, the only thing that matters as a success // indicator is whether the Comms channel had been closed - bool unused = true; - comms_->SendTLV(comms::kMsgExit, sizeof(unused), - reinterpret_cast(&unused)); + comms_->SendTLV(comms::kMsgExit, 0, nullptr); + bool unused; comms_->RecvBool(&unused); if (!comms_->IsTerminated()) { @@ -154,9 +148,7 @@ absl::Status RPCChannel::Exit() { absl::Status RPCChannel::SendFD(int local_fd, int* remote_fd) { absl::MutexLock lock(&mutex_); - bool unused = true; - if (!comms_->SendTLV(comms::kMsgSendFd, sizeof(unused), - reinterpret_cast(&unused))) { + if (!comms_->SendTLV(comms::kMsgSendFd, 0, nullptr)) { return absl::UnavailableError("Sending TLV value failed"); } if (!comms_->SendFD(local_fd)) { @@ -173,8 +165,7 @@ absl::Status RPCChannel::SendFD(int local_fd, int* remote_fd) { absl::Status RPCChannel::RecvFD(int remote_fd, int* local_fd) { absl::MutexLock lock(&mutex_); - if (!comms_->SendTLV(comms::kMsgRecvFd, sizeof(remote_fd), - reinterpret_cast(&remote_fd))) { + if (!comms_->SendTLV(comms::kMsgRecvFd, sizeof(remote_fd), &remote_fd)) { return absl::UnavailableError("Sending TLV value failed"); } @@ -191,8 +182,7 @@ absl::Status RPCChannel::RecvFD(int remote_fd, int* local_fd) { absl::Status RPCChannel::Close(int remote_fd) { absl::MutexLock lock(&mutex_); - if (!comms_->SendTLV(comms::kMsgClose, sizeof(remote_fd), - reinterpret_cast(&remote_fd))) { + if (!comms_->SendTLV(comms::kMsgClose, sizeof(remote_fd), &remote_fd)) { return absl::UnavailableError("Sending TLV value failed"); } @@ -205,8 +195,7 @@ absl::Status RPCChannel::Close(int remote_fd) { absl::StatusOr RPCChannel::Strlen(void* str) { absl::MutexLock lock(&mutex_); - if (!comms_->SendTLV(comms::kMsgStrlen, sizeof(str), - reinterpret_cast(&str))) { + if (!comms_->SendTLV(comms::kMsgStrlen, sizeof(str), &str)) { return absl::UnavailableError("Sending TLV value failed"); } diff --git a/sandboxed_api/sandbox2/comms.cc b/sandboxed_api/sandbox2/comms.cc index 5195a9d..9b2f719 100644 --- a/sandboxed_api/sandbox2/comms.cc +++ b/sandboxed_api/sandbox2/comms.cc @@ -228,7 +228,7 @@ void Comms::Terminate() { } } -bool Comms::SendTLV(uint32_t tag, uint64_t length, const uint8_t* bytes) { +bool Comms::SendTLV(uint32_t tag, uint64_t length, const void* value) { if (length > GetMaxMsgSize()) { SAPI_RAW_LOG(ERROR, "Maximum TLV message size exceeded: (%u > %u)", length, GetMaxMsgSize()); @@ -249,14 +249,14 @@ bool Comms::SendTLV(uint32_t tag, uint64_t length, const uint8_t* bytes) { length); { absl::MutexLock lock(&tlv_send_transmission_mutex_); - if (!Send(reinterpret_cast(&tag), sizeof(tag))) { + if (!Send(&tag, sizeof(tag))) { return false; } - if (!Send(reinterpret_cast(&length), sizeof(length))) { + if (!Send(&length, sizeof(length))) { return false; } if (length > 0) { - if (!Send(bytes, length)) { + if (!Send(value, length)) { return false; } } @@ -279,8 +279,7 @@ bool Comms::RecvString(std::string* v) { } bool Comms::SendString(const std::string& v) { - return SendTLV(kTagString, v.length(), - reinterpret_cast(v.c_str())); + return SendTLV(kTagString, v.length(), v.c_str()); } bool Comms::RecvBytes(std::vector* buffer) { @@ -513,8 +512,9 @@ socklen_t Comms::CreateSockaddrUn(sockaddr_un* sun) { return slen; } -bool Comms::Send(const uint8_t* bytes, uint64_t len) { +bool Comms::Send(const void* data, uint64_t len) { uint64_t total_sent = 0; + const char* bytes = reinterpret_cast(data); const auto op = [bytes, len, &total_sent](int fd) -> ssize_t { PotentiallyBlockingRegion region; return TEMP_FAILURE_RETRY(write(fd, &bytes[total_sent], len - total_sent)); @@ -545,8 +545,9 @@ bool Comms::Send(const uint8_t* bytes, uint64_t len) { return true; } -bool Comms::Recv(uint8_t* bytes, uint64_t len) { +bool Comms::Recv(void* data, uint64_t len) { uint64_t total_recv = 0; + char* bytes = reinterpret_cast(data); const auto op = [bytes, len, &total_recv](int fd) -> ssize_t { PotentiallyBlockingRegion region; return TEMP_FAILURE_RETRY(read(fd, &bytes[total_recv], len - total_recv)); diff --git a/sandboxed_api/sandbox2/comms.h b/sandboxed_api/sandbox2/comms.h index 7863288..530943e 100644 --- a/sandboxed_api/sandbox2/comms.h +++ b/sandboxed_api/sandbox2/comms.h @@ -105,7 +105,7 @@ class Comms { // avoid protobuf serialization issues. uint64_t GetMaxMsgSize() const { return std::numeric_limits::max(); } - bool SendTLV(uint32_t tag, uint64_t length, const uint8_t* bytes); + bool SendTLV(uint32_t tag, uint64_t length, const void* value); // Receive a TLV structure, the memory for the value will be allocated // by std::vector. bool RecvTLV(uint32_t* tag, std::vector* value); @@ -190,8 +190,8 @@ class Comms { socklen_t CreateSockaddrUn(sockaddr_un* sun); // Support for EINTR and size completion. - bool Send(const uint8_t* bytes, uint64_t len); - bool Recv(uint8_t* bytes, uint64_t len); + bool Send(const void* data, uint64_t len); + bool Recv(void* data, uint64_t len); // Receives tag and length. Assumes that the `tlv_transmission_mutex_` mutex // is locked. @@ -212,7 +212,7 @@ class Comms { template bool SendGeneric(T value, uint32_t tag) { - return SendTLV(tag, sizeof(T), reinterpret_cast(&value)); + return SendTLV(tag, sizeof(T), &value); } }; From c33f1fb03ec563caed708bfd8ae85e0282542936 Mon Sep 17 00:00:00 2001 From: Wiktor Garbacz Date: Fri, 18 Sep 2020 07:22:51 -0700 Subject: [PATCH 79/82] Simplify casts Drive-by: check for malloc failure in sapi::v::Array PiperOrigin-RevId: 332446225 Change-Id: I375ea94845e04dffc3353d70737402daa66ae50a --- sandboxed_api/client.cc | 6 +++--- sandboxed_api/var_array.h | 25 ++++++++++++------------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/sandboxed_api/client.cc b/sandboxed_api/client.cc index df2b24d..738fc87 100644 --- a/sandboxed_api/client.cc +++ b/sandboxed_api/client.cc @@ -254,8 +254,8 @@ void HandleReallocMsg(uintptr_t ptr, uintptr_t size, FuncRet* ret) { __sanitizer_get_allocated_size(reinterpret_cast(ptr)); #endif ret->ret_type = v::Type::kPointer; - ret->int_val = reinterpret_cast( - realloc(const_cast(reinterpret_cast(ptr)), size)); + ret->int_val = + reinterpret_cast(realloc(reinterpret_cast(ptr), size)); ret->success = true; #ifdef MEMORY_SANITIZER // Memory is copied to the pointer using an API that the memory sanitizer @@ -273,7 +273,7 @@ void HandleReallocMsg(uintptr_t ptr, uintptr_t size, FuncRet* ret) { void HandleFreeMsg(uintptr_t ptr, FuncRet* ret) { VLOG(1) << "HandleFreeMsg: free(0x" << absl::StrCat(absl::Hex(ptr)) << ")"; - free(const_cast(reinterpret_cast(ptr))); + free(reinterpret_cast(ptr)); ret->ret_type = v::Type::kVoid; ret->success = true; ret->int_val = 0ULL; diff --git a/sandboxed_api/var_array.h b/sandboxed_api/var_array.h index 60968ba..fcf2da9 100644 --- a/sandboxed_api/var_array.h +++ b/sandboxed_api/var_array.h @@ -15,9 +15,11 @@ #ifndef SANDBOXED_API_VAR_ARRAY_H_ #define SANDBOXED_API_VAR_ARRAY_H_ +#include #include #include +#include #include "absl/base/macros.h" #include "absl/status/status.h" #include "absl/strings/str_cat.h" @@ -39,19 +41,19 @@ class Array : public Var, public Pointable { nelem_(nelem), total_size_(nelem_ * sizeof(T)), buffer_owned_(false) { - SetLocal(const_cast(reinterpret_cast(arr_))); + SetLocal(const_cast*>(arr_)); } // The array is allocated and owned by this object. explicit Array(size_t nelem) - : arr_(static_cast(malloc(sizeof(T) * nelem))), - nelem_(nelem), - total_size_(nelem_ * sizeof(T)), - buffer_owned_(true) { - SetLocal(const_cast(reinterpret_cast(arr_))); + : nelem_(nelem), total_size_(nelem_ * sizeof(T)), buffer_owned_(true) { + void* storage = malloc(sizeof(T) * nelem); + CHECK(storage != nullptr); + SetLocal(storage); + arr_ = static_cast(storage); } virtual ~Array() { if (buffer_owned_) { - free(const_cast(reinterpret_cast(arr_))); + free(const_cast*>(arr_)); } } @@ -121,7 +123,7 @@ class Array : public Var, public Pointable { arr_ = static_cast(new_addr); total_size_ = size; nelem_ = size / sizeof(T); - SetLocal(arr_); + SetLocal(new_addr); return absl::OkStatus(); } @@ -142,7 +144,7 @@ class Array : public Var, public Pointable { class CStr : public Array { public: explicit CStr(char* cstr) : Array(strlen(cstr) + 1) { - strcpy(this->GetData(), cstr); // NOLINT + std::copy(cstr, cstr + GetNElem(), GetData()); } std::string ToString() const final { @@ -159,10 +161,7 @@ class ConstCStr : public Array { : Array(cstr, strlen(cstr) + 1) {} std::string ToString() const final { - if (GetData() == nullptr) { - return "CStr: [nullptr]"; - } - return absl::StrCat("CStr: len(w/o NUL):", strlen(GetData()), ", ['", + return absl::StrCat("ConstCStr: len(w/o NUL):", strlen(GetData()), ", ['", GetData(), "']"); } }; From f91f843f50958d7a18821be5111e9ca60dbf2092 Mon Sep 17 00:00:00 2001 From: Wiktor Garbacz Date: Fri, 18 Sep 2020 07:44:34 -0700 Subject: [PATCH 80/82] Use `size_t`/`uintptr_t` instead of `uintptr_t` or `uint64_t` where appropriate PiperOrigin-RevId: 332449107 Change-Id: I623c320c7f31bb73b92799dfbeb9a1e8ce0cdb3b --- sandboxed_api/call.h | 4 +- sandboxed_api/client.cc | 14 +++---- .../examples/stringop/main_stringop.cc | 4 +- sandboxed_api/rpcchannel.cc | 13 ++++--- sandboxed_api/rpcchannel.h | 2 +- sandboxed_api/sandbox.cc | 2 +- sandboxed_api/sandbox.h | 4 +- sandboxed_api/sandbox2/comms.cc | 37 ++++++++++--------- sandboxed_api/sandbox2/comms.h | 19 +++++----- 9 files changed, 49 insertions(+), 50 deletions(-) diff --git a/sandboxed_api/call.h b/sandboxed_api/call.h index a33306a..06def7e 100644 --- a/sandboxed_api/call.h +++ b/sandboxed_api/call.h @@ -23,8 +23,8 @@ namespace sapi { namespace comms { struct ReallocRequest { - uint64_t old_addr; - uint64_t size; + uintptr_t old_addr; + size_t size; }; // Types of TAGs used with Comms channel. diff --git a/sandboxed_api/client.cc b/sandboxed_api/client.cc index 738fc87..9d490b6 100644 --- a/sandboxed_api/client.cc +++ b/sandboxed_api/client.cc @@ -229,7 +229,7 @@ void HandleCallMsg(const FuncCall& call, FuncRet* ret) { } // Handles requests to allocate memory inside the sandboxee. -void HandleAllocMsg(const uintptr_t size, FuncRet* ret) { +void HandleAllocMsg(const size_t size, FuncRet* ret) { VLOG(1) << "HandleAllocMsg: size=" << size; ret->ret_type = v::Type::kPointer; @@ -237,16 +237,15 @@ void HandleAllocMsg(const uintptr_t size, FuncRet* ret) { // Memory is copied to the pointer using an API that the memory sanitizer // is blind to (process_vm_writev). Initialize the memory here so that // the sandboxed code can still be tested with the memory sanitizer. - ret->int_val = - reinterpret_cast(calloc(1, static_cast(size))); + ret->int_val = reinterpret_cast(calloc(1, size)); #else - ret->int_val = reinterpret_cast(malloc(static_cast(size))); + ret->int_val = reinterpret_cast(malloc(size)); #endif ret->success = true; } // Like HandleAllocMsg(), but handles requests to reallocate memory. -void HandleReallocMsg(uintptr_t ptr, uintptr_t size, FuncRet* ret) { +void HandleReallocMsg(uintptr_t ptr, size_t size, FuncRet* ret) { VLOG(1) << "HandleReallocMsg(" << absl::StrCat(absl::Hex(ptr)) << ", " << size << ")"; #ifdef MEMORY_SANITIZER @@ -363,14 +362,13 @@ void ServeRequest(sandbox2::Comms* comms) { break; case comms::kMsgAllocate: VLOG(1) << "Client::kMsgAllocate"; - HandleAllocMsg(BytesAs(bytes), &ret); + HandleAllocMsg(BytesAs(bytes), &ret); break; case comms::kMsgReallocate: VLOG(1) << "Client::kMsgReallocate"; { auto req = BytesAs(bytes); - HandleReallocMsg(static_cast(req.old_addr), - static_cast(req.size), &ret); + HandleReallocMsg(req.old_addr, req.size, &ret); } break; case comms::kMsgFree: diff --git a/sandboxed_api/examples/stringop/main_stringop.cc b/sandboxed_api/examples/stringop/main_stringop.cc index 9880699..c7e5b2e 100644 --- a/sandboxed_api/examples/stringop/main_stringop.cc +++ b/sandboxed_api/examples/stringop/main_stringop.cc @@ -134,7 +134,7 @@ TEST(StringopTest, RawStringLength) { ASSERT_THAT(sandbox.Init(), IsOk()); StringopApi api(&sandbox); SAPI_ASSERT_OK_AND_ASSIGN(void* target_mem_ptr, api.get_raw_c_string()); - SAPI_ASSERT_OK_AND_ASSIGN(uint64_t len, + SAPI_ASSERT_OK_AND_ASSIGN(size_t len, sandbox.rpc_channel()->Strlen(target_mem_ptr)); EXPECT_THAT(len, Eq(10)); } @@ -144,7 +144,7 @@ TEST(StringopTest, RawStringReading) { ASSERT_THAT(sandbox.Init(), IsOk()); StringopApi api(&sandbox); SAPI_ASSERT_OK_AND_ASSIGN(void* target_mem_ptr, api.get_raw_c_string()); - SAPI_ASSERT_OK_AND_ASSIGN(uint64_t len, + SAPI_ASSERT_OK_AND_ASSIGN(size_t len, sandbox.rpc_channel()->Strlen(target_mem_ptr)); EXPECT_THAT(len, Eq(10)); diff --git a/sandboxed_api/rpcchannel.cc b/sandboxed_api/rpcchannel.cc index 8b19547..72a057e 100644 --- a/sandboxed_api/rpcchannel.cc +++ b/sandboxed_api/rpcchannel.cc @@ -37,7 +37,7 @@ absl::Status RPCChannel::Call(const FuncCall& call, uint32_t tag, FuncRet* ret, absl::StatusOr RPCChannel::Return(v::Type exp_type) { uint32_t tag; - uint64_t len; + size_t len; FuncRet ret; if (!comms_->RecvTLV(&tag, &len, &ret, sizeof(ret))) { return absl::UnavailableError("Receiving TLV value failed"); @@ -78,9 +78,10 @@ absl::Status RPCChannel::Allocate(size_t size, void** addr) { absl::Status RPCChannel::Reallocate(void* old_addr, size_t size, void** new_addr) { absl::MutexLock lock(&mutex_); - comms::ReallocRequest req; - req.old_addr = reinterpret_cast(old_addr); - req.size = size; + comms::ReallocRequest req = { + .old_addr = reinterpret_cast(old_addr), + .size = size, + }; if (!comms_->SendTLV(comms::kMsgReallocate, sizeof(comms::ReallocRequest), &req)) { @@ -100,7 +101,7 @@ absl::Status RPCChannel::Reallocate(void* old_addr, size_t size, absl::Status RPCChannel::Free(void* addr) { absl::MutexLock lock(&mutex_); - uint64_t remote = reinterpret_cast(addr); + uintptr_t remote = reinterpret_cast(addr); if (!comms_->SendTLV(comms::kMsgFree, sizeof(remote), &remote)) { return absl::UnavailableError("Sending TLV value failed"); } @@ -193,7 +194,7 @@ absl::Status RPCChannel::Close(int remote_fd) { return absl::OkStatus(); } -absl::StatusOr RPCChannel::Strlen(void* str) { +absl::StatusOr RPCChannel::Strlen(void* str) { absl::MutexLock lock(&mutex_); if (!comms_->SendTLV(comms::kMsgStrlen, sizeof(str), &str)) { return absl::UnavailableError("Sending TLV value failed"); diff --git a/sandboxed_api/rpcchannel.h b/sandboxed_api/rpcchannel.h index 4fec23b..56d7ed8 100644 --- a/sandboxed_api/rpcchannel.h +++ b/sandboxed_api/rpcchannel.h @@ -61,7 +61,7 @@ class RPCChannel { absl::Status Close(int remote_fd); // Returns length of a null-terminated c-style string (invokes strlen). - absl::StatusOr Strlen(void* str); + absl::StatusOr Strlen(void* str); sandbox2::Comms* comms() const { return comms_; } diff --git a/sandboxed_api/sandbox.cc b/sandboxed_api/sandbox.cc index 8c9da3d..d6b1bf2 100644 --- a/sandboxed_api/sandbox.cc +++ b/sandboxed_api/sandbox.cc @@ -394,7 +394,7 @@ absl::Status Sandbox::TransferFromSandboxee(v::Var* var) { } absl::StatusOr Sandbox::GetCString(const v::RemotePtr& str, - uint64_t max_length) { + size_t max_length) { if (!is_active()) { return absl::UnavailableError("Sandbox not active"); } diff --git a/sandboxed_api/sandbox.h b/sandboxed_api/sandbox.h index 5d69eb0..576a144 100644 --- a/sandboxed_api/sandbox.h +++ b/sandboxed_api/sandbox.h @@ -103,8 +103,8 @@ class Sandbox { absl::Status TransferFromSandboxee(v::Var* var); absl::StatusOr GetCString(const v::RemotePtr& str, - uint64_t max_length = 10ULL - << 20 /* 10 MiB*/ + size_t max_length = 10ULL + << 20 /* 10 MiB*/ ); // Waits until the sandbox terminated and returns the result. diff --git a/sandboxed_api/sandbox2/comms.cc b/sandboxed_api/sandbox2/comms.cc index 9b2f719..449db8f 100644 --- a/sandboxed_api/sandbox2/comms.cc +++ b/sandboxed_api/sandbox2/comms.cc @@ -228,7 +228,7 @@ void Comms::Terminate() { } } -bool Comms::SendTLV(uint32_t tag, uint64_t length, const void* value) { +bool Comms::SendTLV(uint32_t tag, size_t length, const void* value) { if (length > GetMaxMsgSize()) { SAPI_RAW_LOG(ERROR, "Maximum TLV message size exceeded: (%u > %u)", length, GetMaxMsgSize()); @@ -296,7 +296,7 @@ bool Comms::RecvBytes(std::vector* buffer) { return true; } -bool Comms::SendBytes(const uint8_t* v, uint64_t len) { +bool Comms::SendBytes(const uint8_t* v, size_t len) { return SendTLV(kTagBytes, len, v); } @@ -417,7 +417,7 @@ bool Comms::SendFD(int fd) { int* fds = reinterpret_cast(CMSG_DATA(cmsg)); fds[0] = fd; - InternalTLV tlv = {kTagFd, sizeof(tlv.val), 0}; + InternalTLV tlv = {kTagFd, 0}; iovec iov; iov.iov_base = &tlv; @@ -512,8 +512,8 @@ socklen_t Comms::CreateSockaddrUn(sockaddr_un* sun) { return slen; } -bool Comms::Send(const void* data, uint64_t len) { - uint64_t total_sent = 0; +bool Comms::Send(const void* data, size_t len) { + size_t total_sent = 0; const char* bytes = reinterpret_cast(data); const auto op = [bytes, len, &total_sent](int fd) -> ssize_t { PotentiallyBlockingRegion region; @@ -545,8 +545,8 @@ bool Comms::Send(const void* data, uint64_t len) { return true; } -bool Comms::Recv(void* data, uint64_t len) { - uint64_t total_recv = 0; +bool Comms::Recv(void* data, size_t len) { + size_t total_recv = 0; char* bytes = reinterpret_cast(data); const auto op = [bytes, len, &total_recv](int fd) -> ssize_t { PotentiallyBlockingRegion region; @@ -574,7 +574,7 @@ bool Comms::Recv(void* data, uint64_t len) { } // Internal helper method (low level). -bool Comms::RecvTL(uint32_t* tag, uint64_t* length) { +bool Comms::RecvTL(uint32_t* tag, size_t* length) { if (!Recv(reinterpret_cast(tag), sizeof(*tag))) { return false; } @@ -611,7 +611,7 @@ bool Comms::RecvTLV(uint32_t* tag, std::string* value) { template bool Comms::RecvTLVGeneric(uint32_t* tag, T* value) { absl::MutexLock lock(&tlv_recv_transmission_mutex_); - uint64_t length; + size_t length; if (!RecvTL(tag, &length)) { return false; } @@ -620,28 +620,29 @@ bool Comms::RecvTLVGeneric(uint32_t* tag, T* value) { return length == 0 || Recv(reinterpret_cast(value->data()), length); } -bool Comms::RecvTLV(uint32_t* tag, uint64_t* length, void* buffer, - uint64_t buffer_size) { +bool Comms::RecvTLV(uint32_t* tag, size_t* length, void* buffer, + size_t buffer_size) { absl::MutexLock lock(&tlv_recv_transmission_mutex_); if (!RecvTL(tag, length)) { return false; } + if (*length == 0) { + return true; + } + if (*length > buffer_size) { SAPI_RAW_LOG(ERROR, "Buffer size too small (0x%x > 0x%x)", *length, buffer_size); return false; - } else if (*length > 0) { - if (!Recv(reinterpret_cast(buffer), *length)) { - return false; - } } - return true; + + return Recv(reinterpret_cast(buffer), *length); } -bool Comms::RecvInt(void* buffer, uint64_t len, uint32_t tag) { +bool Comms::RecvInt(void* buffer, size_t len, uint32_t tag) { uint32_t received_tag; - uint64_t received_length; + size_t received_length; if (!RecvTLV(&received_tag, &received_length, buffer, len)) { return false; } diff --git a/sandboxed_api/sandbox2/comms.h b/sandboxed_api/sandbox2/comms.h index 530943e..fca8c53 100644 --- a/sandboxed_api/sandbox2/comms.h +++ b/sandboxed_api/sandbox2/comms.h @@ -60,7 +60,7 @@ class Comms { static constexpr uint32_t kTagFd = 0X80000201; // Any payload size above this limit will LOG(WARNING). - static constexpr uint64_t kWarnMsgSize = (256ULL << 20); + static constexpr size_t kWarnMsgSize = (256ULL << 20); // Sandbox2-specific convention where FD=1023 is always passed to the // sandboxed process as a communication channel (encapsulated in the @@ -103,9 +103,9 @@ class Comms { // Note: The actual size is "unlimited", although the Buffer API is more // efficient for large transfers. There is an arbitrary limit to ~2GiB to // avoid protobuf serialization issues. - uint64_t GetMaxMsgSize() const { return std::numeric_limits::max(); } + size_t GetMaxMsgSize() const { return std::numeric_limits::max(); } - bool SendTLV(uint32_t tag, uint64_t length, const void* value); + bool SendTLV(uint32_t tag, size_t length, const void* value); // Receive a TLV structure, the memory for the value will be allocated // by std::vector. bool RecvTLV(uint32_t* tag, std::vector* value); @@ -113,7 +113,7 @@ class Comms { // by std::string. bool RecvTLV(uint32_t* tag, std::string* value); // Receives a TLV value into a specified buffer without allocating memory. - bool RecvTLV(uint32_t* tag, uint64_t* length, void* buffer, uint64_t buffer_size); + bool RecvTLV(uint32_t* tag, size_t* length, void* buffer, size_t buffer_size); // Sends/receives various types of data. bool RecvUint8(uint8_t* v) { return RecvIntGeneric(v, kTagUint8); } @@ -138,7 +138,7 @@ class Comms { bool SendString(const std::string& v); bool RecvBytes(std::vector* buffer); - bool SendBytes(const uint8_t* v, uint64_t len); + bool SendBytes(const uint8_t* v, size_t len); bool SendBytes(const std::vector& buffer); // Receives remote process credentials. @@ -183,19 +183,18 @@ class Comms { struct ABSL_ATTRIBUTE_PACKED InternalTLV { uint32_t tag; uint32_t len; - uint64_t val; }; // Fills sockaddr_un struct with proper values. socklen_t CreateSockaddrUn(sockaddr_un* sun); // Support for EINTR and size completion. - bool Send(const void* data, uint64_t len); - bool Recv(void* data, uint64_t len); + bool Send(const void* data, size_t len); + bool Recv(void* data, size_t len); // Receives tag and length. Assumes that the `tlv_transmission_mutex_` mutex // is locked. - bool RecvTL(uint32_t* tag, uint64_t* length) + bool RecvTL(uint32_t* tag, size_t* length) ABSL_EXCLUSIVE_LOCKS_REQUIRED(tlv_recv_transmission_mutex_); // T has to be a ContiguousContainer @@ -203,7 +202,7 @@ class Comms { bool RecvTLVGeneric(uint32_t* tag, T* value); // Receives arbitrary integers. - bool RecvInt(void* buffer, uint64_t len, uint32_t tag); + bool RecvInt(void* buffer, size_t len, uint32_t tag); template bool RecvIntGeneric(T* output, uint32_t tag) { From 00e724fb8a240cd7f5e510327263026f64ed7550 Mon Sep 17 00:00:00 2001 From: Christian Blichmann Date: Wed, 23 Sep 2020 06:42:34 -0700 Subject: [PATCH 81/82] CMake build improvements - Try to fix Ninja needlessly rebuilding everything every time See https://stackoverflow.com/questions/47087237/cmake-and-ninja-rebuild-unnecessary-files/47100426#47100426 - Since SAPI requires CMake >= 3.12, remove custom `list_join` PiperOrigin-RevId: 333281764 Change-Id: I334d67d7ee54d21824b19e60a7a7f1e43bb5a057 --- CMakeLists.txt | 21 ++++++++++++--------- cmake/SapiBuildDefs.cmake | 4 ++-- cmake/SapiUtil.cmake | 10 ---------- 3 files changed, 14 insertions(+), 21 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7c993f4..20b732e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,6 +14,16 @@ cmake_minimum_required(VERSION 3.12) +# Fix Ninja generator output to not rebuild entire sub-trees needlessly. +if(CMAKE_GENERATOR MATCHES "Ninja") + file(WRITE "${CMAKE_BINARY_DIR}/UserMakeRulesOverride.cmake" + "string(REPLACE \"-MD\" \"-MMD\" CMAKE_DEPFILE_FLAGS_C \"\${CMAKE_DEPFILE_FLAGS_C}\")\n" + "string(REPLACE \"-MD\" \"-MMD\" CMAKE_DEPFILE_FLAGS_CXX \"\${CMAKE_DEPFILE_FLAGS_CXX}\")\n" + ) + set(CMAKE_USER_MAKE_RULES_OVERRIDE + "${CMAKE_BINARY_DIR}/UserMakeRulesOverride.cmake" CACHE INTERNAL "") +endif() + project(SandboxedAPI C CXX ASM) # SAPI-wide setting for the language level @@ -31,15 +41,8 @@ include(SapiDeps) include(SapiUtil) include(SapiBuildDefs) -# Fix Ninja generator output to not rebuild entire sub-trees needlessly. -if(CMAKE_GENERATOR MATCHES "Ninja") - file(WRITE "${SAPI_BINARY_DIR}/UserMakeRulesOverride.cmake" - "STRING(REPLACE \"-MD\" \"-MMD\" CMAKE_DEPFILE_FLAGS_C \"\${CMAKE_DEPFILE_FLAGS_C}\")\n" - "STRING(REPLACE \"-MD\" \"-MMD\" CMAKE_DEPFILE_FLAGS_CXX \"\${CMAKE_DEPFILE_FLAGS_CXX}\")\n" - ) - set(CMAKE_USER_MAKE_RULES_OVERRIDE - "${SAPI_BINARY_DIR}/UserMakeRulesOverride.cmake" CACHE INTERNAL "") -endif() +# Allow the header generator to auto-configure include paths +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) if (SAPI_FORCE_COLOR_OUTPUT) if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") # GCC diff --git a/cmake/SapiBuildDefs.cmake b/cmake/SapiBuildDefs.cmake index 62583ec..5b9ce28 100644 --- a/cmake/SapiBuildDefs.cmake +++ b/cmake/SapiBuildDefs.cmake @@ -123,7 +123,7 @@ function(add_sapi_library) endif() # Interface - list_join(_sapi_FUNCTIONS "," _sapi_funcs) + list(JOIN _sapi_FUNCTIONS "," _sapi_funcs) foreach(src IN LISTS _sapi_INPUTS) get_filename_component(src "${src}" ABSOLUTE) list(APPEND _sapi_full_inputs "${src}") @@ -149,7 +149,7 @@ function(add_sapi_library) ) else() set(_sapi_isystem "${_sapi_NAME}.isystem") - list_join(_sapi_full_inputs "," _sapi_full_inputs) + list(JOIN _sapi_full_inputs "," _sapi_full_inputs) add_custom_command( OUTPUT "${_sapi_gen_header}" "${_sapi_isystem}" COMMAND sh -c diff --git a/cmake/SapiUtil.cmake b/cmake/SapiUtil.cmake index 8773f59..f4e891f 100644 --- a/cmake/SapiUtil.cmake +++ b/cmake/SapiUtil.cmake @@ -35,16 +35,6 @@ function(create_directory_symlink SOURCE DESTINATION) endif() endfunction() -# Implements list(JOIN ...) for CMake < 3.12. -function(list_join LIST SEP OUTPUT) - foreach(item IN LISTS ${LIST}) - set(_concat "${_concat}${SEP}${item}") - endforeach() - string(LENGTH "${SEP}" _len) - string(SUBSTRING "${_concat}" ${_len} -1 _concat) - set(${OUTPUT} "${_concat}" PARENT_SCOPE) -endfunction() - # Helper function that behaves just like Protobuf's protobuf_generate_cpp(), # except that it strips import paths. This is necessary, because CMake's # protobuf rules don't work well with imports across different directories. From 9331eabd7e2bf974dfbd8123f545d11b80ab8c67 Mon Sep 17 00:00:00 2001 From: Christian Blichmann Date: Wed, 23 Sep 2020 07:59:42 -0700 Subject: [PATCH 82/82] Copybara import of the project: Including changes for GitHub -> internal migration -- b5d7e43ddeff9c087d0f67949bea6ac795c5474a by Federico Stazi <34340238+FedericoStazi@users.noreply.github.com>: Initial curl commit -- 24786c44d89b4a6817204aaacd84fc1aa2747434 by Federico Stazi : Added gitignore and curl submodule -- 6d5cfd575abd05c387f93be060c8fc88fd39e482 by Federico Stazi : Added new line at the end of files -- c7423c5f8a8d460655d0fafa198758c39d5270d1 by Federico Stazi : Remove SHARED from add_sapi_library -- 05c0a4b004feba1c0ae1ba6bf519966f48589ba6 by Federico Stazi : Fix includes -- 5be51fabbef7e7eab032dbfb94239654e44008c3 by Federico Stazi : Improve comments -- 34338411b845d438a5b7615d990d6539771152eb by Federico Stazi : Improve style -- 8c68ac221ff158aab3b285d8b2d6158a895ddbf2 by Federico Stazi : Address review comments -- ac1112ae4de6f5f520054b5608d202a57c296ac4 by Federico Stazi : Minor fix -- f47e1cc6aceb0365cb2e5352d61980628af7f954 by Federico Stazi : Implement all curl methods -- 929123127532589ef19f12114b8e450cc2c976a1 by Federico Stazi : Address reviews and improve code style -- 1b0a8edfd4cdffdc76f3e979a5e1b42cbe289e73 by Federico Stazi : Minor fix -- cea046d3e29b86e04bd6ce7821ee1409cea2db37 by Federico Stazi : Implement stricter policy -- cf23888b88b71add3e60524f3db3604f0ab6c386 by Federico Stazi : Improve and extend examples -- 6167cafbdec1355588c073baa8cdf17fad1fcb9e by Federico Stazi : Implement tests -- 9fed2ec09798e656cd5c518bc13f45eea1abef2e by Federico Stazi : Improved error handling -- e446ec81a13d3c567bdebe00285211d9df9dbed1 by Federico Stazi : Address review comments -- cf41ec4701a6a47ecee3af6765623ca020cebfcd by Federico Stazi <34340238+FedericoStazi@users.noreply.github.com>: Fix project name -- 9a4293a3cfd87b9b13b46a36d5eeee9d575ea519 by Federico Stazi : Fix project name -- bbebeee1a69fed2c70afc6afa2aa79aad990a778 by Federico Stazi : Fix test mock server -- eb783de3f5fc35877db5f08fd53c9a33207a416e by Federico Stazi : Address review comments -- cf6cb89bca2b0275652509afdb4d4e20e9e851ba by Federico Stazi : Minor mock server fix -- b52d9e6e4fa1f9c07a3027b4b4d564457e7a648f by Federico Stazi : Address review comments PiperOrigin-RevId: 333292204 Change-Id: I9ff27348028d9f22486492dc92c0859ff8f44d68 --- .gitmodules | 5 +- oss-internship-2020/curl/.gitignore | 1 + oss-internship-2020/curl/CMakeLists.txt | 144 +++++++++ oss-internship-2020/curl/README.md | 112 +++++++ .../curl/callbacks/callbacks.cc | 37 +++ .../curl/callbacks/callbacks.h | 24 ++ .../curl/curl_wrapper/CMakeLists.txt | 37 +++ .../curl/curl_wrapper/curl_wrapper.cc | 86 +++++ .../curl/curl_wrapper/curl_wrapper.h | 82 +++++ .../curl/examples/CMakeLists.txt | 76 +++++ oss-internship-2020/curl/examples/README.md | 38 +++ oss-internship-2020/curl/examples/example1.cc | 78 +++++ oss-internship-2020/curl/examples/example2.cc | 107 +++++++ oss-internship-2020/curl/examples/example3.cc | 166 ++++++++++ oss-internship-2020/curl/examples/example4.cc | 128 ++++++++ oss-internship-2020/curl/examples/example5.cc | 102 ++++++ oss-internship-2020/curl/examples/example6.cc | 72 +++++ oss-internship-2020/curl/sandbox.h | 67 ++++ oss-internship-2020/curl/tests/CMakeLists.txt | 28 ++ oss-internship-2020/curl/tests/test_utils.cc | 301 ++++++++++++++++++ oss-internship-2020/curl/tests/test_utils.h | 55 ++++ oss-internship-2020/curl/tests/tests.cc | 138 ++++++++ 22 files changed, 1883 insertions(+), 1 deletion(-) create mode 100644 oss-internship-2020/curl/.gitignore create mode 100644 oss-internship-2020/curl/CMakeLists.txt create mode 100644 oss-internship-2020/curl/README.md create mode 100644 oss-internship-2020/curl/callbacks/callbacks.cc create mode 100644 oss-internship-2020/curl/callbacks/callbacks.h create mode 100644 oss-internship-2020/curl/curl_wrapper/CMakeLists.txt create mode 100644 oss-internship-2020/curl/curl_wrapper/curl_wrapper.cc create mode 100644 oss-internship-2020/curl/curl_wrapper/curl_wrapper.h create mode 100644 oss-internship-2020/curl/examples/CMakeLists.txt create mode 100644 oss-internship-2020/curl/examples/README.md create mode 100644 oss-internship-2020/curl/examples/example1.cc create mode 100644 oss-internship-2020/curl/examples/example2.cc create mode 100644 oss-internship-2020/curl/examples/example3.cc create mode 100644 oss-internship-2020/curl/examples/example4.cc create mode 100644 oss-internship-2020/curl/examples/example5.cc create mode 100644 oss-internship-2020/curl/examples/example6.cc create mode 100644 oss-internship-2020/curl/sandbox.h create mode 100644 oss-internship-2020/curl/tests/CMakeLists.txt create mode 100644 oss-internship-2020/curl/tests/test_utils.cc create mode 100644 oss-internship-2020/curl/tests/test_utils.h create mode 100644 oss-internship-2020/curl/tests/tests.cc diff --git a/.gitmodules b/.gitmodules index 7449ecf..ad0eddc 100644 --- a/.gitmodules +++ b/.gitmodules @@ -3,4 +3,7 @@ url = https://github.com/uclouvain/openjpeg.git [submodule "oss-internship-2020/pffft/master"] path = oss-internship-2020/pffft/master - url = https://bitbucket.org/jpommier/pffft/src/master/ \ No newline at end of file + url = https://bitbucket.org/jpommier/pffft/src/master/ +[submodule "oss-internship-2020/curl/curl_wrapper/curl"] + path = oss-internship-2020/curl/curl_wrapper/curl + url = https://github.com/curl/curl diff --git a/oss-internship-2020/curl/.gitignore b/oss-internship-2020/curl/.gitignore new file mode 100644 index 0000000..567609b --- /dev/null +++ b/oss-internship-2020/curl/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/oss-internship-2020/curl/CMakeLists.txt b/oss-internship-2020/curl/CMakeLists.txt new file mode 100644 index 0000000..8d2be38 --- /dev/null +++ b/oss-internship-2020/curl/CMakeLists.txt @@ -0,0 +1,144 @@ +# Copyright 2020 Google LLC +# +# 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. + +cmake_minimum_required(VERSION 3.16) + +project(libcurl_sandbox) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED True) + +option(CURL_SAPI_ENABLE_EXAMPLES "" ON) +option(CURL_SAPI_ENABLE_TESTS "" ON) + +# Add callbacks used by examples and tests +if (CURL_SAPI_ENABLE_EXAMPLES OR CURL_SAPI_ENABLE_TESTS) + list(APPEND CURL_SAPI_CALLBACKS + "${CMAKE_CURRENT_SOURCE_DIR}/callbacks/callbacks.h" + "${CMAKE_CURRENT_SOURCE_DIR}/callbacks/callbacks.cc" + ) +endif() + +# Add folder containing the non-sandboxed custom curl library +add_subdirectory(curl_wrapper) + +# Setup Sandboxed API +set(SAPI_ROOT "" CACHE PATH "Path to the Sandboxed API source tree") +set(SAPI_ENABLE_EXAMPLES ${CURL_SAPI_ENABLE_EXAMPLES} CACHE BOOL "" FORCE) +set(SAPI_ENABLE_TESTS ${CURL_SAPI_ENABLE_TESTS} CACHE BOOL "" FORCE) +add_subdirectory( + "${SAPI_ROOT}" + "${CMAKE_BINARY_DIR}/sandboxed-api-build" + EXCLUDE_FROM_ALL +) + +# Generate SAPI header +add_sapi_library(curl_sapi + + # List of all the methods in https://curl.haxx.se/libcurl/c/allfuncs.html + # Some are added or modified because the original ones are not supported + # by Sandboxed API (details can be found in curl_wrapper.h) + FUNCTIONS curl_easy_cleanup + curl_easy_duphandle + curl_easy_escape + curl_easy_getinfo + curl_easy_getinfo_ptr + curl_easy_init + curl_easy_pause + curl_easy_perform + curl_easy_recv + curl_easy_reset + curl_easy_send + curl_easy_setopt + curl_easy_setopt_ptr + curl_easy_setopt_long + curl_easy_setopt_curl_off_t + curl_easy_strerror + curl_easy_unescape + curl_easy_upkeep + curl_free + curl_getdate_sapi + curl_global_cleanup + curl_global_init + curl_global_init_mem + curl_global_sslset + curl_mime_addpart + curl_mime_data + curl_mime_data_cb + curl_mime_encoder + curl_mime_filedata + curl_mime_filename + curl_mime_free + curl_mime_headers + curl_mime_init + curl_mime_name + curl_mime_subparts + curl_mime_type + curl_multi_add_handle + curl_multi_assign + curl_multi_cleanup + curl_multi_fdset_sapi + curl_multi_info_read + curl_multi_init + curl_multi_perform + curl_multi_remove_handle + curl_multi_setopt + curl_multi_setopt_ptr + curl_multi_setopt_long + curl_multi_setopt_curl_off_t + curl_multi_socket_action + curl_multi_strerror + curl_multi_timeout + curl_multi_poll_sapi + curl_multi_wait_sapi + curl_multi_wakeup + curl_share_init + curl_share_setopt + curl_share_setopt_ptr + curl_share_setopt_long + curl_share_strerror + curl_slist_append + curl_slist_free_all + curl_url + curl_url_cleanup + curl_url_dup + curl_url_get + curl_url_set + curl_version + curl_version_info + + INPUTS curl_wrapper/curl/include/curl/curl.h + curl_wrapper/curl_wrapper.h + + LIBRARY curl_wrapper_and_callbacks + + LIBRARY_NAME Curl + + NAMESPACE "" +) + +# Include generated SAPI header +target_include_directories(curl_sapi INTERFACE + "${PROJECT_BINARY_DIR}" +) + +# Add examples +if (CURL_SAPI_ENABLE_EXAMPLES) + add_subdirectory(examples) +endif() + +# Add tests +if (CURL_SAPI_ENABLE_TESTS) + add_subdirectory(tests) +endif() diff --git a/oss-internship-2020/curl/README.md b/oss-internship-2020/curl/README.md new file mode 100644 index 0000000..3000838 --- /dev/null +++ b/oss-internship-2020/curl/README.md @@ -0,0 +1,112 @@ +# LibCurl Sandbox + +This library is a sandboxed version of curl's C API, +[libcurl](https://curl.haxx.se/libcurl/c/), implemented using Sandboxed API. + +## Setup + +The repository can be cloned using: `git clone --recursive [URL to this repo]` +The `--recursive` flag ensures that submodules are also cloned. + +Alternatively, if the repository has already been cloned but the submodules have +not, these can be cloned using: `git submodule update --init --recursive` + +The full list of Sandboxed API dependencies can be found on +[Sandboxed API Getting Started page](https://developers.google.com/sandboxed-api/docs/getting-started). + +The following commands, used from the current `curl/` directory, build the +library: `mkdir -p build cd build cmake .. -G Ninja -D SAPI_ROOT=[path to +sandboxed-api] cmake --build .` + +## Implementation details + +All of libcurl's methods are supported by the library. However, a few of these +have different signatures defined in the sandboxed header `custom_curl.h`, which +wraps and extends libcurl. + +This is necessary because Sandboxed API sandboxes most of libcurl correctly, but +encounters some issues when sandboxing a few methods. The simplest solution is +wrapping these methods into wrapper methods that accomplish the same tasks but +can also be sandboxed. + +The next sections describe the issues encountered and contain some information +on the signatures of the wrapper methods solving these issues. + +#### Variadic methods + +Variadic methods are currently not supported by Sandboxed API. To solve this, +these methods are defined with an additional explicit parameter in +`custom_curl.h`. + +The methods are: - `curl_easy_setopt`. Use `curl_easy_setopt_ptr`, +`curl_easy_setopt_long` or `curl_easy_setopt_curl_off_t` instead. - +`curl_easy_getinfo`. Use `curl_easy_getinfo_ptr` instead. - `curl_multi_setopt`. +Use `curl_multi_setopt_ptr`, `curl_multi_setopt_long` or +`curl_multi_setopt_curl_off_t` instead. - `curl_share_setopt`. Use +`curl_share_setopt_ptr` or `curl_share_setopt_long` instead + +#### Methods with incomplete array arguments + +Incomplete array arguments are currently not supported by Sandboxed API. To +solve this, methods taking an incomplete array argument have a wrapper in +`custom_curl.h`, and take a pointer as the argument. + +The methods are: - `curl_multi_poll`. Use `curl_multi_poll_sapi` instead. - +`curl_multi_wait`. Use `curl_multi_wait_sapi` instead. + +#### Methods with conflicts on the generated header + +Some methods create conflicts on the generated header because of redefined +`#define` directives from files included by the header. To solve this, the +conflicting types and methods are redefined in `custom_curl.h`. + +The types are: - `time_t`. Use `time_t_sapi` instead. - `fd_set`. Use +`fd_set_sapi` instead. + +The methods are: - `curl_getdate`. Use `curl_getdate_sapi` instead. - +`curl_multi_fdset`. Use `curl_multi_fdset_sapi` instead. + +#### Function pointers + +The functions whose pointers will be passed to the library's methods +(*callbacks*) can't be implemented in the files making use of the library, but +must be in other files. These files must be compiled together with the library, +and this is done by adding their absolute path to the cmake variable +`CURL_SAPI_CALLBACKS`. + +The pointers can then be obtained using an `RPCChannel` object, as shown in +`example2.cc`. + +## Examples + +The `examples` directory contains the sandboxed versions of example source codes +taken from [this page](https://curl.haxx.se/libcurl/c/example.html) on curl's +website. More information about each example can be found in the examples' +[README](examples/README.md). + +To build these examples when building the library, the cmake variable +`CURL_SAPI_ENABLE_EXAMPLES` must be set to `ON`. This enables Sandboxed API +examples as well. + +## Policy + +The `sandbox.h` file contains a policy allowing all is necessary for libcurl to +perform simple requests. It is used by all the examples, except by example3. +This example needs some additional policies and files in its namespace (since it +uses HTTPS), and the file `example3.cc` shows how to easily extend an existing +policy. + +## Testing + +The `tests` folder contains some test cases created using Google Test. The class +`CurlTestUtils` is used to facilitate some tasks that all test cases need, +including the setup of a mock local server on which test requests are performed. + +To build these tests when building the library, the cmake variable +`CURL_SAPI_ENABLE_TESTS` must be set to `ON`. This enables Sandboxed API tests +as well. + +## Callbacks + +The `callbacks.h` and `callbacks.cc` files implement all the callbacks used by +examples and tests. diff --git a/oss-internship-2020/curl/callbacks/callbacks.cc b/oss-internship-2020/curl/callbacks/callbacks.cc new file mode 100644 index 0000000..015e66c --- /dev/null +++ b/oss-internship-2020/curl/callbacks/callbacks.cc @@ -0,0 +1,37 @@ +// Copyright 2020 Google LLC +// +// 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. + +#include "callbacks.h" // NOLINT(build/include) + +#include +#include + +#include "sandboxed_api/vars.h" + +size_t WriteToMemory(char* contents, size_t size, size_t num_bytes, + void* userp) { + size_t real_size = size * num_bytes; + auto* mem = static_cast(userp); + + char* ptr = static_cast(realloc(mem->data, mem->size + real_size + 1)); + if (ptr == nullptr) return 0; + + mem->data = ptr; + auto data = static_cast(mem->data); + memcpy(&(data[mem->size]), contents, real_size); + mem->size += real_size; + data[mem->size] = 0; + + return real_size; +} diff --git a/oss-internship-2020/curl/callbacks/callbacks.h b/oss-internship-2020/curl/callbacks/callbacks.h new file mode 100644 index 0000000..306e2eb --- /dev/null +++ b/oss-internship-2020/curl/callbacks/callbacks.h @@ -0,0 +1,24 @@ +// Copyright 2020 Google LLC +// +// 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. + +#ifndef TESTS_CALLBACKS_H +#define TESTS_CALLBACKS_H + +#include + +// Append contents to the string stored by userp, which is a sapi::LenValStruct* +extern "C" size_t WriteToMemory(char* contents, size_t size, size_t num_bytes, + void* userp); + +#endif // TESTS_CALLBACKS_H diff --git a/oss-internship-2020/curl/curl_wrapper/CMakeLists.txt b/oss-internship-2020/curl/curl_wrapper/CMakeLists.txt new file mode 100644 index 0000000..78d2526 --- /dev/null +++ b/oss-internship-2020/curl/curl_wrapper/CMakeLists.txt @@ -0,0 +1,37 @@ +# Copyright 2020 Google LLC +# +# 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. + +# Wrapper library including curl, wrappers for some methods and callbacks +# The CURL_SAPI_CALLBACKS variable should contain the absolute paths of +# all the files implementing the callbacks +add_library(curl_wrapper_and_callbacks OBJECT + curl_wrapper.h + curl_wrapper.cc + "${CURL_SAPI_CALLBACKS}" +) +set_target_properties(curl_wrapper_and_callbacks + PROPERTIES LINKER_LANGUAGE C +) + +# Flags needed to build curl statically +set(CURL_HIDDEN_SYMBOLS OFF) +set(BUILD_SHARED_LIBS OFF) + +# Link the wrapper to the original curl library (testing is disabled in curl) +set(BUILD_TESTING OFF) +add_subdirectory(curl) +target_link_libraries(curl_wrapper_and_callbacks + CURL::libcurl + sapi::sapi +) diff --git a/oss-internship-2020/curl/curl_wrapper/curl_wrapper.cc b/oss-internship-2020/curl/curl_wrapper/curl_wrapper.cc new file mode 100644 index 0000000..6105fbe --- /dev/null +++ b/oss-internship-2020/curl/curl_wrapper/curl_wrapper.cc @@ -0,0 +1,86 @@ +// Copyright 2020 Google LLC +// +// 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. + +#include "curl_wrapper.h" // NOLINT(build/include) + +CURLcode curl_easy_setopt_ptr(CURL* handle, CURLoption option, + void* parameter) { + return curl_easy_setopt(handle, option, parameter); +} + +CURLcode curl_easy_setopt_long(CURL* handle, CURLoption option, + long parameter) { + return curl_easy_setopt(handle, option, parameter); +} + +CURLcode curl_easy_setopt_curl_off_t(CURL* handle, CURLoption option, + curl_off_t parameter) { + return curl_easy_setopt(handle, option, parameter); +} + +CURLcode curl_easy_getinfo_ptr(CURL* handle, CURLINFO option, void* parameter) { + return curl_easy_getinfo(handle, option, parameter); +} + +time_t_sapi curl_getdate_sapi(char* datestring, time_t_sapi* now) { + return curl_getdate(datestring, now); +} + +CURLMcode curl_multi_fdset_sapi(CURLM* multi_handle, fd_set_sapi* read_fd_set, + fd_set_sapi* write_fd_set, + fd_set_sapi* exc_fd_set, int* max_fd) { + return curl_multi_fdset(multi_handle, read_fd_set, write_fd_set, exc_fd_set, + max_fd); +} + +CURLMcode curl_multi_setopt_ptr(CURLM* handle, CURLMoption option, + void* parameter) { + return curl_multi_setopt(handle, option, parameter); +} + +CURLMcode curl_multi_setopt_long(CURLM* handle, CURLMoption option, + long parameter) { + return curl_multi_setopt(handle, option, parameter); +} + +CURLMcode curl_multi_setopt_curl_off_t(CURLM* handle, CURLMoption option, + curl_off_t parameter) { + return curl_multi_setopt(handle, option, parameter); +} + +CURLMcode curl_multi_poll_sapi(CURLM* multi_handle, + struct curl_waitfd* extra_fds, + unsigned int extra_nfds, int timeout_ms, + int* numfds) { + return curl_multi_poll(multi_handle, extra_fds, extra_nfds, timeout_ms, + numfds); +} + +CURLMcode curl_multi_wait_sapi(CURLM* multi_handle, + struct curl_waitfd* extra_fds, + unsigned int extra_nfds, int timeout_ms, + int* numfds) { + return curl_multi_wait(multi_handle, extra_fds, extra_nfds, timeout_ms, + numfds); +} + +CURLSHcode curl_share_setopt_ptr(CURLSH* handle, CURLSHoption option, + void* parameter) { + return curl_share_setopt(handle, option, parameter); +} + +CURLSHcode curl_share_setopt_long(CURLSH* handle, CURLSHoption option, + long parameter) { + return curl_share_setopt(handle, option, parameter); +} diff --git a/oss-internship-2020/curl/curl_wrapper/curl_wrapper.h b/oss-internship-2020/curl/curl_wrapper/curl_wrapper.h new file mode 100644 index 0000000..4ef612b --- /dev/null +++ b/oss-internship-2020/curl/curl_wrapper/curl_wrapper.h @@ -0,0 +1,82 @@ +// Copyright 2020 Google LLC +// +// 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. + +// Wrapper for curl library + +#ifndef CURL_WRAPPER_H +#define CURL_WRAPPER_H + +#include + +extern "C" { + +// The wrapper method is needed to make the variadic argument explicit +CURLcode curl_easy_setopt_ptr(CURL* handle, CURLoption option, void* parameter); + +// The wrapper method is needed to make the variadic argument explicit +CURLcode curl_easy_setopt_long(CURL* handle, CURLoption option, long parameter); + +// The wrapper method is needed to make the variadic argument explicit +CURLcode curl_easy_setopt_curl_off_t(CURL* handle, CURLoption option, + curl_off_t parameter); + +// The wrapper method is needed to make the variadic argument explicit +CURLcode curl_easy_getinfo_ptr(CURL* handle, CURLINFO option, void* parameter); + +// The typedef and wrapper method are needed because the original method has +// some conflicts in curl_sapi.sapi.h +typedef time_t time_t_sapi; +time_t_sapi curl_getdate_sapi(char* datestring, time_t_sapi* now); + +// The typedef and wrapper method are needed because the original method has +// some conflicts in curl_sapi.sapi.h +typedef fd_set fd_set_sapi; +CURLMcode curl_multi_fdset_sapi(CURLM* multi_handle, fd_set_sapi* read_fd_set, + fd_set_sapi* write_fd_set, + fd_set_sapi* exc_fd_set, int* max_fd); + +// The wrapper method is needed to make the variadic argument explicit +CURLMcode curl_multi_setopt_ptr(CURLM* handle, CURLMoption option, + void* parameter); + +// The wrapper method is needed to make the variadic argument explicit +CURLMcode curl_multi_setopt_long(CURLM* handle, CURLMoption option, + long parameter); + +// The wrapper method is needed to make the variadic argument explicit +CURLMcode curl_multi_setopt_curl_off_t(CURLM* handle, CURLMoption option, + curl_off_t parameter); + +// The wrapper method is needed because incomplete array type is not supported +CURLMcode curl_multi_poll_sapi(CURLM* multi_handle, + struct curl_waitfd* extra_fds, + unsigned int extra_nfds, int timeout_ms, + int* numfds); + +// The wrapper method is needed because incomplete array type is not supported +CURLMcode curl_multi_wait_sapi(CURLM* multi_handle, + struct curl_waitfd* extra_fds, + unsigned int extra_nfds, int timeout_ms, + int* numfds); + +// The wrapper method is needed to make the variadic argument explicit +CURLSHcode curl_share_setopt_ptr(CURLSH* handle, CURLSHoption option, + void* parameter); + +// The wrapper method is needed to make the variadic argument explicit +CURLSHcode curl_share_setopt_long(CURLSH* handle, CURLSHoption option, + long parameter); +} + +#endif // CURL_WRAPPER_H diff --git a/oss-internship-2020/curl/examples/CMakeLists.txt b/oss-internship-2020/curl/examples/CMakeLists.txt new file mode 100644 index 0000000..9193109 --- /dev/null +++ b/oss-internship-2020/curl/examples/CMakeLists.txt @@ -0,0 +1,76 @@ +# Copyright 2020 Google LLC +# +# 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. + +# All the examples are sandboxed versions of curl's examples +# (https://curl.haxx.se/libcurl/c/example.html) + +# Example 1: simple.c +add_executable(example1 + example1.cc + ../sandbox.h +) +target_link_libraries(example1 PRIVATE + curl_sapi + sapi::sapi +) + +# Example 2: getinmemory.c +add_executable(example2 + example2.cc + ../sandbox.h +) +target_link_libraries(example2 PRIVATE + curl_sapi + sapi::sapi +) + +# Example 3: simplessl.c +add_executable(example3 + example3.cc + ../sandbox.h +) +target_link_libraries(example3 PRIVATE + curl_sapi + sapi::sapi +) + +# Example 4: multi-poll.c +add_executable(example4 + example4.cc + ../sandbox.h +) +target_link_libraries(example4 PRIVATE + curl_sapi + sapi::sapi +) + +# Example 5: multithread.c +add_executable(example5 + example5.cc + ../sandbox.h +) +target_link_libraries(example5 PRIVATE + curl_sapi + sapi::sapi +) + +# Example 6: simple.c (using transactions) +add_executable(example6 + example6.cc + ../sandbox.h +) +target_link_libraries(example6 PRIVATE + curl_sapi + sapi::sapi +) diff --git a/oss-internship-2020/curl/examples/README.md b/oss-internship-2020/curl/examples/README.md new file mode 100644 index 0000000..d653a47 --- /dev/null +++ b/oss-internship-2020/curl/examples/README.md @@ -0,0 +1,38 @@ +# LibCurl Sandbox Examples + +Each example in this folder is the sandboxed version of a code snippet from +[this page](https://curl.haxx.se/libcurl/c/example.html) on curl's website. +These examples perform some basic tasks using libcurl, and can be useful both to +understand how to use LibCurl Sandbox, but also to get an idea of how regular +and sandboxed code compare to each other. + +This is the list of the examples: + +- **example1**: sandboxed version of + [simple.c](https://curl.haxx.se/libcurl/c/simple.html). Really simple HTTP + request, downloads and prints out the page at + [example.com](http://example.com). +- **example2**: sandboxed version of + [getinmemory.c](https://curl.haxx.se/libcurl/c/getinmemory.html). Same HTTP + request as example1. The difference is that this example uses a callback to + save the page directly in memory. Only the page size is printed out. +- **example3**: sandboxed version of + [simplessl.c](https://curl.haxx.se/libcurl/c/simplessl.html). HTTPS request + of the [example.com](https://example.com) page, using SSL authentication. + This script takes 4 arguments (SSL certificates file, SSL keys file, SSL + keys password and CA certificates files), and prints out the page. +- **example4**: sandboxed version of + [multi-poll.c](https://curl.haxx.se/libcurl/c/multi-poll.html). Same HTTP + request as example1, with the addition of a polling method that can be used + to track the status of the request. The page is printed out after it is + downloaded. +- **example5**: sandboxed version of + [multithread.c](https://curl.haxx.se/libcurl/c/multithread.html). Four HTTP + request of the pages [example.com](http://example.com), + [example.edu](http://example.edu), [example.net](http://example.net) and + [example.org](http://example.org), performed at the same time using + libcurl's multithreading methods. The pages are printed out. +- **example6**: sandboxed version of + [simple.c](https://curl.haxx.se/libcurl/c/simple.html). Performs the same + tasks as example1, but Sandbox API Transactions are used to show how they + can be used to perform a simple request. diff --git a/oss-internship-2020/curl/examples/example1.cc b/oss-internship-2020/curl/examples/example1.cc new file mode 100644 index 0000000..e5ee5fe --- /dev/null +++ b/oss-internship-2020/curl/examples/example1.cc @@ -0,0 +1,78 @@ +// Copyright 2020 Google LLC +// +// 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. + +// Sandboxed version of simple.c +// Simple HTTP GET request + +#include + +#include "../sandbox.h" // NOLINT(build/include) + +int main(int argc, char* argv[]) { + gflags::ParseCommandLineFlags(&argc, &argv, true); + google::InitGoogleLogging(argv[0]); + + absl::Status status; + + // Initialize sandbox2 and sapi + CurlSapiSandbox sandbox; + status = sandbox.Init(); + if (!status.ok()) { + LOG(FATAL) << "Couldn't initialize Sandboxed API: " << status; + } + CurlApi api(&sandbox); + + // Initialize the curl session + absl::StatusOr curl_handle = api.curl_easy_init(); + if (!curl_handle.ok()) { + LOG(FATAL) << "curl_easy_init failed: " << curl_handle.status(); + } + sapi::v::RemotePtr curl(curl_handle.value()); + if (!curl.GetValue()) LOG(FATAL) << "curl_easy_init failed: curl is NULL"; + + absl::StatusOr curl_code; + + // Specify URL to get + sapi::v::ConstCStr url("http://example.com"); + curl_code = api.curl_easy_setopt_ptr(&curl, CURLOPT_URL, url.PtrBefore()); + if (!curl_code.ok() || curl_code.value() != CURLE_OK) { + LOG(FATAL) << "curl_easy_setopt_ptr failed: " << curl_code.status(); + } + + // Set the library to follow a redirection + curl_code = api.curl_easy_setopt_long(&curl, CURLOPT_FOLLOWLOCATION, 1l); + if (!curl_code.ok() || curl_code.value() != CURLE_OK) { + LOG(FATAL) << "curl_easy_setopt_long failed: " << curl_code.status(); + } + + // Disable authentication of peer certificate + curl_code = api.curl_easy_setopt_long(&curl, CURLOPT_SSL_VERIFYPEER, 0l); + if (!curl_code.ok() || curl_code.value() != CURLE_OK) { + LOG(FATAL) << "curl_easy_setopt_long failed: " << curl_code.status(); + } + + // Perform the request + curl_code = api.curl_easy_perform(&curl); + if (!curl_code.ok() || curl_code.value() != CURLE_OK) { + LOG(FATAL) << "curl_easy_perform failed: " << curl_code.status(); + } + + // Cleanup curl + status = api.curl_easy_cleanup(&curl); + if (!status.ok()) { + LOG(FATAL) << "curl_easy_cleanup failed: " << status; + } + + return EXIT_SUCCESS; +} diff --git a/oss-internship-2020/curl/examples/example2.cc b/oss-internship-2020/curl/examples/example2.cc new file mode 100644 index 0000000..c7abe0c --- /dev/null +++ b/oss-internship-2020/curl/examples/example2.cc @@ -0,0 +1,107 @@ +// Copyright 2020 Google LLC +// +// 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. + +// Sandboxed version of getinmemory.c +// HTTP GET request using callbacks + +#include +#include + +#include "../sandbox.h" // NOLINT(build/include) + +int main(int argc, char* argv[]) { + gflags::ParseCommandLineFlags(&argc, &argv, true); + google::InitGoogleLogging(argv[0]); + + absl::Status status; + + // Initialize sandbox2 and sapi + CurlSapiSandbox sandbox; + status = sandbox.Init(); + if (!status.ok()) { + LOG(FATAL) << "Couldn't initialize Sandboxed API: " << status; + } + CurlApi api(&sandbox); + + // Generate pointer to WriteMemoryCallback function + void* function_ptr; + status = sandbox.rpc_channel()->Symbol("WriteToMemory", &function_ptr); + if (!status.ok()) { + LOG(FATAL) << "sapi::Sandbox::rpc_channel().Symbol failed: " << status; + } + sapi::v::RemotePtr remote_function_ptr(function_ptr); + + // Initialize the curl session + absl::StatusOr curl_handle = api.curl_easy_init(); + if (!curl_handle.ok()) { + LOG(FATAL) << "curl_easy_init failed: " << curl_handle.status(); + } + sapi::v::RemotePtr curl(curl_handle.value()); + if (!curl.GetValue()) { + LOG(FATAL) << "curl_easy_init failed: curl is NULL"; + } + + absl::StatusOr curl_code; + + // Specify URL to get + sapi::v::ConstCStr url("http://example.com"); + curl_code = api.curl_easy_setopt_ptr(&curl, CURLOPT_URL, url.PtrBefore()); + if (!curl_code.ok() || curl_code.value() != CURLE_OK) { + LOG(FATAL) << "curl_easy_setopt_ptr failed: " << curl_code.status(); + } + + // Set WriteMemoryCallback as the write function + curl_code = api.curl_easy_setopt_ptr(&curl, CURLOPT_WRITEFUNCTION, + &remote_function_ptr); + if (!curl_code.ok() || curl_code.value() != CURLE_OK) { + LOG(FATAL) << "curl_easy_setopt_ptr failed: " << curl_code.status(); + } + + // Pass 'chunk' struct to the callback function + sapi::v::LenVal chunk(0); + curl_code = + api.curl_easy_setopt_ptr(&curl, CURLOPT_WRITEDATA, chunk.PtrBoth()); + if (!curl_code.ok() || curl_code.value() != CURLE_OK) { + LOG(FATAL) << "curl_easy_setopt_ptr failed: " << curl_code.status(); + } + + // Set a user agent + sapi::v::ConstCStr user_agent("libcurl-agent/1.0"); + curl_code = api.curl_easy_setopt_ptr(&curl, CURLOPT_USERAGENT, + user_agent.PtrBefore()); + if (!curl_code.ok() || curl_code.value() != CURLE_OK) { + LOG(FATAL) << "curl_easy_setopt_ptr failed: " << curl_code.status(); + } + + // Perform the request + curl_code = api.curl_easy_perform(&curl); + if (!curl_code.ok() || curl_code.value() != CURLE_OK) { + LOG(FATAL) << "curl_easy_perform failed: " << curl_code.status(); + } + + // Retrieve memory size + status = sandbox.TransferFromSandboxee(&chunk); + if (!status.ok()) { + LOG(FATAL) << "sandbox.TransferFromSandboxee failed: " << status; + } + std::cout << "memory size: " << chunk.GetDataSize() << " bytes" << std::endl; + + // Cleanup curl + status = api.curl_easy_cleanup(&curl); + if (!status.ok()) { + LOG(FATAL) << "curl_easy_cleanup failed: " << status; + } + + return EXIT_SUCCESS; +} diff --git a/oss-internship-2020/curl/examples/example3.cc b/oss-internship-2020/curl/examples/example3.cc new file mode 100644 index 0000000..1a73c93 --- /dev/null +++ b/oss-internship-2020/curl/examples/example3.cc @@ -0,0 +1,166 @@ +// Copyright 2020 Google LLC +// +// 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. + +// Sandboxed version of simplessl.c +// HTTPS GET request + +#include + +#include "../sandbox.h" // NOLINT(build/include) + +class CurlSapiSandboxEx3 : public CurlSapiSandbox { + public: + CurlSapiSandboxEx3(std::string ssl_certificate, std::string ssl_key, + std::string ca_certificates) + : ssl_certificate(ssl_certificate), + ssl_key(ssl_key), + ca_certificates(ca_certificates) {} + + private: + std::unique_ptr ModifyPolicy( + sandbox2::PolicyBuilder*) override { + // Add the syscalls and files missing in CurlSandbox to a new PolicyBuilder + auto policy_builder = std::make_unique(); + (*policy_builder) + .AllowFutexOp(FUTEX_WAIT_PRIVATE) + .AllowGetPIDs() + .AllowGetRandom() + .AllowHandleSignals() + .AllowSyscall(__NR_sysinfo) + .AddFile(ssl_certificate) + .AddFile(ssl_key) + .AddFile(ca_certificates); + // Provide the new PolicyBuilder to ModifyPolicy in CurlSandbox + return CurlSapiSandbox::ModifyPolicy(policy_builder.get()); + } + + std::string ssl_certificate; + std::string ssl_key; + std::string ca_certificates; +}; + +int main(int argc, char* argv[]) { + gflags::ParseCommandLineFlags(&argc, &argv, true); + google::InitGoogleLogging(argv[0]); + + absl::Status status; + + // Get input parameters (should be absolute paths) + if (argc != 5) { + LOG(FATAL) << "wrong number of arguments (4 expected)"; + } + std::string ssl_certificate = argv[1]; + std::string ssl_key = argv[2]; + std::string ssl_key_password = argv[3]; + std::string ca_certificates = argv[4]; + + // Initialize sandbox2 and sapi + CurlSapiSandboxEx3 sandbox(ssl_certificate, ssl_key, ca_certificates); + status = sandbox.Init(); + if (!status.ok()) { + LOG(FATAL) << "Couldn't initialize Sandboxed API: " << status; + } + CurlApi api(&sandbox); + + absl::StatusOr curl_code; + + // Initialize curl (CURL_GLOBAL_DEFAULT = 3) + curl_code = api.curl_global_init(3l); + if (!curl_code.ok() || curl_code.value() != CURLE_OK) { + LOG(FATAL) << "curl_global_init failed: " << curl_code.status(); + } + + // Initialize curl easy handle + absl::StatusOr curl_handle = api.curl_easy_init(); + if (!curl_handle.ok()) { + LOG(FATAL) << "curl_easy_init failed: " << curl_handle.status(); + } + sapi::v::RemotePtr curl(curl_handle.value()); + if (!curl.GetValue()) { + LOG(FATAL) << "curl_easy_init failed: curl is NULL"; + } + + // Specify URL to get (using HTTPS) + sapi::v::ConstCStr url("https://example.com"); + curl_code = api.curl_easy_setopt_ptr(&curl, CURLOPT_URL, url.PtrBefore()); + if (!curl_code.ok() || curl_code.value() != CURLE_OK) { + LOG(FATAL) << "curl_easy_setopt_ptr failed: " << curl_code.status(); + } + + // Set the SSL certificate type to "PEM" + sapi::v::ConstCStr ssl_cert_type("PEM"); + curl_code = api.curl_easy_setopt_ptr(&curl, CURLOPT_SSLCERTTYPE, + ssl_cert_type.PtrBefore()); + if (!curl_code.ok() || curl_code.value() != CURLE_OK) { + LOG(FATAL) << "curl_easy_setopt_ptr failed: " << curl_code.status(); + } + + // Set the certificate for client authentication + sapi::v::ConstCStr sapi_ssl_certificate(ssl_certificate.c_str()); + curl_code = api.curl_easy_setopt_ptr(&curl, CURLOPT_SSLCERT, + sapi_ssl_certificate.PtrBefore()); + if (!curl_code.ok() || curl_code.value() != CURLE_OK) { + LOG(FATAL) << "curl_easy_setopt_ptr failed: " << curl_code.status(); + } + + // Set the private key for client authentication + sapi::v::ConstCStr sapi_ssl_key(ssl_key.c_str()); + curl_code = + api.curl_easy_setopt_ptr(&curl, CURLOPT_SSLKEY, sapi_ssl_key.PtrBefore()); + if (!curl_code.ok() || curl_code.value() != CURLE_OK) { + LOG(FATAL) << "curl_easy_setopt_ptr failed: " << curl_code.status(); + } + + // Set the password used to protect the private key + sapi::v::ConstCStr sapi_ssl_key_password(ssl_key_password.c_str()); + curl_code = api.curl_easy_setopt_ptr(&curl, CURLOPT_KEYPASSWD, + sapi_ssl_key_password.PtrBefore()); + if (!curl_code.ok() || curl_code.value() != CURLE_OK) { + LOG(FATAL) << "curl_easy_setopt_ptr failed: " << curl_code.status(); + } + + // Set the file with the certificates vaildating the server + sapi::v::ConstCStr sapi_ca_certificates(ca_certificates.c_str()); + curl_code = api.curl_easy_setopt_ptr(&curl, CURLOPT_CAINFO, + sapi_ca_certificates.PtrBefore()); + if (!curl_code.ok() || curl_code.value() != CURLE_OK) { + LOG(FATAL) << "curl_easy_setopt_ptr failed: " << curl_code.status(); + } + + // Verify the authenticity of the server + curl_code = api.curl_easy_setopt_long(&curl, CURLOPT_SSL_VERIFYPEER, 1L); + if (!curl_code.ok() || curl_code.value() != CURLE_OK) { + LOG(FATAL) << "curl_easy_setopt_long failed: " << curl_code.status(); + } + + // Perform the request + curl_code = api.curl_easy_perform(&curl); + if (!curl_code.ok() || curl_code.value() != CURLE_OK) { + LOG(FATAL) << "curl_easy_perform failed: " << curl_code.status(); + } + + // Cleanup curl easy handle + status = api.curl_easy_cleanup(&curl); + if (!status.ok()) { + LOG(FATAL) << "curl_easy_cleanup failed: " << status; + } + + // Cleanup curl + status = api.curl_global_cleanup(); + if (!status.ok()) { + LOG(FATAL) << "curl_global_cleanup failed: " << status; + } + + return EXIT_SUCCESS; +} diff --git a/oss-internship-2020/curl/examples/example4.cc b/oss-internship-2020/curl/examples/example4.cc new file mode 100644 index 0000000..9103866 --- /dev/null +++ b/oss-internship-2020/curl/examples/example4.cc @@ -0,0 +1,128 @@ +// Copyright 2020 Google LLC +// +// 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. + +// Sandboxed version of multi-poll.c +// HTTP GET request with polling + +#include + +#include "../sandbox.h" // NOLINT(build/include) +#include "curl_sapi.sapi.h" // NOLINT(build/include) +#include "sandboxed_api/util/flag.h" + +int main(int argc, char* argv[]) { + gflags::ParseCommandLineFlags(&argc, &argv, true); + google::InitGoogleLogging(argv[0]); + + absl::Status status; + + // Initialize sandbox2 and sapi + CurlSapiSandbox sandbox; + status = sandbox.Init(); + if (!status.ok()) { + LOG(FATAL) << "Couldn't initialize Sandboxed API: " << status; + } + CurlApi api(&sandbox); + + // Number of running handles + sapi::v::Int still_running(1); + + absl::StatusOr curl_code; + + // Initialize curl (CURL_GLOBAL_DEFAULT = 3) + curl_code = api.curl_global_init(3l); + if (!curl_code.ok() || curl_code.value() != CURLE_OK) { + LOG(FATAL) << "curl_global_init failed: " << curl_code.status(); + } + + // Initialize http_handle + absl::StatusOr curl_handle = api.curl_easy_init(); + if (!curl_handle.ok()) { + LOG(FATAL) << "curl_easy_init failed: " << curl_handle.status(); + } + sapi::v::RemotePtr http_handle(curl_handle.value()); + if (!http_handle.GetValue()) { + LOG(FATAL) << "curl_easy_init failed: http_handle is NULL"; + } + + // Specify URL to get + sapi::v::ConstCStr url("http://example.com"); + curl_code = + api.curl_easy_setopt_ptr(&http_handle, CURLOPT_URL, url.PtrBefore()); + if (!curl_code.ok() || curl_code.value() != CURLE_OK) { + LOG(FATAL) << "curl_easy_setopt_ptr failed: " << curl_code.status(); + } + + // Initialize multi_handle + absl::StatusOr curlm_handle = api.curl_multi_init(); + if (!curlm_handle.ok()) { + LOG(FATAL) << "curl_multi_init failed: " << curlm_handle.status(); + } + sapi::v::RemotePtr multi_handle(curlm_handle.value()); + if (!multi_handle.GetValue()) { + LOG(FATAL) << "curl_multi_init failed: multi_handle is NULL"; + } + + // Add http_handle to the multi stack + curl_code = api.curl_multi_add_handle(&multi_handle, &http_handle); + if (!curl_code.ok() || curl_code.value() != CURLE_OK) { + LOG(FATAL) << "curl_multi_add_handle failed: " << curl_code.status(); + } + + while (still_running.GetValue()) { + sapi::v::Int numfds(0); + + // Perform the request + curl_code = api.curl_multi_perform(&multi_handle, still_running.PtrBoth()); + if (!curl_code.ok() || curl_code.value() != CURLE_OK) { + LOG(FATAL) << "curl_mutli_perform failed: " << curl_code.status(); + } + + if (still_running.GetValue()) { + // Wait for an event or timeout + sapi::v::NullPtr null_ptr; + curl_code = api.curl_multi_poll_sapi(&multi_handle, &null_ptr, 0, 1000, + numfds.PtrBoth()); + if (!curl_code.ok() || curl_code.value() != CURLE_OK) { + LOG(FATAL) << "curl_multi_poll_sapi failed: " << curl_code.status(); + } + } + } + + // Remove http_handle from the multi stack + curl_code = api.curl_multi_remove_handle(&multi_handle, &http_handle); + if (!curl_code.ok() || curl_code.value() != CURLE_OK) { + LOG(FATAL) << "curl_multi_remove_handle failed: " << curl_code.status(); + } + + // Cleanup http_handle + status = api.curl_easy_cleanup(&http_handle); + if (!status.ok()) { + LOG(FATAL) << "curl_easy_cleanup failed: " << status; + } + + // Cleanup multi_handle + curl_code = api.curl_multi_cleanup(&multi_handle); + if (!curl_code.ok() || curl_code.value() != CURLE_OK) { + LOG(FATAL) << "curl_multi_cleanup failed: " << curl_code.status(); + } + + // Cleanup curl + status = api.curl_global_cleanup(); + if (!status.ok()) { + LOG(FATAL) << "curl_global_cleanup failed: " << status; + } + + return EXIT_SUCCESS; +} diff --git a/oss-internship-2020/curl/examples/example5.cc b/oss-internship-2020/curl/examples/example5.cc new file mode 100644 index 0000000..8cc6a16 --- /dev/null +++ b/oss-internship-2020/curl/examples/example5.cc @@ -0,0 +1,102 @@ +// Copyright 2020 Google LLC +// +// 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. + +// Sandboxed version of multithread.c +// Multithreaded HTTP GET requests + +#include + +#include + +#include "../sandbox.h" // NOLINT(build/include) + +void pull_one_url(const std::string& url, CurlApi& api) { + // Initialize the curl session + absl::StatusOr curl_handle = api.curl_easy_init(); + if (!curl_handle.ok()) { + LOG(FATAL) << "curl_easy_init failed: " << curl_handle.status(); + } + sapi::v::RemotePtr curl(curl_handle.value()); + if (!curl.GetValue()) { + LOG(FATAL) << "curl_easy_init failed: curl is NULL"; + } + + absl::StatusOr curl_code; + + // Specify URL to get + sapi::v::ConstCStr sapi_url(url.c_str()); + curl_code = + api.curl_easy_setopt_ptr(&curl, CURLOPT_URL, sapi_url.PtrBefore()); + if (!curl_code.ok() || curl_code.value() != CURLE_OK) { + LOG(FATAL) << "curl_easy_setopt_ptr failed: " << curl_code.status(); + } + + // Perform the request + curl_code = api.curl_easy_perform(&curl); + if (!curl_code.ok() || curl_code.value() != CURLE_OK) { + LOG(FATAL) << "curl_easy_perform failed: " << curl_code.status(); + } + + // Cleanup curl + absl::Status status = api.curl_easy_cleanup(&curl); + if (!status.ok()) { + LOG(FATAL) << "curl_easy_cleanup failed: " << status; + } +} + +const std::vector urls = { + "http://example.com", "http://example.edu", "http://example.net", + "http://example.org"}; + +int main(int argc, char* argv[]) { + gflags::ParseCommandLineFlags(&argc, &argv, true); + google::InitGoogleLogging(argv[0]); + + absl::Status status; + + // Initialize sandbox2 and sapi + CurlSapiSandbox sandbox; + status = sandbox.Init(); + if (!status.ok()) { + LOG(FATAL) << "Couldn't initialize Sandboxed API: " << status; + } + CurlApi api(&sandbox); + + absl::StatusOr curl_code; + + // Initialize curl (CURL_GLOBAL_DEFAULT = 3) + curl_code = api.curl_global_init(3l); + if (!curl_code.ok() || curl_code.value() != CURLE_OK) { + LOG(FATAL) << "curl_global_init failed: " << curl_code.status(); + } + + // Create the threads + std::vector threads; + for (auto& url : urls) { + threads.emplace_back(pull_one_url, std::ref(url), std::ref(api)); + } + + // Join the threads + for (auto& thread : threads) { + thread.join(); + } + + // Cleanup curl + status = api.curl_global_cleanup(); + if (!status.ok()) { + LOG(FATAL) << "curl_global_cleanup failed: " << status; + } + + return EXIT_SUCCESS; +} diff --git a/oss-internship-2020/curl/examples/example6.cc b/oss-internship-2020/curl/examples/example6.cc new file mode 100644 index 0000000..15fb1c2 --- /dev/null +++ b/oss-internship-2020/curl/examples/example6.cc @@ -0,0 +1,72 @@ +// Copyright 2020 Google LLC +// +// 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. + +// Sandboxed version of simple.c using transactions +// Simple HTTP GET request + +#include + +#include "../sandbox.h" // NOLINT(build/include) +#include "sandboxed_api/transaction.h" + +class CurlTransaction : public sapi::Transaction { + public: + explicit CurlTransaction(std::unique_ptr sandbox) + : sapi::Transaction(std::move(sandbox)) { + sapi::Transaction::SetTimeLimit(kTimeOutVal); + } + + private: + // Default timeout value for each transaction run. + static constexpr time_t kTimeOutVal = 2; + + // The main processing function. + absl::Status Main() override; +}; + +absl::Status CurlTransaction::Main() { + CurlApi api(sandbox()); + + // Initialize the curl session + SAPI_ASSIGN_OR_RETURN(void* curl_remote, api.curl_easy_init()); + sapi::v::RemotePtr curl(curl_remote); + TRANSACTION_FAIL_IF_NOT(curl.GetValue(), "curl_easy_init failed"); + + // Specify URL to get + sapi::v::ConstCStr url("http://example.com"); + SAPI_ASSIGN_OR_RETURN( + int setopt_url_code, + api.curl_easy_setopt_ptr(&curl, CURLOPT_URL, url.PtrBefore())); + TRANSACTION_FAIL_IF_NOT(setopt_url_code == CURLE_OK, + "curl_easy_setopt_ptr failed"); + + // Perform the request + SAPI_ASSIGN_OR_RETURN(int perform_code, api.curl_easy_perform(&curl)); + TRANSACTION_FAIL_IF_NOT(setopt_url_code == CURLE_OK, + "curl_easy_perform failed"); + + // Cleanup curl + TRANSACTION_FAIL_IF_NOT(api.curl_easy_cleanup(&curl).ok(), + "curl_easy_cleanup failed"); + + return absl::OkStatus(); +} + +int main(int argc, char* argv[]) { + CurlTransaction curl{std::make_unique()}; + absl::Status status = curl.Run(); + CHECK(status.ok()) << "CurlTransaction failed"; + + return EXIT_SUCCESS; +} diff --git a/oss-internship-2020/curl/sandbox.h b/oss-internship-2020/curl/sandbox.h new file mode 100644 index 0000000..914617f --- /dev/null +++ b/oss-internship-2020/curl/sandbox.h @@ -0,0 +1,67 @@ +// Copyright 2020 Google LLC +// +// 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. + +#ifndef SANDBOX_H_ +#define SANDBOX_H_ + +#include +#include // For mmap arguments +#include + +#include + +#include "curl_sapi.sapi.h" // NOLINT(build/include) +#include "sandboxed_api/sandbox2/util/bpf_helper.h" + +class CurlSapiSandbox : public CurlSandbox { + protected: + std::unique_ptr ModifyPolicy( + sandbox2::PolicyBuilder* policy_builder) override { + // Return a new policy + return (*policy_builder) + .AllowDynamicStartup() + .AllowExit() + .AllowFork() + .AllowFutexOp(FUTEX_WAKE_PRIVATE) + .AllowMmap() + .AllowOpen() + .AllowRead() + .AllowSafeFcntl() + .AllowWrite() + .AllowSyscalls({ + __NR_accept, + __NR_access, + __NR_bind, + __NR_connect, + __NR_getpeername, + __NR_getsockname, + __NR_getsockopt, + __NR_ioctl, + __NR_listen, + __NR_madvise, + __NR_poll, + __NR_recvfrom, + __NR_recvmsg, + __NR_sendmmsg, + __NR_sendto, + __NR_setsockopt, + __NR_socket, + }) + .AllowUnrestrictedNetworking() + .AddDirectory("/lib") + .BuildOrDie(); + } +}; + +#endif // SANDBOX_H_ diff --git a/oss-internship-2020/curl/tests/CMakeLists.txt b/oss-internship-2020/curl/tests/CMakeLists.txt new file mode 100644 index 0000000..6fd4556 --- /dev/null +++ b/oss-internship-2020/curl/tests/CMakeLists.txt @@ -0,0 +1,28 @@ +# Copyright 2020 Google LLC +# +# 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. + +include(GoogleTest) + +add_executable(tests + test_utils.h + test_utils.cc + tests.cc +) + +target_link_libraries(tests + curl_sapi sapi::sapi + gtest gmock gtest_main +) + +gtest_discover_tests(tests) diff --git a/oss-internship-2020/curl/tests/test_utils.cc b/oss-internship-2020/curl/tests/test_utils.cc new file mode 100644 index 0000000..8ea2897 --- /dev/null +++ b/oss-internship-2020/curl/tests/test_utils.cc @@ -0,0 +1,301 @@ +// Copyright 2020 Google LLC +// +// 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. + +#include "test_utils.h" // NOLINT(build/include) + +#include +#include +#include +#include +#include +#include +#include + +#include +#include // NOLINT(build/c++11) + +int CurlTestUtils::port_; +std::thread CurlTestUtils::server_thread_; + +absl::Status CurlTestUtils::CurlTestSetUp() { + // Initialize sandbox2 and sapi + sandbox_ = std::make_unique(); + absl::Status init = sandbox_->Init(); + if (!init.ok()) { + return init; + } + api_ = std::make_unique(sandbox_.get()); + + // Initialize curl + absl::StatusOr curl_handle = api_->curl_easy_init(); + if (!curl_handle.ok()) { + return curl_handle.status(); + } + if (!curl_handle.value()) { + return absl::UnavailableError("curl_easy_init returned NULL "); + } + curl_ = std::make_unique(curl_handle.value()); + + absl::StatusOr curl_code; + + // Specify request URL + sapi::v::ConstCStr sapi_url(kUrl.data()); + curl_code = api_->curl_easy_setopt_ptr(curl_.get(), CURLOPT_URL, + sapi_url.PtrBefore()); + if (!curl_code.ok()) { + return curl_code.status(); + } + if (curl_code.value() != CURLE_OK) { + return absl::UnavailableError( + "curl_easy_setopt_ptr returned with the error code " + + curl_code.value()); + } + + // Set port + curl_code = api_->curl_easy_setopt_long(curl_.get(), CURLOPT_PORT, port_); + if (!curl_code.ok()) { + return curl_code.status(); + } + if (curl_code.value() != CURLE_OK) { + return absl::UnavailableError( + "curl_easy_setopt_long returned with the error code " + + curl_code.value()); + } + + // Generate pointer to the WriteToMemory callback + void* function_ptr; + absl::Status symbol = + sandbox_->rpc_channel()->Symbol("WriteToMemory", &function_ptr); + if (!symbol.ok()) { + return symbol; + } + sapi::v::RemotePtr remote_function_ptr(function_ptr); + + // Set WriteToMemory as the write function + curl_code = api_->curl_easy_setopt_ptr(curl_.get(), CURLOPT_WRITEFUNCTION, + &remote_function_ptr); + if (!curl_code.ok()) { + return curl_code.status(); + } + if (curl_code.value() != CURLE_OK) { + return absl::UnavailableError( + "curl_easy_setopt_ptr returned with the error code " + + curl_code.value()); + } + + // Pass memory chunk object to the callback + chunk_ = std::make_unique(0); + curl_code = api_->curl_easy_setopt_ptr(curl_.get(), CURLOPT_WRITEDATA, + chunk_->PtrBoth()); + if (!curl_code.ok()) { + return curl_code.status(); + } + if (curl_code.value() != CURLE_OK) { + return absl::UnavailableError( + "curl_easy_setopt_ptr returned with the error code " + + curl_code.value()); + } + + return absl::OkStatus(); +} + +absl::Status CurlTestUtils::CurlTestTearDown() { + // Cleanup curl + return api_->curl_easy_cleanup(curl_.get()); +} + +absl::StatusOr CurlTestUtils::PerformRequest() { + // Perform the request + absl::StatusOr curl_code = api_->curl_easy_perform(curl_.get()); + if (!curl_code.ok()) { + return curl_code.status(); + } + if (curl_code.value() != CURLE_OK) { + return absl::UnavailableError( + "curl_easy_perform returned with the error code " + curl_code.value()); + } + + // Get pointer to the memory chunk + absl::Status status = sandbox_->TransferFromSandboxee(chunk_.get()); + if (!status.ok()) { + return status; + } + + return std::string{reinterpret_cast(chunk_->GetData())}; +} + +namespace { + +// Read the socket until str is completely read +std::string ReadUntil(const int socket, const std::string& str, + const size_t max_request_size) { + std::string str_read; + str_read.reserve(max_request_size); + + // Read one char at a time until str is suffix of buf + while (!absl::EndsWith(str_read, str)) { + char next_char; + if (str_read.size() >= max_request_size || + read(socket, &next_char, 1) < 1) { + return ""; + } + str_read += next_char; + } + + return str_read; +} + +// Parse HTTP headers to return the Content-Length +ssize_t GetContentLength(const std::string& headers) { + // Find the Content-Length header + std::string::size_type length_header_start = headers.find("Content-Length: "); + + // There is no Content-Length field + if (length_header_start == std::string::npos) { + return 0; + } + + // Find Content-Length string + std::string::size_type length_start = + length_header_start + std::string{"Content-Length: "}.size(); + std::string::size_type length_bytes = + headers.find("\r\n", length_start) - length_start; + + // length_bytes exceeds maximum + if (length_bytes >= 64) { + return -1; + } + + // Convert string to int and return + return std::stoi(headers.substr(length_start, length_bytes)); +} + +// Read exactly content_bytes from the socket +std::string ReadExact(int socket, size_t content_bytes) { + std::string str_read; + str_read.reserve(content_bytes); + + // Read one char at a time until all chars are read + while (str_read.size() < content_bytes) { + char next_char; + if (read(socket, &next_char, 1) < 1) { + return ""; + } + str_read += next_char; + } + + return str_read; +} + +// Listen on the socket and answer back to requests +void ServerLoop(int listening_socket, sockaddr_in socket_address) { + socklen_t socket_address_size = sizeof(socket_address); + + // Listen on the socket (maximum 1 connection) + if (listen(listening_socket, 1) == -1) { + return; + } + + // Keep accepting connections until the thread is terminated + // (i.e. server_thread_ is assigned to a new thread or destroyed) + for (;;) { + // File descriptor to the connection socket + // This blocks the thread until a connection is established + int accepted_socket = + accept(listening_socket, reinterpret_cast(&socket_address), + reinterpret_cast(&socket_address_size)); + if (accepted_socket == -1) { + return; + } + + constexpr int kMaxRequestSize = 4096; + + // Read until the end of the headers + std::string headers = + ReadUntil(accepted_socket, "\r\n\r\n", kMaxRequestSize); + + if (headers == "") { + close(accepted_socket); + return; + } + + // Get the length of the request content + ssize_t content_length = GetContentLength(headers); + if (content_length > kMaxRequestSize - headers.size() || + content_length < 0) { + close(accepted_socket); + return; + } + + // Read the request content + std::string content = ReadExact(accepted_socket, content_length); + + // Prepare a response for the request + std::string http_response = + "HTTP/1.1 200 OK\nContent-Type: text/plain\nContent-Length: "; + + if (headers.substr(0, 3) == "GET") { + http_response += "2\r\n\r\nOK"; + + } else if (headers.substr(0, 4) == "POST") { + http_response += + std::to_string(content.size()) + "\r\n\r\n" + std::string{content}; + + } else { + close(accepted_socket); + return; + } + + // Ignore any errors, the connection will be closed anyway + write(accepted_socket, http_response.c_str(), http_response.size()); + + // Close the socket + close(accepted_socket); + } +} + +} // namespace + +void CurlTestUtils::StartMockServer() { + // Get the socket file descriptor + int listening_socket = socket(AF_INET, SOCK_STREAM, 0); + + // Create the socket address object + // The port is set to 0, meaning that it will be auto assigned + // Only local connections can access this socket + sockaddr_in socket_address{AF_INET, 0, htonl(INADDR_LOOPBACK)}; + socklen_t socket_address_size = sizeof(socket_address); + if (listening_socket == -1) { + return; + } + + // Bind the file descriptor to the socket address object + if (bind(listening_socket, reinterpret_cast(&socket_address), + socket_address_size) == -1) { + return; + } + + // Assign an available port to the socket address object + if (getsockname(listening_socket, + reinterpret_cast(&socket_address), + &socket_address_size) == -1) { + return; + } + + // Get the port number + port_ = ntohs(socket_address.sin_port); + + // Set server_thread_ operation to socket listening + server_thread_ = std::thread(ServerLoop, listening_socket, socket_address); +} diff --git a/oss-internship-2020/curl/tests/test_utils.h b/oss-internship-2020/curl/tests/test_utils.h new file mode 100644 index 0000000..fa74769 --- /dev/null +++ b/oss-internship-2020/curl/tests/test_utils.h @@ -0,0 +1,55 @@ +// Copyright 2020 Google LLC +// +// 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. + +#ifndef TESTS_H_ +#define TESTS_H_ + +#include "../sandbox.h" // NOLINT(build/include) +#include "curl_sapi.sapi.h" // NOLINT(build/include) +#include "gtest/gtest.h" +#include "sandboxed_api/util/flag.h" +#include "sandboxed_api/util/status_matchers.h" + +// Helper class that can be used to test Curl Sandboxed +class CurlTestUtils { + protected: + // Initialize and set up the curl handle + absl::Status CurlTestSetUp(); + // Clean up the curl handle + absl::Status CurlTestTearDown(); + + // Perform a request to the mock server, return the response + absl::StatusOr PerformRequest(); + + // Start a mock server (only once) that will manage connections for the tests + // The server listens on a port asynchronously by creating a thread + // The port number is stored in port_ + // Responds with "OK" to a GET request + // Responds with the POST request fields to a POST request + static void StartMockServer(); + + std::unique_ptr sandbox_; + std::unique_ptr api_; + std::unique_ptr curl_; + + static std::thread server_thread_; + + static constexpr absl::string_view kUrl = "http://127.0.0.1/"; + static int port_; + + private: + std::unique_ptr chunk_; +}; + +#endif // TESTS_H_ diff --git a/oss-internship-2020/curl/tests/tests.cc b/oss-internship-2020/curl/tests/tests.cc new file mode 100644 index 0000000..c786169 --- /dev/null +++ b/oss-internship-2020/curl/tests/tests.cc @@ -0,0 +1,138 @@ +// Copyright 2020 Google LLC +// +// 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. + +#include "test_utils.h" // NOLINT(build/include) + +class CurlTest : public CurlTestUtils, public ::testing::Test { + protected: + void SetUp() override { + // Start mock server, get port number and check for any error + StartMockServer(); + ASSERT_TRUE(server_thread_.joinable()); + ASSERT_TRUE(CurlTestSetUp().ok()); + } + + void TearDown() override { + ASSERT_TRUE(CurlTestTearDown().ok()); + // Detach the server thread + server_thread_.detach(); + } +}; + +TEST_F(CurlTest, EffectiveUrl) { + sapi::v::RemotePtr effective_url_ptr(nullptr); + + ASSERT_TRUE(PerformRequest().ok()); + + // Get effective URL + SAPI_ASSERT_OK_AND_ASSIGN( + int getinfo_code, + api_->curl_easy_getinfo_ptr(curl_.get(), CURLINFO_EFFECTIVE_URL, + effective_url_ptr.PtrBoth())); + ASSERT_EQ(getinfo_code, CURLE_OK); + + // Store effective URL in a string + SAPI_ASSERT_OK_AND_ASSIGN(std::string effective_url, + sandbox_->GetCString(sapi::v::RemotePtr( + effective_url_ptr.GetPointedVar()))); + + // Compare effective URL with original URL + ASSERT_EQ(effective_url, kUrl); +} + +TEST_F(CurlTest, EffectivePort) { + sapi::v::Int effective_port; + + ASSERT_TRUE(PerformRequest().ok()); + + // Get effective port + SAPI_ASSERT_OK_AND_ASSIGN(int getinfo_code, api_->curl_easy_getinfo_ptr( + curl_.get(), CURLINFO_PRIMARY_PORT, + effective_port.PtrBoth())); + ASSERT_EQ(getinfo_code, CURLE_OK); + + // Compare effective port with port set by the mock server + ASSERT_EQ(effective_port.GetValue(), port_); +} + +TEST_F(CurlTest, ResponseCode) { + sapi::v::Int response_code; + + ASSERT_TRUE(PerformRequest().ok()); + + // Get response code + SAPI_ASSERT_OK_AND_ASSIGN( + int getinfo_code, + api_->curl_easy_getinfo_ptr(curl_.get(), CURLINFO_RESPONSE_CODE, + response_code.PtrBoth())); + ASSERT_EQ(getinfo_code, CURLE_OK); + + // Check response code + ASSERT_EQ(response_code.GetValue(), 200); +} + +TEST_F(CurlTest, ContentType) { + sapi::v::RemotePtr content_type_ptr(nullptr); + + ASSERT_TRUE(PerformRequest().ok()); + + // Get effective URL + SAPI_ASSERT_OK_AND_ASSIGN(int getinfo_code, api_->curl_easy_getinfo_ptr( + curl_.get(), CURLINFO_CONTENT_TYPE, + content_type_ptr.PtrBoth())); + ASSERT_EQ(getinfo_code, CURLE_OK); + + // Store content type in a string + SAPI_ASSERT_OK_AND_ASSIGN(std::string content_type, + sandbox_->GetCString(sapi::v::RemotePtr( + content_type_ptr.GetPointedVar()))); + + // Compare content type with "text/plain" + ASSERT_EQ(content_type, "text/plain"); +} + +TEST_F(CurlTest, GETResponse) { + SAPI_ASSERT_OK_AND_ASSIGN(std::string response, PerformRequest()); + + // Compare response with expected response + ASSERT_EQ(response, "OK"); +} + +TEST_F(CurlTest, POSTResponse) { + sapi::v::ConstCStr post_fields("postfields"); + + // Set request method to POST + SAPI_ASSERT_OK_AND_ASSIGN(int setopt_post, api_->curl_easy_setopt_long( + curl_.get(), CURLOPT_POST, 1l)); + ASSERT_EQ(setopt_post, CURLE_OK); + + // Set the size of the POST fields + SAPI_ASSERT_OK_AND_ASSIGN( + int setopt_post_fields_size, + api_->curl_easy_setopt_long(curl_.get(), CURLOPT_POSTFIELDSIZE, + post_fields.GetSize())); + ASSERT_EQ(setopt_post_fields_size, CURLE_OK); + + // Set the POST fields + SAPI_ASSERT_OK_AND_ASSIGN( + int setopt_post_fields, + api_->curl_easy_setopt_ptr(curl_.get(), CURLOPT_POSTFIELDS, + post_fields.PtrBefore())); + ASSERT_EQ(setopt_post_fields, CURLE_OK); + + SAPI_ASSERT_OK_AND_ASSIGN(std::string response, PerformRequest()); + + // Compare response with expected response + ASSERT_EQ(std::string(post_fields.GetData()), response); +}