mirror of
https://github.com/odin-lang/Odin.git
synced 2026-04-18 12:30:28 +00:00
Merge pull request #3859 from laytan/wasm-stbtt-object-linking-preopens
wasm: support `vendor:stb/truetype` and `vendor:fontstash`
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -38,6 +37,114 @@ _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: []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) {
|
||||
iovs := wasi.ciovec_t(data)
|
||||
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 {
|
||||
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 {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -5824,7 +5824,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") {
|
||||
@@ -5979,7 +5978,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) {
|
||||
|
||||
20
vendor/fontstash/fontstash.odin
vendored
20
vendor/fontstash/fontstash.odin
vendored
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
20
vendor/fontstash/fontstash_os.odin
vendored
Normal file
20
vendor/fontstash/fontstash_os.odin
vendored
Normal file
@@ -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
vendored
Normal file
BIN
vendor/stb/lib/stb_truetype_wasm.o
vendored
Normal file
Binary file not shown.
4
vendor/stb/src/Makefile
vendored
4
vendor/stb/src/Makefile
vendored
@@ -6,6 +6,10 @@ else
|
||||
all: unix
|
||||
endif
|
||||
|
||||
wasm:
|
||||
mkdir -p ../lib
|
||||
clang -c -Os --target=wasm32 -nostdlib stb_truetype_wasm.c -o ../lib/stb_truetype_wasm.o
|
||||
|
||||
unix:
|
||||
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
|
||||
|
||||
46
vendor/stb/src/stb_truetype_wasm.c
vendored
Normal file
46
vendor/stb/src/stb_truetype_wasm.c
vendored
Normal file
@@ -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
vendor/stb/truetype/stb_truetype.odin
vendored
2
vendor/stb/truetype/stb_truetype.odin
vendored
@@ -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"
|
||||
}
|
||||
|
||||
89
vendor/stb/truetype/stb_truetype_wasm.odin
vendored
Normal file
89
vendor/stb/truetype/stb_truetype_wasm.odin
vendored
Normal file
@@ -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);
|
||||
Reference in New Issue
Block a user