wasm: support more vendor libraries

Adds support for:
- box2d
- cgltf
- stb image
- stb rect pack
This commit is contained in:
Laytan Laats
2024-09-09 18:49:13 +02:00
parent d783bca297
commit 5ae27c6ebc
45 changed files with 2828 additions and 231 deletions
+12
View File
@@ -0,0 +1,12 @@
# vendor:libc
A (very small) subset of a libc implementation over Odin libraries.
This is mainly intended for use in Odin WASM builds to allow using libraries like box2d, cgltf etc. without emscripten hacks.
You can use this with clang by doing `clang -c --target=wasm32 --sysroot=$(odin root)/vendor/libc` (+ all other flags and inputs).
This will (if all the libc usage of the library is implemented) spit out a `.o` file you can use with the foreign import system.
If you then also make sure this package is included in the Odin side of the project (`@(require) import "vendor:libc"`) you will be able
compile to WASM like Odin expects.
This is currently used by `vendor:box2d`, `vendor:stb/image`, `vendor:stb/truetype`, `vendor:stb/rect_pack`, and `vendor:cgltf`.
You can see how building works by looking at those.
+15
View File
@@ -0,0 +1,15 @@
package odin_libc
import "base:runtime"
@(require, linkage="strong", link_name="__odin_libc_assert_fail")
__odin_libc_assert_fail :: proc "c" (func: cstring, file: cstring, line: i32, expr: cstring) -> ! {
context = g_ctx
loc := runtime.Source_Code_Location{
file_path = string(file),
line = line,
column = 0,
procedure = string(func),
}
context.assertion_failure_proc("runtime assertion", string(expr), loc)
}
+16
View File
@@ -0,0 +1,16 @@
#ifdef NDEBUG
#define assert(e) ((void)0)
#else
#ifdef __FILE_NAME__
#define __ASSERT_FILE_NAME __FILE_NAME__
#else /* __FILE_NAME__ */
#define __ASSERT_FILE_NAME __FILE__
#endif /* __FILE_NAME__ */
void __odin_libc_assert_fail(const char *, const char *, int, const char *);
#define assert(e) \
(__builtin_expect(!(e), 0) ? __odin_libc_assert_fail(__func__, __ASSERT_FILE_NAME, __LINE__, #e) : (void)0)
#endif /* NDEBUG */
+21
View File
@@ -0,0 +1,21 @@
#include <stdbool.h>
float sqrtf(float);
float cosf(float);
float sinf(float);
float atan2f(float, float);
bool isnan(float);
bool isinf(float);
double floor(double x);
double ceil(double x);
double sqrt(double x);
double pow(double x, double y);
double fmod(double x, double y);
double cos(double x);
double acos(double x);
double fabs(double x);
int abs(int);
double ldexp(double, int);
double exp(double);
float log(float);
float sin(float);
+47
View File
@@ -0,0 +1,47 @@
#include <stddef.h>
#include <stdarg.h>
#pragma once
typedef struct {} FILE;
#define SEEK_SET 0
#define SEEK_CUR 1
#define SEEK_END 2
#define stdout ((FILE *)2)
#define stderr ((FILE *)3)
FILE *fopen(const char *, char *);
int fclose(FILE *);
int fseek(FILE *, long, int);
long ftell(FILE *);
size_t fread(void *, size_t, size_t, FILE *);
size_t fwrite(const void *, size_t, size_t, FILE *);
int vfprintf(FILE *, const char *, va_list);
int vsnprintf(char *, size_t, const char *, va_list);
static inline int snprintf(char *buf, size_t size, const char *fmt, ...) {
va_list args;
va_start(args, fmt);
int result = vsnprintf(buf, size, fmt, args);
va_end(args);
return result;
}
static inline int fprintf(FILE *f, const char *fmt, ...) {
va_list args;
va_start(args, fmt);
int result = vfprintf(f, fmt, args);
va_end(args);
return result;
}
static inline int printf(const char *fmt, ...) {
va_list args;
va_start(args, fmt);
int result = vfprintf(stdout, fmt, args);
va_end(args);
return result;
}
+19
View File
@@ -0,0 +1,19 @@
#include <stddef.h>
void *malloc(size_t size);
void *aligned_alloc(size_t alignment, size_t size);
void free(void *);
void *realloc(void *, size_t);
void qsort(void* base, size_t num, size_t size, int (*compare)(const void*, const void*));
int atoi(const char *);
long atol(const char *);
long long atoll(const char *);
double atof(const char *);
long strtol(const char *, char **, int);
+21
View File
@@ -0,0 +1,21 @@
#include <stddef.h>
void *memcpy(void *, const void *, size_t);
void *memset(void *, int, size_t);
void *memmove(void *, void *, size_t);
int memcmp(const void *, const void *, size_t);
unsigned long strlen(const char *str);
char *strchr(const char *, int);
char *strrchr(const char *, int);
char *strncpy(char *, const char *, size_t);
char *strcpy(char *, const char *);
size_t strcspn(const char *, const char *);
int strcmp(const char *, const char *);
int strncmp(const char *, const char *, size_t);
char *strstr(const char *, const char *);
+25
View File
@@ -0,0 +1,25 @@
package odin_libc
import "base:runtime"
import "core:mem"
@(private)
g_ctx: runtime.Context
@(private)
g_allocator: mem.Compat_Allocator
@(init)
init_context :: proc() {
g_ctx = context
// Wrapping the allocator with the mem.Compat_Allocator so we can
// mimic the realloc semantics.
mem.compat_allocator_init(&g_allocator, g_ctx.allocator)
g_ctx.allocator = mem.compat_allocator(&g_allocator)
}
// NOTE: the allocator must respect an `old_size` of `-1` on resizes!
set_context :: proc(ctx := context) {
g_ctx = ctx
}
+100
View File
@@ -0,0 +1,100 @@
package odin_libc
import "base:builtin"
import "core:math"
@(require, linkage="strong", link_name="sqrtf")
sqrtf :: proc "c" (v: f32) -> f32 {
return math.sqrt(v)
}
@(require, linkage="strong", link_name="cosf")
cosf :: proc "c" (v: f32) -> f32 {
return math.cos(v)
}
@(require, linkage="strong", link_name="sinf")
sinf :: proc "c" (v: f32) -> f32 {
return math.sin(v)
}
@(require, linkage="strong", link_name="atan2f")
atan2f :: proc "c" (v: f32, v2: f32) -> f32 {
return math.atan2(v, v2)
}
@(require, linkage="strong", link_name="isnan")
isnan :: proc "c" (v: f32) -> bool {
return math.is_nan(v)
}
@(require, linkage="strong", link_name="isinf")
isinf :: proc "c" (v: f32) -> bool {
return math.is_inf(v)
}
@(require, linkage="strong", link_name="sqrt")
sqrt :: proc "c" (x: f64) -> f64 {
return math.sqrt(x)
}
@(require, linkage="strong", link_name="floor")
floor :: proc "c" (x: f64) -> f64 {
return math.floor(x)
}
@(require, linkage="strong", link_name="ceil")
ceil :: proc "c" (x: f64) -> f64 {
return math.ceil(x)
}
@(require, linkage="strong", link_name="pow")
pow :: proc "c" (x, y: f64) -> f64 {
return math.pow(x, y)
}
@(require, linkage="strong", link_name="fmod")
fmod :: proc "c" (x, y: f64) -> f64 {
return math.mod(x, y)
}
@(require, linkage="strong", link_name="cos")
cos :: proc "c" (x: f64) -> f64 {
return math.cos(x)
}
@(require, linkage="strong", link_name="acos")
acos :: proc "c" (x: f64) -> f64 {
return math.acos(x)
}
@(require, linkage="strong", link_name="fabs")
fabs :: proc "c" (x: f64) -> f64 {
return math.abs(x)
}
@(require, linkage="strong", link_name="abs")
abs :: proc "c" (x: i32) -> i32 {
return builtin.abs(x)
}
@(require, linkage="strong", link_name="ldexp")
ldexp :: proc "c" (x: f64, y: i32) -> f64{
return math.ldexp(x, int(y))
}
@(require, linkage="strong", link_name="exp")
exp :: proc "c" (x: f64) -> f64 {
return math.exp(x)
}
@(require, linkage="strong", link_name="log")
log :: proc "c" (x: f32) -> f32 {
return math.ln(x)
}
@(require, linkage="strong", link_name="sin")
sin :: proc "c" (x: f32) -> f32 {
return math.sin(x)
}
+106
View File
@@ -0,0 +1,106 @@
package odin_libc
import "core:c"
import "core:io"
import "core:os"
import stb "vendor:stb/sprintf"
FILE :: uintptr
@(require, linkage="strong", link_name="fopen")
fopen :: proc "c" (path: cstring, mode: cstring) -> FILE {
context = g_ctx
unimplemented("odin_libc.fopen")
}
@(require, linkage="strong", link_name="fseek")
fseek :: proc "c" (file: FILE, offset: c.long, whence: i32) -> i32 {
context = g_ctx
handle := os.Handle(file-1)
_, err := os.seek(handle, i64(offset), int(whence))
if err != nil {
return -1
}
return 0
}
@(require, linkage="strong", link_name="ftell")
ftell :: proc "c" (file: FILE) -> c.long {
context = g_ctx
handle := os.Handle(file-1)
off, err := os.seek(handle, 0, os.SEEK_CUR)
if err != nil {
return -1
}
return c.long(off)
}
@(require, linkage="strong", link_name="fclose")
fclose :: proc "c" (file: FILE) -> i32 {
context = g_ctx
handle := os.Handle(file-1)
if os.close(handle) != nil {
return -1
}
return 0
}
@(require, linkage="strong", link_name="fread")
fread :: proc "c" (buffer: [^]byte, size: uint, count: uint, file: FILE) -> uint {
context = g_ctx
handle := os.Handle(file-1)
n, _ := os.read(handle, buffer[:min(size, count)])
return uint(max(0, n))
}
@(require, linkage="strong", link_name="fwrite")
fwrite :: proc "c" (buffer: [^]byte, size: uint, count: uint, file: FILE) -> uint {
context = g_ctx
handle := os.Handle(file-1)
n, _ := os.write(handle, buffer[:min(size, count)])
return uint(max(0, n))
}
@(require, linkage="strong", link_name="vsnprintf")
vsnprintf :: proc "c" (buf: [^]byte, count: uint, fmt: cstring, args: ^c.va_list) -> i32 {
i32_count := i32(count)
assert_contextless(i32_count >= 0)
return stb.vsnprintf(buf, i32_count, fmt, args)
}
@(require, linkage="strong", link_name="vfprintf")
vfprintf :: proc "c" (file: FILE, fmt: cstring, args: ^c.va_list) -> i32 {
context = g_ctx
handle := os.Handle(file-1)
MAX_STACK :: 4096
buf: []byte
stack_buf: [MAX_STACK]byte = ---
{
n := stb.vsnprintf(&stack_buf[0], MAX_STACK, fmt, args)
if n <= 0 {
return n
}
if n >= MAX_STACK {
buf = make([]byte, n)
n2 := stb.vsnprintf(raw_data(buf), i32(len(buf)), fmt, args)
assert(n == n2)
} else {
buf = stack_buf[:n]
}
}
defer if len(buf) > MAX_STACK {
delete(buf)
}
_, err := io.write_full(os.stream_from_handle(handle), buf)
if err != nil {
return -1
}
return i32(len(buf))
}
+119
View File
@@ -0,0 +1,119 @@
package odin_libc
import "base:runtime"
import "core:c"
import "core:slice"
import "core:sort"
import "core:strconv"
import "core:strings"
@(require, linkage="strong", link_name="malloc")
malloc :: proc "c" (size: uint) -> rawptr {
context = g_ctx
ptr, err := runtime.mem_alloc_non_zeroed(int(size))
assert(err == nil, "allocation failure")
return raw_data(ptr)
}
@(require, linkage="strong", link_name="aligned_alloc")
aligned_alloc :: proc "c" (alignment: uint, size: uint) -> rawptr {
context = g_ctx
ptr, err := runtime.mem_alloc_non_zeroed(int(size), int(alignment))
assert(err == nil, "allocation failure")
return raw_data(ptr)
}
@(require, linkage="strong", link_name="free")
free :: proc "c" (ptr: rawptr) {
context = g_ctx
runtime.mem_free(ptr)
}
@(require, linkage="strong", link_name="realloc")
realloc :: proc "c" (ptr: rawptr, new_size: uint) -> rawptr {
context = g_ctx
// -1 for the old_size, assumed to be wrapped with the mem.Compat_Allocator to get the right size.
// Note that realloc does not actually care about alignment and is allowed to just align it to something
// else than the original allocation.
ptr, err := runtime.non_zero_mem_resize(ptr, -1, int(new_size))
assert(err != nil, "realloc failure")
return raw_data(ptr)
}
@(require, linkage="strong", link_name="qsort")
qsort :: proc "c" (base: rawptr, num: uint, size: uint, cmp: proc "c" (a, b: rawptr) -> i32) {
context = g_ctx
Inputs :: struct {
base: rawptr,
num: uint,
size: uint,
cmp: proc "c" (a, b: rawptr) -> i32,
}
sort.sort({
collection = &Inputs{base, num, size, cmp},
len = proc(it: sort.Interface) -> int {
inputs := (^Inputs)(it.collection)
return int(inputs.num)
},
less = proc(it: sort.Interface, i, j: int) -> bool {
inputs := (^Inputs)(it.collection)
a := rawptr(uintptr(inputs.base) + (uintptr(i) * uintptr(inputs.size)))
b := rawptr(uintptr(inputs.base) + (uintptr(j) * uintptr(inputs.size)))
return inputs.cmp(a, b) < 0
},
swap = proc(it: sort.Interface, i, j: int) {
inputs := (^Inputs)(it.collection)
a := rawptr(uintptr(inputs.base) + (uintptr(i) * uintptr(inputs.size)))
b := rawptr(uintptr(inputs.base) + (uintptr(j) * uintptr(inputs.size)))
slice.ptr_swap_non_overlapping(a, b, int(inputs.size))
},
})
}
@(require, linkage="strong", link_name="atoi")
atoi :: proc "c" (str: cstring) -> i32 {
return i32(atoll(str))
}
@(require, linkage="strong", link_name="atol")
atol :: proc "c" (str: cstring) -> c.long {
return c.long(atoll(str))
}
@(require, linkage="strong", link_name="atoll")
atoll :: proc "c" (str: cstring) -> c.longlong {
context = g_ctx
sstr := string(str)
sstr = strings.trim_left_space(sstr)
i, _ := strconv.parse_i64_of_base(sstr, 10)
return c.longlong(i)
}
@(require, linkage="strong", link_name="atof")
atof :: proc "c" (str: cstring) -> f64 {
context = g_ctx
sstr := string(str)
sstr = strings.trim_left_space(sstr)
f, _ := strconv.parse_f64(sstr)
return f
}
@(require, linkage="strong", link_name="strtol")
strtol :: proc "c" (str: cstring, str_end: ^cstring, base: i32) -> c.long {
context = g_ctx
sstr := string(str)
sstr = strings.trim_left_space(sstr)
n: int
i, _ := strconv.parse_i64_of_base(sstr, int(base), &n)
str_end ^= cstring(raw_data(sstr)[n:])
return c.long(clamp(i, i64(min(c.long)), i64(max(c.long))))
}
+111
View File
@@ -0,0 +1,111 @@
package odin_libc
import "base:intrinsics"
import "core:c"
import "core:strings"
import "core:mem"
// NOTE: already defined by Odin.
// void *memcpy(void *, const void *, size_t);
// void *memset(void *, int, size_t);
@(require, linkage="strong", link_name="memcmp")
memcmp :: proc "c" (lhs: [^]byte, rhs: [^]byte, count: uint) -> i32 {
icount := int(count)
assert_contextless(icount >= 0)
return i32(mem.compare(lhs[:icount], rhs[:icount]))
}
@(require, linkage="strong", link_name="strlen")
strlen :: proc "c" (str: cstring) -> c.ulong {
return c.ulong(len(str))
}
@(require, linkage="strong", link_name="strchr")
strchr :: proc "c" (str: cstring, ch: i32) -> cstring {
bch := u8(ch)
sstr := string(str)
if bch == 0 {
return cstring(raw_data(sstr)[len(sstr):])
}
idx := strings.index_byte(sstr, bch)
if idx < 0 {
return nil
}
return cstring(raw_data(sstr)[idx:])
}
@(require, linkage="strong", link_name="strrchr")
strrchr :: proc "c" (str: cstring, ch: i32) -> cstring {
bch := u8(ch)
sstr := string(str)
if bch == 0 {
return cstring(raw_data(sstr)[len(sstr):])
}
idx := strings.last_index_byte(sstr, bch)
if idx < 0 {
return nil
}
return cstring(raw_data(sstr)[idx:])
}
@(require, linkage="strong", link_name="strncpy")
strncpy :: proc "c" (dst: [^]byte, src: cstring, count: uint) -> cstring {
icount := int(count)
assert_contextless(icount >= 0)
cnt := min(len(src), icount)
intrinsics.mem_copy_non_overlapping(dst, rawptr(src), cnt)
intrinsics.mem_zero(dst, icount-cnt)
return cstring(dst)
}
@(require, linkage="strong", link_name="strcpy")
strcpy :: proc "c" (dst: [^]byte, src: cstring) -> cstring {
intrinsics.mem_copy_non_overlapping(dst, rawptr(src), len(src)+1)
return cstring(dst)
}
@(require, linkage="strong", link_name="strcspn")
strcspn :: proc "c" (dst: cstring, src: cstring) -> uint {
context = g_ctx
sdst := string(dst)
idx := strings.index_any(sdst, string(src))
if idx == -1 {
return len(sdst)
}
return uint(idx)
}
@(require, linkage="strong", link_name="strncmp")
strncmp :: proc "c" (lhs: cstring, rhs: cstring, count: uint) -> i32 {
icount := int(count)
assert_contextless(icount >= 0)
lhss := strings.string_from_null_terminated_ptr(([^]byte)(lhs), icount)
rhss := strings.string_from_null_terminated_ptr(([^]byte)(rhs), icount)
return i32(strings.compare(lhss, rhss))
}
@(require, linkage="strong", link_name="strcmp")
strcmp :: proc "c" (lhs: cstring, rhs: cstring) -> i32 {
return i32(strings.compare(string(lhs), string(rhs)))
}
@(require, linkage="strong", link_name="strstr")
strstr :: proc "c" (str: cstring, substr: cstring) -> cstring {
if substr == "" {
return str
}
idx := strings.index(string(str), string(substr))
if idx < 0 {
return nil
}
return cstring(([^]byte)(str)[idx:])
}