From 6f1cc8071c3ff49c5431cc8ad078d12883f91545 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Tue, 2 Jul 2024 15:28:08 +0200 Subject: [PATCH 1/5] wasm: add foreign import and linking of wasm object files --- src/build_settings.cpp | 9 --------- src/check_decl.cpp | 7 +++++-- src/checker.cpp | 3 +-- src/linker.cpp | 14 ++++++++++++++ src/llvm_backend_utility.cpp | 6 +++++- src/parser.cpp | 3 +-- 6 files changed, 26 insertions(+), 16 deletions(-) diff --git a/src/build_settings.cpp b/src/build_settings.cpp index c3b4f2506..9c93a5b69 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -860,15 +860,6 @@ gb_internal bool is_arch_x86(void) { 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 // join_path // is_dir diff --git a/src/check_decl.cpp b/src/check_decl.cpp index 883cfcba9..3c4a4b3de 100644 --- a/src/check_decl.cpp +++ b/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) { 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.link_name = name; diff --git a/src/checker.cpp b/src/checker.cpp index 734659510..c3d2ae5eb 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -5000,9 +5000,8 @@ gb_internal void check_foreign_import_fullpaths(Checker *c) { String file_str = op.value.value_string; file_str = string_trim_whitespace(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 = {}; bool ok = determine_path_from_string(nullptr, decl, base_dir, file_str, &foreign_path, /*use error not syntax_error*/true); if (ok) { diff --git a/src/linker.cpp b/src/linker.cpp index 371736743..34c0af7e5 100644 --- a/src/linker.cpp +++ b/src/linker.cpp @@ -85,6 +85,20 @@ gb_internal i32 linker_stage(LinkerData *gen) { if (extra_linker_flags.len != 0) { 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) { diff --git a/src/llvm_backend_utility.cpp b/src/llvm_backend_utility.cpp index 98ed0c57e..1165476be 100644 --- a/src/llvm_backend_utility.cpp +++ b/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); 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)) { import_name = substring(import_name, module_name.len+WASM_MODULE_NAME_SEPARATOR.len, import_name.len); } diff --git a/src/parser.cpp b/src/parser.cpp index 583f4a57d..997d32fa5 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -5813,7 +5813,6 @@ gb_internal bool determine_path_from_string(BlockingMutex *file_mutex, Ast *node return false; } - if (collection_name.len > 0) { // NOTE(bill): `base:runtime` == `core:runtime` if (collection_name == "core") { @@ -5968,7 +5967,7 @@ gb_internal void parse_setup_file_decls(Parser *p, AstFile *f, String const &bas Token fp_token = fp->BasicLit.token; String file_str = string_trim_whitespace(string_value_from_token(f, fp_token)); String fullpath = file_str; - if (allow_check_foreign_filepath()) { + if (!is_arch_wasm() || string_ends_with(fullpath, str_lit(".o"))) { String foreign_path = {}; bool ok = determine_path_from_string(&p->file_decl_mutex, node, base_dir, file_str, &foreign_path); if (!ok) { From 4e18e1b19192ceaffcda33ddb3ddfcc1e041f4f3 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Tue, 2 Jul 2024 15:29:24 +0200 Subject: [PATCH 2/5] wasi: make `os.open` work with absolute paths --- core/os/os_wasi.odin | 114 ++++++++++++++++++++++++++++++- core/sys/wasm/wasi/wasi_api.odin | 4 +- 2 files changed, 115 insertions(+), 3 deletions(-) diff --git a/core/os/os_wasi.odin b/core/os/os_wasi.odin index 8a1acb194..d38e73c79 100644 --- a/core/os/os_wasi.odin +++ b/core/os/os_wasi.odin @@ -38,6 +38,112 @@ _alloc_command_line_arguments :: proc() -> (args: []string) { 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: [dynamic]Preopen + +@(private, init) +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 + } + + 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(&preopens, Preopen{fd, strip_prefixes(string(buf))}) + } + } +} + +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) { iovs := wasi.ciovec_t(data) n, err := wasi.fd_write(wasi.fd_t(fd), {iovs}) @@ -87,7 +193,13 @@ open :: proc(path: string, mode: int = O_RDONLY, perm: int = 0) -> (Handle, Errn if mode & O_SYNC == O_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) } close :: proc(fd: Handle) -> Errno { diff --git a/core/sys/wasm/wasi/wasi_api.odin b/core/sys/wasm/wasi/wasi_api.odin index e9ceb4667..22abd8dc4 100644 --- a/core/sys/wasm/wasi/wasi_api.odin +++ b/core/sys/wasm/wasi/wasi_api.odin @@ -962,7 +962,7 @@ prestat_dir_t :: struct { } prestat_t :: struct { - tag: u8, + tag: preopentype_t, using u: struct { dir: prestat_dir_t, }, @@ -1158,7 +1158,7 @@ foreign wasi { /** * A buffer into which to write the preopened directory name. */ - path: string, + path: []byte, ) -> errno_t --- /** * Create a directory. From 10c68a89510c64c2693be8864dcae6bc54929811 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Tue, 2 Jul 2024 15:41:48 +0200 Subject: [PATCH 3/5] wasm: support `vendor:stb/truetype` and `vendor:fontstash` --- core/os/os_wasi.odin | 2 +- vendor/fontstash/fontstash.odin | 20 +---- vendor/fontstash/fontstash_os.odin | 20 +++++ vendor/stb/lib/stb_truetype_wasm.o | Bin 0 -> 41425 bytes vendor/stb/src/Makefile | 4 + vendor/stb/src/stb_truetype_wasm.c | 46 +++++++++++ vendor/stb/truetype/stb_truetype.odin | 2 + vendor/stb/truetype/stb_truetype_wasm.odin | 92 +++++++++++++++++++++ 8 files changed, 168 insertions(+), 18 deletions(-) create mode 100644 vendor/fontstash/fontstash_os.odin create mode 100644 vendor/stb/lib/stb_truetype_wasm.o create mode 100644 vendor/stb/src/stb_truetype_wasm.c create mode 100644 vendor/stb/truetype/stb_truetype_wasm.odin diff --git a/core/os/os_wasi.odin b/core/os/os_wasi.odin index d38e73c79..edcf979ad 100644 --- a/core/os/os_wasi.odin +++ b/core/os/os_wasi.odin @@ -96,7 +96,7 @@ 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] == '/' { + if len(prefix) == 0 && len(path) > 0 && path[0] != '/' { return true } diff --git a/vendor/fontstash/fontstash.odin b/vendor/fontstash/fontstash.odin index 9b556a04a..2c692db06 100644 --- a/vendor/fontstash/fontstash.odin +++ b/vendor/fontstash/fontstash.odin @@ -1,14 +1,14 @@ -//+build windows, linux, darwin //+vet !using-param package fontstash import "base:runtime" + import "core:log" -import "core:os" import "core:mem" import "core:math" import "core:strings" import "core:slice" + import stbtt "vendor:stb/truetype" // 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 } -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 // optionally init with ascii characters at a wanted size AddFontMem :: proc( @@ -1192,4 +1178,4 @@ EndState :: proc(using ctx: ^FontContext) { } __dirtyRectReset(ctx) } -} \ No newline at end of file +} diff --git a/vendor/fontstash/fontstash_os.odin b/vendor/fontstash/fontstash_os.odin new file mode 100644 index 000000000..6182573bd --- /dev/null +++ b/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) +} + diff --git a/vendor/stb/lib/stb_truetype_wasm.o b/vendor/stb/lib/stb_truetype_wasm.o new file mode 100644 index 0000000000000000000000000000000000000000..15c4fa0d5204ed2642368175e70ac3a9fc077d69 GIT binary patch literal 41425 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 a?pto#f9RH-`)ALq% + +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" diff --git a/vendor/stb/truetype/stb_truetype.odin b/vendor/stb/truetype/stb_truetype.odin index 876138c3a..6993cd2b7 100644 --- a/vendor/stb/truetype/stb_truetype.odin +++ b/vendor/stb/truetype/stb_truetype.odin @@ -17,6 +17,8 @@ when LIB != "" { } foreign import stbtt { LIB } +} else when ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32 { + foreign import stbtt "../lib/stb_truetype_wasm.o" } else { foreign import stbtt "system:stb_truetype" } diff --git a/vendor/stb/truetype/stb_truetype_wasm.odin b/vendor/stb/truetype/stb_truetype_wasm.odin new file mode 100644 index 000000000..2c22d45dc --- /dev/null +++ b/vendor/stb/truetype/stb_truetype_wasm.odin @@ -0,0 +1,92 @@ +//+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: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, + } + inputs := Inputs{base, num, size, cmp} + + sort.sort({ + collection = &inputs, + 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))) + + tmp := intrinsics.alloca(inputs.size, runtime.DEFAULT_ALIGNMENT) + intrinsics.mem_copy_non_overlapping(tmp, a, inputs.size) + intrinsics.mem_copy_non_overlapping(a, b, inputs.size) + intrinsics.mem_copy_non_overlapping(b, tmp, 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); From 0ef5191540cb750b512a1aea47732090b6a711ae Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Tue, 2 Jul 2024 20:10:21 +0200 Subject: [PATCH 4/5] use slice.ptr_swap instead of alloca --- vendor/stb/truetype/stb_truetype_wasm.odin | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/vendor/stb/truetype/stb_truetype_wasm.odin b/vendor/stb/truetype/stb_truetype_wasm.odin index 2c22d45dc..cb516bc1d 100644 --- a/vendor/stb/truetype/stb_truetype_wasm.odin +++ b/vendor/stb/truetype/stb_truetype_wasm.odin @@ -8,6 +8,7 @@ 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") @@ -33,10 +34,9 @@ qsort :: proc "c" (base: rawptr, num: uint, size: uint, cmp: proc "c" (a, b: raw size: uint, cmp: proc "c" (a, b: rawptr) -> i32, } - inputs := Inputs{base, num, size, cmp} sort.sort({ - collection = &inputs, + collection = &Inputs{base, num, size, cmp}, len = proc(it: sort.Interface) -> int { inputs := (^Inputs)(it.collection) return int(inputs.num) @@ -53,10 +53,7 @@ qsort :: proc "c" (base: rawptr, num: uint, size: uint, cmp: proc "c" (a, b: raw a := rawptr(uintptr(inputs.base) + (uintptr(i) * uintptr(inputs.size))) b := rawptr(uintptr(inputs.base) + (uintptr(j) * uintptr(inputs.size))) - tmp := intrinsics.alloca(inputs.size, runtime.DEFAULT_ALIGNMENT) - intrinsics.mem_copy_non_overlapping(tmp, a, inputs.size) - intrinsics.mem_copy_non_overlapping(a, b, inputs.size) - intrinsics.mem_copy_non_overlapping(b, tmp, inputs.size) + slice.ptr_swap_non_overlapping(a, b, int(inputs.size)) }, }) } From 5399093050f192c13ea494e69455be8e1052e06c Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Tue, 2 Jul 2024 20:17:24 +0200 Subject: [PATCH 5/5] make preopens a slice and remove bad current_dir --- core/os/os_wasi.odin | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/core/os/os_wasi.odin b/core/os/os_wasi.odin index edcf979ad..7b7fb4686 100644 --- a/core/os/os_wasi.odin +++ b/core/os/os_wasi.odin @@ -26,7 +26,6 @@ O_CLOEXEC :: 0x80000 stdin: Handle = 0 stdout: Handle = 1 stderr: Handle = 2 -current_dir: Handle = 3 args := _alloc_command_line_arguments() @@ -51,9 +50,9 @@ Preopen :: struct { prefix: string, } @(private) -preopens: [dynamic]Preopen +preopens: []Preopen -@(private, init) +@(init, private) init_preopens :: proc() { strip_prefixes :: proc(path: string) -> string { @@ -73,6 +72,7 @@ init_preopens :: proc() { 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 { @@ -87,9 +87,10 @@ init_preopens :: proc() { if err = wasi.fd_prestat_dir_name(fd, buf); err != .SUCCESS { panic("could not get filesystem preopen dir name") } - append(&preopens, Preopen{fd, strip_prefixes(string(buf))}) + append(&dyn_preopens, Preopen{fd, strip_prefixes(string(buf))}) } } + preopens = dyn_preopens[:] } wasi_match_preopen :: proc(path: string) -> (wasi.fd_t, string, bool) {