Comments (36)
I probably need to add "error" and "__overflow" wrapped function to Box86 (error is a bit tricky, but __overflow is a trivial "iFpi" function).
Are you using the main branch or the dynarec one (it shouldn't change much the result of your current tests anyway)?
from box86.
FYI, I have added the 2 missing function in the Dynarec branch.
from box86.
Thanks.
du doesn't work:
16806|0xb6fbeea7: Calling malloc (00000014, 00000000, B6FBE2B0...) => return 0xA810FBD8
Program received signal SIGSEGV, Segmentation fault.
0xb6dcbaee in strlen () from /usr/lib/libc.so.6
(gdb) bt
#0 0xb6dcbaee in strlen () from /usr/lib/libc.so.6
#1 0xb6da8b00 in __vfprintf_internal () from /usr/lib/libc.so.6
#2 0xb6dba72c in __vsnprintf_internal () from /usr/lib/libc.so.6
#3 0xb6d98464 in snprintf () from /usr/lib/libc.so.6
#4 0xa807a5f0 in x86Int3 (emu=0xa80f7d70) at /tmp/box86/src/emu/x86int3.c:95
#5 0xa804e2d8 in Run (emu=0xa80f7d70, step=-104, step@entry=0) at /tmp/box86/src/emu/x86run.c:839
#6 0xa80287d0 in main (argc=<optimized out>, argv=<optimized out>, env=<optimized out>) at /tmp/box86/src/main.c:533
(gdb) up 4
#4 0xa807a5f0 in x86Int3 (emu=0xa80f7d70) at /tmp/box86/src/emu/x86int3.c:95
95 snprintf(buff, 255, "%04d|%p: Calling %s(\"%s\", %d)", tid, *(void**)(R_ESP), s, *(char**)(R_ESP+4), *(int*)(R_ESP+8));
(gdb) p s
$5 = 0xb6ffe8c8 <__stack_chk_guard> ""
glxgears runs at native speed (with vblank_mode=0 to disable vsync).
I added some libpng symbols, and glmark2 runs, at 90% of native performance:
BOX86_LD_LIBRARY_PATH: /tmp/mu/lib/i386-linux-gnu/:/tmp/mu/usr/lib/i386-linux-gnu/
Using default BOX86_PATH: ./:bin/
Counted 67 Env var
Looking for ./glmark2
Using native(wrapped) libjpeg.so.8
Using native(wrapped) libpng16.so.16
Using native(wrapped) libX11.so.6
Using native(wrapped) libGL.so.1
Using emulated /tmp/mu/usr/lib/i386-linux-gnu/libstdc++.so.6
Using native(wrapped) libm.so.6
Using native(wrapped) libc.so.6
Using native(wrapped) ld-linux.so.2
Using native(wrapped) libpthread.so.0
Using native(wrapped) librt.so.1
Using emulated /tmp/mu/lib/i386-linux-gnu/libgcc_s.so.1
Warning: Weak Symbol _ZGTtnaj not found, cannot apply R_386_JMP_SLOT @0xb66af198 (0x6b666)
Warning: Weak Symbol _ZGTtdlPv not found, cannot apply R_386_JMP_SLOT @0xb66af474 (0x6c1d6)
=======================================================
glmark2 2014.03+git20150611.fa71af2d
=======================================================
OpenGL Information
GL_VENDOR: panfrost
GL_RENDERER: panfrost
GL_VERSION: 2.1 Mesa 19.3.0-devel (git-075a96aa92)
=======================================================
[build] use-vbo=false: FPS: 532 FrameTime: 1.880 ms
[build] use-vbo=true: FPS: 851 FrameTime: 1.175 ms
[texture] texture-filter=nearest: FPS: 868 FrameTime: 1.152 ms
[texture] texture-filter=linear: FPS: 858 FrameTime: 1.166 ms
[texture] texture-filter=mipmap: FPS: 956 FrameTime: 1.046 ms
[shading] shading=gouraud: FPS: 584 FrameTime: 1.712 ms
[shading] shading=blinn-phong-inf: FPS: 588 FrameTime: 1.701 ms
[shading] shading=phong: FPS: 498 FrameTime: 2.008 ms
[shading] shading=cel: FPS: 460 FrameTime: 2.174 ms
[bump] bump-render=high-poly: FPS: 260 FrameTime: 3.846 ms
[bump] bump-render=normals: FPS: 1094 FrameTime: 0.914 ms
[bump] bump-render=height: FPS: 936 FrameTime: 1.068 ms
[effect2d] kernel=0,1,0;1,-4,1;0,1,0;: FPS: 704 FrameTime: 1.420 ms
[effect2d] kernel=1,1,1,1,1;1,1,1,1,1;1,1,1,1,1;: FPS: 190 FrameTime: 5.263 ms
[pulsar] light=false:quads=5:texture=false: FPS: 1045 FrameTime: 0.957 ms
[desktop] blur-radius=5:effect=blur:passes=1:separable=true:windows=4: FPS: 113 FrameTime: 8.850 ms
[desktop] effect=shadow:windows=4: FPS: 409 FrameTime: 2.445 ms
[buffer] columns=200:interleave=false:update-dispersion=0.9:update-fraction=0.5:update-method=map: FPS: 13 FrameTime: 76.923 ms
[buffer] columns=200:interleave=false:update-dispersion=0.9:update-fraction=0.5:update-method=subdata: FPS: 13 FrameTime: 76.923 ms
[buffer] columns=200:interleave=true:update-dispersion=0.9:update-fraction=0.5:update-method=map: FPS: 18 FrameTime: 55.556 ms
[ideas] speed=duration: FPS: 154 FrameTime: 6.494 ms
[jellyfish] <default>: FPS: 371 FrameTime: 2.695 ms
=======================================================
glmark2 Score: 523
=======================================================
The terrain bench crashes with SIGILL in jpeg_CreateDecompress:
#0 0xb6c71f60 in ?? ()
#1 0xb6b16f88 in jpeg_CreateDecompress () from /usr/lib/libjpeg.so.8
#2 0xa8079c94 in x86Int3 (emu=0xa80f7d98) at /tmp/box86/src/emu/x86int3.c:204
#3 0xa804e2d8 in Run (emu=0xa80f7d98, step=80, step@entry=0) at /tmp/box86/src/emu/x86run.c:839
#4 0xa80287d0 in main (argc=<optimized out>, argv=<optimized out>, env=<optimized out>) at /tmp/box86/src/main.c:533
from box86.
The du
issue: not sure what is happening. The crash you backtrace is box86 own trace that try to printf some NULL string. That's bad but this crash shouldn't happens if you run without any log right?
About glmark2: 90% of native is very good :D \o/. I guess you are using the dynarec to get that number.
The jpeg crash, well, that probably a callback that I didn't wrapped. I'll check that back later.
from box86.
When running du without logging, I just get:
Counted 69 Env var
Looking for du
Using native(wrapped) libc.so.6
Using native(wrapped) ld-linux.so.2
Using native(wrapped) libpthread.so.0
Using native(wrapped) librt.so.1
./du: cannot read directory '..': Invalid argument
0 ..
from box86.
Ah, sounds like a bug in an dynarec opcode.
Can you try without dynarec (using BOX86_DYNAREC=0
)
from box86.
I actually wasn't using dynarec - I didn't realise you had to run cmake with -DARM_DYNAREC=1...
from box86.
Ah, damn, so that's a bug even in the Interpretor (so glmark2 runing at 90% with the interpretor, glmark is mostly GL code, with very few C code around the tests)
from box86.
Also, I should have fixed the crash in du
when using BOX86_LOG=3
from box86.
And I have just pused some changes in COMPILE.md about Dynarec compilation.
Be sure to have -m32
in you compilation flags, or the Dynarec will probably not work (and just segfault).
I'll make that parameter automatic in cmake project later.
from box86.
For du, the function that is trying to be logged is openat64. The strstr(s, "open")
matches that, so it logs the function wrong. I think du doesn't work properly without logging because I didn't add one of the symbols it needs properly.
I recompiled with dynarec, and gzip went from 3.5% of native speed to 25% of native, which is a big improvement. glmark2 now runs at 98% of native speed.
When running make in parallel, it doesn't wait for wrapper.h to be built before building files that depend on it, so I have to use make clean; timeout 1 make; make -j8
to compile box86 properly.
EDIT: I recompiled without my patches and du behaves in the same way.
from box86.
strace shows that this call is causing the error:
openat(AT_FDCWD, ".", O_RDONLY|O_NOCTTY|O_NONBLOCK|O_DIRECT|O_LARGEFILE|O_CLOEXEC) = -1 EINVAL (Invalid argument)
With native du:
openat(AT_FDCWD, ".", O_RDONLY|O_NOCTTY|O_NONBLOCK|O_LARGEFILE|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 3
from box86.
Ah, nice intel. So the O_DIRECTORY
is missing.
from box86.
and O_DIRECT
should not be there. And O_NOFOLLOW
is also missing
from box86.
The only important one is O_DIRECT, which newer kernels don't like:
https://bugzilla.redhat.com/show_bug.cgi?id=567113#c11
from box86.
This also happens with grep -r
:
openat(AT_FDCWD, ".", O_RDONLY|O_NOCTTY|O_NONBLOCK|O_DIRECT|O_LARGEFILE|O_CLOEXEC) = -1 EINVAL (Invalid argument)
from box86.
It seems the flags are different between architectures so some translation will have to be done.
from box86.
These are the differences:
Flag | Arm | x86 |
---|---|---|
O_DIRECTORY | 040000 | 0200000 |
O_DIRECT | 0200000 | 040000 |
O_NOFOLLOW | 0100000 | 0400000 |
O_LARGEFILE | 0400000 | 0100000 |
So the O_DIRECTORY flag on x86 is the O_DIRECT flag on arm.
This could be fixed with something like (untested):
flag = (flag&~0740000) + (flag&0140000)*4 + (flag&0600000)/4;
for all the functions using these flags, maybe by adding an "I/O flag type" for the wrapper functions.
from box86.
Wow, nice debug. Thanks a lot!
So the bad news is , I need to wrap many functions because of that.
from box86.
I think only the open.* functions need fixing, everything else should be fine.
from box86.
So, I tried to a push a first workarournd with commit aad4c91
I'll try to get a more portable solution later.
from box86.
So, did you just grep for functions taking flags? Most of these flag types are unrelated.
Also, they aren't AT_ flags, they're file open flags - maybe you got mixed up with the first field of openat, which can be an AT_ flag.
du does at least somewhat work, but does crash with abort (core dumped)
partway though.
from box86.
bah yeah, I somewhat looked for "at" function using flags. I'll recheck what flags it is, maybe I was a bit too fast :(
from box86.
diff --git a/rebuild_wrappers.py b/rebuild_wrappers.py
index c037a83..042956d 100755
--- a/rebuild_wrappers.py
+++ b/rebuild_wrappers.py
@@ -4,7 +4,7 @@ import os
import glob
import sys
-values = ['E', 'e', 'v', 'c', 'w', 'i', 'I', 'C', 'W', 'u', 'U', 'f', 'd', 'D', 'L', 'p', 'V']
+values = ['E', 'e', 'v', 'c', 'w', 'i', 'I', 'C', 'W', 'u', 'U', 'f', 'd', 'D', 'L', 'p', 'V', 'O']
def splitchar(s):
ret = [len(s)]
i = 0
@@ -226,6 +226,11 @@ typedef union ui64_s {
#else
#define ST0val ST0.d
#endif
+#ifndef NOALIGN
+int oflag_convert(int flag) { return (flag&~0740000) + (flag&0140000)*4 + (flag&0600000)/4; }
+#else
+int oflag_convert(int flag) { return flag; }
+#endif
""",
"wrapper.h": """/*****************************************************************
@@ -249,6 +254,7 @@ typedef void (*wrapper_t)(x86emu_t* emu, uintptr_t fnc);
// o = stdout
// C = unsigned byte c = char
// W = unsigned short w = short
+// O = file open flag
// Q = ...
// S8 = struct, 8 bytes
@@ -292,8 +298,8 @@ typedef void (*wrapper_t)(x86emu_t* emu, uintptr_t fnc);
# First part: typedefs
for v in gbl["()"]:
- # E e v c w i I C W u U f d D L p V
- types = ["x86emu_t*", "x86emu_t**", "void", "int8_t", "int16_t", "int32_t", "int64_t", "uint8_t", "uint16_t", "uint32_t", "uint64_t", "float", "double", "long double", "double", "void*", "void*"]
+ # E e v c w i I C W u U f d D L p V O
+ types = ["x86emu_t*", "x86emu_t**", "void", "int8_t", "int16_t", "int32_t", "int64_t", "uint8_t", "uint16_t", "uint32_t", "uint64_t", "float", "double", "long double", "double", "void*", "void*", "int32_t"]
if len(values) != len(types):
raise NotImplementedError("len(values) = {lenval} != len(types) = {lentypes}".format(lenval=len(values), lentypes=len(types)))
@@ -303,8 +309,8 @@ typedef void (*wrapper_t)(x86emu_t* emu, uintptr_t fnc);
if k != "()":
file.write("\n#if " + k + "\n")
for v in gbl[k]:
- # E e v c w i I C W u U f d D L p V
- types = ["x86emu_t*", "x86emu_t**", "void", "int8_t", "int16_t", "int32_t", "int64_t", "uint8_t", "uint16_t", "uint32_t", "uint64_t", "float", "double", "long double", "double", "void*", "void*"]
+ # E e v c w i I C W u U f d D L p V O
+ types = ["x86emu_t*", "x86emu_t**", "void", "int8_t", "int16_t", "int32_t", "int64_t", "uint8_t", "uint16_t", "uint32_t", "uint64_t", "float", "double", "long double", "double", "void*", "void*", "int32_t"]
if len(values) != len(types):
raise NotImplementedError("len(values) = {lenval} != len(types) = {lentypes}".format(lenval=len(values), lentypes=len(types)))
@@ -344,10 +350,11 @@ typedef void (*wrapper_t)(x86emu_t* emu, uintptr_t fnc);
"*(long double*)(R_ESP + {p}), ", # D
"FromLD((void*)(R_ESP + {p})), ", # L
"*(void**)(R_ESP + {p}), ", # p
- "(void*)(R_ESP + {p}), " # V
+ "(void*)(R_ESP + {p}), ", # V
+ "oflag_convert(*(int32_t*)(R_ESP + {p})), ", # O
]
- # E e v c w i I C W u U f d D L p V
- deltas = [0, 0, 4, 4, 4, 4, 8, 4, 4, 4, 8, 4, 8, 12, 12, 4, 0]
+ # E e v c w i I C W u U f d D L p V O
+ deltas = [0, 0, 4, 4, 4, 4, 8, 4, 4, 4, 8, 4, 8, 12, 12, 4, 0, 4]
if len(values) != len(arg):
raise NotImplementedError("len(values) = {lenval} != len(arg) = {lenarg}".format(lenval=len(values), lenarg=len(arg)))
if len(values) != len(deltas):
@@ -374,6 +381,7 @@ typedef void (*wrapper_t)(x86emu_t* emu, uintptr_t fnc);
"double db=fn({0}); fpu_do_push(emu); ST0val = db;", # L
"R_EAX=(uintptr_t)fn({0});", # p
"\n#error Invalid return type: va_list\n", # V
+ "\n#error Invalid return type: oflag\n", # O
]
if len(values) != len(vals):
raise NotImplementedError("len(values) = {lenval} != len(vals) = {lenvals}".format(lenval=len(values), lenvals=len(vals)))
diff --git a/src/wrapped/wrappedlibc_private.h b/src/wrapped/wrappedlibc_private.h
index 79562ec..150eaa6 100755
--- a/src/wrapped/wrappedlibc_private.h
+++ b/src/wrapped/wrappedlibc_private.h
@@ -1188,15 +1188,15 @@ GO(_obstack_newchunk, vFpi)
GOM(obstack_vprintf, iFEppVV) // Weak
// __obstack_vprintf_chk
// on_exit // Weak
-GOM(open, iFEpiu) //Weak
-GOM(__open, iFEpiu) //Weak
-GO(__open_2, iFpi)
-GOM(open64, iFEpiu) //Weak
+GOM(open, iFEpOu) //Weak
+GOM(__open, iFEpOu) //Weak
+GO(__open_2, iFpO)
+GOM(open64, iFEpOu) //Weak
// __open64 // Weak
-GO(__open64_2, iFpi)
-GOW(openat, iFipiu)
+GO(__open64_2, iFpO)
+GOW(openat, iFipOu)
// __openat_2
-GOW(openat64, iFipiuuuuu) // variable arg...
+GOW(openat64, iFipOuuuuu) // variable arg...
// __openat64_2
// __open_catalog
GOW(opendir, pFp)
from box86.
Ah oops, I didn't saw your post and did ... just like you :) !
from box86.
Rather than do (flag&~0740000) & (flag&0140000)<<2 | (flag&0600000)>>2
, you need to do (flag&~0740000) + (flag&0140000)*4 + (flag&0600000)/4
.
from box86.
I did an &
? Wow, sorry for that.
I prefer to use logical operation for flags, that why I used ors and shifts. but the and is a mistake, I'll fix that, and add parenthesis around shift just to be sure.
from box86.
My way is faster, though:
#include <iostream>
#include <chrono>
int arith(int flag) {
return (flag&~0740000) + (flag&0140000)*4 + (flag&0600000)/4;
}
int log(int flag) {
return (flag&~0740000) | ((flag&0140000)<<2) | ((flag&0600000)>>2);
}
int main()
{
for (int i = 0; i < 11; ++i)
{
for (auto& x : {arith, log})
{
unsigned p = 0;
auto start = std::chrono::steady_clock::now();
for (int i = 0; i < 200000000; ++i)
p += x(i);
auto end = std::chrono::steady_clock::now();
std::chrono::duration<double> elapsed_seconds = end-start;
if (i)
std::cout << p << " elapsed time: " << elapsed_seconds.count() << "s\n";
}
}
}
$ g++ --version
g++ (GCC) 8.3.0
Copyright (C) 2018 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
$ g++ test.cpp -o test -O3
$ ./test
3649838848 elapsed time: 0.784133s
3649838848 elapsed time: 0.871706s
3649838848 elapsed time: 0.786289s
3649838848 elapsed time: 0.874663s
3649838848 elapsed time: 0.791375s
3649838848 elapsed time: 0.869405s
3649838848 elapsed time: 0.781652s
3649838848 elapsed time: 0.872683s
3649838848 elapsed time: 0.781764s
3649838848 elapsed time: 0.869669s
3649838848 elapsed time: 0.782279s
3649838848 elapsed time: 0.871678s
3649838848 elapsed time: 0.781617s
3649838848 elapsed time: 0.870333s
3649838848 elapsed time: 0.781778s
3649838848 elapsed time: 0.873591s
3649838848 elapsed time: 0.78662s
3649838848 elapsed time: 0.869386s
3649838848 elapsed time: 0.781619s
3649838848 elapsed time: 0.870121s
Total time for arith: 7.839126s
Total time for log: 8.713235s
You can say "broken compiler" all you want, but at least for current compilers, using arithmetic operators is over 10% faster.
from box86.
Oh, that's a bit surprising.
I checked with godbolt compiler explorer the asm output for both function, to understand the speed difference: https://godbolt.org/z/fm89Tv
And has you can see, the artimetic as 1 instruction more.
The difference is that this
add r3, r3, r2, lsl #2
in the addition pus a left shift (the *4)
became
lsl r3, r0, #2
...
orr r3, r3, r2
So yeah, compiler optimisation issue, it could have use the same trick and do an or
with the left shift.
Not a big deal, but it seems math are more optimized than logic.
Anyway, why I stick to Logic is not for speed (that not really important there), but for safety. As you have already seen, I do A LOT of mistakes... And applying 2 times the same flags (by mistake) with a OR is harmless, while with an ADD, it's not the same ending flag at all.
from box86.
With this patch, another one to force logging to /dev/null and some more wrapped functions, I can use proot to get an emulated shell working:
diff --git a/src/main.c b/src/main.c
index 88bb131..4289d95 100755
--- a/src/main.c
+++ b/src/main.c
@@ -271,6 +272,10 @@ void PrintHelp() {
}
int main(int argc, const char **argv, const char **env) {
+ if(argc > 1 && !strcmp(argv[1], "-E")) {
+ argv += 4;
+ argc -= 4;
+ }
// trying to open and load 1st arg
if(argc==1) {
$ ls --version | head -n1
ls (GNU coreutils) 8.31
$ BOX86_LD_LIBRARY_PATH=/usr/lib/i386-linux-gnu proot -q /tmp/box86/build/box86 -R . -w / /bin/bash --norc
bash-5.0$ arch
i686
bash-5.0$ cat /proc/
Display all 209 possibilities? (y or n)
bash-5.0$ cat /proc/cpuinfo | grep -i arm
model name : ARMv7 Processor rev 1 (v7l)
model name : ARMv7 Processor rev 1 (v7l)
model name : ARMv7 Processor rev 1 (v7l)
model name : ARMv7 Processor rev 1 (v7l)
bash-5.0$ ls --version | head -n1
ls (GNU coreutils) 8.30
bash-5.0$ /host-rootfs/bin/file -m /host-rootfs/usr/share/file/misc/magic /host-rootfs/bin/file
/host-rootfs/bin/file: ELF 32-bit LSB pie executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-armhf.so.3, BuildID[sha1]=c69cd255292653ec64033ee6ce47daa24bbeb913, for GNU/Linux 3.2.0, stripped
bash-5.0$ /host-rootfs/bin/file -m /host-rootfs/usr/share/file/misc/magic /bin/ls
/bin/ls: ELF 32-bit LSB pie executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=728a8169184c77ded3f193547fec36a7a7186b08, stripped
bash-5.0$ which ls
Segmentation fault (core dumped)
bash-5.0$ /bin/ls /
bin debootstrap dev etc home host-rootfs lib lib64 libx32 proc run sbin sys test tmp usr var
bash-5.0$
from box86.
For the logging, why not using the env var BOX86_LOG=0?
(also, the initial plan was to have default logging to 0, but while it's still heavily developped, I fixed it to 1, but maybe it would be a good idea to soon put the default back to 0.
Do you want your argc/argv patch to be merge? A chroot is interesting :)
I see the wich ls
command segfaulted.
What function did you had to wrap?
from box86.
proot doesn't seem to pass environment variables to box86 properly.
The argv/argc patch should probably be fixed by adding an option to proot to not prepend command-line options that would be used by qemu.
which
is actually a shell script.
If I execute it manually with bash, or dash and a shell option, it works:
bash-5.0$ which ls
Segmentation fault (core dumped)
bash-5.0$ bash /bin/which ls
bin/ls
bash-5.0$ dash /bin/which ls
Segmentation fault (core dumped)
bash-5.0$ dash -e /bin/which ls
/usr/bin/ls
I've attached a patch with the extra functions I have wrapped:
from box86.
Do you mind if I push your wrappers in?
from box86.
That's why I've posted them.
from box86.
Great, thanks. It's done.
from box86.
I think this ticket can be closed now!
from box86.
Related Issues (20)
- Unimplemented Opcode (FF) 64 88 07 BD A4 15 57 65 HOT 1
- Termux Proot Steam
- Cannot load MVCI32.dll Segmentation Fault HOT 2
- Steam in Termux issue HOT 1
- Command line return value handling HOT 8
- Info HOT 4
- Steam won't start on raspberry pi HOT 8
- steam cant seem to run HOT 5
- Issue with Counter Strike Source Dedicated Server
- PltResolver Error: sighold@GLIBC_2.1 HOT 2
- Games menu wrong HOT 15
- Wrap OpenSLES functions HOT 2
- libncurses5:armhf doesnt work on my side.. HOT 5
- Not reading ELF header correctly on PowerPC? HOT 4
- Which cmake option to use with Raspberry pi 5? HOT 13
- Some Windows EXE files are failing to load on newer box86 builds HOT 2
- Am I supposed to compile, HOT 9
- Error when launching native "Penumbra" games
- Call of Juarez - crash on new game
- Wrapping an x86 shared library
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from box86.