Browse Source

Merge pull request #3859 from laytan/wasm-stbtt-object-linking-preopens

wasm: support `vendor:stb/truetype` and `vendor:fontstash`
gingerBill 1 year ago
parent
commit
8b49549fd3

+ 115 - 2
core/os/os_wasi.odin

@@ -26,7 +26,6 @@ O_CLOEXEC  :: 0x80000
 stdin:  Handle = 0
 stdin:  Handle = 0
 stdout: Handle = 1
 stdout: Handle = 1
 stderr: Handle = 2
 stderr: Handle = 2
-current_dir: Handle = 3
 
 
 args := _alloc_command_line_arguments()
 args := _alloc_command_line_arguments()
 
 
@@ -38,6 +37,114 @@ _alloc_command_line_arguments :: proc() -> (args: []string) {
 	return
 	return
 }
 }
 
 
+// WASI works with "preopened" directories, the environment retrieves directories
+// (for example with `wasmtime --dir=. module.wasm`) and those given directories
+// are the only ones accessible by the application.
+//
+// So in order to facilitate the `os` API (absolute paths etc.) we keep a list
+// of the given directories and match them when needed (notably `os.open`).
+
+@(private)
+Preopen :: struct {
+	fd:     wasi.fd_t,
+	prefix: string,
+}
+@(private)
+preopens: []Preopen
+
+@(init, private)
+init_preopens :: proc() {
+
+	strip_prefixes :: proc(path: string) -> string {
+		path := path
+		loop: for len(path) > 0 {
+			switch {
+			case path[0] == '/':
+				path = path[1:]
+			case len(path) > 2  && path[0] == '.' && path[1] == '/':
+				path = path[2:]
+			case len(path) == 1 && path[0] == '.':
+				path = path[1:]
+			case:
+				break loop
+			}
+		}
+		return path
+	}
+
+	dyn_preopens: [dynamic]Preopen
+	loop: for fd := wasi.fd_t(3); ; fd += 1 {
+		desc, err := wasi.fd_prestat_get(fd)
+		#partial switch err {
+		case .BADF: break loop
+		case:       panic("fd_prestat_get returned an unexpected error")
+		case .SUCCESS:
+		}
+
+		switch desc.tag {
+		case .DIR:
+			buf := make([]byte, desc.dir.pr_name_len) or_else panic("could not allocate memory for filesystem preopens")
+			if err = wasi.fd_prestat_dir_name(fd, buf); err != .SUCCESS {
+				panic("could not get filesystem preopen dir name")
+			}
+			append(&dyn_preopens, Preopen{fd, strip_prefixes(string(buf))})
+		}
+	}
+	preopens = dyn_preopens[:]
+}
+
+wasi_match_preopen :: proc(path: string) -> (wasi.fd_t, string, bool) {
+
+	prefix_matches :: proc(prefix, path: string) -> bool {
+		// Empty is valid for any relative path.
+		if len(prefix) == 0 && len(path) > 0 && path[0] != '/' {
+			return true
+		}
+
+		if len(path) < len(prefix) {
+			return false
+		}
+
+		if path[:len(prefix)] != prefix {
+			return false
+		}
+
+		// Only match on full components.
+		i := len(prefix)
+		for i > 0 && prefix[i-1] == '/' {
+			i -= 1
+		}
+		return path[i] == '/'
+	}
+
+	path := path
+	for len(path) > 0 && path[0] == '/' {
+		path = path[1:]
+	}
+
+	match: Preopen
+	#reverse for preopen in preopens {
+		if (match.fd == 0 || len(preopen.prefix) > len(match.prefix)) && prefix_matches(preopen.prefix, path) {
+			match = preopen
+		}
+	}
+
+	if match.fd == 0 {
+		return 0, "", false
+	}
+
+	relative := path[len(match.prefix):]
+	for len(relative) > 0 && relative[0] == '/' {
+		relative = relative[1:]
+	}
+
+	if len(relative) == 0 {
+		relative = "."
+	}
+
+	return match.fd, relative, true
+}
+
 write :: proc(fd: Handle, data: []byte) -> (int, Errno) {
 write :: proc(fd: Handle, data: []byte) -> (int, Errno) {
 	iovs := wasi.ciovec_t(data)
 	iovs := wasi.ciovec_t(data)
 	n, err := wasi.fd_write(wasi.fd_t(fd), {iovs})
 	n, err := wasi.fd_write(wasi.fd_t(fd), {iovs})
@@ -87,7 +194,13 @@ open :: proc(path: string, mode: int = O_RDONLY, perm: int = 0) -> (Handle, Errn
 	if mode & O_SYNC == O_SYNC {
 	if mode & O_SYNC == O_SYNC {
 		fdflags += {.SYNC}
 		fdflags += {.SYNC}
 	}
 	}
-	fd, err := wasi.path_open(wasi.fd_t(current_dir),{.SYMLINK_FOLLOW},path,oflags,rights,{},fdflags)
+
+	dir_fd, relative, ok := wasi_match_preopen(path)
+	if !ok {
+		return INVALID_HANDLE, Errno(wasi.errno_t.BADF)
+	}
+
+	fd, err := wasi.path_open(dir_fd, {.SYMLINK_FOLLOW}, relative, oflags, rights, {}, fdflags)
 	return Handle(fd), Errno(err)
 	return Handle(fd), Errno(err)
 }
 }
 close :: proc(fd: Handle) -> Errno {
 close :: proc(fd: Handle) -> Errno {

+ 2 - 2
core/sys/wasm/wasi/wasi_api.odin

@@ -962,7 +962,7 @@ prestat_dir_t :: struct {
 }
 }
 
 
 prestat_t :: struct {
 prestat_t :: struct {
-	tag: u8,
+	tag: preopentype_t,
 	using u: struct {
 	using u: struct {
 		dir: prestat_dir_t,
 		dir: prestat_dir_t,
 	},
 	},
@@ -1158,7 +1158,7 @@ foreign wasi {
 		/**
 		/**
 		 * A buffer into which to write the preopened directory name.
 		 * A buffer into which to write the preopened directory name.
 		 */
 		 */
-		path: string,
+		path: []byte,
 	) -> errno_t ---
 	) -> errno_t ---
 	/**
 	/**
 	 * Create a directory.
 	 * Create a directory.

+ 0 - 9
src/build_settings.cpp

@@ -860,15 +860,6 @@ gb_internal bool is_arch_x86(void) {
 	return false;
 	return false;
 }
 }
 
 
-gb_internal bool allow_check_foreign_filepath(void) {
-	switch (build_context.metrics.arch) {
-	case TargetArch_wasm32:
-	case TargetArch_wasm64p32:
-		return false;
-	}
-	return true;
-}
-
 // TODO(bill): OS dependent versions for the BuildContext
 // TODO(bill): OS dependent versions for the BuildContext
 // join_path
 // join_path
 // is_dir
 // is_dir

+ 5 - 2
src/check_decl.cpp

@@ -1178,9 +1178,12 @@ gb_internal void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d) {
 			if (foreign_library->LibraryName.paths.count >= 1) {
 			if (foreign_library->LibraryName.paths.count >= 1) {
 				module_name = foreign_library->LibraryName.paths[0];
 				module_name = foreign_library->LibraryName.paths[0];
 			}
 			}
-			name = concatenate3_strings(permanent_allocator(), module_name, WASM_MODULE_NAME_SEPARATOR, name);
+
+			if (!string_ends_with(module_name, str_lit(".o"))) {
+				name = concatenate3_strings(permanent_allocator(), module_name, WASM_MODULE_NAME_SEPARATOR, name);
+			}
 		}
 		}
-		
+
 		e->Procedure.is_foreign = true;
 		e->Procedure.is_foreign = true;
 		e->Procedure.link_name = name;
 		e->Procedure.link_name = name;
 
 

+ 1 - 2
src/checker.cpp

@@ -5000,9 +5000,8 @@ gb_internal void check_foreign_import_fullpaths(Checker *c) {
 
 
 				String file_str = op.value.value_string;
 				String file_str = op.value.value_string;
 				file_str = string_trim_whitespace(file_str);
 				file_str = string_trim_whitespace(file_str);
-
 				String fullpath = file_str;
 				String fullpath = file_str;
-				if (allow_check_foreign_filepath()) {
+				if (!is_arch_wasm() || string_ends_with(file_str, str_lit(".o"))) {
 					String foreign_path = {};
 					String foreign_path = {};
 					bool ok = determine_path_from_string(nullptr, decl, base_dir, file_str, &foreign_path, /*use error not syntax_error*/true);
 					bool ok = determine_path_from_string(nullptr, decl, base_dir, file_str, &foreign_path, /*use error not syntax_error*/true);
 					if (ok) {
 					if (ok) {

+ 14 - 0
src/linker.cpp

@@ -85,6 +85,20 @@ gb_internal i32 linker_stage(LinkerData *gen) {
 			if (extra_linker_flags.len != 0) {
 			if (extra_linker_flags.len != 0) {
 				lib_str = gb_string_append_fmt(lib_str, " %.*s", LIT(extra_linker_flags));
 				lib_str = gb_string_append_fmt(lib_str, " %.*s", LIT(extra_linker_flags));
 			}
 			}
+
+			for_array(i, e->LibraryName.paths) {
+				String lib = e->LibraryName.paths[i];
+
+				if (lib.len == 0) {
+					continue;
+				}
+
+				if (!string_ends_with(lib, str_lit(".o"))) {
+					continue;
+				}
+
+				inputs = gb_string_append_fmt(inputs, " \"%.*s\"", LIT(lib));
+			}
 		}
 		}
 
 
 		if (build_context.metrics.os == TargetOs_orca) {
 		if (build_context.metrics.os == TargetOs_orca) {

+ 5 - 1
src/llvm_backend_utility.cpp

@@ -2029,7 +2029,11 @@ gb_internal void lb_set_wasm_procedure_import_attributes(LLVMValueRef value, Ent
 		GB_ASSERT(foreign_library->LibraryName.paths.count == 1);
 		GB_ASSERT(foreign_library->LibraryName.paths.count == 1);
 		
 		
 		module_name = foreign_library->LibraryName.paths[0];
 		module_name = foreign_library->LibraryName.paths[0];
-		
+
+		if (string_ends_with(module_name, str_lit(".o"))) {
+			return;
+		}
+
 		if (string_starts_with(import_name, module_name)) {
 		if (string_starts_with(import_name, module_name)) {
 			import_name = substring(import_name, module_name.len+WASM_MODULE_NAME_SEPARATOR.len, import_name.len);
 			import_name = substring(import_name, module_name.len+WASM_MODULE_NAME_SEPARATOR.len, import_name.len);
 		}
 		}

+ 1 - 2
src/parser.cpp

@@ -5824,7 +5824,6 @@ gb_internal bool determine_path_from_string(BlockingMutex *file_mutex, Ast *node
 		return false;
 		return false;
 	}
 	}
 
 
-
 	if (collection_name.len > 0) {
 	if (collection_name.len > 0) {
 		// NOTE(bill): `base:runtime` == `core:runtime`
 		// NOTE(bill): `base:runtime` == `core:runtime`
 		if (collection_name == "core") {
 		if (collection_name == "core") {
@@ -5979,7 +5978,7 @@ gb_internal void parse_setup_file_decls(Parser *p, AstFile *f, String const &bas
 				Token fp_token = fp->BasicLit.token;
 				Token fp_token = fp->BasicLit.token;
 				String file_str = string_trim_whitespace(string_value_from_token(f, fp_token));
 				String file_str = string_trim_whitespace(string_value_from_token(f, fp_token));
 				String fullpath = file_str;
 				String fullpath = file_str;
-				if (allow_check_foreign_filepath()) {
+				if (!is_arch_wasm() || string_ends_with(fullpath, str_lit(".o"))) {
 					String foreign_path = {};
 					String foreign_path = {};
 					bool ok = determine_path_from_string(&p->file_decl_mutex, node, base_dir, file_str, &foreign_path);
 					bool ok = determine_path_from_string(&p->file_decl_mutex, node, base_dir, file_str, &foreign_path);
 					if (!ok) {
 					if (!ok) {

+ 3 - 17
vendor/fontstash/fontstash.odin

@@ -1,14 +1,14 @@
-//+build windows, linux, darwin
 //+vet !using-param
 //+vet !using-param
 package fontstash
 package fontstash
 
 
 import "base:runtime"
 import "base:runtime"
+
 import "core:log"
 import "core:log"
-import "core:os"
 import "core:mem"
 import "core:mem"
 import "core:math"
 import "core:math"
 import "core:strings"
 import "core:strings"
 import "core:slice"
 import "core:slice"
+
 import stbtt "vendor:stb/truetype"
 import stbtt "vendor:stb/truetype"
 
 
 // This is a port from Fontstash into odin - specialized for nanovg
 // This is a port from Fontstash into odin - specialized for nanovg
@@ -321,20 +321,6 @@ __AtlasAddWhiteRect :: proc(ctx: ^FontContext, w, h: int) -> bool {
 	return true
 	return true
 }
 }
 
 
-AddFontPath :: proc(
-	ctx: ^FontContext,
-	name: string,
-	path: string,
-) -> int {
-	data, ok := os.read_entire_file(path)
-
-	if !ok {
-		log.panicf("FONT: failed to read font at %s", path)
-	}
-
-	return AddFontMem(ctx, name, data, true)
-}
-
 // push a font to the font stack
 // push a font to the font stack
 // optionally init with ascii characters at a wanted size
 // optionally init with ascii characters at a wanted size
 AddFontMem :: proc(
 AddFontMem :: proc(
@@ -1192,4 +1178,4 @@ EndState :: proc(using ctx: ^FontContext) {
 		}
 		}
 		__dirtyRectReset(ctx)
 		__dirtyRectReset(ctx)
 	}
 	}
-}
+}

+ 20 - 0
vendor/fontstash/fontstash_os.odin

@@ -0,0 +1,20 @@
+//+build !js
+package fontstash
+
+import "core:log"
+import "core:os"
+
+AddFontPath :: proc(
+	ctx: ^FontContext,
+	name: string,
+	path: string,
+) -> int {
+	data, ok := os.read_entire_file(path)
+
+	if !ok {
+		log.panicf("FONT: failed to read font at %s", path)
+	}
+
+	return AddFontMem(ctx, name, data, true)
+}
+

BIN
vendor/stb/lib/stb_truetype_wasm.o


+ 4 - 0
vendor/stb/src/Makefile

@@ -6,6 +6,10 @@ else
 all: unix
 all: unix
 endif
 endif
 
 
+wasm:
+	mkdir -p ../lib
+	clang -c -Os --target=wasm32 -nostdlib stb_truetype_wasm.c -o ../lib/stb_truetype_wasm.o
+
 unix:
 unix:
 	mkdir -p ../lib
 	mkdir -p ../lib
 	$(CC) -c -O2 -Os -fPIC stb_image.c stb_image_write.c stb_image_resize.c stb_truetype.c stb_rect_pack.c stb_vorbis.c
 	$(CC) -c -O2 -Os -fPIC stb_image.c stb_image_write.c stb_image_resize.c stb_truetype.c stb_rect_pack.c stb_vorbis.c

+ 46 - 0
vendor/stb/src/stb_truetype_wasm.c

@@ -0,0 +1,46 @@
+#include <stddef.h>
+
+void *stbtt_malloc(size_t size);
+void stbtt_free(void *ptr);
+
+void stbtt_qsort(void* base, size_t num, size_t size, int (*compare)(const void*, const void*));
+
+double stbtt_floor(double x);
+double stbtt_ceil(double x);
+double stbtt_sqrt(double x);
+double stbtt_pow(double x, double y);
+double stbtt_fmod(double x, double y);
+double stbtt_cos(double x);
+double stbtt_acos(double x);
+double stbtt_fabs(double x);
+
+unsigned long stbtt_strlen(const char *str);
+
+void *memcpy(void *dst, const void *src, size_t count);
+void *memset(void *dst, int x, size_t count);
+
+#define STBRP_SORT stbtt_qsort
+#define STBRP_ASSERT(condition) ((void)0)
+
+#define STBTT_malloc(x,u)  ((void)(u),stbtt_malloc(x))
+#define STBTT_free(x,u)    ((void)(u),stbtt_free(x))
+
+#define STBTT_assert(condition) ((void)0)
+
+#define STBTT_ifloor(x)   ((int) stbtt_floor(x))
+#define STBTT_iceil(x)    ((int) stbtt_ceil(x))
+#define STBTT_sqrt(x)      stbtt_sqrt(x)
+#define STBTT_pow(x,y)     stbtt_pow(x,y)
+#define STBTT_fmod(x,y)    stbtt_fmod(x,y)
+#define STBTT_cos(x)       stbtt_cos(x)
+#define STBTT_acos(x)      stbtt_acos(x)
+#define STBTT_fabs(x)      stbtt_fabs(x)
+#define STBTT_strlen(x)    stbtt_strlen(x)
+#define STBTT_memcpy       memcpy
+#define STBTT_memset       memset
+
+#define STB_RECT_PACK_IMPLEMENTATION
+#include "stb_rect_pack.h"
+
+#define STB_TRUETYPE_IMPLEMENTATION
+#include "stb_truetype.h"

+ 2 - 0
vendor/stb/truetype/stb_truetype.odin

@@ -17,6 +17,8 @@ when LIB != "" {
 	}
 	}
 
 
 	foreign import stbtt { LIB }
 	foreign import stbtt { LIB }
+} else when ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32 {
+	foreign import stbtt "../lib/stb_truetype_wasm.o"
 } else {
 } else {
 	foreign import stbtt "system:stb_truetype"
 	foreign import stbtt "system:stb_truetype"
 }
 }

+ 89 - 0
vendor/stb/truetype/stb_truetype_wasm.odin

@@ -0,0 +1,89 @@
+//+build wasm32, wasm64p32
+package stb_truetype
+
+import "base:builtin"
+import "base:intrinsics"
+import "base:runtime"
+
+import "core:c"
+import "core:math"
+import "core:mem"
+import "core:slice"
+import "core:sort"
+
+@(require, linkage="strong", link_name="stbtt_malloc")
+malloc :: proc "c" (size: uint) -> rawptr {
+	context = runtime.default_context()
+	ptr, _ := runtime.mem_alloc_non_zeroed(int(size))
+	return raw_data(ptr)
+}
+
+@(require, linkage="strong", link_name="stbtt_free")
+free :: proc "c" (ptr: rawptr) {
+	context = runtime.default_context()
+	builtin.free(ptr)
+}
+
+@(require, linkage="strong", link_name="stbtt_qsort")
+qsort :: proc "c" (base: rawptr, num: uint, size: uint, cmp: proc "c" (a, b: rawptr) -> i32) {
+	context = runtime.default_context()
+
+	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="stbtt_floor")
+floor :: proc "c" (x: f64) -> f64 { return math.floor(x) }
+@(require, linkage="strong", link_name="stbtt_ceil")
+ceil :: proc "c" (x: f64) -> f64 { return math.ceil(x) }
+@(require, linkage="strong", link_name="stbtt_sqrt")
+sqrt :: proc "c" (x: f64) -> f64 { return math.sqrt(x) }
+@(require, linkage="strong", link_name="stbtt_pow")
+pow :: proc "c" (x, y: f64) -> f64 { return math.pow(x, y) }
+
+@(require, linkage="strong", link_name="stbtt_fmod")
+fmod :: proc "c" (x, y: f64) -> f64 {
+	context = runtime.default_context();
+	// NOTE: only called in the `stbtt_GetGlyphSDF` code path.
+	panic("`math.round` is broken on 32 bit targets, see #3856")
+}
+
+@(require, linkage="strong", link_name="stbtt_cos")
+cos :: proc "c" (x: f64) -> f64 { return math.cos(x) }
+@(require, linkage="strong", link_name="stbtt_acos")
+acos :: proc "c" (x: f64) -> f64 { return math.acos(x) }
+@(require, linkage="strong", link_name="stbtt_fabs")
+fabs :: proc "c" (x: f64) -> f64 { return math.abs(x) }
+
+@(require, linkage="strong", link_name="stbtt_strlen")
+strlen :: proc "c" (str: cstring) -> c.ulong { return c.ulong(len(str)) }
+
+// NOTE: defined in runtime.
+// void *memcpy(void *dst, const void *src, size_t count);
+// void *memset(void *dst, int x, size_t count);