From 6c9f9cc1dc7231507c7442e37ee44238c3d8f4a3 Mon Sep 17 00:00:00 2001
From: Diego Nehab <1635557+diegonehab@users.noreply.github.com>
Date: Mon, 13 Jan 2025 13:14:24 +0000
Subject: [PATCH 01/82] refactor: unify mock_pma_entry implementations
---
src/mock-pma-entry.h | 165 +++++++++++++++++++++++++++++
src/pma.cpp | 12 ---
src/pma.h | 18 ----
src/replay-step-state-access.h | 141 ++----------------------
uarch/uarch-machine-state-access.h | 162 +++-------------------------
5 files changed, 187 insertions(+), 311 deletions(-)
create mode 100644 src/mock-pma-entry.h
diff --git a/src/mock-pma-entry.h b/src/mock-pma-entry.h
new file mode 100644
index 000000000..883bd6725
--- /dev/null
+++ b/src/mock-pma-entry.h
@@ -0,0 +1,165 @@
+// Copyright Cartesi and individual authors (see AUTHORS)
+// SPDX-License-Identifier: LGPL-3.0-or-later
+//
+// This program is free software: you can redistribute it and/or modify it under
+// the terms of the GNU Lesser General Public License as published by the Free
+// Software Foundation, either version 3 of the License, or (at your option) any
+// later version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT ANY
+// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+// PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License along
+// with this program (see COPYING). If not, see .
+//
+
+#ifndef MOCK_PMA_ENTRY_H
+#define MOCK_PMA_ENTRY_H
+
+#include "clint.h"
+#include "htif.h"
+#include "plic.h"
+#include "pma-constants.h"
+#include "shadow-state.h"
+#include "shadow-tlb.h"
+
+namespace cartesi {
+
+class mock_pma_entry {
+public:
+ struct flags {
+ bool M;
+ bool IO;
+ bool E;
+ bool R;
+ bool W;
+ bool X;
+ bool IR;
+ bool IW;
+ PMA_ISTART_DID DID;
+ };
+
+private:
+ uint64_t m_pma_index;
+ uint64_t m_start;
+ uint64_t m_length;
+ flags m_flags;
+ const pma_driver *m_driver{nullptr};
+
+ static constexpr flags split_flags(uint64_t istart) {
+ flags f{};
+ f.M = ((istart & PMA_ISTART_M_MASK) >> PMA_ISTART_M_SHIFT) != 0;
+ f.IO = ((istart & PMA_ISTART_IO_MASK) >> PMA_ISTART_IO_SHIFT) != 0;
+ f.E = ((istart & PMA_ISTART_E_MASK) >> PMA_ISTART_E_SHIFT) != 0;
+ f.R = ((istart & PMA_ISTART_R_MASK) >> PMA_ISTART_R_SHIFT) != 0;
+ f.W = ((istart & PMA_ISTART_W_MASK) >> PMA_ISTART_W_SHIFT) != 0;
+ f.X = ((istart & PMA_ISTART_X_MASK) >> PMA_ISTART_X_SHIFT) != 0;
+ f.IR = ((istart & PMA_ISTART_IR_MASK) >> PMA_ISTART_IR_SHIFT) != 0;
+ f.IW = ((istart & PMA_ISTART_IW_MASK) >> PMA_ISTART_IW_SHIFT) != 0;
+ f.DID = static_cast((istart & PMA_ISTART_DID_MASK) >> PMA_ISTART_DID_SHIFT);
+ return f;
+ }
+
+public:
+ template
+ mock_pma_entry(uint64_t pma_index, uint64_t istart, uint64_t ilength, ERR_F errf) :
+ m_pma_index{pma_index},
+ m_start{istart & PMA_ISTART_START_MASK},
+ m_length{ilength},
+ m_flags{split_flags(istart)} {
+ if (m_flags.IO) {
+ switch (m_flags.DID) {
+ case PMA_ISTART_DID::shadow_state:
+ m_driver = &shadow_state_driver;
+ break;
+ case PMA_ISTART_DID::shadow_TLB:
+ m_driver = &shadow_tlb_driver;
+ break;
+ case PMA_ISTART_DID::CLINT:
+ m_driver = &clint_driver;
+ break;
+ case PMA_ISTART_DID::PLIC:
+ m_driver = &plic_driver;
+ break;
+ case PMA_ISTART_DID::HTIF:
+ m_driver = &htif_driver;
+ break;
+ default:
+ errf("unsupported device in mock_pma_entry");
+ break;
+ }
+ }
+ }
+
+ uint64_t get_index() const {
+ return m_pma_index;
+ }
+
+ flags get_flags() const {
+ return m_flags;
+ }
+
+ uint64_t get_start() const {
+ return m_start;
+ }
+
+ uint64_t get_length() const {
+ return m_length;
+ }
+
+ bool get_istart_M() const {
+ return m_flags.M;
+ }
+
+ bool get_istart_IO() const {
+ return m_flags.IO;
+ }
+
+ bool get_istart_E() const {
+ return m_flags.E;
+ }
+
+ bool get_istart_R() const {
+ return m_flags.R;
+ }
+
+ bool get_istart_W() const {
+ return m_flags.W;
+ }
+
+ bool get_istart_X() const {
+ return m_flags.X;
+ }
+
+ bool get_istart_IR() const {
+ return m_flags.IR;
+ }
+
+ const auto *get_driver() const {
+ return m_driver;
+ }
+
+ const auto &get_device_noexcept() const {
+ return *this;
+ }
+
+ static void *get_context() {
+ return nullptr;
+ }
+
+ // NOLINTNEXTLINE(readability-convert-member-functions-to-static)
+ void mark_dirty_page(uint64_t address_in_range) {
+ (void) address_in_range;
+ // Dummy implementation.
+ }
+};
+
+template
+static inline mock_pma_entry make_mock_pma_entry(uint64_t index, uint64_t istart, uint64_t ilength, ERR_F errf) {
+ return mock_pma_entry{index, istart, ilength, errf};
+}
+
+} // namespace cartesi
+
+#endif
diff --git a/src/pma.cpp b/src/pma.cpp
index 45d29a5e5..9a112b4dd 100644
--- a/src/pma.cpp
+++ b/src/pma.cpp
@@ -73,11 +73,6 @@ pma_memory::pma_memory(const std::string &description, uint64_t length, const ca
}
}
-pma_memory::pma_memory(const std::string & /*description*/, uint64_t length, const mockd & /*m*/) :
- m_length{length},
- m_host_memory{nullptr},
- m_mmapped{false} {}
-
pma_memory::pma_memory(const std::string &description, uint64_t length, const std::string &path, const callocd &c) :
pma_memory{description, length, c} {
// Try to load image file, if any
@@ -259,13 +254,6 @@ pma_entry make_callocd_memory_pma_entry(const std::string &description, uint64_t
memory_peek};
}
-pma_entry make_mockd_memory_pma_entry(const std::string &description, uint64_t start, uint64_t length) {
- if (length == 0) {
- throw std::invalid_argument{description + " length cannot be zero"s};
- }
- return pma_entry{description, start, length, pma_memory{description, length, pma_memory::mockd{}}, pma_peek_error};
-}
-
pma_entry make_device_pma_entry(const std::string &description, uint64_t start, uint64_t length, pma_peek peek,
const pma_driver *driver, void *context) {
if (length == 0) {
diff --git a/src/pma.h b/src/pma.h
index 2224cfdcf..7c2ae90aa 100644
--- a/src/pma.h
+++ b/src/pma.h
@@ -123,9 +123,6 @@ class pma_memory final {
/// \brief Calloc'd range data (just a tag).
struct callocd {};
- /// \brief Mock'd range data (just a tag).
- struct mockd {};
-
/// \brief Constructor for calloc'd ranges.
/// \param description Informative description of PMA entry for use in error messages
/// \param length Length of range.
@@ -139,12 +136,6 @@ class pma_memory final {
/// \param c Calloc'd range data (just a tag).
pma_memory(const std::string &description, uint64_t length, const callocd &c);
- /// \brief Constructor for mock ranges.
- /// \param description Informative description of PMA entry for use in error messages
- /// \param length Length of range.
- /// \param m Mock'd range data (just a tag).
- pma_memory(const std::string &description, uint64_t length, const mockd &m);
-
/// \brief No copy constructor
pma_memory(const pma_memory &other) = delete;
@@ -551,15 +542,6 @@ pma_entry make_callocd_memory_pma_entry(const std::string &description, uint64_t
pma_entry make_mmapd_memory_pma_entry(const std::string &description, uint64_t start, uint64_t length,
const std::string &path, bool shared);
-/// \brief Creates a PMA entry for a new mock memory region (no allocation).
-/// \param description Informative description of PMA entry for use in error messages
-/// \param start Start of physical memory range in the target address
-/// space on which to map the memory region.
-/// \param length Length of physical memory range in the
-/// target address space on which to map the memory region.
-/// \returns Corresponding PMA entry
-pma_entry make_mockd_memory_pma_entry(const std::string &description, uint64_t start, uint64_t length);
-
/// \brief Creates a PMA entry for a new memory-mapped IO device.
/// \param description Informative description of PMA entry for use in error messages
/// \param start Start of physical memory range in the target address
diff --git a/src/replay-step-state-access.h b/src/replay-step-state-access.h
index b8d61d3d2..6f1162223 100644
--- a/src/replay-step-state-access.h
+++ b/src/replay-step-state-access.h
@@ -20,12 +20,10 @@
#include
#include
-#include "clint.h"
#include "compiler-defines.h"
#include "device-state-access.h"
-#include "htif.h"
#include "i-state-access.h"
-#include "plic.h"
+#include "mock-pma-entry.h"
#include "pma-constants.h"
#include "replay-step-state-access-interop.h"
#include "riscv-constants.h"
@@ -42,136 +40,6 @@ namespace cartesi {
// \file this code is designed to be compiled for a free-standing environment.
// Environment-specific functions have the prefix "interop_" and are declared in "replay-step-state-access-interop.h"
-//??D can we merge this calss with uarch_pma_entry in uarch/uarch-machine-state-access.h ?
-//??D maybe implement base class in pma.h and subclass here if there is some minor difference?
-class mock_pma_entry final {
-public:
- struct flags {
- bool M;
- bool IO;
- bool E;
- bool R;
- bool W;
- bool X;
- bool IR;
- bool IW;
- PMA_ISTART_DID DID;
- };
-
-private:
- uint64_t m_pma_index;
- uint64_t m_start;
- uint64_t m_length;
- flags m_flags;
- const pma_driver *m_driver{nullptr};
-
- static constexpr flags split_flags(uint64_t istart) {
- flags f{};
- f.M = ((istart & PMA_ISTART_M_MASK) >> PMA_ISTART_M_SHIFT) != 0;
- f.IO = ((istart & PMA_ISTART_IO_MASK) >> PMA_ISTART_IO_SHIFT) != 0;
- f.E = ((istart & PMA_ISTART_E_MASK) >> PMA_ISTART_E_SHIFT) != 0;
- f.R = ((istart & PMA_ISTART_R_MASK) >> PMA_ISTART_R_SHIFT) != 0;
- f.W = ((istart & PMA_ISTART_W_MASK) >> PMA_ISTART_W_SHIFT) != 0;
- f.X = ((istart & PMA_ISTART_X_MASK) >> PMA_ISTART_X_SHIFT) != 0;
- f.IR = ((istart & PMA_ISTART_IR_MASK) >> PMA_ISTART_IR_SHIFT) != 0;
- f.IW = ((istart & PMA_ISTART_IW_MASK) >> PMA_ISTART_IW_SHIFT) != 0;
- f.DID = static_cast((istart & PMA_ISTART_DID_MASK) >> PMA_ISTART_DID_SHIFT);
- return f;
- }
-
-public:
- mock_pma_entry(uint64_t pma_index, uint64_t istart, uint64_t ilength) :
- m_pma_index{pma_index},
- m_start{istart & PMA_ISTART_START_MASK},
- m_length{ilength},
- m_flags{split_flags(istart)} {
- if (m_flags.IO) {
- switch (m_flags.DID) {
- case PMA_ISTART_DID::shadow_state:
- m_driver = &shadow_state_driver;
- break;
- case PMA_ISTART_DID::shadow_TLB:
- m_driver = &shadow_tlb_driver;
- break;
- case PMA_ISTART_DID::CLINT:
- m_driver = &clint_driver;
- break;
- case PMA_ISTART_DID::PLIC:
- m_driver = &plic_driver;
- break;
- case PMA_ISTART_DID::HTIF:
- m_driver = &htif_driver;
- break;
- default:
- interop_throw_runtime_error("unsupported device in build_mock_pma_entry");
- break;
- }
- }
- }
-
- uint64_t get_index() const {
- return m_pma_index;
- }
-
- flags get_flags() const {
- return m_flags;
- }
-
- uint64_t get_start() const {
- return m_start;
- }
-
- uint64_t get_length() const {
- return m_length;
- }
-
- bool get_istart_M() const {
- return m_flags.M;
- }
-
- bool get_istart_IO() const {
- return m_flags.IO;
- }
-
- bool get_istart_E() const {
- return m_flags.E;
- }
-
- bool get_istart_R() const {
- return m_flags.R;
- }
-
- bool get_istart_W() const {
- return m_flags.W;
- }
-
- bool get_istart_X() const {
- return m_flags.X;
- }
-
- bool get_istart_IR() const {
- return m_flags.IR;
- }
-
- const auto *get_driver() const {
- return m_driver;
- }
-
- const auto &get_device_noexcept() const {
- return *this;
- }
-
- static void *get_context() {
- return nullptr;
- }
-
- // NOLINTNEXTLINE(readability-convert-member-functions-to-static)
- void mark_dirty_page(uint64_t address_in_range) {
- (void) address_in_range;
- // Dummy implementation.
- }
-};
-
// \brief checks if a buffer is large enough to hold a data block of N elements of size S starting at a given offset
// \param max The maximum offset allowed
// \param current The current offset
@@ -896,8 +764,11 @@ class replay_step_state_access : public i_state_access(index);
+ if (!m_pmas[i]) {
+ m_pmas[i] =
+ make_mock_pma_entry(index, istart, ilength, [](const char *err) { interop_throw_runtime_error(err); });
}
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
return m_context.pmas[index].value();
diff --git a/uarch/uarch-machine-state-access.h b/uarch/uarch-machine-state-access.h
index aa9346eb9..d43c64d86 100644
--- a/uarch/uarch-machine-state-access.h
+++ b/uarch/uarch-machine-state-access.h
@@ -19,10 +19,9 @@
#include "uarch-runtime.h" // must be included first, because of assert
-#include "clint.h"
-#include "plic.h"
+#define MOCK_THROW_RUNTIME_ERROR(err) abort()
+#include "mock-pma-entry.h"
#include "device-state-access.h"
-#include "htif.h"
#include "i-state-access.h"
#include "pma-constants.h"
#include "riscv-constants.h"
@@ -50,142 +49,9 @@ static void raw_write_memory(uint64_t paddr, T val) {
*p = val;
}
-//??D can we merge this class with the mock_pma_entry in src/replay-step-state-access.h ?
-//??D maybe implement base class in pma.h and subclass here if there is some minor difference?
-class uarch_pma_entry final {
-public:
- struct flags {
- bool M{};
- bool IO{};
- bool E{};
- bool R{};
- bool W{};
- bool X{};
- bool IR{};
- bool IW{};
- PMA_ISTART_DID DID{PMA_ISTART_DID::memory};
- };
-
-private:
-
- uint64_t m_pma_index;
- uint64_t m_start;
- uint64_t m_length;
- flags m_flags;
- const pma_driver *m_driver{nullptr};
-
- static constexpr flags split_flags(uint64_t istart) {
- flags f{};
- f.M = (((istart & PMA_ISTART_M_MASK) >> PMA_ISTART_M_SHIFT) != 0);
- f.IO = (((istart & PMA_ISTART_IO_MASK) >> PMA_ISTART_IO_SHIFT) != 0);
- f.E = (((istart & PMA_ISTART_E_MASK) >> PMA_ISTART_E_SHIFT) != 0);
- f.R = (((istart & PMA_ISTART_R_MASK) >> PMA_ISTART_R_SHIFT) != 0);
- f.W = (((istart & PMA_ISTART_W_MASK) >> PMA_ISTART_W_SHIFT) != 0);
- f.X = (((istart & PMA_ISTART_X_MASK) >> PMA_ISTART_X_SHIFT) != 0);
- f.IR = (((istart & PMA_ISTART_IR_MASK) >> PMA_ISTART_IR_SHIFT) != 0);
- f.IW = (((istart & PMA_ISTART_IW_MASK) >> PMA_ISTART_IW_SHIFT) != 0);
- f.DID = static_cast((istart & PMA_ISTART_DID_MASK) >> PMA_ISTART_DID_SHIFT);
- return f;
- }
-
-public:
-
- uarch_pma_entry(uint64_t pma_index, uint64_t istart, uint64_t ilength) :
- m_pma_index{pma_index},
- m_start{istart & PMA_ISTART_START_MASK},
- m_length{ilength},
- m_flags{split_flags(istart)} {
- if (m_flags.IO) {
- switch (m_flags.DID) {
- case PMA_ISTART_DID::shadow_state:
- m_driver = &shadow_state_driver;
- break;
- case PMA_ISTART_DID::shadow_TLB:
- m_driver = &shadow_tlb_driver;
- break;
- case PMA_ISTART_DID::CLINT:
- m_driver = &clint_driver;
- break;
- case PMA_ISTART_DID::PLIC:
- m_driver = &plic_driver;
- break;
- case PMA_ISTART_DID::HTIF:
- m_driver = &htif_driver;
- break;
- default:
- // Other unsupported device in uarch (eg. VirtIO)
- abort();
- break;
- }
- }
- }
-
- uint64_t get_index() const {
- return m_pma_index;
- }
-
- flags get_flags() const {
- return m_flags;
- }
-
- uint64_t get_start() const {
- return m_start;
- }
-
- uint64_t get_length() const {
- return m_length;
- }
-
- bool get_istart_M() const {
- return m_flags.M;
- }
-
- bool get_istart_IO() const {
- return m_flags.IO;
- }
-
- bool get_istart_E() const {
- return m_flags.E;
- }
-
- bool get_istart_R() const {
- return m_flags.R;
- }
-
- bool get_istart_W() const {
- return m_flags.W;
- }
-
- bool get_istart_X() const {
- return m_flags.X;
- }
-
- bool get_istart_IR() const {
- return m_flags.IR;
- }
-
- const pma_driver *get_driver() const {
- return m_driver;
- }
-
- const auto &get_device_noexcept() const {
- return *this;
- }
-
- static void *get_context() {
- return nullptr;
- }
-
- void mark_dirty_page(uint64_t /*address_in_range*/) {
- // Dummy implementation here.
- // This runs in microarchitecture.
- // The Host pages affected by writes will be marked dirty by uarch_bridge.
- }
-};
-
// Provides access to the state of the big emulator from microcode
-class uarch_machine_state_access : public i_state_access {
- std::array, PMA_MAX> &m_pmas; //NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members)
+class uarch_machine_state_access : public i_state_access {
+ std::array, PMA_MAX> m_pmas;
public:
explicit uarch_machine_state_access(std::array, PMA_MAX>& pmas) : m_pmas(pmas) {}
@@ -196,7 +62,7 @@ class uarch_machine_state_access : public i_state_access;
+ friend i_state_access;
// NOLINTBEGIN(readability-convert-member-functions-to-static)
@@ -569,17 +435,21 @@ class uarch_machine_state_access : public i_state_access(index);
+ if (!m_pmas[i]) {
+ m_pmas[i] = make_mock_pma_entry(index, istart, ilength, [](const char * /*err*/) {
+ abort();
+ });
}
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
return m_pmas[index].value();
}
- unsigned char *do_get_host_memory(uarch_pma_entry &/*pma*/) {
+ unsigned char *do_get_host_memory(mock_pma_entry &/*pma*/) {
return nullptr;
}
@@ -639,14 +509,14 @@ class uarch_machine_state_access : public i_state_access
- unsigned char *do_replace_tlb_entry(uint64_t vaddr, uint64_t paddr, uarch_pma_entry &pma) {
+ unsigned char *do_replace_tlb_entry(uint64_t vaddr, uint64_t paddr, mock_pma_entry &pma) {
uint64_t eidx = tlb_get_entry_index(vaddr);
volatile tlb_cold_entry &tlbce = do_get_tlb_entry_cold(eidx);
volatile tlb_hot_entry &tlbhe = do_get_tlb_hot_entry(eidx);
// Mark page that was on TLB as dirty so we know to update the Merkle tree
if constexpr (ETYPE == TLB_WRITE) {
if (tlbhe.vaddr_page != TLB_INVALID_PAGE) {
- uarch_pma_entry &pma = do_read_pma_entry(tlbce.pma_index);
+ mock_pma_entry &pma = do_read_pma_entry(tlbce.pma_index);
pma.mark_dirty_page(tlbce.paddr_page - pma.get_start());
}
}
@@ -675,7 +545,7 @@ class uarch_machine_state_access : public i_state_access(eidx);
- uarch_pma_entry &pma = do_read_pma_entry(tlbce.pma_index);
+ mock_pma_entry &pma = do_read_pma_entry(tlbce.pma_index);
pma.mark_dirty_page(tlbce.paddr_page - pma.get_start());
} else {
tlbhe.vaddr_page = TLB_INVALID_PAGE;
From 1153cc63b6899829a68c40cfd8d455a7ca9b050f Mon Sep 17 00:00:00 2001
From: Diego Nehab <1635557+diegonehab@users.noreply.github.com>
Date: Mon, 20 Jan 2025 14:39:28 +0000
Subject: [PATCH 02/82] refactor: remove htif depencency on os.h
---
src/Makefile | 2 +-
src/device-state-access.h | 8 +
src/htif.cpp | 6 +-
src/i-device-state-access.h | 14 +
src/i-state-access.h | 12 +
src/record-step-state-access.h | 8 +
src/replay-step-state-access.h | 8 +
src/state-access.h | 11 +
uarch/uarch-machine-state-access.h | 41 +-
uarch/uarch-printf.c | 1834 ++++++++++++++--------------
uarch/uarch-printf.h | 225 ++--
uarch/uarch-runtime.cpp | 20 -
uarch/uarch-runtime.h | 2 +-
13 files changed, 1113 insertions(+), 1078 deletions(-)
diff --git a/src/Makefile b/src/Makefile
index eff7a414c..2fcc1a4c4 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -282,7 +282,7 @@ CLANG_TIDY=clang-tidy
CLANG_TIDY_TARGETS=$(patsubst %.cpp,%.clang-tidy,$(patsubst %.c,%.clang-tidy,$(LINTER_SOURCES)))
CLANG_FORMAT=clang-format
-CLANG_FORMAT_UARCH_FILES:=$(wildcard ../uarch/*.cpp)
+CLANG_FORMAT_UARCH_FILES:=$(wildcard ../uarch/*.cpp, ../uarch/*.h)
CLANG_FORMAT_UARCH_FILES:=$(filter-out %uarch-printf%,$(strip $(CLANG_FORMAT_UARCH_FILES)))
CLANG_FORMAT_FILES:=$(wildcard *.cpp) $(wildcard *.c) $(wildcard *.h) $(wildcard *.hpp) $(CLANG_FORMAT_UARCH_FILES)
CLANG_FORMAT_IGNORE_FILES:=interpret-jump-table.h
diff --git a/src/device-state-access.h b/src/device-state-access.h
index 97dcad701..812d2c1cb 100644
--- a/src/device-state-access.h
+++ b/src/device-state-access.h
@@ -146,6 +146,14 @@ class device_state_access : public i_device_state_access {
bool do_write_memory(uint64_t paddr, const unsigned char *data, uint64_t length) override {
return m_a.write_memory(paddr, data, length);
}
+
+ void do_putchar(uint8_t c) override {
+ return m_a.putchar(c);
+ }
+
+ int do_getchar() override {
+ return m_a.getchar();
+ }
};
} // namespace cartesi
diff --git a/src/htif.cpp b/src/htif.cpp
index fc5b00b39..9ffcec2ef 100644
--- a/src/htif.cpp
+++ b/src/htif.cpp
@@ -19,7 +19,6 @@
#include "htif.h"
#include "i-device-state-access.h"
#include "interpret.h"
-#include "os.h"
#include "pma-driver.h"
namespace cartesi {
@@ -93,15 +92,14 @@ static execute_status htif_console(i_device_state_access *a, uint64_t cmd, uint6
if (cmd < 64 && (((a->read_htif_iconsole() >> cmd) & 1) != 0)) {
if (cmd == HTIF_CONSOLE_CMD_PUTCHAR) {
const uint8_t ch = data & 0xff;
- os_putchar(ch);
+ a->putchar(ch);
a->write_htif_fromhost(HTIF_BUILD(HTIF_DEV_CONSOLE, cmd, 0, 0));
} else if (cmd == HTIF_CONSOLE_CMD_GETCHAR) {
// In blockchain, this command will never be enabled as there is no way to input the same character
// to every participant in a dispute: where would c come from? So if the code reached here in the
// blockchain, there must be some serious bug
// In interactive mode, we just get the next character from the console and send it back in the ack
- os_poll_tty(0);
- const int c = os_getchar() + 1;
+ const int c = a->getchar() + 1;
a->write_htif_fromhost(HTIF_BUILD(HTIF_DEV_CONSOLE, cmd, 0, static_cast(c)));
}
}
diff --git a/src/i-device-state-access.h b/src/i-device-state-access.h
index efdfaa06e..784e2c684 100644
--- a/src/i-device-state-access.h
+++ b/src/i-device-state-access.h
@@ -193,6 +193,18 @@ class i_device_state_access {
return do_write_memory(paddr, data, length);
}
+ /// \brief Writes a character to the console
+ /// \param c Character to output
+ void putchar(uint8_t c) {
+ do_putchar(c);
+ }
+
+ /// \brief Reads a character from the console
+ /// \returns Character read if any, -1 otherwise
+ int getchar() {
+ return do_getchar();
+ }
+
private:
virtual void do_set_mip(uint64_t mask) = 0;
virtual void do_reset_mip(uint64_t mask) = 0;
@@ -216,6 +228,8 @@ class i_device_state_access {
virtual uint64_t do_read_htif_iyield() = 0;
virtual bool do_read_memory(uint64_t paddr, unsigned char *data, uint64_t length) = 0;
virtual bool do_write_memory(uint64_t paddr, const unsigned char *data, uint64_t length) = 0;
+ virtual void do_putchar(uint8_t c) = 0;
+ virtual int do_getchar() = 0;
};
} // namespace cartesi
diff --git a/src/i-state-access.h b/src/i-state-access.h
index 694d728ad..d255899ee 100644
--- a/src/i-state-access.h
+++ b/src/i-state-access.h
@@ -740,6 +740,18 @@ class i_state_access { // CRTP
return derived().do_write_memory_with_padding(paddr, data, data_length, write_length_log2_size);
}
+ /// \brief Writes a character to the console
+ /// \param c Character to output
+ void putchar(uint8_t c) {
+ return derived().do_putchar(c);
+ }
+
+ /// \brief Reads a character from the console
+ /// \returns Character read if any, -1 otherwise
+ int getchar() {
+ return derived().do_getchar();
+ }
+
#ifdef DUMP_COUNTERS
auto &get_statistics() {
return derived().do_get_statistics();
diff --git a/src/record-step-state-access.h b/src/record-step-state-access.h
index b1b443d78..a54cc2555 100644
--- a/src/record-step-state-access.h
+++ b/src/record-step-state-access.h
@@ -756,6 +756,14 @@ class record_step_state_access : public i_state_access {
}
}
+ // NOLINTNEXTLINE(readability-convert-member-functions-to-static)
+ void do_putchar(uint8_t c) {
+ os_putchar(c);
+ }
+
+ // NOLINTNEXTLINE(readability-convert-member-functions-to-static)
+ int do_getchar() {
+ os_poll_tty(0);
+ return os_getchar();
+ }
+
#ifdef DUMP_COUNTERS
machine_statistics &do_get_statistics() {
return m_m.get_state().stats;
diff --git a/uarch/uarch-machine-state-access.h b/uarch/uarch-machine-state-access.h
index d43c64d86..6d21dae88 100644
--- a/uarch/uarch-machine-state-access.h
+++ b/uarch/uarch-machine-state-access.h
@@ -20,17 +20,17 @@
#include "uarch-runtime.h" // must be included first, because of assert
#define MOCK_THROW_RUNTIME_ERROR(err) abort()
-#include "mock-pma-entry.h"
+#include "compiler-defines.h"
#include "device-state-access.h"
#include "i-state-access.h"
+#include "machine-reg.h"
+#include "mock-pma-entry.h"
#include "pma-constants.h"
#include "riscv-constants.h"
-#include "machine-reg.h"
#include "shadow-pmas.h"
+#include "strict-aliasing.h"
#include "uarch-constants.h"
#include "uarch-defines.h"
-#include "strict-aliasing.h"
-#include "compiler-defines.h"
#include
namespace cartesi {
@@ -66,10 +66,9 @@ class uarch_machine_state_access : public i_state_access
- void do_read_memory_word(uint64_t paddr, const unsigned char */*hpage*/, uint64_t /*hoffset*/, T *pval) {
+ void do_read_memory_word(uint64_t paddr, const unsigned char * /*hpage*/, uint64_t /*hoffset*/, T *pval) {
*pval = raw_read_memory(paddr);
}
- bool do_read_memory(uint64_t /*paddr*/, unsigned char */*data*/, uint64_t /*length*/) {
+ bool do_read_memory(uint64_t /*paddr*/, unsigned char * /*data*/, uint64_t /*length*/) {
// This is not implemented yet because it's not being used
abort();
return false;
}
- bool do_write_memory(uint64_t /*paddr*/, const unsigned char */*data*/, uint64_t /*length*/) {
+ bool do_write_memory(uint64_t /*paddr*/, const unsigned char * /*data*/, uint64_t /*length*/) {
// This is not implemented yet because it's not being used
abort();
return false;
}
template
- void do_write_memory_word(uint64_t paddr, const unsigned char */*hpage*/, uint64_t /*hoffset*/, T val) {
+ void do_write_memory_word(uint64_t paddr, const unsigned char * /*hpage*/, uint64_t /*hoffset*/, T val) {
raw_write_memory(paddr, val);
}
@@ -441,20 +440,18 @@ class uarch_machine_state_access : public i_state_access(index);
if (!m_pmas[i]) {
- m_pmas[i] = make_mock_pma_entry(index, istart, ilength, [](const char * /*err*/) {
- abort();
- });
+ m_pmas[i] = make_mock_pma_entry(index, istart, ilength, [](const char * /*err*/) { abort(); });
}
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
return m_pmas[index].value();
}
- unsigned char *do_get_host_memory(mock_pma_entry &/*pma*/) {
+ unsigned char *do_get_host_memory(mock_pma_entry & /*pma*/) {
return nullptr;
}
template
- volatile tlb_hot_entry& do_get_tlb_hot_entry(uint64_t eidx) {
+ volatile tlb_hot_entry &do_get_tlb_hot_entry(uint64_t eidx) {
// Volatile is used, so the compiler does not optimize out, or do of order writes.
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast,performance-no-int-to-ptr)
volatile tlb_hot_entry *tlbe = reinterpret_cast(tlb_get_entry_hot_abs_addr(eidx));
@@ -462,7 +459,7 @@ class uarch_machine_state_access : public i_state_access
- volatile tlb_cold_entry& do_get_tlb_entry_cold(uint64_t eidx) {
+ volatile tlb_cold_entry &do_get_tlb_entry_cold(uint64_t eidx) {
// Volatile is used, so the compiler does not optimize out, or do of order writes.
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast,performance-no-int-to-ptr)
volatile tlb_cold_entry *tlbe = reinterpret_cast(tlb_get_entry_cold_abs_addr(eidx));
@@ -534,7 +531,7 @@ class uarch_machine_state_access : public i_state_access(paddr_page);
+ return cast_addr_to_ptr(paddr_page);
}
template
@@ -573,6 +570,14 @@ class uarch_machine_state_access : public i_state_access
-#include
-
-#include "uarch-printf.h"
-
-
-// define this globally (e.g. gcc -DPRINTF_INCLUDE_CONFIG_H ...) to include the
-// printf_config.h header file
-// default: undefined
-#ifdef PRINTF_INCLUDE_CONFIG_H
-#include "printf_config.h"
-#endif
-
-
-// 'ntoa' conversion buffer size, this must be big enough to hold one converted
-// numeric number including padded zeros (dynamically created on stack)
-// default: 32 byte
-#ifndef PRINTF_NTOA_BUFFER_SIZE
-#define PRINTF_NTOA_BUFFER_SIZE 32U
-#endif
-
-// 'ftoa' conversion buffer size, this must be big enough to hold one converted
-// float number including padded zeros (dynamically created on stack)
-// default: 32 byte
-#ifndef PRINTF_FTOA_BUFFER_SIZE
-#define PRINTF_FTOA_BUFFER_SIZE 32U
-#endif
-
-// support for the floating point type (%f)
-// default: activated
-#ifndef PRINTF_DISABLE_SUPPORT_FLOAT
-#define PRINTF_SUPPORT_FLOAT
-#endif
-
-// support for exponential floating point notation (%e/%g)
-// default: activated
-#ifndef PRINTF_DISABLE_SUPPORT_EXPONENTIAL
-#define PRINTF_SUPPORT_EXPONENTIAL
-#endif
-
-// define the default floating point precision
-// default: 6 digits
-#ifndef PRINTF_DEFAULT_FLOAT_PRECISION
-#define PRINTF_DEFAULT_FLOAT_PRECISION 6U
-#endif
-
-// define the largest float suitable to print with %f
-// default: 1e9
-#ifndef PRINTF_MAX_FLOAT
-#define PRINTF_MAX_FLOAT 1e9
-#endif
-
-// support for the long long types (%llu or %p)
-// default: activated
-#ifndef PRINTF_DISABLE_SUPPORT_LONG_LONG
-#define PRINTF_SUPPORT_LONG_LONG
-#endif
-
-// support for the ptrdiff_t type (%t)
-// ptrdiff_t is normally defined in as long or long long type
-// default: activated
-#ifndef PRINTF_DISABLE_SUPPORT_PTRDIFF_T
-#define PRINTF_SUPPORT_PTRDIFF_T
-#endif
-
-///////////////////////////////////////////////////////////////////////////////
-
-// internal flag definitions
-#define FLAGS_ZEROPAD (1U << 0U)
-#define FLAGS_LEFT (1U << 1U)
-#define FLAGS_PLUS (1U << 2U)
-#define FLAGS_SPACE (1U << 3U)
-#define FLAGS_HASH (1U << 4U)
-#define FLAGS_UPPERCASE (1U << 5U)
-#define FLAGS_CHAR (1U << 6U)
-#define FLAGS_SHORT (1U << 7U)
-#define FLAGS_LONG (1U << 8U)
-#define FLAGS_LONG_LONG (1U << 9U)
-#define FLAGS_PRECISION (1U << 10U)
-#define FLAGS_ADAPT_EXP (1U << 11U)
-
-
-// import float.h for DBL_MAX
-#if defined(PRINTF_SUPPORT_FLOAT)
-#include
-#endif
-
-
-// output function type
-typedef void (*out_fct_type)(char character, void* buffer, size_t idx, size_t maxlen);
-
-
-// wrapper (used as buffer) for output function type
-typedef struct {
- void (*fct)(char character, void* arg);
- void* arg;
-} out_fct_wrap_type;
-
-
-// internal buffer output
-static inline void _out_buffer(char character, void* buffer, size_t idx, size_t maxlen)
-{
- if (idx < maxlen) {
- ((char*)buffer)[idx] = character;
- }
-}
-
-
-// internal null output
-static inline void _out_null(char character, void* buffer, size_t idx, size_t maxlen)
-{
- (void)character; (void)buffer; (void)idx; (void)maxlen;
-}
-
-
-// internal _putchar wrapper
-static inline void _out_char(char character, void* buffer, size_t idx, size_t maxlen)
-{
- (void)buffer; (void)idx; (void)maxlen;
- if (character) {
- _putchar(character);
- }
-}
-
-
-// internal output function wrapper
-static inline void _out_fct(char character, void* buffer, size_t idx, size_t maxlen)
-{
- (void)idx; (void)maxlen;
- if (character) {
- // buffer is the output fct pointer
- ((out_fct_wrap_type*)buffer)->fct(character, ((out_fct_wrap_type*)buffer)->arg);
- }
-}
-
-
-// internal secure strlen
-// \return The length of the string (excluding the terminating 0) limited by 'maxsize'
-static inline unsigned int _strnlen_s(const char* str, size_t maxsize)
-{
- const char* s;
- for (s = str; *s && maxsize--; ++s);
- return (unsigned int)(s - str);
-}
-
-
-// internal test if char is a digit (0-9)
-// \return true if char is a digit
-static inline bool _is_digit(char ch)
-{
- return (ch >= '0') && (ch <= '9');
-}
-
-
-// internal ASCII string to unsigned int conversion
-static unsigned int _atoi(const char** str)
-{
- unsigned int i = 0U;
- while (_is_digit(**str)) {
- i = i * 10U + (unsigned int)(*((*str)++) - '0');
- }
- return i;
-}
-
-
-// output the specified string in reverse, taking care of any zero-padding
-static size_t _out_rev(out_fct_type out, char* buffer, size_t idx, size_t maxlen, const char* buf, size_t len, unsigned int width, unsigned int flags)
-{
- const size_t start_idx = idx;
-
- // pad spaces up to given width
- if (!(flags & FLAGS_LEFT) && !(flags & FLAGS_ZEROPAD)) {
- for (size_t i = len; i < width; i++) {
- out(' ', buffer, idx++, maxlen);
- }
- }
-
- // reverse string
- while (len) {
- out(buf[--len], buffer, idx++, maxlen);
- }
-
- // append pad spaces up to given width
- if (flags & FLAGS_LEFT) {
- while (idx - start_idx < width) {
- out(' ', buffer, idx++, maxlen);
- }
- }
-
- return idx;
-}
-
-
-// internal itoa format
-static size_t _ntoa_format(out_fct_type out, char* buffer, size_t idx, size_t maxlen, char* buf, size_t len, bool negative, unsigned int base, unsigned int prec, unsigned int width, unsigned int flags)
-{
- // pad leading zeros
- if (!(flags & FLAGS_LEFT)) {
- if (width && (flags & FLAGS_ZEROPAD) && (negative || (flags & (FLAGS_PLUS | FLAGS_SPACE)))) {
- width--;
- }
- while ((len < prec) && (len < PRINTF_NTOA_BUFFER_SIZE)) {
- buf[len++] = '0';
- }
- while ((flags & FLAGS_ZEROPAD) && (len < width) && (len < PRINTF_NTOA_BUFFER_SIZE)) {
- buf[len++] = '0';
- }
- }
-
- // handle hash
- if (flags & FLAGS_HASH) {
- if (!(flags & FLAGS_PRECISION) && len && ((len == prec) || (len == width))) {
- len--;
- if (len && (base == 16U)) {
- len--;
- }
- }
- if ((base == 16U) && !(flags & FLAGS_UPPERCASE) && (len < PRINTF_NTOA_BUFFER_SIZE)) {
- buf[len++] = 'x';
- }
- else if ((base == 16U) && (flags & FLAGS_UPPERCASE) && (len < PRINTF_NTOA_BUFFER_SIZE)) {
- buf[len++] = 'X';
- }
- else if ((base == 2U) && (len < PRINTF_NTOA_BUFFER_SIZE)) {
- buf[len++] = 'b';
- }
- if (len < PRINTF_NTOA_BUFFER_SIZE) {
- buf[len++] = '0';
- }
- }
-
- if (len < PRINTF_NTOA_BUFFER_SIZE) {
- if (negative) {
- buf[len++] = '-';
- }
- else if (flags & FLAGS_PLUS) {
- buf[len++] = '+'; // ignore the space if the '+' exists
- }
- else if (flags & FLAGS_SPACE) {
- buf[len++] = ' ';
- }
- }
-
- return _out_rev(out, buffer, idx, maxlen, buf, len, width, flags);
-}
-
-
-// internal itoa for 'long' type
-static size_t _ntoa_long(out_fct_type out, char* buffer, size_t idx, size_t maxlen, unsigned long value, bool negative, unsigned long base, unsigned int prec, unsigned int width, unsigned int flags)
-{
- char buf[PRINTF_NTOA_BUFFER_SIZE];
- size_t len = 0U;
-
- // no hash for 0 values
- if (!value) {
- flags &= ~FLAGS_HASH;
- }
-
- // write if precision != 0 and value is != 0
- if (!(flags & FLAGS_PRECISION) || value) {
- do {
- const char digit = (char)(value % base);
- buf[len++] = digit < 10 ? '0' + digit : (flags & FLAGS_UPPERCASE ? 'A' : 'a') + digit - 10;
- value /= base;
- } while (value && (len < PRINTF_NTOA_BUFFER_SIZE));
- }
-
- return _ntoa_format(out, buffer, idx, maxlen, buf, len, negative, (unsigned int)base, prec, width, flags);
-}
-
-
-// internal itoa for 'long long' type
-#if defined(PRINTF_SUPPORT_LONG_LONG)
-static size_t _ntoa_long_long(out_fct_type out, char* buffer, size_t idx, size_t maxlen, unsigned long long value, bool negative, unsigned long long base, unsigned int prec, unsigned int width, unsigned int flags)
-{
- char buf[PRINTF_NTOA_BUFFER_SIZE];
- size_t len = 0U;
-
- // no hash for 0 values
- if (!value) {
- flags &= ~FLAGS_HASH;
- }
-
- // write if precision != 0 and value is != 0
- if (!(flags & FLAGS_PRECISION) || value) {
- do {
- const char digit = (char)(value % base);
- buf[len++] = digit < 10 ? '0' + digit : (flags & FLAGS_UPPERCASE ? 'A' : 'a') + digit - 10;
- value /= base;
- } while (value && (len < PRINTF_NTOA_BUFFER_SIZE));
- }
-
- return _ntoa_format(out, buffer, idx, maxlen, buf, len, negative, (unsigned int)base, prec, width, flags);
-}
-#endif // PRINTF_SUPPORT_LONG_LONG
-
-
-#if defined(PRINTF_SUPPORT_FLOAT)
-
-#if defined(PRINTF_SUPPORT_EXPONENTIAL)
-// forward declaration so that _ftoa can switch to exp notation for values > PRINTF_MAX_FLOAT
-static size_t _etoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, double value, unsigned int prec, unsigned int width, unsigned int flags);
-#endif
-
-
-// internal ftoa for fixed decimal floating point
-static size_t _ftoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, double value, unsigned int prec, unsigned int width, unsigned int flags)
-{
- char buf[PRINTF_FTOA_BUFFER_SIZE];
- size_t len = 0U;
- double diff = 0.0;
-
- // powers of 10
- static const double pow10[] = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000 };
-
- // test for special values
- if (value != value)
- return _out_rev(out, buffer, idx, maxlen, "nan", 3, width, flags);
- if (value < -DBL_MAX)
- return _out_rev(out, buffer, idx, maxlen, "fni-", 4, width, flags);
- if (value > DBL_MAX)
- return _out_rev(out, buffer, idx, maxlen, (flags & FLAGS_PLUS) ? "fni+" : "fni", (flags & FLAGS_PLUS) ? 4U : 3U, width, flags);
-
- // test for very large values
- // standard printf behavior is to print EVERY whole number digit -- which could be 100s of characters overflowing your buffers == bad
- if ((value > PRINTF_MAX_FLOAT) || (value < -PRINTF_MAX_FLOAT)) {
-#if defined(PRINTF_SUPPORT_EXPONENTIAL)
- return _etoa(out, buffer, idx, maxlen, value, prec, width, flags);
-#else
- return 0U;
-#endif
- }
-
- // test for negative
- bool negative = false;
- if (value < 0) {
- negative = true;
- value = 0 - value;
- }
-
- // set default precision, if not set explicitly
- if (!(flags & FLAGS_PRECISION)) {
- prec = PRINTF_DEFAULT_FLOAT_PRECISION;
- }
- // limit precision to 9, cause a prec >= 10 can lead to overflow errors
- while ((len < PRINTF_FTOA_BUFFER_SIZE) && (prec > 9U)) {
- buf[len++] = '0';
- prec--;
- }
-
- int whole = (int)value;
- double tmp = (value - whole) * pow10[prec];
- unsigned long frac = (unsigned long)tmp;
- diff = tmp - frac;
-
- if (diff > 0.5) {
- ++frac;
- // handle rollover, e.g. case 0.99 with prec 1 is 1.0
- if (frac >= pow10[prec]) {
- frac = 0;
- ++whole;
- }
- }
- else if (diff < 0.5) {
- }
- else if ((frac == 0U) || (frac & 1U)) {
- // if halfway, round up if odd OR if last digit is 0
- ++frac;
- }
-
- if (prec == 0U) {
- diff = value - (double)whole;
- if ((!(diff < 0.5) || (diff > 0.5)) && (whole & 1)) {
- // exactly 0.5 and ODD, then round up
- // 1.5 -> 2, but 2.5 -> 2
- ++whole;
- }
- }
- else {
- unsigned int count = prec;
- // now do fractional part, as an unsigned number
- while (len < PRINTF_FTOA_BUFFER_SIZE) {
- --count;
- buf[len++] = (char)(48U + (frac % 10U));
- if (!(frac /= 10U)) {
- break;
- }
- }
- // add extra 0s
- while ((len < PRINTF_FTOA_BUFFER_SIZE) && (count-- > 0U)) {
- buf[len++] = '0';
- }
- if (len < PRINTF_FTOA_BUFFER_SIZE) {
- // add decimal
- buf[len++] = '.';
- }
- }
-
- // do whole part, number is reversed
- while (len < PRINTF_FTOA_BUFFER_SIZE) {
- buf[len++] = (char)(48 + (whole % 10));
- if (!(whole /= 10)) {
- break;
- }
- }
-
- // pad leading zeros
- if (!(flags & FLAGS_LEFT) && (flags & FLAGS_ZEROPAD)) {
- if (width && (negative || (flags & (FLAGS_PLUS | FLAGS_SPACE)))) {
- width--;
- }
- while ((len < width) && (len < PRINTF_FTOA_BUFFER_SIZE)) {
- buf[len++] = '0';
- }
- }
-
- if (len < PRINTF_FTOA_BUFFER_SIZE) {
- if (negative) {
- buf[len++] = '-';
- }
- else if (flags & FLAGS_PLUS) {
- buf[len++] = '+'; // ignore the space if the '+' exists
- }
- else if (flags & FLAGS_SPACE) {
- buf[len++] = ' ';
- }
- }
-
- return _out_rev(out, buffer, idx, maxlen, buf, len, width, flags);
-}
-
-
-#if defined(PRINTF_SUPPORT_EXPONENTIAL)
-// internal ftoa variant for exponential floating-point type, contributed by Martijn Jasperse
-static size_t _etoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, double value, unsigned int prec, unsigned int width, unsigned int flags)
-{
- // check for NaN and special values
- if ((value != value) || (value > DBL_MAX) || (value < -DBL_MAX)) {
- return _ftoa(out, buffer, idx, maxlen, value, prec, width, flags);
- }
-
- // determine the sign
- const bool negative = value < 0;
- if (negative) {
- value = -value;
- }
-
- // default precision
- if (!(flags & FLAGS_PRECISION)) {
- prec = PRINTF_DEFAULT_FLOAT_PRECISION;
- }
-
- // determine the decimal exponent
- // based on the algorithm by David Gay (https://www.ampl.com/netlib/fp/dtoa.c)
- union {
- uint64_t U;
- double F;
- } conv;
-
- conv.F = value;
- int exp2 = (int)((conv.U >> 52U) & 0x07FFU) - 1023; // effectively log2
- conv.U = (conv.U & ((1ULL << 52U) - 1U)) | (1023ULL << 52U); // drop the exponent so conv.F is now in [1,2)
- // now approximate log10 from the log2 integer part and an expansion of ln around 1.5
- int expval = (int)(0.1760912590558 + exp2 * 0.301029995663981 + (conv.F - 1.5) * 0.289529654602168);
- // now we want to compute 10^expval but we want to be sure it won't overflow
- exp2 = (int)(expval * 3.321928094887362 + 0.5);
- const double z = expval * 2.302585092994046 - exp2 * 0.6931471805599453;
- const double z2 = z * z;
- conv.U = (uint64_t)(exp2 + 1023) << 52U;
- // compute exp(z) using continued fractions, see https://en.wikipedia.org/wiki/Exponential_function#Continued_fractions_for_ex
- conv.F *= 1 + 2 * z / (2 - z + (z2 / (6 + (z2 / (10 + z2 / 14)))));
- // correct for rounding errors
- if (value < conv.F) {
- expval--;
- conv.F /= 10;
- }
-
- // the exponent format is "%+03d" and largest value is "307", so set aside 4-5 characters
- unsigned int minwidth = ((expval < 100) && (expval > -100)) ? 4U : 5U;
-
- // in "%g" mode, "prec" is the number of *significant figures* not decimals
- if (flags & FLAGS_ADAPT_EXP) {
- // do we want to fall-back to "%f" mode?
- if ((value >= 1e-4) && (value < 1e6)) {
- if ((int)prec > expval) {
- prec = (unsigned)((int)prec - expval - 1);
- }
- else {
- prec = 0;
- }
- flags |= FLAGS_PRECISION; // make sure _ftoa respects precision
- // no characters in exponent
- minwidth = 0U;
- expval = 0;
- }
- else {
- // we use one sigfig for the whole part
- if ((prec > 0) && (flags & FLAGS_PRECISION)) {
- --prec;
- }
- }
- }
-
- // will everything fit?
- unsigned int fwidth = width;
- if (width > minwidth) {
- // we didn't fall-back so subtract the characters required for the exponent
- fwidth -= minwidth;
- } else {
- // not enough characters, so go back to default sizing
- fwidth = 0U;
- }
- if ((flags & FLAGS_LEFT) && minwidth) {
- // if we're padding on the right, DON'T pad the floating part
- fwidth = 0U;
- }
-
- // rescale the float value
- if (expval) {
- value /= conv.F;
- }
-
- // output the floating part
- const size_t start_idx = idx;
- idx = _ftoa(out, buffer, idx, maxlen, negative ? -value : value, prec, fwidth, flags & ~FLAGS_ADAPT_EXP);
-
- // output the exponent part
- if (minwidth) {
- // output the exponential symbol
- out((flags & FLAGS_UPPERCASE) ? 'E' : 'e', buffer, idx++, maxlen);
- // output the exponent value
- idx = _ntoa_long(out, buffer, idx, maxlen, (expval < 0) ? -expval : expval, expval < 0, 10, 0, minwidth-1, FLAGS_ZEROPAD | FLAGS_PLUS);
- // might need to right-pad spaces
- if (flags & FLAGS_LEFT) {
- while (idx - start_idx < width) out(' ', buffer, idx++, maxlen);
- }
- }
- return idx;
-}
-#endif // PRINTF_SUPPORT_EXPONENTIAL
-#endif // PRINTF_SUPPORT_FLOAT
-
-
-// internal vsnprintf
-static int _vsnprintf(out_fct_type out, char* buffer, const size_t maxlen, const char* format, va_list va)
-{
- unsigned int flags, width, precision, n;
- size_t idx = 0U;
-
- if (!buffer) {
- // use null output function
- out = _out_null;
- }
-
- while (*format)
- {
- // format specifier? %[flags][width][.precision][length]
- if (*format != '%') {
- // no
- out(*format, buffer, idx++, maxlen);
- format++;
- continue;
- }
- else {
- // yes, evaluate it
- format++;
- }
-
- // evaluate flags
- flags = 0U;
- do {
- switch (*format) {
- case '0': flags |= FLAGS_ZEROPAD; format++; n = 1U; break;
- case '-': flags |= FLAGS_LEFT; format++; n = 1U; break;
- case '+': flags |= FLAGS_PLUS; format++; n = 1U; break;
- case ' ': flags |= FLAGS_SPACE; format++; n = 1U; break;
- case '#': flags |= FLAGS_HASH; format++; n = 1U; break;
- default : n = 0U; break;
- }
- } while (n);
-
- // evaluate width field
- width = 0U;
- if (_is_digit(*format)) {
- width = _atoi(&format);
- }
- else if (*format == '*') {
- const int w = va_arg(va, int);
- if (w < 0) {
- flags |= FLAGS_LEFT; // reverse padding
- width = (unsigned int)-w;
- }
- else {
- width = (unsigned int)w;
- }
- format++;
- }
-
- // evaluate precision field
- precision = 0U;
- if (*format == '.') {
- flags |= FLAGS_PRECISION;
- format++;
- if (_is_digit(*format)) {
- precision = _atoi(&format);
- }
- else if (*format == '*') {
- const int prec = (int)va_arg(va, int);
- precision = prec > 0 ? (unsigned int)prec : 0U;
- format++;
- }
- }
-
- // evaluate length field
- switch (*format) {
- case 'l' :
- flags |= FLAGS_LONG;
- format++;
- if (*format == 'l') {
- flags |= FLAGS_LONG_LONG;
- format++;
- }
- break;
- case 'h' :
- flags |= FLAGS_SHORT;
- format++;
- if (*format == 'h') {
- flags |= FLAGS_CHAR;
- format++;
- }
- break;
-#if defined(PRINTF_SUPPORT_PTRDIFF_T)
- case 't' :
- flags |= (sizeof(ptrdiff_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG);
- format++;
- break;
-#endif
- case 'j' :
- flags |= (sizeof(intmax_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG);
- format++;
- break;
- case 'z' :
- flags |= (sizeof(size_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG);
- format++;
- break;
- default :
- break;
- }
-
- // evaluate specifier
- switch (*format) {
- case 'd' :
- case 'i' :
- case 'u' :
- case 'x' :
- case 'X' :
- case 'o' :
- case 'b' : {
- // set the base
- unsigned int base;
- if (*format == 'x' || *format == 'X') {
- base = 16U;
- }
- else if (*format == 'o') {
- base = 8U;
- }
- else if (*format == 'b') {
- base = 2U;
- }
- else {
- base = 10U;
- flags &= ~FLAGS_HASH; // no hash for dec format
- }
- // uppercase
- if (*format == 'X') {
- flags |= FLAGS_UPPERCASE;
- }
-
- // no plus or space flag for u, x, X, o, b
- if ((*format != 'i') && (*format != 'd')) {
- flags &= ~(FLAGS_PLUS | FLAGS_SPACE);
- }
-
- // ignore '0' flag when precision is given
- if (flags & FLAGS_PRECISION) {
- flags &= ~FLAGS_ZEROPAD;
- }
-
- // convert the integer
- if ((*format == 'i') || (*format == 'd')) {
- // signed
- if (flags & FLAGS_LONG_LONG) {
-#if defined(PRINTF_SUPPORT_LONG_LONG)
- const long long value = va_arg(va, long long);
- idx = _ntoa_long_long(out, buffer, idx, maxlen, (unsigned long long)(value > 0 ? value : 0 - value), value < 0, base, precision, width, flags);
-#endif
- }
- else if (flags & FLAGS_LONG) {
- const long value = va_arg(va, long);
- idx = _ntoa_long(out, buffer, idx, maxlen, (unsigned long)(value > 0 ? value : 0 - value), value < 0, base, precision, width, flags);
- }
- else {
- const int value = (flags & FLAGS_CHAR) ? (char)va_arg(va, int) : (flags & FLAGS_SHORT) ? (short int)va_arg(va, int) : va_arg(va, int);
- idx = _ntoa_long(out, buffer, idx, maxlen, (unsigned int)(value > 0 ? value : 0 - value), value < 0, base, precision, width, flags);
- }
- }
- else {
- // unsigned
- if (flags & FLAGS_LONG_LONG) {
-#if defined(PRINTF_SUPPORT_LONG_LONG)
- idx = _ntoa_long_long(out, buffer, idx, maxlen, va_arg(va, unsigned long long), false, base, precision, width, flags);
-#endif
- }
- else if (flags & FLAGS_LONG) {
- idx = _ntoa_long(out, buffer, idx, maxlen, va_arg(va, unsigned long), false, base, precision, width, flags);
- }
- else {
- const unsigned int value = (flags & FLAGS_CHAR) ? (unsigned char)va_arg(va, unsigned int) : (flags & FLAGS_SHORT) ? (unsigned short int)va_arg(va, unsigned int) : va_arg(va, unsigned int);
- idx = _ntoa_long(out, buffer, idx, maxlen, value, false, base, precision, width, flags);
- }
- }
- format++;
- break;
- }
-#if defined(PRINTF_SUPPORT_FLOAT)
- case 'f' :
- case 'F' :
- if (*format == 'F') flags |= FLAGS_UPPERCASE;
- idx = _ftoa(out, buffer, idx, maxlen, va_arg(va, double), precision, width, flags);
- format++;
- break;
-#if defined(PRINTF_SUPPORT_EXPONENTIAL)
- case 'e':
- case 'E':
- case 'g':
- case 'G':
- if ((*format == 'g')||(*format == 'G')) flags |= FLAGS_ADAPT_EXP;
- if ((*format == 'E')||(*format == 'G')) flags |= FLAGS_UPPERCASE;
- idx = _etoa(out, buffer, idx, maxlen, va_arg(va, double), precision, width, flags);
- format++;
- break;
-#endif // PRINTF_SUPPORT_EXPONENTIAL
-#endif // PRINTF_SUPPORT_FLOAT
- case 'c' : {
- unsigned int l = 1U;
- // pre padding
- if (!(flags & FLAGS_LEFT)) {
- while (l++ < width) {
- out(' ', buffer, idx++, maxlen);
- }
- }
- // char output
- out((char)va_arg(va, int), buffer, idx++, maxlen);
- // post padding
- if (flags & FLAGS_LEFT) {
- while (l++ < width) {
- out(' ', buffer, idx++, maxlen);
- }
- }
- format++;
- break;
- }
-
- case 's' : {
- const char* p = va_arg(va, char*);
- unsigned int l = _strnlen_s(p, precision ? precision : (size_t)-1);
- // pre padding
- if (flags & FLAGS_PRECISION) {
- l = (l < precision ? l : precision);
- }
- if (!(flags & FLAGS_LEFT)) {
- while (l++ < width) {
- out(' ', buffer, idx++, maxlen);
- }
- }
- // string output
- while ((*p != 0) && (!(flags & FLAGS_PRECISION) || precision--)) {
- out(*(p++), buffer, idx++, maxlen);
- }
- // post padding
- if (flags & FLAGS_LEFT) {
- while (l++ < width) {
- out(' ', buffer, idx++, maxlen);
- }
- }
- format++;
- break;
- }
-
- case 'p' : {
- width = sizeof(void*) * 2U;
- flags |= FLAGS_ZEROPAD | FLAGS_UPPERCASE;
-#if defined(PRINTF_SUPPORT_LONG_LONG)
- const bool is_ll = sizeof(uintptr_t) == sizeof(long long);
- if (is_ll) {
- idx = _ntoa_long_long(out, buffer, idx, maxlen, (uintptr_t)va_arg(va, void*), false, 16U, precision, width, flags);
- }
- else {
-#endif
- idx = _ntoa_long(out, buffer, idx, maxlen, (unsigned long)((uintptr_t)va_arg(va, void*)), false, 16U, precision, width, flags);
-#if defined(PRINTF_SUPPORT_LONG_LONG)
- }
-#endif
- format++;
- break;
- }
-
- case '%' :
- out('%', buffer, idx++, maxlen);
- format++;
- break;
-
- default :
- out(*format, buffer, idx++, maxlen);
- format++;
- break;
- }
- }
-
- // termination
- out((char)0, buffer, idx < maxlen ? idx : maxlen - 1U, maxlen);
-
- // return written chars without terminating \0
- return (int)idx;
-}
-
-
-///////////////////////////////////////////////////////////////////////////////
-
-int printf_(const char* format, ...)
-{
- va_list va;
- va_start(va, format);
- char buffer[1];
- const int ret = _vsnprintf(_out_char, buffer, (size_t)-1, format, va);
- va_end(va);
- return ret;
-}
-
-
-int sprintf_(char* buffer, const char* format, ...)
-{
- va_list va;
- va_start(va, format);
- const int ret = _vsnprintf(_out_buffer, buffer, (size_t)-1, format, va);
- va_end(va);
- return ret;
-}
-
-
-int snprintf_(char* buffer, size_t count, const char* format, ...)
-{
- va_list va;
- va_start(va, format);
- const int ret = _vsnprintf(_out_buffer, buffer, count, format, va);
- va_end(va);
- return ret;
-}
-
-
-int vprintf_(const char* format, va_list va)
-{
- char buffer[1];
- return _vsnprintf(_out_char, buffer, (size_t)-1, format, va);
-}
-
-
-int vsnprintf_(char* buffer, size_t count, const char* format, va_list va)
-{
- return _vsnprintf(_out_buffer, buffer, count, format, va);
-}
-
-
-int fctprintf(void (*out)(char character, void* arg), void* arg, const char* format, ...)
-{
- va_list va;
- va_start(va, format);
- const out_fct_wrap_type out_fct_wrap = { out, arg };
- const int ret = _vsnprintf(_out_fct, (char*)(uintptr_t)&out_fct_wrap, (size_t)-1, format, va);
- va_end(va);
- return ret;
-}
+///////////////////////////////////////////////////////////////////////////////
+// \author (c) Marco Paland (info@paland.com)
+// 2014-2019, PALANDesign Hannover, Germany
+//
+// \license The MIT License (MIT)
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE 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
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 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 IN
+// THE SOFTWARE.
+//
+// \brief Tiny printf, sprintf and (v)snprintf implementation, optimized for speed on
+// embedded systems with a very limited resources. These routines are thread
+// safe and reentrant!
+// Use this instead of the bloated standard/newlib printf cause these use
+// malloc for printf (and may not be thread safe).
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#define PRINTF_DISABLE_SUPPORT_FLOAT
+#define PRINTF_DISABLE_SUPPORT_EXPONENTIAL
+
+#include
+#include
+
+#include "uarch-printf.h"
+
+
+// define this globally (e.g. gcc -DPRINTF_INCLUDE_CONFIG_H ...) to include the
+// printf_config.h header file
+// default: undefined
+#ifdef PRINTF_INCLUDE_CONFIG_H
+#include "printf_config.h"
+#endif
+
+
+// 'ntoa' conversion buffer size, this must be big enough to hold one converted
+// numeric number including padded zeros (dynamically created on stack)
+// default: 32 byte
+#ifndef PRINTF_NTOA_BUFFER_SIZE
+#define PRINTF_NTOA_BUFFER_SIZE 32U
+#endif
+
+// 'ftoa' conversion buffer size, this must be big enough to hold one converted
+// float number including padded zeros (dynamically created on stack)
+// default: 32 byte
+#ifndef PRINTF_FTOA_BUFFER_SIZE
+#define PRINTF_FTOA_BUFFER_SIZE 32U
+#endif
+
+// support for the floating point type (%f)
+// default: activated
+#ifndef PRINTF_DISABLE_SUPPORT_FLOAT
+#define PRINTF_SUPPORT_FLOAT
+#endif
+
+// support for exponential floating point notation (%e/%g)
+// default: activated
+#ifndef PRINTF_DISABLE_SUPPORT_EXPONENTIAL
+#define PRINTF_SUPPORT_EXPONENTIAL
+#endif
+
+// define the default floating point precision
+// default: 6 digits
+#ifndef PRINTF_DEFAULT_FLOAT_PRECISION
+#define PRINTF_DEFAULT_FLOAT_PRECISION 6U
+#endif
+
+// define the largest float suitable to print with %f
+// default: 1e9
+#ifndef PRINTF_MAX_FLOAT
+#define PRINTF_MAX_FLOAT 1e9
+#endif
+
+// support for the long long types (%llu or %p)
+// default: activated
+#ifndef PRINTF_DISABLE_SUPPORT_LONG_LONG
+#define PRINTF_SUPPORT_LONG_LONG
+#endif
+
+// support for the ptrdiff_t type (%t)
+// ptrdiff_t is normally defined in as long or long long type
+// default: activated
+#ifndef PRINTF_DISABLE_SUPPORT_PTRDIFF_T
+#define PRINTF_SUPPORT_PTRDIFF_T
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+// internal flag definitions
+#define FLAGS_ZEROPAD (1U << 0U)
+#define FLAGS_LEFT (1U << 1U)
+#define FLAGS_PLUS (1U << 2U)
+#define FLAGS_SPACE (1U << 3U)
+#define FLAGS_HASH (1U << 4U)
+#define FLAGS_UPPERCASE (1U << 5U)
+#define FLAGS_CHAR (1U << 6U)
+#define FLAGS_SHORT (1U << 7U)
+#define FLAGS_LONG (1U << 8U)
+#define FLAGS_LONG_LONG (1U << 9U)
+#define FLAGS_PRECISION (1U << 10U)
+#define FLAGS_ADAPT_EXP (1U << 11U)
+
+
+// import float.h for DBL_MAX
+#if defined(PRINTF_SUPPORT_FLOAT)
+#include
+#endif
+
+
+// output function type
+typedef void (*out_fct_type)(char character, void* buffer, size_t idx, size_t maxlen);
+
+
+// wrapper (used as buffer) for output function type
+typedef struct {
+ void (*fct)(char character, void* arg);
+ void* arg;
+} out_fct_wrap_type;
+
+
+// internal buffer output
+static inline void _out_buffer(char character, void* buffer, size_t idx, size_t maxlen)
+{
+ if (idx < maxlen) {
+ ((char*)buffer)[idx] = character;
+ }
+}
+
+
+// internal null output
+static inline void _out_null(char character, void* buffer, size_t idx, size_t maxlen)
+{
+ (void)character; (void)buffer; (void)idx; (void)maxlen;
+}
+
+
+// internal _putchar wrapper
+static inline void _out_char(char character, void* buffer, size_t idx, size_t maxlen)
+{
+ (void)buffer; (void)idx; (void)maxlen;
+ if (character) {
+ _putchar(character);
+ }
+}
+
+
+// internal output function wrapper
+static inline void _out_fct(char character, void* buffer, size_t idx, size_t maxlen)
+{
+ (void)idx; (void)maxlen;
+ if (character) {
+ // buffer is the output fct pointer
+ ((out_fct_wrap_type*)buffer)->fct(character, ((out_fct_wrap_type*)buffer)->arg);
+ }
+}
+
+
+// internal secure strlen
+// \return The length of the string (excluding the terminating 0) limited by 'maxsize'
+static inline unsigned int _strnlen_s(const char* str, size_t maxsize)
+{
+ const char* s;
+ for (s = str; *s && maxsize--; ++s);
+ return (unsigned int)(s - str);
+}
+
+
+// internal test if char is a digit (0-9)
+// \return true if char is a digit
+static inline bool _is_digit(char ch)
+{
+ return (ch >= '0') && (ch <= '9');
+}
+
+
+// internal ASCII string to unsigned int conversion
+static unsigned int _atoi(const char** str)
+{
+ unsigned int i = 0U;
+ while (_is_digit(**str)) {
+ i = i * 10U + (unsigned int)(*((*str)++) - '0');
+ }
+ return i;
+}
+
+
+// output the specified string in reverse, taking care of any zero-padding
+static size_t _out_rev(out_fct_type out, char* buffer, size_t idx, size_t maxlen, const char* buf, size_t len, unsigned int width, unsigned int flags)
+{
+ const size_t start_idx = idx;
+
+ // pad spaces up to given width
+ if (!(flags & FLAGS_LEFT) && !(flags & FLAGS_ZEROPAD)) {
+ for (size_t i = len; i < width; i++) {
+ out(' ', buffer, idx++, maxlen);
+ }
+ }
+
+ // reverse string
+ while (len) {
+ out(buf[--len], buffer, idx++, maxlen);
+ }
+
+ // append pad spaces up to given width
+ if (flags & FLAGS_LEFT) {
+ while (idx - start_idx < width) {
+ out(' ', buffer, idx++, maxlen);
+ }
+ }
+
+ return idx;
+}
+
+
+// internal itoa format
+static size_t _ntoa_format(out_fct_type out, char* buffer, size_t idx, size_t maxlen, char* buf, size_t len, bool negative, unsigned int base, unsigned int prec, unsigned int width, unsigned int flags)
+{
+ // pad leading zeros
+ if (!(flags & FLAGS_LEFT)) {
+ if (width && (flags & FLAGS_ZEROPAD) && (negative || (flags & (FLAGS_PLUS | FLAGS_SPACE)))) {
+ width--;
+ }
+ while ((len < prec) && (len < PRINTF_NTOA_BUFFER_SIZE)) {
+ buf[len++] = '0';
+ }
+ while ((flags & FLAGS_ZEROPAD) && (len < width) && (len < PRINTF_NTOA_BUFFER_SIZE)) {
+ buf[len++] = '0';
+ }
+ }
+
+ // handle hash
+ if (flags & FLAGS_HASH) {
+ if (!(flags & FLAGS_PRECISION) && len && ((len == prec) || (len == width))) {
+ len--;
+ if (len && (base == 16U)) {
+ len--;
+ }
+ }
+ if ((base == 16U) && !(flags & FLAGS_UPPERCASE) && (len < PRINTF_NTOA_BUFFER_SIZE)) {
+ buf[len++] = 'x';
+ }
+ else if ((base == 16U) && (flags & FLAGS_UPPERCASE) && (len < PRINTF_NTOA_BUFFER_SIZE)) {
+ buf[len++] = 'X';
+ }
+ else if ((base == 2U) && (len < PRINTF_NTOA_BUFFER_SIZE)) {
+ buf[len++] = 'b';
+ }
+ if (len < PRINTF_NTOA_BUFFER_SIZE) {
+ buf[len++] = '0';
+ }
+ }
+
+ if (len < PRINTF_NTOA_BUFFER_SIZE) {
+ if (negative) {
+ buf[len++] = '-';
+ }
+ else if (flags & FLAGS_PLUS) {
+ buf[len++] = '+'; // ignore the space if the '+' exists
+ }
+ else if (flags & FLAGS_SPACE) {
+ buf[len++] = ' ';
+ }
+ }
+
+ return _out_rev(out, buffer, idx, maxlen, buf, len, width, flags);
+}
+
+
+// internal itoa for 'long' type
+static size_t _ntoa_long(out_fct_type out, char* buffer, size_t idx, size_t maxlen, unsigned long value, bool negative, unsigned long base, unsigned int prec, unsigned int width, unsigned int flags)
+{
+ char buf[PRINTF_NTOA_BUFFER_SIZE];
+ size_t len = 0U;
+
+ // no hash for 0 values
+ if (!value) {
+ flags &= ~FLAGS_HASH;
+ }
+
+ // write if precision != 0 and value is != 0
+ if (!(flags & FLAGS_PRECISION) || value) {
+ do {
+ const char digit = (char)(value % base);
+ buf[len++] = digit < 10 ? '0' + digit : (flags & FLAGS_UPPERCASE ? 'A' : 'a') + digit - 10;
+ value /= base;
+ } while (value && (len < PRINTF_NTOA_BUFFER_SIZE));
+ }
+
+ return _ntoa_format(out, buffer, idx, maxlen, buf, len, negative, (unsigned int)base, prec, width, flags);
+}
+
+
+// internal itoa for 'long long' type
+#if defined(PRINTF_SUPPORT_LONG_LONG)
+static size_t _ntoa_long_long(out_fct_type out, char* buffer, size_t idx, size_t maxlen, unsigned long long value, bool negative, unsigned long long base, unsigned int prec, unsigned int width, unsigned int flags)
+{
+ char buf[PRINTF_NTOA_BUFFER_SIZE];
+ size_t len = 0U;
+
+ // no hash for 0 values
+ if (!value) {
+ flags &= ~FLAGS_HASH;
+ }
+
+ // write if precision != 0 and value is != 0
+ if (!(flags & FLAGS_PRECISION) || value) {
+ do {
+ const char digit = (char)(value % base);
+ buf[len++] = digit < 10 ? '0' + digit : (flags & FLAGS_UPPERCASE ? 'A' : 'a') + digit - 10;
+ value /= base;
+ } while (value && (len < PRINTF_NTOA_BUFFER_SIZE));
+ }
+
+ return _ntoa_format(out, buffer, idx, maxlen, buf, len, negative, (unsigned int)base, prec, width, flags);
+}
+#endif // PRINTF_SUPPORT_LONG_LONG
+
+
+#if defined(PRINTF_SUPPORT_FLOAT)
+
+#if defined(PRINTF_SUPPORT_EXPONENTIAL)
+// forward declaration so that _ftoa can switch to exp notation for values > PRINTF_MAX_FLOAT
+static size_t _etoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, double value, unsigned int prec, unsigned int width, unsigned int flags);
+#endif
+
+
+// internal ftoa for fixed decimal floating point
+static size_t _ftoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, double value, unsigned int prec, unsigned int width, unsigned int flags)
+{
+ char buf[PRINTF_FTOA_BUFFER_SIZE];
+ size_t len = 0U;
+ double diff = 0.0;
+
+ // powers of 10
+ static const double pow10[] = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000 };
+
+ // test for special values
+ if (value != value)
+ return _out_rev(out, buffer, idx, maxlen, "nan", 3, width, flags);
+ if (value < -DBL_MAX)
+ return _out_rev(out, buffer, idx, maxlen, "fni-", 4, width, flags);
+ if (value > DBL_MAX)
+ return _out_rev(out, buffer, idx, maxlen, (flags & FLAGS_PLUS) ? "fni+" : "fni", (flags & FLAGS_PLUS) ? 4U : 3U, width, flags);
+
+ // test for very large values
+ // standard printf behavior is to print EVERY whole number digit -- which could be 100s of characters overflowing your buffers == bad
+ if ((value > PRINTF_MAX_FLOAT) || (value < -PRINTF_MAX_FLOAT)) {
+#if defined(PRINTF_SUPPORT_EXPONENTIAL)
+ return _etoa(out, buffer, idx, maxlen, value, prec, width, flags);
+#else
+ return 0U;
+#endif
+ }
+
+ // test for negative
+ bool negative = false;
+ if (value < 0) {
+ negative = true;
+ value = 0 - value;
+ }
+
+ // set default precision, if not set explicitly
+ if (!(flags & FLAGS_PRECISION)) {
+ prec = PRINTF_DEFAULT_FLOAT_PRECISION;
+ }
+ // limit precision to 9, cause a prec >= 10 can lead to overflow errors
+ while ((len < PRINTF_FTOA_BUFFER_SIZE) && (prec > 9U)) {
+ buf[len++] = '0';
+ prec--;
+ }
+
+ int whole = (int)value;
+ double tmp = (value - whole) * pow10[prec];
+ unsigned long frac = (unsigned long)tmp;
+ diff = tmp - frac;
+
+ if (diff > 0.5) {
+ ++frac;
+ // handle rollover, e.g. case 0.99 with prec 1 is 1.0
+ if (frac >= pow10[prec]) {
+ frac = 0;
+ ++whole;
+ }
+ }
+ else if (diff < 0.5) {
+ }
+ else if ((frac == 0U) || (frac & 1U)) {
+ // if halfway, round up if odd OR if last digit is 0
+ ++frac;
+ }
+
+ if (prec == 0U) {
+ diff = value - (double)whole;
+ if ((!(diff < 0.5) || (diff > 0.5)) && (whole & 1)) {
+ // exactly 0.5 and ODD, then round up
+ // 1.5 -> 2, but 2.5 -> 2
+ ++whole;
+ }
+ }
+ else {
+ unsigned int count = prec;
+ // now do fractional part, as an unsigned number
+ while (len < PRINTF_FTOA_BUFFER_SIZE) {
+ --count;
+ buf[len++] = (char)(48U + (frac % 10U));
+ if (!(frac /= 10U)) {
+ break;
+ }
+ }
+ // add extra 0s
+ while ((len < PRINTF_FTOA_BUFFER_SIZE) && (count-- > 0U)) {
+ buf[len++] = '0';
+ }
+ if (len < PRINTF_FTOA_BUFFER_SIZE) {
+ // add decimal
+ buf[len++] = '.';
+ }
+ }
+
+ // do whole part, number is reversed
+ while (len < PRINTF_FTOA_BUFFER_SIZE) {
+ buf[len++] = (char)(48 + (whole % 10));
+ if (!(whole /= 10)) {
+ break;
+ }
+ }
+
+ // pad leading zeros
+ if (!(flags & FLAGS_LEFT) && (flags & FLAGS_ZEROPAD)) {
+ if (width && (negative || (flags & (FLAGS_PLUS | FLAGS_SPACE)))) {
+ width--;
+ }
+ while ((len < width) && (len < PRINTF_FTOA_BUFFER_SIZE)) {
+ buf[len++] = '0';
+ }
+ }
+
+ if (len < PRINTF_FTOA_BUFFER_SIZE) {
+ if (negative) {
+ buf[len++] = '-';
+ }
+ else if (flags & FLAGS_PLUS) {
+ buf[len++] = '+'; // ignore the space if the '+' exists
+ }
+ else if (flags & FLAGS_SPACE) {
+ buf[len++] = ' ';
+ }
+ }
+
+ return _out_rev(out, buffer, idx, maxlen, buf, len, width, flags);
+}
+
+
+#if defined(PRINTF_SUPPORT_EXPONENTIAL)
+// internal ftoa variant for exponential floating-point type, contributed by Martijn Jasperse
+static size_t _etoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, double value, unsigned int prec, unsigned int width, unsigned int flags)
+{
+ // check for NaN and special values
+ if ((value != value) || (value > DBL_MAX) || (value < -DBL_MAX)) {
+ return _ftoa(out, buffer, idx, maxlen, value, prec, width, flags);
+ }
+
+ // determine the sign
+ const bool negative = value < 0;
+ if (negative) {
+ value = -value;
+ }
+
+ // default precision
+ if (!(flags & FLAGS_PRECISION)) {
+ prec = PRINTF_DEFAULT_FLOAT_PRECISION;
+ }
+
+ // determine the decimal exponent
+ // based on the algorithm by David Gay (https://www.ampl.com/netlib/fp/dtoa.c)
+ union {
+ uint64_t U;
+ double F;
+ } conv;
+
+ conv.F = value;
+ int exp2 = (int)((conv.U >> 52U) & 0x07FFU) - 1023; // effectively log2
+ conv.U = (conv.U & ((1ULL << 52U) - 1U)) | (1023ULL << 52U); // drop the exponent so conv.F is now in [1,2)
+ // now approximate log10 from the log2 integer part and an expansion of ln around 1.5
+ int expval = (int)(0.1760912590558 + exp2 * 0.301029995663981 + (conv.F - 1.5) * 0.289529654602168);
+ // now we want to compute 10^expval but we want to be sure it won't overflow
+ exp2 = (int)(expval * 3.321928094887362 + 0.5);
+ const double z = expval * 2.302585092994046 - exp2 * 0.6931471805599453;
+ const double z2 = z * z;
+ conv.U = (uint64_t)(exp2 + 1023) << 52U;
+ // compute exp(z) using continued fractions, see https://en.wikipedia.org/wiki/Exponential_function#Continued_fractions_for_ex
+ conv.F *= 1 + 2 * z / (2 - z + (z2 / (6 + (z2 / (10 + z2 / 14)))));
+ // correct for rounding errors
+ if (value < conv.F) {
+ expval--;
+ conv.F /= 10;
+ }
+
+ // the exponent format is "%+03d" and largest value is "307", so set aside 4-5 characters
+ unsigned int minwidth = ((expval < 100) && (expval > -100)) ? 4U : 5U;
+
+ // in "%g" mode, "prec" is the number of *significant figures* not decimals
+ if (flags & FLAGS_ADAPT_EXP) {
+ // do we want to fall-back to "%f" mode?
+ if ((value >= 1e-4) && (value < 1e6)) {
+ if ((int)prec > expval) {
+ prec = (unsigned)((int)prec - expval - 1);
+ }
+ else {
+ prec = 0;
+ }
+ flags |= FLAGS_PRECISION; // make sure _ftoa respects precision
+ // no characters in exponent
+ minwidth = 0U;
+ expval = 0;
+ }
+ else {
+ // we use one sigfig for the whole part
+ if ((prec > 0) && (flags & FLAGS_PRECISION)) {
+ --prec;
+ }
+ }
+ }
+
+ // will everything fit?
+ unsigned int fwidth = width;
+ if (width > minwidth) {
+ // we didn't fall-back so subtract the characters required for the exponent
+ fwidth -= minwidth;
+ } else {
+ // not enough characters, so go back to default sizing
+ fwidth = 0U;
+ }
+ if ((flags & FLAGS_LEFT) && minwidth) {
+ // if we're padding on the right, DON'T pad the floating part
+ fwidth = 0U;
+ }
+
+ // rescale the float value
+ if (expval) {
+ value /= conv.F;
+ }
+
+ // output the floating part
+ const size_t start_idx = idx;
+ idx = _ftoa(out, buffer, idx, maxlen, negative ? -value : value, prec, fwidth, flags & ~FLAGS_ADAPT_EXP);
+
+ // output the exponent part
+ if (minwidth) {
+ // output the exponential symbol
+ out((flags & FLAGS_UPPERCASE) ? 'E' : 'e', buffer, idx++, maxlen);
+ // output the exponent value
+ idx = _ntoa_long(out, buffer, idx, maxlen, (expval < 0) ? -expval : expval, expval < 0, 10, 0, minwidth-1, FLAGS_ZEROPAD | FLAGS_PLUS);
+ // might need to right-pad spaces
+ if (flags & FLAGS_LEFT) {
+ while (idx - start_idx < width) out(' ', buffer, idx++, maxlen);
+ }
+ }
+ return idx;
+}
+#endif // PRINTF_SUPPORT_EXPONENTIAL
+#endif // PRINTF_SUPPORT_FLOAT
+
+
+// internal vsnprintf
+static int _vsnprintf(out_fct_type out, char* buffer, const size_t maxlen, const char* format, va_list va)
+{
+ unsigned int flags, width, precision, n;
+ size_t idx = 0U;
+
+ if (!buffer) {
+ // use null output function
+ out = _out_null;
+ }
+
+ while (*format)
+ {
+ // format specifier? %[flags][width][.precision][length]
+ if (*format != '%') {
+ // no
+ out(*format, buffer, idx++, maxlen);
+ format++;
+ continue;
+ }
+ else {
+ // yes, evaluate it
+ format++;
+ }
+
+ // evaluate flags
+ flags = 0U;
+ do {
+ switch (*format) {
+ case '0': flags |= FLAGS_ZEROPAD; format++; n = 1U; break;
+ case '-': flags |= FLAGS_LEFT; format++; n = 1U; break;
+ case '+': flags |= FLAGS_PLUS; format++; n = 1U; break;
+ case ' ': flags |= FLAGS_SPACE; format++; n = 1U; break;
+ case '#': flags |= FLAGS_HASH; format++; n = 1U; break;
+ default : n = 0U; break;
+ }
+ } while (n);
+
+ // evaluate width field
+ width = 0U;
+ if (_is_digit(*format)) {
+ width = _atoi(&format);
+ }
+ else if (*format == '*') {
+ const int w = va_arg(va, int);
+ if (w < 0) {
+ flags |= FLAGS_LEFT; // reverse padding
+ width = (unsigned int)-w;
+ }
+ else {
+ width = (unsigned int)w;
+ }
+ format++;
+ }
+
+ // evaluate precision field
+ precision = 0U;
+ if (*format == '.') {
+ flags |= FLAGS_PRECISION;
+ format++;
+ if (_is_digit(*format)) {
+ precision = _atoi(&format);
+ }
+ else if (*format == '*') {
+ const int prec = (int)va_arg(va, int);
+ precision = prec > 0 ? (unsigned int)prec : 0U;
+ format++;
+ }
+ }
+
+ // evaluate length field
+ switch (*format) {
+ case 'l' :
+ flags |= FLAGS_LONG;
+ format++;
+ if (*format == 'l') {
+ flags |= FLAGS_LONG_LONG;
+ format++;
+ }
+ break;
+ case 'h' :
+ flags |= FLAGS_SHORT;
+ format++;
+ if (*format == 'h') {
+ flags |= FLAGS_CHAR;
+ format++;
+ }
+ break;
+#if defined(PRINTF_SUPPORT_PTRDIFF_T)
+ case 't' :
+ flags |= (sizeof(ptrdiff_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG);
+ format++;
+ break;
+#endif
+ case 'j' :
+ flags |= (sizeof(intmax_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG);
+ format++;
+ break;
+ case 'z' :
+ flags |= (sizeof(size_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG);
+ format++;
+ break;
+ default :
+ break;
+ }
+
+ // evaluate specifier
+ switch (*format) {
+ case 'd' :
+ case 'i' :
+ case 'u' :
+ case 'x' :
+ case 'X' :
+ case 'o' :
+ case 'b' : {
+ // set the base
+ unsigned int base;
+ if (*format == 'x' || *format == 'X') {
+ base = 16U;
+ }
+ else if (*format == 'o') {
+ base = 8U;
+ }
+ else if (*format == 'b') {
+ base = 2U;
+ }
+ else {
+ base = 10U;
+ flags &= ~FLAGS_HASH; // no hash for dec format
+ }
+ // uppercase
+ if (*format == 'X') {
+ flags |= FLAGS_UPPERCASE;
+ }
+
+ // no plus or space flag for u, x, X, o, b
+ if ((*format != 'i') && (*format != 'd')) {
+ flags &= ~(FLAGS_PLUS | FLAGS_SPACE);
+ }
+
+ // ignore '0' flag when precision is given
+ if (flags & FLAGS_PRECISION) {
+ flags &= ~FLAGS_ZEROPAD;
+ }
+
+ // convert the integer
+ if ((*format == 'i') || (*format == 'd')) {
+ // signed
+ if (flags & FLAGS_LONG_LONG) {
+#if defined(PRINTF_SUPPORT_LONG_LONG)
+ const long long value = va_arg(va, long long);
+ idx = _ntoa_long_long(out, buffer, idx, maxlen, (unsigned long long)(value > 0 ? value : 0 - value), value < 0, base, precision, width, flags);
+#endif
+ }
+ else if (flags & FLAGS_LONG) {
+ const long value = va_arg(va, long);
+ idx = _ntoa_long(out, buffer, idx, maxlen, (unsigned long)(value > 0 ? value : 0 - value), value < 0, base, precision, width, flags);
+ }
+ else {
+ const int value = (flags & FLAGS_CHAR) ? (char)va_arg(va, int) : (flags & FLAGS_SHORT) ? (short int)va_arg(va, int) : va_arg(va, int);
+ idx = _ntoa_long(out, buffer, idx, maxlen, (unsigned int)(value > 0 ? value : 0 - value), value < 0, base, precision, width, flags);
+ }
+ }
+ else {
+ // unsigned
+ if (flags & FLAGS_LONG_LONG) {
+#if defined(PRINTF_SUPPORT_LONG_LONG)
+ idx = _ntoa_long_long(out, buffer, idx, maxlen, va_arg(va, unsigned long long), false, base, precision, width, flags);
+#endif
+ }
+ else if (flags & FLAGS_LONG) {
+ idx = _ntoa_long(out, buffer, idx, maxlen, va_arg(va, unsigned long), false, base, precision, width, flags);
+ }
+ else {
+ const unsigned int value = (flags & FLAGS_CHAR) ? (unsigned char)va_arg(va, unsigned int) : (flags & FLAGS_SHORT) ? (unsigned short int)va_arg(va, unsigned int) : va_arg(va, unsigned int);
+ idx = _ntoa_long(out, buffer, idx, maxlen, value, false, base, precision, width, flags);
+ }
+ }
+ format++;
+ break;
+ }
+#if defined(PRINTF_SUPPORT_FLOAT)
+ case 'f' :
+ case 'F' :
+ if (*format == 'F') flags |= FLAGS_UPPERCASE;
+ idx = _ftoa(out, buffer, idx, maxlen, va_arg(va, double), precision, width, flags);
+ format++;
+ break;
+#if defined(PRINTF_SUPPORT_EXPONENTIAL)
+ case 'e':
+ case 'E':
+ case 'g':
+ case 'G':
+ if ((*format == 'g')||(*format == 'G')) flags |= FLAGS_ADAPT_EXP;
+ if ((*format == 'E')||(*format == 'G')) flags |= FLAGS_UPPERCASE;
+ idx = _etoa(out, buffer, idx, maxlen, va_arg(va, double), precision, width, flags);
+ format++;
+ break;
+#endif // PRINTF_SUPPORT_EXPONENTIAL
+#endif // PRINTF_SUPPORT_FLOAT
+ case 'c' : {
+ unsigned int l = 1U;
+ // pre padding
+ if (!(flags & FLAGS_LEFT)) {
+ while (l++ < width) {
+ out(' ', buffer, idx++, maxlen);
+ }
+ }
+ // char output
+ out((char)va_arg(va, int), buffer, idx++, maxlen);
+ // post padding
+ if (flags & FLAGS_LEFT) {
+ while (l++ < width) {
+ out(' ', buffer, idx++, maxlen);
+ }
+ }
+ format++;
+ break;
+ }
+
+ case 's' : {
+ const char* p = va_arg(va, char*);
+ unsigned int l = _strnlen_s(p, precision ? precision : (size_t)-1);
+ // pre padding
+ if (flags & FLAGS_PRECISION) {
+ l = (l < precision ? l : precision);
+ }
+ if (!(flags & FLAGS_LEFT)) {
+ while (l++ < width) {
+ out(' ', buffer, idx++, maxlen);
+ }
+ }
+ // string output
+ while ((*p != 0) && (!(flags & FLAGS_PRECISION) || precision--)) {
+ out(*(p++), buffer, idx++, maxlen);
+ }
+ // post padding
+ if (flags & FLAGS_LEFT) {
+ while (l++ < width) {
+ out(' ', buffer, idx++, maxlen);
+ }
+ }
+ format++;
+ break;
+ }
+
+ case 'p' : {
+ width = sizeof(void*) * 2U;
+ flags |= FLAGS_ZEROPAD | FLAGS_UPPERCASE;
+#if defined(PRINTF_SUPPORT_LONG_LONG)
+ const bool is_ll = sizeof(uintptr_t) == sizeof(long long);
+ if (is_ll) {
+ idx = _ntoa_long_long(out, buffer, idx, maxlen, (uintptr_t)va_arg(va, void*), false, 16U, precision, width, flags);
+ }
+ else {
+#endif
+ idx = _ntoa_long(out, buffer, idx, maxlen, (unsigned long)((uintptr_t)va_arg(va, void*)), false, 16U, precision, width, flags);
+#if defined(PRINTF_SUPPORT_LONG_LONG)
+ }
+#endif
+ format++;
+ break;
+ }
+
+ case '%' :
+ out('%', buffer, idx++, maxlen);
+ format++;
+ break;
+
+ default :
+ out(*format, buffer, idx++, maxlen);
+ format++;
+ break;
+ }
+ }
+
+ // termination
+ out((char)0, buffer, idx < maxlen ? idx : maxlen - 1U, maxlen);
+
+ // return written chars without terminating \0
+ return (int)idx;
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+int printf_(const char* format, ...)
+{
+ va_list va;
+ va_start(va, format);
+ char buffer[1];
+ const int ret = _vsnprintf(_out_char, buffer, (size_t)-1, format, va);
+ va_end(va);
+ return ret;
+}
+
+
+int sprintf_(char* buffer, const char* format, ...)
+{
+ va_list va;
+ va_start(va, format);
+ const int ret = _vsnprintf(_out_buffer, buffer, (size_t)-1, format, va);
+ va_end(va);
+ return ret;
+}
+
+
+int snprintf_(char* buffer, size_t count, const char* format, ...)
+{
+ va_list va;
+ va_start(va, format);
+ const int ret = _vsnprintf(_out_buffer, buffer, count, format, va);
+ va_end(va);
+ return ret;
+}
+
+
+int vprintf_(const char* format, va_list va)
+{
+ char buffer[1];
+ return _vsnprintf(_out_char, buffer, (size_t)-1, format, va);
+}
+
+
+int vsnprintf_(char* buffer, size_t count, const char* format, va_list va)
+{
+ return _vsnprintf(_out_buffer, buffer, count, format, va);
+}
+
+
+int fctprintf(void (*out)(char character, void* arg), void* arg, const char* format, ...)
+{
+ va_list va;
+ va_start(va, format);
+ const out_fct_wrap_type out_fct_wrap = { out, arg };
+ const int ret = _vsnprintf(_out_fct, (char*)(uintptr_t)&out_fct_wrap, (size_t)-1, format, va);
+ va_end(va);
+ return ret;
+}
diff --git a/uarch/uarch-printf.h b/uarch/uarch-printf.h
index 6104ccfb7..43341e9ef 100644
--- a/uarch/uarch-printf.h
+++ b/uarch/uarch-printf.h
@@ -1,117 +1,108 @@
-///////////////////////////////////////////////////////////////////////////////
-// \author (c) Marco Paland (info@paland.com)
-// 2014-2019, PALANDesign Hannover, Germany
-//
-// \license The MIT License (MIT)
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE 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
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 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 IN
-// THE SOFTWARE.
-//
-// \brief Tiny printf, sprintf and snprintf implementation, optimized for speed on
-// embedded systems with a very limited resources.
-// Use this instead of bloated standard/newlib printf.
-// These routines are thread safe and reentrant.
-//
-///////////////////////////////////////////////////////////////////////////////
-
-#ifndef _PRINTF_H_
-#define _PRINTF_H_
-
-#include
-#include
-
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-
-/**
- * Output a character to a custom device like UART, used by the printf() function
- * This function is declared here only. You have to write your custom implementation somewhere
- * \param character Character to output
- */
-void _putchar(char character);
-
-
-/**
- * Tiny printf implementation
- * You have to implement _putchar if you use printf()
- * To avoid conflicts with the regular printf() API it is overridden by macro defines
- * and internal underscore-appended functions like printf_() are used
- * \param format A string that specifies the format of the output
- * \return The number of characters that are written into the array, not counting the terminating null character
- */
-#define printf printf_
-int printf_(const char* format, ...);
-
-
-/**
- * Tiny sprintf implementation
- * Due to security reasons (buffer overflow) YOU SHOULD CONSIDER USING (V)SNPRINTF INSTEAD!
- * \param buffer A pointer to the buffer where to store the formatted string. MUST be big enough to store the output!
- * \param format A string that specifies the format of the output
- * \return The number of characters that are WRITTEN into the buffer, not counting the terminating null character
- */
-#define sprintf sprintf_
-int sprintf_(char* buffer, const char* format, ...);
-
-
-/**
- * Tiny snprintf/vsnprintf implementation
- * \param buffer A pointer to the buffer where to store the formatted string
- * \param count The maximum number of characters to store in the buffer, including a terminating null character
- * \param format A string that specifies the format of the output
- * \param va A value identifying a variable arguments list
- * \return The number of characters that COULD have been written into the buffer, not counting the terminating
- * null character. A value equal or larger than count indicates truncation. Only when the returned value
- * is non-negative and less than count, the string has been completely written.
- */
-#define snprintf snprintf_
-#define vsnprintf vsnprintf_
-int snprintf_(char* buffer, size_t count, const char* format, ...);
-int vsnprintf_(char* buffer, size_t count, const char* format, va_list va);
-
-
-/**
- * Tiny vprintf implementation
- * \param format A string that specifies the format of the output
- * \param va A value identifying a variable arguments list
- * \return The number of characters that are WRITTEN into the buffer, not counting the terminating null character
- */
-#define vprintf vprintf_
-int vprintf_(const char* format, va_list va);
-
-
-/**
- * printf with output function
- * You may use this as dynamic alternative to printf() with its fixed _putchar() output
- * \param out An output function which takes one character and an argument pointer
- * \param arg An argument pointer for user data passed to output function
- * \param format A string that specifies the format of the output
- * \return The number of characters that are sent to the output function, not counting the terminating null character
- */
-int fctprintf(void (*out)(char character, void* arg), void* arg, const char* format, ...);
-
-
-#ifdef __cplusplus
-}
-#endif
-
-
-#endif // _PRINTF_H_
+///////////////////////////////////////////////////////////////////////////////
+// \author (c) Marco Paland (info@paland.com)
+// 2014-2019, PALANDesign Hannover, Germany
+//
+// \license The MIT License (MIT)
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE 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
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 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 IN
+// THE SOFTWARE.
+//
+// \brief Tiny printf, sprintf and snprintf implementation, optimized for speed on
+// embedded systems with a very limited resources.
+// Use this instead of bloated standard/newlib printf.
+// These routines are thread safe and reentrant.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#ifndef _PRINTF_H_
+#define _PRINTF_H_
+
+#include
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Output a character to a custom device like UART, used by the printf() function
+ * This function is declared here only. You have to write your custom implementation somewhere
+ * \param character Character to output
+ */
+void _putchar(char character);
+
+/**
+ * Tiny printf implementation
+ * You have to implement _putchar if you use printf()
+ * To avoid conflicts with the regular printf() API it is overridden by macro defines
+ * and internal underscore-appended functions like printf_() are used
+ * \param format A string that specifies the format of the output
+ * \return The number of characters that are written into the array, not counting the terminating null character
+ */
+#define printf printf_
+int printf_(const char *format, ...);
+
+/**
+ * Tiny sprintf implementation
+ * Due to security reasons (buffer overflow) YOU SHOULD CONSIDER USING (V)SNPRINTF INSTEAD!
+ * \param buffer A pointer to the buffer where to store the formatted string. MUST be big enough to store the output!
+ * \param format A string that specifies the format of the output
+ * \return The number of characters that are WRITTEN into the buffer, not counting the terminating null character
+ */
+#define sprintf sprintf_
+int sprintf_(char *buffer, const char *format, ...);
+
+/**
+ * Tiny snprintf/vsnprintf implementation
+ * \param buffer A pointer to the buffer where to store the formatted string
+ * \param count The maximum number of characters to store in the buffer, including a terminating null character
+ * \param format A string that specifies the format of the output
+ * \param va A value identifying a variable arguments list
+ * \return The number of characters that COULD have been written into the buffer, not counting the terminating
+ * null character. A value equal or larger than count indicates truncation. Only when the returned value
+ * is non-negative and less than count, the string has been completely written.
+ */
+#define snprintf snprintf_
+#define vsnprintf vsnprintf_
+int snprintf_(char *buffer, size_t count, const char *format, ...);
+int vsnprintf_(char *buffer, size_t count, const char *format, va_list va);
+
+/**
+ * Tiny vprintf implementation
+ * \param format A string that specifies the format of the output
+ * \param va A value identifying a variable arguments list
+ * \return The number of characters that are WRITTEN into the buffer, not counting the terminating null character
+ */
+#define vprintf vprintf_
+int vprintf_(const char *format, va_list va);
+
+/**
+ * printf with output function
+ * You may use this as dynamic alternative to printf() with its fixed _putchar() output
+ * \param out An output function which takes one character and an argument pointer
+ * \param arg An argument pointer for user data passed to output function
+ * \param format A string that specifies the format of the output
+ * \return The number of characters that are sent to the output function, not counting the terminating null character
+ */
+int fctprintf(void (*out)(char character, void *arg), void *arg, const char *format, ...);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // _PRINTF_H_
diff --git a/uarch/uarch-runtime.cpp b/uarch/uarch-runtime.cpp
index bc52e5d62..bb8758875 100644
--- a/uarch/uarch-runtime.cpp
+++ b/uarch/uarch-runtime.cpp
@@ -103,23 +103,3 @@ extern "C" NO_RETURN void abort() {
// execution will never reach this point
__builtin_trap();
}
-
-namespace cartesi {
-
-void os_open_tty() {}
-
-void os_close_tty() {}
-
-bool os_poll_tty(uint64_t /*timeout_us*/) {
- return false;
-}
-
-int os_getchar() {
- return -1;
-}
-
-void os_putchar(uint8_t ch) {
- _putchar(ch);
-}
-
-} // namespace cartesi
diff --git a/uarch/uarch-runtime.h b/uarch/uarch-runtime.h
index cbf0f2464..96e804e13 100644
--- a/uarch/uarch-runtime.h
+++ b/uarch/uarch-runtime.h
@@ -17,8 +17,8 @@
#ifndef UARCH_RUNTIME_H
#define UARCH_RUNTIME_H
-#include "uarch-printf.h"
#include "compiler-defines.h"
+#include "uarch-printf.h"
#include
#include
From cf6a83ca02daddf71655134095ac26ef5dca4318 Mon Sep 17 00:00:00 2001
From: Diego Nehab <1635557+diegonehab@users.noreply.github.com>
Date: Thu, 9 Jan 2025 16:59:42 +0000
Subject: [PATCH 03/82] refactor: move TLB management to interpret.cpp
---
src/Makefile | 11 +-
src/find-pma-entry.h | 17 +-
src/host-addr.h | 98 ++++
src/i-state-access.h | 264 ++++++-----
src/interpret.cpp | 258 ++++++++---
src/machine-state.h | 2 +-
src/machine.cpp | 204 +++++---
src/machine.h | 31 ++
src/pma.h | 10 +
src/record-send-cmio-state-access.h | 21 +-
src/record-step-state-access.h | 254 +++++-----
src/replay-send-cmio-state-access.h | 22 +-
src/replay-step-state-access.h | 690 ++++++++++++++--------------
src/shadow-pmas.h | 10 +
src/shadow-state.h | 1 +
src/shadow-tlb-factory.cpp | 56 +--
src/shadow-tlb.h | 122 ++---
src/state-access.h | 249 ++++------
src/strict-aliasing.h | 94 ++--
src/tlb.h | 98 ++++
src/translate-virtual-address.h | 24 +-
src/uarch-bridge.h | 28 +-
src/uarch-solidity-compat.h | 23 +-
src/uarch-state-access.h | 4 +
src/uarch-step.cpp | 28 +-
25 files changed, 1480 insertions(+), 1139 deletions(-)
create mode 100644 src/host-addr.h
create mode 100644 src/tlb.h
diff --git a/src/Makefile b/src/Makefile
index 2fcc1a4c4..f7780c2fb 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -374,20 +374,21 @@ LIBCARTESI_OBJS:= \
base64.o \
interpret.o \
virtual-machine.o \
- uarch-machine.o \
- uarch-step.o \
- uarch-reset-state.o \
sha3.o \
machine-merkle-tree.o \
pristine-merkle-tree.o \
- uarch-interpret.o \
machine-c-api.o \
uarch-pristine-ram.o \
uarch-pristine-state-hash.o \
uarch-pristine-hash.o \
- send-cmio-response.o \
replay-step-state-access-interop.o
+ #send-cmio-response.o \
+ #uarch-machine.o \
+ #uarch-interpret.o \
+ #uarch-step.o \
+ #uarch-reset-state.o \
+
CARTESI_CLUA_OBJS:= \
clua.o \
clua-i-virtual-machine.o
diff --git a/src/find-pma-entry.h b/src/find-pma-entry.h
index a9a41f919..1e2b223b9 100644
--- a/src/find-pma-entry.h
+++ b/src/find-pma-entry.h
@@ -27,10 +27,11 @@ namespace cartesi {
/// \tparam STATE_ACCESS Class of machine state accessor object.
/// \param a Machine state accessor object.
/// \param paddr Target physical address of word.
+/// \param index Receives index where PMA entry was found.
/// \returns PMA entry where word falls, or empty sentinel.
template
-static FORCE_INLINE auto &find_pma_entry(STATE_ACCESS a, uint64_t paddr) {
- uint64_t index = 0;
+auto &find_pma_entry(STATE_ACCESS &a, uint64_t paddr, uint64_t &index) {
+ index = 0;
while (true) {
auto &pma = a.read_pma_entry(index);
const auto length = pma.get_length();
@@ -53,6 +54,18 @@ static FORCE_INLINE auto &find_pma_entry(STATE_ACCESS a, uint64_t paddr) {
}
}
+/// \brief Returns PMAs entry where a word falls.
+/// \tparam T uint8_t, uint16_t, uint32_t, or uint64_t.
+/// \tparam STATE_ACCESS Class of machine state accessor object.
+/// \param a Machine state accessor object.
+/// \param paddr Target physical address of word.
+/// \returns PMA entry where word falls, or empty sentinel.
+template
+FORCE_INLINE auto &find_pma_entry(STATE_ACCESS &a, uint64_t paddr) {
+ uint64_t index = 0;
+ return find_pma_entry(a, paddr, index);
+}
+
} // namespace cartesi
#endif // FIND_PMA_ENTRY_H
diff --git a/src/host-addr.h b/src/host-addr.h
new file mode 100644
index 000000000..b6a33f6d1
--- /dev/null
+++ b/src/host-addr.h
@@ -0,0 +1,98 @@
+// Copyright Cartesi and individual authors (see AUTHORS)
+// SPDX-License-Identifier: LGPL-3.0-or-later
+//
+// This program is free software: you can redistribute it and/or modify it under
+// the terms of the GNU Lesser General Public License as published by the Free
+// Software Foundation, either version 3 of the License, or (at your option) any
+// later version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT ANY
+// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+// PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License along
+// with this program (see COPYING). If not, see .
+//
+
+#ifndef HOST_ADDR_H
+#define HOST_ADDR_H
+
+namespace cartesi {
+
+// This is simply an uint64_t as a separate type, not automatically convertible to uint64_t
+// It prevents any attempt of passing a host_addr where one is not expected
+enum class host_addr : uint64_t {};
+
+// Comparison operaator
+static constexpr bool operator<(host_addr a, host_addr b) {
+ return static_cast(a) < static_cast(b);
+}
+
+// Addition between host_addr
+static constexpr host_addr operator+(host_addr a, host_addr b) {
+ return host_addr{static_cast(a) + static_cast(b)};
+}
+
+// Subtraction between host_addr
+static constexpr host_addr operator-(host_addr a, host_addr b) {
+ return host_addr{static_cast(a) - static_cast(b)};
+}
+
+// Addition between host_addr and uint64_t
+static constexpr host_addr operator+(uint64_t a, host_addr b) {
+ return host_addr{a + static_cast(b)};
+}
+
+// Addition between host_addr and uint64_t
+static constexpr host_addr operator+(host_addr a, uint64_t b) {
+ return host_addr{static_cast(a) + b};
+}
+
+// Subtraction between host_addr and uint64_t
+static constexpr host_addr operator-(uint64_t a, host_addr b) {
+ return host_addr{a - static_cast(b)};
+}
+
+// Subtraction between host_addr and uint64_t
+static constexpr host_addr operator-(host_addr a, uint64_t b) {
+ return host_addr{static_cast(a) - b};
+}
+
+/// \brief Converts pointer to host_addr.
+/// \tparam PTR Pointer type to perform the cast.
+/// \param ptr The pointer to retrieve its unsigned integer representation.
+/// \returns Corresponding host_addr.
+/// \details Use cast_host_addr_to_ptr The address returned by this function,
+/// can later be converted to a pointer using cast_addr_to_ptr,
+/// and must be read/written using aliased_aligned_read/aliased_aligned_write,
+/// otherwise strict aliasing rules may be violated.
+static inline host_addr cast_ptr_to_host_addr(const void *ptr) {
+ static_assert(sizeof(void *) == sizeof(uintptr_t));
+ static_assert(sizeof(host_addr) >= sizeof(uintptr_t));
+ // Note that bellow we cast the pointer to void* first,
+ // according to the C spec this is required is to ensure the same presentation, before casting to uintptr_t
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast,bugprone-casting-through-void)
+ return host_addr{reinterpret_cast(ptr)};
+}
+
+/// \brief Casts a pointer to an unsigned integer.
+/// \details The pointer returned by this function
+/// must only be read/written using aliased_aligned_read/aliased_aligned_write,
+/// otherwise strict aliasing rules may be violated.
+/// \tparam T Unsigned integer type to cast to.
+/// \tparam PTR Pointer type to perform the cast.
+/// \param addr The address of the pointer represented by an unsigned integer.
+/// \returns A pointer.
+static inline void *cast_host_addr_to_ptr(host_addr host_addr) {
+ // Enforcement on type arguments
+ static_assert(sizeof(void *) == sizeof(uintptr_t));
+ static_assert(sizeof(host_addr) >= sizeof(uintptr_t));
+ // Note that bellow we cast the address to void* first,
+ // according to the C spec this is required is to ensure the same presentation, before casting to PTR
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast,bugprone-casting-through-void,performance-no-int-to-ptr)
+ return reinterpret_cast(static_cast(host_addr));
+}
+
+} // namespace cartesi
+
+#endif
diff --git a/src/i-state-access.h b/src/i-state-access.h
index d255899ee..2b2fad014 100644
--- a/src/i-state-access.h
+++ b/src/i-state-access.h
@@ -26,13 +26,25 @@
#include "compiler-defines.h"
#include "meta.h"
-#include "shadow-tlb.h"
+#include "tlb.h"
namespace cartesi {
// Forward declarations
enum class bracket_type;
+// Type trait that should return the pma_entry type for a state access class
+template
+struct i_state_access_pma_entry {};
+template
+using i_state_access_pma_entry_t = typename i_state_access_pma_entry::type;
+
+// Type trait that should return the fast_addr type for a state access class
+template
+struct i_state_access_fast_addr {};
+template
+using i_state_access_fast_addr_t = typename i_state_access_fast_addr::type;
+
/// \class i_state_access
/// \brief Interface for machine state access.
/// \details \{
@@ -40,8 +52,8 @@ enum class bracket_type;
/// The "run" function does not need a log, and must be as fast as possible.
/// Both functions share the exact same implementation of what it means to advance the machine state by one cycle.
/// In this common implementation, all state accesses go through a class that implements the i_state_access interface.
-/// When looging is needed, a logged_state_access class is used.
-/// When no logging is needed, a state_access class is used.
+/// When logging is needed, a record state access class is used.
+/// When no logging is needed, a direct state access class is used.
///
/// In a typical design, i_state_access would be pure virtual.
/// For speed, we avoid virtual methods and instead use templates.
@@ -54,7 +66,7 @@ enum class bracket_type;
/// Methods are provided to read and write each state component.
/// \}
/// \tparam DERIVED Derived class implementing the interface. (An example of CRTP.)
-template
+template
class i_state_access { // CRTP
i_state_access() = default;
friend DERIVED;
@@ -70,6 +82,10 @@ class i_state_access { // CRTP
}
public:
+ using pma_entry = i_state_access_pma_entry_t;
+ using fast_addr = i_state_access_fast_addr_t;
+
+ //??D We should probably remove this from the interface
/// \brief Returns machine state for direct access.
auto &get_naked_state() {
return derived().do_get_naked_state();
@@ -99,8 +115,8 @@ class i_state_access { // CRTP
/// \brief Writes register to general-purpose register.
/// \param reg Register index.
/// \param val New register value.
- /// \details Writes to register zero *break* the machine. There is an assertion to catch this, but NDEBUG will let
- /// the value pass through.
+ /// \details Writes to register zero *break* the machine.
+ /// There is an assertion to catch this, but NDEBUG will let the value pass through.
void write_x(int reg, uint64_t val) {
return derived().do_write_x(reg, val);
}
@@ -591,21 +607,9 @@ class i_state_access { // CRTP
return derived().do_read_htif_iyield();
}
- /// \brief Poll for external interrupts.
- /// \param mcycle Current machine mcycle.
- /// \param mcycle_max Maximum mcycle to wait for interrupts.
- /// \returns A pair, the first value is the new machine mcycle advanced by the relative elapsed time while
- /// polling, the second value is a boolean that is true when the poll is stopped due do an external interrupt
- /// request.
- /// \details When mcycle_max is greater than mcycle, this function will sleep until an external interrupt
- /// is triggered or mcycle_max relative elapsed time is reached.
- std::pair poll_external_interrupts(uint64_t mcycle, uint64_t mcycle_max) {
- return derived().do_poll_external_interrupts(mcycle, mcycle_max);
- }
-
/// \brief Reads PMA entry at a given index.
/// \param index Index of PMA
- PMA_ENTRY_TYPE &read_pma_entry(uint64_t index) {
+ pma_entry &read_pma_entry(uint64_t index) {
return derived().do_read_pma_entry(index);
}
@@ -631,98 +635,124 @@ class i_state_access { // CRTP
return derived().do_write_memory(paddr, data, length);
}
- /// \brief Reads a word from memory.
- /// \tparam T Type of word to read.
- /// \param paddr Target physical address.
- /// \param hpage Pointer to page start in host memory.
- /// \param hoffset Offset in page (must be aligned to sizeof(T)).
- /// \param pval Pointer to word receiving value.
- template
- void read_memory_word(uint64_t paddr, const unsigned char *hpage, uint64_t hoffset, T *pval) {
- static_assert(std::is_integral_v && sizeof(T) <= sizeof(uint64_t), "unsupported type");
- return derived().template do_read_memory_word(paddr, hpage, hoffset, pval);
- }
-
- /// \brief Writes a word to memory.
- /// \tparam T Type of word to write.
- /// \param paddr Target physical address.
- /// \param hpage Pointer to page start in host memory.
- /// \param hoffset Offset in page (must be aligned to sizeof(T)).
- /// \param val Value to be written.
- template
- void write_memory_word(uint64_t paddr, unsigned char *hpage, uint64_t hoffset, T val) {
- static_assert(std::is_integral_v && sizeof(T) <= sizeof(uint64_t), "unsupported type");
- return derived().template do_write_memory_word(paddr, hpage, hoffset, val);
- }
-
- auto get_host_memory(PMA_ENTRY_TYPE &pma) {
- return derived().do_get_host_memory(pma);
- }
-
- /// \brief Try to translate a virtual address to a host pointer through the TLB.
- /// \tparam ETYPE TLB entry type.
- /// \tparam T Type of word that would be read with the pointer.
- /// \param vaddr Target virtual address.
- /// \param phptr Pointer to host pointer receiving value.
- /// \returns True if successful (TLB hit), false otherwise.
- template
- bool translate_vaddr_via_tlb(uint64_t vaddr, unsigned char **phptr) {
- return derived().template do_translate_vaddr_via_tlb(vaddr, phptr);
+ /// \brief Write a data buffer to memory padded with 0
+ /// \param paddr Destination physical address.
+ /// \param data Pointer to source data buffer.
+ /// \param data_length Length of data buffer.
+ /// \param write_length_log2_size Log2 size of the total write length.
+ void write_memory_with_padding(uint64_t paddr, const unsigned char *data, uint64_t data_length,
+ int write_length_log2_size) {
+ return derived().do_write_memory_with_padding(paddr, data, data_length, write_length_log2_size);
}
- /// \brief Try to read a word from memory through the TLB.
- /// \tparam ETYPE TLB entry type.
- /// \tparam T Type of word to read.
- /// \param vaddr Target virtual address.
+ /// \brief Reads a word from memory.
+ /// \tparam T Type of word to read, potentially unaligned.
+ /// \tparam A Type to which \p paddr and \p haddr are known to be aligned.
+ /// \param faddr Implementation-defined fast address.
/// \param pval Pointer to word receiving value.
- /// \returns True if successful (TLB hit), false otherwise.
- template
- bool read_memory_word_via_tlb(uint64_t vaddr, T *pval) {
- static_assert(std::is_integral_v && sizeof(T) <= sizeof(uint64_t), "unsupported type");
- return derived().template do_read_memory_word_via_tlb(vaddr, pval);
+ /// \warning T must not cross page boundary starting from \p faddr
+ /// \warning T may or may not cross a Merkle tree word boundary starting from \p faddr!
+ template
+ void read_memory_word(fast_addr faddr, uint64_t pma_index, T *pval) {
+ static_assert(std::is_integral::value && sizeof(T) <= sizeof(uint64_t), "unsupported type");
+ return derived().template do_read_memory_word(faddr, pma_index, pval);
}
- /// \brief Try to write a word to memory through the TLB.
- /// \tparam ETYPE TLB entry type.
+ /// \brief Writes a word to memory.
/// \tparam T Type of word to write.
- /// \param vaddr Target virtual address.
+ /// \tparam A Type to which \p paddr and \p haddr are known to be aligned.
+ /// \param faddr Implementation-defined fast address.
/// \param val Value to be written.
- /// \returns True if successful (TLB hit), false otherwise.
- template
- bool write_memory_word_via_tlb(uint64_t vaddr, T val) {
- static_assert(std::is_integral_v && sizeof(T) <= sizeof(uint64_t), "unsupported type");
- return derived().template do_write_memory_word_via_tlb(vaddr, val);
+ /// \details \p haddr is ONLY valid when there is a host machine.
+ /// It should never be referenced outside of this context.
+ /// \warning T must not cross page boundary starting from \p faddr
+ /// \warning T may or may not cross a Merkle tree word boundary starting from \p faddr!
+ template
+ void write_memory_word(fast_addr faddr, uint64_t pma_index, T val) {
+ static_assert(std::is_integral::value && sizeof(T) <= sizeof(uint64_t), "unsupported type");
+ return derived().template do_write_memory_word(faddr, pma_index, val);
+ }
+
+ /// \brief Reads TLB's vaddr_page
+ /// \tparam USE TLB set
+ /// \param slot_index Slot index
+ /// \returns Value in slot.
+ template
+ uint64_t read_tlb_vaddr_page(uint64_t slot_index) {
+ return derived().template do_read_tlb_vaddr_page