reorganize files, fix wasm, good to go

This commit is contained in:
Matthew 2026-05-03 12:42:00 +10:00
parent 83d09f6b49
commit b0c0120fc4
46 changed files with 1894 additions and 976 deletions

View File

@ -21,10 +21,10 @@ if [ -n "$wasm" ]; then
cpp_compiler="clang++"
c_compiler="clang"
flags="-target wasm32 -nostdlib -Wl,--no-entry -c -static -mbulk-memory -mbulk-memory-opt -matomics -msimd128 -mno-gc"
flags="-target wasm32 -nostdlib -c -static -mbulk-memory -mbulk-memory-opt -matomics -msimd128 -mno-gc"
files="${ext_path}/tinyalloc/tinyalloc.c"
includes="-I${ext_path}/tinyalloc"
out=""
out="-obuild/dlibincludes.wasm"
$c_compiler $flags $includes $files $out
else

View File

@ -39,9 +39,7 @@ alias f64 = double;
alias b32 = uint;
alias intptr = i64;
alias uintptr = u64;
alias uintptr = size_t;
alias usize = size_t;
alias Vec2 = Vector!(f32, 2);

View File

@ -72,6 +72,12 @@ WasmGrow(u32 size)
return grow(0, size);
}
extern(C) u32
WasmGrow2(u32 index, u32 size)
{
return grow(0, size);
}
extern(C) u32
WasmSize()
{

View File

@ -5,7 +5,7 @@ import dlib.util;
import std.traits;
import std.math.traits;
import std.meta;
import core.internal.traits : allSatisfy;
public import std.math.rounding : round;
public import core.math : sqrt, cos, sin;
@ -815,7 +815,7 @@ align(16) struct Matrix(T, int D)
this(U...)(U values)
{
static if((U.length == N) && allSatisfy!(IsTypeAssignable, U))
static if((U.length == N) && (allSatisfy!(IsTypeAssignable, U) || allSatisfy!(isFloatingPoint, U)))
{
static foreach(i, x; values)
{

View File

@ -11,7 +11,28 @@ public import dlib.externdecl;
version(WebAssembly)
{
version(unittest)
{
extern(C) void _start()
{
import wasm;
import std.format;
char[512] buffer;
u32[5] arr = [3, 55, 123, 528, 3943];
string str = Str(sformat(buffer, "Nigger %s pppp %s", "faggot", arr));
Console(str, true);
static foreach(test_fn; __traits(getUnitTests, __traits(parent, _start)))
{
test_fn();
}
Console("success", true);
}
}
}
else
{

View File

@ -12,7 +12,7 @@ import core.stdc.string;
static if(NativeTarget)
{
import std.format : sformat;
import std.stdio : write, writeln, writef, writefln, stderr;
import std.stdio : writeln, writef, writefln;
import dlib.platform;
}

View File

@ -1,224 +0,0 @@
module arsd.color;
char toHex(int a) {
if(a < 10)
return cast(char) (a + '0');
else
return cast(char) (a - 10 + 'a');
}
struct Color {
int r, g, b, a;
this(int r, int g, int b, int a = 255) {
this.r = r;
this.g = g;
this.b = b;
this.a = a;
}
void toTempString(char[] data) {
data[0] = '#';
data[1] = toHex(r >> 4);
data[2] = toHex(r & 0x0f);
data[3] = toHex(g >> 4);
data[4] = toHex(g & 0x0f);
data[5] = toHex(b >> 4);
data[6] = toHex(b & 0x0f);
}
static Color fromHsl(double h, double s, double l, double a = 255) {
h = h % 360;
double C = (1 - absInternal(2 * l - 1)) * s;
double hPrime = h / 60;
double X = C * (1 - absInternal(hPrime % 2 - 1));
double r, g, b;
if(h is double.nan)
r = g = b = 0;
else if (hPrime >= 0 && hPrime < 1) {
r = C;
g = X;
b = 0;
} else if (hPrime >= 1 && hPrime < 2) {
r = X;
g = C;
b = 0;
} else if (hPrime >= 2 && hPrime < 3) {
r = 0;
g = C;
b = X;
} else if (hPrime >= 3 && hPrime < 4) {
r = 0;
g = X;
b = C;
} else if (hPrime >= 4 && hPrime < 5) {
r = X;
g = 0;
b = C;
} else if (hPrime >= 5 && hPrime < 6) {
r = C;
g = 0;
b = X;
}
double m = l - C / 2;
r += m;
g += m;
b += m;
return Color(
cast(int)(r * 255),
cast(int)(g * 255),
cast(int)(b * 255),
cast(int)(a));
}
static immutable Color white = Color(255, 255, 255, 255);
static immutable Color black = Color(0, 0, 0, 255);
static immutable Color red = Color(255, 0, 0, 255);
static immutable Color blue = Color(0, 0, 255, 255);
static immutable Color green = Color(0, 255, 0, 255);
static immutable Color yellow = Color(255, 255, 0, 255);
static immutable Color teal = Color(0, 255, 255, 255);
static immutable Color purple = Color(255, 0, 255, 255);
static immutable Color gray = Color(127, 127, 127, 255);
static immutable Color transparent = Color(0, 0, 0, 0);
}
struct Point {
int x;
int y;
pure const nothrow @safe:
Point opBinary(string op)(in Point rhs) @nogc {
return Point(mixin("x" ~ op ~ "rhs.x"), mixin("y" ~ op ~ "rhs.y"));
}
Point opBinary(string op)(int rhs) @nogc {
return Point(mixin("x" ~ op ~ "rhs"), mixin("y" ~ op ~ "rhs"));
}
}
struct Size {
int width;
int height;
}
nothrow @safe @nogc pure
double absInternal(double a) { return a < 0 ? -a : a; }
struct Rectangle {
int left; ///
int top; ///
int right; ///
int bottom; ///
pure const nothrow @safe @nogc:
///
this(int left, int top, int right, int bottom) {
this.left = left;
this.top = top;
this.right = right;
this.bottom = bottom;
}
///
this(in Point upperLeft, in Point lowerRight) {
this(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y);
}
///
this(in Point upperLeft, in Size size) {
this(upperLeft.x, upperLeft.y, upperLeft.x + size.width, upperLeft.y + size.height);
}
///
@property Point upperLeft() {
return Point(left, top);
}
///
@property Point upperRight() {
return Point(right, top);
}
///
@property Point lowerLeft() {
return Point(left, bottom);
}
///
@property Point lowerRight() {
return Point(right, bottom);
}
///
@property Point center() {
return Point((right + left) / 2, (bottom + top) / 2);
}
///
@property Size size() {
return Size(width, height);
}
///
@property int width() {
return right - left;
}
///
@property int height() {
return bottom - top;
}
/// Returns true if this rectangle entirely contains the other
bool contains(in Rectangle r) {
return contains(r.upperLeft) && contains(r.lowerRight);
}
/// ditto
bool contains(in Point p) {
return (p.x >= left && p.x < right && p.y >= top && p.y < bottom);
}
/// Returns true of the two rectangles at any point overlap
bool overlaps(in Rectangle r) {
// the -1 in here are because right and top are exclusive
return !((right-1) < r.left || (r.right-1) < left || (bottom-1) < r.top || (r.bottom-1) < top);
}
/++
Returns a Rectangle representing the intersection of this and the other given one.
History:
Added July 1, 2021
+/
Rectangle intersectionOf(in Rectangle r) {
auto tmp = Rectangle(max(left, r.left), max(top, r.top), min(right, r.right), min(bottom, r.bottom));
if(tmp.left >= tmp.right || tmp.top >= tmp.bottom)
tmp = Rectangle.init;
return tmp;
}
}
private int max(int a, int b) @nogc nothrow pure @safe {
return a >= b ? a : b;
}
private int min(int a, int b) @nogc nothrow pure @safe {
return a <= b ? a : b;
}
enum arsd_jsvar_compatible = "arsd_jsvar_compatible";
class MemoryImage {}

View File

@ -1,8 +0,0 @@
module arsd.simpleaudio;
struct AudioOutputThread {
this(int) {}
void start() {}
void beep(int = 0) {}
void boop(int = 0) {}
}

View File

@ -1,315 +0,0 @@
module arsd.simpledisplay;
public import arsd.color;
import arsd.webassembly;
//shared static this() { eval("hi there"); }
// the js bridge is SO EXPENSIVE we have to minimize using it.
class SimpleWindow {
this(int width, int height, string title = "D Application") {
this.width = width;
this.height = height;
element = eval!NativeHandle(q{
var s = document.getElementById("screen");
var canvas = document.createElement("canvas");
canvas.addEventListener("contextmenu", function(event) { event.preventDefault(); });
canvas.setAttribute("width", $0);
canvas.setAttribute("height", $1);
canvas.setAttribute("title", $2);
s.appendChild(canvas);
return canvas;
}, width, height, title);
canvasContext = eval!NativeHandle(q{
return $0.getContext("2d");
}, element);
}
NativeHandle element;
NativeHandle canvasContext;
int width;
int height;
void close() {
eval(q{ clearInterval($0); }, intervalId);
intervalId = 0;
}
void delegate() onClosing;
ScreenPainter draw() {
return ScreenPainter(this);
}
int intervalId;
void eventLoop(T...)(int timeout, T t) {
foreach(arg; t) {
static if(is(typeof(arg) : void delegate())) {
sdpy_timer = arg;
} else static if(is(typeof(arg) : void delegate(KeyEvent))) {
sdpy_key = arg;
} else static if(is(typeof(arg) : void delegate(MouseEvent))) {
sdpy_mouse = arg;
} else static assert(0, typeof(arg).stringof);
}
if(timeout)
intervalId = eval!int(q{
return setInterval(function(a) { exports.sdpy_timer_trigger(); }, $0);
}, timeout);
eval(q{
function translate(key) {
var k = 0;
switch(key) {
case "[": k = 1; break;
case "]": k = 2; break;
case "Left": case "ArrowLeft": k = 3; break;
case "Right": case "ArrowRight": k = 4; break;
case "Down": case "ArrowDown": k = 5; break;
case "Up": case "ArrowUp": k = 6; break;
case " ": k = 7; break;
// "Enter", "Esc" / "Escape"
default: k = 0;
}
return k;
}
document.body.addEventListener("keydown", function(event) {
exports.sdpy_key_trigger(1, translate(event.key));
event.preventDefault();
}, true);
document.body.addEventListener("keyup", function(event) {
exports.sdpy_key_trigger(0, translate(event.key));
event.preventDefault();
}, true);
$0.addEventListener("mousedown", function(event) {
exports.sdpy_mouse_trigger(1, event.button, event.offsetX, event.offsetY);
}, true);
$0.addEventListener("mouseup", function(event) {
exports.sdpy_mouse_trigger(0, event.button);
}, true);
}, element);
}
}
void delegate() sdpy_timer;
void delegate(KeyEvent) sdpy_key;
void delegate(MouseEvent) sdpy_mouse;
export extern(C) void sdpy_timer_trigger() {
sdpy_timer();
}
export extern(C) void sdpy_key_trigger(int pressed, int key) {
KeyEvent ke;
ke.pressed = pressed ? true : false;
ke.key = key;
if(sdpy_key)
sdpy_key(ke);
}
export extern(C) void sdpy_mouse_trigger(int pressed, int button, int x, int y) {
MouseEvent me;
me.type = pressed ? MouseEventType.buttonPressed : MouseEventType.buttonReleased;
switch(button) {
case 0:
me.button = MouseButton.left;
break;
case 1:
me.button = MouseButton.middle;
break;
case 2:
me.button = MouseButton.right;
break;
default:
}
me.x = x;
me.y = y;
if(sdpy_mouse)
sdpy_mouse(me);
}
// push arguments in reverse order then push the command
enum canvasRender = q{
};
struct ScreenPainter {
this(SimpleWindow window) {
// no need to arc here tbh
this.w = window.width;
this.h = window.height;
this.element = NativeHandle(window.element.handle, false);
this.context = NativeHandle(window.canvasContext.handle, false);
}
@disable this(this); // for now...
NativeHandle element;
NativeHandle context;
private int w, h;
void clear() {
addCommand(1);
}
void outlineColor(Color c) {
char[7] data;
c.toTempString(data[]);
addCommand(2, 7, data[0], data[1], data[2], data[3], data[4], data[5], data[6]);
return;
//context.properties.strokeStyle!string = cast(immutable)(data[]);
}
void fillColor(Color c) {
char[7] data;
c.toTempString(data[]);
addCommand(3, 7, data[0], data[1], data[2], data[3], data[4], data[5], data[6]);
return;
//context.properties.fillStyle!string = cast(immutable)(data[]);
}
void drawPolygon(Point[] points) {
addCommand(8);
addCommand(cast(double) points.length);
foreach(point; points) {
push(cast(double) point.x);
push(cast(double) point.y);
}
}
void drawRectangle(Point p, int w, int h) {
addCommand(4, p.x, p.y, w, h);
}
void drawRectangle(Point p, Size s) {
drawRectangle(p, s.width, s.height);
}
void drawText(Point p, in char[] txt, Point lowerRight = Point(0, 0), uint alignment = 0) {
// FIXME use the new system
addCommand(5, p.x, p.y + 16, txt.length);
foreach(c; txt)
push(cast(double) c);
return;
eval(q{
var context = $0;
context.font = "18px sans-serif";
context.strokeText($1, $2, $3 + 16);
}, context, txt, p.x, p.y);
}
void drawCircle(Point upperLeft, int diameter) {
addCommand(6, upperLeft.x + diameter / 2, upperLeft.y + diameter / 2, diameter / 2);
}
void drawLine(Point p1, Point p2) {
drawLine(p1.x, p1.y, p2.x, p2.y);
}
void drawLine(int x1, int y1, int x2, int y2) {
addCommand(7, x1, y1, x2, y2);
}
private:
void addCommand(T...)(int cmd, T args) {
push(cmd);
foreach(arg; args) {
push(arg);
}
}
// 50ish % on ronaroids total cpu without this
// with it, we at like 16%
static __gshared double[] commandStack;
size_t commandStackPosition;
void push(T)(T t) {
if(commandStackPosition == commandStack.length) {
commandStack.length = commandStack.length + 1024;
commandStack.assumeUniqueReference();
}
commandStack[commandStackPosition++] = t;
}
~this() {
executeCanvasCommands(this.context.handle, this.commandStack.ptr, commandStackPosition);
}
}
extern(C) void executeCanvasCommands(int handle, double* start, size_t len);
struct KeyEvent {
int key;
bool pressed;
}
enum MouseEventType : int {
motion = 0, /// The mouse moved inside the window
buttonPressed = 1, /// A mouse button was pressed or the wheel was spun
buttonReleased = 2, /// A mouse button was released
}
struct MouseEvent {
MouseEventType type;
int x;
int y;
int dx;
int dy;
MouseButton button;
int modifierState;
}
enum MouseButton : int {
none = 0,
left = 1, ///
right = 2, ///
middle = 4, ///
wheelUp = 8, ///
wheelDown = 16, ///
backButton = 32, /// often found on the thumb and used for back in browsers
forwardButton = 64, /// often found on the thumb and used for forward in browsers
}
enum TextAlignment : uint {
Left = 0, ///
Center = 1, ///
Right = 2, ///
VerticalTop = 0, ///
VerticalCenter = 4, ///
VerticalBottom = 8, ///
}
enum Key {
LeftBracket = 1,
RightBracket,
Left,
Right,
Down,
Up,
Space
}
enum MouseCursor { cross }
class OperatingSystemFont {}
enum UsingSimpledisplayX11 = false;
enum SimpledisplayTimerAvailable = false;
class Sprite{}
enum bool OpenGlEnabled = false;
alias ScreenPainterImplementation = ScreenPainter;
mixin template ExperimentalTextComponent() {
class TextLayout {
}
}

View File

@ -1,178 +0,0 @@
/+
This is the D interface to my webassembly javascript bridge.
+/
module arsd.webassembly;
struct AcquireArgument {
int type;
const(void)* ptr;
int length;
}
// the basic bridge functions defined in webassembly-core.js {
@trusted @nogc pure nothrow
{
extern(C) void retain(int);
extern(C) void release(int);
extern(C) int acquire(int returnType, string callingModuleName, string code, AcquireArgument[] arguments);
extern(C) void abort();
extern(C) int monotimeNow();
}
// }
export extern(C) int invoke_d_array_delegate(size_t ptr, size_t funcptr, ubyte[] arg) {
void delegate(in ubyte[] arr) dg;
dg.ptr = cast(void*) ptr;
dg.funcptr = cast(typeof(dg.funcptr)) funcptr;
dg(arg);
return 0;
};
/++
Evaluates the given code in Javascript. The arguments are available in JS as $0, $1, $2, ....
The `this` object in the evaluated code is set to an object representing the D module that
you can store some stuff in across calls without having to hit the global namespace.
Note that if you want to return a value from javascript, you MUST use the return keyword
in the script string.
Wrong: `eval!NativeHandle("document");`
Right: `eval!NativeHandle("return document");`
+/
template eval(T = void) {
T eval(Args...)(string code, Args args, string callingModuleName = __MODULE__) @trusted @nogc pure {
AcquireArgument[Args.length] aa;
foreach(idx, ref arg; args) {
// FIXME: some other type for unsigned....
static if(is(typeof(arg) : const int)) {
aa[idx].type = 0;
aa[idx].ptr = cast(void*) arg;
aa[idx].length = arg.sizeof;
} else static if(is(immutable typeof(arg) == immutable string)) {
aa[idx].type = 1;
aa[idx].ptr = arg.ptr;
aa[idx].length = arg.length;
} else static if(is(immutable typeof(arg) == immutable NativeHandle)) {
aa[idx].type = 2;
aa[idx].ptr = cast(void*) arg.handle;
aa[idx].length = NativeHandle.sizeof;
} else static if(is(typeof(arg) : const float)) {
aa[idx].type = 3;
aa[idx].ptr = cast(void*) &arg;
aa[idx].length = arg.sizeof;
} else static if(is(immutable typeof(arg) == immutable ubyte[])) {
aa[idx].type = 4;
aa[idx].ptr = arg.ptr;
aa[idx].length = arg.length;
/*
} else static if(is(typeof(arg) == delegate)) {
aa[idx].type = 5;
aa[idx].ptr = cast(void*) &arg;
aa[idx].length = arg.sizeof;
*/
} else {
static assert(0);
}
}
static if(is(T == void))
acquire(0, callingModuleName, code, aa[]);
else static if(is(T == int))
return acquire(1, callingModuleName, code, aa[]);
else static if(is(T == float))
return *cast(float*) cast(void*) acquire(2, callingModuleName, code, aa[]);
else static if(is(T == NativeHandle))
return NativeHandle(acquire(3, callingModuleName, code, aa[]));
else static if(is(T == string)) {
auto ptr = cast(int*) acquire(7, callingModuleName, code, aa[]);
auto len = *ptr;
ptr++;
return (cast(immutable(char)*) ptr)[0 .. len];
}
else static assert(0);
}
}
// and do some opDispatch on the native things to call their methods and it should look p cool
struct NativeHandle {
@trusted @nogc pure:
int handle;
bool arc;
this(int handle, bool arc = true) {
this.handle = handle;
this.arc = arc;
}
this(this) {
if(arc) retain(handle);
}
~this() {
if(arc) release(handle);
}
// never store these, they don't affect the refcount
PropertiesHelper properties() {
return PropertiesHelper(handle);
}
// never store these, they don't affect the refcount
MethodsHelper methods() {
return MethodsHelper(handle);
}
}
struct MethodsHelper {
@trusted @nogc pure:
@disable this();
@disable this(this);
int handle;
private this(int handle) { this.handle = handle; }
template opDispatch(string name) {
template opDispatch(T = NativeHandle)
{
T opDispatch(Args...)(Args args, string callingModuleName = __MODULE__) @trusted @nogc pure
{
return eval!T(q{
return $0[$1].apply($0, Array.prototype.slice.call(arguments, 2));
}, NativeHandle(this.handle, false), name, args, callingModuleName);
}
}
}
}
struct PropertiesHelper {
@trusted @nogc pure:
@disable this();
@disable this(this);
int handle;
private this(int handle) { this.handle = handle; }
template opDispatch(string name) {
template opDispatch(T = NativeHandle) {
T opDispatch() {
return eval!T(q{
return $0[$1];
}, NativeHandle(this.handle, false), name);
}
void opDispatch(T value) {
return eval!void(q{
return $0[$1] = $2;
}, NativeHandle(this.handle, false), name, value);
}
}
}
}

View File

@ -1,17 +0,0 @@
module std.stdio;
import arsd.webassembly;
void writeln(T...)(T t) {
eval(q{
var str = "";
for(var i = 0; i < arguments.length; i++)
str += arguments[i];
str += "\n";
var txt = document.createTextNode(str);
var fd = document.getElementById("stdout");
fd.appendChild(txt);
}, t);
}

View File

11
test.sh
View File

@ -2,16 +2,19 @@
name="Test_Runner"
shared_src="package.d platform.d fonts.d aliases.d math.d util.d alloc.d assets.d external.d"
shared_src="dlib/package.d dlib/platform.d dlib/fonts.d dlib/aliases.d dlib/math.d dlib/util.d dlib/alloc.d dlib/assets.d dlib/externdecl.d"
if [ "$1" == "wasm" ]; then
flags="-c -mtriple=wasm32-unknown-unknown-wasm --Xcc=-DBUILD_WASM -Iexternal/arsd-webassembly -L--no-entry -L--allow-undefined -i=core -i=std -i=. --of=build/dlibmain.wasm --d-version=inline_concat -verrors=90"
flags="-c -vgc -mtriple=wasm32-unknown-unknown-wasm -fvisibility=public -dllimport=all --unittest --Xcc=-DBUILD_WASM -Iwasm/runtime -L--no-entry -i=core -i=std -i=. -g --real-precision=double --of=build/dlibmain.wasm --d-version=inline_concat -verrors=90"
wasm_src="wasm/runtime/object.d"
/bin/bash ./build.sh build wasm
ldc2 $flags $shared_src external/arsd-webassembly/object.d external/arsd-webassembly/core/stdc/string.d
ldc2 $flags $shared_src $wasm_src
wasm-ld "build/dlibmain.wasm build/dlibincludes.wasm -obuild/dlib.wasm"
wasm-ld build/dlibmain.wasm build/dlibincludes.wasm --error-limit=0 --export-memory -obuild/dlib.wasm
cp build/dlib.wasm wasm/dlib.wasm
else
flags="-P-I/usr/include/freetype2 -L-lfreetype --main --unittest -g --of=$name"

Binary file not shown.

BIN
wasm/dlib.wasm Executable file

Binary file not shown.

BIN
wasm/dlibmain.wasm Normal file

Binary file not shown.

12
wasm/index.html Normal file
View File

@ -0,0 +1,12 @@
<html>
<head>
</head>
<body style="background-color: black; color: white;">
Test page
</body>
<script type="text/javascript" src="./wasm.js"></script>
<script>
StartWasm("./dlib.wasm");
</script>
</html>

View File

@ -0,0 +1,83 @@
module core.checkedint;
version(LDC)
{
import ldc.intrinsics;
}
pragma(inline, true)
uint addu()(uint x, uint y, ref bool overflow)
{
version (LDC)
{
if (!__ctfe)
{
auto res = llvm_uadd_with_overflow(x, y);
overflow |= res.overflow;
return res.result;
}
}
immutable uint r = x + y;
immutable bool o = r < x;
assert(o == (r < y));
if (o)
overflow = true;
return r;
}
uint mulu()(uint x, uint y, ref bool overflow)
{
version (D_InlineAsm_X86) enum useAsm = true;
else version (D_InlineAsm_X86_64) enum useAsm = true;
else enum useAsm = false;
version (LDC)
{
if (!__ctfe)
{
auto res = llvm_umul_with_overflow(x, y);
overflow |= res.overflow;
return res.result;
}
}
else static if (useAsm)
{
if (!__ctfe)
{
uint r;
bool o;
asm pure nothrow @nogc @trusted
{
mov EAX, x;
mul y; // EDX:EAX = EAX * y
mov r, EAX;
setc o;
}
overflow |= o;
return r;
}
}
immutable ulong r = ulong(x) * ulong(y);
if (r >> 32)
overflow = true;
return cast(uint) r;
}
pragma(inline, true)
int adds()(int x, int y, ref bool overflow)
{
version (LDC)
{
if (!__ctfe)
{
auto res = llvm_sadd_with_overflow(x, y);
overflow |= res.overflow;
return res.result;
}
}
long r = cast(long)x + cast(long)y;
if (r < int.min || r > int.max)
overflow = true;
return cast(int)r;
}

View File

@ -2326,17 +2326,6 @@ private struct BufSlice
this.to = to;
}
invariant
{
if (buf is null)
{
assert(from == 0);
assert(to == 0);
}
assert(from <= to);
}
auto getSlice() inout nothrow scope { return buf[from .. to]; }
size_t length() const scope { return to - from; }
}

View File

@ -0,0 +1,694 @@
/**
* Written in the D programming language.
* This module provides functions to converting different values to const(ubyte)[]
*
* Copyright: Copyright Igor Stepanov 2013-2013.
* License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
* Authors: Igor Stepanov
* Source: $(DRUNTIMESRC core/internal/_convert.d)
*/
module core.internal.convert;
/+
A @nogc function can allocate memory during CTFE.
+/
@nogc nothrow pure @trusted
private ubyte[] ctfe_alloc(size_t n)
{
if (!__ctfe)
{
assert(0, "CTFE only");
}
else
{
static ubyte[] alloc(size_t x) nothrow pure
{
if (__ctfe) // Needed to prevent _d_newarray from appearing in compiled prorgam.
return new ubyte[x];
else
assert(0);
}
return (cast(ubyte[] function(size_t) @nogc nothrow pure) &alloc)(n);
}
}
@trusted pure nothrow @nogc
const(ubyte)[] toUbyte(T)(const scope ref T val) if (__traits(isFloating, T) && (is(T : real) || is(T : ireal)))
{
if (__ctfe)
{
static if (floatFormat!T == FloatFormat.Float || floatFormat!T == FloatFormat.Double)
{
static if (is(T : ireal)) // https://issues.dlang.org/show_bug.cgi?id=19932
const f = val.im;
else
alias f = val;
static if (T.sizeof == uint.sizeof)
uint bits = *cast(const uint*) &f;
else static if (T.sizeof == ulong.sizeof)
ulong bits = *cast(const ulong*) &f;
ubyte[] result = ctfe_alloc(T.sizeof);
version (BigEndian)
{
foreach_reverse (ref b; result)
{
b = cast(ubyte) bits;
bits >>= 8;
}
}
else
{
foreach (ref b; result)
{
b = cast(ubyte) bits;
bits >>= 8;
}
}
return result;
}
else static if (floatFormat!T == FloatFormat.DoubleDouble)
{
// Parse DoubleDoubles as a pair of doubles.
// The layout of the type is:
//
// [1| 11 | 52 ][1| 11 | 52 ]
// [S| Exponent | Fraction (hi) ][S| Exponent | Fraction (low) ]
//
// We can get the least significant bits by subtracting the IEEE
// double precision portion from the real value.
import core.math : toPrec;
ubyte[] buff = ctfe_alloc(T.sizeof);
enum msbSize = double.sizeof;
static if (is(T : ireal))
double hi = toPrec!double(val.im);
else
double hi = toPrec!double(val);
buff[0 .. msbSize] = toUbyte(hi)[];
if (val is cast(T)0.0 || val is cast(T)-0.0 ||
val is T.nan || val is -T.nan ||
val is T.infinity || val > T.max ||
val is -T.infinity || val < -T.max)
{
// Zero, NaN, and Inf are all representable as doubles, so the
// least significant part can be 0.0.
buff[msbSize .. $] = 0;
}
else
{
static if (is(T : ireal))
double low = toPrec!double(val.im - hi);
else
double low = toPrec!double(val - hi);
buff[msbSize .. $] = toUbyte(low)[];
}
// Arrays don't index differently between little and big-endian targets.
return buff;
}
else
{
auto parsed = parse(val);
ulong mantissa = parsed.mantissa;
uint exp = parsed.exponent;
uint sign = parsed.sign;
ubyte[] buff = ctfe_alloc(T.sizeof);
size_t off_bytes = 0;
size_t off_bits = 0;
// Quadruples won't fit in one ulong, so check for that.
enum mantissaMax = FloatTraits!T.MANTISSA < ulong.sizeof*8 ?
FloatTraits!T.MANTISSA : ulong.sizeof*8;
for (; off_bytes < mantissaMax/8; ++off_bytes)
{
buff[off_bytes] = cast(ubyte)mantissa;
mantissa >>= 8;
}
static if (floatFormat!T == FloatFormat.Quadruple)
{
ulong mantissa2 = parsed.mantissa2;
off_bytes--; // go back one, since mantissa only stored data in 56
// bits, ie 7 bytes
for (; off_bytes < FloatTraits!T.MANTISSA/8; ++off_bytes)
{
buff[off_bytes] = cast(ubyte)mantissa2;
mantissa2 >>= 8;
}
}
else
{
off_bits = FloatTraits!T.MANTISSA%8;
buff[off_bytes] = cast(ubyte)mantissa;
}
for (size_t i=0; i<FloatTraits!T.EXPONENT/8; ++i)
{
ubyte cur_exp = cast(ubyte)exp;
exp >>= 8;
buff[off_bytes] |= (cur_exp << off_bits);
++off_bytes;
buff[off_bytes] |= cur_exp >> 8 - off_bits;
}
exp <<= 8 - FloatTraits!T.EXPONENT%8 - 1;
buff[off_bytes] |= exp;
sign <<= 7;
buff[off_bytes] |= sign;
version (BigEndian)
{
for (size_t left = 0, right = buff.length - 1; left < right; left++, right--)
{
const swap = buff[left];
buff[left] = buff[right];
buff[right] = swap;
}
}
return buff;
}
}
else
{
return (cast(const(ubyte)*)&val)[0 .. T.sizeof];
}
}
@safe pure nothrow @nogc
private Float parse(bool is_denormalized = false, T:ireal)(T x)
{
return parse(x.im);
}
@safe pure nothrow @nogc
private Float parse(bool is_denormalized = false, T:real)(T x_) if (floatFormat!T != FloatFormat.Real80)
{
import core.internal.traits : Unqual;
Unqual!T x = x_;
static assert(floatFormat!T != FloatFormat.DoubleDouble,
"doubledouble float format not supported in CTFE");
if (x is cast(T)0.0) return FloatTraits!T.ZERO;
if (x is cast(T)-0.0) return FloatTraits!T.NZERO;
if (x is T.nan) return FloatTraits!T.NAN;
if (x is -T.nan) return FloatTraits!T.NNAN;
if (x is T.infinity || x > T.max) return FloatTraits!T.INF;
if (x is -T.infinity || x < -T.max) return FloatTraits!T.NINF;
uint sign = x < 0;
x = sign ? -x : x;
int e = binLog2(x);
real x2 = x;
uint exp = cast(uint)(e + (2^^(FloatTraits!T.EXPONENT-1) - 1));
if (!exp)
{
if (is_denormalized)
return Float(0, 0, sign);
else
return denormalizedMantissa(x, sign);
}
x2 /= binPow2(e);
static if (!is_denormalized)
x2 -= 1.0;
static if (floatFormat!T == FloatFormat.Quadruple)
{
// Store the 112-bit mantissa in two ulongs, specifically the lower 56
// bits of each, with the most significant bits in mantissa2. There's
// an edge case exposed by the labeled test below, where only a subnormal
// with the highest bit set being the 57th bit will "overflow" to the
// 57th bit in mantissa2 with the following logic, but that special case
// is handled by an additional check in denormalizedMantissa for
// Quadruples below.
x2 *= 2UL<<(FloatTraits!T.MANTISSA - (ulong.sizeof - 1)*8 - 1);
ulong mant2 = cast(ulong) x2;
x2 -= mant2;
x2 *= 2UL<<((ulong.sizeof - 1)*8 - 1);
ulong mant = cast(ulong) x2;
return Float(mant, exp, sign, mant2);
}
else
{
x2 *= 2UL<<(FloatTraits!T.MANTISSA);
ulong mant = shiftrRound(cast(ulong)x2);
return Float(mant, exp, sign);
}
}
@safe pure nothrow @nogc
private Float parse(bool _ = false, T:real)(T x_) if (floatFormat!T == FloatFormat.Real80)
{
import core.internal.traits : Unqual;
Unqual!T x = x_;
//HACK @@@3632@@@
if (x == 0.0L)
{
real y = 1.0L/x;
if (y == real.infinity) // -0.0
return FloatTraits!T.ZERO;
else
return FloatTraits!T.NZERO; //0.0
}
if (x != x) //HACK: should be if (x is real.nan) and if (x is -real.nan)
{
auto y = cast(double)x;
if (y is double.nan)
return FloatTraits!T.NAN;
else
return FloatTraits!T.NNAN;
}
if (x == real.infinity) return FloatTraits!T.INF;
if (x == -real.infinity) return FloatTraits!T.NINF;
enum EXPONENT_MED = (2^^(FloatTraits!T.EXPONENT-1) - 1);
uint sign = x < 0;
x = sign ? -x : x;
int e = binLog2(x);
uint exp = cast(uint)(e + EXPONENT_MED);
if (!exp)
{
return denormalizedMantissa(x, sign);
}
int pow = (FloatTraits!T.MANTISSA-1-e);
x *= binPow2((pow / EXPONENT_MED)*EXPONENT_MED); //To avoid overflow in 2.0L ^^ pow
x *= binPow2(pow % EXPONENT_MED);
ulong mant = cast(ulong)x;
return Float(mant, exp, sign);
}
private struct Float
{
ulong mantissa;
uint exponent;
uint sign;
ulong mantissa2;
}
private template FloatTraits(T) if (floatFormat!T == FloatFormat.Float)
{
enum DATASIZE = 4;
enum EXPONENT = 8;
enum MANTISSA = 23;
enum ZERO = Float(0, 0, 0);
enum NZERO = Float(0, 0, 1);
enum NAN = Float(0x400000UL, 0xff, 0);
enum NNAN = Float(0x400000UL, 0xff, 1);
enum INF = Float(0, 255, 0);
enum NINF = Float(0, 255, 1);
}
private template FloatTraits(T) if (floatFormat!T == FloatFormat.Double)
{
enum DATASIZE = 8;
enum EXPONENT = 11;
enum MANTISSA = 52;
enum ZERO = Float(0, 0, 0);
enum NZERO = Float(0, 0, 1);
enum NAN = Float(0x8000000000000UL, 0x7ff, 0);
enum NNAN = Float(0x8000000000000UL, 0x7ff, 1);
enum INF = Float(0, 0x7ff, 0);
enum NINF = Float(0, 0x7ff, 1);
}
private template FloatTraits(T) if (floatFormat!T == FloatFormat.Real80)
{
enum DATASIZE = 10;
enum EXPONENT = 15;
enum MANTISSA = 64;
enum ZERO = Float(0, 0, 0);
enum NZERO = Float(0, 0, 1);
enum NAN = Float(0xC000000000000000UL, 0x7fff, 0);
enum NNAN = Float(0xC000000000000000UL, 0x7fff, 1);
enum INF = Float(0x8000000000000000UL, 0x7fff, 0);
enum NINF = Float(0x8000000000000000UL, 0x7fff, 1);
}
private template FloatTraits(T) if (floatFormat!T == FloatFormat.DoubleDouble) //Unsupported in CTFE
{
enum DATASIZE = 16;
enum EXPONENT = 11;
enum MANTISSA = 106;
enum ZERO = Float(0, 0, 0);
enum NZERO = Float(0, 0, 1);
enum NAN = Float(0x8000000000000UL, 0x7ff, 0);
enum NNAN = Float(0x8000000000000UL, 0x7ff, 1);
enum INF = Float(0, 0x7ff, 0);
enum NINF = Float(0, 0x7ff, 1);
}
private template FloatTraits(T) if (floatFormat!T == FloatFormat.Quadruple)
{
enum DATASIZE = 16;
enum EXPONENT = 15;
enum MANTISSA = 112;
enum ZERO = Float(0, 0, 0);
enum NZERO = Float(0, 0, 1);
enum NAN = Float(0, 0x7fff, 0, 0x80000000000000UL);
enum NNAN = Float(0, 0x7fff, 1, 0x80000000000000UL);
enum INF = Float(0, 0x7fff, 0);
enum NINF = Float(0, 0x7fff, 1);
}
@safe pure nothrow @nogc
private real binPow2(int pow)
{
static real binPosPow2(int pow) @safe pure nothrow @nogc
{
assert(pow > 0);
if (pow == 1) return 2.0L;
int subpow = pow/2;
real p = binPosPow2(subpow);
real ret = p*p;
if (pow%2)
{
ret *= 2.0L;
}
return ret;
}
if (!pow) return 1.0L;
if (pow > 0) return binPosPow2(pow);
return 1.0L/binPosPow2(-pow);
}
//Need in CTFE, because CTFE float and double expressions computed more precisely that run-time expressions.
@safe pure nothrow @nogc
private ulong shiftrRound(ulong x)
{
return (x >> 1) + (x & 1);
}
@safe pure nothrow @nogc
private uint binLog2(T)(const T x)
{
assert(x > 0);
int max = 2 ^^ (FloatTraits!T.EXPONENT-1)-1;
int min = -max+1;
int med = (min + max) / 2;
if (x < T.min_normal) return -max;
while ((max - min) > 1)
{
if (binPow2(med) > x)
{
max = med;
}
else
{
min = med;
}
med = (min + max) / 2;
}
if (x < binPow2(max))
return min;
return max;
}
@safe pure nothrow @nogc
private Float denormalizedMantissa(T)(T x, uint sign) if (floatFormat!T == FloatFormat.Real80)
{
x *= 2.0L^^FloatTraits!T.MANTISSA;
auto fl = parse(x);
uint pow = FloatTraits!T.MANTISSA - fl.exponent + 1;
return Float(fl.mantissa >> pow, 0, sign);
}
@safe pure nothrow @nogc
private Float denormalizedMantissa(T)(T x, uint sign)
if (floatFormat!T == FloatFormat.Float || floatFormat!T == FloatFormat.Double)
{
x *= 2.0L^^FloatTraits!T.MANTISSA;
auto fl = parse!true(x);
ulong mant = fl.mantissa >> (FloatTraits!T.MANTISSA - fl.exponent);
return Float(shiftrRound(mant), 0, sign);
}
@safe pure nothrow @nogc
private Float denormalizedMantissa(T)(T x, uint sign) if (floatFormat!T == FloatFormat.Quadruple)
{
x *= 2.0L^^FloatTraits!T.MANTISSA;
auto fl = parse!true(x);
uint offset = FloatTraits!T.MANTISSA - fl.exponent + 1;
enum mantissaSize = (ulong.sizeof - 1) * 8;
if (offset < mantissaSize)
{ // Create a new mantissa ulong with the trailing mantissa2 bits that
// need to be shifted into mantissa, by shifting the needed bits left,
// zeroing out the first byte, and then ORing it with mantissa shifted
// right by offset.
ulong shiftedMantissa = ((fl.mantissa2 << (mantissaSize - offset)) &
0x00FFFFFFFFFFFFFFUL) | fl.mantissa >> offset;
return Float(shiftedMantissa, 0, sign, fl.mantissa2 >> offset);
}
else if (offset > mantissaSize)
return Float(fl.mantissa2 >> offset - mantissaSize , 0, sign, 0);
else
// Handle special case mentioned in parse() above by zeroing out the
// 57'th bit of mantissa2, "shifting" it into mantissa, and setting the
// first bit of mantissa2.
return Float(fl.mantissa2 & 0x00FFFFFFFFFFFFFFUL , 0, sign, 1);
}
private enum FloatFormat
{
Float,
Double,
Real80,
DoubleDouble,
Quadruple
}
template floatFormat(T) if (is(T:real) || is(T:ireal))
{
static if (T.mant_dig == 24)
enum floatFormat = FloatFormat.Float;
else static if (T.mant_dig == 53)
{
// Double precision, or real == double
static if (T.sizeof == double.sizeof)
enum floatFormat = FloatFormat.Double;
// 80-bit real with rounding precision set to 53 bits.
else static if (T.sizeof == real.sizeof)
enum floatFormat = FloatFormat.Real80;
}
else static if (T.mant_dig == 64)
enum floatFormat = FloatFormat.Real80;
else static if (T.mant_dig == 106)
enum floatFormat = FloatFormat.DoubleDouble;
else static if (T.mant_dig == 113)
enum floatFormat = FloatFormat.Quadruple;
else
static assert(0);
}
package template floatSize(T) if (is(T:real) || is(T:ireal))
{
enum floatSize = FloatTraits!(T).DATASIZE;
}
// all toUbyte functions must be evaluable at compile time
@trusted pure nothrow @nogc
const(ubyte)[] toUbyte(T)(return scope const T[] arr) if (T.sizeof == 1)
{
pragma(inline, true);
return cast(const(ubyte)[])arr;
}
private const(ubyte)[] toUbyte_array_ctfe(T)(return scope const T[] arr)
{
pragma(inline, false);
ubyte[] ret = ctfe_alloc(T.sizeof * arr.length);
static if (is(T EType == enum)) // Odd style is to avoid template instantiation in most cases.
alias E = OriginalType!EType;
else
alias E = T;
static if (is(E == struct) || is(E == union) || __traits(isStaticArray, E) || !is(typeof(arr[0] is null)))
{
size_t offset = 0;
foreach (ref cur; arr)
{
ret[offset .. offset + T.sizeof] = toUbyte(cur)[0 .. T.sizeof];
offset += T.sizeof;
}
}
else
{
foreach (cur; arr)
assert(cur is null, "Unable to compute byte representation of non-null pointer at compile time");
}
return ret;
}
@trusted pure nothrow @nogc
const(ubyte)[] toUbyte(T)(return scope const T[] arr) if (T.sizeof > 1)
{
pragma(inline, true);
return __ctfe ? toUbyte_array_ctfe(arr)
: (cast(const(ubyte)*)(arr.ptr))[0 .. T.sizeof*arr.length];
}
private const(ubyte)[] toUbyte_integral_ctfe(T)(const return ref scope T val)
{
pragma(inline, false);
static if (T.sizeof == 1)
{
ubyte[] result = ctfe_alloc(1);
result[0] = cast(ubyte) val;
return result;
}
else
{
import core.internal.traits : Unqual;
ubyte[] tmp = ctfe_alloc(T.sizeof);
Unqual!T val_ = val;
for (size_t i = 0; i < T.sizeof; ++i)
{
size_t idx;
version (LittleEndian) idx = i;
else idx = T.sizeof-i-1;
tmp[idx] = cast(ubyte)(val_&0xff);
val_ >>= 8;
}
return tmp;
}
}
@trusted pure nothrow @nogc
const(ubyte)[] toUbyte(T)(const ref scope T val) if (__traits(isIntegral, T) && !is(T == enum) && !is(T == __vector))
{
pragma(inline, true);
return __ctfe ? toUbyte_integral_ctfe(val)
: (cast(const ubyte*) &val)[0 .. T.sizeof];
}
private const(ubyte)[] toUbyte_vector_ctfe(T)(const return ref scope T val)
{
pragma(inline, false);
static if (is(typeof(val[0]) : void))
assert(0, "Unable to compute byte representation of " ~ T.stringof ~ " at compile time.");
else
{
// This code looks like it should work in CTFE but it segfaults:
// auto a = val.array;
// return toUbyte(a);
alias E = typeof(val[0]);
ubyte[] result = ctfe_alloc(T.sizeof);
for (size_t i = 0, j = 0; i < T.sizeof; i += E.sizeof, ++j)
{
result[i .. i + E.sizeof] = toUbyte(val[j]);
}
return result;
}
}
@trusted pure nothrow @nogc
const(ubyte)[] toUbyte(T)(const ref scope T val) if (is(T == __vector))
{
pragma(inline, true);
return __ctfe ? toUbyte_vector_ctfe(val)
: (cast(const ubyte*) &val)[0 .. T.sizeof];
}
private const(ubyte)[] toUbyte_enum_ctfe(T)(const return ref scope T val)
{
pragma(inline, false);
static if (is(T V == enum)){}
return toUbyte(*cast(const V*) &val);
}
@trusted pure nothrow @nogc
const(ubyte)[] toUbyte(T)(const ref return scope T val) if (is(T == enum))
{
pragma(inline, true);
return __ctfe ? toUbyte_enum_ctfe(val)
: (cast(const(ubyte)*)&val)[0 .. T.sizeof];
}
nothrow pure @safe unittest
{
// Issue 19008 - check toUbyte works on enums.
enum Month : uint { jan = 1}
Month m = Month.jan;
const bytes = toUbyte(m);
enum ctfe_works = (() { Month x = Month.jan; return toUbyte(x).length > 0; })();
}
private const(ubyte)[] toUbyte_delegate_ctfe(T)(const return ref scope T val)
{
pragma(inline, false);
if (val !is null) assert(0, "Unable to compute byte representation of non-null pointer at compile time");
return ctfe_alloc(T.sizeof);
}
@trusted pure nothrow @nogc
const(ubyte)[] toUbyte(T)(const ref T val) if (is(T == delegate) || is(T : V*, V) && __traits(getAliasThis, T).length == 0)
{
pragma(inline, true);
return __ctfe ? toUbyte_delegate_ctfe(val)
: (cast(const(ubyte)*)&val)[0 .. T.sizeof];
}
private const(ubyte)[] toUbyte_aggregate_ctfe(T)(const return ref scope T val)
{
pragma(inline, false);
ubyte[] bytes = ctfe_alloc(T.sizeof);
foreach (key, ref cur; val.tupleof)
{
static if (is(typeof(cur) EType == enum)) // Odd style is to avoid template instantiation in most cases.
alias CurType = OriginalType!EType;
else
alias CurType = typeof(cur);
static if (is(CurType == struct) || is(CurType == union) || __traits(isStaticArray, CurType) || !is(typeof(cur is null)))
{
bytes[val.tupleof[key].offsetof .. val.tupleof[key].offsetof + CurType.sizeof] = toUbyte(cur)[];
}
else
{
assert(cur is null, "Unable to compute byte representation of non-null reference field at compile time");
//skip, because val bytes are zeros
}
}
return bytes;
}
@trusted pure nothrow @nogc
const(ubyte)[] toUbyte(T)(const return ref scope T val) if (is(T == struct) || is(T == union))
{
pragma(inline, true);
return __ctfe ? toUbyte_aggregate_ctfe(val)
: (cast(const(ubyte)*)&val)[0 .. T.sizeof];
}
// Strips off all `enum`s from type `T`.
// Perhaps move to core.internal.types.
private template OriginalType(T)
{
static if (is(T EType == enum))
alias OriginalType = .OriginalType!EType;
else
alias OriginalType = T;
}

View File

@ -0,0 +1,123 @@
module core.internal.traits;
alias AliasSeq(T...) = T;
template allSatisfy(alias F, T...)
{
static foreach (Ti; T)
{
static if (!is(typeof(allSatisfy) == bool) && // not yet defined
!F!(Ti))
{
enum allSatisfy = false;
}
}
static if (!is(typeof(allSatisfy) == bool)) // if not yet defined
{
enum allSatisfy = true;
}
}
template Unqual(T : const U, U)
{
static if (is(U == shared V, V))
alias Unqual = V;
else
alias Unqual = U;
}
alias Unconst(T : const U, U) = U;
template hasElaborateMove(S)
{
static if (__traits(isStaticArray, S))
{
enum bool hasElaborateMove = S.sizeof && hasElaborateMove!(BaseElemOf!S);
}
else static if (is(S == struct))
{
enum hasElaborateMove = (is(typeof(S.init.opPostMove(lvalueOf!S))) &&
!is(typeof(S.init.opPostMove(rvalueOf!S)))) ||
anySatisfy!(.hasElaborateMove, Fields!S);
}
else
{
enum bool hasElaborateMove = false;
}
}
template substInout(T)
{
static if (is(T == immutable))
{
alias substInout = T;
}
else static if (is(T : shared const U, U) || is(T : const U, U))
{
// U is top-unqualified
mixin("alias substInout = "
~ (is(T == shared) ? "shared " : "")
~ (is(T == const) || is(T == inout) ? "const " : "") // substitute inout to const
~ "substInoutForm!U;");
}
else
static assert(0);
}
template anySatisfy(alias F, Ts...)
{
static foreach (T; Ts)
{
static if (!is(typeof(anySatisfy) == bool) && // not yet defined
F!T)
{
enum anySatisfy = true;
}
}
static if (!is(typeof(anySatisfy) == bool)) // if not yet defined
{
enum anySatisfy = false;
}
}
template hasElaborateCopyConstructor(S)
{
static if (__traits(isStaticArray, S))
{
enum bool hasElaborateCopyConstructor = S.sizeof && hasElaborateCopyConstructor!(BaseElemOf!S);
}
else static if (is(S == struct))
{
enum hasElaborateCopyConstructor = __traits(hasCopyConstructor, S) || __traits(hasPostblit, S);
}
else
{
enum bool hasElaborateCopyConstructor = false;
}
}
template hasElaborateDestructor(S)
{
static if (__traits(isStaticArray, S))
{
enum bool hasElaborateDestructor = S.sizeof && hasElaborateDestructor!(BaseElemOf!S);
}
else static if (is(S == struct))
{
// Once https://issues.dlang.org/show_bug.cgi?id=24865 is fixed, then
// this should be the implementation, but until that's fixed, we need the
// uncommented code.
// enum hasElaborateDestructor = __traits(hasMember, S, "__xdtor");
enum hasElaborateDestructor = hasDtor([__traits(allMembers, S)]);
}
else
{
enum bool hasElaborateDestructor = false;
}
}
enum bool isAggregateType(T) = is(T == struct) || is(T == union) ||
is(T == class) || is(T == interface);
enum bool isPointer(T) = is(T == U*, U) && !isAggregateType!T;

View File

@ -436,7 +436,7 @@ unittest
{
debug(utf) printf("utf.encode.unittest\n");
char[] s = "abcd".dup;
char[] s = r"abcd";
encode(s, cast(dchar)'a');
assert(s.length == 5);
assert(s == "abcda");

View File

@ -71,3 +71,41 @@ void copyEmplace(S, T)(ref S source, ref T target) @system
*cast(Unconst!(T)*) &target = *cast(Unconst!(T)*) &source;
}
}
T* emplace(T)(T* chunk) @safe pure nothrow
{
import core.internal.lifetime : emplaceRef;
emplaceRef!T(*chunk);
return chunk;
}
template forward(args...)
{
import core.internal.traits : AliasSeq;
template fwd(alias arg)
{
// by ref || lazy || const/immutable
static if (__traits(isRef, arg) ||
__traits(isOut, arg) ||
__traits(isLazy, arg) ||
!is(typeof(move(arg))))
alias fwd = arg;
// (r)value
else
@property auto fwd()
{
version (DigitalMars) { /* @@BUG 23890@@ */ } else pragma(inline, true);
return move(arg);
}
}
alias Result = AliasSeq!();
static foreach (arg; args)
Result = AliasSeq!(Result, fwd!arg);
static if (Result.length == 1)
alias forward = Result[0];
else
alias forward = Result;
}

View File

@ -6,5 +6,5 @@ float llvm_sqrt(float x) pure nothrow @nogc @safe;
pragma(LDC_intrinsic, "llvm.sqrt.f64")
double llvm_sqrt(double x) pure nothrow @nogc @safe;
float sqrt(float x ) => x < 0 ? float.nan : llvm_sqrt(x);
double sqrt(double x) => x < 0 ? double.nan : llvm_sqrt(x);
float sqrt(float x ) => x < 0.0f ? float.nan : llvm_sqrt(x);
double sqrt(double x) => x < 0.0f ? double.nan : llvm_sqrt(x);

View File

@ -1,17 +1,21 @@
// Minimal druntime for webassembly. Assumes your program has a main function.
// **************************************************
// ** VALID FOR 1.42.0 (DMD v2.112.1, LLVM 21.1.8) **
// **************************************************
module object;
public import core.internal.cast_ : _d_cast;
static import arsd.webassembly;
import std.meta;
alias AliasSeq(TList...) = TList;
// import core.arsd.memory_allocation;
import core.stdc.string;
import dlib.externdecl;
import wasm;
version(CarelessAlocation)
{
version = inline_concat;
@ -34,6 +38,102 @@ alias sizediff_t = ptrdiff_t;
enum immutable(void)* rtinfoNoPointers = null;
enum immutable(void)* rtinfoHasPointers = cast(void*)1;
int __cmp(C1, C2)(C1 lhs, C2 rhs)
if ((is(C1 : const(Object)) || (is(C1 == interface) && (__traits(getLinkage, C1) == "D"))) &&
(is(C2 : const(Object)) || (is(C2 == interface) && (__traits(getLinkage, C2) == "D"))))
{
static if (is(C1 == typeof(null)) && is(C2 == typeof(null)))
{
return 0;
}
else static if (is(C1 == typeof(null)))
{
// Regard null references as always being "less than"
return -1;
}
else static if (is(C2 == typeof(null)))
{
return 1;
}
else
{
if (lhs is rhs)
return 0;
if (lhs is null)
return -1;
if (rhs is null)
return 1;
return lhs.opCmp(rhs);
}
}
class TypeInfo_AssociativeArray : TypeInfo
{
override string toString() const
{
return MakeString(value.toString(), "[", key.toString(), "]");
}
override bool opEquals(Object o)
{
if (this is o)
return true;
auto c = cast(const TypeInfo_AssociativeArray)o;
return c && this.key == c.key &&
this.value == c.value;
}
override bool equals(in void* p1, in void* p2) @trusted const
{
return xopEquals(p1, p2);
}
override hash_t getHash(scope const void* p) nothrow @trusted const
{
return xtoHash(p);
}
// BUG: need to add the rest of the functions
override @property size_t tsize() nothrow pure const
{
return (char[int]).sizeof;
}
override const(void)[] initializer() const @trusted
{
return (cast(void *)null)[0 .. (char[int]).sizeof];
}
override @property inout(TypeInfo) next() nothrow pure inout { return value; }
override @property uint flags() nothrow pure const { return 1; }
// TypeInfo entry is generated from the type of this template to help rt/aaA.d
// private static import core.internal.newaa;
// alias Entry(K, V) = core.internal.newaa.Entry!(K, V);
TypeInfo value;
TypeInfo key;
TypeInfo entry;
bool function(scope const void* p1, scope const void* p2) nothrow @safe xopEquals;
hash_t function(scope const void*) nothrow @safe xtoHash;
alias aaOpEqual(K, V) = core.internal.newaa._aaOpEqual!(K, V);
alias aaGetHash(K, V) = core.internal.newaa._aaGetHash!(K, V);
override @property size_t talign() nothrow pure const
{
return (char[int]).alignof;
}
version (WithArgTypes) override int argTypes(out TypeInfo arg1, out TypeInfo arg2)
{
arg1 = typeid(void*);
return 0;
}
}
template RTInfoImpl(size_t[] pointerBitmap)
{
immutable size_t[pointerBitmap.length] RTInfoImpl = pointerBitmap[];
@ -90,70 +190,62 @@ void reserve(T)(ref T[] arr, size_t length) @trusted {
arr = (cast(T*) (malloc(length * T.sizeof)))[0 .. 0];
}
// TODO: fix these to use sprintf
extern(C) void _d_arraybounds(string file, size_t line) {
arsd.webassembly.eval(
q{ console.error("Range error: " + $0 + ":" + $1 )},
file, line);
arsd.webassembly.abort();
extern(C) void _d_arraybounds(string file, size_t line)
{
Abort("Range Error");
}
/// Called when an out of range slice of an array is created
extern(C) void _d_arraybounds_slice(string file, uint line, size_t lwr, size_t upr, size_t length)
{
arsd.webassembly.eval(
q{ console.error("Range error: " + $0 + ":" + $1 + " [" + $2 + ".." + $3 + "] <> " + $4)},
file, line, lwr, upr, length);
arsd.webassembly.abort();
Abort("Range error on slice creation");
}
/// Called when an out of range array index is accessed
extern(C) void _d_arraybounds_index(string file, uint line, size_t index, size_t length)
{
arsd.webassembly.eval(
q{ console.error("Array index " + $0 + " out of bounds '[0.."+$1+"]' " + $2 + ":" + $3)},
index, length, file, line);
arsd.webassembly.abort();
Abort("Range error on indexing array");
}
// }
extern(C) void _d_assert(string file, uint line) @trusted @nogc pure
{
arsd.webassembly.eval(q{ console.error("Assert failure: " + $0 + ":" + $1); /*, "[" + $2 + ".." + $3 + "] <> " + $4);*/ }, file, line);//, lwr, upr, length);
arsd.webassembly.abort();
}
void _d_assertp(immutable(char)* file, uint line)
{
// import core.stdc.string : strlen;
size_t sz = 0;
while(file[sz] != '\0') sz++;
arsd.webassembly.eval(q{ console.error("Assert failure: " + $0 + ":" + $1 + "(" + $2 + ")"); /*, "[" + $2 + ".." + $3 + "] <> " + $4);*/ }, file[0 .. sz], line);//, lwr, upr, length);
arsd.webassembly.abort();
Abort("Assertion failure");
}
void _d_assertp(immutable(char)* file, uint line)
{
Abort("Assertion failure");
}
extern(C) void _d_assert_msg(string msg, string file, uint line) @trusted @nogc pure
{
arsd.webassembly.eval(q{ console.error("Assert failure: " + $0 + ":" + $1 + "(" + $2 + ")"); /*, "[" + $2 + ".." + $3 + "] <> " + $4);*/ }, file, line, msg);//, lwr, upr, length);
arsd.webassembly.abort();
Abort("Assertion failure with msg");
}
void __switch_error(string file, size_t line) @trusted @nogc pure
{
_d_assert_msg("final switch error",file, line);
_d_assert_msg("final switch error", file, line);
}
bool __equals(T1, T2)(scope const T1[] lhs, scope const T2[] rhs) {
if (lhs.length != rhs.length) {
if (lhs.length != rhs.length)
{
return false;
}
foreach(i; 0..lhs.length) {
if (lhs[i] != rhs[i]) {
foreach(i; 0..lhs.length)
{
if (lhs[i] != rhs[i])
{
return false;
}
}
return true;
}
@ -581,13 +673,6 @@ class TypeInfo
return __cmp(this.toString(), ti.toString());
}
@system unittest
{
assert(typeid(void) <= typeid(void));
assert(typeid(void).opCmp(null));
assert(!typeid(void).opCmp(typeid(void)));
}
override bool opEquals(Object o)
{
return opEquals(cast(TypeInfo) o);
@ -604,14 +689,6 @@ class TypeInfo
return ti && this.toString() == ti.toString();
}
@system unittest
{
auto anotherObj = new Object();
assert(typeid(void).opEquals(typeid(void)));
assert(typeid(void) != anotherObj); // calling .opEquals here directly is a type mismatch
}
/**
* Computes a hash of the instance of a type.
* Params:
@ -662,36 +739,6 @@ class TypeInfo
}
}
@system unittest
{
class _TypeInfo_Dummy : TypeInfo
{
override const(void)[] initializer() const { return []; }
@property override size_t tsize() nothrow pure const @safe @nogc { return tsize_val; }
size_t tsize_val;
}
auto dummy = new _TypeInfo_Dummy();
cast(void)dummy.initializer(); // For coverage completeness
int a = 2, b = -2;
dummy.swap(&a, &b);
// does nothing because tsize is 0
assert(a == 2);
assert(b == -2);
dummy.tsize_val = int.sizeof;
dummy.swap(&a, &b);
assert(a == -2);
assert(b == 2);
void* ptr_a = null, ptr_b = cast(void*)1;
dummy.tsize_val = (void*).sizeof;
dummy.swap(&ptr_a, &ptr_b);
assert(ptr_a is cast(void*)1);
assert(ptr_b is null);
}
/** Get TypeInfo for 'next' type, as defined by what kind of type this is,
null if none. */
@property inout(TypeInfo) next() nothrow pure inout @nogc { return null; }
@ -746,6 +793,7 @@ else
*/
@property immutable(void)* rtInfo() nothrow pure const @trusted @nogc { return rtinfoHasPointers; } // better safe than sorry
}
class TypeInfo_Class : TypeInfo
{
override string toString() const pure { return name; }
@ -1610,114 +1658,22 @@ class TypeInfo_Enum : TypeInfo
this.base == c.base;
}
@system unittest
{
enum E { A, B, C }
enum EE { A, B, C }
assert(typeid(E).opEquals(typeid(E)));
assert(!typeid(E).opEquals(typeid(EE)));
}
override size_t getHash(scope const void* p) const { return base.getHash(p); }
@system unittest
{
enum E { A, B, C }
E e1 = E.A;
E e2 = E.B;
assert(typeid(E).getHash(&e1) == hashOf(E.A));
assert(typeid(E).getHash(&e2) == hashOf(E.B));
enum ES : string { A = "foo", B = "bar" }
ES es1 = ES.A;
ES es2 = ES.B;
assert(typeid(ES).getHash(&es1) == hashOf("foo"));
assert(typeid(ES).getHash(&es2) == hashOf("bar"));
}
override bool equals(in void* p1, in void* p2) const { return base.equals(p1, p2); }
@system unittest
{
enum E { A, B, C }
E e1 = E.A;
E e2 = E.B;
assert(typeid(E).equals(&e1, &e1));
assert(!typeid(E).equals(&e1, &e2));
}
override int compare(in void* p1, in void* p2) const { return base.compare(p1, p2); }
@system unittest
{
enum E { A, B, C }
E e1 = E.A;
E e2 = E.B;
assert(typeid(E).compare(&e1, &e1) == 0);
assert(typeid(E).compare(&e1, &e2) < 0);
assert(typeid(E).compare(&e2, &e1) > 0);
}
override @property size_t tsize() nothrow pure const { return base.tsize; }
@safe unittest
{
enum E { A, B, C }
enum ES : string { A = "a", B = "b", C = "c"}
assert(typeid(E).tsize == E.sizeof);
assert(typeid(ES).tsize == ES.sizeof);
assert(typeid(E).tsize != ES.sizeof);
}
override void swap(void* p1, void* p2) const { return base.swap(p1, p2); }
@system unittest
{
enum E { A, B, C }
E e1 = E.A;
E e2 = E.B;
typeid(E).swap(&e1, &e2);
assert(e1 == E.B);
assert(e2 == E.A);
}
override @property inout(TypeInfo) next() nothrow pure inout { return base.next; }
@system unittest
{
enum E { A, B, C }
assert(typeid(E).next is null);
}
override @property uint flags() nothrow pure const { return base.flags; }
@safe unittest
{
enum E { A, B, C }
assert(typeid(E).flags == 0);
}
override const(OffsetTypeInfo)[] offTi() const { return base.offTi; }
@system unittest
{
enum E { A, B, C }
assert(typeid(E).offTi is null);
}
override void destroy(void* p) const { return base.destroy(p); }
override void postblit(void* p) const { return base.postblit(p); }
@ -2222,7 +2178,6 @@ extern (C) void[] _d_arrayappendcd(ref byte[] x, dchar c)
/* TODO: see if needed
alias AliasSeq(T...) = T;
static foreach(type; AliasSeq!(byte, char, dchar, double, float, int, long, short, ubyte, uint, ulong, ushort, void, wchar)) {
mixin(q{
class TypeInfo_}~type.mangleof~q{ : TypeInfo {
@ -2363,27 +2318,6 @@ class TypeInfo_Delegate : TypeInfo
return null;
}
@safe unittest
{
double sqr(double x) { return x * x; }
sqr(double.init); // for coverage completeness
auto delegate_str = "double delegate(double) pure nothrow @nogc @safe";
assert(typeid(typeof(&sqr)).toString() == delegate_str);
assert(delegate_str.hashOf() == typeid(typeof(&sqr)).hashOf());
assert(typeid(typeof(&sqr)).toHash() == typeid(typeof(&sqr)).hashOf());
int g;
alias delegate_type = typeof((int a, int b) => a + b + g);
delegate_str = "int delegate(int, int) pure nothrow @nogc @safe";
assert(typeid(delegate_type).toString() == delegate_str);
assert(delegate_str.hashOf() == typeid(delegate_type).hashOf());
assert(typeid(delegate_type).toHash() == typeid(delegate_type).hashOf());
}
override bool opEquals(Object o)
{
if (this is o)
@ -2392,19 +2326,6 @@ class TypeInfo_Delegate : TypeInfo
return c && this.deco == c.deco;
}
@system unittest
{
double sqr(double x) { return x * x; }
int dbl(int x) { return x + x; }
sqr(double.init); // for coverage completeness
dbl(int.init); // for coverage completeness
Object obj = typeid(typeof(&sqr));
assert(obj.opEquals(typeid(typeof(&sqr))));
assert(typeid(typeof(&sqr)) == typeid(typeof(&sqr)));
assert(typeid(typeof(&dbl)) != typeid(typeof(&sqr)));
}
override size_t getHash(scope const void* p) @trusted const
{
return hashOf(*cast(const void delegate() *)p);
@ -2612,7 +2533,7 @@ class TypeInfo_Const : TypeInfo
}
///For some reason, getHash for interfaces wanted that
pragma(mangle, "_D9invariant12_d_invariantFC6ObjectZv")
pragma(mangle, "_D2rt10invariant_12_d_invariantFC6ObjectZv")
extern(D) void _d_invariant(Object o)
{
TypeInfo_Class c;
@ -2886,7 +2807,6 @@ TTo[] __ArrayCast(TFrom, TTo)(return scope TFrom[] from) nothrow
if ((fromSize % TTo.sizeof) != 0)
{
//onArrayCastError(TFrom.stringof, fromSize, TTo.stringof, toLength * TTo.sizeof);
import arsd.webassembly;
abort();
}
@ -2966,7 +2886,8 @@ V[K] dup(T : V[K], K, V)(T aa)
/** ditto */
V[K] dup(T : V[K], K, V)(T* aa)
{
return (*aa).dup;
assert(false, "Not implemented for platform");
return (*aa).dup;
}
T[] dup(T)(scope T[] array) pure nothrow @trusted if (__traits(isPOD, T) && !is(const(T) : T))

72
wasm/runtime/std/format.d Normal file
View File

@ -0,0 +1,72 @@
module std.format;
import dlib.util;
import std.traits;
import wasm;
char[]
sformat(Char, Args...)(scope return char[] buf, scope const(Char)[] fmt, Args args)
{
SprintfType type = SprintfType.None;
static foreach(i; 0 .. Args.length)
{
static if(isArray!(Args[i]) || is(Args[i] == string))
{
{
alias ElementType = typeof(*Args[i].init.ptr);
with(SprintfType)
{
static if(is(Args[i] == string)) type = String;
else static if(is(ElementType == ubyte)) type = U8Array;
else static if(is(ElementType == byte)) type = I8Array;
else static if(is(ElementType == ushort)) type = U16Array;
else static if(is(ElementType == short)) type = I16Array;
else static if(is(ElementType == uint)) type = U32Array;
else static if(is(ElementType == int)) type = I32Array;
else static if(is(ElementType == ulong)) type = U64Array;
else static if(is(ElementType == long)) type = I64Array;
else static if(is(ElementType == float)) type = F32Array;
else static if(is(ElementType == double)) type = F64Array;
else static if(is(ElementType == char)) type = CharArray;
else static assert(false, "Type unsupported");
}
SprintfLoadArray(args[i].length, args[i].ptr, type);
}
}
else
{
with(SprintfType)
{
static if(is(Args[i] == ubyte)) type = U8;
else static if(is(Args[i] == byte)) type = I8;
else static if(is(Args[i] == ushort)) type = U16;
else static if(is(Args[i] == short)) type = I16;
else static if(is(Args[i] == uint)) type = U32;
else static if(is(Args[i] == int)) type = I32;
else static if(is(Args[i] == ulong)) type = U64;
else static if(is(Args[i] == long)) type = I64;
else static if(is(Args[i] == size_t)) type = SizeT;
else static if(is(Args[i] == float)) type = F32;
else static if(is(Args[i] == double)) type = F64;
else static if(is(Args[i] == bool)) type = Bool;
else static if(is(Args[i] == char)) type = Char;
else static assert(false, "Type unsupported");
}
SprintfLoadValue(&args[i], type);
}
}
size_t length = SprintfEnd(buf, Str(fmt));
return buf[0 .. length];
}
char[]
sformat(alias fmt, Args...)(char[] buf, Args args) if(isSomeString!(typeof(fmt)))
{
return .sformat(buf, fmt, args);
}

View File

@ -0,0 +1,258 @@
module std.math.traits;
import std.traits;
enum RealFormat
{
ieeeHalf,
ieeeSingle,
ieeeDouble,
ieeeExtended, // x87 80-bit real
ieeeExtended53, // x87 real rounded to precision of double.
ibmExtended, // IBM 128-bit extended
ieeeQuadruple,
}
template floatTraits(T)
{
import std.traits : Unqual;
// EXPMASK is a ushort mask to select the exponent portion (without sign)
// EXPSHIFT is the number of bits the exponent is left-shifted by in its ushort
// EXPBIAS is the exponent bias - 1 (exp == EXPBIAS yields ×2^-1).
// EXPPOS_SHORT is the index of the exponent when represented as a ushort array.
// SIGNPOS_BYTE is the index of the sign when represented as a ubyte array.
// RECIP_EPSILON is the value such that (smallest_subnormal) * RECIP_EPSILON == T.min_normal
enum Unqual!T RECIP_EPSILON = (1/T.epsilon);
static if (T.mant_dig == 24)
{
// Single precision float
enum ushort EXPMASK = 0x7F80;
enum ushort EXPSHIFT = 7;
enum ushort EXPBIAS = 0x3F00;
enum uint EXPMASK_INT = 0x7F80_0000;
enum uint MANTISSAMASK_INT = 0x007F_FFFF;
enum realFormat = RealFormat.ieeeSingle;
version (LittleEndian)
{
enum EXPPOS_SHORT = 1;
enum SIGNPOS_BYTE = 3;
}
else
{
enum EXPPOS_SHORT = 0;
enum SIGNPOS_BYTE = 0;
}
}
else static if (T.mant_dig == 53)
{
static if (T.sizeof == 8)
{
// Double precision float, or real == double
enum ushort EXPMASK = 0x7FF0;
enum ushort EXPSHIFT = 4;
enum ushort EXPBIAS = 0x3FE0;
enum uint EXPMASK_INT = 0x7FF0_0000;
enum uint MANTISSAMASK_INT = 0x000F_FFFF; // for the MSB only
enum ulong MANTISSAMASK_LONG = 0x000F_FFFF_FFFF_FFFF;
enum realFormat = RealFormat.ieeeDouble;
version (LittleEndian)
{
enum EXPPOS_SHORT = 3;
enum SIGNPOS_BYTE = 7;
}
else
{
enum EXPPOS_SHORT = 0;
enum SIGNPOS_BYTE = 0;
}
}
else static if (T.sizeof == 12)
{
// Intel extended real80 rounded to double
enum ushort EXPMASK = 0x7FFF;
enum ushort EXPSHIFT = 0;
enum ushort EXPBIAS = 0x3FFE;
enum realFormat = RealFormat.ieeeExtended53;
version (LittleEndian)
{
enum EXPPOS_SHORT = 4;
enum SIGNPOS_BYTE = 9;
}
else
{
enum EXPPOS_SHORT = 0;
enum SIGNPOS_BYTE = 0;
}
}
else
static assert(false, "No traits support for " ~ T.stringof);
}
else static if (T.mant_dig == 64)
{
// Intel extended real80
enum ushort EXPMASK = 0x7FFF;
enum ushort EXPSHIFT = 0;
enum ushort EXPBIAS = 0x3FFE;
enum realFormat = RealFormat.ieeeExtended;
version (LittleEndian)
{
enum EXPPOS_SHORT = 4;
enum SIGNPOS_BYTE = 9;
}
else
{
enum EXPPOS_SHORT = 0;
enum SIGNPOS_BYTE = 0;
}
}
else static if (T.mant_dig == 113)
{
// Quadruple precision float
enum ushort EXPMASK = 0x7FFF;
enum ushort EXPSHIFT = 0;
enum ushort EXPBIAS = 0x3FFE;
enum realFormat = RealFormat.ieeeQuadruple;
version (LittleEndian)
{
enum EXPPOS_SHORT = 7;
enum SIGNPOS_BYTE = 15;
}
else
{
enum EXPPOS_SHORT = 0;
enum SIGNPOS_BYTE = 0;
}
}
else static if (T.mant_dig == 106)
{
// IBM Extended doubledouble
enum ushort EXPMASK = 0x7FF0;
enum ushort EXPSHIFT = 4;
enum realFormat = RealFormat.ibmExtended;
// For IBM doubledouble the larger magnitude double comes first.
// It's really a double[2] and arrays don't index differently
// between little and big-endian targets.
enum DOUBLEPAIR_MSB = 0;
enum DOUBLEPAIR_LSB = 1;
// The exponent/sign byte is for most significant part.
version (LittleEndian)
{
enum EXPPOS_SHORT = 3;
enum SIGNPOS_BYTE = 7;
}
else
{
enum EXPPOS_SHORT = 0;
enum SIGNPOS_BYTE = 0;
}
}
else
static assert(false, "No traits support for " ~ T.stringof);
}
bool isNaN(X)(X x) @nogc @trusted pure nothrow
if (isFloatingPoint!(X))
{
version (all)
{
return x != x;
}
else
{
/*
Code kept for historical context. At least on Intel, the simple test
x != x uses one dedicated instruction (ucomiss/ucomisd) that runs in one
cycle. Code for 80- and 128-bits is larger but still smaller than the
integrals-based solutions below. Future revisions may enable the code
below conditionally depending on hardware.
*/
alias F = floatTraits!(X);
static if (F.realFormat == RealFormat.ieeeSingle)
{
const uint p = *cast(uint *)&x;
// Sign bit (MSB) is irrelevant so mask it out.
// Next 8 bits should be all set.
// At least one bit among the least significant 23 bits should be set.
return (p & 0x7FFF_FFFF) > 0x7F80_0000;
}
else static if (F.realFormat == RealFormat.ieeeDouble)
{
const ulong p = *cast(ulong *)&x;
// Sign bit (MSB) is irrelevant so mask it out.
// Next 11 bits should be all set.
// At least one bit among the least significant 52 bits should be set.
return (p & 0x7FFF_FFFF_FFFF_FFFF) > 0x7FF0_0000_0000_0000;
}
else static if (F.realFormat == RealFormat.ieeeExtended ||
F.realFormat == RealFormat.ieeeExtended53)
{
const ushort e = F.EXPMASK & (cast(ushort *)&x)[F.EXPPOS_SHORT];
const ulong ps = *cast(ulong *)&x;
return e == F.EXPMASK &&
ps & 0x7FFF_FFFF_FFFF_FFFF; // not infinity
}
else static if (F.realFormat == RealFormat.ieeeQuadruple)
{
const ushort e = F.EXPMASK & (cast(ushort *)&x)[F.EXPPOS_SHORT];
const ulong psLsb = (cast(ulong *)&x)[MANTISSA_LSB];
const ulong psMsb = (cast(ulong *)&x)[MANTISSA_MSB];
return e == F.EXPMASK &&
(psLsb | (psMsb& 0x0000_FFFF_FFFF_FFFF)) != 0;
}
else
{
return x != x;
}
}
}
bool isInfinity(X)(X x) @nogc @trusted pure nothrow
if (isFloatingPoint!(X))
{
import std.math.traits : floatTraits, RealFormat, MANTISSA_MSB, MANTISSA_LSB;
alias F = floatTraits!(X);
static if (F.realFormat == RealFormat.ieeeSingle)
{
return ((*cast(uint *)&x) & 0x7FFF_FFFF) == 0x7F80_0000;
}
else static if (F.realFormat == RealFormat.ieeeDouble)
{
return ((*cast(ulong *)&x) & 0x7FFF_FFFF_FFFF_FFFF)
== 0x7FF0_0000_0000_0000;
}
else static if (F.realFormat == RealFormat.ieeeExtended ||
F.realFormat == RealFormat.ieeeExtended53)
{
const ushort e = cast(ushort)(F.EXPMASK & (cast(ushort *)&x)[F.EXPPOS_SHORT]);
const ulong ps = *cast(ulong *)&x;
// On Motorola 68K, infinity can have hidden bit = 1 or 0. On x86, it is always 1.
return e == F.EXPMASK && (ps & 0x7FFF_FFFF_FFFF_FFFF) == 0;
}
else static if (F.realFormat == RealFormat.ieeeQuadruple)
{
const long psLsb = (cast(long *)&x)[MANTISSA_LSB];
const long psMsb = (cast(long *)&x)[MANTISSA_MSB];
return (psLsb == 0)
&& (psMsb & 0x7FFF_FFFF_FFFF_FFFF) == 0x7FFF_0000_0000_0000;
}
else
{
return (x < -X.max) || (X.max < x);
}
}
version (LittleEndian)
{
enum MANTISSA_LSB = 0;
enum MANTISSA_MSB = 1;
}
else
{
enum MANTISSA_LSB = 1;
enum MANTISSA_MSB = 0;
}

22
wasm/runtime/std/stdio.d Normal file
View File

@ -0,0 +1,22 @@
module std.stdio;
import dlib.util;
import std.traits;
void
writefln(alias fmt, A...)(A args) if(isSomeString!(typeof(fmt)))
{
static foreach(i; 0 .. A.length)
{
{
static if()
}
}
}
void
writefln(Char, A...)(in Char[] fmt, A args)
{
writefln(Str(fmt), args);
}

84
wasm/runtime/std/traits.d Normal file
View File

@ -0,0 +1,84 @@
module std.traits;
enum bool isFloatingPoint(T) = __traits(isFloating, T) && is(T : real);
template isIntegral(T)
{
static if (!__traits(isIntegral, T))
enum isIntegral = false;
else static if (is(T U == enum))
enum isIntegral = isIntegral!U;
else
enum isIntegral = __traits(isZeroInit, T) // Not char, wchar, or dchar.
&& !is(immutable T == immutable bool) && !is(T == __vector);
}
template isNumeric(T)
{
static if (!__traits(isArithmetic, T))
enum isNumeric = false;
else static if (__traits(isFloating, T))
enum isNumeric = is(T : real); // Not __vector, imaginary, or complex.
else static if (is(T U == enum))
enum isNumeric = isNumeric!U;
else
enum isNumeric = __traits(isZeroInit, T) // Not char, wchar, or dchar.
&& !is(immutable T == immutable bool) && !is(T == __vector);
}
enum bool isSigned(T) = __traits(isArithmetic, T) && !__traits(isUnsigned, T)
&& is(T : real);
enum hasMember(T, string name) = __traits(hasMember, T, name);
enum bool isArray(T) = isStaticArray!T || isDynamicArray!T;
enum bool isStaticArray(T) = __traits(isStaticArray, T);
template isDynamicArray(T)
{
static if (is(T == U[], U))
enum bool isDynamicArray = true;
else static if (is(T U == enum))
// BUG: isDynamicArray / isStaticArray considers enums
// with appropriate base types as dynamic/static arrays
// Retain old behaviour for now, see
// https://github.com/dlang/phobos/pull/7574
enum bool isDynamicArray = isDynamicArray!U;
else
enum bool isDynamicArray = false;
}
enum isAssignable(Lhs, Rhs = Lhs) = isRvalueAssignable!(Lhs, Rhs) && isLvalueAssignable!(Lhs, Rhs);
enum isRvalueAssignable(Lhs, Rhs = Lhs) = __traits(compiles, { lvalueOf!Lhs = rvalueOf!Rhs; });
enum isLvalueAssignable(Lhs, Rhs = Lhs) = __traits(compiles, { lvalueOf!Lhs = lvalueOf!Rhs; });
enum bool isInstanceOf(alias S, T) = is(T == S!Args, Args...);
template isInstanceOf(alias S, alias T)
{
enum impl(alias T : S!Args, Args...) = true;
enum impl(alias T) = false;
enum isInstanceOf = impl!T;
}
import core.internal.traits : CoreUnqual = Unqual;
alias Unqual = CoreUnqual;
@property T rvalueOf(T)(inout __InoutWorkaroundStruct = __InoutWorkaroundStruct.init);
@property ref T lvalueOf(T)(inout __InoutWorkaroundStruct = __InoutWorkaroundStruct.init);
enum bool isIterable(T) = is(typeof({ foreach (elem; T.init) {} }));
template isAggregateType(T)
{
static if (is(T == enum))
enum isAggregateType = isAggregateType!(OriginalType!T);
else
enum isAggregateType = is(T == struct) || is(T == class) || is(T == interface) || is(T == union);
}
enum bool isSomeString(T) = is(immutable T == immutable C[], C) && (is(C == char) || is(C == wchar) || is(C == dchar));

51
wasm/runtime/wasm.d Normal file
View File

@ -0,0 +1,51 @@
module wasm;
import ldc.attributes;
enum SprintfType : size_t
{
None,
U8,
I8,
U16,
I16,
U32,
I32,
U64,
I64,
SizeT,
F32,
F64,
Bool,
String,
U8Array,
I8Array,
U16Array,
I16Array,
U32Array,
I32Array,
U64Array,
I64Array,
F32Array,
F64Array,
Char,
CharArray,
}
extern extern(C) @nogc pure @llvmAttr("wasm-import-module", "env"):
@llvmAttr("wasm-import-name", "Console") void
Console(string str, bool write_line);
@llvmAttr("wasm-import-name", "Abort") void
Abort(string message);
@llvmAttr("wasm-import-name", "SprintfLoadValue") void
SprintfLoadValue(const(void)* ptr, SprintfType type);
@llvmAttr("wasm-import-name", "SprintfLoadArray") void
SprintfLoadArray(size_t length, const(void)* ptr, SprintfType type);
@llvmAttr("wasm-import-name", "SprintfEnd") size_t
SprintfEnd(char[] buffer, string format);

View File

@ -0,0 +1,285 @@
const TYPES = {
U8: 1,
I8: 2,
U16: 3,
I16: 4,
U32: 5,
I32: 6,
U64: 7,
I64: 8,
SizeT: 9,
F32: 10,
F64: 11,
Bool: 12,
String: 13,
U8Array: 14,
I8Array: 15,
U16Array: 16,
I16Array: 17,
U32Array: 18,
I32Array: 19,
U64Array: 20,
I64Array: 21,
F32Array: 22,
F64Array: 23,
Char: 24,
CharArray: 25,
};
function assert(condition, message)
{
if(!condition)
{
console.assert(condition, message);
throw "Assert failure";
}
}
class WasmManager
{
constructor(memory, ptr_size = 4)
{
this.ptr_size = ptr_size;
this.sprintf_values = [];
this.stdout_string = "";
switch(this.ptr_size)
{
case 8: this.LoadSize = this.LoadU64; break;
case 4:
default: this.LoadSize = this.LoadU32; break;
}
}
SetMemory(memory)
{
this.memory = memory;
this.data_view = new DataView(this.memory.buffer);
}
LoadBool = (addr) => Boolean(this.data_view.getUint8(addr, true));
LoadF32 = (addr) => this.data_view.getFloat32(addr, true);
LoadF64 = (addr) => this.data_view.getFloat64(addr, true);
LoadU8 = (addr) => this.data_view.getUint8 (addr, true);
LoadI8 = (addr) => this.data_view.getInt8 (addr, true);
LoadU16 = (addr) => this.data_view.getUint16 (addr, true);
LoadI16 = (addr) => this.data_view.getInt16 (addr, true);
LoadU32 = (addr) => this.data_view.getUint32 (addr, true);
LoadI32 = (addr) => this.data_view.getInt32 (addr, true);
LoadU64(addr)
{
const lo = this.data_view.getUint32(addr+0, true);
const hi = this.data_view.getUint32(addr+4, true);
return lo + hi*4294967296;
}
LoadI64(addr)
{
const lo = this.data_view.getUint32(addr+0, true);
const hi = this.data_view.getInt32 (addr+4, true);
return lo + hi*4294967296;
}
LoadArray = (array_class, length, addr) => new array_class(this.memory.buffer, addr, length);
LoadString = (length, addr) => new TextDecoder().decode(this.LoadArray(Uint8Array, length, addr));
LoadCString(ptr)
{
if(!ptr) return null;
let length = 0;
for(; this.LoadU8(ptr+length); length += 1) {}
return this.LoadString(ptr, length);
}
StoreString(ptr, value)
{
const src = new TextEncoder().encode(value);
const dst = new Uint8Array(this.memory.buffer, ptr, src.length);
dst.set(src);
return src.length;
}
Imports()
{
const manager = this;
return {
env: {
Abort(string_length, string_ptr)
{
console.error(manager.LoadString(string_length, string_ptr));
},
SprintfLoadValue(ptr, type)
{
let value = null;
switch(type)
{
case TYPES.U8: value = manager.LoadU8(ptr); break;
case TYPES.I8: value = manager.LoadI8(ptr); break;
case TYPES.U16: value = manager.LoadU16(ptr); break;
case TYPES.I16: value = manager.LoadI16(ptr); break;
case TYPES.U32: value = manager.LoadU32(ptr); break;
case TYPES.I32: value = manager.LoadI32(ptr); break;
case TYPES.U64: value = manager.LoadU64(ptr); break;
case TYPES.I64: value = manager.LoadI64(ptr); break;
case TYPES.SizeT: value = manager.LoadSize(ptr); break;
case TYPES.F32: value = manager.LoadF32(ptr); break;
case TYPES.F64: value = manager.LoadF64(ptr); break;
case TYPES.Bool: value = manager.LoadBool(ptr); break;
case TYPES.Char: value = manager.LoadString(1, ptr); break;
default: break;
}
if(value)
{
if(type === TYPES.Bool)
{
manager.sprintf_values.push(value ? 'true' : 'false');
}
else
{
manager.sprintf_values.push(''+value);
}
}
},
SprintfLoadArray(length, ptr, type)
{
let value = null;
switch(type)
{
case TYPES.CharArray:
case TYPES.String: value = manager.LoadString(length, ptr); break;
case TYPES.U8Array: value = manager.LoadArray(Uint8Array, length, ptr); break;
case TYPES.I8Array: value = manager.LoadArray(Int8Array, length, ptr); break;
case TYPES.U16Array: value = manager.LoadArray(Uint16Array, length, ptr); break;
case TYPES.I16Array: value = manager.LoadArray(Int16Array, length, ptr); break;
case TYPES.U32Array: value = manager.LoadArray(Uint32Array, length, ptr); break;
case TYPES.I32Array: value = manager.LoadArray(Int32Array, length, ptr); break;
case TYPES.U64Array: value = manager.LoadArray(BigUint64Array, length, ptr); break;
case TYPES.I64Array: value = manager.LoadArray(BigInt64Array, length, ptr); break;
case TYPES.F32Array: value = manager.LoadArray(Float32Array, length, ptr); break;
case TYPES.F64Array: value = manager.LoadArray(Float64Array, length, ptr); break;
}
if(value)
{
if(type !== TYPES.String && type !== TYPES.CharArray)
{
value = `[${value.join(", ")}]`;
}
manager.sprintf_values.push(value);
}
},
SprintfEnd(buffer_length, buffer_ptr, string_length, string_ptr)
{
let format_string = manager.LoadString(string_length, string_ptr);
let index = format_string.indexOf("%");
let result = format_string.substring(0, index);
let format = format_string.substring(index);
function Next()
{
index = format.indexOf("%");
result += format.substring(0, index);
format = format.substring(index);
}
let value_index = 0;
for(; value_index < manager.sprintf_values.length;)
{
assert(format.length);
const char = format.charAt(1);
assert(char !== " " && char.length, "Invalid format string " + format_string);
if(char === "%")
{
result += "%";
format = format.substring(2);
Next();
continue;
}
else if(char >= '0' && char <= '9')
{
let digit_index = 2;
for(;;)
{
let next_char = format.charAt(digit_index++);
assert(next_char !== " " && next_char.length, "Invalid format string " + format_string);
if(next_char < '0' || next_char > '9')
{
continue;
}
result += manager.sprintf_values[value_index++];
format = format.substring(digit_index);
Next();
break;
}
}
else if((char >= 'a' && char <= 'z') || (char >= 'A' && char <= 'Z'))
{
result += manager.sprintf_values[value_index++];
format = format.substring(2);
Next();
}
else assert(false, "Invalid format string " + format_string);
}
result += format;
const bytes_written = manager.StoreString(buffer_ptr, result);
assert(bytes_written <= buffer_length, "Format string is longer than buffer length");
manager.sprintf_values = [];
return bytes_written;
},
Console(string_length, string_ptr, write_line)
{
manager.stdout_string += manager.LoadString(string_length, string_ptr);
if(write_line || manager.stdout_string.includes("\n"))
{
console.log(manager.stdout_string);
manager.stdout_string = "";
}
},
},
}
}
}
async function StartWasm(path)
{
const manager = new WasmManager();
const response = await fetch(path);
const data = await response.arrayBuffer();
const wasm = await WebAssembly.instantiate(data, manager.Imports());
const exports = wasm.instance.exports;
if(exports.memory)
{
manager.SetMemory(exports.memory);
}
exports?._start();
return;
}