Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
timotheecour committed May 15, 2021
1 parent 888500e commit 344d5e0
Show file tree
Hide file tree
Showing 7 changed files with 3,725 additions and 36 deletions.
77 changes: 48 additions & 29 deletions compiler/builddeps.nim
Original file line number Diff line number Diff line change
Expand Up @@ -6,43 +6,62 @@ we could refine the logic to delay compilation until cgen phase instead.
(see also `tests/osproc/treadlines.nim`)
* allow some form of reporting so that caller can tell whether a dependency
doesn't exist, or was already built, or builds with error, or builds successfully, etc.
* allow some way to expose this to user code
]#

import std/[osproc, os, strutils]
import msgs, options, ast, lineinfos, extccomp, pathutils

const prefix = "__" # prevent clashing with user files

type Job = object
input: string
copts: string

proc addDependencyImpl(conf: ConfigRef, name: string, info: TLineInfo, jobs: seq[Job], linkerFlags = "") =
if name in conf.dependencies: return
conf.dependencies.add name
# xxx we could also build this under $nimb/build/
let dir = conf.getNimcacheDir().string
createDir dir
for job in jobs:
let objFile = dir / ("$1_$2_$3.o" % [prefix, name, job.input.splitFile.name])
if optForceFullMake in conf.globalOptions or not objFile.fileExists:
# xxx
# let cppExe = getCompilerExe(c.config; compiler: TSystemCC; cfile: AbsoluteFile): string =
# compilePattern = getCompilerExe(conf, c, cfile.cname)
when defined(osx):
let cppExe = "clang++"
else:
let cppExe = "g++"
when defined(linux):
# PRTEMP: avoids:
# /usr/bin/ld: /home/runner/work/Nim/Nim/nimcache/r_linux_amd64/nimdragonbox.o: relocation R_X86_64_32S against `.rodata' can not be used when making a PIE object; recompile with -fPIE
let options = "-fPIE"
else:
let options = ""
let inputFile = conf.libpath.string / job.input
let cmd = "$# -c $# $# -O3 -o $# $#" % [cppExe.quoteShell, job.copts, options, objFile.quoteShell, inputFile.quoteShell]
# xxx use md5 hash to recompile if needed
writePrettyCmdsStderr displayProgressCC(conf, inputFile, cmd)
let (outp, status) = execCmdEx(cmd)
if status != 0:
localError(conf, info, "building '$#' failed: cmd: $#\noutput:\n$#" % [name, cmd, outp])
conf.addExternalFileToLink(objFile.AbsoluteFile)
if linkerFlags.len > 0:
# conf.addExternalFileToLink(linkerFlags)
conf.addLinkOption(linkerFlags)

proc addDependency*(conf: ConfigRef, name: string, info: TLineInfo) =
case name
of "dragonbox":
if name notin conf.dependencies:
conf.dependencies.add name
# xxx we could also build this under $nimb/build/
let dir = conf.getNimcacheDir().string
createDir dir
let objFile = dir / ("$1nimdragonbox.o" % prefix)
if optForceFullMake in conf.globalOptions or not objFile.fileExists:
# xxx
# let cppExe = getCompilerExe(c.config; compiler: TSystemCC; cfile: AbsoluteFile): string =
# compilePattern = getCompilerExe(conf, c, cfile.cname)
when defined(osx):
let cppExe = "clang++"
else:
let cppExe = "g++"
when defined(linux):
# PRTEMP: avoids:
# /usr/bin/ld: /home/runner/work/Nim/Nim/nimcache/r_linux_amd64/nimdragonbox.o: relocation R_X86_64_32S against `.rodata' can not be used when making a PIE object; recompile with -fPIE
let options = "-fPIE"
else:
let options = ""
let inputFile = conf.libpath.string / "vendor/drachennest/dragonbox.cc"
let cmd = "$# -c -std=c++11 $# -O3 -o $# $#" % [cppExe.quoteShell, options, objFile.quoteShell, inputFile.quoteShell]
# xxx use md5 hash to recompile if needed
writePrettyCmdsStderr displayProgressCC(conf, inputFile, cmd)
let (outp, status) = execCmdEx(cmd)
if status != 0:
localError(conf, info, "building '$#' failed: cmd: $#\noutput:\n$#" % [name, cmd, outp])
conf.addExternalFileToLink(objFile.AbsoluteFile)
of "drachennest_dragonbox":
addDependencyImpl(conf, name, info, jobs = @[Job(input: "vendor/drachennest/dragonbox.cc", copts: "-std=c++11")])
of "dragonbox_dragonbox":
let includeDir = conf.libpath.string / "vendor/dragonbox/include"
let copts = "-std=c++17 -I $#" % includeDir.quoteShell
let jobs = @[
Job(input: "vendor/dragonbox/impl.cc", copts: copts),
Job(input: "vendor/dragonbox/dragonbox_to_chars.cpp", copts: copts)]
addDependencyImpl(conf, name, info, jobs = jobs, linkerFlags = "-lc++")
else:
localError(conf, info, "expected: 'dragonbox', got: '$1'" % name)
27 changes: 20 additions & 7 deletions lib/std/strfloats.nim
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,7 @@ proc toStringSprintf*(buf: var array[strFloatBufLen, char]; value: BiggestFloat)
result = 3

when useDragonbox:
import private/deputils
addDependency("dragonbox")
proc dragonboxToString(buf: ptr char, value: cdouble): ptr char {.importc: "nim_dragonbox_Dtoa".}

proc toStringDragonbox*(buf: var array[strFloatBufLen, char]; value: BiggestFloat): int {.inline.} =
let first = buf[0].addr
let ret = dragonboxToString(first, value)
template fixup(buf, ret, first, result) =
result = cast[int](ret) - cast[int](first)
if buf[result-1] in {'f', 'n'}: # inf, -inf, nan
return result
Expand All @@ -90,6 +84,25 @@ when useDragonbox:
buf[result+1] = '0'
result += 2

import private/deputils

addDependency("dragonbox_dragonbox")
proc nimtoStringDragonbox0Impl(buf: ptr char, value: cdouble): ptr char {.importc: "nimtoStringDragonbox0ImplDouble".}
proc nimtoStringDragonbox0Impl(buf: ptr char, value: cfloat): ptr char {.importc: "nimtoStringDragonbox0ImplFloat".}

addDependency("drachennest_dragonbox")
proc toStringDragonboxImpl(buf: ptr char, value: cdouble): ptr char {.importc: "nim_dragonbox_Dtoa".}

proc toStringDragonbox0*(buf: var array[strFloatBufLen, char]; value: float|float32): int {.inline.} =
let first = buf[0].addr
let ret = nimtoStringDragonbox0Impl(first, value)
fixup(buf, ret, first, result)

proc toStringDragonbox*(buf: var array[strFloatBufLen, char]; value: BiggestFloat): int {.inline.} =
let first = buf[0].addr
let ret = toStringDragonboxImpl(first, value)
fixup(buf, ret, first, result)

template toString*(buf: var array[strFloatBufLen, char]; value: BiggestFloat): int =
toStringDragonbox(buf, value)
else:
Expand Down
238 changes: 238 additions & 0 deletions lib/vendor/dragonbox/dragonbox_to_chars.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
// The contents of this file is based on contents of:
//
// https://github.com/ulfjack/ryu/blob/master/ryu/common.h,
// https://github.com/ulfjack/ryu/blob/master/ryu/d2s.c, and
// https://github.com/ulfjack/ryu/blob/master/ryu/f2s.c,
//
// which are distributed under the following terms:
//--------------------------------------------------------------------------------
// Copyright 2018 Ulf Adams
//
// The contents of this file may be used under the terms of the Apache License,
// Version 2.0.
//
// (See accompanying file LICENSE-Apache or copy at
// http://www.apache.org/licenses/LICENSE-2.0)
//
// Alternatively, the contents of this file may be used under the terms of
// the Boost Software License, Version 1.0.
// (See accompanying file LICENSE-Boost or copy at
// https://www.boost.org/LICENSE_1_0.txt)
//
// Unless required by applicable law or agreed to in writing, this software
// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.
//--------------------------------------------------------------------------------
// Modifications Copyright 2020 Junekey Jeon
//
// Following modifications were made to the original contents:
// - Put everything inside the namespace jkj::dragonbox::to_chars_detail
// - Combined decimalLength9 (from common.h) and decimalLength17 (from d2s.c)
// into a single template function decimal_length
// - Combined to_chars (from f2s.c) and to_chars (from d2s.c) into a
// single template function fp_to_chars_impl
// - Removed index counting statements; replaced them with pointer increments
// - Removed usages of DIGIT_TABLE; replaced them with radix_100_table
//
// These modifications, together with other contents of this file may be used
// under the same terms as the original contents.


#include "dragonbox/dragonbox_to_chars.h"

namespace jkj::dragonbox {
namespace to_chars_detail {
static constexpr char radix_100_table[] = {
'0', '0', '0', '1', '0', '2', '0', '3', '0', '4',
'0', '5', '0', '6', '0', '7', '0', '8', '0', '9',
'1', '0', '1', '1', '1', '2', '1', '3', '1', '4',
'1', '5', '1', '6', '1', '7', '1', '8', '1', '9',
'2', '0', '2', '1', '2', '2', '2', '3', '2', '4',
'2', '5', '2', '6', '2', '7', '2', '8', '2', '9',
'3', '0', '3', '1', '3', '2', '3', '3', '3', '4',
'3', '5', '3', '6', '3', '7', '3', '8', '3', '9',
'4', '0', '4', '1', '4', '2', '4', '3', '4', '4',
'4', '5', '4', '6', '4', '7', '4', '8', '4', '9',
'5', '0', '5', '1', '5', '2', '5', '3', '5', '4',
'5', '5', '5', '6', '5', '7', '5', '8', '5', '9',
'6', '0', '6', '1', '6', '2', '6', '3', '6', '4',
'6', '5', '6', '6', '6', '7', '6', '8', '6', '9',
'7', '0', '7', '1', '7', '2', '7', '3', '7', '4',
'7', '5', '7', '6', '7', '7', '7', '8', '7', '9',
'8', '0', '8', '1', '8', '2', '8', '3', '8', '4',
'8', '5', '8', '6', '8', '7', '8', '8', '8', '9',
'9', '0', '9', '1', '9', '2', '9', '3', '9', '4',
'9', '5', '9', '6', '9', '7', '9', '8', '9', '9'
};

template <class UInt>
static constexpr std::uint32_t decimal_length(UInt const v) {
if constexpr (std::is_same_v<UInt, std::uint32_t>) {
// Function precondition: v is not a 10-digit number.
// (f2s: 9 digits are sufficient for round-tripping.)
// (d2fixed: We print 9-digit blocks.)
assert(v < 1000000000);
if (v >= 100000000) { return 9; }
if (v >= 10000000) { return 8; }
if (v >= 1000000) { return 7; }
if (v >= 100000) { return 6; }
if (v >= 10000) { return 5; }
if (v >= 1000) { return 4; }
if (v >= 100) { return 3; }
if (v >= 10) { return 2; }
return 1;
}
else {
static_assert(std::is_same_v<UInt, std::uint64_t>);
// This is slightly faster than a loop.
// The average output length is 16.38 digits, so we check high-to-low.
// Function precondition: v is not an 18, 19, or 20-digit number.
// (17 digits are sufficient for round-tripping.)
assert(v < 100000000000000000L);
if (v >= 10000000000000000L) { return 17; }
if (v >= 1000000000000000L) { return 16; }
if (v >= 100000000000000L) { return 15; }
if (v >= 10000000000000L) { return 14; }
if (v >= 1000000000000L) { return 13; }
if (v >= 100000000000L) { return 12; }
if (v >= 10000000000L) { return 11; }
if (v >= 1000000000L) { return 10; }
if (v >= 100000000L) { return 9; }
if (v >= 10000000L) { return 8; }
if (v >= 1000000L) { return 7; }
if (v >= 100000L) { return 6; }
if (v >= 10000L) { return 5; }
if (v >= 1000L) { return 4; }
if (v >= 100L) { return 3; }
if (v >= 10L) { return 2; }
return 1;
}
}

template <class Float>
static char* to_chars_impl(unsigned_fp_t<Float> v, char* buffer)
{
auto output = v.significand;
auto const olength = decimal_length(output);

// Print the decimal digits.
// The following code is equivalent to:
// for (uint32_t i = 0; i < olength - 1; ++i) {
// const uint32_t c = output % 10; output /= 10;
// result[index + olength - i] = (char) ('0' + c);
// }
// result[index] = '0' + output % 10;

uint32_t i = 0;
if constexpr (sizeof(Float) == 8) {
// We prefer 32-bit operations, even on 64-bit platforms.
// We have at most 17 digits, and uint32_t can store 9 digits.
// If output doesn't fit into uint32_t, we cut off 8 digits,
// so the rest will fit into uint32_t.
if ((output >> 32) != 0) {
// Expensive 64-bit division.
const uint64_t q = output / 100000000;
uint32_t output2 = ((uint32_t)output) - 100000000 * ((uint32_t)q);
output = q;

const uint32_t c = output2 % 10000;
output2 /= 10000;
const uint32_t d = output2 % 10000;
const uint32_t c0 = (c % 100) << 1;
const uint32_t c1 = (c / 100) << 1;
const uint32_t d0 = (d % 100) << 1;
const uint32_t d1 = (d / 100) << 1;
memcpy(buffer + olength - i - 1, radix_100_table + c0, 2);
memcpy(buffer + olength - i - 3, radix_100_table + c1, 2);
memcpy(buffer + olength - i - 5, radix_100_table + d0, 2);
memcpy(buffer + olength - i - 7, radix_100_table + d1, 2);
i += 8;
}
}

auto output2 = (uint32_t)output;
while (output2 >= 10000) {
#ifdef __clang__ // https://bugs.llvm.org/show_bug.cgi?id=38217
const uint32_t c = output2 - 10000 * (output2 / 10000);
#else
const uint32_t c = output2 % 10000;
#endif
output2 /= 10000;
const uint32_t c0 = (c % 100) << 1;
const uint32_t c1 = (c / 100) << 1;
memcpy(buffer + olength - i - 1, radix_100_table + c0, 2);
memcpy(buffer + olength - i - 3, radix_100_table + c1, 2);
i += 4;
}
if (output2 >= 100) {
const uint32_t c = (output2 % 100) << 1;
output2 /= 100;
memcpy(buffer + olength - i - 1, radix_100_table + c, 2);
i += 2;
}
if (output2 >= 10) {
const uint32_t c = output2 << 1;
// We can't use memcpy here: the decimal dot goes between these two digits.
buffer[olength - i] = radix_100_table[c + 1];
buffer[0] = radix_100_table[c];
}
else {
buffer[0] = (char)('0' + output2);
}

// Print decimal point if needed.
if (olength > 1) {
buffer[1] = '.';
buffer += olength + 1;
}
else {
++buffer;
}

// Print the exponent.
*buffer = 'E';
++buffer;
int32_t exp = v.exponent + (int32_t)olength - 1;
if (exp < 0) {
*buffer = '-';
++buffer;
exp = -exp;
}
if constexpr (sizeof(Float) == 8) {
if (exp >= 100) {
const int32_t c = exp % 10;
memcpy(buffer, radix_100_table + 2 * (exp / 10), 2);
buffer[2] = (char)('0' + c);
buffer += 3;
}
else if (exp >= 10) {
memcpy(buffer, radix_100_table + 2 * exp, 2);
buffer += 2;
}
else {
*buffer = (char)('0' + exp);
++buffer;
}
}
else {
if (exp >= 10) {
memcpy(buffer, radix_100_table + 2 * exp, 2);
buffer += 2;
}
else {
*buffer = (char)('0' + exp);
++buffer;
}
}

return buffer;
}

char* to_chars(unsigned_fp_t<float> v, char* buffer) {
return to_chars_impl(v, buffer);
}
char* to_chars(unsigned_fp_t<double> v, char* buffer) {
return to_chars_impl(v, buffer);
}
}
}
10 changes: 10 additions & 0 deletions lib/vendor/dragonbox/impl.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#include "dragonbox/dragonbox_to_chars.h"

extern "C" char* nimtoStringDragonbox0ImplDouble(char* buffer, double value){
return jkj::dragonbox::to_chars_n(value, buffer);
// could also pass options, eg: `jkj::dragonbox::policy::cache::compressed`
}

extern "C" char* nimtoStringDragonbox0ImplFloat(char* buffer, float value){
return jkj::dragonbox::to_chars_n(value, buffer);
}
Loading

0 comments on commit 344d5e0

Please sign in to comment.