├── .gitignore ├── .vscode └── c_cpp_properties.json ├── DESIGN.md ├── Makefile ├── TODO.md ├── make.bat ├── src ├── ast.cpp ├── ast.h ├── ast.md ├── cg_llvm.cpp ├── cg_llvm.h ├── cg_llvm.inl ├── grammar.ebnf ├── lexer.cpp ├── lexer.h ├── lexer.inl ├── main.cpp ├── mir.h ├── parser.cpp ├── parser.h ├── system_posix.cpp ├── system_windows.cpp └── util │ ├── allocator.cpp │ ├── allocator.h │ ├── array.h │ ├── assert.cpp │ ├── assert.h │ ├── atomic.h │ ├── cpprt.cpp │ ├── exchange.h │ ├── file.cpp │ ├── file.h │ ├── forward.h │ ├── hash.h │ ├── info.h │ ├── lock.cpp │ ├── lock.h │ ├── map.h │ ├── maybe.h │ ├── move.h │ ├── pool.cpp │ ├── pool.h │ ├── slab.cpp │ ├── slab.h │ ├── slice.h │ ├── stream.cpp │ ├── stream.h │ ├── string.cpp │ ├── string.h │ ├── system.h │ ├── thread.cpp │ ├── thread.h │ ├── time.cpp │ ├── time.h │ ├── traits.h │ ├── types.h │ ├── unicode.cpp │ └── unicode.h ├── test └── ks.odin └── unity.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | .* 2 | !/.gitignore 3 | *.o 4 | *.d 5 | thor 6 | thor.exe -------------------------------------------------------------------------------- /.vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "Linux", 5 | "includePath": [ 6 | "${workspaceFolder}/**" 7 | ], 8 | "defines": [], 9 | "cStandard": "c23", 10 | "cppStandard": "c++20", 11 | "compilerPath": "/usr/bin/clang++", 12 | "compilerArgs": [ 13 | "-Isrc" 14 | ] 15 | } 16 | ], 17 | "version": 4 18 | } -------------------------------------------------------------------------------- /DESIGN.md: -------------------------------------------------------------------------------- 1 | # Thor (A new compiler for Odin) 2 | ## Goals 3 | * 4 GiB/s of compile speed [ONTARGET] (we are already 6 GiB/s /w 32 threads) 4 | * Including linking time! 5 | * Deterministic builds 6 | * Same input produces the same executable 7 | * Same MD5SUM / SHA1 hashes 8 | * Incremental builds (rebuild what has changed) 9 | * Serialized AST [DONE] 10 | * Slab -> Cache -> Ref 11 | * Serialized IR 12 | * Slab -> Cache -> Ref 13 | * Data oriented [DONE] 14 | * No pointers, just indices and parallel arrays for the AST/IR 15 | * Fast builds 16 | * Fully threaded and out-of-order 17 | * Build an object as soon as possible 18 | * When a function is _complete_ it can be generated 19 | * Employ a middle IR (MIR) 20 | * Compile to C 21 | * Compile to LLVM 22 | * Monomorphization at the AST 23 | * Fixing all the issues with current Odin Parapoly 24 | * Global monomorphization cache 25 | * To reduce code bloat 26 | * Better debug support 27 | * Said said he would help with this. 28 | * Smaller executables 29 | * Static type information (no initialization needed @ runtime) 30 | * Smaller compiler codebase [ONTARGET] 31 | * Heavy code reuse and abstractions 32 | * deducing-this, concepts, etc 33 | * Compiler can be incrementally rebuilt and built fast [DONE] 34 | * Optional unity builds (every source file included in a `unity.cpp`) 35 | * Only fast linker support. 36 | * Only radlink on Windows 37 | * Only mold on Linux 38 | * LTO support 39 | 40 | # Architecture 41 | * Modern C++23 42 | * No template metaprogramming nonsense 43 | * Will only use templates for containers and varadic packs 44 | * Will use concepts though 45 | * Will use lambdas though 46 | * Monadic (Maybe, Mutex, et al) [ONTARGET] 47 | * Less error prone 48 | * Table driven [DONE] 49 | * Lexer and parser 50 | * No implicit copies (all explicit) [ONTARGET] 51 | * Every struct has copy assignment and copy constructor deleted at all times. 52 | * Extensive use of move semantics for containers 53 | * Multiple allocators 54 | * Temporary [DONE] 55 | * Scratch [DONE] 56 | * Permanent 57 | * No memory leaks (not even on exit) [ONTARGET] 58 | * No data races 59 | * Avoid the use of hash tables unless necessary 60 | * Flat arrays that are simply indexed, that is so much faster. 61 | 62 | 63 | # Non-goals 64 | * Architectures other than `amd64` and `aarch64` 65 | * So size_of(int) = 8, size_of(ptr) = 8 always basically... 66 | * Operating systems other than macOS, Windows and Linux 67 | * `Odin vet` 68 | * Documentation generation ala `Odin doc` 69 | * ABI compatability with existing Odin -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Options you can call this Makefile with 2 | DEBUG ?= 0 # Debug builds 3 | LTO ?= 0 # Link-time optimization 4 | ASAN ?= 0 # Address sanitizer 5 | TSAN ?= 0 # Thread sanitizer 6 | UBSAN ?= 0 # Undefined behavior sanitizer 7 | PROFILE ?= 0 # Profile build 8 | 9 | # Disable all built-in rules and variables 10 | MAKEFLAGS += --no-builtin-rules 11 | MAKEFLAGS += --no-builtin-variables 12 | 13 | # Some helper functions 14 | rwildcard = $(foreach d,$(wildcard $1*),$(call rwildcard,$d/,$2) $(filter $(subst *,%,$2),$d)) 15 | uniq = $(if $1,$(firstword $1) $(call uniq,$(filter-out $(firstword $1),$1))) 16 | 17 | UNAME := $(shell uname) 18 | 19 | # 20 | # C and C++ compiler 21 | # 22 | CC := gcc 23 | CC ?= clang 24 | # We use the C frontend with -xc++ to avoid linking in the C++ runtime library 25 | CXX := $(CC) -xc++ 26 | 27 | # Determine if the C or C++ compiler should be used as the linker frontend 28 | ifneq (,$(findstring -xc++,$(CXX))) 29 | LD := $(CC) 30 | else 31 | LD := $(CXX) 32 | endif 33 | 34 | # Determine the type of build we're building 35 | ifeq ($(DEBUG),1) 36 | TYPE := debug 37 | STRIP := true 38 | else ifeq ($(PROFILE),1) 39 | TYPE := profile 40 | STRIP := true 41 | else 42 | TYPE := release 43 | STRIP := strip 44 | endif 45 | 46 | OBJDIR := .build/$(TYPE)/objs 47 | DEPDIR := .build/$(TYPE)/deps 48 | 49 | SRCS := $(call rwildcard, src, *.cpp) 50 | OBJS := $(filter %.o,$(SRCS:%.cpp=$(OBJDIR)/%.o)) 51 | DEPS := $(filter %.d,$(SRCS:%.cpp=$(DEPDIR)/%.d)) 52 | 53 | BIN := thor 54 | 55 | # 56 | # Dependency flags 57 | # 58 | DEPFLAGS := -MMD 59 | DEPFLAGS += -MP 60 | 61 | # 62 | # C++ flags 63 | # 64 | CXXFLAGS := -Isrc 65 | CXXFLAGS += -pipe 66 | CXXFLAGS += -Wall 67 | CXXFLAGS += -Wextra 68 | CXXFLAGS += -std=c++20 69 | CXXFLAGS += -fno-exceptions 70 | CXXFLAGS += -fno-rtti 71 | # For reasons unknown, glibcxx decides to override C headers like , so 72 | # that when you include them in C++, it actually ends up includng . We 73 | # do not desire this behavior, we do not want C++ headers to ever be included in 74 | # the Thor compiler under any circumstance, we prefer the C ones, always. 75 | CXXFLAGS += -D_GLIBCXX_INCLUDE_NEXT_C_HEADERS 76 | # Give each function and data its own section so the linker can remove unused 77 | # references to each. This adds to the link time so it's not enabled by default 78 | CXXFLAGS += -ffunction-sections 79 | CXXFLAGS += -fdata-sections 80 | # Enable LTO if requested 81 | ifeq ($(LTO),1) 82 | CXXFLAGS += -flto 83 | endif 84 | ifeq ($(DEBUG),1) 85 | # Options for debug builds 86 | CXXFLAGS += -g 87 | CXXFLAGS += -O0 88 | CXXFLAGS += -fno-omit-frame-pointer 89 | else ifeq ($(PROFILE),1) 90 | # Options for profile builds 91 | CXXFLAGS += -pg 92 | CXXFLAGS += -no-pie 93 | 94 | CXXFLAGS += -O2 95 | CXXFLAGS += -fno-inline-functions 96 | CXXFLAGS += -fno-inline-functions-called-once 97 | CXXFLAGS += -fno-optimize-sibling-calls 98 | else 99 | # Options for release builds 100 | CXXFLAGS += -DNDEBUG 101 | CXXFLAGS += -O3 102 | CXXFLAGS += -fno-stack-protector 103 | CXXFLAGS += -fno-stack-check 104 | CXXFLAGS += -fno-unwind-tables 105 | CXXFLAGS += -fno-asynchronous-unwind-tables 106 | endif 107 | # Sanitizer selection 108 | ifeq ($(ASAN),1) 109 | CXXFLAGS += -fsanitize=address 110 | endif 111 | ifeq ($(TSAN),1) 112 | CXXFLAGS += -fsanitize=thread 113 | endif 114 | ifeq ($(UBSAN),1) 115 | CXXFLAGS += -fsanitize=undefined 116 | endif 117 | 118 | # 119 | # Linker flags 120 | # 121 | LDFLAGS := -ldl 122 | ifneq ($(UNAME),Darwin) 123 | LDFLAGS += -static-libgcc 124 | endif 125 | ifeq ($(LTO),1) 126 | LDFLAGS += -flto 127 | endif 128 | ifeq ($(PROFILE),1) 129 | LDFLAGS += -pg 130 | endif 131 | # Sanitizer selection 132 | ifeq ($(ASAN),1) 133 | LDFLAGS += -fsanitize=address 134 | endif 135 | ifeq ($(TSAN),1) 136 | LDFLAGS += -fsanitize=thread 137 | endif 138 | ifeq ($(UBSAN),1) 139 | LDFLAGS += -fsanitize=undefined 140 | endif 141 | ifneq ($(UNAME),Darwin) 142 | LDFLAGS += -Wl,--gc-sections 143 | endif 144 | 145 | all: $(BIN) 146 | 147 | $(DEPDIR): 148 | @mkdir -p $(addprefix $(DEPDIR)/,$(call uniq,$(dir $(SRCS)))) 149 | $(OBJDIR): 150 | @mkdir -p $(addprefix $(OBJDIR)/,$(call uniq,$(dir $(SRCS)))) 151 | 152 | # The rule that compiles source files to object files 153 | $(OBJDIR)/%.o: %.cpp $(DEPDIR)/%.d | $(OBJDIR) $(DEPDIR) 154 | $(CXX) -MT $@ $(DEPFLAGS) -MF $(DEPDIR)/$*.Td $(CXXFLAGS) -c -o $@ $< 155 | @mv -f $(DEPDIR)/$*.Td $(DEPDIR)/$*.d 156 | 157 | # The rule that links object files and makes the executable 158 | $(BIN): $(OBJS) 159 | $(LD) $(OBJS) $(LDFLAGS) -o $@ 160 | $(STRIP) $@ 161 | 162 | clean: 163 | rm -rf .build $(BIN) 164 | 165 | .PHONY: all clean 166 | 167 | $(DEPS): 168 | include $(wildcard $(DEPS)) -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # TODO List 2 | ## Parser 3 | ### Types 4 | * StructType 5 | * ProcType 6 | ### Statements 7 | * ForeignStmt 8 | * ForStmt 9 | * SwitchStmt 10 | ## IR 11 | Everything 12 | ## Driver 13 | Everything 14 | ## CG 15 | Everything -------------------------------------------------------------------------------- /make.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | cl.exe /nologo /I src /std:c++20 /GR- unity.cpp /link Winmm.lib /out:thor.exe -------------------------------------------------------------------------------- /src/ast.cpp: -------------------------------------------------------------------------------- 1 | #include "util/allocator.h" 2 | #include "util/file.h" 3 | #include "util/stream.h" 4 | 5 | #include "ast.h" 6 | 7 | #include 8 | #include // memcpy 9 | 10 | namespace Thor { 11 | 12 | struct AstFileHeader { 13 | Uint8 magic[4]; // tast 14 | Uint32 version; 15 | Uint64 slabs; 16 | }; 17 | 18 | Maybe AstFile::create(System& sys, StringView filename) { 19 | StringTable table{sys.allocator}; 20 | auto ref = table.insert(filename); 21 | if (!ref) { 22 | return {}; 23 | } 24 | return AstFile { sys, move(table), ref }; 25 | } 26 | 27 | Maybe AstFile::load(System& sys, Stream& stream) { 28 | AstFileHeader header; 29 | if (!stream.read(Slice{&header, 1}.cast())) { 30 | return {}; 31 | } 32 | if (header.magic != Slice{"tast"}.cast()) { 33 | return {}; 34 | } 35 | if (header.version != 1) { 36 | return {}; 37 | } 38 | auto string_table = StringTable::load(sys.allocator, stream); 39 | if (!string_table) { 40 | return {}; 41 | } 42 | // Read in the filename AstStringRef which is just Uint32[2] 43 | AstStringRef filename; 44 | if (!stream.read(Slice{&filename, 1}.cast())) { 45 | return {}; 46 | } 47 | // Count the # of slabs indicated by the bitset. 48 | Ulen n_slabs = 0; 49 | for (Uint64 i = 0; i < 64; i++) { 50 | if ((header.slabs & (1_u64 << Uint64(i))) != 0) { 51 | n_slabs++; 52 | } 53 | } 54 | Array> slabs{sys.allocator}; 55 | if (!slabs.resize(n_slabs)) { 56 | return {}; 57 | } 58 | for (Ulen i = 0; i < n_slabs; i++) { 59 | if ((header.slabs & (1_u64 << Uint64(i))) != 0) { 60 | if (!slabs[i]->load(sys.allocator, stream)) { 61 | return {}; 62 | } 63 | } 64 | } 65 | // Read in the IDs 66 | Array ids{sys.allocator}; 67 | // Read the AstID list in. 68 | return AstFile { 69 | sys, 70 | move(*string_table), 71 | filename, 72 | move(slabs), 73 | move(ids) 74 | }; 75 | } 76 | 77 | Bool AstFile::save(Stream& stream) const { 78 | AstFileHeader header { 79 | .magic = { 't', 'a', 's', 't' }, 80 | .version = 1, 81 | .slabs = 0 82 | }; 83 | // Determine which slabs are in-use. There is only 64 possible slab types 84 | // due to a 6-bit AstSlabID. We can store the occupancy in one 64-bit word. 85 | Ulen i = 0; 86 | for (const auto& slab : slabs_) { 87 | if (slab) { 88 | header.slabs |= 1_u64 << Uint64(i); 89 | } 90 | i++; 91 | } 92 | auto src = Slice{&header, 1}.cast(); 93 | if (!stream.write(src)) { 94 | return false; 95 | } 96 | if (!string_table_.save(stream)) { 97 | return false; 98 | } 99 | for (const auto& slab : slabs_) { 100 | if (slab && !slab->save(stream)) { 101 | return false; 102 | } 103 | } 104 | return true; 105 | } 106 | 107 | AstFile::~AstFile() { 108 | // TODO(dweiler): Call destructors on nodes 109 | } 110 | 111 | AstIDArray AstFile::insert(Slice ids) { 112 | const auto offset = ids_.length(); 113 | if (ids.is_empty() || !ids_.resize(offset + ids.length())) { 114 | return {}; 115 | } 116 | memcpy(ids_.data() + offset, ids.data(), ids.length() * sizeof(AstID)); 117 | return AstIDArray { Uint64(offset), Uint64(ids.length()) }; 118 | } 119 | 120 | // Stmt 121 | void AstStmt::dump(const AstFile& ast, StringBuilder& builder, Ulen nest) const { 122 | using enum Kind; 123 | switch (kind) { 124 | case EMPTY: return to_stmt()->dump(ast, builder, nest); 125 | case EXPR: return to_stmt()->dump(ast, builder, nest); 126 | case ASSIGN: return to_stmt()->dump(ast, builder, nest); 127 | case BLOCK: return to_stmt()->dump(ast, builder, nest); 128 | case IMPORT: return to_stmt()->dump(ast, builder, nest); 129 | case PACKAGE: return to_stmt()->dump(ast, builder, nest); 130 | case DEFER: return to_stmt()->dump(ast, builder, nest); 131 | case RETURN: return to_stmt()->dump(ast, builder, nest); 132 | case BREAK: return to_stmt()->dump(ast, builder, nest); 133 | case CONTINUE: return to_stmt()->dump(ast, builder, nest); 134 | case FALLTHROUGH: return to_stmt()->dump(ast, builder, nest); 135 | case FOREIGNIMPORT: return to_stmt()->dump(ast, builder, nest); 136 | case IF: return to_stmt()->dump(ast, builder, nest); 137 | case WHEN: return to_stmt()->dump(ast, builder, nest); 138 | case FOR: return to_stmt()->dump(ast, builder, nest); 139 | case DECL: return to_stmt()->dump(ast, builder, nest); 140 | case USING: return to_stmt()->dump(ast, builder, nest); 141 | } 142 | } 143 | 144 | void AstEmptyStmt::dump(const AstFile&, StringBuilder& builder, Ulen nest) const { 145 | builder.rep(nest * 2, ' '); 146 | builder.put(';'); 147 | } 148 | 149 | void AstExprStmt::dump(const AstFile& ast, StringBuilder& builder, Ulen nest) const { 150 | builder.rep(nest * 2, ' '); 151 | ast[expr].dump(ast, builder); 152 | } 153 | 154 | void AstAssignStmt::dump(const AstFile& ast, StringBuilder& builder, Ulen nest) const { 155 | static constexpr const StringView OP[] = { 156 | #define ASSIGN(ENUM, NAME, MATCH) MATCH, 157 | #include "lexer.inl" 158 | }; 159 | 160 | builder.rep(nest * 2, ' '); 161 | Bool first = true; 162 | for (auto value : ast[lhs]) { 163 | if (!first) { 164 | builder.put(','); 165 | builder.put(' '); 166 | } 167 | ast[value].dump(ast, builder); 168 | first = false; 169 | } 170 | 171 | builder.put(' '); 172 | builder.put(OP[Uint32(kind)]); 173 | builder.put(' '); 174 | 175 | first = true; 176 | for (auto value : ast[rhs]) { 177 | if (!first) { 178 | builder.put(','); 179 | builder.put(' '); 180 | } 181 | ast[value].dump(ast, builder); 182 | first = false; 183 | } 184 | } 185 | 186 | void AstBlockStmt::dump(const AstFile& ast, StringBuilder& builder, Ulen nest) const { 187 | builder.rep(nest * 2, ' '); 188 | builder.put('{'); 189 | builder.put('\n'); 190 | nest++; 191 | Bool first = true; 192 | for (auto stmt : ast[stmts]) { 193 | const auto& node = ast[stmt]; 194 | if (node.is_stmt()) { 195 | continue; 196 | } 197 | if (!first) { 198 | builder.put('\n'); 199 | } 200 | ast[stmt].dump(ast, builder, nest); 201 | first = false; 202 | } 203 | nest--; 204 | builder.put('\n'); 205 | builder.rep(nest * 2, ' '); 206 | builder.put('}'); 207 | } 208 | 209 | void AstImportStmt::dump(const AstFile& ast, StringBuilder& builder, Ulen nest) const { 210 | builder.rep(nest * 2, ' '); 211 | builder.put("import"); 212 | builder.put(' '); 213 | if (alias) { 214 | builder.put(ast[alias]); 215 | builder.put(' '); 216 | } 217 | ast[expr].dump(ast, builder); 218 | } 219 | 220 | void AstPackageStmt::dump(const AstFile& ast, StringBuilder& builder, Ulen nest) const { 221 | builder.rep(nest * 2, ' '); 222 | builder.put("package"); 223 | builder.put(' '); 224 | builder.put(ast[name]); 225 | } 226 | 227 | void AstDeferStmt::dump(const AstFile& ast, StringBuilder& builder, Ulen nest) const { 228 | builder.rep(nest * 2, ' '); 229 | builder.put("defer"); 230 | const auto& defer = ast[stmt]; 231 | if (defer.is_stmt()) { 232 | defer.dump(ast, builder, nest); 233 | } else { 234 | builder.put(' '); 235 | defer.dump(ast, builder, 0); 236 | } 237 | } 238 | 239 | void AstReturnStmt::dump(const AstFile& ast, StringBuilder& builder, Ulen nest) const { 240 | builder.rep(nest * 2, ' '); 241 | builder.put("return"); 242 | builder.put(' '); 243 | Bool first = true; 244 | for (auto expr : ast[exprs]) { 245 | if (!first) { 246 | builder.put(','); 247 | builder.put(' '); 248 | } 249 | ast[expr].dump(ast, builder); 250 | first = false; 251 | } 252 | } 253 | 254 | void AstBreakStmt::dump(const AstFile& ast, StringBuilder& builder, Ulen nest) const { 255 | builder.rep(nest * 2, ' '); 256 | builder.put("break"); 257 | if (label) { 258 | builder.put(' '); 259 | builder.put(ast[label]); 260 | } 261 | } 262 | 263 | void AstContinueStmt::dump(const AstFile& ast, StringBuilder& builder, Ulen nest) const { 264 | builder.rep(nest * 2, ' '); 265 | builder.put("continue"); 266 | if (label) { 267 | builder.put(' '); 268 | builder.put(ast[label]); 269 | } 270 | } 271 | 272 | void AstFallthroughStmt::dump(const AstFile&, StringBuilder& builder, Ulen nest) const { 273 | builder.rep(nest * 2, ' '); 274 | builder.put("fallthrough"); 275 | } 276 | 277 | void AstForeignImportStmt::dump(const AstFile& ast, StringBuilder& builder, Ulen nest) const { 278 | builder.rep(nest * 2, ' '); 279 | builder.put("foreign import"); 280 | builder.put(' '); 281 | if (ident) { 282 | builder.put(ast[ident]); 283 | builder.put(' '); 284 | } 285 | if (names.length() == 1) { 286 | ast[ast[names][0]].dump(ast, builder); 287 | } else { 288 | builder.put('{'); 289 | builder.put('\n'); 290 | Bool first = true; 291 | for (auto expr : ast[names]) { 292 | if (!first) { 293 | builder.put(','); 294 | builder.put('\n'); 295 | } 296 | builder.rep((nest + 1) * 2, ' '); 297 | ast[expr].dump(ast, builder); 298 | first = false; 299 | } 300 | builder.put('\n'); 301 | builder.put('}'); 302 | } 303 | } 304 | 305 | void AstIfStmt::dump(const AstFile& ast, StringBuilder& builder, Ulen nest) const { 306 | if (builder.last() != "else") { 307 | builder.rep(nest * 2, ' '); 308 | } else { 309 | builder.put(' '); 310 | } 311 | builder.put("if"); 312 | builder.put(' '); 313 | if (init) { 314 | ast[init].dump(ast, builder, 0); 315 | builder.put(';'); 316 | builder.put(' '); 317 | } 318 | ast[cond].dump(ast, builder); 319 | builder.put('\n'); 320 | ast[on_true].dump(ast, builder, nest); 321 | if (on_false) { 322 | builder.put('\n'); 323 | builder.rep(nest * 2, ' '); 324 | builder.put("else"); 325 | if (!ast[on_false].is_stmt()) { 326 | builder.put('\n'); 327 | } 328 | ast[on_false].dump(ast, builder, nest); 329 | } 330 | } 331 | 332 | void AstWhenStmt::dump(const AstFile& ast, StringBuilder& builder, Ulen nest) const { 333 | builder.rep(nest * 2, ' '); 334 | builder.put("when"); 335 | ast[cond].dump(ast, builder); 336 | builder.put(' '); 337 | ast[on_true].dump(ast, builder, nest+1); 338 | if (on_false) { 339 | builder.put("else"); 340 | ast[on_false].dump(ast, builder, nest+1); 341 | } 342 | } 343 | 344 | void AstForStmt::dump(const AstFile& ast, StringBuilder& builder, Ulen nest) const { 345 | builder.rep(nest * 2, ' '); 346 | builder.put("for"); 347 | builder.put(' '); 348 | if (in) { 349 | ast[in].dump(ast, builder, nest); 350 | } else { 351 | Bool first = true; 352 | for (auto value : ast[init]) { 353 | if (!first) { 354 | builder.put(','); 355 | builder.put(' '); 356 | } 357 | ast[value].dump(ast, builder, nest); 358 | first = false; 359 | } 360 | 361 | 362 | if(ast[ast[init][0]].is_stmt()) { 363 | builder.put(' '); 364 | builder.put("in"); 365 | builder.put(' '); 366 | } else if(ast[ast[init][0]].is_stmt()) { 367 | builder.put(';'); 368 | builder.put(' '); 369 | } 370 | 371 | if(cond) { 372 | ast[cond].dump(ast, builder); 373 | builder.put(';'); 374 | builder.put(' '); 375 | } 376 | 377 | if(post) { 378 | ast[post].dump(ast, builder, nest); 379 | } 380 | } 381 | 382 | builder.put(' '); 383 | ast[body].dump(ast, builder, nest); 384 | } 385 | 386 | void AstDeclStmt::dump(const AstFile& ast, StringBuilder& builder, Ulen nest) const { 387 | Bool first = true; 388 | builder.rep(nest * 2, ' '); 389 | for (auto value : ast[lhs]) { 390 | if (!first) { 391 | builder.put(','); 392 | builder.put(' '); 393 | } 394 | ast[value].dump(ast, builder); 395 | first = false; 396 | } 397 | if (type) { 398 | builder.put(':'); 399 | builder.put(' '); 400 | ast[type].dump(ast, builder); 401 | } else { 402 | builder.put(' '); 403 | builder.put(':'); 404 | } 405 | if (!rhs.is_empty()) { 406 | if (is_const) { 407 | builder.put(':'); 408 | } else { 409 | builder.put('='); 410 | } 411 | builder.put(' '); 412 | Bool first = true; 413 | for (auto value : ast[rhs]) { 414 | if (!first) { 415 | builder.put(','); 416 | builder.put(' '); 417 | } 418 | ast[value].dump(ast, builder); 419 | first = false; 420 | } 421 | } 422 | } 423 | 424 | void AstUsingStmt::dump(const AstFile& ast, StringBuilder& builder, Ulen nest) const { 425 | builder.rep(nest * 2, ' '); 426 | builder.put("using"); 427 | builder.put(' '); 428 | ast[expr].dump(ast, builder); 429 | } 430 | 431 | // Expr 432 | void AstExpr::dump(const AstFile& ast, StringBuilder& builder) const { 433 | using enum Kind; 434 | switch (kind) { 435 | case BIN: return to_expr()->dump(ast, builder); 436 | case UNARY: return to_expr()->dump(ast, builder); 437 | case IF: return to_expr()->dump(ast, builder); 438 | case WHEN: return to_expr()->dump(ast, builder); 439 | case FORIN: return to_expr()->dump(ast, builder); 440 | case DEREF: return to_expr()->dump(ast, builder); 441 | case OR_RETURN: return to_expr()->dump(ast, builder); 442 | case OR_BREAK: return to_expr()->dump(ast, builder); 443 | case OR_CONTINUE: return to_expr()->dump(ast, builder); 444 | case CALL: return to_expr()->dump(ast, builder); 445 | case IDENT: return to_expr()->dump(ast, builder); 446 | case UNDEF: return to_expr()->dump(ast, builder); 447 | case CONTEXT: return to_expr()->dump(ast, builder); 448 | case PROC: return to_expr()->dump(ast, builder); 449 | case SLICE: return to_expr()->dump(ast, builder); 450 | case INDEX: return to_expr()->dump(ast, builder); 451 | case INT: return to_expr()->dump(ast, builder); 452 | case FLOAT: return to_expr()->dump(ast, builder); 453 | case STRING: return to_expr()->dump(ast, builder); 454 | case IMAGINARY: return to_expr()->dump(ast, builder); 455 | case COMPOUND: return to_expr()->dump(ast, builder); 456 | case CAST: return to_expr()->dump(ast, builder); 457 | case SELECTOR: return to_expr()->dump(ast, builder); 458 | case ACCESS: return to_expr()->dump(ast, builder); 459 | case ASSERT: return to_expr()->dump(ast, builder); 460 | case TYPE: return to_expr()->dump(ast, builder); 461 | } 462 | } 463 | 464 | void AstBinExpr::dump(const AstFile& ast, StringBuilder& builder) const { 465 | static constexpr const StringView OP[] = { 466 | #define OPERATOR(ENUM, NAME, MATCH, PREC, NAMED, ASI) MATCH, 467 | #include "lexer.inl" 468 | }; 469 | ast[lhs].dump(ast, builder); 470 | builder.put(' '); 471 | builder.put(OP[Uint32(op)]); 472 | builder.put(' '); 473 | ast[rhs].dump(ast, builder); 474 | } 475 | 476 | void AstUnaryExpr::dump(const AstFile& ast, StringBuilder& builder) const { 477 | builder.put('('); 478 | static constexpr const StringView OP[] = { 479 | #define OPERATOR(ENUM, NAME, MATCH, PREC, NAMED, ASI) MATCH, 480 | #include "lexer.inl" 481 | }; 482 | builder.put(OP[Uint32(op)]); 483 | ast[operand].dump(ast, builder); 484 | builder.put(')'); 485 | } 486 | 487 | void AstIfExpr::dump(const AstFile& ast, StringBuilder& builder) const { 488 | ast[on_true].dump(ast, builder); 489 | builder.put(' '); 490 | builder.put("if"); 491 | builder.put(' '); 492 | ast[cond].dump(ast, builder); 493 | builder.put(' '); 494 | builder.put("else"); 495 | builder.put(' '); 496 | ast[on_false].dump(ast, builder); 497 | } 498 | 499 | void AstWhenExpr::dump(const AstFile& ast, StringBuilder& builder) const { 500 | ast[on_true].dump(ast, builder); 501 | builder.put(' '); 502 | builder.put("when"); 503 | builder.put(' '); 504 | ast[cond].dump(ast, builder); 505 | builder.put(' '); 506 | builder.put("else"); 507 | builder.put(' '); 508 | ast[on_false].dump(ast, builder); 509 | } 510 | 511 | void AstForInExpr::dump(const AstFile& ast, StringBuilder& builder) const { 512 | Bool first = true; 513 | for (auto arg : ast[lhs]) { 514 | if (!first) { 515 | builder.put(','); 516 | builder.put(' '); 517 | } 518 | ast[arg].dump(ast, builder); 519 | first = false; 520 | } 521 | builder.put(' '); 522 | builder.put("in"); 523 | builder.put(' '); 524 | ast[rhs].dump(ast, builder); 525 | } 526 | 527 | void AstDerefExpr::dump(const AstFile& ast, StringBuilder& builder) const { 528 | ast[operand].dump(ast, builder); 529 | builder.put('^'); 530 | } 531 | 532 | void AstOrReturnExpr::dump(const AstFile& ast, StringBuilder& builder) const { 533 | ast[operand].dump(ast, builder); 534 | builder.put(' '); 535 | builder.put("or_return"); 536 | } 537 | 538 | void AstOrBreakExpr::dump(const AstFile& ast, StringBuilder& builder) const { 539 | ast[operand].dump(ast, builder); 540 | builder.put(' '); 541 | builder.put("or_break"); 542 | } 543 | 544 | void AstOrContinueExpr::dump(const AstFile& ast, StringBuilder& builder) const { 545 | ast[operand].dump(ast, builder); 546 | builder.put(' '); 547 | builder.put("or_continue"); 548 | } 549 | 550 | void AstCallExpr::dump(const AstFile& ast, StringBuilder& builder) const { 551 | ast[operand].dump(ast, builder); 552 | builder.put('('); 553 | Bool first = true; 554 | for (auto arg : ast[args]) { 555 | if (!first) { 556 | builder.put(','); 557 | builder.put(' '); 558 | } 559 | ast[arg].dump(ast, builder); 560 | first = false; 561 | } 562 | builder.put(')'); 563 | } 564 | 565 | void AstIdentExpr::dump(const AstFile& ast, StringBuilder& builder) const { 566 | builder.put(ast[ident]); 567 | } 568 | 569 | void AstUndefExpr::dump(const AstFile&, StringBuilder& builder) const { 570 | builder.put("---"); 571 | } 572 | 573 | void AstContextExpr::dump(const AstFile&, StringBuilder& builder) const { 574 | builder.put("context"); 575 | } 576 | 577 | void AstProcExpr::dump(const AstFile& ast, StringBuilder& builder) const { 578 | ast[type].dump(ast, builder); 579 | ast[body].dump(ast, builder, 0); 580 | } 581 | 582 | void AstSliceExpr::dump(const AstFile& ast, StringBuilder& builder) const { 583 | ast[operand].dump(ast, builder); 584 | builder.put('['); 585 | if (lhs) { 586 | ast[lhs].dump(ast, builder); 587 | } 588 | builder.put(':'); 589 | if (rhs) { 590 | ast[rhs].dump(ast, builder); 591 | } 592 | builder.put(']'); 593 | } 594 | 595 | void AstIndexExpr::dump(const AstFile& ast, StringBuilder& builder) const { 596 | ast[operand].dump(ast, builder); 597 | builder.put('['); 598 | ast[lhs].dump(ast, builder); 599 | if (rhs) { 600 | builder.put(','); 601 | builder.put(' '); 602 | ast[rhs].dump(ast, builder); 603 | } 604 | builder.put(']'); 605 | } 606 | 607 | void AstIntExpr::dump(const AstFile&, StringBuilder& builder) const { 608 | builder.put(value); 609 | } 610 | 611 | void AstFloatExpr::dump(const AstFile&, StringBuilder& builder) const { 612 | builder.put(value); 613 | } 614 | 615 | void AstStringExpr::dump(const AstFile& ast, StringBuilder& builder) const { 616 | builder.put('"'); 617 | builder.put(ast[value]); 618 | builder.put('"'); 619 | } 620 | 621 | void AstImaginaryExpr::dump(const AstFile&, StringBuilder& builder) const { 622 | builder.put(value); 623 | builder.put('i'); 624 | } 625 | 626 | void AstCompoundExpr::dump(const AstFile& ast, StringBuilder& builder) const { 627 | builder.put('{'); 628 | Bool first = true; 629 | for (auto field : ast[fields]) { 630 | if (!first) { 631 | builder.put(','); 632 | builder.put(' '); 633 | } 634 | ast[field].dump(ast, builder); 635 | first = false; 636 | } 637 | builder.put('}'); 638 | } 639 | 640 | void AstCastExpr::dump(const AstFile& ast, StringBuilder& builder) const { 641 | if (type) { 642 | builder.put('('); 643 | ast[type].dump(ast, builder); 644 | builder.put(')'); 645 | builder.put('('); 646 | ast[expr].dump(ast, builder); 647 | builder.put(')'); 648 | } else { 649 | builder.put("auto_cast"); 650 | ast[expr].dump(ast, builder); 651 | } 652 | } 653 | 654 | void AstSelectorExpr::dump(const AstFile& ast, StringBuilder& builder) const { 655 | builder.put('.'); 656 | builder.put(ast[name]); 657 | } 658 | 659 | void AstAccessExpr::dump(const AstFile& ast, StringBuilder& builder) const { 660 | ast[operand].dump(ast, builder); 661 | if (is_arrow) { 662 | builder.put("->"); 663 | } else { 664 | builder.put('.'); 665 | } 666 | builder.put(ast[field]); 667 | } 668 | 669 | void AstAssertExpr::dump(const AstFile& ast, StringBuilder& builder) const { 670 | ast[operand].dump(ast, builder); 671 | builder.put('.'); 672 | if (type) { 673 | builder.put('('); 674 | ast[type].dump(ast, builder); 675 | builder.put(')'); 676 | } else { 677 | builder.put('?'); 678 | } 679 | } 680 | 681 | void AstTypeExpr::dump(const AstFile& ast, StringBuilder& builder) const { 682 | ast[type].dump(ast, builder); 683 | } 684 | 685 | // Type 686 | void AstType::dump(const AstFile& ast, StringBuilder& builder) const { 687 | using enum Kind; 688 | switch (kind) { 689 | case TYPEID: return to_type()->dump(ast, builder); 690 | case UNION: return to_type()->dump(ast, builder); 691 | case STRUCT: return to_type()->dump(ast, builder); 692 | case ENUM: return to_type()->dump(ast, builder); 693 | case PROC: return to_type()->dump(ast, builder); 694 | case PTR: return to_type()->dump(ast, builder); 695 | case MULTIPTR: return to_type()->dump(ast, builder); 696 | case SLICE: return to_type()->dump(ast, builder); 697 | case ARRAY: return to_type()->dump(ast, builder); 698 | case DYNARRAY: return to_type()->dump(ast, builder); 699 | case MAP: return to_type()->dump(ast, builder); 700 | case MATRIX: return to_type()->dump(ast, builder); 701 | case BITSET: return to_type()->dump(ast, builder); 702 | case NAMED: return to_type()->dump(ast, builder); 703 | case PARAM: return to_type()->dump(ast, builder); 704 | case PAREN: return to_type()->dump(ast, builder); 705 | case DISTINCT: return to_type()->dump(ast, builder); 706 | } 707 | } 708 | 709 | void AstTypeIDType::dump(const AstFile&, StringBuilder& builder) const { 710 | builder.put("typeid"); 711 | } 712 | 713 | void AstUnionType::dump(const AstFile& ast, StringBuilder& builder) const { 714 | builder.put("union"); 715 | builder.put(' '); 716 | builder.put('{'); 717 | builder.put('\n'); 718 | Bool first = true; 719 | for (const auto type : ast[types]) { 720 | if (!first) { 721 | builder.put(','); 722 | builder.put('\n'); 723 | } 724 | builder.rep(2, ' '); 725 | ast[type].dump(ast, builder); 726 | first = false; 727 | } 728 | builder.put('\n'); 729 | builder.put('}'); 730 | } 731 | 732 | void AstStructType::dump(const AstFile& ast, StringBuilder& builder) const { 733 | builder.put("struct"); 734 | builder.put(' '); 735 | builder.put('{'); 736 | builder.put('\n'); 737 | Bool first = true; 738 | for (const auto decl : ast[decls]) { 739 | if (!first) { 740 | builder.put(','); 741 | builder.put('\n'); 742 | } 743 | ast[decl].dump(ast, builder, 1); 744 | first = false; 745 | } 746 | builder.put('\n'); 747 | builder.put('}'); 748 | } 749 | 750 | void AstEnumType::dump(const AstFile& ast, StringBuilder& builder) const { 751 | builder.put("enum"); 752 | builder.put(' '); 753 | if (base) { 754 | ast[base].dump(ast, builder); 755 | builder.put(' '); 756 | } 757 | builder.put('{'); 758 | Bool first = true; 759 | for (const auto e : ast[enums]) { 760 | if (!first) { 761 | builder.put(','); 762 | builder.put(' '); 763 | } 764 | ast[e].dump(ast, builder); 765 | first = false; 766 | } 767 | builder.put('}'); 768 | } 769 | 770 | void AstProcType::dump(const AstFile& ast, StringBuilder& builder) const { 771 | builder.put("proc"); 772 | builder.put(' '); 773 | builder.put('('); 774 | Bool first = true; 775 | for (const auto decl : ast[fields]) { 776 | if (!first) { 777 | builder.put(','); 778 | builder.put(' '); 779 | } 780 | ast[decl].dump(ast, builder, 0); 781 | first = false; 782 | } 783 | builder.put(')'); 784 | builder.put(' '); 785 | builder.put("->"); 786 | builder.put(' '); 787 | 788 | builder.put('('); 789 | first = true; 790 | for (const auto type : ast[types]) { 791 | if (!first) { 792 | builder.put(','); 793 | builder.put(' '); 794 | } 795 | ast[type].dump(ast, builder, 0); 796 | first = false; 797 | } 798 | builder.put(')'); 799 | } 800 | 801 | void AstPtrType::dump(const AstFile& ast, StringBuilder& builder) const { 802 | builder.put('^'); 803 | ast[base].dump(ast, builder); 804 | } 805 | 806 | void AstMultiPtrType::dump(const AstFile& ast, StringBuilder& builder) const { 807 | builder.put("[^]"); 808 | ast[base].dump(ast, builder); 809 | } 810 | 811 | void AstSliceType::dump(const AstFile& ast, StringBuilder& builder) const { 812 | builder.put("[]"); 813 | ast[base].dump(ast, builder); 814 | } 815 | 816 | void AstArrayType::dump(const AstFile& ast, StringBuilder& builder) const { 817 | builder.put('['); 818 | if (size) { 819 | ast[size].dump(ast, builder); 820 | } else { 821 | builder.put('?'); 822 | } 823 | builder.put(']'); 824 | ast[base].dump(ast, builder); 825 | } 826 | 827 | void AstDynArrayType::dump(const AstFile& ast, StringBuilder& builder) const { 828 | builder.put('['); 829 | builder.put("dynamic"); 830 | builder.put(']'); 831 | ast[base].dump(ast, builder); 832 | } 833 | 834 | void AstMapType::dump(const AstFile& ast, StringBuilder& builder) const { 835 | builder.put("map"); 836 | builder.put('['); 837 | ast[kt].dump(ast, builder); 838 | builder.put(']'); 839 | ast[vt].dump(ast, builder); 840 | } 841 | 842 | void AstMatrixType::dump(const AstFile& ast, StringBuilder& builder) const { 843 | builder.put("matrix"); 844 | builder.put('['); 845 | ast[rows].dump(ast, builder); 846 | builder.put(','); 847 | builder.put(' '); 848 | ast[cols].dump(ast, builder); 849 | builder.put(']'); 850 | ast[base].dump(ast, builder); 851 | } 852 | 853 | void AstBitsetType::dump(const AstFile& ast, StringBuilder& builder) const { 854 | builder.put("bit_set"); 855 | builder.put('['); 856 | ast[expr].dump(ast, builder); 857 | if (type) { 858 | builder.put(';'); 859 | builder.put(' '); 860 | ast[type].dump(ast, builder); 861 | } 862 | builder.put(']'); 863 | } 864 | 865 | void AstNamedType::dump(const AstFile& ast, StringBuilder& builder) const { 866 | if (pkg) { 867 | builder.put(ast[pkg]); 868 | builder.put('.'); 869 | } 870 | builder.put(ast[name]); 871 | } 872 | 873 | void AstParamType::dump(const AstFile& ast, StringBuilder& builder) const { 874 | ast[name].dump(ast, builder); 875 | builder.put('('); 876 | Bool first = true; 877 | for (const auto expr : ast[exprs]) { 878 | if (!first) { 879 | builder.put(','); 880 | builder.put(' '); 881 | } 882 | ast[expr].dump(ast, builder); 883 | first = false; 884 | } 885 | builder.put(')'); 886 | } 887 | 888 | void AstParenType::dump(const AstFile& ast, StringBuilder& builder) const { 889 | builder.put('('); 890 | ast[type].dump(ast, builder); 891 | builder.put(')'); 892 | } 893 | 894 | void AstDistinctType::dump(const AstFile& ast, StringBuilder& builder) const { 895 | builder.put("distinct"); 896 | builder.put(' '); 897 | ast[type].dump(ast, builder); 898 | } 899 | 900 | // Field 901 | void AstField::dump(const AstFile& ast, StringBuilder& builder) const { 902 | ast[operand].dump(ast, builder); 903 | if (expr) { 904 | builder.put('='); 905 | ast[expr].dump(ast, builder); 906 | } 907 | } 908 | 909 | // Directive 910 | void AstDirective::dump(const AstFile& ast, StringBuilder& builder) const { 911 | builder.put('#'); 912 | builder.put(ast[name]); 913 | if (!args.is_empty()) { 914 | builder.put('('); 915 | Bool first = true; 916 | for (auto arg : ast[args]) { 917 | if (!first) { 918 | builder.put(','); 919 | builder.put(' '); 920 | } 921 | ast[arg].dump(ast, builder); 922 | first = false; 923 | } 924 | builder.put(')'); 925 | } 926 | } 927 | 928 | } // namespace Thor 929 | -------------------------------------------------------------------------------- /src/ast.md: -------------------------------------------------------------------------------- 1 | # Tree 2 | ```mermaid 3 | flowchart LR 4 | AstNode --> AstField 5 | AstNode --> AstDirective 6 | AstNode --> AstExpr 7 | AstNode --> AstType 8 | AstNode --> AstStmt 9 | AstExpr --> AstBinExpr 10 | AstExpr --> AstUnaryExpr 11 | AstExpr --> AstIfExpr 12 | AstExpr --> AstWhenExpr 13 | AstExpr --> AstDerefExpr 14 | AstExpr --> AstOrReturnExpr 15 | AstExpr --> AstOrBreakExpr 16 | AstExpr --> AstOrContinueExpr 17 | AstExpr --> AstIdentExpr 18 | AstExpr --> AstUndefExpr 19 | AstExpr --> AstContextExpr 20 | AstExpr --> AstProcExpr 21 | AstExpr --> AstRangeExpr 22 | AstExpr --> AstSliceRangeExpr 23 | AstExpr --> AstIntExpr 24 | AstExpr --> AstFloatExpr 25 | AstExpr --> AstStringExpr 26 | AstExpr --> AstImaginaryExpr 27 | AstExpr --> AstCastExpr 28 | AstExpr --> AstTypeExpr 29 | AstType --> AstTypeIDType 30 | AstType --> AstUnionType 31 | AstType --> AstEnumType 32 | AstType --> AstProcType 33 | AstType --> AstPtrType 34 | AstType --> AstMultiPtrType 35 | AstType --> AstSliceType 36 | AstType --> AstArrayType 37 | AstType --> AstDynArrayType 38 | AstType --> AstMapType 39 | AstType --> AstMatrixType 40 | AstType --> AstNamedType 41 | AstType --> AstParamType 42 | AstType --> AstParenType 43 | AstType --> AstDistinctType 44 | AstStmt --> AstEmptyStmt 45 | AstStmt --> AstExprStmt 46 | AstStmt --> AstAssignStmt 47 | AstStmt --> AstBlockStmt 48 | AstStmt --> AstImportStmt 49 | AstStmt --> AstPackageStmt 50 | AstStmt --> AstDeferStmt 51 | AstStmt --> AstReturnStmt 52 | AstStmt --> AstBreakStmt 53 | AstStmt --> AstContinueStmt 54 | AstStmt --> AstFallthroughStmt 55 | AstStmt --> AstForeignImportStmt 56 | AstStmt --> AstIfStmt 57 | AstStmt --> AstWhenStmt 58 | AstStmt --> AstDeclStmt 59 | AstStmt --> AstUsingStmt 60 | ``` -------------------------------------------------------------------------------- /src/cg_llvm.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "util/system.h" 4 | 5 | #include "cg_llvm.h" 6 | 7 | namespace Thor { 8 | 9 | Bool LLVM::try_link(void (*&proc)(void), const char* sym) { 10 | if (auto symbol = sys_.linker.link(sys_, lib_, sym)) { 11 | proc = symbol; 12 | return true; 13 | } 14 | InlineAllocator<1024> buf; 15 | StringBuilder builder{buf}; 16 | builder.put("Could not link procedure:"); 17 | builder.put(' '); 18 | builder.put('\"'); 19 | builder.put(StringView { sym, strlen(sym) }); 20 | builder.put('\"'); 21 | builder.put('\n'); 22 | if (auto result = builder.result()) { 23 | sys_.console.write(sys_, *result); 24 | } else { 25 | sys_.console.write(sys_, StringView { "Out of memory" }); 26 | } 27 | return false; 28 | } 29 | 30 | LLVM::LLVM(LLVM&& other) 31 | : sys_{other.sys_} 32 | , lib_{other.lib_} 33 | #define FN(_, NAME, ...) \ 34 | , NAME{exchange(other.NAME, nullptr)} 35 | #include "cg_llvm.inl" 36 | { 37 | } 38 | 39 | LLVM::~LLVM() { 40 | if (lib_) { 41 | sys_.linker.close(sys_, lib_); 42 | } 43 | } 44 | 45 | Maybe LLVM::load(System& sys, StringView name) { 46 | auto lib = sys.linker.load(sys, name); 47 | if (!lib) { 48 | InlineAllocator<1024> buf; 49 | StringBuilder builder{buf}; 50 | builder.put("Could not load:"); 51 | builder.put(' '); 52 | builder.put('\"'); 53 | builder.put(name); 54 | builder.put('\"'); 55 | builder.put('\n'); 56 | if (auto result = builder.result()) { 57 | sys.console.write(sys, *result); 58 | } else { 59 | sys.console.write(sys, StringView { "Out of memory" }); 60 | } 61 | return {}; 62 | } 63 | 64 | LLVM result{sys, lib}; 65 | 66 | // Link all functions now. 67 | #define FN(_, NAME, ...) \ 68 | if (!result.link(result.NAME, "LLVM" #NAME)) return {}; 69 | #include "cg_llvm.inl" 70 | 71 | return result; 72 | } 73 | 74 | } // namespace Thor -------------------------------------------------------------------------------- /src/cg_llvm.h: -------------------------------------------------------------------------------- 1 | #ifndef THOR_CG_LLVM_H 2 | #define THOR_CG_LLVM_H 3 | #include "util/maybe.h" 4 | #include "util/string.h" 5 | #include "util/system.h" 6 | 7 | namespace Thor { 8 | 9 | struct LLVM { 10 | LLVM(System& sys, Linker::Library* lib) 11 | : sys_{sys} 12 | , lib_{lib} 13 | { 14 | } 15 | 16 | LLVM(LLVM&& other); 17 | ~LLVM(); 18 | 19 | static Maybe load(System& sys, StringView name); 20 | 21 | struct OpaqueContext; 22 | struct OpaqueModule; 23 | struct OpaqueType; 24 | struct OpaqueValue; 25 | struct OpaqueBasicBlock; 26 | struct OpaqueBuilder; 27 | struct OpaqueTargetMachineOptions; 28 | struct OpaqueTargetMachine; 29 | struct OpaqueTarget; 30 | struct OpaquePassBuilderOptions; 31 | struct OpaqueError; 32 | struct OpaqueAttribute; 33 | 34 | using ContextRef = OpaqueContext*; 35 | using ModuleRef = OpaqueModule*; 36 | using TypeRef = OpaqueType*; 37 | using ValueRef = OpaqueValue*; 38 | using BasicBlockRef = OpaqueBasicBlock*; 39 | using BuilderRef = OpaqueBuilder*; 40 | using TargetMachineOptionsRef = OpaqueTargetMachineOptions*; 41 | using TargetMachineRef = OpaqueTargetMachine*; 42 | using TargetRef = OpaqueTarget*; 43 | using PassBuilderOptionsRef = OpaquePassBuilderOptions*; 44 | using ErrorRef = OpaqueError*; 45 | using AttributeRef = OpaqueAttribute*; 46 | using Bool = int; 47 | using Ulen = decltype(sizeof 0); 48 | using Opcode = int; 49 | using AttributeIndex = unsigned; 50 | 51 | enum class CodeGenOptLevel : int { None, Less, Default, Aggressive }; 52 | enum class RelocMode : int { Default, Static, PIC, DynamicNoPic, ROPI, RWPI, ROPI_RWPI }; 53 | enum class CodeModel : int { Default, JITDefault, Tiny, Small, Kernel, Medium, Large }; 54 | enum class CodeGenFileType : int { Assembly, Object }; 55 | enum class VerifierFailureAction : int { AbortProcess, PrintMessage, ReturnStatus }; 56 | enum class IntPredicate : int { EQ = 32, NE, UGT, UGE, ULT, ULE, SGT, SGE, SLT, SLE }; 57 | enum class RealPredicate : int { False, OEQ, OGT, OGE, OLT, OLE, ONE, ORD, UNO, UEQ, UGT, UGE, ULT, ULE, UNE, True }; 58 | 59 | enum class Linkage : int { 60 | External, 61 | AvailableExternally, 62 | OnceAny, 63 | OnceODR, 64 | OnceODRAutoHide, 65 | WeakAny, 66 | WeakODE, 67 | Appending, 68 | Internal, 69 | Private, 70 | DLLImport, 71 | DLLExport, 72 | ExternalWeak, 73 | Ghost, 74 | Common, 75 | LinkerPrivate, 76 | LinkerPrivateWeak 77 | }; 78 | 79 | private: 80 | Thor::Bool try_link(void (*&)(void), const char* sym); 81 | 82 | System& sys_; 83 | Linker::Library* lib_; 84 | 85 | public: 86 | template 87 | [[nodiscard]] Thor::Bool link(F& proc, const char* sym) { 88 | return try_link(reinterpret_cast(proc), sym); 89 | } 90 | 91 | #define FN(RETURN, NAME, ...) \ 92 | RETURN (*NAME)(__VA_ARGS__) = nullptr; 93 | #include "cg_llvm.inl" 94 | }; 95 | 96 | } // namespace Thor 97 | 98 | #endif // THOR_CG_LLVM_H -------------------------------------------------------------------------------- /src/cg_llvm.inl: -------------------------------------------------------------------------------- 1 | #ifndef FN 2 | #define FN(...) 3 | #endif 4 | 5 | // 6 | // Analysis.h 7 | // 8 | FN(Bool, VerifyModule, ModuleRef, VerifierFailureAction, char**) 9 | 10 | // 11 | // Core.h 12 | // 13 | 14 | // Global 15 | FN(void, Shutdown, void) 16 | FN(void, GetVersion, unsigned*, unsigned*, unsigned*) 17 | FN(void, DisposeMessage, char*) 18 | // Context 19 | FN(ContextRef, ContextCreate, void) 20 | FN(void, ContextDispose, ContextRef) 21 | FN(unsigned, GetEnumAttributeKindForName, const char*, Ulen) 22 | FN(AttributeRef, CreateEnumAttribute, ContextRef, unsigned, Uint64) 23 | // Types 24 | FN(TypeRef, Int1TypeInContext, ContextRef) 25 | FN(TypeRef, Int8TypeInContext, ContextRef) 26 | FN(TypeRef, Int16TypeInContext, ContextRef) 27 | FN(TypeRef, Int32TypeInContext, ContextRef) 28 | FN(TypeRef, Int64TypeInContext, ContextRef) 29 | FN(TypeRef, FloatTypeInContext, ContextRef) 30 | FN(TypeRef, DoubleTypeInContext, ContextRef) 31 | FN(TypeRef, PointerTypeInContext, ContextRef, unsigned) 32 | FN(TypeRef, VoidTypeInContext, ContextRef) 33 | FN(TypeRef, StructTypeInContext, ContextRef, TypeRef*, unsigned, Bool) 34 | FN(TypeRef, FunctionType, TypeRef, TypeRef*, unsigned, Bool) 35 | FN(TypeRef, StructCreateNamed, ContextRef, const char*) 36 | FN(TypeRef, ArrayType2, TypeRef, Uint64) 37 | FN(TypeRef, GetTypeByName2, ContextRef, const char*) 38 | // Values 39 | FN(ValueRef, ConstNull, ValueRef, const char*, Ulen) 40 | FN(ValueRef, ConstPointerNull, TypeRef) 41 | FN(ValueRef, ConstInt, TypeRef, unsigned long long, Bool) 42 | FN(ValueRef, ConstReal, TypeRef, double) 43 | FN(ValueRef, ConstStructInContext, ContextRef, ValueRef*, unsigned, Bool) 44 | FN(ValueRef, ConstArray2, TypeRef, ValueRef*, Uint64) 45 | FN(ValueRef, ConstNamedStruct, TypeRef, ValueRef*, unsigned) 46 | FN(ValueRef, AddGlobal, ModuleRef, TypeRef, const char*) 47 | FN(ValueRef, GetParam, ValueRef, unsigned) 48 | FN(ValueRef, GetBasicBlockParent, BasicBlockRef) 49 | FN(ValueRef, GetBasicBlockTerminator, BasicBlockRef) 50 | FN(ValueRef, BuildRetVoid, BuilderRef) 51 | FN(ValueRef, BuildRet, BuilderRef, ValueRef) 52 | FN(ValueRef, BuildBr, BuilderRef, BasicBlockRef) 53 | FN(ValueRef, BuildCondBr, BuilderRef, ValueRef, BasicBlockRef, BasicBlockRef) 54 | FN(ValueRef, BuildAdd, BuilderRef, ValueRef, ValueRef, const char*) 55 | FN(ValueRef, BuildFAdd, BuilderRef, ValueRef, ValueRef, const char*) 56 | FN(ValueRef, BuildSub, BuilderRef, ValueRef, ValueRef, const char*) 57 | FN(ValueRef, BuildFSub, BuilderRef, ValueRef, ValueRef, const char*) 58 | FN(ValueRef, BuildMul, BuilderRef, ValueRef, ValueRef, const char*) 59 | FN(ValueRef, BuildFMul, BuilderRef, ValueRef, ValueRef, const char*) 60 | FN(ValueRef, BuildUDiv, BuilderRef, ValueRef, ValueRef, const char*) 61 | FN(ValueRef, BuildSDiv, BuilderRef, ValueRef, ValueRef, const char*) 62 | FN(ValueRef, BuildURem, BuilderRef, ValueRef, ValueRef, const char*) 63 | FN(ValueRef, BuildSRem, BuilderRef, ValueRef, ValueRef, const char*) 64 | FN(ValueRef, BuildFRem, BuilderRef, ValueRef, ValueRef, const char*) 65 | FN(ValueRef, BuildShl, BuilderRef, ValueRef, ValueRef, const char*) 66 | FN(ValueRef, BuildLShr, BuilderRef, ValueRef, ValueRef, const char*) 67 | FN(ValueRef, BuildAShr, BuilderRef, ValueRef, ValueRef, const char*) 68 | FN(ValueRef, BuildAnd, BuilderRef, ValueRef, ValueRef, const char*) 69 | FN(ValueRef, BuildOr, BuilderRef, ValueRef, ValueRef, const char*) 70 | FN(ValueRef, BuildXor, BuilderRef, ValueRef, ValueRef, const char*) 71 | FN(ValueRef, BuildNeg, BuilderRef, ValueRef, const char*) 72 | FN(ValueRef, BuildFNeg, BuilderRef, ValueRef, const char*) 73 | FN(ValueRef, BuildNot, BuilderRef, ValueRef, const char*) 74 | FN(ValueRef, BuildMemCpy, BuilderRef, ValueRef, unsigned, ValueRef, unsigned, ValueRef) 75 | FN(ValueRef, BuildMemSet, BuilderRef, ValueRef, ValueRef, ValueRef, unsigned) 76 | FN(ValueRef, BuildAlloca, BuilderRef, TypeRef, const char*) 77 | FN(ValueRef, BuildLoad2, BuilderRef, TypeRef, ValueRef, const char*) 78 | FN(ValueRef, BuildStore, BuilderRef, ValueRef, ValueRef) 79 | FN(ValueRef, BuildGEP2, BuilderRef, TypeRef, ValueRef, ValueRef*, unsigned, const char*) 80 | FN(ValueRef, BuildGlobalString, BuilderRef, const char*, const char*) 81 | FN(ValueRef, BuildCast, BuilderRef, Opcode, ValueRef, TypeRef, const char*) 82 | FN(ValueRef, BuildICmp, BuilderRef, IntPredicate, ValueRef, ValueRef, const char*) 83 | FN(ValueRef, BuildFCmp, BuilderRef, IntPredicate, ValueRef, ValueRef, const char*) 84 | FN(ValueRef, BuildPhi, BuilderRef, TypeRef, const char*) 85 | FN(ValueRef, BuildCall2, BuilderRef, TypeRef, ValueRef, ValueRef*, unsigned, const char*) 86 | FN(ValueRef, BuildSelect, BuilderRef, ValueRef, ValueRef, ValueRef, const char*) 87 | FN(ValueRef, BuildExtractValue, BuilderRef, ValueRef, unsigned, const char*) 88 | // Builder 89 | FN(BasicBlockRef, CreateBasicBlockInContext, ContextRef, const char*) 90 | FN(void, AppendExistingBasicBlock, ValueRef, BasicBlockRef) 91 | FN(BuilderRef, CreateBuilderInContext, ContextRef) 92 | FN(void, PositionBuilderAtEnd, BuilderRef, BasicBlockRef) 93 | FN(BasicBlockRef, GetInsertBlock, BuilderRef) 94 | FN(void, DisposeBuilder, BuilderRef) 95 | 96 | 97 | // 98 | // Error.h 99 | // 100 | FN(void, ConsumeError, ErrorRef) 101 | 102 | // 103 | // Target.h 104 | // 105 | FN(void, InitializeX86TargetInfo, void) 106 | FN(void, InitializeX86Target, void) 107 | FN(void, InitializeX86TargetMC, void) 108 | FN(void, InitializeX86AsmPrinter, void) 109 | FN(void, InitializeX86AsmParser, void) 110 | FN(void, InitializeAArch64TargetInfo, void) 111 | FN(void, InitializeAArch64Target, void) 112 | FN(void, InitializeAArch64TargetMC, void) 113 | FN(void, InitializeAArch64AsmPrinter, void) 114 | FN(void, InitializeAArch64AsmParser, void) 115 | 116 | // 117 | // TargetMachine.h 118 | // 119 | FN(Bool, GetTargetFromTriple, const char*, TargetRef*, char**) 120 | FN(TargetMachineRef, CreateTargetMachine, TargetRef, const char*, const char*, const char*, CodeGenOptLevel, RelocMode, CodeModel) 121 | FN(void, DisposeTargetMachine, TargetMachineRef) 122 | FN(Bool, TargetMachineEmitToFile, TargetMachineRef, ModuleRef, const char*, CodeGenFileType, char**) 123 | 124 | // 125 | // PassBuilder.h 126 | // 127 | FN(ErrorRef, RunPasses, ModuleRef, const char*, TargetMachineRef, PassBuilderOptionsRef) 128 | FN(PassBuilderOptionsRef, CreatePassBuilderOptions, void) 129 | FN(void, DisposePassBuilderOptions, PassBuilderOptionsRef) 130 | 131 | #undef FN -------------------------------------------------------------------------------- /src/grammar.ebnf: -------------------------------------------------------------------------------- 1 | Stmt := EmptyStmt 2 | | BlockStmt 3 | | PackageStmt 4 | | ImportStmt 5 | | DeferStmt 6 | | ReturnStmt 7 | | BreakStmt 8 | | ContinueStmt 9 | | FallthroughStmt 10 | | ForeignImportStmt 11 | | IfStmt 12 | | WhenStmt 13 | | ForStmt 14 | | SwitchStmt 15 | | UsingStmt 16 | | DeclStmt 17 | | AssignStmt 18 | 19 | EmptyStmt := ';' 20 | BlockStmt := '{' Stmt* '}' 21 | PackageStmt := 'package' Ident ';' 22 | ImportStmt := 'import' Ident? StringExpr ';' 23 | BreakStmt := 'break' Ident? ';' 24 | ContinueStmt := 'continue' Ident? ';' 25 | FallthroughStmt := 'fallthrough' ';' 26 | ForeignImportStmt := 'foreign' 'import' Ident? StringLit ';' 27 | | 'foreign' 'import' Ident? '{' (Expr ',')+ '}' ';' 28 | 29 | ExprStmt := Expr 30 | IfStmt := 'if' ';' Expr? (DoStmt | BlockStmt) ('else' (IfStmt | DoStmt | BlockStmt))? 31 | | 'if' DeclStmt ';' Expr (DoStmt | BlockStmt) ('else' (IfStmt | DoStmt | BlockStmt))? 32 | | 'if' ExprStmt (DoStmt | BlockStmt) ('else' (IfStmt | DoStmt | BlockStmt))? 33 | WhenStmt := 'when' Expr BlockStmt ('else' BlockStmt)? 34 | ForStmt := 'for' (DoStmt | BlockStmt) 35 | | 'for' Expr (DoStmt | BlockStmt) 36 | | 'for' Stmt Expr ';' Stmt? (DoStmt | BlockStmt) 37 | | 'for' Expr (',' Expr)* 'in' Expr (DoStmt | BlockStmt) 38 | SwitchStmt := 'switch' Expr? '{' ('case' Expr? ':' Stmt*)? '}' 39 | DeferStmt := 'defer' Stmt 40 | ReturnStmt := 'return' (Expr (',' Expr)*)? ';' 41 | UsingStmt := 'using' Expr ';' 42 | DeclStmt := Expr (',' Expr)* ':' Type? (('=' | ':') Expr (',' Expr)*)? ';' 43 | AssignStmt := Expr (',' Expr)* AssignOp Expr (',' Expr)* ';' 44 | Expr := IfExpr 45 | | WhenExpr 46 | | DerefExpr 47 | | OrReturnExpr 48 | | OrBreakExpr 49 | | IdentExpr 50 | | UndefExpr 51 | | ContextExpr 52 | | RangeExpr 53 | | IntExpr 54 | | FloatExpr 55 | | StringExpr 56 | | ImaginaryExpr 57 | | CompoundExpr 58 | | SelectorExpr 59 | | CastExpr 60 | | TypeExpr 61 | IfExpr := Expr 'if' Expr 'else' Expr 62 | | Expr '?' Expr ':' Expr 63 | WhenExpr := Expr 'when' Expr 'else' Expr 64 | DerefExpr := Expr '^' 65 | OrReturnExpr := Expr 'or_return' 66 | OrBreakExpr := Expr 'or_break' 67 | IdentExpr := Ident 68 | UndefExpr := '---' 69 | ContextExpr := 'context' 70 | RangeExpr := Expr '..=' Expr 71 | | Expr '..<' Expr 72 | SliceExpr := Expr '[' Expr? ':' Expr? ']' 73 | IntExpr := IntLit 74 | FloatExpr := FloatLit 75 | StringExpr := StringLit 76 | ImaginaryExpr := ImaginaryLit 77 | CompoundExpr := '{' Field (',' Field)* '}' 78 | SelectorExpr := '.' Ident 79 | AccessExpr := Expr '.' Ident 80 | CastExpr := '(' Type ')' Expr 81 | | 'auto_cast' Expr 82 | TypeExpr := Type 83 | 84 | Type := UnionType 85 | | StructType 86 | | EnumType 87 | | PtrType 88 | | MultiPtrType 89 | | SliceType 90 | | DynArrayType 91 | | ArrayType 92 | | MapType 93 | | MatrixType 94 | | NamedType 95 | | ParamType 96 | | ParenType 97 | | DistinctType 98 | | TypeIDType 99 | UnionType := 'union' '{' (Type ',')* Type? '}' 100 | EnumType := 'enum' Type? '{' Field (',' Field)* '}' 101 | PtrType := '^' Type 102 | MultiPtrType := '[' '^' ']' Type 103 | SliceType := '[' ']' Type 104 | ArrayType := '[' (Expr | '?') ']' Type 105 | DynArrayType := '[' 'dynamic' ']' Type 106 | MapType := 'map' [' Type ']' Type 107 | MatrixType := 'matrix' '[' Expr ',' Expr ']' Type 108 | NamedType := Ident ('.' Ident)? 109 | ParenType := '(' Type ')' 110 | DistinctType := 'distinct' Type 111 | TypeIDType := 'typeid' 112 | 113 | AttributeList := '@' Field 114 | | '@' '(' Field (',' Field)* ')' 115 | DirectiveList := Directive* 116 | Field := Expr ('=' Expr)? 117 | Directive := '#' Ident '(' Expr (',' Expr)* ')' 118 | -------------------------------------------------------------------------------- /src/lexer.cpp: -------------------------------------------------------------------------------- 1 | #include "lexer.h" 2 | #include "string.h" 3 | 4 | #include "util/file.h" 5 | 6 | namespace Thor { 7 | 8 | Maybe Lexer::open(System& sys, StringView filename) { 9 | if (filename.is_empty()) { 10 | return {}; 11 | } 12 | auto file = File::open(sys, filename, File::Access::RD); 13 | if (!file) { 14 | return {}; 15 | } 16 | const auto length = file->tell(); 17 | if (length == 0 || length >= 0xff'ff'ff'ff_ulen) { 18 | return {}; 19 | } 20 | auto map = file->map(sys.allocator); 21 | if (map.is_empty()) { 22 | return {}; 23 | } 24 | return Lexer{move(map)}; 25 | } 26 | 27 | void Lexer::eat() { 28 | if (rune_ == '\n') { 29 | position_.advance_line(); 30 | } 31 | if (position_.next_offset >= input_.length()) { 32 | rune_ = 0; // EOF 33 | return; 34 | } 35 | auto rune = input_[position_.next_offset]; 36 | if (rune == 0) { 37 | // Error: Unexpected NUL 38 | } else if (rune & 0x80) { 39 | // TODO(dweiler): UTF-8 40 | } 41 | position_.advance_column(); 42 | rune_ = rune; 43 | } 44 | 45 | void Lexer::scan_escape() { 46 | Uint32 l = 0; 47 | Uint32 b = 0; 48 | switch (rune_) { 49 | case 'a': case 'b': 50 | case 'e': case 'f': 51 | case 'n': case 'r': 52 | case 't': case 'v': 53 | case '\\': 54 | case '\'': 55 | case '\"': 56 | eat(); 57 | return; 58 | case '0': case '1': case '2': case '3': 59 | case '4': case '5': case '6': case '7': 60 | l = 3; 61 | b = 8; 62 | eat(); 63 | break; 64 | case 'x': 65 | eat(); // Eat 'x' 66 | l = 2; 67 | b = 16; 68 | break; 69 | case 'u': 70 | [[fallthrough]]; 71 | case 'U': 72 | eat(); // Eat 'u' 73 | l = 4; 74 | b = 16; 75 | break; 76 | default: 77 | // ERROR: Unknown escape sequence in string 78 | return; 79 | } 80 | for (Ulen i = 0; i < l; i++) { 81 | Uint32 d = rune_ - '0'; 82 | if (d >= b) { 83 | if (rune_ == 0) { 84 | // ERROR: Unterminated escape sequence 85 | } else { 86 | // ERROR: Unknown character in escape sequence 87 | } 88 | return; 89 | } 90 | eat(); // Eat character in escape sequence 91 | } 92 | } 93 | 94 | Token Lexer::scan_string() { 95 | const auto beg = position_.this_offset; 96 | const auto quote = rune_; 97 | const auto raw = quote == '`'; 98 | for (;;) { 99 | if (!raw) { 100 | if (rune_ == '\n' || rune_ == 0) { 101 | // ERROR: String literal is not terminated. 102 | break; 103 | } 104 | } 105 | eat(); 106 | if (rune_ == quote) { 107 | eat(); // Consume quote 108 | break; 109 | } 110 | if (!raw && rune_ == '\\') { 111 | scan_escape(); 112 | } 113 | } 114 | return { LiteralKind::STRING, beg, position_.delta(beg) }; 115 | } 116 | 117 | Token Lexer::scan_number(Bool leading_period) { 118 | 119 | const auto beg = position_.this_offset; 120 | Token token = { LiteralKind::INTEGER, beg, 1_u16 }; 121 | if (leading_period) { 122 | token.as_literal = LiteralKind::FLOAT; 123 | } 124 | 125 | if (rune_ == '0') { 126 | eat(); // Eat '0' 127 | switch (rune_) { 128 | case 'b': 129 | eat(); 130 | while (rune_.is_digit(2) || rune_ == '_') eat(); 131 | break; 132 | case 'o': 133 | eat(); 134 | while (rune_.is_digit(8) || rune_ == '_') eat(); 135 | break; 136 | case 'd': 137 | eat(); 138 | while (rune_.is_digit(10) || rune_ == '_') eat(); 139 | break; 140 | case 'z': 141 | eat(); 142 | while (rune_.is_digit(12) || rune_ == '_') eat(); 143 | break; 144 | case 'x': 145 | eat(); 146 | while (rune_.is_digit(16) || rune_ == '_') eat(); 147 | break; 148 | case 'h': 149 | eat(); 150 | while (rune_.is_digit(16) || rune_ == '_') eat(); 151 | break; 152 | // TODO(Oliver): hexadecimal floats 153 | default: 154 | while (rune_.is_digit(16) || rune_ == '_') eat(); 155 | break; 156 | } 157 | } else { 158 | while (rune_.is_digit(10) || rune_ == '_') eat(); 159 | } 160 | 161 | if (rune_ == '.') { 162 | // Need to peek to check if the next character is '.' as the left-hand side 163 | // of the range operators: ..<, ..= permit an integer literal without space 164 | // between them. This is the only part of the lexer grammar that appears to 165 | // require LR(2). 166 | auto peek = position_.this_offset + 1; 167 | if (peek < input_.length() && input_[peek] == '.') { 168 | return token; 169 | } 170 | eat(); // Eat '.' 171 | token.as_literal = LiteralKind::FLOAT; 172 | while (rune_.is_digit(10) || rune_ == '_') eat(); 173 | } 174 | 175 | if (rune_ == 'e' || rune_ == 'E') { 176 | token.as_literal = LiteralKind::FLOAT; 177 | eat(); // Eat 'e' or 'E' 178 | if (rune_ == '-' || rune_ == '+') { 179 | eat(); // Eat '-' or '+' 180 | } 181 | while (rune_.is_digit(10) || rune_ == '_') eat(); 182 | } 183 | 184 | switch (rune_) { 185 | case 'i': 186 | case 'j': 187 | case 'k': 188 | eat(); 189 | token.as_literal = LiteralKind::IMAGINARY; 190 | default: 191 | break; 192 | } 193 | 194 | token.length = position_.delta(beg); 195 | return token; 196 | } 197 | 198 | Token Lexer::advance() { 199 | // Skip whitespace 200 | for (;;) { 201 | switch (rune_) { 202 | case ' ': case '\t': case '\r': 203 | eat(); // Consume 204 | continue; 205 | case '\n': 206 | if (!asi_) { 207 | eat(); 208 | continue; 209 | } 210 | } 211 | break; 212 | } 213 | const auto beg = position_.this_offset; 214 | if (rune_.is_char()) { 215 | while (rune_.is_alpha()) eat(); 216 | const auto len = position_.delta(beg); 217 | const auto str = input_.slice(beg).truncate(len); 218 | static constexpr const struct { 219 | StringView match; 220 | OperatorKind kind; 221 | } OPERATORS[] = { 222 | #define OPERATOR_true(ENUM, MATCH) \ 223 | { MATCH, OperatorKind::ENUM }, 224 | #define OPERATOR_false(...) 225 | #define OPERATOR(ENUM, NAME, MATCH, PREC, NAMED, ASI) \ 226 | OPERATOR_ ## NAMED (ENUM, MATCH) 227 | #include "lexer.inl" 228 | }; 229 | for (Ulen i = 0; i < countof(OPERATORS); i++) { 230 | if (const auto& op = OPERATORS[i]; op.match == str) { 231 | return { op.kind, beg, len }; 232 | } 233 | } 234 | static constexpr const struct { 235 | StringView match; 236 | KeywordKind kind; 237 | } KEYWORDS[] = { 238 | #define KEYWORD(ENUM, MATCH, ASI) \ 239 | { MATCH, KeywordKind::ENUM }, 240 | #include "lexer.inl" 241 | }; 242 | for (Ulen i = 0; i < countof(KEYWORDS); i++) { 243 | if (const auto& kw = KEYWORDS[i]; kw.match == str) { 244 | return { kw.kind, beg, len }; 245 | } 246 | } 247 | return { TokenKind::IDENTIFIER, beg, len }; 248 | } 249 | switch (rune_) { 250 | case '0': case '1': case '2': case '3': case '4': 251 | case '5': case '6': case '7': case '8': case '9': 252 | return scan_number(false); 253 | case 0: 254 | // EOF 255 | eat(); // Eat EOF 256 | if (asi_) { 257 | asi_ = false; 258 | return { TokenKind::IMPLICITSEMI, beg, 1_u16 }; 259 | } 260 | return { TokenKind::ENDOF, beg, 1_u16 }; 261 | case '\n': 262 | eat(); // Eat '\n' 263 | if (asi_) { 264 | asi_ = false; 265 | return { TokenKind::IMPLICITSEMI, beg, 1_u16 }; 266 | } 267 | return advance(); 268 | case '\\': 269 | eat(); 270 | asi_ = false; 271 | return next(); // Recurse 272 | case '@': eat(); return { TokenKind::ATTRIBUTE, beg, 1_u16 }; 273 | case '#': eat(); return { TokenKind::DIRECTIVE, beg, 1_u16 }; 274 | case '$': eat(); return { TokenKind::CONST, beg, 1_u16 }; 275 | case ';': eat(); return { TokenKind::EXPLICITSEMI, beg, 1_u16 }; 276 | case ',': eat(); return { TokenKind::COMMA, beg, 1_u16 }; 277 | case '{': eat(); return { TokenKind::LBRACE, beg, 1_u16 }; 278 | case '}': eat(); return { TokenKind::RBRACE, beg, 1_u16 }; 279 | case '(': eat(); return { OperatorKind::LPAREN, beg, 1_u16 }; 280 | case ')': eat(); return { OperatorKind::RPAREN, beg, 1_u16 }; 281 | case '[': eat(); return { OperatorKind::LBRACKET, beg, 1_u16 }; 282 | case ']': eat(); return { OperatorKind::RBRACKET, beg, 1_u16 }; 283 | case '?': eat(); return { OperatorKind::QUESTION, beg, 1_u16 }; 284 | case ':': eat(); return { OperatorKind::COLON, beg, 1_u16 }; 285 | case '^': eat(); return { OperatorKind::POINTER, beg, 1_u16 }; 286 | case '%': 287 | eat(); // Eat '%' 288 | switch (rune_) { 289 | case '=': 290 | eat(); // Eat '=' 291 | return { AssignKind::MOD, beg, 2_u16 }; 292 | case '%': 293 | eat(); // Eat '%' 294 | if (rune_ == '=') { 295 | eat(); // Eat '=' 296 | return { AssignKind::MOD, beg, 3_u16 }; 297 | } 298 | return { OperatorKind::MOD, beg, 2_u16 }; 299 | } 300 | return { OperatorKind::MOD, beg, 1_u16 }; 301 | case '*': 302 | eat(); // Eat '*' 303 | if (rune_ == '=') { 304 | return { AssignKind::MUL, beg, 2_u16 }; 305 | } 306 | return { OperatorKind::MUL, beg, 1_u16 }; 307 | case '/': 308 | eat(); // Eat '/' 309 | switch (rune_) { 310 | case '/': 311 | // Scan to EOL or EOF 312 | eat(); // Eat '/' 313 | while (rune_ != '\n' && rune_ != 0) eat(); 314 | eat(); // Eat '\n' 315 | return { TokenKind::COMMENT, beg, position_.delta(beg) }; // '//' 316 | case '*': 317 | eat(); // Eat '*' 318 | for (Ulen i = 1; i != 0; /**/) switch (rune_) { 319 | case 0: 320 | // EOF 321 | i = 0; 322 | break; 323 | case '/': 324 | eat(); // Eat '/' 325 | if (rune_ == '*') { 326 | eat(); // Eat '*' 327 | i++; 328 | } 329 | break; 330 | case '*': 331 | eat(); // Eat '*' 332 | if (rune_ == '/') { 333 | eat(); // Eat '/' 334 | i--; 335 | } 336 | break; 337 | default: 338 | eat(); // Eat what ever is in the comment 339 | break; 340 | } 341 | // This also limits comments to no more than 64 KiB 342 | return { TokenKind::COMMENT, beg, position_.delta(beg) }; // '/*' 343 | case '=': 344 | eat(); // '=' 345 | return { AssignKind::QUO, beg, 2_u16 }; // '/=' 346 | } 347 | return { OperatorKind::QUO, beg, 1_u16 }; // '/' 348 | case '~': 349 | eat(); // Eat '~' 350 | if (rune_ == '=') { 351 | eat(); // Eat '=' 352 | return { AssignKind::XOR, beg, 2_u16 }; 353 | } 354 | return { OperatorKind::XOR, beg, 1_u16 }; 355 | case '!': 356 | eat(); // Eat '!' 357 | if (rune_ == '=') { 358 | eat(); // Eat '=' 359 | return { OperatorKind::NEQ, beg, 2_u16 }; 360 | } 361 | return { OperatorKind::LNOT, beg, 1_u16 }; 362 | case '+': 363 | eat(); // Eat '+' 364 | if (rune_ == '=') { 365 | eat(); // Eat '=' 366 | return { AssignKind::ADD, beg, 2_u16 }; 367 | } 368 | return { OperatorKind::ADD, beg, 1_u16 }; 369 | case '-': 370 | eat(); // Eat '-' 371 | switch (rune_) { 372 | case '=': 373 | eat(); // Eat '=' 374 | return { AssignKind::SUB, beg, 2_u16 }; 375 | case '>': 376 | eat(); // Eat '>' 377 | return { OperatorKind::ARROW, beg, 2_u16 }; 378 | case '-': 379 | eat(); // Eat '-' 380 | if (rune_ == '-') { 381 | return { TokenKind::UNDEFINED, beg, 3_u16 }; 382 | } else { 383 | // ERROR: Decrement operator '--' isn't allowed in Odin 384 | } 385 | } 386 | return { OperatorKind::SUB, beg, 1_u16 }; 387 | case '=': 388 | eat(); // Eat '=' 389 | if (rune_ == '=') { 390 | return { OperatorKind::EQ, beg, 2_u16 }; 391 | } 392 | return { AssignKind::EQ, beg, 1_u16 }; 393 | case '.': 394 | eat(); // Eat '.' 395 | switch (rune_) { 396 | case '.': 397 | eat(); // Eat '.' 398 | switch (rune_) { 399 | case '<': // ..< 400 | eat(); // Eat '<' 401 | return { OperatorKind::RANGEHALF, beg, position_.delta(beg) }; // '..<' 402 | case '=': // ..= 403 | eat(); // Eat '=' 404 | return { OperatorKind::RANGEFULL, beg, position_.delta(beg) }; // '..=' 405 | } 406 | return { OperatorKind::ELLIPSIS, beg, 2_u16 }; // '..' 407 | case '0': case '1': case '2': case '3': case '4': 408 | case '5': case '6': case '7': case '8': case '9': 409 | return scan_number(true); 410 | } 411 | return { OperatorKind::PERIOD, beg, 1_u16 }; // '.' 412 | case '<': 413 | eat(); // Eat '<' 414 | switch (rune_) { 415 | case '=': 416 | eat(); // Eat '=' 417 | return { OperatorKind::LT, beg, 2_u16 }; // '<=' 418 | case '<': 419 | eat(); // Eat '<' 420 | if (rune_ == '=') { 421 | eat(); // Eat '=' 422 | return { AssignKind::SHL, beg, 3_u16 }; // '<<' 423 | } 424 | return { OperatorKind::SHL, beg, 2_u16 }; // '<<=' 425 | } 426 | return { OperatorKind::LT, beg, 1_u16 }; // '<' 427 | case '>': 428 | eat(); // Eat '>' 429 | switch (rune_) { 430 | case '=': 431 | eat(); // Eat '=' 432 | return { OperatorKind::GT, beg, 2_u16 }; // '>=' 433 | case '>': 434 | eat(); // Eat '>' 435 | if (rune_ == '=') { 436 | eat(); // Eat '=' 437 | return { AssignKind::SHR, beg, 3_u16 }; // '>>' 438 | } 439 | return { OperatorKind::SHR, beg, 2_u16 }; // '>>=' 440 | } 441 | return { OperatorKind::GT, beg, 1_u16 }; // '>' 442 | case '&': 443 | eat(); // Eat '&' 444 | switch (rune_) { 445 | case '~': 446 | eat(); // Eat '~' 447 | if (rune_ == '=') { 448 | eat(); // Eat '=' 449 | return { AssignKind::ANDNOT, beg, 3_u16 }; // '&~=' 450 | } 451 | return { OperatorKind::ANDNOT, beg, 2_u16 }; // '&~' 452 | case '=': 453 | eat(); // Eat '=' 454 | return { AssignKind::BAND, beg, 2_u16 }; // '&=' 455 | case '&': 456 | eat(); // Eat '&' 457 | if (rune_ == '=') { 458 | eat(); // Eat '=' 459 | return { AssignKind::LAND, beg, 3_u16 }; // '&&=' 460 | } 461 | return { OperatorKind::LAND, beg, 2_u16 }; // '&&' 462 | } 463 | return { OperatorKind::BAND, beg, 1_u16 }; 464 | case '|': 465 | eat(); // Eat '|' 466 | switch (rune_) { 467 | case '=': 468 | eat(); // Eat '=' 469 | return { AssignKind::BOR, beg, 2_u16 }; // '|=' 470 | case '|': 471 | eat(); // Eat '|' 472 | if (rune_ == '=') { 473 | eat(); // Eat '=' 474 | return { AssignKind::LOR, beg, 3_u16 }; // '||=' 475 | } 476 | return { OperatorKind::LOR, beg, 2_u16 }; // '|=' 477 | } 478 | return { OperatorKind::BOR, beg, 1_u16 }; // '|' 479 | case '`': 480 | [[fallthrough]]; 481 | case '"': 482 | return scan_string(); 483 | default: 484 | eat(); 485 | return { TokenKind::INVALID, beg, 1_u16 }; 486 | } 487 | return { TokenKind::ENDOF, beg, 1_u16 }; 488 | } 489 | 490 | Token Lexer::next() { 491 | static constexpr const Bool KIND_ASI[] = { 492 | #define KIND(ENUM, NAME, ASI) ASI, 493 | #include "lexer.inl" 494 | }; 495 | static constexpr const Bool OPERATOR_ASI[] = { 496 | #define OPERATOR(ENUM, NAME, MATCH, PREC, NAMEED, ASI) ASI, 497 | #include "lexer.inl" 498 | }; 499 | static constexpr const Bool KEYWORD_ASI[] = { 500 | #define KEYWORD(ENUM, MATCH, ASI) ASI, 501 | #include "lexer.inl" 502 | }; 503 | const auto token = advance(); 504 | Bool asi = false; 505 | switch (token.kind) { 506 | case TokenKind::OPERATOR: 507 | asi = OPERATOR_ASI[Uint32(token.as_operator)]; 508 | break; 509 | case TokenKind::KEYWORD: 510 | asi = KEYWORD_ASI[Uint32(token.as_keyword)]; 511 | break; 512 | default: 513 | asi = KIND_ASI[Uint32(token.kind)]; 514 | break; 515 | } 516 | asi_ = asi; 517 | return token; 518 | } 519 | 520 | } // namespace Thor -------------------------------------------------------------------------------- /src/lexer.h: -------------------------------------------------------------------------------- 1 | #ifndef THOR_LEXER_H 2 | #define THOR_LEXER_H 3 | #include "util/array.h" 4 | #include "util/maybe.h" 5 | #include "util/string.h" 6 | #include "util/unicode.h" 7 | 8 | namespace Thor { 9 | 10 | struct System; 11 | 12 | enum class TokenKind : Uint8 { 13 | #define KIND(ENUM, NAME, ASI) ENUM, 14 | #include "lexer.inl" 15 | }; 16 | 17 | enum class AssignKind : Uint8 { 18 | #define ASSIGN(ENUM, NAME, MATCH) ENUM, 19 | #include "lexer.inl" 20 | }; 21 | 22 | enum class LiteralKind : Uint8 { 23 | #define LITERAL(ENUM, NAME) ENUM, 24 | #include "lexer.inl" 25 | }; 26 | 27 | enum class OperatorKind : Uint8 { 28 | #define OPERATOR(ENUM, NAME, MATCH, PREC, NAMED, ASI) ENUM, 29 | #include "lexer.inl" 30 | }; 31 | 32 | enum class KeywordKind : Uint8 { 33 | #define KEYWORD(ENUM, MATCH, ASI) ENUM, 34 | #include "lexer.inl" 35 | }; 36 | 37 | enum class DirectiveKind : Uint8 { 38 | #define DIRECTIVE(ENUM, MATCH) ENUM, 39 | #include "lexer.inl" 40 | }; 41 | 42 | enum class CConvKind : Uint8 { 43 | #define CCONV(ENUM, MATCH) ENUM, 44 | #include "lexer.inl" 45 | }; 46 | 47 | template 48 | constexpr Ulen countof(const T(&)[E]) { 49 | return E; 50 | } 51 | 52 | struct Token { 53 | THOR_FORCEINLINE constexpr Token(TokenKind kind, Uint32 offset, Uint16 length) 54 | : kind{kind} 55 | , length{length} 56 | , offset{offset} 57 | { 58 | } 59 | THOR_FORCEINLINE constexpr Token(AssignKind kind, Uint32 offset, Uint16 length) 60 | : Token{TokenKind::ASSIGNMENT, offset, length} 61 | { 62 | as_assign = kind; 63 | } 64 | THOR_FORCEINLINE constexpr Token(LiteralKind kind, Uint32 offset, Uint16 length) 65 | : Token{TokenKind::LITERAL, offset, length} 66 | { 67 | as_literal = kind; 68 | } 69 | THOR_FORCEINLINE constexpr Token(OperatorKind kind, Uint32 offset, Uint16 length) 70 | : Token{TokenKind::OPERATOR, offset, length} 71 | { 72 | as_operator = kind; 73 | } 74 | THOR_FORCEINLINE constexpr Token(KeywordKind kind, Uint32 offset, Uint16 length) 75 | : Token{TokenKind::KEYWORD, offset, length} 76 | { 77 | as_keyword = kind; 78 | } 79 | THOR_FORCEINLINE constexpr Token(DirectiveKind kind, Uint32 offset, Uint16 length) 80 | : Token{TokenKind::DIRECTIVE, offset, length} 81 | { 82 | as_directive = kind; 83 | } 84 | void dump(System& sys, StringView input); 85 | TokenKind kind; // 1b 86 | union { 87 | Nat as_nat{}; 88 | AssignKind as_assign; 89 | LiteralKind as_literal; 90 | OperatorKind as_operator; 91 | KeywordKind as_keyword; 92 | DirectiveKind as_directive; 93 | }; // 1b 94 | Uint16 length; // 2b (if 0, length is too long and must be re-lexed at the given offset) 95 | Uint32 offset; // 4b 96 | }; 97 | static_assert(sizeof(Token) == 8, "Token cannot be larger than 64-bits"); 98 | 99 | struct Allocator; 100 | 101 | struct Position { 102 | Uint32 line = 0; 103 | Uint32 column = 0; 104 | Uint32 next_offset = 0; 105 | Uint32 this_offset = 0; 106 | void advance_column() { 107 | this_offset = next_offset; 108 | next_offset++; 109 | column++; 110 | } 111 | void advance_line() { 112 | line++; 113 | column = 0; 114 | } 115 | Uint16 delta(Uint32 beg) const { 116 | const auto diff = this_offset - beg; 117 | if (diff >= 0xffff_u16) { 118 | // Too large to fit in 16-bits. 119 | return 0; 120 | } 121 | return static_cast(diff); 122 | } 123 | }; 124 | 125 | struct Lexer { 126 | static Maybe open(System& sys, StringView file); 127 | Lexer(Lexer&& other) 128 | : map_{move(other.map_)} 129 | , input_{other.input_} 130 | , position_{other.position_} 131 | , rune_{exchange(other.rune_, 0)} 132 | , asi_{exchange(other.asi_, false)} 133 | { 134 | } 135 | Token next(); 136 | void eat(); 137 | THOR_FORCEINLINE constexpr StringView input() const { 138 | return input_; 139 | } 140 | THOR_FORCEINLINE constexpr StringView string(Token token) const { 141 | return input_.slice(token.offset).truncate(token.length); 142 | } 143 | 144 | // Calculate the source position for a given token. 145 | struct SourcePosition { 146 | Uint32 line = 0; 147 | Uint32 column = 0; 148 | }; 149 | SourcePosition position(Uint32 offset) const { 150 | Uint32 line = 1; 151 | Uint32 column = 1; 152 | for (Uint32 i = 0; i < offset; i++) { 153 | if (input_[i] == '\n') { 154 | column = 1; 155 | line++; 156 | } else { 157 | column++; 158 | } 159 | } 160 | return SourcePosition { line, column }; 161 | } 162 | 163 | private: 164 | Token advance(); 165 | Token scan_string(); 166 | void scan_escape(); 167 | Token scan_number(Bool leading_period); 168 | Lexer(Array&& map) 169 | : map_{move(map)} 170 | , input_{map_.slice().cast()} 171 | { 172 | eat(); 173 | } 174 | Array map_; 175 | StringView input_; 176 | Position position_; 177 | Rune rune_ = 0; 178 | Bool asi_ = false; 179 | }; 180 | 181 | } // namespace Thor 182 | 183 | #endif // THOR_LEXER_H 184 | -------------------------------------------------------------------------------- /src/lexer.inl: -------------------------------------------------------------------------------- 1 | #ifndef KIND 2 | #define KIND(...) 3 | #endif 4 | #ifndef ASSIGN 5 | #define ASSIGN(...) 6 | #endif 7 | #ifndef LITERAL 8 | #define LITERAL(...) 9 | #endif 10 | #ifndef OPERATOR 11 | #define OPERATOR(...) 12 | #endif 13 | #ifndef KEYWORD 14 | #define KEYWORD(...) 15 | #endif 16 | #ifndef DIRECTIVE 17 | #define DIRECTIVE(...) 18 | #endif 19 | #ifndef CCONV 20 | #define CCONV(...) 21 | #endif 22 | 23 | // Token kinds 24 | // ENUM NAME ASI 25 | KIND(ENDOF, "eof", false) 26 | KIND(INVALID, "invalid", false) 27 | KIND(COMMENT, "comment", false) 28 | KIND(IDENTIFIER, "identifier", true) 29 | KIND(LITERAL, "literal", true) 30 | KIND(OPERATOR, "operator", false) // ASI handled separate 31 | KIND(KEYWORD, "keyword", false) // ASI handled separate 32 | KIND(ASSIGNMENT, "assignment", false) 33 | KIND(DIRECTIVE, "directive", false) // '#' 34 | KIND(ATTRIBUTE, "attribute", false) // '@' 35 | KIND(CONST, "const", false) // '$' 36 | KIND(EXPLICITSEMI, "semicolon", false) // ';' (actually in the text) 37 | KIND(IMPLICITSEMI, "semicolon", false) // ';' (inserted by the lexer) 38 | KIND(COMMA, "comma", false) // ',' 39 | KIND(LBRACE, "left brace", false) // '{' 40 | KIND(RBRACE, "right brace", true) // '}' 41 | KIND(UNDEFINED, "undefined", true) // '---' 42 | 43 | // Assignment tokens 44 | // 45 | // Different from operators because assignments are strictly statements. 46 | // 47 | // ENUM NAME MATCH 48 | ASSIGN(EQ, "eq", "=") 49 | ASSIGN(ADD, "add", "+=") 50 | ASSIGN(SUB, "sub", "-=") 51 | ASSIGN(MUL, "mul", "*=") 52 | ASSIGN(QUO, "quo", "/=") 53 | ASSIGN(MOD, "mod", "%=") 54 | ASSIGN(REM, "rem", "%%=") 55 | ASSIGN(BAND, "bit and", "&=") 56 | ASSIGN(BOR, "bit or", "|=") 57 | ASSIGN(XOR, "xor", "~=") 58 | ASSIGN(ANDNOT, "and not", "&~=") 59 | ASSIGN(SHL, "shift left", "<<=") 60 | ASSIGN(SHR, "shift right", ">>=") 61 | ASSIGN(LAND, "logical and", "&&=") 62 | ASSIGN(LOR, "logical or", "||=") 63 | 64 | // Literal tokens 65 | // 66 | // ENUM NAME 67 | LITERAL(INTEGER, "integer") 68 | LITERAL(FLOAT, "float") 69 | LITERAL(IMAGINARY, "imaginary") 70 | LITERAL(RUNE, "rune") 71 | LITERAL(STRING, "string") 72 | LITERAL(BOOLEAN, "boolean") 73 | 74 | // Operators 75 | // ENUM NAME MATCH PREC NAMED ASI 76 | OPERATOR(LNOT, "logical not", "!", 0, false, false) 77 | OPERATOR(POINTER, "pointer", "^", 0, false, true) 78 | OPERATOR(ARROW, "arrow", "->", 0, false, false) 79 | OPERATOR(LPAREN, "left paren", "(", 0, false, false) 80 | OPERATOR(RPAREN, "right paren", ")", 0, false, true) 81 | OPERATOR(LBRACKET, "left bracket", "[", 0, false, false) 82 | OPERATOR(RBRACKET, "right bracket", "]", 0, false, true) 83 | OPERATOR(COLON, "colon", ":", 0, false, false) 84 | OPERATOR(PERIOD, "period", ".", 0, false, false) 85 | OPERATOR(IN, "in", "in", 6, true, false) 86 | OPERATOR(NOT_IN, "not_in", "not_in", 6, true, false) 87 | OPERATOR(AUTO_CAST, "auto_cast", "auto_cast", 0, true, false) 88 | OPERATOR(CAST, "cast", "cast", 0, true, false) 89 | OPERATOR(TRANSMUTE, "transmute", "transmute", 0, true, false) 90 | OPERATOR(OR_ELSE, "or_else", "or_else", 1, true, false) 91 | OPERATOR(OR_RETURN, "or_return", "or_return", 1, true, true) 92 | OPERATOR(OR_BREAK, "or_break", "or_break", 0, true, true) 93 | OPERATOR(OR_CONTINUE, "or_continue", "or_continue", 0, true, true) 94 | OPERATOR(QUESTION, "question", "?", 1, false, true) 95 | OPERATOR(ELLIPSIS, "ellipsis", "..", 2, false, false) 96 | OPERATOR(RANGEFULL, "full range", "..=", 2, false, false) 97 | OPERATOR(RANGEHALF, "half range", "..<", 2, false, false) 98 | OPERATOR(LOR, "logical or", "||", 3, false, false) 99 | OPERATOR(LAND, "logical and", "&&", 4, false, false) 100 | OPERATOR(EQ, "equal", "==", 5, false, false) 101 | OPERATOR(NEQ, "not equal", "!=", 5, false, false) 102 | OPERATOR(LT, "less-than", "<", 5, false, false) 103 | OPERATOR(GT, "greater-than", ">", 5, false, false) 104 | OPERATOR(LTEQ, "less-than or equal", "<=", 5, false, false) 105 | OPERATOR(GTEQ, "greater-than or equal", ">=", 5, false, false) 106 | OPERATOR(ADD, "addition", "+", 6, false, false) 107 | OPERATOR(SUB, "subtraction", "-", 6, false, false) 108 | OPERATOR(BOR, "bit or", "|", 6, false, false) 109 | OPERATOR(XOR, "xor", "~", 6, false, false) 110 | OPERATOR(QUO, "quo", "/", 7, false, false) 111 | OPERATOR(MUL, "mul", "*", 7, false, false) 112 | OPERATOR(MOD, "mod", "%", 7, false, false) 113 | OPERATOR(REM, "rem", "%%", 7, false, false) 114 | OPERATOR(BAND, "bit and", "&", 7, false, false) 115 | OPERATOR(ANDNOT, "and not", "&~", 7, false, false) 116 | OPERATOR(SHL, "shift left", "<<", 7, false, false) 117 | OPERATOR(SHR, "shift right", ">>", 7, false, false) 118 | 119 | // Keyword tokens 120 | // 121 | // ENUM MATCH ASI 122 | KEYWORD(IMPORT, "import", false) 123 | KEYWORD(FOREIGN, "foreign", false) 124 | KEYWORD(PACKAGE, "package", false) 125 | KEYWORD(TYPEID, "typeid", true) 126 | KEYWORD(WHERE, "where", false) 127 | KEYWORD(WHEN, "when", false) // Can also be an operator (x when y else z) 128 | KEYWORD(IF, "if", false) // Can also be an operator (x if y else z) 129 | KEYWORD(ELSE, "else", false) // Can also be an operator (x if y else z) 130 | KEYWORD(FOR, "for", false) 131 | KEYWORD(SWITCH, "switch", false) 132 | KEYWORD(DO, "do", false) 133 | KEYWORD(CASE, "case", false) 134 | KEYWORD(BREAK, "break", true) 135 | KEYWORD(CONTINUE, "continue", true) 136 | KEYWORD(FALLTHROUGH, "fallthrough", true) 137 | KEYWORD(DEFER, "defer", false) 138 | KEYWORD(RETURN, "return", true) 139 | KEYWORD(PROC, "proc", false) 140 | KEYWORD(STRUCT, "struct", false) 141 | KEYWORD(UNION, "union", false) 142 | KEYWORD(ENUM, "enum", false) 143 | KEYWORD(BITSET, "bit_set", false) 144 | KEYWORD(BITFIELD, "bit_field", false) 145 | KEYWORD(MAP, "map", false) 146 | KEYWORD(DYNAMIC, "dynamic", false) 147 | KEYWORD(DISTINCT, "distinct", false) 148 | KEYWORD(USING, "using", false) 149 | KEYWORD(CONTEXT, "context", true) 150 | KEYWORD(MATRIX, "matrix", false) 151 | KEYWORD(ASM, "asm", false) 152 | 153 | // Directive tokens 154 | // 155 | // ENUM MATCH 156 | DIRECTIVE(OPTIONAL_OK, "optional_ok") 157 | DIRECTIVE(OPTIONAL_ALLOCATOR_ERROR, "optional_allocator_error") 158 | DIRECTIVE(BOUNDS_CHECK, "bounds_check") 159 | DIRECTIVE(NO_BOUNDS_CHECK, "no_bounds_check") 160 | DIRECTIVE(TYPE_ASSERT, "type_assert") 161 | DIRECTIVE(NO_TYPE_ASSERT, "no_type_assert") 162 | DIRECTIVE(ALIGN, "align") 163 | DIRECTIVE(RAW_UNION, "raw_union") 164 | DIRECTIVE(PACKED, "packed") 165 | DIRECTIVE(TYPE, "type") 166 | DIRECTIVE(SIMD, "simd") 167 | DIRECTIVE(SOA, "soa") 168 | DIRECTIVE(PARTIAL, "partial") 169 | DIRECTIVE(SPARSE, "sparse") 170 | DIRECTIVE(FORCE_INLINE, "force_inline") 171 | DIRECTIVE(FORCE_NO_INLINE, "force_no_inline") 172 | DIRECTIVE(NO_NIL, "no_nil") 173 | DIRECTIVE(SHARED_NIL, "shared_nil") 174 | DIRECTIVE(NO_ALIAS, "no_alias") 175 | DIRECTIVE(C_VARARG, "c_vararg") 176 | DIRECTIVE(ANY_INT, "any_int") 177 | DIRECTIVE(SUBTYPE, "subtype") 178 | DIRECTIVE(BY_PTR, "by_ptr") 179 | DIRECTIVE(ASSERT, "assert") 180 | DIRECTIVE(PANIC, "panic") 181 | DIRECTIVE(UNROLL, "unroll") 182 | DIRECTIVE(LOCATION, "location") 183 | DIRECTIVE(PROCEDURE, "procedure") 184 | DIRECTIVE(FILE, "file") 185 | DIRECTIVE(LOAD, "load") 186 | DIRECTIVE(LOAD_HASH, "load_hash") 187 | DIRECTIVE(LOAD_DIRECTORY, "load_directory") 188 | DIRECTIVE(DEFINED, "defined") 189 | DIRECTIVE(CONFIG, "config") 190 | DIRECTIVE(MAYBE, "maybe") 191 | DIRECTIVE(CALLER_LOCATION, "caller_location") 192 | DIRECTIVE(CALLER_EXPRESSION, "caller_expression") 193 | DIRECTIVE(NO_COPY, "no_copy") 194 | DIRECTIVE(CONST, "const") 195 | 196 | // Calling conventions 197 | // 198 | // ENUM MATCH 199 | CCONV(ODIN, "odin") 200 | CCONV(CONTEXTLESS, "contextless") 201 | CCONV(CDECL, "cdecl") 202 | CCONV(STDCALL, "stdcall") 203 | CCONV(FASTCALL, "fastcall") 204 | CCONV(NONE, "none") 205 | CCONV(NAKED, "naked") 206 | CCONV(WIN64, "win64") 207 | CCONV(SYSV, "sysv") 208 | CCONV(SYSTEM, "system") 209 | 210 | #undef KIND 211 | #undef ASSIGN 212 | #undef LITERAL 213 | #undef OPERATOR 214 | #undef KEYWORD 215 | #undef DIRECTIVE 216 | #undef CCONV 217 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include "util/system.h" 2 | #include "util/file.h" 3 | #include "util/map.h" 4 | #include "util/stream.h" 5 | 6 | #include "parser.h" 7 | 8 | #include "cg_llvm.h" 9 | 10 | namespace Thor { 11 | extern const Filesystem STD_FILESYSTEM; 12 | extern const Heap STD_HEAP; 13 | extern const Console STD_CONSOLE; 14 | extern const Process STD_PROCESS; 15 | extern const Linker STD_LINKER; 16 | extern const Scheduler STD_SCHEDULER; 17 | extern const Chrono STD_CHRONO; 18 | } 19 | 20 | using namespace Thor; 21 | 22 | int main(int, char **) { 23 | System sys { 24 | STD_FILESYSTEM, 25 | STD_HEAP, 26 | STD_CONSOLE, 27 | STD_PROCESS, 28 | STD_LINKER, 29 | STD_SCHEDULER, 30 | STD_CHRONO, 31 | }; 32 | 33 | 34 | auto parser = Parser::open(sys, "test/ks.odin"); 35 | if (!parser) { 36 | return 1; 37 | } 38 | 39 | Array> stmts{sys.allocator}; 40 | for (;;) { 41 | auto stmt = parser->parse_stmt(false, {}, {}); 42 | if (!stmt) { 43 | break; 44 | } 45 | if (!stmts.push_back(move(stmt))) { 46 | break; 47 | } 48 | } 49 | 50 | auto& ast = parser->ast(); 51 | StringBuilder builder{sys.allocator}; 52 | for (auto stmt : stmts) { 53 | if (ast[stmt].is_stmt()) { 54 | continue; 55 | } 56 | ast[stmt].dump(ast, builder, 0); 57 | builder.put('\n'); 58 | } 59 | if (auto result = builder.result()) { 60 | sys.console.write(sys, *result); 61 | sys.console.write(sys, StringView { "\n" }); 62 | } 63 | 64 | return 0; 65 | } 66 | -------------------------------------------------------------------------------- /src/mir.h: -------------------------------------------------------------------------------- 1 | #ifndef THOR_MIR_H 2 | #define THOR_MIR_H 3 | 4 | namespace Thor { 5 | 6 | 7 | 8 | } // namespace Thor 9 | 10 | #endif // THOR_MIR_H -------------------------------------------------------------------------------- /src/parser.h: -------------------------------------------------------------------------------- 1 | #ifndef THOR_PARSER_H 2 | #define THOR_PARSER_H 3 | #include "lexer.h" 4 | #include "ast.h" 5 | #include "util/system.h" 6 | 7 | namespace Thor { 8 | 9 | struct Parser { 10 | static Maybe open(System& sys, StringView file); 11 | AstStringRef parse_ident(Uint32* poffset = nullptr); 12 | 13 | using DirectiveList = Maybe>>; 14 | using AttributeList = Maybe>>; 15 | 16 | // Expression parsers 17 | AstRef parse_expr(Bool lhs); 18 | AstRef parse_value(Bool lhs); 19 | 20 | AstRef parse_operand(); 21 | AstRef parse_bin_expr(Bool lhs, Uint32 prec); 22 | AstRef parse_unary_expr(Bool lhs); 23 | AstRef parse_operand(Bool lhs); // Operand parser for AstBinExpr or AstUnaryExpr 24 | 25 | AstRef parse_int_expr(); 26 | AstRef parse_float_expr(); 27 | AstRef parse_string_expr(); 28 | AstRef parse_imaginary_expr(); 29 | AstRef parse_compound_expr(); 30 | AstRef parse_proc_expr(); 31 | AstRef parse_paren_expr(); 32 | AstRef parse_ident_expr(); 33 | AstRef parse_undef_expr(); 34 | AstRef parse_context_expr(); 35 | AstRef parse_if_expr(AstRef expr); 36 | AstRef parse_when_expr(AstRef on_true); 37 | AstRef parse_deref_expr(AstRef operand); 38 | AstRef parse_or_return_expr(AstRef operand); 39 | AstRef parse_or_break_expr(AstRef operand); 40 | AstRef parse_or_continue_expr(AstRef operand); 41 | AstRef parse_call_expr(AstRef operand); 42 | 43 | // Statement parsers 44 | AstRef parse_stmt(Bool use, DirectiveList&& directives, AttributeList&& attributes); 45 | AstRef parse_expr_stmt(); 46 | AstRef parse_empty_stmt(); 47 | AstRef parse_block_stmt(); 48 | AstRef parse_package_stmt(); 49 | AstRef parse_import_stmt(); 50 | AstRef parse_break_stmt(); 51 | AstRef parse_continue_stmt(); 52 | AstRef parse_fallthrough_stmt(); 53 | AstRef parse_foreign_import_stmt(); 54 | AstRef parse_if_stmt(); 55 | AstRef parse_do_stmt(); 56 | AstRef parse_when_stmt(); 57 | AstRef parse_for_stmt(); 58 | AstRef parse_defer_stmt(); 59 | AstRef parse_return_stmt(); 60 | AstRef parse_using_stmt(); 61 | 62 | // Type parsers 63 | AstRef parse_type(); 64 | AstRef parse_typeid_type(); 65 | AstRef parse_union_type(); 66 | AstRef parse_struct_type(); 67 | AstRef parse_enum_type(); 68 | AstRef parse_ptr_type(); 69 | AstRef parse_proc_type(); 70 | AstRef parse_multiptr_type(Uint32 offset); 71 | AstRef parse_slice_type(Uint32 offset); 72 | AstRef parse_array_type(Uint32 offset); 73 | AstRef parse_dynarray_type(Uint32 offset); 74 | AstRef parse_map_type(); 75 | AstRef parse_bitset_type(); 76 | AstRef parse_matrix_type(); 77 | AstRef parse_named_type(); 78 | AstRef parse_paren_type(); 79 | AstRef parse_distinct_type(); 80 | 81 | // Misc parsers 82 | AttributeList parse_attributes(); 83 | DirectiveList parse_directives(); 84 | 85 | [[nodiscard]] THOR_FORCEINLINE constexpr AstFile& ast() { return ast_; } 86 | [[nodiscard]] THOR_FORCEINLINE constexpr const AstFile& ast() const { return ast_; } 87 | private: 88 | AstRef parse_unary_atom(AstRef operand, Bool lhs); 89 | AstRef parse_field(Bool allow_assignment); 90 | 91 | AstRef parse_directive(); 92 | 93 | Parser(System& sys, Lexer&& lexer, AstFile&& ast); 94 | 95 | template 96 | Unit error(Uint32 offset, const char (&msg)[E], Ts&&...) { 97 | ScratchAllocator<1024> scratch{sys_.allocator}; 98 | StringBuilder builder{scratch}; 99 | auto position = lexer_.position(offset); 100 | builder.put(ast_.filename()); 101 | builder.put(':'); 102 | builder.put(position.line); 103 | builder.put(':'); 104 | builder.put(position.column); 105 | builder.put(':'); 106 | builder.put(' '); 107 | builder.put("error"); 108 | builder.put(':'); 109 | builder.put(' '); 110 | builder.put(StringView { msg }); 111 | builder.put('\n'); 112 | if (auto result = builder.result()) { 113 | sys_.console.write(sys_, *result); 114 | } else { 115 | sys_.console.write(sys_, StringView { "Out of memory" }); 116 | } 117 | return {}; 118 | } 119 | template 120 | Unit error(const char (&msg)[E], Ts&&... args) { 121 | return error(token_.offset, msg, forward(args)...); 122 | } 123 | THOR_FORCEINLINE constexpr Bool is_kind(TokenKind kind) const { 124 | return token_.kind == kind; 125 | } 126 | THOR_FORCEINLINE constexpr Bool is_semi() const { 127 | return is_kind(TokenKind::EXPLICITSEMI) || is_kind(TokenKind::IMPLICITSEMI); 128 | } 129 | THOR_FORCEINLINE constexpr Bool is_keyword(KeywordKind kind) const { 130 | return is_kind(TokenKind::KEYWORD) && token_.as_keyword == kind; 131 | } 132 | THOR_FORCEINLINE constexpr Bool is_operator(OperatorKind kind) const { 133 | return is_kind(TokenKind::OPERATOR) && token_.as_operator == kind; 134 | } 135 | THOR_FORCEINLINE constexpr Bool is_literal(LiteralKind kind) const { 136 | return is_kind(TokenKind::LITERAL) && token_.as_literal == kind; 137 | } 138 | THOR_FORCEINLINE constexpr Bool is_assignment(AssignKind kind) const { 139 | return is_kind(TokenKind::ASSIGNMENT) && token_.as_assign == kind; 140 | } 141 | 142 | // Eat the current token, advancing the lexer and return the byte position of 143 | // the previous token. 144 | Uint32 eat(); 145 | 146 | System& sys_; 147 | TemporaryAllocator temporary_; 148 | AstFile ast_; 149 | Lexer lexer_; 150 | Token token_; 151 | // >= 0: In Expression 152 | // < 0: In Control Clause 153 | Sint32 expr_level_ = 0; 154 | Bool allow_in_expr_ = false; 155 | }; 156 | 157 | } // namespace Thor 158 | 159 | #endif // THOR_PARSER_H 160 | -------------------------------------------------------------------------------- /src/system_posix.cpp: -------------------------------------------------------------------------------- 1 | #include "util/info.h" 2 | #include "util/assert.h" 3 | 4 | #if defined(THOR_HOST_PLATFORM_POSIX) 5 | 6 | // Implementation of the System for POSIX systems 7 | #include // fstat, struct stat 8 | #include // PROT_READ, PROT_WRITE, MAP_PRIVATE, MAP_ANONYMOUS 9 | #include // open, close, pread, pwrite 10 | #include // O_CLOEXEC, O_RDONLY, O_WRONLY 11 | #include // opendir, readdir, closedir 12 | #include // strlen 13 | #include // dlopen, dlclose, dlsym, RTLD_NOW 14 | #include // printf 15 | #include // pthread_create, pthread_join, pthread_sigblock 16 | #include // sigset, sigfillset 17 | #include // exit needed from libc since it calls destructors 18 | 19 | #include "util/system.h" 20 | 21 | namespace Thor { 22 | 23 | static Filesystem::File* filesystem_open_file(System& sys, StringView name, Filesystem::Access access) { 24 | int flags = O_CLOEXEC; 25 | switch (access) { 26 | case Filesystem::Access::RD: 27 | flags |= O_RDONLY; 28 | break; 29 | case Filesystem::Access::WR: 30 | flags |= O_WRONLY; 31 | flags |= O_CREAT; 32 | flags |= O_TRUNC; 33 | break; 34 | } 35 | ScratchAllocator<1024> scratch{sys.allocator}; 36 | auto path = scratch.allocate(name.length() + 1, false); 37 | if (!path) { 38 | // Out of memory. 39 | return nullptr; 40 | } 41 | memcpy(path, name.data(), name.length()); 42 | path[name.length()] = '\0'; 43 | auto fd = open(path, flags, 0666); 44 | if (fd < 0) { 45 | return nullptr; 46 | } 47 | return reinterpret_cast(fd); 48 | } 49 | 50 | static void filesystem_close_file(System&, Filesystem::File* file) { 51 | close(reinterpret_cast
(file)); 52 | } 53 | 54 | static Uint64 filesystem_read_file(System&, Filesystem::File* file, Uint64 offset, Slice data) { 55 | auto fd = reinterpret_cast
(file); 56 | auto v = pread(fd, data.data(), data.length(), offset); 57 | if (v < 0) { 58 | return 0; 59 | } 60 | return Uint64(v); 61 | } 62 | 63 | static Uint64 filesystem_write_file(System&, Filesystem::File* file, Uint64 offset, Slice data) { 64 | auto fd = reinterpret_cast
(file); 65 | auto v = pwrite(fd, data.data(), data.length(), offset); 66 | if (v < 0) { 67 | return 0; 68 | } 69 | return Uint64(v); 70 | } 71 | 72 | static Uint64 filesystem_tell_file(System&, Filesystem::File* file) { 73 | auto fd = reinterpret_cast
(file); 74 | struct stat buf; 75 | if (fstat(fd, &buf) != -1) { 76 | return Uint64(buf.st_size); 77 | } 78 | return 0; 79 | } 80 | 81 | Filesystem::Directory* filesystem_open_dir(System& sys, StringView name) { 82 | ScratchAllocator<1024> scratch{sys.allocator}; 83 | auto path = scratch.allocate(name.length() + 1, false); 84 | if (!path) { 85 | return nullptr; 86 | } 87 | memcpy(path, name.data(), name.length()); 88 | path[name.length()] = '\0'; 89 | auto dir = opendir(path); 90 | return reinterpret_cast(dir); 91 | } 92 | 93 | void filesystem_close_dir(System&, Filesystem::Directory* handle) { 94 | auto dir = reinterpret_cast(handle); 95 | closedir(dir); 96 | } 97 | 98 | Bool filesystem_read_dir(System&, Filesystem::Directory* handle, Filesystem::Item& item) { 99 | auto dir = reinterpret_cast(handle); 100 | auto next = readdir(dir); 101 | using Kind = Filesystem::Item::Kind; 102 | while (next) { 103 | // Skip '.' and '..' 104 | while (next && next->d_name[0] == '.' && !(next->d_name[1 + (next->d_name[1] == '.')])) { 105 | next = readdir(dir); 106 | } 107 | if (!next) { 108 | return false; 109 | } 110 | switch (next->d_type) { 111 | case DT_LNK: 112 | item = { StringView { next->d_name, strlen(next->d_name) }, Kind::LINK }; 113 | return true; 114 | case DT_DIR: 115 | item = { StringView { next->d_name, strlen(next->d_name) }, Kind::DIR }; 116 | return true; 117 | case DT_REG: 118 | item = { StringView { next->d_name, strlen(next->d_name) }, Kind::FILE }; 119 | return true; 120 | } 121 | next = readdir(dir); 122 | } 123 | return false; 124 | } 125 | 126 | extern const Filesystem STD_FILESYSTEM = { 127 | .open_file = filesystem_open_file, 128 | .close_file = filesystem_close_file, 129 | .read_file = filesystem_read_file, 130 | .write_file = filesystem_write_file, 131 | .tell_file = filesystem_tell_file, 132 | .open_dir = filesystem_open_dir, 133 | .close_dir = filesystem_close_dir, 134 | .read_dir = filesystem_read_dir, 135 | }; 136 | 137 | static void* heap_allocate(System&, Ulen length, [[maybe_unused]] Bool zero) { 138 | #if defined(THOR_CFG_USE_MALLOC) 139 | return zero ? calloc(length, 1) : malloc(length); 140 | #else 141 | auto addr = mmap(nullptr, 142 | length, 143 | PROT_WRITE, 144 | MAP_PRIVATE | MAP_ANONYMOUS, 145 | -1, 146 | 0); 147 | if (addr == MAP_FAILED) { 148 | return nullptr; 149 | } 150 | return addr; 151 | #endif 152 | } 153 | 154 | static void heap_deallocate(System&, void *addr, [[maybe_unused]] Ulen length) { 155 | #if defined(THOR_CFG_USE_MALLOC) 156 | free(addr); 157 | #else 158 | munmap(addr, length); 159 | #endif 160 | } 161 | 162 | extern const Heap STD_HEAP = { 163 | .allocate = heap_allocate, 164 | .deallocate = heap_deallocate, 165 | }; 166 | 167 | static void console_write(System&, StringView data) { 168 | write(1, data.data(), data.length()); 169 | } 170 | 171 | extern const Console STD_CONSOLE = { 172 | .write = console_write, 173 | }; 174 | 175 | static void process_assert(System& sys, StringView msg, StringView file, Sint32 line) { 176 | InlineAllocator<4096> data; 177 | StringBuilder builder{data}; 178 | builder.put(file); 179 | builder.put(':'); 180 | builder.put(line); 181 | builder.put(' '); 182 | builder.put("Assertion failure"); 183 | builder.put(':'); 184 | builder.put(' '); 185 | builder.put(msg); 186 | builder.put('\n'); 187 | if (auto result = builder.result()) { 188 | sys.console.write(sys, *result); 189 | } else { 190 | sys.console.write(sys, StringView { "Out of memory" }); 191 | } 192 | exit(3); 193 | } 194 | 195 | extern const Process STD_PROCESS = { 196 | .assert = process_assert, 197 | }; 198 | 199 | static Linker::Library* linker_load(System&, StringView name) { 200 | // Search for the librart next to the executable first. 201 | InlineAllocator<1024> buf; 202 | StringBuilder path{buf}; 203 | path.put("./"); 204 | path.put(name); 205 | path.put('.'); 206 | path.put("so"); 207 | path.put('\0'); 208 | auto result = path.result(); 209 | if (!result) { 210 | return nullptr; 211 | } 212 | if (auto lib = dlopen(result->data(), RTLD_NOW)) { 213 | return static_cast(lib); 214 | } 215 | // Skip the "./" in result to try the system path now. 216 | if (auto lib = dlopen(result->data() + 2, RTLD_NOW)) { 217 | return static_cast(lib); 218 | } 219 | return nullptr; 220 | } 221 | 222 | static void linker_close(System&, Linker::Library* lib) { 223 | dlclose(static_cast(lib)); 224 | } 225 | 226 | static void (*linker_link(System&, Linker::Library* lib, const char* sym))(void) { 227 | if (auto addr = dlsym(static_cast(lib), sym)) { 228 | return reinterpret_cast(addr); 229 | } 230 | return nullptr; 231 | } 232 | 233 | extern const Linker STD_LINKER = { 234 | .load = linker_load, 235 | .close = linker_close, 236 | .link = linker_link 237 | }; 238 | 239 | struct SystemThread { 240 | SystemThread(System& sys, void (*fn)(System&, void*), void* user) 241 | : sys{sys} 242 | , fn{fn} 243 | , user{user} 244 | { 245 | } 246 | System& sys; 247 | void (*fn)(System& sys, void* user); 248 | void* user; 249 | pthread_t handle; 250 | }; 251 | 252 | struct Mutex { 253 | pthread_mutex_t handle; 254 | }; 255 | 256 | struct Cond { 257 | pthread_cond_t handle; 258 | }; 259 | 260 | static void* scheduler_thread_proc(void* user) { 261 | auto thread = reinterpret_cast(user); 262 | thread->fn(thread->sys, thread->user); 263 | return nullptr; 264 | } 265 | 266 | static Scheduler::Thread* scheduler_thread_start(System& sys, void (*fn)(System& sys, void* user), void* user) { 267 | auto thread = sys.allocator.create(sys, fn, user); 268 | if (!thread) { 269 | return nullptr; 270 | } 271 | 272 | // When constructing the thread we need to block all signals. Once the thread 273 | // is constructed it will inherit our signal mask. We do this because we don't 274 | // want any of our threads to be delivered signals as they're a source of many 275 | // dataraces and deadlocks. Many applications do this wrong and block signals 276 | // at the start of the thread but this has a datarace where a signal can still 277 | // be delivered to the thread after it's constructed but before the thread has 278 | // executed the code to block signals. 279 | sigset_t nset; 280 | sigset_t oset; 281 | sigfillset(&nset); 282 | if (pthread_sigmask(SIG_SETMASK, &nset, &oset) != 0) { 283 | return nullptr; 284 | } 285 | 286 | // The thread can now be constructed and during construction it will inherit 287 | // the signal mask set in this process, which in this case is one that blocks 288 | // all signals. 289 | if (pthread_create(&thread->handle, nullptr, scheduler_thread_proc, thread) != 0) { 290 | // Restore the previous signal mask. This cannot fail since [oset] is derived 291 | // from the previous (valid) signal mask state. 292 | pthread_sigmask(SIG_SETMASK, &oset, nullptr); 293 | sys.allocator.destroy(thread); 294 | return nullptr; 295 | } 296 | 297 | // Restore the previous signal mask. This cannot fail since [oset] is derived 298 | // from the previous (valid) signal mask state. 299 | pthread_sigmask(SIG_SETMASK, &oset, nullptr); 300 | 301 | return reinterpret_cast(thread); 302 | } 303 | 304 | static void scheduler_thread_join(System& sys, Scheduler::Thread* thr) { 305 | auto thread = reinterpret_cast(thr); 306 | THOR_ASSERT(sys, &thread->sys == &sys); 307 | pthread_join(thread->handle, nullptr); 308 | sys.allocator.destroy(thread); 309 | } 310 | 311 | static Scheduler::Mutex* scheduler_mutex_create(System& sys) { 312 | // There is no specified default type on POSIX. While most should default to 313 | // non-recursive (i.e normal) mutexes, we cannot be sure and so to ensure we 314 | // only get non-recursive mutexes just be explicit and request it. 315 | pthread_mutexattr_t attr; 316 | if (pthread_mutexattr_init(&attr) != 0) { 317 | return nullptr; 318 | } 319 | if (pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL) != 0) { 320 | pthread_mutexattr_destroy(&attr); 321 | return nullptr; 322 | } 323 | 324 | auto mutex = sys.allocator.create(); 325 | if (!mutex) { 326 | pthread_mutexattr_destroy(&attr); 327 | return nullptr; 328 | } 329 | 330 | if (pthread_mutex_init(&mutex->handle, &attr) != 0) { 331 | sys.allocator.destroy(mutex); 332 | pthread_mutexattr_destroy(&attr); 333 | return nullptr; 334 | } 335 | 336 | pthread_mutexattr_destroy(&attr); 337 | 338 | return reinterpret_cast(mutex); 339 | } 340 | 341 | static void scheduler_mutex_destroy([[maybe_unused]] System& sys, Scheduler::Mutex* m) { 342 | auto mutex = reinterpret_cast(m); 343 | if (pthread_mutex_destroy(&mutex->handle) != 0) { 344 | THOR_ASSERT(sys, !"Could not destroy mutex"); 345 | } 346 | sys.allocator.destroy(mutex); 347 | } 348 | 349 | 350 | static void scheduler_mutex_lock([[maybe_unused]] System& sys, Scheduler::Mutex* m) { 351 | auto mutex = reinterpret_cast(m); 352 | if (pthread_mutex_lock(&mutex->handle) != 0) { 353 | THOR_ASSERT(sys, !"Could not lock mutex"); 354 | } 355 | } 356 | 357 | static void scheduler_mutex_unlock([[maybe_unused]] System& sys, Scheduler::Mutex* m) { 358 | auto mutex = reinterpret_cast(m); 359 | if (pthread_mutex_unlock(&mutex->handle) != 0) { 360 | THOR_ASSERT(sys, !"Could not unlock mutex"); 361 | } 362 | } 363 | 364 | static Scheduler::Cond* scheduler_cond_create(System& sys) { 365 | auto cond = sys.allocator.create(); 366 | if (!cond) { 367 | return nullptr; 368 | } 369 | if (pthread_cond_init(&cond->handle, nullptr) != 0) { 370 | sys.allocator.destroy(cond); 371 | return nullptr; 372 | } 373 | return reinterpret_cast(cond); 374 | } 375 | 376 | static void scheduler_cond_destroy([[maybe_unused]] System& sys, Scheduler::Cond* c) { 377 | auto cond = reinterpret_cast(c); 378 | if (pthread_cond_destroy(&cond->handle) != 0) { 379 | THOR_ASSERT(sys, !"Could not destroy condition variable"); 380 | } 381 | sys.allocator.destroy(cond); 382 | } 383 | 384 | static void scheduler_cond_signal([[maybe_unused]] System& sys, Scheduler::Cond* c) { 385 | auto cond = reinterpret_cast(c); 386 | if (pthread_cond_signal(&cond->handle) != 0) { 387 | THOR_ASSERT(sys, !"Could not signal condition variable"); 388 | } 389 | } 390 | 391 | static void scheduler_cond_broadcast([[maybe_unused]] System& sys, Scheduler::Cond* c) { 392 | auto cond = reinterpret_cast(c); 393 | if (pthread_cond_broadcast(&cond->handle) != 0) { 394 | THOR_ASSERT(sys, !"Could not broadcast condition variable"); 395 | } 396 | } 397 | 398 | static void scheduler_cond_wait([[maybe_unused]] System& sys, Scheduler::Cond* c, Scheduler::Mutex* m) { 399 | auto cond = reinterpret_cast(c); 400 | auto mutex = reinterpret_cast(m); 401 | if (pthread_cond_wait(&cond->handle, &mutex->handle) != 0) { 402 | THOR_ASSERT(sys, !"Could not wait condition variable"); 403 | } 404 | } 405 | 406 | static void scheduler_yield(System&) { 407 | sched_yield(); 408 | } 409 | 410 | extern const Scheduler STD_SCHEDULER = { 411 | .thread_start = scheduler_thread_start, 412 | .thread_join = scheduler_thread_join, 413 | 414 | .mutex_create = scheduler_mutex_create, 415 | .mutex_destroy = scheduler_mutex_destroy, 416 | .mutex_lock = scheduler_mutex_lock, 417 | .mutex_unlock = scheduler_mutex_unlock, 418 | 419 | .cond_create = scheduler_cond_create, 420 | .cond_destroy = scheduler_cond_destroy, 421 | .cond_signal = scheduler_cond_signal, 422 | .cond_broadcast = scheduler_cond_broadcast, 423 | .cond_wait = scheduler_cond_wait, 424 | 425 | .yield = scheduler_yield, 426 | }; 427 | 428 | static Float64 chrono_monotonic_now(System&) { 429 | struct timespec ts{}; 430 | clock_gettime(CLOCK_MONOTONIC, &ts); 431 | return static_cast(ts.tv_sec) + ts.tv_nsec / 1.0e9; 432 | } 433 | 434 | static Float64 chrono_wall_now(System&) { 435 | struct timespec ts{}; 436 | clock_gettime(CLOCK_REALTIME, &ts); 437 | return static_cast(ts.tv_sec) + ts.tv_nsec / 1.0e9; 438 | } 439 | 440 | extern const Chrono STD_CHRONO = { 441 | .monotonic_now = chrono_monotonic_now, 442 | .wall_now = chrono_wall_now, 443 | }; 444 | 445 | } // namespace Thor 446 | 447 | #endif // THOR_HOST_PLATFORM_POSIX -------------------------------------------------------------------------------- /src/system_windows.cpp: -------------------------------------------------------------------------------- 1 | #include "util/info.h" 2 | #include "util/atomic.h" 3 | 4 | // Implementation of the System for Windows systems 5 | #if defined(THOR_HOST_PLATFORM_WINDOWS) 6 | 7 | #define WIN32_LEAN_AND_MEAN 8 | #include 9 | #include 10 | 11 | #if defined(THOR_CFG_USE_MALLOC) 12 | #include 13 | #endif 14 | 15 | namespace Thor { 16 | 17 | static Ulen convert_utf8_to_utf16(Slice utf8, Slice utf16) { 18 | Uint32 cp = 0; 19 | Ulen len = 0; 20 | auto out = utf16.data(); 21 | for (Ulen i = 0; i < utf8.length(); i++) { 22 | auto element = &utf8[i]; 23 | auto ch = *element; 24 | if (ch <= 0x7f) { 25 | cp = ch; 26 | } else if (ch <= 0xbf) { 27 | cp = (cp << 6) | (ch & 0x3f); 28 | } else if (ch <= 0xdf) { 29 | cp = ch & 0x1f; 30 | } else if (ch <= 0xef) { 31 | cp = ch & 0x0f; 32 | } else { 33 | cp = ch & 0x07; 34 | } 35 | element++; 36 | if ((*element & 0xc0) != 0x80 && cp <= 0x10ffff) { 37 | if (cp > 0xffff) { 38 | len += 2; 39 | if (out) { 40 | *out++ = static_cast(0xd800 | (cp >> 10)); 41 | *out++ = static_cast(0xdc00 | (cp & 0x03ff)); 42 | } 43 | } else if (cp < 0xd800 || cp >= 0xe000) { 44 | len += 1; 45 | if (out) { 46 | *out++ = static_cast(cp); 47 | } 48 | } 49 | } 50 | } 51 | return len; 52 | } 53 | 54 | static Ulen convert_utf16_to_utf8(Slice utf16, Slice utf8) { 55 | Uint32 cp = 0; 56 | Ulen len = 0; 57 | auto out = utf8.data(); 58 | for (Ulen i = 0; i < utf16.length(); i++) { 59 | auto element = &utf16[i]; 60 | auto ch = *element; 61 | if (ch >= 0xd800 && ch <= 0xdbff) { 62 | cp = ((ch - 0xd800) << 10) | 0x10000; 63 | } else { 64 | if (ch >= 0xdc00 && ch <= 0xdfff) { 65 | cp |= ch - 0xdc00; 66 | } else { 67 | cp = ch; 68 | } 69 | if (cp < 0x7f) { 70 | len += 1; 71 | if (out) { 72 | *out++ = static_cast(cp); 73 | } 74 | } else if (cp < 0x7ff) { 75 | len += 2; 76 | if (out) { 77 | *out++ = static_cast(0xc0 | ((cp >> 6) & 0x1f)); 78 | *out++ = static_cast(0x80 | (cp & 0x3f)); 79 | } 80 | } else if (cp < 0xffff) { 81 | len += 3; 82 | if (out) { 83 | *out++ = static_cast(0xe0 | ((cp >> 12) & 0x0f)); 84 | *out++ = static_cast(0x80 | ((cp >> 6) & 0x3f)); 85 | *out++ = static_cast(0x80 | (cp & 0x3f)); 86 | } 87 | } else { 88 | len += 4; 89 | if (out) { 90 | *out++ = static_cast(0xf0 | ((cp >> 18) & 0x07)); 91 | *out++ = static_cast(0x80 | ((cp >> 12) & 0x3f)); 92 | *out++ = static_cast(0x80 | ((cp >> 6) & 0x3f)); 93 | *out++ = static_cast(0x80 | (cp & 0x3f)); 94 | } 95 | } 96 | cp = 0; 97 | } 98 | } 99 | return len; 100 | } 101 | 102 | // We need a mechanism to convert between UTF-8 and UTF-16 here, using only the 103 | // provided allocator. The rest of this code is untested. 104 | static Slice utf8_to_utf16(Allocator& allocator, Slice utf8) { 105 | const auto len = convert_utf8_to_utf16(utf8, {}); 106 | const auto data = allocator.allocate(len + 1, false); 107 | if (!data) { 108 | return {}; 109 | } 110 | Slice utf16{ data, len }; 111 | convert_utf8_to_utf16(utf8, utf16); 112 | data[len] = 0; 113 | return utf16; 114 | } 115 | static Slice utf16_to_utf8(Allocator& allocator, Slice utf16) { 116 | const auto len = convert_utf16_to_utf8(utf16, {}); 117 | const auto data = allocator.allocate(len + 1, false); 118 | if (!data) { 119 | return {}; 120 | } 121 | Slice utf8{ data, len }; 122 | convert_utf16_to_utf8(utf16, utf8); 123 | data[len] = 0; 124 | return utf8; 125 | } 126 | 127 | static Filesystem::File* filesystem_open_file(System& sys, StringView name, Filesystem::Access access) { 128 | ScratchAllocator<1024> scratch{sys.allocator}; 129 | auto filename = utf8_to_utf16(scratch, name.cast()); 130 | if (filename.is_empty()) { 131 | return nullptr; 132 | } 133 | DWORD dwDesiredAccess = 0; 134 | DWORD dwShareMode = 0; 135 | DWORD dwCreationDisposition = 0; 136 | DWORD dwFlagsAndAttributes = FILE_ATTRIBUTE_NORMAL; 137 | switch (access) { 138 | case Filesystem::Access::RD: 139 | dwDesiredAccess |= GENERIC_READ; 140 | dwShareMode |= FILE_SHARE_READ; 141 | dwCreationDisposition = OPEN_EXISTING; 142 | break; 143 | case Filesystem::Access::WR: 144 | dwDesiredAccess |= GENERIC_WRITE; 145 | dwShareMode |= FILE_SHARE_WRITE; 146 | dwCreationDisposition = CREATE_ALWAYS; 147 | break; 148 | } 149 | auto handle = CreateFileW(reinterpret_cast(filename.data()), 150 | dwDesiredAccess, 151 | dwShareMode, 152 | nullptr, 153 | dwCreationDisposition, 154 | dwFlagsAndAttributes, 155 | nullptr); 156 | if (handle == INVALID_HANDLE_VALUE) { 157 | return nullptr; 158 | } 159 | return reinterpret_cast(handle); 160 | } 161 | 162 | static void filesystem_close_file(System&, Filesystem::File* file) { 163 | CloseHandle(reinterpret_cast(file)); 164 | } 165 | 166 | static Uint64 filesystem_read_file(System&, Filesystem::File* file, Uint64 offset, Slice data) { 167 | OVERLAPPED overlapped{}; 168 | overlapped.OffsetHigh = static_cast((offset & 0xffffffff00000000_u64) >> 32); 169 | overlapped.Offset = static_cast((offset & 0x00000000ffffffff_u64)); 170 | DWORD rd = 0; 171 | auto result = ReadFile(reinterpret_cast(file), 172 | data.data(), 173 | static_cast(min(data.length(), 0xffffffff_ulen)), 174 | &rd, 175 | &overlapped); 176 | if (!result && GetLastError() != ERROR_HANDLE_EOF) { 177 | return 0; 178 | } 179 | return static_cast(rd); 180 | } 181 | 182 | static Uint64 filesystem_write_file(System&, Filesystem::File* file, Uint64 offset, Slice data) { 183 | OVERLAPPED overlapped{}; 184 | overlapped.OffsetHigh = static_cast((offset & 0xffffffff00000000_u64) >> 32); 185 | overlapped.Offset = static_cast((offset & 0x00000000ffffffff_u64)); 186 | DWORD wr = 0; 187 | auto result = WriteFile(reinterpret_cast(file), 188 | data.data(), 189 | static_cast(min(data.length(), 0xffffffff_ulen)), 190 | &wr, 191 | &overlapped); 192 | return static_cast(wr); 193 | } 194 | 195 | static Uint64 filesystem_tell_file(System&, Filesystem::File* file) { 196 | auto handle = reinterpret_cast(file); 197 | BY_HANDLE_FILE_INFORMATION info; 198 | if (GetFileInformationByHandle(file, &info)) { 199 | return (static_cast(info.nFileSizeHigh) << 32) | info.nFileSizeLow; 200 | } 201 | return 0; 202 | } 203 | 204 | struct FindData { 205 | FindData(Allocator& allocator) 206 | : allocator{allocator} 207 | , handle{INVALID_HANDLE_VALUE} 208 | { 209 | } 210 | ~FindData() { 211 | if (handle != INVALID_HANDLE_VALUE) { 212 | FindClose(handle); 213 | } 214 | } 215 | ScratchAllocator<4096> allocator; 216 | HANDLE handle; 217 | WIN32_FIND_DATAW data; 218 | }; 219 | 220 | Filesystem::Directory* filesystem_open_dir(System& sys, StringView name) { 221 | auto find = sys.allocator.create(sys.allocator); 222 | if (!find) { 223 | return nullptr; 224 | } 225 | auto path = utf8_to_utf16(find->allocator, name.cast()); 226 | if (path.is_empty()) { 227 | sys.allocator.destroy(find); 228 | return nullptr; 229 | } 230 | auto handle = FindFirstFileW(reinterpret_cast(path.data()), 231 | &find->data); 232 | if (handle != INVALID_HANDLE_VALUE) { 233 | find->handle = handle; 234 | return reinterpret_cast(find); 235 | } 236 | sys.allocator.destroy(find); 237 | return nullptr; 238 | } 239 | 240 | void filesystem_close_dir(System& sys, Filesystem::Directory* handle) { 241 | auto find = reinterpret_cast(handle); 242 | sys.allocator.destroy(find); 243 | } 244 | 245 | Bool filesystem_read_dir(System&, Filesystem::Directory* handle, Filesystem::Item& item) { 246 | auto find = reinterpret_cast(handle); 247 | // Skip '.' and '..' 248 | if (find->data.cFileName[0] == L'.' && 249 | find->data.cFileName[1 + !!(find->data.cFileName[1] == L'.')]) 250 | { 251 | if (!FindNextFileW(find->handle, &find->data)) { 252 | return false; 253 | } 254 | } 255 | Slice utf16 { 256 | reinterpret_cast(find->data.cFileName), 257 | wcslen(find->data.cFileName) 258 | }; 259 | auto utf8 = utf16_to_utf8(find->allocator, utf16); 260 | if (utf8.is_empty()) { 261 | return false; 262 | } 263 | if (find->data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { 264 | item = { utf8.cast(), Filesystem::Item::Kind::DIR }; 265 | } else { 266 | item = { utf8.cast(), Filesystem::Item::Kind::FILE }; 267 | } 268 | return true; 269 | } 270 | 271 | extern const Filesystem STD_FILESYSTEM = { 272 | .open_file = filesystem_open_file, 273 | .close_file = filesystem_close_file, 274 | .read_file = filesystem_read_file, 275 | .write_file = filesystem_write_file, 276 | .tell_file = filesystem_tell_file, 277 | .open_dir = filesystem_open_dir, 278 | .close_dir = filesystem_close_dir, 279 | .read_dir = filesystem_read_dir, 280 | }; 281 | 282 | static void* heap_allocate(System&, Ulen length, [[maybe_unused]] Bool zero) { 283 | #if defined(THOR_CFG_USE_MALLOC) 284 | return zero ? calloc(length, 1) : malloc(length); 285 | #else 286 | return VirtualAlloc(nullptr, length, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); 287 | #endif 288 | } 289 | 290 | static void heap_deallocate(System&, void *address, [[maybe_unused]] Ulen length) { 291 | #if defined(THOR_CFG_USE_MALLOC) 292 | free(address); 293 | #else 294 | VirtualFree(address, length, MEM_RELEASE); 295 | #endif 296 | } 297 | 298 | extern const Heap STD_HEAP = { 299 | .allocate = heap_allocate, 300 | .deallocate = heap_deallocate, 301 | }; 302 | 303 | static void console_write(System&, StringView data) { 304 | auto handle = GetStdHandle(STD_OUTPUT_HANDLE); 305 | WriteConsoleA(handle, data.data(), data.length(), nullptr, nullptr); 306 | } 307 | 308 | extern const Console STD_CONSOLE = { 309 | .write = console_write, 310 | }; 311 | 312 | static void process_assert(System& sys, StringView msg, StringView file, Sint32 line) { 313 | InlineAllocator<4096> data; 314 | StringBuilder builder{data}; 315 | builder.put(file); 316 | builder.put(':'); 317 | builder.put(line); 318 | builder.put(' '); 319 | builder.put("Assertion failure"); 320 | builder.put(':'); 321 | builder.put(' '); 322 | builder.put(msg); 323 | builder.put('\n'); 324 | if (auto result = builder.result()) { 325 | sys.console.write(sys, *result); 326 | } else { 327 | sys.console.write(sys, StringView { "Out of memory" }); 328 | } 329 | ExitProcess(3); 330 | } 331 | 332 | extern const Process STD_PROCESS = { 333 | .assert = process_assert, 334 | }; 335 | 336 | static Linker::Library* linker_load(System&, StringView name) { 337 | InlineAllocator<1024> buf; 338 | StringBuilder path{buf}; 339 | path.put(name); 340 | path.put('.'); 341 | path.put("dll"); 342 | path.put('\0'); 343 | auto result = path.result(); 344 | if (!result) { 345 | return nullptr; 346 | } 347 | if (auto lib = LoadLibraryA(result->data())) { 348 | return reinterpret_cast(lib); 349 | } 350 | return nullptr; 351 | } 352 | 353 | static void linker_close(System&, Linker::Library* lib) { 354 | FreeLibrary(reinterpret_cast(lib)); 355 | } 356 | 357 | static void (*linker_link(System&, Linker::Library* lib, const char* sym))(void) { 358 | if (auto addr = GetProcAddress(reinterpret_cast(lib), sym)) { 359 | return reinterpret_cast(addr); 360 | } 361 | return nullptr; 362 | } 363 | 364 | extern const Linker STD_LINKER = { 365 | .load = linker_load, 366 | .close = linker_close, 367 | .link = linker_link 368 | }; 369 | 370 | struct SystemThread { 371 | SystemThread(System& sys, void (*fn)(System&, void*), void* user) 372 | : sys{sys} 373 | , fn{fn} 374 | , user{user} 375 | { 376 | } 377 | System& sys; 378 | void (*fn)(System& sys, void* user); 379 | void* user; 380 | HANDLE handle; 381 | }; 382 | 383 | struct Mutex { 384 | SRWLOCK handle; 385 | }; 386 | 387 | struct Cond { 388 | CONDITION_VARIABLE handle; 389 | }; 390 | 391 | static DWORD WINAPI scheduler_thread_proc(void* user) { 392 | auto thread = reinterpret_cast(user); 393 | thread->fn(thread->sys, thread->user); 394 | return 0; 395 | } 396 | 397 | static Scheduler::Thread* scheduler_thread_start(System& sys, void (*fn)(System& sys, void* user), void* user) { 398 | auto thread = sys.allocator.create(sys, fn, user); 399 | if (!thread) { 400 | return nullptr; 401 | } 402 | thread->handle = CreateThread(nullptr, 403 | 0, 404 | scheduler_thread_proc, 405 | nullptr, 406 | 0, 407 | nullptr); 408 | if (thread->handle == INVALID_HANDLE_VALUE) { 409 | sys.allocator.destroy(thread); 410 | return nullptr; 411 | } 412 | return reinterpret_cast(thread); 413 | } 414 | 415 | static void scheduler_thread_join(System& sys, Scheduler::Thread* t) { 416 | auto thread = reinterpret_cast(t); 417 | THOR_ASSERT(sys, &sys == &thread->sys); 418 | WaitForSingleObject(thread->handle, INFINITE); 419 | sys.allocator.destroy(thread); 420 | } 421 | 422 | static Scheduler::Mutex* scheduler_mutex_create(System& sys) { 423 | auto mutex = sys.allocator.create(); 424 | if (!mutex) { 425 | return nullptr; 426 | } 427 | InitializeSRWLock(&mutex->handle); 428 | return reinterpret_cast(mutex); 429 | } 430 | 431 | static void scheduler_mutex_destroy([[maybe_unused]] System& sys, Scheduler::Mutex* m) { 432 | auto mutex = reinterpret_cast(m); 433 | sys.allocator.destroy(mutex); 434 | } 435 | 436 | static void scheduler_mutex_lock([[maybe_unused]] System& sys, Scheduler::Mutex* m) { 437 | auto mutex = reinterpret_cast(m); 438 | AcquireSRWLockExclusive(&mutex->handle); 439 | } 440 | 441 | static void scheduler_mutex_unlock([[maybe_unused]] System& sys, Scheduler::Mutex* m) { 442 | auto mutex = reinterpret_cast(m); 443 | ReleaseSRWLockExclusive(&mutex->handle); 444 | } 445 | 446 | static Scheduler::Cond* scheduler_cond_create(System& sys) { 447 | auto cond = sys.allocator.create(); 448 | if (!cond) { 449 | return nullptr; 450 | } 451 | InitializeConditionVariable(&cond->handle); 452 | return reinterpret_cast(cond); 453 | } 454 | 455 | static void scheduler_cond_destroy([[maybe_unused]] System& sys, Scheduler::Cond* c) { 456 | auto cond = reinterpret_cast(c); 457 | sys.allocator.destroy(cond); 458 | } 459 | 460 | static void scheduler_cond_signal([[maybe_unused]] System& sys, Scheduler::Cond* c) { 461 | auto cond = reinterpret_cast(c); 462 | WakeConditionVariable(&cond->handle); 463 | } 464 | 465 | static void scheduler_cond_broadcast([[maybe_unused]] System& sys, Scheduler::Cond* c) { 466 | auto cond = reinterpret_cast(c); 467 | WakeAllConditionVariable(&cond->handle); 468 | } 469 | 470 | static void scheduler_cond_wait([[maybe_unused]] System& sys, Scheduler::Cond* c, Scheduler::Mutex* m) { 471 | auto cond = reinterpret_cast(c); 472 | auto mutex = reinterpret_cast(m); 473 | SleepConditionVariableSRW(&cond->handle, &mutex->handle, INFINITE, 0); 474 | } 475 | 476 | static void scheduler_yield(System&) { 477 | SwitchToThread(); 478 | } 479 | 480 | extern const Scheduler STD_SCHEDULER = { 481 | .thread_start = scheduler_thread_start, 482 | .thread_join = scheduler_thread_join, 483 | 484 | .mutex_create = scheduler_mutex_create, 485 | .mutex_destroy = scheduler_mutex_destroy, 486 | .mutex_lock = scheduler_mutex_lock, 487 | .mutex_unlock = scheduler_mutex_unlock, 488 | 489 | .cond_create = scheduler_cond_create, 490 | .cond_destroy = scheduler_cond_destroy, 491 | .cond_signal = scheduler_cond_signal, 492 | .cond_broadcast = scheduler_cond_broadcast, 493 | .cond_wait = scheduler_cond_wait, 494 | 495 | .yield = scheduler_yield 496 | }; 497 | 498 | // Windows is rife with various timing-related discrepancies and historical bugs 499 | // which make it diffcult to provide robust monotonic and wall time. 500 | // 501 | // Read more information here: 502 | // https://learn.microsoft.com/en-us/windows/win32/sysinfo/acquiring-high-resolution-time-stamps 503 | // https://learn.microsoft.com/en-us/troubleshoot/windows-server/performance/programs-queryperformancecounter-function-perform-poorly 504 | // https://learn.microsoft.com/en-us/windows/win32/api/profileapi/nf-profileapi-queryperformancecounter 505 | // 506 | // Suffice to say that the TSC cannot be trusted since a system might not have 507 | // an invariant TSC and processors might not have synchronized TSC. The modern 508 | // advent of virtualization and live migrations can also cause it to be highly 509 | // unreliable. 510 | // 511 | // The suggested method (in 2025) is to make use of QPC, however QPC is really 512 | // only useful for measuring a time interval. What most "serious" applications 513 | // do is use a combination of FILETIME & QPC. The former contains UTC time, but 514 | // it does not have sufficient resolution. QPC on the other hand is only useful 515 | // for measuring a time interval, but does have high resolution. So what if we 516 | // combine them, deriving the initial time with FILETIME and accumulate QPC? We 517 | // then just have to periodically re-sync them to account for drift. 518 | struct SystemTime { 519 | SystemTime() { 520 | QueryPerformanceCounter(&qpc_last_); 521 | QueryPerformanceFrequency(&qpc_freq_); 522 | tic_last_ = GetTickCount64(); 523 | } 524 | 525 | Float64 lo_res_now() { 526 | FILETIME ft; 527 | GetSystemTimeAsFileTime(&ft); 528 | // FILETIME is in 100s of nanoseconds so we need to do a little math here: 529 | // BIAS - Number of 100ns between Jan 1st, 1601 and Jan 1st, 1970. 530 | static constexpr const auto BIAS = 116444736000000000ULL; 531 | ULARGE_INTEGER time; 532 | memcpy(&time, &ft, sizeof ft); 533 | return static_cast(time.QuadPart - BIAS) / 10000.0; 534 | } 535 | 536 | Float64 hi_res_now() { 537 | LARGE_INTEGER qpc; 538 | QueryPerformanceCounter(&qpc); 539 | ULONGLONG tic = GetTickCount64(); 540 | Sint64 qpc_elapsed = ((qpc.QuadPart - qpc_last_.QuadPart) * 1000) / qpc_freq_.QuadPart; 541 | Sint64 tic_elapsed; 542 | if (tic >= tic_last_) { 543 | tic_elapsed = tic - tic_last_; 544 | } else { 545 | tic_elapsed = (tic + Sint64(0x100000000I64)) - tic_last_; 546 | } 547 | // Resync when QPC differs from GetTickCount64() by more than 500ms. Permits 548 | // exactly 1sec of drift (500ms on either side). 549 | auto diff = tic_elapsed - qpc_elapsed; 550 | if (diff > 500 || diff < -500) { 551 | sync_ = false; 552 | } 553 | qpc_last_ = qpc; 554 | tic_last_ = tic; 555 | return (qpc.QuadPart * 1000.0) / static_cast(qpc_freq_.QuadPart); 556 | } 557 | 558 | Float64 wall_now() { 559 | auto lo = lo_res_now(); 560 | auto hi = hi_res_now(); 561 | if (!sync_) { 562 | // Increase the timer resolution for a short period when querying lo now. 563 | timeBeginPeriod(1); 564 | sync_lo_ = lo = lo_res_now(); 565 | timeEndPeriod(1); 566 | sync_hi_ = hi; 567 | sync_ = true; 568 | } 569 | 570 | // Schedule a re-sync if we've drifted too much. In this case "too much" is 571 | // defined by 2x the duration of the lo res timer precision. On Windows the 572 | // default scheduling quanta is 64 Hz or 15.625ms. So drifting anymore than 573 | // 31.25ms would indicate a desync. 574 | static constexpr const auto DRIFT_TOLERANCE = 15.625 * 2.0; 575 | const auto hi_elapsed = hi - sync_hi_; 576 | const auto lo_elapsed = lo - sync_lo_; 577 | if (abs(hi_elapsed - lo_elapsed) > DRIFT_TOLERANCE) { 578 | sync_ = true; 579 | } 580 | 581 | // We also have to account for time running backwards since this is used for 582 | // monotonic time. We're careful here not to correct for large deltas since 583 | // DST or clock changes can happen. Here we consider anything >= 2sec to be 584 | // not worth accounting for. 585 | const auto utc = lo + hi; 586 | if (utc < utc_last_ && (utc_last_ - utc) < 2000.0) { 587 | return utc_last_ / 1000.0; 588 | } 589 | utc_last_ = utc; 590 | return utc / 1000.0; 591 | } 592 | 593 | Float64 monotonic_now() { 594 | const auto now = wall_now(); 595 | if (now < last_mono_) { 596 | // On clock change, DST, or large backward changes which wall_now does not 597 | // account for, we'll just return the last monotonic time. 598 | return last_mono_; 599 | } 600 | last_mono_ = now; 601 | return now; 602 | } 603 | 604 | // Simple test-and-set spinloop lock to serialize the intricate management of 605 | // all the state here. 606 | void lock() { 607 | for (;;) { 608 | if (!lock_.exchange(true, MemoryOrder::acquire)) { 609 | break; 610 | } 611 | while (lock_.load(MemoryOrder::relaxed)) { 612 | _mm_pause(); 613 | } 614 | } 615 | } 616 | 617 | void unlock() { 618 | lock_.store(false, MemoryOrder::release); 619 | } 620 | 621 | private: 622 | Bool sync_ = false; 623 | Float64 utc_last_ = 0; 624 | LARGE_INTEGER qpc_last_ = {}; 625 | LARGE_INTEGER qpc_freq_ = {}; 626 | ULONGLONG tic_last_ = 0; 627 | Float64 sync_lo_ = 0; 628 | Float64 sync_hi_ = 0; 629 | Float64 last_mono_ = 0; 630 | Atomic lock_; 631 | }; 632 | 633 | static SystemTime& chrono_singleton() { 634 | // TODO(dweiler): We should store this off the System& someday. 635 | static SystemTime s_time; 636 | return s_time; 637 | } 638 | 639 | static Float64 chrono_monotonic_now(System&) { 640 | auto &s = chrono_singleton(); 641 | s.lock(); 642 | const auto now = s.monotonic_now(); 643 | s.unlock(); 644 | return now; 645 | } 646 | 647 | static Float64 chrono_wall_now(System&) { 648 | auto& s = chrono_singleton(); 649 | s.lock(); 650 | const auto now = s.wall_now(); 651 | s.unlock(); 652 | return now; 653 | } 654 | 655 | extern const Chrono STD_CHRONO = { 656 | .monotonic_now = chrono_monotonic_now, 657 | .wall_now = chrono_wall_now, 658 | }; 659 | 660 | } // namespace Thor 661 | 662 | #endif // THOR_HOST_PLATFORM_WINDOWS -------------------------------------------------------------------------------- /src/util/allocator.cpp: -------------------------------------------------------------------------------- 1 | #include "util/allocator.h" 2 | #include "util/system.h" 3 | 4 | #if THOR_HAS_FEATURE(address_sanitizer) && defined(__SANITIZE_ADDRESS__) 5 | extern "C" void __asan_poison_memory_region(void const volatile*, decltype(sizeof 0)); 6 | extern "C" void __asan_unpoison_memory_region(void const volatile*, decltype(sizeof 0)); 7 | #define ASAN_POISON_MEMORY_REGION(addr, size) \ 8 | __asan_poison_memory_region(reinterpret_cast(addr), (size)) 9 | #define ASAN_UNPOISON_MEMORY_REGION(addr, size) \ 10 | __asan_unpoison_memory_region(reinterpret_cast(addr), (size)) 11 | #else 12 | #define ASAN_POISON_MEMORY_REGION(addr, size) \ 13 | (static_cast(addr), static_cast(size)) 14 | #define ASAN_UNPOISON_MEMORY_REGION(addr, size) \ 15 | (static_cast(addr), static_cast(size)) 16 | #endif 17 | 18 | #if THOR_HAS_INCLUDE() && THOR_HAS_INCLUDE() 19 | #include 20 | #include 21 | #else 22 | #define VALGRIND_MALLOCLIKE_BLOCK(...) 23 | #define VALGRIND_FREELIKE_BLOCK(...) 24 | #define VALGRIND_RESIZEINPLACE_BLOCK(...) 25 | #define VALGRIND_MAKE_MEM_NOACCESS(...) 26 | #define VALGRIND_MAKE_MEM_DEFINED(...) 27 | #define VALGRIND_MAKE_MEM_UNDEFINED(...) 28 | #endif 29 | 30 | namespace Thor { 31 | 32 | #define ASSERT(...) 33 | 34 | void Allocator::memzero(Address addr, Ulen len) { 35 | const auto n_words = len / sizeof(Uint64); 36 | const auto n_bytes = len % sizeof(Uint64); 37 | const auto dst_w = reinterpret_cast(addr); 38 | const auto dst_b = reinterpret_cast(dst_w + n_words); 39 | for (Ulen i = 0; i < n_words; i++) dst_w[i] = 0_u64; 40 | for (Ulen i = 0; i < n_bytes; i++) dst_b[i] = 0_u8; 41 | } 42 | 43 | void Allocator::memcopy(Address dst, Address src, Ulen len) { 44 | const auto dst_b = reinterpret_cast(dst); 45 | const auto src_b = reinterpret_cast(src); 46 | for (Ulen i = 0; i < len; i++) { 47 | dst_b[i] = src_b[i]; 48 | } 49 | } 50 | 51 | ArenaAllocator::ArenaAllocator(Address base, Ulen length) 52 | : region_{base, base + length} 53 | , cursor_{base} 54 | { 55 | ASAN_POISON_MEMORY_REGION(base, length); 56 | VALGRIND_MAKE_MEM_NOACCESS(base, length); 57 | } 58 | 59 | ArenaAllocator::~ArenaAllocator() { 60 | VALGRIND_MAKE_MEM_UNDEFINED(region_.beg, region_.end - region_.beg); 61 | } 62 | 63 | Bool ArenaAllocator::owns(Address addr, Ulen len) const { 64 | return addr >= region_.beg && (addr + len <= region_.end); 65 | } 66 | 67 | Address ArenaAllocator::alloc(Ulen req_len, Bool zero) { 68 | const Ulen new_len = round(req_len); 69 | if (cursor_ + new_len > region_.end) { 70 | return 0; 71 | } 72 | auto addr = cursor_; 73 | ASAN_UNPOISON_MEMORY_REGION(addr, req_len); 74 | VALGRIND_MALLOCLIKE_BLOCK(addr, req_len, 0, zero); 75 | cursor_ += new_len; 76 | if (zero) { 77 | memzero(addr, req_len); 78 | } 79 | return addr; 80 | } 81 | 82 | void ArenaAllocator::free(Address addr, Ulen req_old_len) { 83 | if (addr == 0) return; 84 | const Ulen old_len = round(req_old_len); 85 | ASSERT(addr >= region_.beg); 86 | ASAN_POISON_MEMORY_REGION(addr, req_old_len); 87 | VALGRIND_FREELIKE_BLOCK(addr, 0); 88 | if (addr + old_len == cursor_) { 89 | cursor_ -= old_len; 90 | } 91 | } 92 | 93 | void ArenaAllocator::shrink(Address addr, Ulen req_old_len, Ulen req_new_len) { 94 | const Ulen old_len = round(req_old_len); 95 | const Ulen new_len = round(req_new_len); 96 | ASSERT(addr >= region_.beg); 97 | ASAN_POISON_MEMORY_REGION(addr + req_new_len, req_old_len - req_new_len); 98 | VALGRIND_RESIZEINPLACE_BLOCK(addr, req_old_len, req_new_len, 0); 99 | if (addr + old_len == cursor_) { 100 | cursor_ -= old_len; 101 | cursor_ += new_len; 102 | } 103 | } 104 | 105 | Address ArenaAllocator::grow(Address src_addr, Ulen req_old_len, Ulen req_new_len, Bool zero) { 106 | const Ulen old_len = round(req_old_len); 107 | const Ulen new_len = round(req_new_len); 108 | ASSERT(src_addr >= region_.beg); 109 | const auto req_delta = req_new_len - req_old_len; 110 | if (src_addr + old_len == cursor_) { 111 | const auto delta = new_len - old_len; 112 | if (cursor_ + delta >= region_.end) { 113 | // Out of memory. 114 | return 0; 115 | } 116 | ASAN_UNPOISON_MEMORY_REGION(src_addr + req_old_len, req_delta); 117 | VALGRIND_RESIZEINPLACE_BLOCK(src_addr, req_old_len, req_new_len, 0); 118 | if (zero) { 119 | // RESIZEINPLACE doesn't appear to have a mechanism to specify the growed 120 | // area is initialized, so do it manually here. 121 | VALGRIND_MAKE_MEM_DEFINED(src_addr + req_old_len, req_delta); 122 | } 123 | if (zero) { 124 | memzero(src_addr + req_old_len, req_delta); 125 | } 126 | cursor_ += delta; 127 | return src_addr; 128 | } 129 | // Otherwise allocate new memory and copy. 130 | const auto dst_addr = alloc(req_new_len, false); 131 | if (!dst_addr) { 132 | // Out of memory. 133 | return 0; 134 | } 135 | memcopy(dst_addr, src_addr, req_old_len); 136 | if (zero) { 137 | memzero(dst_addr + req_old_len, req_delta); 138 | } 139 | free(src_addr, req_old_len); 140 | return dst_addr; 141 | } 142 | 143 | TemporaryAllocator::~TemporaryAllocator() { 144 | for (auto node = tail_; node; /**/) { 145 | const auto addr = reinterpret_cast
(node); 146 | const auto prev = node->prev_; 147 | allocator_.free(addr, sizeof(Block) + node->arena_.length()); 148 | node = prev; 149 | } 150 | } 151 | 152 | Bool TemporaryAllocator::add(Ulen len) { 153 | // 2 MiB chunks and double in size until large enough for 'len' 154 | Ulen block_size = 2 << 20; 155 | while (block_size < len) { 156 | block_size *= 2; 157 | } 158 | const auto addr = allocator_.alloc(sizeof(Block) + block_size, false); 159 | if (!addr) { 160 | return false; 161 | } 162 | const auto ptr = reinterpret_cast(addr); 163 | const auto node = new (ptr, Nat{}) Block{block_size}; 164 | if (tail_) { 165 | tail_->next_ = node; 166 | node->prev_ = tail_; 167 | tail_ = node; 168 | } else { 169 | tail_ = node; 170 | head_ = node; 171 | } 172 | tail_ = node; 173 | return true; 174 | } 175 | 176 | Address TemporaryAllocator::alloc(Ulen new_len, Bool zero) { 177 | new_len = round(new_len); 178 | if (!tail_ && !add(new_len)) { 179 | return 0; 180 | } 181 | if (const auto addr = tail_->arena_.alloc(new_len, zero)) { 182 | return addr; 183 | } 184 | if (!add(new_len)) { 185 | return 0; 186 | } 187 | return alloc(new_len, zero); 188 | } 189 | 190 | void TemporaryAllocator::free(Address addr, Ulen old_len) { 191 | if (addr == 0) return; 192 | for (auto node = tail_; node; node = node->prev_) { 193 | if (node->arena_.owns(addr, old_len)) { 194 | node->arena_.free(addr, old_len); 195 | return; 196 | } 197 | } 198 | } 199 | 200 | void TemporaryAllocator::shrink(Address addr, Ulen old_len, Ulen new_len) { 201 | for (auto node = head_; node; node = node->next_) { 202 | if (node->arena_.owns(addr, old_len)) { 203 | node->arena_.shrink(addr, old_len, new_len); 204 | return; 205 | } 206 | } 207 | } 208 | 209 | Address TemporaryAllocator::grow(Address old_addr, Ulen old_len, Ulen new_len, Bool zero) { 210 | // Attempt in-place growth. 211 | for (auto node = head_; node; node = node->next_) { 212 | if (!node->arena_.owns(old_addr, old_len)) { 213 | continue; 214 | } 215 | if (auto new_addr = node->arena_.grow(old_addr, old_len, new_len, zero)) { 216 | return new_addr; 217 | } 218 | } 219 | // Could not grow in-place, allocate fresh memory. 220 | const auto new_addr = alloc(new_len, false); 221 | if (!new_addr) { 222 | return 0; 223 | } 224 | // Copy the old part over. 225 | memcopy(new_addr, old_addr, old_len); 226 | if (zero) { 227 | // Zero the remainder part if requested. 228 | memzero(new_addr + old_len, new_len - old_len); 229 | } 230 | free(old_addr, old_len); 231 | return new_addr; 232 | } 233 | 234 | Address SystemAllocator::alloc(Ulen new_len, Bool zero) { 235 | if (const auto ptr = sys_.heap.allocate(sys_, new_len, zero)) { 236 | ASAN_UNPOISON_MEMORY_REGION(ptr, new_len); 237 | VALGRIND_MALLOCLIKE_BLOCK(ptr, new_len, 0, zero); 238 | return reinterpret_cast
(ptr); 239 | } 240 | return 0; 241 | } 242 | 243 | void SystemAllocator::free(Address addr, Ulen old_len) { 244 | if (addr == 0) return; 245 | const auto ptr = reinterpret_cast(addr); 246 | sys_.heap.deallocate(sys_, ptr, old_len); 247 | ASAN_POISON_MEMORY_REGION(ptr, old_len); 248 | VALGRIND_FREELIKE_BLOCK(ptr, 0); 249 | } 250 | 251 | void SystemAllocator::shrink(Address, Ulen, Ulen) { 252 | // no-op 253 | } 254 | 255 | Address SystemAllocator::grow(Address old_addr, Ulen old_len, Ulen new_len, Bool zero) { 256 | const auto new_ptr = sys_.heap.allocate(sys_, new_len, false); 257 | if (!new_ptr) { 258 | return 0; 259 | } 260 | const auto new_addr = reinterpret_cast
(new_ptr); 261 | memcopy(new_addr, old_addr, old_len); 262 | if (zero) { 263 | memzero(new_addr + old_len, new_len - old_len); 264 | } 265 | const auto old_ptr = reinterpret_cast(old_addr); 266 | sys_.heap.deallocate(sys_, old_ptr, old_len); 267 | return new_addr; 268 | } 269 | 270 | } // namespace Thor -------------------------------------------------------------------------------- /src/util/allocator.h: -------------------------------------------------------------------------------- 1 | #ifndef THOR_ALLOCATOR_H 2 | #define THOR_ALLOCATOR_H 3 | #include "util/types.h" 4 | #include "util/forward.h" 5 | #include "util/exchange.h" 6 | namespace Thor { 7 | 8 | struct System; 9 | 10 | struct Allocator { 11 | static void memzero(Address addr, Ulen len); 12 | static void memcopy(Address dst, Address src, Ulen len); 13 | 14 | static constexpr Ulen round(Ulen len) { 15 | return ((len + 16 - 1) / 16) * 16; 16 | } 17 | 18 | virtual Address alloc(Ulen length, Bool zero) = 0; 19 | virtual void free(Address addr, Ulen old_len) = 0; 20 | virtual void shrink(Address addr, Ulen old_len, Ulen new_len) = 0; 21 | virtual Address grow(Address addr, Ulen old_len, Ulen new_len, Bool zero) = 0; 22 | 23 | // Helper functions when working with typed data 24 | template 25 | T* allocate(Ulen count, Bool zero) { 26 | if (auto addr = alloc(count * sizeof(T), zero)) { 27 | return reinterpret_cast(addr); 28 | } 29 | return nullptr; 30 | } 31 | 32 | template 33 | void deallocate(T* ptr, Ulen count) { 34 | auto addr = reinterpret_cast
(ptr); 35 | free(addr, count * sizeof(T)); 36 | } 37 | 38 | // Helpers for allocating+construct and destruct+deallocate objects. 39 | template 40 | T* create(Ts&&... args) { 41 | if (auto data = allocate(1, false)) { 42 | return new (data, Nat{}) T{forward(args)...}; 43 | } 44 | return nullptr; 45 | } 46 | 47 | template 48 | void destroy(T* obj) { 49 | if (obj) { 50 | obj->~T(); 51 | deallocate(obj, 1); 52 | } 53 | } 54 | }; 55 | 56 | struct ArenaAllocator : Allocator { 57 | ArenaAllocator(Address base, Ulen length); 58 | ArenaAllocator(const ArenaAllocator&) = delete; 59 | ArenaAllocator(ArenaAllocator&& other) = delete; 60 | ~ArenaAllocator(); 61 | Bool owns(Address addr, Ulen len) const; 62 | virtual Address alloc(Ulen new_len, Bool zero); 63 | virtual void free(Address addr, Ulen old_len); 64 | virtual void shrink(Address addr, Ulen old_len, Ulen new_len); 65 | virtual Address grow(Address addr, Ulen old_len, Ulen new_len, Bool zero); 66 | constexpr Ulen length() const { 67 | return region_.end - region_.beg; 68 | } 69 | private: 70 | struct { Address beg, end; } region_; 71 | Address cursor_; 72 | }; 73 | 74 | template 75 | struct InlineAllocator : ArenaAllocator { 76 | constexpr InlineAllocator() 77 | : ArenaAllocator{reinterpret_cast
(data_), E} 78 | { 79 | } 80 | InlineAllocator(const InlineAllocator&) = delete; 81 | InlineAllocator(InlineAllocator&&) = delete; 82 | private: 83 | alignas(16) Uint8 data_[E]; 84 | }; 85 | 86 | struct TemporaryAllocator : Allocator { 87 | TemporaryAllocator(const TemporaryAllocator&) = delete; 88 | TemporaryAllocator(TemporaryAllocator&& other) 89 | : allocator_{other.allocator_} 90 | , head_{exchange(other.head_, nullptr)} 91 | , tail_{exchange(other.tail_, nullptr)} 92 | { 93 | } 94 | constexpr TemporaryAllocator(Allocator& allocator) 95 | : allocator_{allocator} 96 | { 97 | } 98 | ~TemporaryAllocator(); 99 | virtual Address alloc(Ulen new_len, Bool zero); 100 | virtual void free(Address addr, Ulen old_len); 101 | virtual void shrink(Address addr, Ulen old_len, Ulen new_len); 102 | virtual Address grow(Address addr, Ulen old_len, Ulen new_len, Bool zero); 103 | private: 104 | // Add a new block to the temporary allocator. 105 | Bool add(Ulen len); 106 | struct Block { 107 | Block(Ulen length) 108 | : arena_{reinterpret_cast
(data_), length} 109 | { 110 | } 111 | ArenaAllocator arena_; 112 | Block* prev_ = nullptr; 113 | Block* next_ = nullptr; 114 | Uint8 data_[]; 115 | }; 116 | Allocator& allocator_; 117 | Block* head_ = nullptr; 118 | Block* tail_ = nullptr; 119 | }; 120 | 121 | template 122 | struct ScratchAllocator : Allocator { 123 | constexpr ScratchAllocator(Allocator& allocator) 124 | : temporary_{allocator} 125 | { 126 | } 127 | ScratchAllocator(const ScratchAllocator&) = delete; 128 | ScratchAllocator(ScratchAllocator&&) = delete; 129 | virtual Address alloc(Ulen new_len, Bool zero) { 130 | if (auto addr = inline_.alloc(new_len, zero)) return addr; 131 | return temporary_.alloc(new_len, zero); 132 | } 133 | virtual void free(Address addr, Ulen old_len) { 134 | if (inline_.owns(addr, old_len)) { 135 | inline_.free(addr, old_len); 136 | } else { 137 | temporary_.free(addr, old_len); 138 | } 139 | } 140 | virtual void shrink(Address addr, Ulen old_len, Ulen new_len) { 141 | if (inline_.owns(addr, old_len)) { 142 | inline_.shrink(addr, old_len, new_len); 143 | } else { 144 | temporary_.shrink(addr, old_len, new_len); 145 | } 146 | } 147 | virtual Address grow(Address old_addr, Ulen old_len, Ulen new_len, Bool zero) { 148 | if (inline_.owns(old_addr, old_len)) { 149 | if (auto new_addr = inline_.grow(old_addr, old_len, new_len, zero)) { 150 | return new_addr; 151 | } else if (auto new_addr = temporary_.alloc(new_len, false)) { 152 | // Could not grow in-place inside the inline allocator. Going to have to 153 | // move the result to the temporary allocator instead. 154 | memcopy(new_addr, old_addr, old_len); 155 | if (zero) { 156 | memzero(new_addr + old_len, new_len - old_len); 157 | } 158 | inline_.free(old_addr, old_len); 159 | return new_addr; 160 | } else { 161 | return 0; 162 | } 163 | } 164 | return temporary_.grow(old_addr, old_len, new_len, zero); 165 | } 166 | private: 167 | InlineAllocator inline_; 168 | TemporaryAllocator temporary_; 169 | }; 170 | 171 | struct SystemAllocator : Allocator { 172 | constexpr SystemAllocator(System& sys) 173 | : sys_{sys} 174 | { 175 | } 176 | virtual Address alloc(Ulen new_len, Bool zero); 177 | virtual void free(Address addr, Ulen old_len); 178 | virtual void shrink(Address, Ulen, Ulen); 179 | virtual Address grow(Address addr, Ulen old_len, Ulen new_len, Bool zero); 180 | private: 181 | System& sys_; 182 | }; 183 | 184 | } // namespace Thor 185 | 186 | #endif // THOR_ALLOCATOR_H -------------------------------------------------------------------------------- /src/util/array.h: -------------------------------------------------------------------------------- 1 | #ifndef THOR_ARRAY_H 2 | #define THOR_ARRAY_H 3 | #include "util/allocator.h" 4 | #include "util/slice.h" 5 | #include "util/maybe.h" 6 | 7 | namespace Thor { 8 | 9 | struct Allocator; 10 | 11 | // Simple dynamic array implementation. Use like Array where T is the element 12 | // type. Can append elements with push_back(elem), query length with length(), 13 | // access elements with operator[], or a pointer to the whole array with data(). 14 | // Requires a polymorphic allocator on construction. 15 | template 16 | struct Array { 17 | // Minimum capacity of the array when populated with the first element. 18 | static inline constexpr const auto MIN_CAPACITY = 16; 19 | 20 | // The resize factor of the capacity as a percentage. 21 | static inline constexpr const auto RESIZE_FACTOR = 250; 22 | 23 | constexpr Array(Allocator& allocator) 24 | : allocator_{allocator} 25 | { 26 | } 27 | constexpr Array(Array&& other) 28 | : data_{exchange(other.data_, nullptr)} 29 | , length_{exchange(other.length_, 0)} 30 | , capacity_{exchange(other.capacity_, 0)} 31 | , allocator_{other.allocator_} 32 | { 33 | } 34 | 35 | constexpr Array(const Array&) = delete; 36 | 37 | ~Array() { drop(); } 38 | 39 | Array& operator=(const Array&) = delete; 40 | 41 | Array& operator=(Array&& other) { 42 | return *new (drop(), Nat{}) Array{move(other)}; 43 | } 44 | 45 | [[nodiscard]] Bool resize(Ulen length) { 46 | if (length < length_) { 47 | if constexpr (!TriviallyDestructible) { 48 | for (Ulen i = length_ - 1; i > length; i--) { 49 | data_[i].~T(); 50 | } 51 | } 52 | } else if (length > length_) { 53 | if (!reserve(length)) { 54 | return false; 55 | } 56 | for (Ulen i = length_; i < length; i++) { 57 | new (data_ + i, Nat{}) T{}; 58 | } 59 | } 60 | length_ = length; 61 | return true; 62 | } 63 | 64 | [[nodiscard]] Bool reserve(Ulen length) { 65 | if (length < capacity_) { 66 | return true; 67 | } 68 | Ulen capacity = MIN_CAPACITY; 69 | while (capacity < length) { 70 | capacity = (capacity * RESIZE_FACTOR) / 100; 71 | } 72 | 73 | auto data = allocator_.allocate(capacity, false); 74 | if (!data) { 75 | return false; 76 | } 77 | for (Ulen i = 0; i < length_; i++) { 78 | new (data + i, Nat{}) T{move(data_[i])}; 79 | } 80 | drop(); 81 | data_ = data; 82 | capacity_ = capacity; 83 | return true; 84 | } 85 | 86 | template 87 | [[nodiscard]] Bool emplace_back(Ts&&... args) { 88 | if (!reserve(length_ + 1)) return false; 89 | new (data_ + length_, Nat{}) T{forward(args)...}; 90 | length_++; 91 | return true; 92 | } 93 | 94 | [[nodiscard]] Bool push_back(T&& value) 95 | requires MoveConstructible 96 | { 97 | if (!reserve(length_ + 1)) return false; 98 | new (data_ + length_, Nat{}) T{move(value)}; 99 | length_++; 100 | return true; 101 | } 102 | 103 | [[nodiscard]] Bool push_back(const T& value) 104 | requires CopyConstructible 105 | { 106 | if (!reserve(length_ + 1)) return false; 107 | new (data_ + length_, Nat{}) T{value}; 108 | length_++; 109 | return true; 110 | } 111 | 112 | Maybe> copy(Allocator& allocator) 113 | requires CopyConstructible || MaybeCopyable 114 | { 115 | // We do not use resize here because that would default construct the T and 116 | // T may not have a default constructor. Instead we reserve the space then 117 | // placement new the copy into each slot, remembering to increase the length 118 | // for each element we add. This length has to be incremented and cannot be 119 | // folded with one result.length_ = length_ at the end because it's possible 120 | // that say the first N elements copy successfully but N+1 fails, in this 121 | // case we return {} and the [result] destructor has to destroy the N copies 122 | // which is dependent on the length stored in [result.length_]. 123 | Array result{allocator}; 124 | if (!result.reserve(length_)) { 125 | return {}; 126 | } 127 | for (Ulen i = length_; i < length_; i++) { 128 | if constexpr (CopyConstructible) { 129 | new (result.data_ + i, Nat{}) T{data_[i]}; // Call the copy constructor 130 | } else if constexpr (MaybeCopyable) { 131 | if (auto copied = data_[i].copy()) { 132 | new (result.data_ + i, Nat{}) T{move(*copied)}; 133 | } 134 | } 135 | result.length_++; 136 | } 137 | return result; 138 | } 139 | 140 | void pop_back() { 141 | if constexpr (!TriviallyDestructible) { 142 | data_[length_ - 1].~T(); 143 | } 144 | length_--; 145 | } 146 | 147 | void pop_front() { 148 | if constexpr (!TriviallyDestructible) { 149 | data_[0].~T(); 150 | } 151 | for (Ulen i = 1; i < length_; i--) { 152 | data_[i - 1] = move(data_[i]); 153 | } 154 | length_--; 155 | } 156 | 157 | void clear() { 158 | destruct(); 159 | length_ = 0; 160 | } 161 | 162 | void reset() { 163 | drop(); 164 | length_ = 0; 165 | capacity_ = 0; 166 | } 167 | 168 | THOR_FORCEINLINE constexpr T* data() { return data_; } 169 | THOR_FORCEINLINE constexpr const T* data() const { return data_; } 170 | 171 | THOR_FORCEINLINE constexpr T& last() { return data_[length_ - 1]; } 172 | THOR_FORCEINLINE constexpr const T& last() const { return data_[length_ - 1]; } 173 | 174 | [[nodiscard]] THOR_FORCEINLINE constexpr auto length() const { return length_; } 175 | [[nodiscard]] THOR_FORCEINLINE constexpr auto capacity() const { return capacity_; } 176 | [[nodiscard]] THOR_FORCEINLINE constexpr auto is_empty() const { return length_ == 0; } 177 | [[nodiscard]] THOR_FORCEINLINE constexpr Allocator& allocator() { return allocator_; } 178 | [[nodiscard]] THOR_FORCEINLINE constexpr Allocator& allocator() const { return allocator_; } 179 | 180 | [[nodiscard]] THOR_FORCEINLINE constexpr T& operator[](Ulen index) { return data_[index]; } 181 | [[nodiscard]] THOR_FORCEINLINE constexpr const T& operator[](Ulen index) const { return data_[index]; } 182 | 183 | THOR_FORCEINLINE constexpr Slice slice() { return { data_, length_ }; } 184 | THOR_FORCEINLINE constexpr Slice slice() const { return { data_, length_ }; } 185 | 186 | // Just enough to make range based for loops work 187 | [[nodiscard]] THOR_FORCEINLINE constexpr T* begin() { return data_; } 188 | [[nodiscard]] THOR_FORCEINLINE constexpr const T* begin() const { return data_; } 189 | [[nodiscard]] THOR_FORCEINLINE constexpr T* end() { return data_ + length_; } 190 | [[nodiscard]] THOR_FORCEINLINE constexpr const T* end() const { return data_ + length_; } 191 | 192 | private: 193 | void destruct() { 194 | if constexpr (!TriviallyDestructible) { 195 | for (Ulen i = length_ - 1; i < length_; i--) { 196 | data_[i].~T(); 197 | } 198 | } 199 | } 200 | 201 | Array* drop() { 202 | destruct(); 203 | allocator_.deallocate(data_, capacity_); 204 | return this; 205 | } 206 | 207 | T* data_ = nullptr; 208 | Ulen length_ = 0; 209 | Ulen capacity_ = 0; 210 | Allocator& allocator_; 211 | }; 212 | 213 | } // namespace Thor 214 | 215 | #endif // THOR_ARRAY_H -------------------------------------------------------------------------------- /src/util/assert.cpp: -------------------------------------------------------------------------------- 1 | #include "util/assert.h" 2 | #include "util/system.h" 3 | 4 | namespace Thor { 5 | 6 | void assert(System& sys, StringView cond, StringView file, Sint32 line) { 7 | sys.process.assert(sys, cond, file, line); 8 | for (;;); 9 | } 10 | 11 | } // namespace Thor -------------------------------------------------------------------------------- /src/util/assert.h: -------------------------------------------------------------------------------- 1 | #ifndef THOR_ASSERT_H 2 | #define THOR_ASSERT_H 3 | #include "util/string.h" 4 | 5 | namespace Thor { 6 | 7 | [[noreturn]] void assert(System& sys, StringView cond, StringView file, Sint32 line); 8 | 9 | } // namespace Thor 10 | 11 | #if defined(NDEBUG) 12 | #define THOR_ASSERT(...) 13 | #else 14 | #define THOR_ASSERT(sys, cond) \ 15 | ((cond) ? (void)0 : ::Thor::assert((sys), #cond, __FILE__, __LINE__)) 16 | #endif 17 | 18 | #endif // THOR_ASSERT_H 19 | -------------------------------------------------------------------------------- /src/util/atomic.h: -------------------------------------------------------------------------------- 1 | #ifndef THOR_ATOMIC_H 2 | #define THOR_ATOMIC_H 3 | #include // TODO(dweiler): replace 4 | 5 | #include "util/forward.h" 6 | #include "util/types.h" 7 | 8 | namespace Thor { 9 | 10 | using MemoryOrder = std::memory_order; // TODO(dweiler): replace 11 | 12 | template 13 | struct Atomic { 14 | Atomic() = default; 15 | constexpr Atomic(T initial) 16 | : value_{forward(initial)} 17 | { 18 | } 19 | 20 | THOR_FORCEINLINE T load(MemoryOrder order = MemoryOrder::seq_cst) const { 21 | return value_.load(order); 22 | } 23 | 24 | THOR_FORCEINLINE void store(T desired, MemoryOrder order = MemoryOrder::seq_cst) { 25 | value_.store(desired, order); 26 | } 27 | 28 | THOR_FORCEINLINE T exchange(T desired, MemoryOrder order = MemoryOrder::seq_cst) { 29 | return value_.exchange(desired, order); 30 | } 31 | 32 | THOR_FORCEINLINE Bool compare_exchange_weak(T expected, T desired, MemoryOrder order = MemoryOrder::seq_cst) { 33 | T expected_or_actual = expected; 34 | return value_.compare_exchange_weak(expected_or_actual, desired, order); 35 | } 36 | 37 | private: 38 | std::atomic value_; // TODO(dweiler): replace 39 | }; 40 | 41 | } // namespace Thor 42 | 43 | #endif // THOR_ATOMIC_H -------------------------------------------------------------------------------- /src/util/cpprt.cpp: -------------------------------------------------------------------------------- 1 | #include "util/types.h" 2 | 3 | using namespace Thor; 4 | 5 | // libstdc++ ABI implementation. 6 | #if !defined(THOR_COMPILER_MSVC) 7 | struct Guard { 8 | Uint8 done; 9 | Uint8 pending; 10 | Uint8 padding[62]; 11 | }; 12 | static_assert(sizeof(Guard) == 64); 13 | 14 | void operator delete(void*) noexcept { 15 | // See comment below 16 | } 17 | void operator delete(void*, unsigned long) noexcept { 18 | // When a base class contains a virtual destructor, the compiler will generate 19 | // two destructors for a derived class. The regular destructor and a special 20 | // destructor called a "deleting destructor" which is called when "delete" is 21 | // used. This deleting destructor will call this operator delete. We never use 22 | // "delete" though and so these deleting destructors are never called. The ABI 23 | // requires they be generated though because the compiler cannot tell that a 24 | // client won't use delete. 25 | } 26 | 27 | extern "C" { 28 | 29 | int __cxa_guard_acquire(Guard* guard) { 30 | if (guard->done) { 31 | return 0; 32 | } 33 | if (guard->pending) { 34 | *(volatile int *)0 = 0; 35 | } 36 | guard->pending = 1; 37 | return 1; 38 | } 39 | 40 | void __cxa_guard_release(Guard* guard) { 41 | guard->done = 1; 42 | } 43 | 44 | void __cxa_pure_virtual() { 45 | // No-op 46 | } 47 | 48 | } // extern "C" 49 | 50 | #endif 51 | -------------------------------------------------------------------------------- /src/util/exchange.h: -------------------------------------------------------------------------------- 1 | #ifndef THOR_EXCHANGE_H 2 | #define THOR_EXCHANGE_H 3 | #include "util/move.h" 4 | #include "util/forward.h" 5 | 6 | namespace Thor { 7 | 8 | template 9 | THOR_FORCEINLINE constexpr T exchange(T& obj, U&& new_value) { 10 | T old_value = move(obj); 11 | obj = forward(new_value); 12 | return old_value; 13 | } 14 | 15 | } // namespace Thor 16 | 17 | #endif // THOR_EXCHANGE_H -------------------------------------------------------------------------------- /src/util/file.cpp: -------------------------------------------------------------------------------- 1 | #include "util/file.h" 2 | 3 | namespace Thor { 4 | 5 | Maybe File::open(System& sys, StringView name, Access access) { 6 | if (name.is_empty()) { 7 | return {}; 8 | } 9 | if (auto file = sys.filesystem.open_file(sys, name, access)) { 10 | return File{sys, file}; 11 | } 12 | return {}; 13 | } 14 | 15 | Uint64 File::read(Uint64 offset, Slice data) const { 16 | Uint64 total = 0; 17 | while (total < data.length()) { 18 | if (auto rd = sys_.filesystem.read_file(sys_, file_, offset, data)) { 19 | total += rd; 20 | offset += rd; 21 | data = data.slice(rd); 22 | } else { 23 | break; 24 | } 25 | } 26 | return total; 27 | } 28 | 29 | Uint64 File::write(Uint64 offset, Slice data) const { 30 | Uint64 total = 0; 31 | while (total < data.length()) { 32 | if (auto wr = sys_.filesystem.write_file(sys_, file_, offset, data)) { 33 | total += wr; 34 | offset += wr; 35 | data = data.slice(wr); 36 | } else { 37 | break; 38 | } 39 | } 40 | return total; 41 | } 42 | 43 | Uint64 File::tell() const { 44 | return sys_.filesystem.tell_file(sys_, file_); 45 | } 46 | 47 | void File::close() { 48 | if (file_) { 49 | sys_.filesystem.close_file(sys_, file_); 50 | file_ = nullptr; 51 | } 52 | } 53 | 54 | Array File::map(Allocator& allocator) const { 55 | Array result{allocator}; 56 | if (!result.resize(tell())) { 57 | return {allocator}; 58 | } 59 | if (read(0, result.slice()) != result.length()) { 60 | return {allocator}; 61 | } 62 | return result; 63 | } 64 | 65 | } // namespace Thor -------------------------------------------------------------------------------- /src/util/file.h: -------------------------------------------------------------------------------- 1 | #ifndef THOR_FILE_H 2 | #define THOR_FILE_H 3 | #include "util/array.h" 4 | #include "util/maybe.h" 5 | #include "util/system.h" 6 | 7 | namespace Thor { 8 | 9 | struct File { 10 | using Access = Filesystem::Access; 11 | 12 | static Maybe open(System& sys, StringView name, Access access); 13 | 14 | constexpr File(File&& other) 15 | : sys_{other.sys_} 16 | , file_{exchange(other.file_, nullptr)} 17 | { 18 | } 19 | 20 | ~File() { close(); } 21 | 22 | File& operator=(File&& other) { 23 | return *new (drop(), Nat{}) File{move(other)}; 24 | } 25 | 26 | [[nodiscard]] Uint64 read(Uint64 offset, Slice data) const; 27 | [[nodiscard]] Uint64 write(Uint64 offset, Slice data) const; 28 | [[nodiscard]] Uint64 tell() const; 29 | 30 | void close(); 31 | 32 | Array map(Allocator& allocator) const; 33 | 34 | private: 35 | File* drop() { 36 | close(); 37 | return this; 38 | } 39 | constexpr File(System& sys, Filesystem::File* file) 40 | : sys_{sys} 41 | , file_{file} 42 | { 43 | } 44 | System& sys_; 45 | Filesystem::File* file_; 46 | }; 47 | 48 | } // namespace Thor 49 | 50 | #endif // THOR_FILE_H -------------------------------------------------------------------------------- /src/util/forward.h: -------------------------------------------------------------------------------- 1 | #ifndef THOR_FORWARD_H 2 | #define THOR_FORWARD_H 3 | #include "util/traits.h" 4 | 5 | namespace Thor { 6 | 7 | template 8 | constexpr T&& forward(RemoveReference&& arg) { 9 | return static_cast(arg); 10 | } 11 | 12 | template 13 | constexpr T&& forward(RemoveReference& arg) { 14 | return static_cast(arg); 15 | } 16 | 17 | } // namespace Thor 18 | 19 | #endif // THOR_FORWARD_H -------------------------------------------------------------------------------- /src/util/hash.h: -------------------------------------------------------------------------------- 1 | #ifndef THOR_HASH_H 2 | #define THOR_HASH_H 3 | 4 | #include "util/traits.h" 5 | 6 | namespace Thor { 7 | 8 | template 9 | concept Hashable = requires(const K& key) { 10 | { key.hash(0_u64) } -> Same; 11 | }; 12 | 13 | constexpr const Hash FNV_OFFSET = 14695981039346656037_u64; 14 | constexpr const Hash FNV_PRIME = 1099511628211_u64; 15 | 16 | template 17 | constexpr Hash hash(T v, Hash h = FNV_OFFSET) { 18 | return hash(hi(v), hash(lo(v), h)); 19 | } 20 | template<> 21 | constexpr Hash hash(Uint8 v, Hash h) { 22 | return (h ^ Hash(v)) * FNV_PRIME; 23 | } 24 | constexpr Hash hash(Hashable auto v, Hash h = FNV_OFFSET) { 25 | return v.hash(h); 26 | } 27 | 28 | } // namespace Thor 29 | 30 | #endif // THOR_HASH_H -------------------------------------------------------------------------------- /src/util/info.h: -------------------------------------------------------------------------------- 1 | #ifndef THOR_INFO_H 2 | #define THOR_INFO_H 3 | 4 | // Work out the compiler being used. 5 | #if defined(__clang__) 6 | #define THOR_COMPILER_CLANG 7 | #elif defined(__GNUC__) || defined(__GNUG__) 8 | #define THOR_COMPILER_GCC 9 | #elif defined(_MSC_VER) 10 | #define THOR_COMPILER_MSVC 11 | #else 12 | #error Unsupported compiler 13 | #endif 14 | 15 | // Work out the host platform being targeted. 16 | #if defined(_WIN32) 17 | #define THOR_HOST_PLATFORM_WINDOWS 18 | #elif defined(__linux__) 19 | #define THOR_HOST_PLATFORM_LINUX 20 | #define THOR_HOST_PLATFORM_POSIX 21 | #elif defined(__APPLE__) && defined(__MACH__) 22 | #define THOR_HOST_PLATFORM_MACOS 23 | #define THOR_HOST_PLATFORM_POSIX 24 | #else 25 | #error Unsupported platform 26 | #endif 27 | 28 | #if defined(THOR_COMPILER_CLANG) || defined(THOR_COMPILER_GCC) 29 | #define THOR_FORCEINLINE __attribute__((__always_inline__)) inline 30 | #elif defined(THOR_COMPILER_MSVC) 31 | #define THOR_FORCEINLINE __forceinline 32 | #endif 33 | 34 | #if defined(__has_builtin) 35 | #define THOR_HAS_BUILTIN(...) __has_builtin(__VA_ARGS__) 36 | #else 37 | #define THOR_HAS_BUILTIN(...) 0 38 | #endif 39 | 40 | #if defined(__has_feature) 41 | #define THOR_HAS_FEATURE(...) __has_feature(__VA_ARGS__) 42 | #else 43 | #define THOR_HAS_FEATURE(...) 0 44 | #endif 45 | 46 | #if defined(__has_include) 47 | #define THOR_HAS_INCLUDE(...) __has_include(__VA_ARGS__) 48 | #else 49 | #define THOR_HAS_INCLUDE(...) 0 50 | #endif 51 | 52 | // These are debug build options 53 | // #define THOR_CFG_USE_MALLOC 1 54 | 55 | #endif // THOR_INFO_H -------------------------------------------------------------------------------- /src/util/lock.cpp: -------------------------------------------------------------------------------- 1 | #include "util/lock.h" 2 | #include "util/system.h" 3 | #include "util/assert.h" 4 | 5 | namespace Thor { 6 | 7 | struct LockWaiter { 8 | LockWaiter(System& sys) 9 | : sys{sys} 10 | , mutex{sys.scheduler.mutex_create(sys)} 11 | , cond{sys.scheduler.cond_create(sys)} 12 | { 13 | } 14 | ~LockWaiter() { 15 | sys.scheduler.cond_destroy(sys, cond); 16 | sys.scheduler.mutex_destroy(sys, mutex); 17 | } 18 | System& sys; 19 | Bool park = false; 20 | Scheduler::Mutex* mutex = nullptr; 21 | Scheduler::Cond* cond = nullptr; 22 | LockWaiter* next = nullptr; 23 | LockWaiter* tail = nullptr; 24 | }; 25 | 26 | void Lock::lock_slow(System& sys) { 27 | Ulen spin_count = 0; 28 | Ulen spin_limit = 40; 29 | for (;;) { 30 | auto current_word = word_.load(); 31 | if (!(current_word & IS_LOCKED_BIT)) { 32 | // Not possible for another thread to hold the queue lock while the lock 33 | // itself is no longer held since the queue lock is only acquired when the 34 | // lock is held. Thus the queue lock prevents unlock. 35 | THOR_ASSERT(sys, !(current_word & IS_QUEUE_LOCKED_BIT)); 36 | if (word_.compare_exchange_weak(current_word, current_word | IS_LOCKED_BIT)) { 37 | // We acquired the lock. 38 | return; 39 | } 40 | } 41 | 42 | // No queue and haven't spun too much, just try again. 43 | if (!(current_word & ~QUEUE_HEAD_MASK) && spin_count < spin_limit) { 44 | spin_count++; 45 | sys.scheduler.yield(sys); 46 | continue; 47 | } 48 | 49 | // Put this thread on the queue. We can do this without allocating memory 50 | // since lock_slow will be held in place, meaning the stack frame is as good 51 | // a place as any for the Waiter. 52 | LockWaiter waiter{sys}; 53 | 54 | // Reload the current word though since some time might've passed. 55 | current_word = word_.load(); 56 | 57 | // Only proceed if the queue lock is not held, the Lock is held, and we 58 | // succeed in acquiring the queue lock. 59 | if ((current_word & IS_QUEUE_LOCKED_BIT) 60 | || !(current_word & IS_LOCKED_BIT) 61 | || !word_.compare_exchange_weak(current_word, current_word | IS_QUEUE_LOCKED_BIT)) 62 | { 63 | sys.scheduler.yield(sys); 64 | continue; 65 | } 66 | 67 | waiter.park = true; 68 | 69 | // This thread now owns the queue. No other thread can enqueue or dequeue 70 | // until we're done. It's also not possible to release the Lock while we 71 | // hold this queue lock. 72 | auto head = reinterpret_cast(current_word & ~QUEUE_HEAD_MASK); 73 | if (head) { 74 | // Put this thread at the end of the queue. 75 | head->tail->next = &waiter; 76 | head->tail = &waiter; 77 | 78 | // Release the queue lock now. 79 | current_word = word_.load(); 80 | THOR_ASSERT(sys, current_word & ~QUEUE_HEAD_MASK); 81 | THOR_ASSERT(sys, current_word & IS_QUEUE_LOCKED_BIT); 82 | THOR_ASSERT(sys, current_word & IS_LOCKED_BIT); 83 | word_.store(current_word & ~IS_QUEUE_LOCKED_BIT); 84 | } else { 85 | // Otherwise this thread is the head of the queue. 86 | head = &waiter; 87 | waiter.tail = &waiter; 88 | 89 | // Release the queue lock and install ourselves as the head. No CAS needed 90 | // since we own the queue lock currently. 91 | current_word = word_.load(); 92 | THOR_ASSERT(sys, ~(current_word & ~QUEUE_HEAD_MASK)); 93 | THOR_ASSERT(sys, current_word & IS_QUEUE_LOCKED_BIT); 94 | THOR_ASSERT(sys, current_word & IS_LOCKED_BIT); 95 | auto new_word = current_word; 96 | new_word |= reinterpret_cast
(head); 97 | new_word &= ~IS_QUEUE_LOCKED_BIT; 98 | word_.store(new_word); 99 | } 100 | 101 | // At this point any other thread which acquires the queue lock will see 102 | // this thread on the queue now, and any thread that acquires waiter's lock 103 | // will see that waiter wants to park. 104 | sys.scheduler.mutex_lock(sys, waiter.mutex); 105 | while (waiter.park) { 106 | sys.scheduler.cond_wait(sys, waiter.cond, waiter.mutex); 107 | } 108 | sys.scheduler.mutex_unlock(sys, waiter.mutex); 109 | 110 | THOR_ASSERT(sys, !waiter.park); 111 | THOR_ASSERT(sys, !waiter.next); 112 | THOR_ASSERT(sys, !waiter.tail); 113 | 114 | // Loop around and try again. 115 | } 116 | } 117 | 118 | void Lock::unlock_slow(System& sys) { 119 | // Generally speaking the fast path can only fail for three reasons: 120 | // 1. Spurious CAS failure, unlikely but does happen. 121 | // 2. Someone put a thread on the queue 122 | // 3. The queue lock is held. 123 | // 124 | // However (3) is essentially the same as (2) since it can only be held if 125 | // someone is *about* to put a thread on the wait queue. 126 | 127 | // Here we will acquire (or release) the wait queue lock. 128 | for (;;) { 129 | auto current_word = word_.load(); 130 | THOR_ASSERT(sys, current_word & IS_LOCKED_BIT); 131 | if (current_word == IS_LOCKED_BIT) { 132 | if (word_.compare_exchange_weak(IS_LOCKED_BIT, 0)) { 133 | // The fast path's weak CAS failed spuriously, but we made it to the 134 | // slow path and now we succeeded. This is a sort of "semi-fast" or 135 | // medium-path, the lock is unlocked though and we're done. 136 | return; 137 | } 138 | // Loop around and try again. 139 | sys.scheduler.yield(sys); 140 | continue; 141 | } 142 | 143 | // The slow path now begins. 144 | if (current_word & IS_QUEUE_LOCKED_BIT) { 145 | sys.scheduler.yield(sys); 146 | continue; 147 | } 148 | 149 | // Wasn't just the fast path's weak CAS failing spuriously. The queue lock 150 | // is not held either, so there should be an entry on the queue. 151 | THOR_ASSERT(sys, current_word & ~QUEUE_HEAD_MASK); 152 | 153 | if (word_.compare_exchange_weak(current_word, current_word | IS_QUEUE_LOCKED_BIT)) { 154 | break; 155 | } 156 | } 157 | 158 | auto current_word = word_.load(); 159 | 160 | // Once we acquire the queue lock, the Lock must still be held and the queue 161 | // must be non-empty because only lock_slow could have held the queue lock and 162 | // if it did, then it only releases it after it puts something on the queue. 163 | THOR_ASSERT(sys, current_word & IS_LOCKED_BIT); 164 | THOR_ASSERT(sys, current_word & IS_QUEUE_LOCKED_BIT); 165 | auto head = reinterpret_cast(current_word & ~QUEUE_HEAD_MASK); 166 | THOR_ASSERT(sys, head); 167 | 168 | // Either this was the only thread on the queue, in which case the queue can 169 | // be deleted, or there are still more threads on the queue, in which case we 170 | // need to create a new head for the queue. 171 | auto new_head = head->next; 172 | if (new_head) { 173 | new_head->tail = head->tail; 174 | } 175 | 176 | // Change the queue head, possibly removing it if new_head is nullptr. Like 177 | // in lock_slow, no CAS needed since we own the queue lock and the lock itself 178 | // so nothing about the lock can actually be changed at this point in time. 179 | current_word = word_.load(); 180 | THOR_ASSERT(sys, current_word & IS_LOCKED_BIT); 181 | THOR_ASSERT(sys, current_word & IS_QUEUE_LOCKED_BIT); 182 | THOR_ASSERT(sys, (current_word & ~QUEUE_HEAD_MASK) == reinterpret_cast
(head)); 183 | auto new_word = current_word; 184 | new_word &= ~IS_LOCKED_BIT; // Release the lock 185 | new_word &= ~IS_QUEUE_LOCKED_BIT; // Release the queue lock 186 | new_word &= QUEUE_HEAD_MASK; // Clear out the queue head 187 | new_word |= reinterpret_cast
(new_head); // Install new queue head 188 | word_.store(new_word); 189 | 190 | // The lock is now available to be locked. We just have to wake up the old 191 | // queue head. This has to be done very carefully though. 192 | head->next = nullptr; 193 | head->tail = nullptr; 194 | 195 | // This scope can run either before or during the Waiter's critical section 196 | // acquired in lock_slow. 197 | { 198 | // So be sure to hold the lock across the call to signal because a spurious 199 | // wakeup could cause the thread at the head of the queue to exit and delete 200 | // head before we have a change to mutate it here. 201 | sys.scheduler.mutex_lock(sys, head->mutex); 202 | head->park = false; 203 | sys.scheduler.cond_signal(sys, head->cond); 204 | sys.scheduler.mutex_unlock(sys, head->mutex); 205 | } 206 | } 207 | 208 | } // namespace Thor -------------------------------------------------------------------------------- /src/util/lock.h: -------------------------------------------------------------------------------- 1 | #ifndef THOR_LOCK_H 2 | #define THOR_LOCK_H 3 | #include "util/atomic.h" 4 | 5 | namespace Thor { 6 | 7 | struct System; 8 | 9 | // Low-level adaptive mutex that requires sizeof(void*) of storage. Has a fast 10 | // path that is similar to a spin-lock but doesn't burn CPU and a slow path that 11 | // is similar to the mutex in any language you've used, except uses far less 12 | // storage and amoritizs the cost of constructing system sync primitives which 13 | // require heap allocation. 14 | struct Lock { 15 | void lock(System& sys) { 16 | if (word_.compare_exchange_weak(0, IS_LOCKED_BIT, MemoryOrder::acquire)) { 17 | return; 18 | } 19 | lock_slow(sys); 20 | } 21 | 22 | void unlock(System& sys) { 23 | if (word_.compare_exchange_weak(IS_LOCKED_BIT, 0, MemoryOrder::release)) { 24 | return; 25 | } 26 | unlock_slow(sys); 27 | } 28 | 29 | private: 30 | static inline constexpr const Address IS_LOCKED_BIT = 1; 31 | static inline constexpr const Address IS_QUEUE_LOCKED_BIT = 2; 32 | static inline constexpr const Address QUEUE_HEAD_MASK = 3; 33 | 34 | void lock_slow(System& sys); 35 | void unlock_slow(System& sys); 36 | 37 | Atomic
word_; 38 | }; 39 | 40 | } // namespace Thor 41 | 42 | #endif // THOR_LOCK_H -------------------------------------------------------------------------------- /src/util/map.h: -------------------------------------------------------------------------------- 1 | #ifndef THOR_MAP_H 2 | #define THOR_MAP_H 3 | #include "util/allocator.h" 4 | #include "util/hash.h" 5 | 6 | namespace Thor { 7 | 8 | template 9 | struct Map { 10 | using H = Hash; 11 | constexpr Map(Allocator& allocator) 12 | : allocator_{allocator} 13 | { 14 | } 15 | Map(Map&& other) 16 | : allocator_{other.allocator_} 17 | , ks_{exchange(other.ks_, nullptr)} 18 | , vs_{exchange(other.vs_, nullptr)} 19 | , hs_{exchange(other.hs_, nullptr)} 20 | , length_{exchange(other.length_, 0)} 21 | , capacity_{exchange(other.capacity_, 0)} 22 | { 23 | } 24 | Map& operator=(Map&& other) { 25 | return *new (drop(), Nat{}) Map{move(other)}; 26 | } 27 | void reset() { 28 | drop(); 29 | length_ = 0; 30 | capacity_ = 0; 31 | ks_ = nullptr; 32 | vs_ = nullptr; 33 | hs_ = nullptr; 34 | } 35 | ~Map() { drop(); } 36 | [[nodiscard]] THOR_FORCEINLINE constexpr auto length() const { return length_; } 37 | [[nodiscard]] THOR_FORCEINLINE constexpr auto capacity() const { return capacity_; } 38 | struct Tuple { 39 | const K& k; 40 | V& v; 41 | }; 42 | Maybe find(const K& k) { 43 | if (length_ == 0) return {}; 44 | auto h = hash(k); 45 | h |= h == 0; 46 | auto q = capacity_ - 1; 47 | auto m = h & q; 48 | while (hs_[m] != 0) { 49 | if (ks_[m] == k) { 50 | return Tuple { ks_[m], vs_[m] }; 51 | } 52 | m = (m + 1) & q; 53 | } 54 | return {}; 55 | } 56 | Bool insert(K k, V v) { 57 | if (length_ >= capacity_ / 2) { 58 | if (!expand()) { 59 | return false; 60 | } 61 | } 62 | auto h = hash(k); 63 | h |= h == 0; 64 | assign(ks_, vs_, hs_, forward(k), forward(v), h, capacity_); 65 | length_++; 66 | return true; 67 | } 68 | [[nodiscard]] THOR_FORCEINLINE constexpr Allocator& allocator() const { 69 | return allocator_; 70 | } 71 | struct Iterator { 72 | constexpr Iterator(Map& map, Ulen n) 73 | : map_{map} 74 | , n_{n} 75 | { 76 | while (n_ < map_.capacity_ && map_.hs_[n_] == 0) n_++; 77 | } 78 | THOR_FORCEINLINE constexpr Tuple operator*() { 79 | return { map_.ks_[n_], map_.vs_[n_] }; 80 | } 81 | constexpr Iterator& operator++() { 82 | do ++n_; while (n_ < map_.capacity_ && map_.hs_[n_] == 0); 83 | return *this; 84 | } 85 | [[nodiscard]] THOR_FORCEINLINE friend Bool operator==(const Iterator& lhs, const Iterator& rhs) { return lhs.n_ == rhs.n_; } 86 | [[nodiscard]] THOR_FORCEINLINE friend Bool operator!=(const Iterator& lhs, const Iterator& rhs) { return lhs.n_ != rhs.n_; } 87 | private: 88 | Map& map_; 89 | Ulen n_; 90 | }; 91 | 92 | THOR_FORCEINLINE constexpr Iterator begin() { return Iterator{*this, 0}; } 93 | THOR_FORCEINLINE constexpr Iterator end() { return Iterator{*this, capacity_}; } 94 | 95 | private: 96 | friend struct Iterator; 97 | 98 | static Uint64 assign(K* ks, V* vs, H* hs, K&& k, V&& v, H h, Ulen capacity) { 99 | auto q = capacity - 1; 100 | auto m = h & q; 101 | while (hs[m] != 0) { 102 | if (ks[m] == k) { 103 | ks[m] = forward(k); 104 | vs[m] = forward(v); 105 | hs[m] = h; 106 | return m; 107 | } 108 | m = (m + 1) & q; 109 | } 110 | ks[m] = forward(k); 111 | vs[m] = forward(v); 112 | hs[m] = h; 113 | return m; 114 | } 115 | Bool expand() { 116 | const auto old_capacity = capacity_; 117 | const auto new_capacity = (old_capacity ? old_capacity : 1) * 2; 118 | auto ks = allocator_.allocate(new_capacity, false); 119 | auto vs = allocator_.allocate(new_capacity, false); 120 | auto hs = allocator_.allocate(new_capacity, true); 121 | for (Ulen i = 0; i < old_capacity; i++) if (hs_[i]) { 122 | assign(ks, vs, hs, move(ks_[i]), move(vs_[i]), hs_[i], new_capacity); 123 | } 124 | drop(); 125 | ks_ = ks; 126 | vs_ = vs; 127 | hs_ = hs; 128 | capacity_ = new_capacity; 129 | return true; 130 | } 131 | Map* drop() { 132 | if constexpr (!TriviallyDestructible || !TriviallyDestructible) { 133 | for (Ulen i = 0; i < capacity_; i++) if (hs_[i]) { 134 | if constexpr (!TriviallyDestructible) ks_[i].~K(); 135 | if constexpr (!TriviallyDestructible) vs_[i].~V(); 136 | } 137 | } 138 | allocator_.deallocate(ks_, capacity_); 139 | allocator_.deallocate(vs_, capacity_); 140 | allocator_.deallocate(hs_, capacity_); 141 | return this; 142 | } 143 | Allocator& allocator_; 144 | K* ks_ = nullptr; 145 | V* vs_ = nullptr; 146 | Uint64* hs_ = nullptr; 147 | Ulen length_ = 0; 148 | Ulen capacity_ = 0; 149 | }; 150 | 151 | } // Thor 152 | 153 | #endif // THOR_MAP_H -------------------------------------------------------------------------------- /src/util/maybe.h: -------------------------------------------------------------------------------- 1 | #ifndef THOR_MAYBE_H 2 | #define THOR_MAYBE_H 3 | #include "util/exchange.h" 4 | #include "util/traits.h" 5 | namespace Thor { 6 | 7 | struct Allocator; 8 | 9 | template 10 | struct Maybe; 11 | 12 | // Concept that checks for T::copy(allocator) method which returns a Maybe. 13 | template 14 | concept MaybeCopyable = requires(const T& value) { 15 | { value.copy(declval()) } -> Same>; 16 | }; 17 | 18 | // Simple optional type, use like Maybe, check if valid with if (maybe) or 19 | // if (maybe.is_valid()). The underlying T can be extracted (unboxed) with the 20 | // use of the -> or * operators, i.e maybe->thing or (*maybe).thing, where thing 21 | // is a field of T. 22 | template 23 | struct Maybe { 24 | constexpr Maybe() 25 | : as_nat_{} 26 | { 27 | } 28 | constexpr Maybe(Unit) 29 | : as_nat_{} 30 | { 31 | } 32 | constexpr Maybe(T&& value) 33 | : as_value_{move(value)} 34 | , valid_{true} 35 | { 36 | } 37 | constexpr Maybe(Maybe&& other) 38 | : valid_{exchange(other.valid_, false)} 39 | { 40 | if (valid_) { 41 | new (&as_value_, Nat{}) T{move(other.as_value_)}; 42 | if constexpr (!TriviallyDestructible) { 43 | other.as_value_.~T(); 44 | } 45 | } 46 | } 47 | constexpr Maybe& operator=(T&& value) { 48 | return *new(drop(), Nat{}) Maybe{move(value)}; 49 | } 50 | constexpr Maybe& operator=(Maybe&& value) { 51 | return *new(drop(), Nat{}) Maybe{move(value)}; 52 | } 53 | 54 | Maybe copy() 55 | requires CopyConstructible 56 | { 57 | if (!is_valid()) { 58 | return {}; 59 | } 60 | return Maybe { as_value_ }; 61 | } 62 | 63 | Maybe copy(Allocator& allocator) 64 | requires MaybeCopyable 65 | { 66 | if (!is_valid()) { 67 | return {}; 68 | } 69 | return as_value_.copy(allocator); 70 | } 71 | 72 | [[nodiscard]] THOR_FORCEINLINE constexpr auto is_valid() const { return valid_; } 73 | [[nodiscard]] THOR_FORCEINLINE constexpr operator Bool() const { return is_valid(); } 74 | [[nodiscard]] THOR_FORCEINLINE constexpr T& value() { return as_value_; } 75 | [[nodiscard]] THOR_FORCEINLINE constexpr const T& value() const { return as_value_; } 76 | [[nodiscard]] THOR_FORCEINLINE constexpr T& operator*() { return as_value_; } 77 | [[nodiscard]] THOR_FORCEINLINE constexpr const T& operator*() const { return as_value_; } 78 | [[nodiscard]] THOR_FORCEINLINE constexpr T* operator->() { return &as_value_; } 79 | [[nodiscard]] THOR_FORCEINLINE constexpr const T* operator->() const { return &as_value_; } 80 | 81 | ~Maybe() 82 | requires (!TriviallyDestructible) 83 | { 84 | drop(); 85 | } 86 | 87 | constexpr ~Maybe() requires TriviallyDestructible = default; 88 | 89 | THOR_FORCEINLINE void reset() { 90 | drop(); 91 | valid_ = false; 92 | } 93 | 94 | template 95 | void emplace(Ts&&... args) { 96 | new (drop(), Nat{}) Maybe{T{forward(args)...}}; 97 | } 98 | private: 99 | THOR_FORCEINLINE constexpr Maybe* drop() { 100 | if constexpr (!TriviallyDestructible) { 101 | if (is_valid()) as_value_.~T(); 102 | } 103 | return this; 104 | } 105 | union { 106 | Nat as_nat_; 107 | T as_value_; 108 | }; 109 | Bool valid_ = false; 110 | }; 111 | 112 | } // namespace Thor 113 | 114 | #endif // THOR_MAYBE_H -------------------------------------------------------------------------------- /src/util/move.h: -------------------------------------------------------------------------------- 1 | #ifndef THOR_MOVE_H 2 | #define THOR_MOVE_H 3 | #include "util/traits.h" 4 | 5 | namespace Thor { 6 | 7 | template 8 | THOR_FORCEINLINE constexpr RemoveReference&& move(T&& arg) { 9 | return static_cast&&>(arg); 10 | } 11 | 12 | } // namespace Thor 13 | 14 | #endif // THOR_MOVE_H -------------------------------------------------------------------------------- /src/util/pool.cpp: -------------------------------------------------------------------------------- 1 | #include "util/pool.h" 2 | #include "util/slice.h" 3 | #include "util/stream.h" 4 | namespace Thor { 5 | 6 | // The serialized representation of the Pool 7 | struct PoolHeader { 8 | Uint8 magic[4]; // 'Pool' 9 | Uint32 version; 10 | Uint64 length; 11 | Uint64 size; 12 | Uint64 capacity; 13 | }; 14 | // Following the header: 15 | // Uint32 used[PoolHeader::capacity / BITS] 16 | // Uint8 data[PoolHeader::size * PoolHeader::capacity] 17 | static_assert(sizeof(PoolHeader) == 32); 18 | 19 | Maybe Pool::create(Allocator& allocator, Ulen size, Ulen capacity) { 20 | // Ensure capacity is a multiple of BITS 21 | capacity = ((capacity + (BITS - 1)) / BITS) * BITS; 22 | auto data = allocator.allocate(size * capacity, true); 23 | if (!data) { 24 | return {}; 25 | } 26 | auto used = allocator.allocate(capacity / BITS, true); 27 | if (!used) { 28 | allocator.deallocate(data, size * capacity); 29 | return {}; 30 | } 31 | return Pool { 32 | allocator, 33 | size, 34 | 0_ulen, 35 | capacity, 36 | data, 37 | used 38 | }; 39 | } 40 | 41 | Maybe Pool::load(Allocator& allocator, Stream& stream) { 42 | PoolHeader header; 43 | if (!stream.read(Slice{&header, 1}.cast())) { 44 | return {}; 45 | } 46 | if (Slice{header.magic} != Slice{"pool"}.cast()) { 47 | return {}; 48 | } 49 | if (header.version != 1) { 50 | return {}; 51 | } 52 | const auto n_words = header.capacity / BITS; 53 | const auto n_bytes = header.size * header.capacity; 54 | auto used = allocator.allocate(n_words, false); 55 | auto data = allocator.allocate(n_bytes, false); 56 | if (!used || !data) { 57 | allocator.deallocate(used, n_words); 58 | allocator.deallocate(data, n_bytes); 59 | return {}; 60 | } 61 | if (!stream.read(Slice{used, n_words}.cast()) || 62 | !stream.read(Slice{data, n_bytes}.cast())) 63 | { 64 | allocator.deallocate(used, n_words); 65 | allocator.deallocate(data, n_bytes); 66 | return {}; 67 | } 68 | return Pool { 69 | allocator, 70 | Ulen(header.size), 71 | Ulen(header.length), 72 | Ulen(header.capacity), 73 | data, 74 | used 75 | }; 76 | } 77 | 78 | Bool Pool::save(Stream& stream) const { 79 | PoolHeader header = { 80 | .magic = { 'p', 'o', 'o', 'l' }, 81 | .version = Uint64(1), 82 | .length = Uint64(length_), 83 | .size = Uint64(size_), 84 | .capacity = Uint64(capacity_), 85 | }; 86 | return stream.write(Slice{&header, 1}.cast()) 87 | && stream.write(Slice{used_, capacity_ / BITS}.cast()) 88 | && stream.write(Slice{data_, size_ * capacity_}.cast()); 89 | } 90 | 91 | Pool::Pool(Pool&& other) 92 | : allocator_{other.allocator_} 93 | , size_{exchange(other.size_, 0)} 94 | , length_{exchange(other.length_, 0)} 95 | , capacity_{exchange(other.capacity_, 0)} 96 | , data_{exchange(other.data_, nullptr)} 97 | , used_{exchange(other.used_, nullptr)} 98 | , last_{exchange(other.last_, 0)} 99 | { 100 | } 101 | 102 | #if defined(THOR_COMPILER_MSVC) 103 | 104 | typedef unsigned long DWORD; 105 | 106 | // Count the number of trailing zero bits in [value] which is the same as 107 | // giving the index to the first non-zero bit. 108 | static inline Uint32 count_trailing_zeros(Uint64 value) { 109 | DWORD trailing_zero = 0; 110 | if (_BitScanForward(&trailing_zero, value)) { 111 | return trailing_zero; 112 | } 113 | return 64; 114 | } 115 | #else 116 | static inline Uint32 count_trailing_zeros(Uint64 value) { 117 | return __builtin_ctzll(value); 118 | } 119 | #endif 120 | 121 | Maybe Pool::allocate() { 122 | const auto n_words = Uint32(capacity_ / BITS); 123 | const auto w_index = last_; 124 | if (auto scan = ~used_[w_index]) { 125 | auto b_index = count_trailing_zeros(scan); 126 | used_[w_index] |= Word(1) << b_index; 127 | length_++; 128 | return PoolRef { w_index * BITS + b_index }; 129 | } 130 | for (Uint32 w_index = n_words - 1; w_index < n_words; w_index--) { 131 | if (auto scan = ~used_[w_index]) { 132 | auto b_index = count_trailing_zeros(scan); 133 | used_[w_index] |= Word(1) << b_index; 134 | length_++; 135 | last_ = w_index; 136 | return PoolRef { w_index * BITS + b_index }; 137 | } 138 | } 139 | return {}; // Out of memory. 140 | } 141 | 142 | void Pool::deallocate(PoolRef ref) { 143 | const auto w_index = ref.index / BITS; 144 | const auto b_index = ref.index % BITS; 145 | used_[w_index] &= ~(Word(1) << b_index); 146 | length_--; 147 | } 148 | 149 | } // namespace Thor -------------------------------------------------------------------------------- /src/util/pool.h: -------------------------------------------------------------------------------- 1 | #ifndef THOR_POOL_H 2 | #define THOR_POOL_H 3 | // #include "util/types.h" 4 | #include "util/maybe.h" 5 | #include "util/allocator.h" 6 | 7 | namespace Thor { 8 | 9 | struct PoolRef { 10 | Uint32 index; 11 | }; 12 | 13 | struct Allocator; 14 | 15 | // Pool allocator, used to allocate objects of a fixed size. The pool also has a 16 | // fixed capacity (# of objects). Does not communicate using pointers but rather 17 | // PoolRef (plain typed index). Allocate object with allocate(), deallocate with 18 | // deallocate(). The address (pointer) of the object can be looked-up by passing 19 | // the PoolRef to operator[] like a key. 20 | struct Stream; 21 | 22 | struct Pool { 23 | static Maybe create(Allocator& allocator, Ulen size, Ulen capacity); 24 | 25 | static Maybe load(Allocator& allocator, Stream& stream); 26 | Bool save(Stream& stream) const; 27 | 28 | Pool(Pool&& other); 29 | ~Pool() { drop(); } 30 | 31 | [[nodiscard]] THOR_FORCEINLINE constexpr auto length() const { return length_; } 32 | [[nodiscard]] THOR_FORCEINLINE constexpr auto is_empty() const { return length_ == 0; } 33 | 34 | constexpr Pool(const Pool&) = delete; 35 | constexpr Pool& operator=(const Pool&) = delete; 36 | 37 | Pool& operator=(Pool&& other) { 38 | return *new (drop(), Nat{}) Pool{move(other)}; 39 | } 40 | 41 | Maybe allocate(); 42 | void deallocate(PoolRef ref); 43 | 44 | THOR_FORCEINLINE constexpr auto operator[](PoolRef ref) { return data_ + size_ * ref.index; } 45 | THOR_FORCEINLINE constexpr auto operator[](PoolRef ref) const { return data_ + size_ * ref.index; } 46 | 47 | private: 48 | using Word = Uint64; 49 | static constexpr const auto BITS = Uint32(sizeof(Word) * 8); 50 | constexpr Pool(Allocator& allocator, Ulen size, Ulen length, Ulen capacity, Uint8* data, Word* used) 51 | : allocator_{allocator} 52 | , size_{size} 53 | , length_{length} 54 | , capacity_{capacity} 55 | , data_{data} 56 | , used_{used} 57 | , last_{0} 58 | { 59 | } 60 | 61 | Pool* drop() { 62 | allocator_.deallocate(data_, size_ * capacity_); 63 | allocator_.deallocate(used_, capacity_ / BITS); 64 | return this; 65 | } 66 | 67 | Allocator& allocator_; 68 | Ulen size_; // Size of an object in the pool 69 | Ulen length_; // # of objects in the pool 70 | Ulen capacity_; // Always a multiple of 32 (max # of objects in pool) 71 | Uint8* data_; // Object memory 72 | Word* used_; // Bitset where bit N indicates object N is in-use or not. 73 | Uint32 last_; // Last w_index 74 | }; 75 | 76 | } 77 | 78 | #endif // THOR_POOL_H -------------------------------------------------------------------------------- /src/util/slab.cpp: -------------------------------------------------------------------------------- 1 | #include "util/slab.h" 2 | #include "util/stream.h" 3 | 4 | namespace Thor { 5 | 6 | struct SlabHeader { 7 | Uint8 magic[4]; // slab 8 | Uint32 version; 9 | Uint64 size; 10 | Uint64 capacity; 11 | Uint64 caches; 12 | }; 13 | // Following the header: 14 | // Uint32 used[SlabHeader::capacity / 32] 15 | // Pool pools[] 16 | // 17 | // Only pools that are valid are stored. Active pools are indicated by the used 18 | // bitset. That is ((used[i/32] & (1 << (i%32)) != 0 indicates if pool i exists. 19 | Maybe Slab::load(Allocator& allocator, Stream& stream) { 20 | SlabHeader header; 21 | if (!stream.read(Slice{&header, 1}.cast())) { 22 | return {}; 23 | } 24 | if (Slice{header.magic} != Slice{"slab"}.cast()) { 25 | return {}; 26 | } 27 | if (header.version != 1) { 28 | return {}; 29 | } 30 | ScratchAllocator<1024> scratch{allocator}; 31 | auto n_words = header.capacity / 32; 32 | auto used = scratch.allocate(n_words, true); 33 | if (!used) { 34 | return {}; 35 | } 36 | if (!stream.read(Slice{used, n_words}.cast())) { 37 | return {}; 38 | } 39 | auto n_caches = Ulen(header.caches); 40 | Array> caches{allocator}; 41 | if (!caches.resize(n_caches)) { 42 | return {}; 43 | } 44 | for (Ulen i = 0; i < n_caches; i++) { 45 | const auto w_index = Uint32(i / 32); 46 | const auto b_index = Uint32(i % 32); 47 | if ((used[w_index] & (1_u32 << b_index)) != 0) { 48 | if (auto cache = Pool::load(allocator, stream)) { 49 | caches[i] = move(*cache); 50 | } else { 51 | return {}; 52 | } 53 | } 54 | } 55 | return Slab { 56 | move(caches), 57 | Ulen(header.size), 58 | Ulen(header.capacity) 59 | }; 60 | } 61 | 62 | Bool Slab::save(Stream& stream) const { 63 | SlabHeader header = { 64 | .magic = { 's', 'l', 'a', 'b' }, 65 | .version = 1, 66 | .size = Uint64(size_), 67 | .capacity = Uint64(capacity_), 68 | .caches = Uint64(caches_.length()), 69 | }; 70 | ScratchAllocator<1024> scratch{caches_.allocator()}; 71 | auto n_words = capacity_ / 32; 72 | auto used = scratch.allocate(n_words, true); 73 | if (!used) { 74 | return false; 75 | } 76 | Ulen i = 0; 77 | for (const auto& cache : caches_) { 78 | if (cache) { 79 | const auto w_index = Uint32(i / 32); 80 | const auto b_index = Uint32(i % 32); 81 | used[w_index] |= 1_u32 << b_index; 82 | } 83 | i++; 84 | } 85 | if (!stream.write(Slice{&header, 1}.cast()) 86 | || !stream.write(Slice{used, n_words}.cast())) 87 | { 88 | return false; 89 | } 90 | for (const auto& cache : caches_) { 91 | if (cache && !cache->save(stream)) { 92 | return false; 93 | } 94 | } 95 | return true; 96 | } 97 | 98 | Maybe Slab::allocate() { 99 | const auto n_caches = caches_.length(); 100 | for (Ulen i = n_caches - 1; i < n_caches; i--) { 101 | if (auto& cache = caches_[i]; cache.is_valid()) { 102 | if (auto c_ref = cache->allocate()) { 103 | return SlabRef { Uint32(i * capacity_) + c_ref->index }; 104 | } 105 | } 106 | } 107 | auto pool = Pool::create(caches_.allocator(), size_, capacity_); 108 | if (!pool) { 109 | return {}; 110 | } 111 | // Search for an empty Pool in the caches array 112 | for (auto& cache : caches_) { 113 | if (!cache.is_valid()) { 114 | cache = move(*pool); 115 | return allocate(); 116 | } 117 | } 118 | // No empty Pool in caches array, append a new Pool. 119 | if (!caches_.push_back(move(*pool))) { 120 | return {}; 121 | } 122 | return allocate(); 123 | } 124 | 125 | void Slab::deallocate(SlabRef slab_ref) { 126 | const auto cache_idx = Uint32(slab_ref.index / capacity_); 127 | const auto cache_ref = Uint32(slab_ref.index % capacity_); 128 | auto* cache = &caches_[cache_idx]; 129 | (*cache)->deallocate(PoolRef { cache_ref }); 130 | while (!caches_.is_empty() && (*cache)->is_empty()) { 131 | if (cache != &caches_.last()) { 132 | cache->reset(); 133 | break; 134 | } 135 | caches_.pop_back(); 136 | cache = &caches_.last(); 137 | if (!cache->is_valid()) { 138 | break; 139 | } 140 | } 141 | } 142 | 143 | } // namespace Thor -------------------------------------------------------------------------------- /src/util/slab.h: -------------------------------------------------------------------------------- 1 | #ifndef THOR_SLAB_H 2 | #define THOR_SLAB_H 3 | #include "util/pool.h" 4 | #include "util/array.h" 5 | 6 | namespace Thor { 7 | 8 | struct SlabRef { 9 | Uint32 index; 10 | }; 11 | 12 | // Simple Slab allocator. Works exactly like Pool except doesn't have a fixed 13 | // capacity. Instead you specify a per-pool fixed capacity and when the pool the 14 | // slab uses is full, it allocates another pool with the same fixed capacity. 15 | // These pools are typically called "caches" in the slab allocator literature. 16 | // Does not communicate using pointers but rather SlabRef (plain typed index). 17 | // Allocate object with allocate(), deallocate with deallocate(). The address 18 | // (pointer) of the object can be looked-up by passing the SlabRef to operator[] 19 | // like a key. 20 | struct Stream; 21 | struct Slab { 22 | constexpr Slab(Allocator& allocator, Ulen size, Ulen capacity) 23 | : caches_{allocator} 24 | , size_{size} 25 | , capacity_{capacity} 26 | { 27 | } 28 | static Maybe load(Allocator& allocator, Stream& stream); 29 | Bool save(Stream& stream) const; 30 | Maybe allocate(); 31 | void deallocate(SlabRef slab_ref); 32 | THOR_FORCEINLINE constexpr Uint8* operator[](SlabRef slab_ref) { 33 | const auto cache_idx = Uint32(slab_ref.index / capacity_); 34 | const auto cache_ref = Uint32(slab_ref.index % capacity_); 35 | return (*caches_[cache_idx])[PoolRef { cache_ref }]; 36 | } 37 | THOR_FORCEINLINE constexpr const Uint8* operator[](SlabRef slab_ref) const { 38 | const auto cache_idx = Uint32(slab_ref.index / capacity_); 39 | const auto cache_ref = Uint32(slab_ref.index % capacity_); 40 | return (*caches_[cache_idx])[PoolRef { cache_ref }]; 41 | } 42 | private: 43 | Slab(Array>&& caches, Ulen size, Ulen capacity) 44 | : caches_{move(caches)} 45 | , size_{size} 46 | , capacity_{capacity} 47 | { 48 | } 49 | Array> caches_; 50 | Ulen size_; 51 | Ulen capacity_; 52 | }; 53 | 54 | } // namespace Thor 55 | 56 | #endif // THOR_SLAB_H -------------------------------------------------------------------------------- /src/util/slice.h: -------------------------------------------------------------------------------- 1 | #ifndef THOR_SLICE_H 2 | #define THOR_SLICE_H 3 | #include "util/types.h" 4 | #include "util/exchange.h" 5 | #include "util/hash.h" 6 | 7 | namespace Thor { 8 | 9 | // Slice is a convenience type around a pointer and a length. Think of it like a 10 | // span or a view. Specialization for T = const char is implicitly provided so 11 | // that Slice is the same as StringView. 12 | template 13 | struct Slice { 14 | constexpr Slice() = default; 15 | constexpr Slice(T* data, Ulen length) 16 | : data_{data} 17 | , length_{length} 18 | { 19 | } 20 | 21 | template 22 | constexpr Slice(T (&data)[E]) 23 | : data_{data} 24 | , length_{E} 25 | { 26 | if constexpr (is_same) { 27 | length_--; 28 | } 29 | } 30 | 31 | constexpr Slice(const Slice& other) 32 | : data_{other.data_} 33 | , length_{other.length_} 34 | { 35 | } 36 | 37 | constexpr Slice& operator=(const Slice&) = default; 38 | constexpr Slice(Slice&& other) 39 | : data_{exchange(other.data_, nullptr)} 40 | , length_{exchange(other.length_, 0)} 41 | { 42 | } 43 | 44 | [[nodiscard]] THOR_FORCEINLINE constexpr T& operator[](Ulen index) { return data_[index]; } 45 | [[nodiscard]] THOR_FORCEINLINE constexpr const T& operator[](Ulen index) const { return data_[index]; } 46 | 47 | constexpr Slice slice(Ulen offset) const { 48 | return Slice{data_ + offset, length_ - offset}; 49 | } 50 | constexpr Slice truncate(Ulen length) const { 51 | return Slice{data_, length}; 52 | } 53 | 54 | // Just enough to make range based for loops work 55 | [[nodiscard]] THOR_FORCEINLINE constexpr T* begin() { return data_; } 56 | [[nodiscard]] THOR_FORCEINLINE constexpr const T* begin() const { return data_; } 57 | [[nodiscard]] THOR_FORCEINLINE constexpr T* end() { return data_ + length_; } 58 | [[nodiscard]] THOR_FORCEINLINE constexpr const T* end() const { return data_ + length_; } 59 | 60 | [[nodiscard]] THOR_FORCEINLINE constexpr auto length() const { return length_; } 61 | [[nodiscard]] THOR_FORCEINLINE constexpr auto is_empty() const { return length_ == 0; } 62 | 63 | [[nodiscard]] THOR_FORCEINLINE constexpr T* data() { return data_; } 64 | [[nodiscard]] THOR_FORCEINLINE constexpr const T* data() const { return data_; } 65 | 66 | template 67 | THOR_FORCEINLINE Slice cast() { 68 | const auto ptr = reinterpret_cast(data_); 69 | return Slice { ptr, (length_ * sizeof(T)) / sizeof(U) }; 70 | } 71 | 72 | template 73 | THOR_FORCEINLINE Slice cast() const { 74 | const auto ptr = reinterpret_cast(data_); 75 | return Slice { ptr, (length_ * sizeof(T)) / sizeof(U) }; 76 | } 77 | 78 | [[nodiscard]] friend constexpr Bool operator==(const Slice& lhs, const Slice& rhs) { 79 | const auto lhs_len = lhs.length(); 80 | const auto rhs_len = rhs.length(); 81 | if (lhs_len != rhs_len) { 82 | return false; 83 | } 84 | if (lhs.data() == rhs.data()) { 85 | return true; 86 | } 87 | for (Ulen i = 0; i < lhs_len; i++) { 88 | if (lhs[i] != rhs[i]) { 89 | return false; 90 | } 91 | } 92 | return true; 93 | } 94 | 95 | constexpr Hash hash(Hash h = FNV_OFFSET) const { 96 | for (Ulen i = 0; i < length_; i++) { 97 | h = Thor::hash(data_[i], h); 98 | } 99 | return h; 100 | } 101 | 102 | private: 103 | T* data_ = nullptr; 104 | Ulen length_ = 0; 105 | }; 106 | 107 | } // namespace Thor 108 | 109 | #endif // THOR_SLICE_H -------------------------------------------------------------------------------- /src/util/stream.cpp: -------------------------------------------------------------------------------- 1 | #include "util/stream.h" 2 | 3 | namespace Thor { 4 | 5 | Maybe FileStream::open(System& sys, StringView name, File::Access access) { 6 | auto file = File::open(sys, name, access); 7 | if (!file) { 8 | return {}; 9 | } 10 | return FileStream { move(*file) }; 11 | } 12 | 13 | Bool FileStream::write(Slice data) { 14 | const auto nb = file_.write(offset_, data); 15 | offset_ += nb; 16 | return nb == data.length(); 17 | } 18 | 19 | Bool FileStream::read(Slice data) { 20 | const auto nb = file_.read(offset_, data); 21 | offset_ += nb; 22 | return nb == data.length(); 23 | } 24 | 25 | Uint64 FileStream::tell() const { 26 | return offset_; 27 | } 28 | 29 | } // namespace Thor -------------------------------------------------------------------------------- /src/util/stream.h: -------------------------------------------------------------------------------- 1 | #ifndef THOR_STREAM_H 2 | #define THOR_STREAM_H 3 | #include "util/file.h" 4 | 5 | namespace Thor { 6 | 7 | struct Stream { 8 | virtual ~Stream() {} 9 | virtual Bool write(Slice data) = 0; 10 | virtual Bool read(Slice data) = 0; 11 | virtual Uint64 tell() const = 0; 12 | }; 13 | 14 | struct FileStream : Stream { 15 | FileStream(FileStream&& other) 16 | : Stream{move(other)} 17 | , file_{move(other.file_)} 18 | , offset_{exchange(other.offset_, 0)} 19 | { 20 | } 21 | static Maybe open(System& sys, StringView name, File::Access access); 22 | virtual Bool write(Slice data); 23 | virtual Bool read(Slice data); 24 | virtual Uint64 tell() const; 25 | private: 26 | FileStream(File&& file) 27 | : file_{move(file)} 28 | { 29 | } 30 | File file_; 31 | Uint64 offset_ = 0; 32 | }; 33 | 34 | } // namespace Thor 35 | 36 | #endif // THOR_STREAM_H -------------------------------------------------------------------------------- /src/util/string.cpp: -------------------------------------------------------------------------------- 1 | #include // TODO(dweiler): remove 2 | #include // TODO(dweiler): remove 3 | #include 4 | 5 | #include "util/string.h" 6 | #include "util/stream.h" 7 | 8 | namespace Thor { 9 | 10 | // StringBuilder 11 | void StringBuilder::put(char ch) { 12 | error_ = !build_.push_back(ch); 13 | last_ = { &build_.last(), 1 }; 14 | } 15 | 16 | void StringBuilder::put(StringView view) { 17 | const auto offset = build_.length(); 18 | if (!build_.resize(offset + view.length())) { 19 | error_ = true; 20 | return; 21 | } 22 | const auto len = view.length(); 23 | for (Ulen i = 0; i < len; i++) { 24 | build_[offset + i] = view[i]; 25 | } 26 | last_ = { &build_[offset], len }; 27 | } 28 | 29 | void StringBuilder::put(Float64 value) { 30 | char buffer[DBL_MANT_DIG + DBL_DECIMAL_DIG * 2 + 1]; 31 | auto n = snprintf(buffer, sizeof buffer, "%g", value); 32 | if (n <= 0) { 33 | error_ = true; 34 | return; 35 | } 36 | put(StringView { buffer, Ulen(n) }); 37 | } 38 | 39 | void StringBuilder::put(Uint64 value) { 40 | if (value == 0) { 41 | return put('0'); 42 | } 43 | 44 | Uint64 length = 0; 45 | for (Uint64 v = value; v; v /= 10, length++); 46 | 47 | Ulen offset = build_.length(); 48 | if (!build_.resize(offset + length)) { 49 | error_ = true; 50 | return; 51 | } 52 | 53 | char *const fill = build_.data() + offset; 54 | for (; value; value /= 10) { 55 | fill[--length] = '0' + (value % 10); 56 | } 57 | } 58 | 59 | void StringBuilder::put(Sint64 value) { 60 | if (value < 0) { 61 | put('-'); 62 | put(Uint64(-value)); 63 | } else { 64 | put(Uint64(value)); 65 | } 66 | } 67 | 68 | void StringBuilder::rep(Ulen n, char ch) { 69 | for (Ulen i = 0; i < n; i++) put(ch); 70 | } 71 | 72 | void StringBuilder::lpad(Ulen n, char ch, char pad) { 73 | if (n) rep(n - 1, pad); 74 | put(ch); 75 | } 76 | 77 | void StringBuilder::lpad(Ulen n, StringView view, char pad) { 78 | const auto l = view.length(); 79 | if (n >= l) rep(n - 1, pad); 80 | put(view); 81 | } 82 | 83 | void StringBuilder::rpad(Ulen n, char ch, char pad) { 84 | put(ch); 85 | if (n) rep(n - 1, pad); 86 | } 87 | 88 | void StringBuilder::rpad(Ulen n, StringView view, char pad) { 89 | const auto l = view.length(); 90 | put(view); 91 | if (n >= l) rep(n - l, pad); 92 | } 93 | 94 | void StringBuilder::reset() { 95 | build_.reset(); 96 | error_ = false; 97 | } 98 | 99 | Maybe StringBuilder::result() const { 100 | if (error_) { 101 | return {}; 102 | } 103 | return StringView { build_.slice() }; 104 | } 105 | 106 | // StringTable 107 | StringTable::StringTable(StringTable&& other) 108 | : map_{move(other.map_)} 109 | , data_{exchange(other.data_, nullptr)} 110 | , capacity_{exchange(other.capacity_, 0)} 111 | , length_{exchange(other.length_, 0)} 112 | { 113 | } 114 | 115 | StringRef StringTable::insert(StringView src) { 116 | if (src.length() >= 0xff'ff'ff'ff_u32) { 117 | // Cannot handle strings larger than 4 GiB. 118 | return {}; 119 | } 120 | if (auto find = map_.find(src)) { 121 | // Duplicate string found, reuse it. 122 | return find->v; 123 | } 124 | if (length_ + src.length() >= capacity_ && !grow(src.length())) { 125 | // Out of memory. 126 | return {}; 127 | } 128 | StringRef ref { Uint32(length_), Uint32(src.length()) }; 129 | StringView dst { data_ + length_, src.length() }; 130 | memcpy(data_ + length_, src.data(), src.length()); 131 | if (map_.insert(dst, ref)) { 132 | length_ += src.length(); 133 | return ref; 134 | } 135 | return {}; 136 | } 137 | 138 | Bool StringTable::grow(Ulen additional) { 139 | Map map{allocator()}; 140 | auto old_capacity = capacity_; 141 | auto new_capacity = old_capacity ? old_capacity : 1; 142 | while (length_ + additional >= new_capacity) { 143 | new_capacity *= 2; 144 | } 145 | auto data = allocator().allocate(new_capacity, true); 146 | if (!data) { 147 | return false; 148 | } 149 | for (const auto kv : map_) { 150 | auto k = kv.k; 151 | auto v = kv.v; 152 | auto beg = data + v.offset; 153 | memcpy(beg, k.data(), k.length()); 154 | map.insert(StringView { beg, v.length }, v); 155 | } 156 | drop(); 157 | map_ = move(map); 158 | data_ = data; 159 | capacity_ = new_capacity; 160 | return true; 161 | } 162 | 163 | Maybe StringTable::load(Allocator& allocator, Stream& stream) { 164 | Uint32 length = 0; 165 | if (!stream.read(Slice{&length, 1}.cast()) || length == 0) { 166 | return {}; 167 | } 168 | auto data = allocator.allocate(length, false); 169 | if (!data) { 170 | return {}; 171 | } 172 | if (!stream.read(Slice{data, length}.cast())) { 173 | allocator.deallocate(data, length); 174 | return {}; 175 | } 176 | return StringTable { allocator, data, length }; 177 | } 178 | 179 | Bool StringTable::save(Stream& stream) const { 180 | return stream.write(Slice{&length_, 1}.cast()) 181 | && stream.write(Slice{data_, length_}.cast()); 182 | } 183 | 184 | } // namespace Thor 185 | -------------------------------------------------------------------------------- /src/util/string.h: -------------------------------------------------------------------------------- 1 | #ifndef THOR_STRING_H 2 | #define THOR_STRING_H 3 | #include "util/array.h" 4 | #include "util/maybe.h" 5 | #include "util/map.h" 6 | 7 | namespace Thor { 8 | 9 | using StringView = Slice; 10 | 11 | // Utility for building a string incrementally. 12 | struct StringBuilder { 13 | constexpr StringBuilder(Allocator& allocator) 14 | : build_{allocator} 15 | { 16 | } 17 | void put(char ch); 18 | void put(StringView view); 19 | THOR_FORCEINLINE void put(Float32 v) { put(Float64(v)); } 20 | void put(Float64 v); 21 | THOR_FORCEINLINE void put(Uint8 v) { put(Uint16(v)); } 22 | THOR_FORCEINLINE void put(Uint16 v) { put(Uint32(v)); } 23 | THOR_FORCEINLINE void put(Uint32 v) { put(Uint64(v)); } 24 | void put(Uint64 v); 25 | void put(Sint64 v); 26 | THOR_FORCEINLINE void put(Sint32 v) { put(Sint64(v)); } 27 | THOR_FORCEINLINE void put(Sint16 v) { put(Sint32(v)); } 28 | THOR_FORCEINLINE void put(Sint8 v) { put(Sint16(v)); } 29 | void rep(Ulen n, char ch = ' '); 30 | void lpad(Ulen n, char ch, char pad = ' '); 31 | void lpad(Ulen n, StringView view, char pad = ' '); 32 | void rpad(Ulen n, char ch, char pad = ' '); 33 | void rpad(Ulen n, StringView view, char pad = ' '); 34 | void reset(); 35 | Maybe result() const; 36 | StringView last() const { return last_; } // Last inserted string token 37 | private: 38 | Array build_; 39 | Bool error_ = false; 40 | StringView last_; 41 | }; 42 | 43 | struct StringRef { 44 | constexpr StringRef() = default; 45 | constexpr StringRef(Unit) : StringRef{}{} 46 | constexpr StringRef(Uint32 offset, Uint32 length) 47 | : offset{offset} 48 | , length{length} 49 | { 50 | } 51 | Uint32 offset = 0; 52 | Uint32 length = ~0_u32; 53 | THOR_FORCEINLINE constexpr auto is_valid() const { 54 | return length != ~0_u32; 55 | } 56 | THOR_FORCEINLINE constexpr operator Bool() const { 57 | return is_valid(); 58 | } 59 | }; 60 | 61 | // Limited to no larger than 4 GiB of string data. Odin source files are limited 62 | // to 2 GiB so this shouldn't ever be an issue as the StringTable represents an 63 | // interned representation of identifiers in a single Odin source file. 64 | struct Stream; 65 | 66 | struct StringTable { 67 | constexpr StringTable(Allocator& allocator) 68 | : map_{allocator} 69 | { 70 | } 71 | 72 | static Maybe load(Allocator& allocator, Stream& stream); 73 | Bool save(Stream& stream) const; 74 | 75 | StringTable(StringTable&& other); 76 | 77 | StringTable& operator=(StringTable&& other) { 78 | return *new (drop(), Nat{}) StringTable{move(other)}; 79 | } 80 | 81 | ~StringTable() { drop(); } 82 | 83 | [[nodiscard]] StringRef insert(StringView src); 84 | 85 | THOR_FORCEINLINE constexpr StringView operator[](StringRef ref) const { 86 | return StringView { data_ + ref.offset, ref.length }; 87 | } 88 | 89 | THOR_FORCEINLINE constexpr Allocator& allocator() { 90 | // We don't store a copy of the allocator since it's the same one used for 91 | // both map_ and data_ and since map_ already stores the allocator we can 92 | // just read it from there. 93 | return map_.allocator(); 94 | } 95 | 96 | Slice data() const { return { data_, length_ }; } 97 | 98 | private: 99 | constexpr StringTable(Allocator& allocator, char* data, Uint32 length) 100 | : map_{allocator} 101 | , data_{data} 102 | , capacity_{length} 103 | , length_{length} 104 | { 105 | } 106 | 107 | StringTable* drop() { 108 | allocator().deallocate(data_, capacity_); 109 | return this; 110 | } 111 | 112 | [[nodiscard]] Bool grow(Ulen additional); 113 | 114 | Map map_; 115 | char* data_ = nullptr; 116 | Uint32 capacity_ = 0; 117 | Uint32 length_ = 0; 118 | }; 119 | 120 | } // namespace Thor 121 | 122 | #endif // THOR_STRING_H 123 | -------------------------------------------------------------------------------- /src/util/system.h: -------------------------------------------------------------------------------- 1 | #ifndef THOR_SYSTEM_H 2 | #define THOR_SYSTEM_H 3 | #include "util/string.h" 4 | 5 | namespace Thor { 6 | 7 | struct System; 8 | 9 | struct Filesystem { 10 | struct File; 11 | struct Directory; 12 | 13 | struct Item { 14 | enum class Kind { 15 | FILE, 16 | LINK, 17 | DIR, 18 | }; 19 | StringView name; 20 | Kind kind; 21 | }; 22 | 23 | enum class Access : Uint8 { RD, WR }; 24 | File* (*open_file)(System& sys, StringView name, Access access); 25 | void (*close_file)(System& sys, File* file); 26 | Uint64 (*read_file)(System& sys, File* file, Uint64 offset, Slice data); 27 | Uint64 (*write_file)(System& sys, File* file, Uint64 offset, Slice data); 28 | Uint64 (*tell_file)(System& sys, File* file); 29 | 30 | Directory* (*open_dir)(System& sys, StringView name); 31 | void (*close_dir)(System& sys, Directory*); 32 | Bool (*read_dir)(System& sys, Directory*, Item& item); 33 | }; 34 | 35 | struct Heap { 36 | void *(*allocate)(System& sys, Ulen len, Bool zero); 37 | void (*deallocate)(System& sys, void* addr, Ulen len); 38 | }; 39 | 40 | struct Console { 41 | void (*write)(System& sys, StringView data); 42 | }; 43 | 44 | struct Process { 45 | // NOTE: assert should not return 46 | void (*assert)(System& sys, StringView msg, StringView file, Sint32 line); 47 | }; 48 | 49 | struct Linker { 50 | struct Library; 51 | Library* (*load)(System& sys, StringView name); 52 | void (*close)(System& sys, Library* library); 53 | void (*(*link)(System& sys, Library* library, const char* symbol))(void); 54 | }; 55 | 56 | struct Scheduler { 57 | struct Thread; 58 | struct Mutex; 59 | struct Cond; 60 | 61 | Thread* (*thread_start)(System& sys, void (*fn)(System& sys, void* user), void* user); 62 | void (*thread_join)(System& sys, Thread* thread); 63 | 64 | Mutex* (*mutex_create)(System& sys); 65 | void (*mutex_destroy)(System& sys, Mutex* mutex); 66 | void (*mutex_lock)(System& sys, Mutex* mutex); 67 | void (*mutex_unlock)(System& sys, Mutex* mutex); 68 | 69 | Cond* (*cond_create)(System& sys); 70 | void (*cond_destroy)(System& sys, Cond* cond); 71 | void (*cond_signal)(System& sys, Cond* cond); 72 | void (*cond_broadcast)(System& sys, Cond* cond); 73 | void (*cond_wait)(System& sys, Cond* cond, Mutex* mutex); 74 | 75 | void (*yield)(System& sys); 76 | }; 77 | 78 | struct Chrono { 79 | Float64 (*monotonic_now)(System& sys); 80 | Float64 (*wall_now)(System& sys); 81 | }; 82 | 83 | struct System { 84 | private: 85 | SystemAllocator allocator_; 86 | public: 87 | constexpr System(const Filesystem& filesystem, 88 | const Heap& heap, 89 | const Console& console, 90 | const Process& process, 91 | const Linker& linker, 92 | const Scheduler& scheduler, 93 | const Chrono& chrono) 94 | : allocator_{*this} 95 | , filesystem{filesystem} 96 | , heap{heap} 97 | , console{console} 98 | , process{process} 99 | , linker{linker} 100 | , scheduler{scheduler} 101 | , chrono{chrono} 102 | , allocator{allocator_} 103 | { 104 | } 105 | const Filesystem& filesystem; 106 | const Heap& heap; 107 | const Console& console; 108 | const Process& process; 109 | const Linker& linker; 110 | const Scheduler& scheduler; 111 | const Chrono& chrono; 112 | TemporaryAllocator allocator; 113 | }; 114 | 115 | } // namespace Thor 116 | 117 | #endif // THOR_SYSTEM_H -------------------------------------------------------------------------------- /src/util/thread.cpp: -------------------------------------------------------------------------------- 1 | #include "util/thread.h" 2 | 3 | namespace Thor { 4 | 5 | Maybe Thread::start(System& sys, Fn fn, void* user) { 6 | auto thread = sys.scheduler.thread_start(sys, fn, user); 7 | if (!thread) { 8 | return {}; 9 | } 10 | return Thread { sys, thread }; 11 | } 12 | 13 | void Thread::join() { 14 | if (thread_) { 15 | sys_.scheduler.thread_join(sys_, thread_); 16 | thread_ = nullptr; 17 | } 18 | } 19 | 20 | } // namespace Thor -------------------------------------------------------------------------------- /src/util/thread.h: -------------------------------------------------------------------------------- 1 | #ifndef THOR_THREAD_H 2 | #define THOR_THREAD_H 3 | #include "util/system.h" 4 | 5 | namespace Thor { 6 | 7 | struct Thread { 8 | using Fn = void(System&, void*); 9 | 10 | static Maybe start(System& sys, Fn fn, void* user = nullptr); 11 | 12 | void join(); 13 | 14 | Thread(Thread&& other) 15 | : sys_{other.sys_} 16 | , thread_{exchange(other.thread_, nullptr)} 17 | { 18 | } 19 | 20 | ~Thread() { join(); } 21 | 22 | private: 23 | Thread(System& sys, Scheduler::Thread* thread) 24 | : sys_{sys} 25 | , thread_{thread} 26 | { 27 | } 28 | System& sys_; 29 | Scheduler::Thread* thread_; 30 | }; 31 | 32 | } // namespace Thor 33 | 34 | #endif // THOR_THREAD_H -------------------------------------------------------------------------------- /src/util/time.cpp: -------------------------------------------------------------------------------- 1 | #include "util/time.h" 2 | #include "util/system.h" 3 | 4 | namespace Thor { 5 | 6 | WallTime Seconds::operator+(WallTime other) const { 7 | return other + *this; 8 | } 9 | 10 | WallTime Seconds::operator-(WallTime other) const { 11 | return WallTime::from_raw(raw_ - other.seconds_since_epoch().raw_); 12 | } 13 | 14 | MonotonicTime Seconds::operator+(MonotonicTime other) const { 15 | return MonotonicTime::from_raw(raw_ - other.seconds_since_epoch().raw_); 16 | } 17 | 18 | WallTime WallTime::now(System& sys) { 19 | return WallTime::from_raw(sys.chrono.wall_now(sys)); 20 | } 21 | 22 | MonotonicTime MonotonicTime::now(System& sys) { 23 | return MonotonicTime::from_raw(sys.chrono.monotonic_now(sys)); 24 | } 25 | 26 | } // namespace Thor -------------------------------------------------------------------------------- /src/util/time.h: -------------------------------------------------------------------------------- 1 | #ifndef THOR_TIME_H 2 | #define THOR_TIME_H 3 | #include "util/types.h" 4 | 5 | namespace Thor { 6 | 7 | struct System; 8 | 9 | template 10 | struct Time; 11 | 12 | struct MonotonicTime; 13 | struct WallTime; 14 | 15 | // Type safe unit representing seconds as 64-bit floating-point value. 16 | struct Seconds { 17 | THOR_FORCEINLINE explicit constexpr Seconds(Float64 value) 18 | : raw_{value} 19 | { 20 | } 21 | THOR_FORCEINLINE Bool is_inf() const { 22 | union { Float64 f; Uint64 u; } bits{raw_}; 23 | return (bits.u & -1ull >> 1) == 0x7ffull << 52; 24 | } 25 | THOR_FORCEINLINE constexpr Seconds operator+(Seconds other) const { 26 | return Seconds{raw_ + other.raw_}; 27 | } 28 | THOR_FORCEINLINE constexpr Seconds operator-(Seconds other) const { 29 | return Seconds{raw_ - other.raw_}; 30 | } 31 | THOR_FORCEINLINE constexpr Seconds operator-() const { 32 | return Seconds{-raw_}; 33 | } 34 | THOR_FORCEINLINE Seconds& operator+=(Seconds other) { 35 | return *this = *this + other; 36 | } 37 | THOR_FORCEINLINE Seconds& operator-=(Seconds other) { 38 | return *this = *this - other; 39 | } 40 | THOR_FORCEINLINE auto value() const { 41 | return raw_; 42 | } 43 | WallTime operator+(WallTime) const; 44 | WallTime operator-(WallTime) const; 45 | MonotonicTime operator+(MonotonicTime) const; 46 | MonotonicTime operator-(MonotonicTime) const; 47 | private: 48 | Float64 raw_ = 0.0; 49 | }; 50 | 51 | // CRTP base for time implementations. 52 | template 53 | struct Time { 54 | THOR_FORCEINLINE static constexpr T from_raw(Float64 seconds) { 55 | return T{seconds}; 56 | } 57 | THOR_FORCEINLINE static constexpr T from_now(System& sys, Seconds time_from_now) { 58 | if (time_from_now.is_inf()) { 59 | return T::from_raw(time_from_now.value()); 60 | } 61 | return T::now(sys) + time_from_now; 62 | } 63 | THOR_FORCEINLINE Bool is_inf() const { 64 | return Seconds{raw_}.is_inf(); 65 | } 66 | THOR_FORCEINLINE constexpr Seconds seconds_since_epoch() const { 67 | return Seconds{raw_}; 68 | } 69 | THOR_FORCEINLINE constexpr Seconds operator-(T other) const { 70 | return Seconds{raw_ - other.raw_}; 71 | } 72 | THOR_FORCEINLINE constexpr T operator+(Seconds other) const { 73 | return from_raw(raw_ + other.value()); 74 | } 75 | THOR_FORCEINLINE constexpr T operator-(Seconds other) const { 76 | return from_raw(raw_ - other.value()); 77 | } 78 | THOR_FORCEINLINE constexpr T operator-() const { 79 | return from_raw(-raw_); 80 | } 81 | THOR_FORCEINLINE T operator+=(Seconds other) { 82 | auto derived = static_cast(this); 83 | return *derived = *derived + other; 84 | } 85 | THOR_FORCEINLINE T operator-=(Seconds other) { 86 | auto derived = static_cast(this); 87 | return *derived = *derived - other; 88 | } 89 | THOR_FORCEINLINE Bool operator<(const Time& other) const { return raw_ < other.raw_; } 90 | THOR_FORCEINLINE Bool operator>(const Time& other) const { return raw_ > other.raw_; } 91 | THOR_FORCEINLINE Bool operator<=(const Time& other) const { return raw_ <= other.raw_; } 92 | THOR_FORCEINLINE Bool operator>=(const Time& other) const { return raw_ >= other.raw_; } 93 | protected: 94 | constexpr Time() = default; 95 | constexpr Time(Float64 raw) 96 | : raw_{raw} 97 | { 98 | } 99 | Float64 raw_ = 0.0; 100 | }; 101 | 102 | // We have both MonotonicTime and WallTime implementations. 103 | struct MonotonicTime : Time { 104 | constexpr MonotonicTime() = default; 105 | static MonotonicTime now(System& sys); 106 | private: 107 | friend struct Time; 108 | constexpr MonotonicTime(Float64 raw) : Time{raw} {} 109 | }; 110 | 111 | struct WallTime : Time { 112 | constexpr WallTime() = default; 113 | static WallTime now(System& sys); 114 | private: 115 | friend struct Time; 116 | constexpr WallTime(Float64 raw) : Time{raw} {} 117 | }; 118 | 119 | } // namespace Thor 120 | 121 | #endif // THOR_TIME_H 122 | -------------------------------------------------------------------------------- /src/util/traits.h: -------------------------------------------------------------------------------- 1 | #ifndef THOR_TRAITS_H 2 | #define THOR_TRAITS_H 3 | #include "util/types.h" 4 | 5 | namespace Thor { 6 | 7 | template struct RemoveReference_ { using Type = T; }; 8 | template struct RemoveReference_ { using Type = T; }; 9 | template struct RemoveReference_ { using Type = T; }; 10 | template 11 | using RemoveReference = typename RemoveReference_::Type; 12 | 13 | template 14 | concept Referenceable = requires { 15 | typename Identity; 16 | }; 17 | 18 | template> 19 | struct AddLValueReference_ { using Type = T; }; 20 | template> 21 | struct AddRValueReference_ { using Type = T; }; 22 | 23 | template struct AddLValueReference_ { using Type = T&; }; 24 | template struct AddRValueReference_ { using Type = T&&; }; 25 | 26 | template using AddLValueReference = typename AddLValueReference_::Type; 27 | template using AddRValueReference = typename AddRValueReference_::Type; 28 | 29 | template 30 | inline constexpr auto is_copy_constructible = 31 | __is_constructible(T, AddLValueReference); 32 | 33 | template 34 | inline constexpr auto is_move_constructible = 35 | __is_constructible(T, AddRValueReference); 36 | 37 | template 38 | concept CopyConstructible = is_copy_constructible; 39 | 40 | template 41 | concept MoveConstructible = is_move_constructible; 42 | 43 | template 44 | inline constexpr auto is_same = false; 45 | 46 | template 47 | inline constexpr auto is_same = true; 48 | 49 | template 50 | concept Same = is_same; 51 | 52 | template 53 | inline constexpr auto is_base_of = __is_base_of(B, D); 54 | 55 | template 56 | concept DerivedFrom = is_base_of; 57 | 58 | template 59 | inline constexpr bool is_polymorphic = __is_polymorphic(T); 60 | 61 | template 62 | inline constexpr bool is_trivially_destructible = 63 | #if THOR_HAS_BUILTIN(__is_trivially_destructible) 64 | __is_trivially_destructible(T); 65 | #elif THOR_HAS_BUILTIN(__has_trivial_destructor) 66 | __has_trivial_destructor(T); 67 | #else 68 | ([] { static_assert(false, "Cannot implement is_trivially_destructible"); }, false); 69 | #endif 70 | 71 | template 72 | concept TriviallyDestructible = is_trivially_destructible; 73 | 74 | template AddLValueReference declval(); 75 | 76 | } // namespace Thor 77 | 78 | #endif // THOR_TRAITS_H -------------------------------------------------------------------------------- /src/util/types.h: -------------------------------------------------------------------------------- 1 | #ifndef THOR_TYPES_H 2 | #define THOR_TYPES_H 3 | 4 | #include "util/info.h" 5 | 6 | struct Nat {}; 7 | inline void *operator new(decltype(sizeof 0), void* ptr, Nat) { 8 | return ptr; 9 | } 10 | 11 | namespace Thor { 12 | 13 | using Uint8 = unsigned char; 14 | using Sint8 = signed char; 15 | using Uint16 = unsigned short; 16 | using Sint16 = signed short; 17 | using Uint32 = unsigned int; 18 | using Sint32 = signed int; 19 | using Uint64 = unsigned long long; 20 | using Sint64 = signed long long; 21 | using Float64 = double; 22 | using Float32 = float; 23 | using Ulen = decltype(sizeof 0); 24 | using Bool = bool; 25 | using Address = unsigned long long; 26 | using Hash = Uint64; 27 | using Unit = struct {}; 28 | 29 | constexpr Uint8 operator""_u8(unsigned long long int v) { return v; } 30 | constexpr Uint16 operator""_u16(unsigned long long int v) { return v; } 31 | constexpr Uint32 operator""_u32(unsigned long long int v) { return v; } 32 | constexpr Uint64 operator""_u64(unsigned long long int v) { return v; } 33 | constexpr Ulen operator""_ulen(unsigned long long int v) { return v; } 34 | 35 | // Helpers for working with lo and hi parts of integer types. 36 | constexpr auto lo(Uint16 v) -> Uint8 { return v; } 37 | constexpr auto hi(Uint16 v) -> Uint8 { return v >> 8; } 38 | constexpr auto lo(Uint32 v) -> Uint16 { return v; } 39 | constexpr auto hi(Uint32 v) -> Uint16 { return v >> 16; } 40 | constexpr auto lo(Uint64 v) -> Uint32 { return v; } 41 | constexpr auto hi(Uint64 v) -> Uint32 { return v >> 32; } 42 | 43 | constexpr auto lo(Sint16 v) -> Uint8 { return lo(Uint16(v)); } 44 | constexpr auto hi(Sint16 v) -> Uint8 { return hi(Uint16(v)); } 45 | constexpr auto lo(Sint32 v) -> Uint16 { return lo(Uint32(v)); } 46 | constexpr auto hi(Sint32 v) -> Uint16 { return hi(Uint32(v)); } 47 | constexpr auto lo(Sint64 v) -> Uint32 { return lo(Uint64(v)); } 48 | constexpr auto hi(Sint64 v) -> Uint32 { return hi(Uint64(v)); } 49 | 50 | template 51 | struct Identity { using Type = T; }; 52 | 53 | } // namespace Thor 54 | 55 | #endif // THOR_TYPES_H 56 | -------------------------------------------------------------------------------- /src/util/unicode.cpp: -------------------------------------------------------------------------------- 1 | #include "util/unicode.h" 2 | 3 | namespace Thor { 4 | 5 | Bool Rune::is_char() const { 6 | if (v_ < 0x80) { 7 | if (v_ == '_') { 8 | return true; 9 | } 10 | return ((v_ | 0x20) - 0x61) < 26; 11 | } 12 | // TODO(dweiler): LU, LL, LT, LM, LO => true 13 | return false; 14 | } 15 | 16 | Bool Rune::is_digit() const { 17 | if (v_ < 0x80) { 18 | return (v_ - '0') < 10; 19 | } 20 | // TODO(dweiler): ND => true 21 | return false; 22 | } 23 | 24 | Bool Rune::is_digit(Uint32 base) const { 25 | if (v_ < 0x80) { 26 | switch (v_) { 27 | case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': 28 | return v_ - '0' < base; 29 | case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': 30 | return v_ - 'a' + 10 < base; 31 | case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': 32 | return v_ - 'A' + 10 < base; 33 | } 34 | } 35 | // TODO(dweiler): ND => true 36 | return false; 37 | } 38 | 39 | Bool Rune::is_alpha() const { 40 | return is_char() || is_digit(); 41 | } 42 | 43 | Bool Rune::is_white() const { 44 | return v_ == ' ' || v_ == '\t' || v_ == '\n' || v_ == '\r'; 45 | } 46 | 47 | } // namespace Thor 48 | -------------------------------------------------------------------------------- /src/util/unicode.h: -------------------------------------------------------------------------------- 1 | #ifndef THOR_UNICODE_H 2 | #define THOR_UNICODE_H 3 | #include "util/types.h" 4 | 5 | namespace Thor { 6 | 7 | struct Rune { 8 | constexpr Rune(Uint32 v) : v_{v} {} 9 | [[nodiscard]] Bool is_char() const; 10 | [[nodiscard]] Bool is_digit() const; 11 | [[nodiscard]] Bool is_digit(Uint32 base) const; 12 | [[nodiscard]] Bool is_alpha() const; 13 | [[nodiscard]] Bool is_white() const; 14 | operator Uint32() const { return v_; } 15 | private: 16 | Uint32 v_; 17 | }; 18 | 19 | } // namespace Thor 20 | 21 | #endif // THOR_UNICODE 22 | -------------------------------------------------------------------------------- /test/ks.odin: -------------------------------------------------------------------------------- 1 | package ks 2 | 3 | import "core:fmt" 4 | 5 | Item :: struct { 6 | name: string, 7 | weight: int, 8 | value: int 9 | } 10 | 11 | foo :: proc(b, c, a: int, d: f32) { 12 | fmt.printf("%d:%d:%d:%f\n", b, c, a, d) 13 | } 14 | 15 | ks :: proc(items: []Item, w: int) -> []bool { 16 | using fmt; 17 | 18 | n := len(items); 19 | mm := make([]int, (n + 1) * (w + 1)) 20 | m := make([][]int, (n + 1)) 21 | defer { delete(m); delete(mm) } 22 | m[0] = mm[:] 23 | for i in 1..=n { 24 | m[i] = mm[i * (w + 1):] 25 | for j in 0..=w { 26 | if items[i - 1].weight > j { 27 | m[i][j] = m[i - 1][j] 28 | } else { 29 | a := m[i - 1][j] 30 | b := m[i - 1][j - items[i - 1].weight] + items[i - 1].value 31 | m[i][j] = max(a, b) 32 | } 33 | } 34 | } 35 | s := make([]bool, n) 36 | for i, j := n, w; i > 0; i -= 1 { 37 | if m[i][j] > m[i - 1][j] { 38 | s[i - 1] = true 39 | j -= items[i - 1].weight 40 | } 41 | } 42 | return s 43 | } 44 | 45 | main :: proc() { 46 | if x: bit_set[ 47 | 1..<2 48 | u32] 49 | false 50 | { 51 | } 52 | else do for i := 0 53 | i < 10; 54 | i += 1 55 | { fmt.printf("%d\n", i) } 56 | 57 | /* 58 | items := []Item { 59 | {"map", 9, 150}, 60 | {"compass", 13, 35}, 61 | {"water", 153, 200}, 62 | {"sandwich", 50, 160}, 63 | {"glucose", 15, 60}, 64 | {"tin", 68, 45}, 65 | {"banana", 27, 60}, 66 | {"apple", 39, 40}, 67 | {"cheese", 23, 30}, 68 | {"beer", 52, 10}, 69 | {"suntan cream", 11, 70}, 70 | {"camera", 32, 30}, 71 | {"T-shirt", 24, 15}, 72 | {"trousers", 48, 10}, 73 | {"umbrella", 73, 40}, 74 | {"waterproof trousers", 42, 70}, 75 | {"waterproof overclothes", 43, 75}, 76 | {"note-case", 22, 80}, 77 | {"sunglasses", 7, 20}, 78 | {"towel", 18, 12}, 79 | {"socks", 4, 50}, 80 | {"book", 30, 10} 81 | } 82 | s := ks(items, 400) 83 | tw := 0 84 | tv := 0 85 | for item, i in items { 86 | s[i] or_continue 87 | fmt.printf("%-22s % 5d % 5d\n", item.name, item.weight, item.value) 88 | tw += item.weight 89 | tv += item.value 90 | } 91 | fmt.printf("%-22s % 5d % 5d\n", "totals:", tw, tv) 92 | */ 93 | 94 | // x, y: int; 95 | } 96 | -------------------------------------------------------------------------------- /unity.cpp: -------------------------------------------------------------------------------- 1 | // Unity builds: To build just run: 2 | // 3 | // MSVC-like compilers (cl.exe, clang-cl, etc) 4 | // cl.exe /nologo /I src /std:c++20 /GR- /unity.cpp /link /out:thor.exe 5 | // 6 | // GNU-like compilers (gcc, clang, etc) 7 | // cc -xc++ -Isrc -std=c++20 -fno-rtti -fno-exceptions unity.cpp -o thor 8 | // 9 | // Thor will not link without the use of -fno-rtii and -fno-exceptions. 10 | #include "src/util/allocator.cpp" 11 | #include "src/util/assert.cpp" 12 | #include "src/util/cpprt.cpp" 13 | #include "src/util/file.cpp" 14 | #include "src/util/lock.cpp" 15 | #include "src/util/pool.cpp" 16 | #include "src/util/slab.cpp" 17 | #include "src/util/stream.cpp" 18 | #include "src/util/string.cpp" 19 | #include "src/util/thread.cpp" 20 | #include "src/util/time.cpp" 21 | #include "src/util/unicode.cpp" 22 | #include "src/ast.cpp" 23 | #include "src/lexer.cpp" 24 | #include "src/main.cpp" 25 | #include "src/parser.cpp" 26 | #include "src/cg_llvm.cpp" 27 | #include "src/system_posix.cpp" 28 | #include "src/system_windows.cpp" 29 | --------------------------------------------------------------------------------