├── .github └── workflows │ └── main.yml ├── .gitignore ├── .vscode ├── launch.json └── rust_prettifier_for_lldb.py ├── Cargo.toml ├── LICENSE ├── README.md ├── TODO.md ├── build.rs ├── notes └── thinking.md ├── resources ├── array_literals.bg ├── atom.bg ├── beagle.ffi.bg ├── binary_tree.bg ├── binary_tree_benchmark.bg ├── bits_test.bg ├── closures.bg ├── comparisons.bg ├── countdown.bg ├── enum_test.bg ├── equal_test.bg ├── extra_map_test.bg ├── ffi_test.bg ├── fib.bg ├── floats.bg ├── grab_bag.bg ├── hamt_benchmark.bg ├── if_test.bg ├── import_test.bg ├── lib_lldb.bg ├── lldb_wrapper.bg ├── many_args_test.bg ├── map_benchmark.bg ├── math_test.bg ├── mut_var.bg ├── namespace_gc.bg ├── namespace_resolution_test.bg ├── persistent_map.bg ├── persistent_vector.bg ├── property_access_test.bg ├── protocol_test.bg ├── raw_mutable_array.bg ├── register_allocation_test.bg ├── repl_test.bg ├── simple_call_test.bg ├── slow_json_parser.bg ├── socket_test.bg ├── std.bg ├── string.bg ├── test_gc.bg ├── thread.bg ├── throw_error.bg └── top_level.bg └── src ├── arm.rs ├── ast.rs ├── builtins.rs ├── code_memory.rs ├── common.rs ├── compiler.rs ├── gc ├── compacting.rs ├── compacting_v2.rs ├── generation_v2.rs ├── mark_and_sweep_v2.rs ├── mod.rs ├── mutex_allocator.rs ├── simple_generation.rs └── simple_mark_and_sweep.rs ├── ir.rs ├── machine_code ├── arm_codegen.rs └── mod.rs ├── main.rs ├── parser.rs ├── pretty_print.rs ├── primitives.rs ├── register_allocation ├── linear_scan.rs ├── mod.rs └── simple.rs ├── runtime.rs └── types.rs /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Run Cargo Tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | test: 13 | runs-on: macos-latest 14 | 15 | strategy: 16 | matrix: 17 | feature-combination: 18 | - "--features compacting,thread-safe" 19 | - "--features compacting-v2,thread-safe" 20 | - "--features simple-generation,thread-safe" 21 | - "--features simple-mark-and-sweep,thread-safe" 22 | - "--features mark-and-sweep-v2,thread-safe" 23 | - "--features compacting" 24 | - "--features compacting-v2" 25 | - "--features simple-generation" 26 | - "--features simple-mark-and-sweep" 27 | - "--features mark-and-sweep-v2" 28 | 29 | steps: 30 | - name: Checkout repository 31 | uses: actions/checkout@v3 32 | 33 | - name: Install Rust 34 | uses: actions-rs/toolchain@v1 35 | with: 36 | toolchain: stable 37 | profile: minimal 38 | override: true 39 | 40 | - name: Run tests with features 41 | run: RUST_BACKTRACE=1 cargo test ${{ matrix.feature-combination }} 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 | Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | 13 | # MSVC Windows builds of rustc generate these, which store debugging information 14 | *.pdb 15 | 16 | # RustRover 17 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 18 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 19 | # and can be added to the global gitignore or merged into this file. For a more nuclear 20 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 21 | #.idea/ 22 | 23 | profile.json -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "lldb", 9 | "request": "launch", 10 | "name": "Debug executable 'main'", 11 | "cargo": { 12 | "args": [ 13 | "build", 14 | // "--release", 15 | // "--profile=release-with-debug", 16 | "--bin=main", 17 | "--package=main", 18 | "--features=generation-v2,thread-safe,json" 19 | ], 20 | "filter": { 21 | "name": "main", 22 | "kind": "bin" 23 | } 24 | }, 25 | "args": ["resources/mut_var.bg"], 26 | "cwd": "${workspaceFolder}", 27 | "preRunCommands": [ 28 | "command script import .vscode/rust_prettifier_for_lldb.py" 29 | ], 30 | }, 31 | { 32 | "type": "lldb", 33 | "request": "launch", 34 | "name": "Debug unit tests in executable 'main'", 35 | "cargo": { 36 | "args": [ 37 | "test", 38 | "--no-run", 39 | "--bin=main", 40 | "--package=main" 41 | ], 42 | "filter": { 43 | "name": "main", 44 | "kind": "bin" 45 | } 46 | }, 47 | "args": [], 48 | "cwd": "${workspaceFolder}", 49 | "preRunCommands": [ 50 | "command script import .vscode/rust_prettifier_for_lldb.py" 51 | ], 52 | 53 | } 54 | ] 55 | } -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "main" 3 | version = "0.1.0" 4 | edition = "2024" 5 | build = "build.rs" 6 | 7 | 8 | [profile.release-with-debug] 9 | inherits = "release" 10 | debug = true 11 | debug-assertions = true 12 | 13 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 14 | 15 | 16 | # It would be great to get rid of all these dependencies 17 | [dependencies] 18 | mmap-rs = "0.6.1" 19 | bincode = { version = "2.0.0", features = ["derive"] } 20 | clap = { version = "4.5.8", features = ["derive"] } 21 | cfg-if = "1.0.0" 22 | libloading = "0.8.5" 23 | libffi = "3.2.0" 24 | libc = "0.2.169" 25 | nanoserde = { version = "0.2.0-beta.0", features = ["json", "binary"] } 26 | 27 | 28 | 29 | [features] 30 | default = [] 31 | simple-mark-and-sweep = [] 32 | compacting = [] 33 | compacting-v2 = [] 34 | mark-and-sweep-v2 = [] 35 | simple-generation = [] 36 | generation-v2 = [] 37 | thread-safe = [] 38 | json = [] 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Jimmy Miller 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Beagle 2 | 3 | ![Cargo Tests](https://github.com/jimmyhmiller/beagle/actions/workflows/main.yml/badge.svg) 4 | 5 | This is the very beginnings of a dynamically typed, functional programming language I am building. It aims to be a modern, fast, multithreaded, dynamically typed langauge, compiling straight to machine code on the fly rather than using a VM. Currently it is in a very Proof of Concept stage with many things not working. The things that do work, only work on macos arm64. 6 | 7 | Currently it has the following things, all at the very beginnings and chock full of bugs. But with some solid foundations to continue on. 8 | 9 | 1. A few basic types (Strings, Numbers, Booleans, etc) 10 | 2. Structs 11 | 3. Atoms 12 | 4. Closures 13 | 5. Theads 14 | 6. Garbage collection (three very basic GC implementations) 15 | 7. Tail Call Optimization (Only way to do loops) 16 | 8. Hand written parser (with basically no error handling) 17 | 18 | While this is incredibly early days. There are some promising things I can already see. A very simple binary trees benchmark from the benchmark games out performs ruby (with yjit) on my machine by almost 2x. In some ways, this is cheating as there are some checks the code currently doesn't do. But in other ways, beagle is at a disadvantage here as our struct property lookup code is incredibly naive and slow. Even without any of these optimizations, that same benchmark is only about 30% slower than node! 19 | 20 | ## Goals 21 | 22 | The goal is to make Beagle a language people would want to use for real development. In other words, over the long run, I want it to be popular. People seem to believe that dynamic languages are dying and will probably stay dead. But the best aspects of dynamic langauges haven't been seen by most people. This is my attempt at bringing the best of dynamically typed langauges to one place. I've taken a lot of inspiration from Clojure. But without its borrowing lisp syntax, staying away from the jvm. But, ultimately, the goal will be the interactive, repl driven development that Clojure enables. But we should also have all the multi-threading and observability benefits of the jvm without relying on that large platform. 23 | 24 | Getting to where I want to be will take years and years of work. But I think its doable. In the mean time my goal is to get beagle to the point where I can build a GUI debugger frontend for itself in itself. I have a rust debugger frontend I've used for it that is fairly hacked together. You can see some debugger code scattered through out. For this, we will need some C interop. 25 | 26 | ## History 27 | 28 | This codebase originally existed [here](https://github.com/jimmyhmiller/PlayGround/tree/master/rust/asm-arm2). It outgrew that junk drawer and moved it here as it matures into proper project. 29 | 30 | 31 | ### Personal TODO list (incomplete) 32 | 33 | * Check argument count 34 | * Need to properly handle parse errors 35 | * Do I want clap? 36 | * Guard on arithemtic (Started) 37 | * import/export 38 | * Namespaces (Started) 39 | * Functions right now are global rather than namespaced (Started) 40 | * Data structures 41 | * Decide if I want to support kabab-case 42 | * Continuations? 43 | * Make closures better 44 | * Floats (started) 45 | * Decimals 46 | * Enums (started) 47 | * Pattern Matching -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | * Need to add ! 2 | * Not a fan of how I'm dealing with dots in namespace names. I'm thinking I shouldn't have them at all 3 | * I think I should probably make namespaces not be a rust construct, but actually defined in the language. Meaning that namespaces would be normal heap objects. This would make gc much easier, as they wouldn't need to be so much of a special case. We might want to be able to say that they are objects that should go in the old generation or permanent or whatever. But having to have them be all separate is a bit awkward. 4 | * I've got some problem with enum and registers... 5 | * Need else if 6 | * Need || and && operators. Or should they be `or` and `and`? 7 | * I feel like I have a bug with moving floats. Maybe I don't. But because they are small objects, I can't do the thing where I write in the first field a pointer. And if I can't do that, I'm guessing I duplicate them when I copy them. I need some way to deal with that. 8 | * Struct Ids should be stored untagged 9 | * My quick and dirty thread-safe gc is slower even without multiple threads 10 | * Do I zero on allocation? 11 | * Code memory needs to be dynamic 12 | * If I try to recursion multiple times in the same function (same code path), it fails. I think because of tail position calcalations 13 | * Implement https://github.com/torvalds/linux/blob/master/tools/perf/Documentation/jitdump-specification.txt 14 | * Technically speaking, my arrays are now mutable. So they should have write barriers for old generation. 15 | * I fixed a bunch of stack map code in compacting. The errors have to also exist in mark and sweep. (Maybe also generational) Cleanup and unify this stuff. 16 | * I should probably have some gc-always runs 17 | * Mulit Arity and Var Args 18 | * I need the ability to re-export functions without a level of indirection 19 | * Figure out a print system for types like the vector 20 | * I need to do indexing with [] I just need to figure out how to distinguish a literal and indexing. How do other languages do that? 21 | * Make namespacing reference work for structs and enums better 22 | * Builtins need better type checking setup 23 | * Need to make the tests start a process so if one crashes, we still know the total results 24 | * I've got some stuff going on with malloc and free that is making rust not happy. In the case I saw, it looks like namsepace bindings are causing issues. I can find it by running the sdl example. 25 | * I need to manage chunks of code better. I think right now I am marking things as mutable, but then code is running. If I just manage writing code in chunks rather than one big region that I keep mutating and then re-execing, this shouldn't be a problem 26 | * Get rid of mmap now that I have libc 27 | * I need a way to associate functions with a struct or I need to say all functions can be so associated 28 | * I like the latter but it has issues. 29 | * The former is more structured and probably faster as I can specialize 30 | * I need a way to do iterator/seqs/whatever 31 | * I need to move current_namespace from runtime to compiler 32 | * I really need to solve this whole namespace vs function thing and fix that problem 33 | * How could I make what I'm doing with protocols a general feature? I would want the ability to do codegen in reaction to registering extensions. But I also want the ability for the optimizer to figure out that we can specialize on type. 34 | 35 | 36 | ``` 37 | thread '' panicked at src/runtime.rs:430:59: 38 | called `Option::unwrap()` on a `None` value 39 | stack backtrace: 40 | 0: rust_begin_unwind 41 | at /rustc/e71f9a9a98b0faf423844bf0ba7438f29dc27d58/library/std/src/panicking.rs:665:5 42 | 1: core::panicking::panic_fmt 43 | at /rustc/e71f9a9a98b0faf423844bf0ba7438f29dc27d58/library/core/src/panicking.rs:76:14 44 | 2: core::panicking::panic 45 | at /rustc/e71f9a9a98b0faf423844bf0ba7438f29dc27d58/library/core/src/panicking.rs:148:5 46 | 3: core::option::unwrap_failed 47 | at /rustc/e71f9a9a98b0faf423844bf0ba7438f29dc27d58/library/core/src/option.rs:2009:5 48 | 4: core::option::Option::unwrap 49 | at /rustc/e71f9a9a98b0faf423844bf0ba7438f29dc27d58/library/core/src/option.rs:972:21 50 | 5: main::runtime::Memory::active_threads 51 | at ./src/runtime.rs:430:29 52 | 6: main::runtime::Runtime::gc 53 | at ./src/runtime.rs:1005:15 54 | 7: main::runtime::Runtime::allocate 55 | at ./src/runtime.rs:871:17 56 | 8: main::builtins::allocate 57 | at ./src/builtins.rs:78:18 58 | note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace. 59 | 1: 0x102c0c09c - std::backtrace_rs::backtrace::trace_unsynchronized::hf4fa2da75bbd5d09 60 | at /rustc/e71f9a9a98b0faf423844bf0ba7438f29dc27d58/library/std/src/../../backtrace/src/backtrace/mod.rs:66:5 61 | 2: 0x102c0c09c - std::sys::backtrace::_print_fmt::h75773692a17404c8 62 | at /rustc/e71f9a9a98b0faf423844bf0ba7438f29dc27d58/library/std/src/sys/backtrace.rs:66:9 63 | 3: 0x102c0c09c - ::fmt::h39ba3129e355bb22 64 | at /rustc/e71f9a9a98b0faf423844bf0ba7438f29dc27d58/library/std/src/sys/backtrace.rs:39:26 65 | 4: 0x102c28268 - core::fmt::rt::Argument::fmt::h34f25d464889fcc7 66 | at /rustc/e71f9a9a98b0faf423844bf0ba7438f29dc27d58/library/core/src/fmt/rt.rs:177:76 67 | 5: 0x102c28268 - core::fmt::write::h8b50d3a0f616451a 68 | at /rustc/e71f9a9a98b0faf423844bf0ba7438f29dc27d58/library/core/src/fmt/mod.rs:1189:21 69 | 6: 0x102c09128 - std::io::Write::write_fmt::h4b3bbae7048e35f8 70 | at /rustc/e71f9a9a98b0faf423844bf0ba7438f29dc27d58/library/std/src/io/mod.rs:1884:15 71 | 7: 0x102c0bf50 - std::sys::backtrace::BacktraceLock::print::h7934b1e389160086 72 | at /rustc/e71f9a9a98b0faf423844bf0ba7438f29dc27d58/library/std/src/sys/backtrace.rs:42:9 73 | 8: 0x102c0d2b8 - std::panicking::default_hook::{{closure}}::hbcd636b20f603d1e 74 | at /rustc/e71f9a9a98b0faf423844bf0ba7438f29dc27d58/library/std/src/panicking.rs:268:22 75 | 9: 0x102c0d0ec - std::panicking::default_hook::ha9081970ba26bc6c 76 | at /rustc/e71f9a9a98b0faf423844bf0ba7438f29dc27d58/library/std/src/panicking.rs:295:9 77 | 10: 0x102baa350 - as core::ops::function::Fn>::call::h24231d3c986eef43 78 | at /rustc/e71f9a9a98b0faf423844bf0ba7438f29dc27d58/library/alloc/src/boxed.rs:1986:9 79 | 11: 0x102baa350 - test::test_main::{{closure}}::h5e1e543293f867e4 80 | at /rustc/e71f9a9a98b0faf423844bf0ba7438f29dc27d58/library/test/src/lib.rs:134:21 81 | 12: 0x102c0dae8 - as core::ops::function::Fn>::call::h3ea003f283d2c744 82 | at /rustc/e71f9a9a98b0faf423844bf0ba7438f29dc27d58/library/alloc/src/boxed.rs:1986:9 83 | 13: 0x102c0dae8 - std::panicking::rust_panic_with_hook::h9a5dc30b684e2ff4 84 | at /rustc/e71f9a9a98b0faf423844bf0ba7438f29dc27d58/library/std/src/panicking.rs:809:13 85 | 14: 0x102c0d6f8 - std::panicking::begin_panic_handler::{{closure}}::hbcb5de8b840ae91c 86 | at /rustc/e71f9a9a98b0faf423844bf0ba7438f29dc27d58/library/std/src/panicking.rs:667:13 87 | 15: 0x102c0c560 - std::sys::backtrace::__rust_end_short_backtrace::ha657d4b4d65dc993 88 | at /rustc/e71f9a9a98b0faf423844bf0ba7438f29dc27d58/library/std/src/sys/backtrace.rs:170:18 89 | 16: 0x102c0d3d8 - rust_begin_unwind 90 | at /rustc/e71f9a9a98b0faf423844bf0ba7438f29dc27d58/library/std/src/panicking.rs:665:5 91 | 17: 0x102c38d38 - core::panicking::panic_nounwind_fmt::runtime::h13e8a6e8075ea543 92 | at /rustc/e71f9a9a98b0faf423844bf0ba7438f29dc27d58/library/core/src/panicking.rs:119:22 93 | 18: 0x102c38d38 - core::panicking::panic_nounwind_fmt::h4a10ecea0e21f67a 94 | at /rustc/e71f9a9a98b0faf423844bf0ba7438f29dc27d58/library/core/src/intrinsics/mod.rs:3535:9 95 | 19: 0x102c38db0 - core::panicking::panic_nounwind::ha9a59379b5f3f41a 96 | at /rustc/e71f9a9a98b0faf423844bf0ba7438f29dc27d58/library/core/src/panicking.rs:223:5 97 | 20: 0x102c38ee0 - core::panicking::panic_cannot_unwind::h1bb1158913507f0a 98 | at /rustc/e71f9a9a98b0faf423844bf0ba7438f29dc27d58/library/core/src/panicking.rs:315:5 99 | 21: 0x102b3d8e4 - main::builtins::allocate::h2a7f9c4eebd6ee94 100 | at /Users/runner/work/beagle/beagle/src/builtins.rs:74:1 101 | thread caused non-unwinding panic. aborting. 102 | error: test failed, to rerun pass `--bin main` 103 | ``` 104 | 105 | 106 | * My janky after_return error stuff is actually calling the start of the function 107 | * I'm guessing this is a side-effect of messing up labels after register allocation 108 | * I am going to either 1. get rid of string constants all together or 2. return a pointer to an object that looks identical whether it is a string constant or not. I could have a buffer of memory that stores my string constants. I'm not sure the correct answer here 109 | * I need to resolve this problem where persistent_vector isn't loaded by std and can't be statically imported because of Struct 110 | * I could add dynamic imports after top level of beagle.core runs 111 | * I could explicitly add multiple standard things that get loaded 112 | * I really need to do some design on loops. As I'm trying to do real stuff, I want them 113 | * I should probably try some project euler problems as small bits of code I need to properly support 114 | * I have enough information I should be able to give exact code that causes any error, even crashes 115 | * I should also be able to step by lines in my debugger 116 | * There are a lot of shortcuts I think I'm taking right now. My register allocated doesn't reuse spilled slots. I null initialize all of them right now. I also don't write back to them when they go out of scope, but that means my gc will think they are still alive even if they aren't, so things only go out of scope at the end of a function. Eventually I need to fix all of this. 117 | * Allow protocols to have a default function they call (eq is a good example) 118 | * Make it so that `==` compiles to a function call testing equality 119 | * Properly detect if variable is undefined 120 | * Make arrays participate in all the instance of stuff -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::fs; 3 | use std::path::Path; 4 | use std::path::PathBuf; 5 | 6 | fn get_build_profile_name() -> String { 7 | // The profile name is always the 3rd last part of the path (with 1 based indexing). 8 | // e.g. /code/core/target/cli/build/my-build-info-9f91ba6f99d7a061/out 9 | env::var("OUT_DIR") 10 | .unwrap() 11 | .split(std::path::MAIN_SEPARATOR) 12 | .nth_back(3) 13 | .unwrap_or("unknown") 14 | .to_string() 15 | } 16 | 17 | fn main() { 18 | // Get the build profile (either "debug" or "release") 19 | let profile = get_build_profile_name(); 20 | let target_dir = PathBuf::from("target").join(&profile); // Points to "target/debug" or "target/release" 21 | 22 | // Define the source and destination paths 23 | let source_dir = PathBuf::from("resources"); 24 | let dest_dir = target_dir.join("resources"); 25 | 26 | // Copy the entire directory recursively 27 | if let Err(e) = fs::create_dir_all(&dest_dir) { 28 | panic!("Failed to create destination directory: {}", e); 29 | } 30 | 31 | copy_recursively(&source_dir, &dest_dir).unwrap(); 32 | 33 | // Inform Cargo to rerun the build script if anything in the resources folder changes 34 | // println!("cargo:rerun-if-changed=resources"); 35 | } 36 | 37 | // Helper function to recursively copy files 38 | fn copy_recursively(src: &PathBuf, dst: &Path) -> std::io::Result<()> { 39 | for entry in fs::read_dir(src)? { 40 | let entry = entry?; 41 | let path = entry.path(); 42 | let dest_path = dst.join(entry.file_name()); 43 | 44 | if path.is_dir() { 45 | fs::create_dir_all(&dest_path)?; 46 | copy_recursively(&path, &dest_path)?; 47 | } else { 48 | fs::copy(&path, &dest_path)?; 49 | } 50 | } 51 | Ok(()) 52 | } 53 | -------------------------------------------------------------------------------- /notes/thinking.md: -------------------------------------------------------------------------------- 1 | What would protocols/traits look like? 2 | 3 | ```rust 4 | protocol Indexed { 5 | fn get(coll, index) 6 | } 7 | 8 | specialize fn Indexed.get(coll, index) for PersistentVector { 9 | 10 | } 11 | 12 | extend PersistentVector with Indexed { 13 | fn get(coll, index) { 14 | vector/get(coll, index) 15 | } 16 | } 17 | 18 | extend PersistentVector { 19 | fn get(coll, index) { 20 | 21 | } 22 | } 23 | 24 | fn get(coll, index) { 25 | throw Unimplemented(typeof(coll)) 26 | } 27 | 28 | specialize fn get(coll, index) for PersistentVector { 29 | 30 | } 31 | 32 | let x = [1] 33 | x.get(0) 34 | 35 | let type = get_type_of(x) 36 | let f = lookup(type, "get") 37 | call(f) 38 | 39 | Indexed.get(x, 0) 40 | 41 | // Can I specialize fns in other modules? 42 | // That becomes a form of monkey patching in some ways 43 | // Or its a way of protocol based development 44 | 45 | ``` 46 | 47 | I'm also interested in this idea that any function can be called with . 48 | 49 | ```rust 50 | fn fib(n) { 51 | ... 52 | } 53 | 54 | 10.fib() 55 | 56 | struct Point { 57 | x 58 | y 59 | } 60 | 61 | fn distance(x, y) { 62 | .. 63 | } 64 | 65 | let x = Point { x: 2, y: 3 } 66 | let y = Point { x: 1, y: 5 } 67 | x.distance(y) 68 | 69 | 70 | fn println(x) { 71 | system/println(x) 72 | } 73 | 74 | specialize fn println(x) for Point { 75 | 76 | } 77 | 78 | x.println() 79 | println(x) 80 | println(2) 81 | 82 | 83 | import { get as coll_get } from "collection" 84 | import { get } from "other" 85 | 86 | x.get() 87 | x.coll_get() 88 | 89 | 90 | ``` 91 | 92 | I need to think about dynamic contexts 93 | 94 | ```rust 95 | 96 | struct Thing { 97 | get 98 | } 99 | 100 | let x = Thing { 101 | get: fn() {} 102 | } 103 | 104 | x.get() 105 | 106 | // Then a evaluate this 107 | 108 | import { get } from "utils" 109 | 110 | x.get() 111 | 112 | ``` 113 | Should I resolve the field? Should I resolve the function? 114 | The good thing about the function is I know at compile time the 115 | function exists. So if I go with that options, I can optimize. 116 | And this way you can't duck type your way into breaking code. 117 | But it does mean any code that any cache of property with the name 118 | get is going to need to be cleared 119 | I don't think that's the end of the world though. 120 | 121 | 122 | 123 | 124 | On thing I do think I want to uphold is that only things you explcitly make dynamic are dynamic. So I don't want to make it so you can customize a random function. Nor do I think the standard way you do things is to pass an object with methods you call. I want predictable execution. -------------------------------------------------------------------------------- /resources/array_literals.bg: -------------------------------------------------------------------------------- 1 | namespace array_literals 2 | import "persistent_vector" as vector 3 | 4 | struct ExampleStruct { 5 | value 6 | } 7 | 8 | fn with_side_effect(x) { 9 | println(x) 10 | x 11 | } 12 | 13 | fn main() { 14 | let x = [1, 2, 3] 15 | println(x) 16 | let y = [2 + 2 - 3, ExampleStruct { value: 2 }, with_side_effect(3), with_side_effect(4)] 17 | println(y) 18 | let q = [] 19 | println(q) 20 | let r = push(q, 1) 21 | println(r) 22 | println(get(r, 0)) 23 | println(r[0]) 24 | let big_array = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,19,20,21,22,23,24] 25 | println(big_array) 26 | "done" 27 | } 28 | 29 | 30 | // Expect 31 | // [1, 2, 3] 32 | // 3 33 | // 4 34 | // [1, ExampleStruct { value: 2 }, 3, 4] 35 | // [] 36 | // [1] 37 | // 1 38 | // 1 39 | // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 19, 20, 21, 22, 23, 24] 40 | // done -------------------------------------------------------------------------------- /resources/atom.bg: -------------------------------------------------------------------------------- 1 | namespace atom 2 | 3 | struct MyPointerStruct { 4 | x 5 | y 6 | } 7 | 8 | 9 | fn create_atom() { 10 | let s = MyPointerStruct { x: 1, y: 2 }; 11 | let new_atom = atom(s) 12 | println(new_atom); 13 | gc(); 14 | println(new_atom); 15 | let new_s = MyPointerStruct { x: 3, y: 4}; 16 | reset!(new_atom, new_s); 17 | println(new_atom); 18 | gc(); 19 | println(new_atom); 20 | gc(); 21 | new_atom 22 | } 23 | 24 | fn main() { 25 | let new_atom = atom(42) 26 | println(new_atom) 27 | reset!(new_atom, 1) 28 | println(new_atom) 29 | println(compare_and_swap!(new_atom, 1, 2)) 30 | println(compare_and_swap!(new_atom, 1, 2)) 31 | println(new_atom) 32 | swap!(new_atom, fn(x) { x + 1 }) 33 | println(new_atom) 34 | let x = deref(new_atom) 35 | println(x) 36 | 37 | let my_atom = create_atom(); 38 | println(my_atom); 39 | gc(); 40 | println(my_atom); 41 | reset!(my_atom, MyPointerStruct { x: 5, y: 6 }); 42 | gc(); 43 | println(my_atom); 44 | "done!" 45 | } 46 | 47 | 48 | // Expect 49 | // Atom { value: 42 } 50 | // Atom { value: 1 } 51 | // true 52 | // false 53 | // Atom { value: 2 } 54 | // Atom { value: 3 } 55 | // 3 56 | // Atom { value: MyPointerStruct { x: 1, y: 2 } } 57 | // Atom { value: MyPointerStruct { x: 1, y: 2 } } 58 | // Atom { value: MyPointerStruct { x: 3, y: 4 } } 59 | // Atom { value: MyPointerStruct { x: 3, y: 4 } } 60 | // Atom { value: MyPointerStruct { x: 3, y: 4 } } 61 | // Atom { value: MyPointerStruct { x: 3, y: 4 } } 62 | // Atom { value: MyPointerStruct { x: 5, y: 6 } } 63 | // done! -------------------------------------------------------------------------------- /resources/beagle.ffi.bg: -------------------------------------------------------------------------------- 1 | namespace beagle.ffi 2 | 3 | import "beagle.builtin" as builtin 4 | 5 | 6 | // Probably should move this stuff to an ffi namespace 7 | struct Library { 8 | id 9 | } 10 | 11 | fn __make_lib_struct(id) { 12 | Library { id: id } 13 | } 14 | 15 | 16 | struct Pointer { 17 | ptr 18 | } 19 | 20 | struct Buffer { 21 | ptr 22 | size 23 | } 24 | 25 | fn __make_pointer_struct(ptr) { 26 | Pointer { ptr: ptr } 27 | } 28 | 29 | fn __make_buffer_struct(ptr, size) { 30 | Buffer { ptr: ptr, size: size } 31 | } 32 | 33 | enum Type { 34 | U8, 35 | U16, 36 | U32, 37 | U64, 38 | I32, 39 | Pointer, 40 | MutablePointer, 41 | String, 42 | Void, 43 | Structure { 44 | types 45 | } 46 | } 47 | 48 | // TODO: I could make a function that maps between a type an integer 49 | // but I don't actually have equality defined for structures 50 | 51 | fn __create_ffi_function(ffi_info) { 52 | // TODO: Hack, but trying to get things to work 53 | fn(a1, a2, a3, a4, a5, a6) { 54 | builtin/__register_c_call(); 55 | let result = call_ffi_info(ffi_info, a1, a2, a3, a4, a5, a6) 56 | builtin/__unregister_c_call() 57 | result 58 | } 59 | } -------------------------------------------------------------------------------- /resources/binary_tree.bg: -------------------------------------------------------------------------------- 1 | namespace binary_tree 2 | 3 | import "beagle.primitive" as primitive 4 | 5 | fn math_max(a, b) { 6 | if a > b { 7 | a 8 | } else { 9 | b 10 | } 11 | } 12 | 13 | fn math_pow(base, exp) { 14 | if exp == 0 { 15 | 1 16 | } else { 17 | base * math_pow(base, exp - 1) 18 | } 19 | } 20 | 21 | 22 | struct TreeNode { 23 | left 24 | right 25 | } 26 | 27 | fn parameterizedTree(depth) { 28 | let maxDepth = math_max(6, depth) 29 | 30 | let stretchDepth = maxDepth + 1 31 | let check = itemCheck(bottomUpTree(stretchDepth)) 32 | print("stretch tree of depth ") 33 | print(stretchDepth) 34 | print(" check: ") 35 | println(check) 36 | 37 | let longLivedTree = bottomUpTree(maxDepth) 38 | loopThroughDepths(4, maxDepth, longLivedTree) 39 | } 40 | 41 | fn main() { 42 | parameterizedTree(18) 43 | } 44 | 45 | fn loopThroughDepths(depth, maxDepth, longLivedTree) { 46 | if depth > maxDepth { 47 | print("long lived tree of depth ") 48 | print(maxDepth) 49 | print(" check: ") 50 | println(itemCheck(longLivedTree)) 51 | } else { 52 | let iterations = math_pow(2, maxDepth - depth + 4) 53 | let check = doWork(iterations, depth) 54 | print(iterations) 55 | print(" trees of depth ") 56 | print(depth) 57 | print(" check: ") 58 | println(check) 59 | loopThroughDepths(depth + 2, maxDepth, longLivedTree) 60 | } 61 | } 62 | 63 | fn doWork(iterations, depth) { 64 | doWorkHelper(iterations, depth, 0, 0) 65 | } 66 | 67 | fn doWorkHelper(iterations, depth, i, accumCheck) { 68 | if i < iterations { 69 | let newCheck = accumCheck + itemCheck(bottomUpTree(depth)) 70 | doWorkHelper(iterations, depth, i + 1, newCheck) 71 | } else { 72 | accumCheck 73 | } 74 | } 75 | 76 | 77 | 78 | fn itemCheck(node) { 79 | if node.left == null { 80 | 1 81 | } else { 82 | 1 + itemCheck(node.left) + itemCheck(node.right) 83 | } 84 | } 85 | 86 | fn bottomUpTree(depth) { 87 | if depth > 0 { 88 | TreeNode { 89 | left: bottomUpTree(depth - 1), 90 | right: bottomUpTree(depth - 1) 91 | } 92 | } else { 93 | TreeNode { 94 | left: null, 95 | right: null 96 | } 97 | } 98 | } 99 | 100 | // Expect 101 | // stretch tree of depth 19 check: 1048575 102 | // 262144 trees of depth 4 check: 8126464 103 | // 65536 trees of depth 6 check: 8323072 104 | // 16384 trees of depth 8 check: 8372224 105 | // 4096 trees of depth 10 check: 8384512 106 | // 1024 trees of depth 12 check: 8387584 107 | // 256 trees of depth 14 check: 8388352 108 | // 64 trees of depth 16 check: 8388544 109 | // 16 trees of depth 18 check: 8388592 110 | // long lived tree of depth 18 check: 524287 111 | // null 112 | -------------------------------------------------------------------------------- /resources/binary_tree_benchmark.bg: -------------------------------------------------------------------------------- 1 | namespace binary_tree_benchmark 2 | import "binary_tree" as tree 3 | 4 | fn main() { 5 | tree/parameterizedTree(21) 6 | } -------------------------------------------------------------------------------- /resources/bits_test.bg: -------------------------------------------------------------------------------- 1 | namespace bits_test 2 | import "beagle.primitive" as primitive 3 | 4 | fn main() { 5 | println(5 & 3); 6 | println(12 & 10); 7 | println(5 | 3); 8 | println(12 | 10); 9 | println(5 ^ 3); 10 | println(12 ^ 10); 11 | println(16 >> 2); 12 | println(16 << 2); 13 | println(16 >>> 2); 14 | println(-16 >> 2); 15 | println(-16 >>> 2); 16 | "done" 17 | } 18 | 19 | // Expect 20 | // 1 21 | // 8 22 | // 7 23 | // 14 24 | // 6 25 | // 6 26 | // 4 27 | // 64 28 | // 4 29 | // -4 30 | // 1073741820 31 | // done -------------------------------------------------------------------------------- /resources/closures.bg: -------------------------------------------------------------------------------- 1 | namespace closures 2 | 3 | fn closure_1() { 4 | let y = fn thing() { 5 | 42 6 | } 7 | println(y) 8 | println(y()) 9 | } 10 | 11 | fn closure_2() { 12 | let x = 42; 13 | let z = 2; 14 | let y = fn closure_fn() { 15 | let q = 1 16 | let y = 5 17 | x + z + q + y 18 | } 19 | println(y()) 20 | } 21 | 22 | 23 | fn closure_3() { 24 | let x = 42; 25 | let f = fn() { 26 | x 27 | } 28 | println(f()) 29 | null 30 | } 31 | 32 | fn main() { 33 | closure_1() 34 | closure_2() 35 | closure_3() 36 | "done" 37 | } 38 | 39 | // Expected output: 40 | // Closure { function, 0, 16, [] } 41 | // 42 42 | // 50 43 | // 42 44 | // done -------------------------------------------------------------------------------- /resources/comparisons.bg: -------------------------------------------------------------------------------- 1 | namespace comparisons 2 | 3 | fn main() { 4 | // primitive_breakpoint!(); 5 | if true { 6 | println("true") 7 | } else { 8 | println("error true") 9 | } 10 | if false { 11 | println("error false") 12 | } else { 13 | println("false") 14 | } 15 | if 1 == 1 { 16 | println("1 == 1") 17 | } else { 18 | println("error 1 == 1") 19 | } 20 | if 1 != 2 { 21 | println("1 != 2") 22 | } else { 23 | println("error 1 != 2") 24 | } 25 | if 1 < 2 { 26 | println("1 < 2") 27 | } else { 28 | println("error 1 < 2") 29 | } 30 | if 2 < 1 { 31 | println("error 2 < 1") 32 | } else { 33 | println("2 < 1") 34 | } 35 | if 1 < 1 { 36 | println("error 1 < 1") 37 | } else { 38 | println("1 < 1") 39 | } 40 | if 1 <= 1 { 41 | println("1 <= 1") 42 | } else { 43 | println("error 1 <= 1") 44 | } 45 | if 1 <= 2 { 46 | println("1 <= 2") 47 | } else { 48 | println("error 1 <= 2") 49 | } 50 | if 2 > 1 { 51 | println("2 > 1") 52 | } else { 53 | println("error 2 > 1") 54 | } 55 | if 1 > 1 { 56 | println("error 1 > 1") 57 | } else { 58 | println("1 > 1") 59 | } 60 | if 1 >= 1 { 61 | println("1 >= 1") 62 | } else { 63 | println("error 1 >= 1") 64 | } 65 | if 2 >= 1 { 66 | println("2 >= 1") 67 | } else { 68 | println("error 2 >= 1") 69 | } 70 | "done!" 71 | } 72 | 73 | // Expect 74 | // true 75 | // false 76 | // 1 == 1 77 | // 1 != 2 78 | // 1 < 2 79 | // 2 < 1 80 | // 1 < 1 81 | // 1 <= 1 82 | // 1 <= 2 83 | // 2 > 1 84 | // 1 > 1 85 | // 1 >= 1 86 | // 2 >= 1 87 | // done! -------------------------------------------------------------------------------- /resources/countdown.bg: -------------------------------------------------------------------------------- 1 | namespace countdown 2 | 3 | fn count_down(x) { 4 | println(x) 5 | if x == 0 { 6 | 0 7 | } else { 8 | count_down(x - 1) 9 | } 10 | } 11 | 12 | fn main() { 13 | count_down(10) 14 | } 15 | 16 | // Expect 17 | // 10 18 | // 9 19 | // 8 20 | // 7 21 | // 6 22 | // 5 23 | // 4 24 | // 3 25 | // 2 26 | // 1 27 | // 0 28 | // 0 -------------------------------------------------------------------------------- /resources/enum_test.bg: -------------------------------------------------------------------------------- 1 | namespace enum_test 2 | 3 | enum Action { 4 | run { speed }, 5 | stop, 6 | } 7 | 8 | fn main() { 9 | let action = Action.run { speed: 5 } 10 | 11 | if action.speed >= 3 { 12 | println("Fast") 13 | } 14 | println(action) 15 | println(action.speed) 16 | println(Action.stop) 17 | 18 | // Need to figure out the proper setup for the enum 19 | // object itself. 20 | // println(Action) 21 | 22 | "done" 23 | } 24 | 25 | // Expect 26 | // Fast 27 | // Action.run { speed: 5 } 28 | // 5 29 | // Action.stop 30 | // done -------------------------------------------------------------------------------- /resources/equal_test.bg: -------------------------------------------------------------------------------- 1 | namespace equal_test 2 | import "beagle.primitive" as primitive 3 | 4 | struct Thing { 5 | x 6 | y 7 | } 8 | 9 | fn main() { 10 | let x = Thing { 11 | x: 1 12 | y: 2 13 | } 14 | 15 | let y = Thing { 16 | x: 1 17 | y: 2 18 | } 19 | println(equal(x, y)) 20 | println(equal(x, x)) 21 | println(equal(x, 1)) 22 | "done" 23 | } -------------------------------------------------------------------------------- /resources/extra_map_test.bg: -------------------------------------------------------------------------------- 1 | namespace extra_map_test 2 | import "persistent_map" as pm 3 | import "beagle.builtin" as builtin 4 | import "beagle.primitive" as primitive 5 | import "raw_mutable_array" as array 6 | 7 | fn test_assoc_node_bitmap() { 8 | let pm = pm/assoc(pm/map(), "key1", "value1") 9 | let added_leaf_box = array/new_array(1) 10 | array/write_field(added_leaf_box, 0, 0) 11 | let h = builtin/hash("key2") 12 | let result = pm/assoc_node(pm.root, 0, h, "key2", "value2", added_leaf_box) 13 | println(result) 14 | println(array/read_field(added_leaf_box, 0)) 15 | } 16 | 17 | 18 | fn main() { 19 | test_assoc_node_bitmap() 20 | } 21 | -------------------------------------------------------------------------------- /resources/ffi_test.bg: -------------------------------------------------------------------------------- 1 | namespace ffi_test 2 | 3 | // TODO: I need to fix my std library dependence on persistent_vector 4 | import "persistent_vector" as vec 5 | import "beagle.ffi" as ffi 6 | import "beagle.primitive" as primitive 7 | import "repl_test" as repl 8 | import "socket_test" as socket 9 | 10 | 11 | 12 | struct State { 13 | rect_x 14 | rect_y 15 | dx 16 | dy 17 | screen_width 18 | screen_height 19 | rect_color 20 | } 21 | 22 | struct Color { 23 | r 24 | g 25 | b 26 | } 27 | 28 | 29 | let state = atom(State { 30 | rect_x: 200, 31 | rect_y: 150, 32 | dx: 2, 33 | dy: 2, 34 | screen_width: 640, 35 | screen_height: 480, 36 | rect_color: Color { r: 255, g: 0, b: 0} 37 | }) 38 | 39 | 40 | let sdl = ffi/load_library("/opt/homebrew/lib/libSDL2-2.0.0.dylib") 41 | let sdl_init = ffi/get_function(sdl, "SDL_Init", [ffi/Type.U32], ffi/Type.I32) 42 | let sdl_create_window = ffi/get_function( 43 | sdl, 44 | "SDL_CreateWindow", 45 | [ffi/Type.String, ffi/Type.I32, ffi/Type.I32, ffi/Type.I32, ffi/Type.I32, ffi/Type.U32], 46 | ffi/Type.Pointer 47 | ) 48 | let sdl_create_renderer = ffi/get_function( 49 | sdl, 50 | "SDL_CreateRenderer", 51 | [ffi/Type.Pointer, ffi/Type.I32, ffi/Type.U32], 52 | ffi/Type.Pointer 53 | ) 54 | let sdl_set_render_draw_color = ffi/get_function( 55 | sdl, 56 | "SDL_SetRenderDrawColor", 57 | [ffi/Type.Pointer, ffi/Type.U8, ffi/Type.U8, ffi/Type.U8, ffi/Type.U8], 58 | ffi/Type.I32 59 | ) 60 | let sdl_render_clear = ffi/get_function(sdl, "SDL_RenderClear", [ffi/Type.Pointer], ffi/Type.I32) 61 | let sdl_render_present = ffi/get_function(sdl, "SDL_RenderPresent", [ffi/Type.Pointer], ffi/Type.Void) 62 | let sdl_poll_event = ffi/get_function(sdl, "SDL_PollEvent", [ffi/Type.Pointer], ffi/Type.I32) 63 | let sdl_delay = ffi/get_function(sdl, "SDL_Delay", [ffi/Type.U32], ffi/Type.Void) 64 | let sdl_render_fill_rect = ffi/get_function( 65 | sdl, 66 | "SDL_RenderFillRect", 67 | [ffi/Type.Pointer, ffi/Type.Pointer], 68 | ffi/Type.I32 69 | ) 70 | 71 | let sdl_get_window_size = ffi/get_function( 72 | sdl, 73 | "SDL_GetWindowSize", 74 | [ffi/Type.Pointer, ffi/Type.Pointer, ffi/Type.Pointer], 75 | ffi/Type.Void 76 | ) 77 | 78 | // Rect structure: [x, y, w, h] (4 * 4 bytes = 16 bytes) 79 | let rect_size = 16 80 | 81 | fn update_state(new_width, new_height) { 82 | swap!(state, fn(s) { 83 | State { 84 | rect_x: s.rect_x + s.dx, 85 | rect_y: s.rect_y + s.dy, 86 | dx: if s.rect_x + s.dx <= 0 || s.rect_x + s.dx + 240 >= s.screen_width { 87 | s.dx * -1 88 | } else { 89 | s.dx 90 | }, 91 | dy: if s.rect_y + s.dy <= 0 || s.rect_y + s.dy + 180 >= s.screen_height { 92 | s.dy * -1 93 | } else { 94 | s.dy 95 | }, 96 | screen_width: new_width, 97 | screen_height: new_height, 98 | rect_color: s.rect_color 99 | } 100 | }) 101 | } 102 | 103 | fn change_color(color) { 104 | // TODO: assoc function for structs 105 | swap!(state, fn(s) { 106 | State { 107 | rect_x: s.rect_x, 108 | rect_y: s.rect_y, 109 | dx: s.dx, 110 | dy: s.dy, 111 | screen_width: s.screen_width, 112 | screen_height: s.screen_height, 113 | rect_color: color 114 | } 115 | }) 116 | } 117 | 118 | fn loop_it(buffer, renderer, rect_ptr, window) { 119 | sdl_poll_event(buffer) 120 | if ffi/get_u32(buffer, 0) == 256 { 121 | true 122 | } else { 123 | // Check if window has been resized 124 | let current_state = deref(state) 125 | let width_ptr = ffi/allocate(4) 126 | let height_ptr = ffi/allocate(4) 127 | sdl_get_window_size(window, width_ptr, height_ptr) 128 | 129 | let new_width = ffi/get_i32(width_ptr, 0) 130 | let new_height = ffi/get_i32(height_ptr, 0) 131 | 132 | let updated_state = update_state(new_width, new_height) 133 | 134 | // Clear the renderer 135 | sdl_set_render_draw_color(renderer, 0, 0, 0, 255) // Black background 136 | sdl_render_clear(renderer) 137 | 138 | // Update the rectangle position 139 | ffi/set_i32(rect_ptr, 0, updated_state.rect_x) 140 | ffi/set_i32(rect_ptr, 4, updated_state.rect_y) 141 | 142 | // Draw the rectangle 143 | let color = current_state.rect_color 144 | sdl_set_render_draw_color(renderer, color.r, color.g, color.b, 255) // Red color 145 | sdl_render_fill_rect(renderer, rect_ptr) 146 | 147 | // Present the updated screen 148 | sdl_render_present(renderer) 149 | 150 | sdl_delay(16) 151 | loop_it(buffer, renderer, rect_ptr, window) 152 | } 153 | } 154 | 155 | fn main() { 156 | 157 | // thread(repl/repl) 158 | thread(fn() { 159 | socket/server(12345, fn(data) { 160 | eval(data) 161 | }) 162 | }) 163 | println("Starting") 164 | sdl_init(32) 165 | 166 | let window = sdl_create_window("SDL2 Window", 100, 100, 640, 480, 4) 167 | 168 | let renderer = sdl_create_renderer(window, -1, 2) 169 | 170 | // Allocate memory for event buffer and rect 171 | let buffer = ffi/allocate(56) 172 | let rect_ptr = ffi/allocate(rect_size) 173 | 174 | // Set up rectangle dimensions: x=200, y=150, w=240, h=180 175 | ffi/set_i32(rect_ptr, 0, 200) // x 176 | ffi/set_i32(rect_ptr, 4, 150) // y 177 | ffi/set_i32(rect_ptr, 8, 240) // w 178 | ffi/set_i32(rect_ptr, 12, 180) // h 179 | 180 | loop_it(buffer, renderer, rect_ptr, window) 181 | 182 | println("Done") 183 | } 184 | -------------------------------------------------------------------------------- /resources/fib.bg: -------------------------------------------------------------------------------- 1 | namespace fib 2 | 3 | fn fib(n) { 4 | if n <= 1 { 5 | n 6 | } else { 7 | fib(n - 1) + fib(n - 2) 8 | } 9 | } 10 | 11 | fn main() { 12 | println(fib(30)); 13 | } 14 | 15 | // Expect 16 | // 832040 17 | // null -------------------------------------------------------------------------------- /resources/floats.bg: -------------------------------------------------------------------------------- 1 | namespace floats 2 | 3 | import "beagle.primitive" as primitive 4 | 5 | let my_float = 3.14159 6 | let my_int = 3 7 | 8 | fn main() { 9 | gc() 10 | gc() 11 | println(my_int) 12 | println(my_float) 13 | let my_float2 = my_float + 1.0 14 | let my_float3 = my_float2 - 1.0 15 | let my_float4 = my_float3 * 2.0 16 | let my_float5 = my_float4 / 2.0 17 | let my_int2 = my_int + 1 18 | let my_int3 = my_int2 - 1 19 | let my_int4 = my_int3 * 2 20 | let my_int5 = my_int4 / 2 21 | gc() 22 | gc() 23 | println(my_int2) 24 | println(my_float2) 25 | println(my_int3) 26 | println(my_float3) 27 | println(my_int4) 28 | println(my_float4) 29 | println(my_int5) 30 | println(my_float5) 31 | "done" 32 | } 33 | 34 | // Expect 35 | // 3 36 | // 3.14159 37 | // 4 38 | // 4.14159 39 | // 3 40 | // 3.14159 41 | // 6 42 | // 6.28318 43 | // 3 44 | // 3.14159 45 | // done -------------------------------------------------------------------------------- /resources/grab_bag.bg: -------------------------------------------------------------------------------- 1 | namespace grab_bag 2 | 3 | import "binary_tree" as binary_tree 4 | 5 | 6 | // Intentionally after to make sure 7 | // declaration order doesn't matter 8 | struct TreeNode { 9 | left 10 | right 11 | } 12 | 13 | 14 | 15 | fn a() { 16 | b() 17 | } 18 | 19 | fn b() { 20 | 42 21 | } 22 | 23 | 24 | struct Range { 25 | start 26 | end 27 | } 28 | 29 | struct OtherStruct { 30 | x 31 | y 32 | } 33 | 34 | fn range(start, end) { 35 | Range { 36 | start: start, 37 | end: end 38 | } 39 | } 40 | 41 | fn other_struct(x, y) { 42 | OtherStruct { 43 | x: x, 44 | y: y 45 | } 46 | } 47 | 48 | 49 | fn get_range_start() { 50 | let range = range(0, 10); 51 | range.start 52 | } 53 | 54 | fn simpleMakeTree() { 55 | println("simpleMakeTree") 56 | TreeNode { 57 | left: null, 58 | right: null 59 | } 60 | } 61 | 62 | fn testMakeTree() { 63 | simpleMakeTree() 64 | TreeNode { 65 | left: simpleMakeTree(), 66 | right: simpleMakeTree() 67 | } 68 | } 69 | 70 | fn testGcWithTree(n) { 71 | binary_tree/bottomUpTree(n) 72 | } 73 | 74 | fn testGcSimple(n) { 75 | println("Starting garbage") 76 | let garbage = TreeNode { 77 | left: null 78 | right: null 79 | } 80 | println("garbage") 81 | 82 | let x = TreeNode { 83 | left: null 84 | right: null 85 | } 86 | 87 | println("x") 88 | 89 | let y = TreeNode { 90 | left: x, 91 | right: x 92 | } 93 | 94 | println("y") 95 | 96 | let z = TreeNode { 97 | left: y, 98 | right: y, 99 | } 100 | 101 | println("z") 102 | println(z) 103 | z 104 | } 105 | 106 | fn testGcNested(n) { 107 | let y = testGcSimple() 108 | 109 | println("done with call") 110 | 111 | let z = TreeNode { 112 | left: y 113 | right: y 114 | } 115 | println("z2") 116 | 117 | let q = TreeNode { 118 | left: z 119 | right: z 120 | } 121 | 122 | println("q") 123 | 124 | y 125 | } 126 | 127 | fn testGc(n) { 128 | if n > 10 { 129 | 0 130 | } else { 131 | let x = TreeNode { 132 | left: null 133 | right: null 134 | } 135 | 1 + testGc(n + 1) 136 | 137 | let y = TreeNode { 138 | left: x, 139 | right: x 140 | } 141 | 142 | 1 + testGc(n + 1) 143 | 144 | let z = TreeNode { 145 | left: y, 146 | right: y, 147 | } 148 | println(x) 149 | println(y) 150 | println(z) 151 | 0 152 | } 153 | } 154 | 155 | fn simpleFunctionWithLocals() { 156 | let x = 2; 157 | let y = 3; 158 | x + y 159 | } 160 | 161 | fn math_with_parens() { 162 | (1 + 2) * 3 163 | } 164 | 165 | fn log(x) { 166 | println(x) 167 | x 168 | } 169 | 170 | fn structs_dont_care_about_order() { 171 | OtherStruct { 172 | y: log(2), 173 | x: log(1) 174 | } 175 | } 176 | 177 | 178 | fn tail_recursive(n, accum) { 179 | if n == 0 { 180 | accum 181 | } else { 182 | // This will blow the stack if we break tail recursion 183 | let x = OtherStruct { x: 1, y: 2 } 184 | let y = OtherStruct { x: 1, y: 2 } 185 | let z = OtherStruct { x: 1, y: 2 } 186 | let a = OtherStruct { x: 1, y: 2 } 187 | let b = OtherStruct { x: 1, y: 2 } 188 | let c = OtherStruct { x: 1, y: 2 } 189 | let d = OtherStruct { x: 1, y: 2 } 190 | let e = OtherStruct { x: 1, y: 2 } 191 | tail_recursive(n - 1, accum + n) 192 | } 193 | } 194 | 195 | 196 | fn main() { 197 | println(simpleFunctionWithLocals()) 198 | println(testGc(8)) 199 | println(testGcSimple(3)) 200 | println(testGcNested(3)) 201 | println(testGcWithTree(3)) 202 | println(testMakeTree()) 203 | println(get_range_start()) 204 | println(range(0, 10)) 205 | println(a()) 206 | println(b()) 207 | println(math_with_parens()) 208 | println(structs_dont_care_about_order()) 209 | println(tail_recursive(300000, 0)) 210 | } 211 | 212 | 213 | // Expect 214 | // 5 215 | // TreeNode { left: null, right: null } 216 | // TreeNode { left: TreeNode { left: null, right: null }, right: TreeNode { left: null, right: null } } 217 | // TreeNode { left: TreeNode { left: TreeNode { left: null, right: null }, right: TreeNode { left: null, right: null } }, right: TreeNode { left: TreeNode { left: null, right: null }, right: TreeNode { left: null, right: null } } } 218 | // TreeNode { left: null, right: null } 219 | // TreeNode { left: TreeNode { left: null, right: null }, right: TreeNode { left: null, right: null } } 220 | // TreeNode { left: TreeNode { left: TreeNode { left: null, right: null }, right: TreeNode { left: null, right: null } }, right: TreeNode { left: TreeNode { left: null, right: null }, right: TreeNode { left: null, right: null } } } 221 | // TreeNode { left: null, right: null } 222 | // TreeNode { left: TreeNode { left: null, right: null }, right: TreeNode { left: null, right: null } } 223 | // TreeNode { left: TreeNode { left: TreeNode { left: null, right: null }, right: TreeNode { left: null, right: null } }, right: TreeNode { left: TreeNode { left: null, right: null }, right: TreeNode { left: null, right: null } } } 224 | // TreeNode { left: null, right: null } 225 | // TreeNode { left: TreeNode { left: null, right: null }, right: TreeNode { left: null, right: null } } 226 | // TreeNode { left: TreeNode { left: TreeNode { left: null, right: null }, right: TreeNode { left: null, right: null } }, right: TreeNode { left: TreeNode { left: null, right: null }, right: TreeNode { left: null, right: null } } } 227 | // TreeNode { left: null, right: null } 228 | // TreeNode { left: TreeNode { left: null, right: null }, right: TreeNode { left: null, right: null } } 229 | // TreeNode { left: TreeNode { left: TreeNode { left: null, right: null }, right: TreeNode { left: null, right: null } }, right: TreeNode { left: TreeNode { left: null, right: null }, right: TreeNode { left: null, right: null } } } 230 | // TreeNode { left: null, right: null } 231 | // TreeNode { left: TreeNode { left: null, right: null }, right: TreeNode { left: null, right: null } } 232 | // TreeNode { left: TreeNode { left: TreeNode { left: null, right: null }, right: TreeNode { left: null, right: null } }, right: TreeNode { left: TreeNode { left: null, right: null }, right: TreeNode { left: null, right: null } } } 233 | // TreeNode { left: null, right: null } 234 | // TreeNode { left: TreeNode { left: null, right: null }, right: TreeNode { left: null, right: null } } 235 | // TreeNode { left: TreeNode { left: TreeNode { left: null, right: null }, right: TreeNode { left: null, right: null } }, right: TreeNode { left: TreeNode { left: null, right: null }, right: TreeNode { left: null, right: null } } } 236 | // 0 237 | // Starting garbage 238 | // garbage 239 | // x 240 | // y 241 | // z 242 | // TreeNode { left: TreeNode { left: TreeNode { left: null, right: null }, right: TreeNode { left: null, right: null } }, right: TreeNode { left: TreeNode { left: null, right: null }, right: TreeNode { left: null, right: null } } } 243 | // TreeNode { left: TreeNode { left: TreeNode { left: null, right: null }, right: TreeNode { left: null, right: null } }, right: TreeNode { left: TreeNode { left: null, right: null }, right: TreeNode { left: null, right: null } } } 244 | // Starting garbage 245 | // garbage 246 | // x 247 | // y 248 | // z 249 | // TreeNode { left: TreeNode { left: TreeNode { left: null, right: null }, right: TreeNode { left: null, right: null } }, right: TreeNode { left: TreeNode { left: null, right: null }, right: TreeNode { left: null, right: null } } } 250 | // done with call 251 | // z2 252 | // q 253 | // TreeNode { left: TreeNode { left: TreeNode { left: null, right: null }, right: TreeNode { left: null, right: null } }, right: TreeNode { left: TreeNode { left: null, right: null }, right: TreeNode { left: null, right: null } } } 254 | // TreeNode { left: TreeNode { left: TreeNode { left: TreeNode { left: null, right: null }, right: TreeNode { left: null, right: null } }, right: TreeNode { left: TreeNode { left: null, right: null }, right: TreeNode { left: null, right: null } } }, right: TreeNode { left: TreeNode { left: TreeNode { left: null, right: null }, right: TreeNode { left: null, right: null } }, right: TreeNode { left: TreeNode { left: null, right: null }, right: TreeNode { left: null, right: null } } } } 255 | // simpleMakeTree 256 | // simpleMakeTree 257 | // simpleMakeTree 258 | // TreeNode { left: TreeNode { left: null, right: null }, right: TreeNode { left: null, right: null } } 259 | // 0 260 | // Range { start: 0, end: 10 } 261 | // 42 262 | // 42 263 | // 9 264 | // 2 265 | // 1 266 | // OtherStruct { x: 1, y: 2 } 267 | // 45000150000 268 | // null -------------------------------------------------------------------------------- /resources/hamt_benchmark.bg: -------------------------------------------------------------------------------- 1 | namespace hamt_benchmark 2 | 3 | import "hamt" as hamt 4 | 5 | fn main() { 6 | 7 | // TODO This fails for some reason. 8 | // I'm betting it has to do with namespace stuff again 9 | println(hamt/make_vec_of_every_size_and_verify(10000)) 10 | } -------------------------------------------------------------------------------- /resources/if_test.bg: -------------------------------------------------------------------------------- 1 | namespace if_test 2 | 3 | import "beagle.primitive" as primitive 4 | 5 | fn side_effect() { 6 | println("side effect") 7 | true 8 | } 9 | 10 | 11 | fn main() { 12 | if true { 13 | println(1) 14 | } 15 | 16 | if false { 17 | println(false) 18 | } else { 19 | println(2) 20 | } 21 | 22 | if true && true { 23 | println(3) 24 | } 25 | 26 | if true && false { 27 | println(false) 28 | } else { 29 | println(4) 30 | } 31 | 32 | if false && true { 33 | println(false) 34 | } else { 35 | println(5) 36 | } 37 | 38 | if true || true { 39 | println(6) 40 | } 41 | 42 | if true || false { 43 | println(7) 44 | } 45 | 46 | if false || true { 47 | println(8) 48 | } 49 | 50 | if false || false { 51 | println(false) 52 | } else { 53 | println(9) 54 | } 55 | 56 | if true || side_effect() { 57 | println(10) 58 | } 59 | 60 | if false && side_effect() { 61 | println(false) 62 | } else { 63 | println(11) 64 | } 65 | 66 | let x = if true { 67 | 12 68 | } else { 69 | false 70 | } 71 | println(x) 72 | 73 | 74 | let y = if false { 75 | false 76 | } else { 77 | 13 78 | } 79 | println(y) 80 | 81 | let x = if false { 82 | false 83 | } else if true { 84 | 14 85 | } else { 86 | false 87 | } 88 | println(x) 89 | 90 | let x = if false { 91 | println("not here") 92 | false 93 | } else if false { 94 | println("not here") 95 | false 96 | } else { 97 | 15 98 | } 99 | println(x) 100 | 101 | let x = if false { 102 | println("not here") 103 | false 104 | } else if false { 105 | println("not here") 106 | false 107 | } else if false { 108 | println("not here") 109 | false 110 | } else { 111 | 16 112 | } 113 | println(x) 114 | 115 | let x = if true { 116 | 17 117 | } else if false { 118 | println("not here") 119 | false 120 | } else if false { 121 | println("not here") 122 | false 123 | } else { 124 | println("not here") 125 | } 126 | println(x) 127 | "done" 128 | } 129 | 130 | // Expect 131 | // 1 132 | // 2 133 | // 3 134 | // 4 135 | // 5 136 | // 6 137 | // 7 138 | // 8 139 | // 9 140 | // 10 141 | // 11 142 | // 12 143 | // 13 144 | // 14 145 | // 15 146 | // 16 147 | // 17 148 | // done 149 | -------------------------------------------------------------------------------- /resources/import_test.bg: -------------------------------------------------------------------------------- 1 | namespace import_test 2 | import "fib" as fib 3 | import "fib" as fib2 4 | 5 | fn main() { 6 | println(fib/fib(10)) 7 | println(fib2/fib(10)) 8 | "done" 9 | } 10 | -------------------------------------------------------------------------------- /resources/lib_lldb.bg: -------------------------------------------------------------------------------- 1 | namespace lib_lldb 2 | 3 | import "beagle.ffi" as ffi 4 | import "beagle.primitive" as primitive 5 | import "persistent_vector" as vector 6 | 7 | 8 | let _lldb = ffi/load_library("/Users/jimmyhmiller/Documents/Code/open-source/lldb-sys.rs/src/liblldb_bindings.dylib") 9 | let _initialize = ffi/get_function(_lldb, "SBDebuggerInitialize", [], ffi/Type.Void) 10 | let _debugger_create = ffi/get_function(_lldb, "SBDebuggerCreate", [ffi/Type.U32], ffi/Type.Pointer) 11 | let _debugger_set_async = ffi/get_function(_lldb, "SBDebuggerSetAsync", [ffi/Type.Pointer, ffi/Type.U32], ffi/Type.Void) 12 | let _debugger_create_target2 = ffi/get_function(_lldb, "SBDebuggerCreateTarget2", [ffi/Type.Pointer, ffi/Type.String], ffi/Type.Pointer) 13 | let _target_is_valid = ffi/get_function(_lldb, "SBTargetIsValid", [ffi/Type.Pointer], ffi/Type.U32) 14 | let _target_find_functions = ffi/get_function(_lldb, "SBTargetFindFunctions", [ffi/Type.Pointer, ffi/Type.String, ffi/Type.U32], ffi/Type.Pointer) 15 | let _symbol_context_list_get_size = ffi/get_function(_lldb, "SBSymbolContextListGetSize", [ffi/Type.Pointer], ffi/Type.U32) 16 | let _symbol_context_list_get_context_at_index = ffi/get_function(_lldb, "SBSymbolContextListGetContextAtIndex", [ffi/Type.Pointer, ffi/Type.U32], ffi/Type.Pointer) 17 | let _target_breakpoint_create_by_name = ffi/get_function(_lldb, "SBTargetBreakpointCreateByName", [ffi/Type.Pointer, ffi/Type.String, ffi/Type.String], ffi/Type.Pointer) 18 | let _target_breakpoint_create_by_address = ffi/get_function(_lldb, "SBTargetBreakpointCreateByAddress", [ffi/Type.Pointer, ffi/Type.U64], ffi/Type.Pointer) 19 | let _breakpoint_set_enabled = ffi/get_function(_lldb, "SBBreakpointSetEnabled", [ffi/Type.Pointer, ffi/Type.U32], ffi/Type.Void) 20 | let _breakpoint_is_enabled = ffi/get_function(_lldb, "SBBreakpointIsEnabled", [ffi/Type.Pointer], ffi/Type.U32) 21 | let _target_enable_all_breakpoints = ffi/get_function(_lldb, "SBTargetEnableAllBreakpoints", [ffi/Type.Pointer], ffi/Type.U32) 22 | let _create_sb_launch_info = ffi/get_function(_lldb, "CreateSBLaunchInfo", [ffi/Type.Pointer], ffi/Type.Pointer) 23 | let _target_launch2 = ffi/get_function(_lldb, "SBTargetLaunch2", [ffi/Type.Pointer, ffi/Type.Pointer, ffi/Type.Pointer], ffi/Type.Pointer) 24 | let _launch_info_set_arguments = ffi/get_function(_lldb, "SBLaunchInfoSetArguments", [ffi/Type.Pointer, ffi/Type.Pointer, ffi/Type.U32], ffi/Type.Void) 25 | let _create_sb_error = ffi/get_function(_lldb, "CreateSBError", [], ffi/Type.Pointer) 26 | let _launch_info_set_launch_flags = ffi/get_function(_lldb, "SBLaunchInfoSetLaunchFlags", [ffi/Type.Pointer, ffi/Type.U32], ffi/Type.Void) 27 | let _process_get_state = ffi/get_function(_lldb, "SBProcessGetState", [ffi/Type.Pointer], ffi/Type.U32) 28 | let _process_get_thread_by_index_id = ffi/get_function(_lldb, "SBProcessGetThreadByIndexID", [ffi/Type.Pointer, ffi/Type.U32], ffi/Type.Pointer) 29 | let _process_continue = ffi/get_function(_lldb, "SBProcessContinue", [ffi/Type.Pointer], ffi/Type.Pointer) 30 | let _thread_get_selected_frame = ffi/get_function(_lldb, "SBThreadGetSelectedFrame", [ffi/Type.Pointer], ffi/Type.Pointer) 31 | let _frame_find_register = ffi/get_function(_lldb, "SBFrameFindRegister", [ffi/Type.Pointer, ffi/Type.String], ffi/Type.Pointer) 32 | let _frame_get_function_name = ffi/get_function(_lldb, "SBFrameGetFunctionName", [ffi/Type.Pointer], ffi/Type.String) 33 | let _frame_get_pc = ffi/get_function(_lldb, "SBFrameGetPC", [ffi/Type.Pointer], ffi/Type.U64) 34 | let _frame_get_pc_address = ffi/get_function(_lldb, "SBFrameGetPCAddress", [ffi/Type.Pointer], ffi/Type.Pointer) 35 | let _thread_step_instruction = ffi/get_function(_lldb, "SBThreadStepInstruction", [ffi/Type.Pointer, ffi/Type.U32, ffi/Type.Pointer], ffi/Type.Void) 36 | let _error_get_c_string = ffi/get_function(_lldb, "SBErrorGetCString", [ffi/Type.Pointer], ffi/Type.String) 37 | let _error_success = ffi/get_function(_lldb, "SBErrorSuccess", [ffi/Type.Pointer], ffi/Type.U32) 38 | let _thread_get_stop_reason = ffi/get_function(_lldb, "SBThreadGetStopReason", [ffi/Type.Pointer], ffi/Type.U32) 39 | 40 | // pub fn SBValueGetValueAsUnsigned2(instance: SBValueRef, fail_value: u64) -> u64; 41 | let _value_get_value_as_unsigned2 = ffi/get_function(_lldb, "SBValueGetValueAsUnsigned2", [ffi/Type.Pointer, ffi/Type.U64], ffi/Type.U64) 42 | 43 | 44 | 45 | // pub fn SBTargetGetInstructionsWithFlavor( 46 | // instance: SBTargetRef, 47 | // base_addr: SBAddressRef, 48 | // flavor_string: *const ::std::os::raw::c_char, 49 | // buf: *mut ::std::os::raw::c_void, 50 | // size: usize, 51 | // ) -> SBInstructionListRef; 52 | 53 | let _target_get_instructions_with_flavor = ffi/get_function( 54 | _lldb, 55 | "SBTargetGetInstructionsWithFlavor", 56 | [ffi/Type.Pointer, ffi/Type.Pointer, ffi/Type.String, ffi/Type.Pointer, ffi/Type.U64], 57 | ffi/Type.Pointer 58 | ) 59 | 60 | // pub fn SBInstructionListGetSize(instance: SBInstructionListRef) -> usize; 61 | let _instruction_list_get_size = ffi/get_function(_lldb, "SBInstructionListGetSize", [ffi/Type.Pointer], ffi/Type.U64) 62 | 63 | // pub fn SBInstructionListGetInstructionAtIndex( 64 | // instance: SBInstructionListRef, 65 | // idx: u32, 66 | // ) -> SBInstructionRef; 67 | 68 | let _instruction_list_get_instruction_at_index = ffi/get_function( 69 | _lldb, 70 | "SBInstructionListGetInstructionAtIndex", 71 | [ffi/Type.Pointer, ffi/Type.U32], 72 | ffi/Type.Pointer 73 | ) 74 | 75 | 76 | 77 | // pub fn SBInstructionGetAddress(instance: SBInstructionRef) -> SBAddressRef; 78 | // pub fn SBInstructionGetMnemonic( 79 | // instance: SBInstructionRef, 80 | // target: SBTargetRef, 81 | // ) -> *const ::std::os::raw::c_char; 82 | // pub fn SBInstructionGetOperands( 83 | // instance: SBInstructionRef, 84 | // target: SBTargetRef, 85 | // ) -> *const ::std::os::raw::c_char; 86 | // pub fn SBInstructionGetComment( 87 | // instance: SBInstructionRef, 88 | // target: SBTargetRef, 89 | // ) -> *const ::std::os::raw::c_char; 90 | 91 | let _instruction_get_address = ffi/get_function(_lldb, "SBInstructionGetAddress", [ffi/Type.Pointer], ffi/Type.Pointer) 92 | let _instruction_get_mnemonic = ffi/get_function(_lldb, "SBInstructionGetMnemonic", [ffi/Type.Pointer, ffi/Type.Pointer], ffi/Type.String) 93 | let _instruction_get_operands = ffi/get_function(_lldb, "SBInstructionGetOperands", [ffi/Type.Pointer, ffi/Type.Pointer], ffi/Type.String) 94 | let _instruction_get_comment = ffi/get_function(_lldb, "SBInstructionGetComment", [ffi/Type.Pointer, ffi/Type.Pointer], ffi/Type.String) 95 | 96 | 97 | // pub fn SBAddressGetLoadAddress(instance: SBAddressRef, target: SBTargetRef) -> lldb_addr_t; 98 | let _address_get_load_address = ffi/get_function(_lldb, "SBAddressGetLoadAddress", [ffi/Type.Pointer, ffi/Type.Pointer], ffi/Type.U64) 99 | 100 | 101 | // pub fn SBProcessReadMemory( 102 | // instance: SBProcessRef, 103 | // addr: lldb_addr_t, 104 | // buf: *mut ::std::os::raw::c_void, 105 | // size: usize, 106 | // error: SBErrorRef, 107 | // ) -> usize; 108 | let _process_read_memory = ffi/get_function( 109 | _lldb, 110 | "SBProcessReadMemory", 111 | [ffi/Type.Pointer, ffi/Type.U64, ffi/Type.Pointer, ffi/Type.U64, ffi/Type.Pointer], 112 | ffi/Type.U64 113 | ) 114 | 115 | 116 | fn initialize() { 117 | _initialize() 118 | } 119 | 120 | fn create_debugger() { 121 | _debugger_create(0) 122 | } 123 | 124 | fn set_async(debugger, async?) { 125 | _debugger_set_async(debugger, if async? { 1 } else { 0 }) 126 | } 127 | 128 | fn create_target(debugger, filename) { 129 | _debugger_create_target2(debugger, filename) 130 | } 131 | 132 | fn is_target_valid(target) { 133 | _target_is_valid(target) == 1 134 | } 135 | 136 | fn find_functions(target, name, mask) { 137 | _target_find_functions(target, name, mask) 138 | } 139 | 140 | fn get_symbol_list_size(list) { 141 | _symbol_context_list_get_size(list) 142 | } 143 | 144 | fn get_context_at_index(list, idx) { 145 | _symbol_context_list_get_context_at_index(list, idx) 146 | } 147 | 148 | fn create_breakpoint(target, symbol_name, module_name) { 149 | _target_breakpoint_create_by_name(target, symbol_name, module_name) 150 | } 151 | 152 | fn create_breakpoint_by_address(target, address) { 153 | _target_breakpoint_create_by_address(target, address) 154 | } 155 | 156 | fn set_breakpoint_enabled(breakpoint, enable?) { 157 | _breakpoint_set_enabled(breakpoint, if enable? { 1 } else { 0 }) 158 | } 159 | 160 | fn breakpoint_enabled?(breakpoint) { 161 | _breakpoint_is_enabled(breakpoint) == 1 162 | } 163 | 164 | fn enable_all_breakpoints(target) { 165 | _target_enable_all_breakpoints(target) 166 | } 167 | 168 | fn create_launch_info(argv) { 169 | _create_sb_launch_info(argv) 170 | } 171 | 172 | fn launch_target(target, launch_info, error) { 173 | _target_launch2(target, launch_info, error) 174 | } 175 | 176 | fn create_error() { 177 | _create_sb_error() 178 | } 179 | 180 | fn set_launch_flags(launch_info, flags) { 181 | _launch_info_set_launch_flags(launch_info, flags) 182 | } 183 | 184 | fn get_process_state(process) { 185 | _process_get_state(process) 186 | } 187 | 188 | fn get_thread_by_index(process, idx) { 189 | _process_get_thread_by_index_id(process, idx) 190 | } 191 | 192 | fn get_selected_frame(thread) { 193 | _thread_get_selected_frame(thread) 194 | } 195 | 196 | fn get_stop_reason(thread) { 197 | _thread_get_stop_reason(thread) 198 | } 199 | 200 | fn find_register(frame, name) { 201 | _frame_find_register(frame, name) 202 | } 203 | 204 | fn get_register_value(register) { 205 | _value_get_value_as_unsigned2(register, 0) 206 | } 207 | 208 | fn get_pc(frame) { 209 | _frame_get_pc(frame) 210 | } 211 | 212 | fn get_pc_address(frame) { 213 | _frame_get_pc_address(frame) 214 | } 215 | 216 | fn get_function_name(frame) { 217 | _frame_get_function_name(frame) 218 | } 219 | 220 | fn step_instruction(thread, step_over, error) { 221 | _thread_step_instruction(thread, if step_over { 1 } else { 0 }, error) 222 | } 223 | 224 | fn error?(error) { 225 | _error_success(error) == 1 226 | } 227 | 228 | fn get_error_message(error) { 229 | _error_get_c_string(error) 230 | } 231 | 232 | let process_stopped = 5; 233 | let stop_at_entry = 4 234 | let process_exited = 10 235 | 236 | fn get_instructions_raw(process, target, base_address) { 237 | let flavor = "intel" 238 | let size = 1024 239 | let loaded_address = _address_get_load_address(base_address, target) 240 | println(loaded_address) 241 | let buffer = ffi/allocate(size) 242 | let error = create_error() 243 | _process_read_memory(process, loaded_address, buffer, size, error) 244 | 245 | _target_get_instructions_with_flavor(target, base_address, flavor, buffer, size) 246 | } 247 | 248 | fn get_memory(process, address, size) { 249 | let buffer = ffi/allocate(size) 250 | let error = create_error() 251 | _process_read_memory(process, address, buffer, size, error) 252 | ffi/get_string(buffer, 0, size) 253 | } 254 | 255 | 256 | fn get_instruction_list_size(list) { 257 | _instruction_list_get_size(list) 258 | } 259 | 260 | fn get_instruction_at_index(list, idx) { 261 | _instruction_list_get_instruction_at_index(list, idx) 262 | } 263 | 264 | fn continue(process) { 265 | _process_continue(process) 266 | } 267 | 268 | fn add_args(target, args) { 269 | let args = vector/to_array(args) 270 | let buffer = ffi/create_array(ffi/Type.String, args) 271 | _launch_info_set_arguments(target, buffer, 1) 272 | } 273 | 274 | struct Instruction { 275 | address 276 | mnemonic 277 | operands 278 | comment 279 | } 280 | 281 | fn get_instruction_info(instruction, target) { 282 | let address = _instruction_get_address(instruction) 283 | let address = _address_get_load_address(address, target) 284 | let mnemonic = _instruction_get_mnemonic(instruction, target) 285 | let operands = _instruction_get_operands(instruction, target) 286 | let comment = _instruction_get_comment(instruction, target) 287 | 288 | Instruction { 289 | address: address, 290 | mnemonic: mnemonic, 291 | operands: operands, 292 | comment: comment 293 | } 294 | } 295 | 296 | fn get_instructions_helper(list, target, result, count, i) { 297 | if i == count { 298 | result 299 | } else { 300 | let instruction = get_instruction_at_index(list, i) 301 | let info = get_instruction_info(instruction, target) 302 | let new_result = push(result, info) 303 | get_instructions_helper(list, target, new_result, count, i + 1) 304 | } 305 | } 306 | 307 | fn get_instructions(process, target, base_address) { 308 | println("HERE!") 309 | let raw_instructions = get_instructions_raw(process, target, base_address) 310 | println("got raw") 311 | let size = get_instruction_list_size(raw_instructions) 312 | println(size) 313 | 314 | get_instructions_helper(raw_instructions, target, [], size, 0) 315 | } 316 | 317 | 318 | 319 | -------------------------------------------------------------------------------- /resources/lldb_wrapper.bg: -------------------------------------------------------------------------------- 1 | namespace lldb_wrapper 2 | 3 | import "beagle.builtin" as builtin 4 | import "lib_lldb" as lldb 5 | import "slow_json_parser" as json 6 | import "beagle.ffi" as ffi 7 | import "persistent_map" as map 8 | 9 | // TODO: Thist code fails in lib_lldb with an update binding 10 | // unsure of the cause right now 11 | 12 | struct State { 13 | process 14 | target 15 | function_names 16 | address_to_name 17 | called_functions 18 | } 19 | 20 | let state = atom(State { 21 | process: 0, 22 | target: 0, 23 | function_names: [], 24 | address_to_name: map/map() 25 | called_functions: map/map() 26 | }) 27 | 28 | fn loop_state() { 29 | let current_state = deref(state) 30 | let process = current_state.process 31 | let target = current_state.target 32 | 33 | let process_state = lldb/get_process_state(process) 34 | if process_state != lldb/process_stopped { 35 | println("Waiting for process to stop") 36 | sleep(3000) 37 | if process_state != lldb/process_exited { 38 | loop_state() 39 | } 40 | println("Exited") 41 | } else { 42 | 43 | let thread = lldb/get_thread_by_index(process, 2) 44 | let frame = lldb/get_selected_frame(thread) 45 | let pc = lldb/get_pc(frame) 46 | let function_name = lldb/get_function_name(frame) 47 | 48 | if function_name == "debugger_info" { 49 | let frame = lldb/get_selected_frame(thread) 50 | let pc = lldb/get_pc_address(frame) 51 | let x1 = lldb/get_register_value(lldb/find_register(frame, "x0")) 52 | let x2 = lldb/get_register_value(lldb/find_register(frame, "x1")) 53 | let data = lldb/get_memory(process, x1, x2) 54 | let data = json/parse(data) 55 | if data["kind"] == "user_function" { 56 | let function_data = data["data"]["UserFunction"] 57 | let function_name = function_data["name"] 58 | let function_address = function_data["pointer"]; 59 | swap!(state, fn(s) { 60 | State { 61 | process: s.process, 62 | target: s.target, 63 | function_names: push(s.function_names, function_name) 64 | address_to_name: assoc(s.address_to_name, function_address, function_name) 65 | called_functions: s.called_functions 66 | } 67 | }) 68 | if function_name == "beagle.core/swap!" { 69 | lldb/create_breakpoint_by_address(target, function_address) 70 | } 71 | } 72 | } else { 73 | let thread = lldb/get_thread_by_index(process, 1) 74 | let stop_reason = lldb/get_stop_reason(thread) 75 | if stop_reason != 0 { 76 | let frame = lldb/get_selected_frame(thread) 77 | let pc = lldb/get_pc(frame) 78 | let result = get(deref(state).address_to_name, pc) 79 | if result != null { 80 | swap!(state, fn(s) { 81 | let called_count = get(s.called_functions, result) 82 | let total = if called_count == null { 1 } else { called_count + 1 } 83 | State { 84 | process: s.process, 85 | target: s.target, 86 | function_names: s.function_names 87 | address_to_name: s.address_to_name 88 | called_functions: assoc(s.called_functions, result, total) 89 | } 90 | }) 91 | } 92 | } 93 | } 94 | 95 | 96 | 97 | lldb/continue(process) 98 | if process_state != lldb/process_exited { 99 | loop_state() 100 | } 101 | } 102 | } 103 | 104 | let sdl = ffi/load_library("/opt/homebrew/lib/libSDL2-2.0.0.dylib") 105 | let sdl_init = ffi/get_function(sdl, "SDL_Init", [ffi/Type.U32], ffi/Type.I32) 106 | let sdl_create_window = ffi/get_function( 107 | sdl, 108 | "SDL_CreateWindow", 109 | [ffi/Type.String, ffi/Type.I32, ffi/Type.I32, ffi/Type.I32, ffi/Type.I32, ffi/Type.U32], 110 | ffi/Type.Pointer 111 | ) 112 | 113 | let sdl_destroy_window = ffi/get_function( 114 | sdl, 115 | "SDL_DestroyWindow", 116 | [ffi/Type.Pointer], 117 | ffi/Type.Void 118 | ) 119 | 120 | let sdl_create_renderer = ffi/get_function( 121 | sdl, 122 | "SDL_CreateRenderer", 123 | [ffi/Type.Pointer, ffi/Type.I32, ffi/Type.U32], 124 | ffi/Type.Pointer 125 | ) 126 | let sdl_set_render_draw_color = ffi/get_function( 127 | sdl, 128 | "SDL_SetRenderDrawColor", 129 | [ffi/Type.Pointer, ffi/Type.U8, ffi/Type.U8, ffi/Type.U8, ffi/Type.U8], 130 | ffi/Type.I32 131 | ) 132 | let sdl_render_clear = ffi/get_function(sdl, "SDL_RenderClear", [ffi/Type.Pointer], ffi/Type.I32) 133 | let sdl_render_present = ffi/get_function(sdl, "SDL_RenderPresent", [ffi/Type.Pointer], ffi/Type.Void) 134 | let sdl_poll_event = ffi/get_function(sdl, "SDL_PollEvent", [ffi/Type.Pointer], ffi/Type.I32) 135 | let sdl_delay = ffi/get_function(sdl, "SDL_Delay", [ffi/Type.U32], ffi/Type.Void) 136 | let sdl_render_fill_rect = ffi/get_function( 137 | sdl, 138 | "SDL_RenderFillRect", 139 | [ffi/Type.Pointer, ffi/Type.Pointer], 140 | ffi/Type.I32 141 | ) 142 | 143 | let sdl_get_window_size = ffi/get_function( 144 | sdl, 145 | "SDL_GetWindowSize", 146 | [ffi/Type.Pointer, ffi/Type.Pointer, ffi/Type.Pointer], 147 | ffi/Type.Void 148 | ) 149 | 150 | let sdl_ttf = ffi/load_library("/opt/homebrew/lib/libSDL2_ttf-2.0.0.dylib") 151 | let ttf_init = ffi/get_function(sdl_ttf, "TTF_Init", [], ffi/Type.I32) 152 | let ttf_open_font = ffi/get_function( 153 | sdl_ttf, 154 | "TTF_OpenFont", 155 | [ffi/Type.String, ffi/Type.I32], 156 | ffi/Type.Pointer 157 | ) 158 | let ttf_render_text_blended = ffi/get_function( 159 | sdl_ttf, 160 | "TTF_RenderText_Blended", 161 | [ffi/Type.Pointer, ffi/Type.String, ffi/Type.Structure { types: [ffi/Type.U8, ffi/Type.U8, ffi/Type.U8, ffi/Type.U8 ]}], 162 | ffi/Type.Pointer 163 | ) 164 | let ttf_close_font = ffi/get_function( 165 | sdl_ttf, 166 | "TTF_Quit", 167 | [], 168 | ffi/Type.Void 169 | ) 170 | 171 | // Additional SDL functions for handling textures 172 | let sdl_create_texture_from_surface = ffi/get_function( 173 | sdl, 174 | "SDL_CreateTextureFromSurface", 175 | [ffi/Type.Pointer, ffi/Type.Pointer], 176 | ffi/Type.Pointer 177 | ) 178 | let sdl_free_surface = ffi/get_function( 179 | sdl, 180 | "SDL_FreeSurface", 181 | [ffi/Type.Pointer], 182 | ffi/Type.Void 183 | ) 184 | let sdl_render_copy = ffi/get_function( 185 | sdl, 186 | "SDL_RenderCopy", 187 | [ffi/Type.Pointer, ffi/Type.Pointer, ffi/Type.Pointer, ffi/Type.Pointer], 188 | ffi/Type.I32 189 | ) 190 | let sdl_destroy_texture = ffi/get_function( 191 | sdl, 192 | "SDL_DestroyTexture", 193 | [ffi/Type.Pointer], 194 | ffi/Type.Void 195 | ) 196 | 197 | // Get the texture dimensions (not surface!) 198 | let sdl_query_texture = ffi/get_function( 199 | sdl, "SDL_QueryTexture", 200 | [ffi/Type.Pointer, ffi/Type.Pointer, ffi/Type.Pointer, ffi/Type.Pointer, ffi/Type.Pointer], 201 | ffi/Type.I32 202 | ) 203 | 204 | let sdl_get_error = ffi/get_function( 205 | sdl, "SDL_GetError", 206 | [], ffi/Type.String 207 | ) 208 | 209 | fn start_debugger_thread() { 210 | lldb/initialize() 211 | let debugger = lldb/create_debugger() 212 | lldb/set_async(debugger, false) 213 | 214 | let target = lldb/create_target(debugger, "/Users/jimmyhmiller/Documents/Code/beagle/target/debug/main") 215 | 216 | let functions = lldb/find_functions(target, "debugger_info", 2) 217 | let size = lldb/get_symbol_list_size(functions) 218 | 219 | let first_function = lldb/get_context_at_index(functions, 0) 220 | 221 | let breakpoint = lldb/create_breakpoint(target, "debugger_info", "main") 222 | lldb/set_breakpoint_enabled(breakpoint, true) 223 | let result = lldb/enable_all_breakpoints(target) 224 | 225 | 226 | let launch_info = lldb/create_launch_info(0) 227 | lldb/add_args(launch_info, ["/Users/jimmyhmiller/Documents/Code/beagle/resources/ffi_test.bg"]) 228 | 229 | let error = lldb/create_error() 230 | let process = lldb/launch_target(target, launch_info, error) 231 | 232 | let process_state = lldb/get_process_state(process) 233 | swap!(state, fn(s) { 234 | State { 235 | process: process, 236 | target: target, 237 | function_names: s.function_names 238 | address_to_name: s.address_to_name 239 | called_functions: s.called_functions 240 | } 241 | }) 242 | thread(loop_state) 243 | } 244 | 245 | 246 | fn for_each(vector, f) { 247 | let length = length(vector) 248 | for_each_helper(vector, f, 0, length) 249 | } 250 | 251 | fn for_each_helper(vector, f, i, length) { 252 | if i < length { 253 | f(get(vector, i), i) 254 | for_each_helper(vector, f, i + 1, length) 255 | } 256 | } 257 | 258 | fn gui_loop(window, buffer, renderer, font) { 259 | sdl_poll_event(buffer) 260 | let status = ffi/get_u32(buffer, 0) 261 | let width_ptr = ffi/allocate(4) 262 | let height_ptr = ffi/allocate(4) 263 | sdl_get_window_size(window, width_ptr, height_ptr) 264 | 265 | let new_width = ffi/get_i32(width_ptr, 0) 266 | let new_height = ffi/get_i32(height_ptr, 0) 267 | 268 | sdl_set_render_draw_color(renderer, 0, 0, 0, 255) 269 | sdl_render_clear(renderer) 270 | 271 | // Create a white color for the text 272 | let white = ffi/allocate(4) 273 | ffi/set_u8(white, 0, 255) // r 274 | ffi/set_u8(white, 1, 255) // g 275 | ffi/set_u8(white, 2, 255) // b 276 | ffi/set_u8(white, 3, 0) // a 277 | 278 | let state = deref(state) 279 | let function_calls = state.called_functions 280 | for_each(keys(function_calls), fn (name, i) { 281 | let surface_message = ttf_render_text_blended(font, name ++ ": " ++ to_string(get(function_calls, name)), white) 282 | let message = sdl_create_texture_from_surface(renderer, surface_message) 283 | let text_width_ptr = ffi/allocate(4) 284 | let text_height_ptr = ffi/allocate(4) 285 | let result = sdl_query_texture(message, 0, 0, text_width_ptr, text_height_ptr) 286 | 287 | let text_width = ffi/get_i32(text_width_ptr, 0) 288 | let text_height = ffi/get_i32(text_height_ptr, 0) 289 | let message_rect = ffi/allocate(16) 290 | 291 | ffi/set_i32(message_rect, 0, 20) // x position 292 | ffi/set_i32(message_rect, 4, (text_height + 10) * i) // y position 293 | ffi/set_i32(message_rect, 8, text_width) // Correct width 294 | ffi/set_i32(message_rect, 12, text_height) // Correct height 295 | 296 | sdl_render_copy(renderer, message, 0, message_rect) 297 | sdl_free_surface(surface_message) 298 | sdl_destroy_texture(message) 299 | ffi/deallocate(message_rect) 300 | ffi/deallocate(text_width_ptr) 301 | ffi/deallocate(text_height_ptr) 302 | }) 303 | 304 | 305 | 306 | sdl_render_present(renderer) 307 | 308 | // Clean up 309 | 310 | ffi/deallocate(white) 311 | 312 | ffi/deallocate(width_ptr) 313 | ffi/deallocate(height_ptr) 314 | 315 | sdl_delay(16) 316 | if status != 256 { 317 | gui_loop(window, buffer, renderer, font) 318 | } else { 319 | sdl_destroy_window(window) 320 | } 321 | } 322 | 323 | fn main() { 324 | start_debugger_thread() 325 | sdl_init(32) 326 | ttf_init() 327 | 328 | // Load a font - using a common system font on macOS 329 | let font = ttf_open_font("/Users/jimmyhmiller/Library/Fonts/UbuntuMono-Regular.ttf", 64) 330 | 331 | let window = sdl_create_window("SDL2 Window", 100, 100, 640, 480, 8228) 332 | let renderer = sdl_create_renderer(window, -1, 2) 333 | let buffer = ffi/allocate(56) 334 | 335 | gui_loop(window, buffer, renderer, font) 336 | } -------------------------------------------------------------------------------- /resources/many_args_test.bg: -------------------------------------------------------------------------------- 1 | namespace many_args_test 2 | 3 | import "beagle.__internal_test__" as test 4 | 5 | fn f(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) { 6 | a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8 + a9 + a10 7 | } 8 | 9 | fn many_args_but_last_is_heap_object(a1, a2, a3, a4, a5, a6, a7, a8, a9) { 10 | a9 11 | } 12 | 13 | struct Thing { 14 | x 15 | y 16 | } 17 | 18 | 19 | fn main() { 20 | println(test/many_args(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)) 21 | println(f(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)) 22 | println(many_args_but_last_is_heap_object(1, 2, 3, 4, 5, 6, 7, 8, Thing { x: 9, y: 10 })) 23 | "done" 24 | } 25 | 26 | // Expect 27 | // 66 28 | // 55 29 | // Thing { x: 9, y: 10 } 30 | // done -------------------------------------------------------------------------------- /resources/map_benchmark.bg: -------------------------------------------------------------------------------- 1 | namespace map_benchmark 2 | 3 | import "persistent_map" as pm 4 | import "beagle.builtin" as builtin 5 | import "beagle.primitive" as primitive 6 | 7 | fn insert_many(m, i, limit) { 8 | if i == limit { 9 | m 10 | } else { 11 | insert_many(pm/assoc(m, i, i), i + 1, limit) 12 | } 13 | } 14 | 15 | fn do_gets(m, i, limit) { 16 | if i == limit { 17 | 0 18 | } else { 19 | let result = pm/get(m, i) 20 | do_gets(m, i + 1, limit) 21 | } 22 | } 23 | 24 | fn main() { 25 | let size = 100000 26 | let empty_map = pm/map() 27 | let big_map = insert_many(empty_map, 0, size) 28 | do_gets(big_map, 0, size) 29 | "done" 30 | } -------------------------------------------------------------------------------- /resources/math_test.bg: -------------------------------------------------------------------------------- 1 | namespace math_test 2 | 3 | // no-std 4 | 5 | fn main() { 6 | _println(5 + 3); 7 | _println(5 - 3); 8 | _println(5 * 3); 9 | _println(5 / 3); 10 | _println(5 + 3 * 2); 11 | _println((5 + 3) * 2); 12 | _println(5 + 3 / 2); 13 | _println((5 + 3) / 2); 14 | _println(5 * 3 + 2); 15 | _println(5 * (3 + 2)); 16 | _println(5 * 3 / 2); 17 | _println(5 * (3 / 2)); 18 | _println(5 / 3 * 2); 19 | _println(5 / (3 * 2)); 20 | _println(5 / 3 / 2); 21 | _println(5 / (3 / 2)); 22 | "done" 23 | } 24 | 25 | // Expect 26 | // 8 27 | // 2 28 | // 15 29 | // 1 30 | // 11 31 | // 16 32 | // 6 33 | // 4 34 | // 17 35 | // 25 36 | // 7 37 | // 5 38 | // 2 39 | // 0 40 | // 0 41 | // 5 42 | // done -------------------------------------------------------------------------------- /resources/mut_var.bg: -------------------------------------------------------------------------------- 1 | namespace mut_var 2 | 3 | 4 | fn main() { 5 | let mut x = 0 6 | let mut y = 0 7 | let mut z = 0 8 | 9 | x = 1 10 | y = 2 11 | z = 3 12 | 13 | let mut counter = 0 14 | let captured = fn() { 15 | println("captured") 16 | println(counter) 17 | counter = counter + 1 18 | } 19 | 20 | println(x) 21 | println(y) 22 | println(z) 23 | 24 | println("counter") 25 | println(counter) 26 | counter = counter + 1 27 | captured(); 28 | println(counter) 29 | "done" 30 | } 31 | 32 | // Expect 33 | // 1 34 | // 2 35 | // 3 36 | // counter 37 | // 0 38 | // captured 39 | // 1 40 | // 2 41 | // done -------------------------------------------------------------------------------- /resources/namespace_gc.bg: -------------------------------------------------------------------------------- 1 | namespace test.gc.in.namespaces 2 | 3 | import "raw_mutable_array" as array 4 | 5 | struct HeapThing { 6 | x 7 | y 8 | z 9 | } 10 | 11 | struct PersistentVector { 12 | count 13 | shift 14 | root 15 | tail 16 | } 17 | 18 | let EMPTY = PersistentVector { 19 | count: 0 20 | shift: 5 21 | root: array/new_array(0) 22 | tail: array/new_array(0) 23 | } 24 | 25 | 26 | let global_thing = HeapThing { x: 1, y: 2, z: 3 } 27 | let global_thing2 = HeapThing { x: global_thing, y: global_thing, z: global_thing } 28 | 29 | fn main() { 30 | println(EMPTY) 31 | gc() 32 | gc() 33 | let local_thing = HeapThing { x: 4, y: 5, z: 6 } 34 | gc() 35 | println(global_thing) 36 | println(global_thing2) 37 | println(EMPTY) 38 | "done" 39 | } 40 | 41 | // Expe0ct 42 | // PersistentVector { count: 0, shift: 5, root: [ ], tail: [ ] } 43 | // HeapThing { x: 1, y: 2, z: 3 } 44 | // HeapThing { x: HeapThing { x: 1, y: 2, z: 3 }, y: HeapThing { x: 1, y: 2, z: 3 }, z: HeapThing { x: 1, y: 2, z: 3 } } 45 | // PersistentVector { count: 0, shift: 5, root: [ ], tail: [ ] } 46 | // done -------------------------------------------------------------------------------- /resources/namespace_resolution_test.bg: -------------------------------------------------------------------------------- 1 | namespace namespace_resolution_test 2 | 3 | import "top_level" as top_level 4 | import "enum_test" as enum_test 5 | 6 | fn main() { 7 | println(top_level/x) 8 | println(enum_test/Action.run { speed: 2 }) 9 | } -------------------------------------------------------------------------------- /resources/persistent_map.bg: -------------------------------------------------------------------------------- 1 | namespace persistent_map 2 | import "beagle.builtin" as builtin 3 | import "beagle.primitive" as primitive 4 | import "raw_mutable_array" as array 5 | import "persistent_vector" as vec 6 | 7 | 8 | // TODO: ChatGPT wrote this 9 | // It's bad 10 | // Going to rewrite 11 | 12 | struct PersistentMap { 13 | count 14 | root 15 | } 16 | 17 | struct PairNode { 18 | key 19 | val 20 | } 21 | 22 | struct BitmapIndexedNode { 23 | bit_map 24 | children 25 | } 26 | 27 | struct CollisionNode { 28 | hash 29 | c 30 | kv_arr 31 | } 32 | 33 | let empty = PersistentMap { 34 | count: 0 35 | root: null 36 | } 37 | 38 | fn map() { 39 | empty 40 | } 41 | 42 | fn count(pm) { 43 | pm.count 44 | } 45 | 46 | fn mask(h, s) { 47 | (h >>> s) & 31 48 | } 49 | 50 | fn bitpos(x) { 51 | 1 << x 52 | } 53 | 54 | fn is_bitmap_indexed_node(n) { 55 | instance_of(n, BitmapIndexedNode) 56 | } 57 | 58 | fn is_collision_node(n) { 59 | instance_of(n, CollisionNode) 60 | } 61 | 62 | fn is_pair_node(n) { 63 | instance_of(n, PairNode) 64 | } 65 | 66 | fn new_pair_node(k, v) { 67 | PairNode { 68 | key: k 69 | val: v 70 | } 71 | } 72 | 73 | fn new_bitmap_indexed_node(bm, child_arr) { 74 | BitmapIndexedNode { 75 | bit_map: bm 76 | children: child_arr 77 | } 78 | } 79 | 80 | fn new_collision_node(h, c, kv_arr) { 81 | CollisionNode { 82 | hash: h 83 | c: c 84 | kv_arr: kv_arr 85 | } 86 | } 87 | 88 | fn collision_count(n) { 89 | n.c 90 | } 91 | 92 | fn collision_hash(n) { 93 | n.hash 94 | } 95 | 96 | fn node_bitmap(n) { 97 | n.bit_map 98 | } 99 | 100 | fn node_children_array(n) { 101 | n.children 102 | } 103 | 104 | fn collision_key(n, i) { 105 | array/read_field(n.kv_arr, 2 * i) 106 | } 107 | 108 | fn collision_val(n, i) { 109 | array/read_field(n.kv_arr, 2 * i + 1) 110 | } 111 | 112 | fn index_from_bitmap(bm, bit) { 113 | builtin/pop_count(bm & (bit - 1)) 114 | } 115 | 116 | fn make_kv_array(k, v) { 117 | let arr = array/new_array(2) 118 | array/write_field(arr, 0, k) 119 | array/write_field(arr, 1, v) 120 | arr 121 | } 122 | 123 | // main entry 124 | fn assoc(pm, k, v) { 125 | let h = builtin/hash(k) 126 | if pm.root == null { 127 | let new_root = new_collision_node(h, 1, make_kv_array(k, v)) 128 | PersistentMap { 129 | count: 1 130 | root: new_root 131 | } 132 | } else { 133 | let added_leaf_box = array/new_array(1) 134 | array/write_field(added_leaf_box, 0, 0) 135 | let new_root = assoc_node(pm.root, 0, h, k, v, added_leaf_box) 136 | let old_val = array/read_field(added_leaf_box, 0) 137 | PersistentMap { 138 | count: pm.count + (if old_val == 1 { 1 } else { 0 }) 139 | root: new_root 140 | } 141 | } 142 | } 143 | 144 | fn assoc_node(n, shift, h, k, v, added_leaf_box) { 145 | if n == null { 146 | // brand-new node 147 | assoc_bitmap_node(null, shift, h, k, v, added_leaf_box) 148 | } else if is_bitmap_indexed_node(n) { 149 | assoc_bitmap_node(n, shift, h, k, v, added_leaf_box) 150 | } else if is_collision_node(n) { 151 | assoc_collision_node(n, shift, h, k, v, added_leaf_box) 152 | } else if is_pair_node(n) { 153 | assoc_key_val_node(n, shift, h, k, v, added_leaf_box) 154 | } else { 155 | println(n) 156 | builtin/throw_error() 157 | } 158 | } 159 | 160 | fn assoc_bitmap_node(n, shift, h, k, v, added_leaf_box) { 161 | if n == null { 162 | // brand-new single-child node 163 | let bit = bitpos(mask(h, shift)) 164 | let arr = array/new_array(1) 165 | array/write_field(arr, 0, new_pair_node(k, v)) 166 | array/write_field(added_leaf_box, 0, 1) // added_leaf_box is not an array 167 | new_bitmap_indexed_node(bit, arr) 168 | } else if is_bitmap_indexed_node(n) == false { 169 | // If n is not actually a BitmapIndexedNode, handle it with assoc_node 170 | assoc_node(n, shift, h, k, v, added_leaf_box) 171 | } else { 172 | // normal path for BitmapIndexedNode 173 | let bm = node_bitmap(n) 174 | let bit = bitpos(mask(h, shift)) 175 | let idx = index_from_bitmap(bm, bit) 176 | let ex = (bm & bit) != 0 177 | let child_arr = node_children_array(n) 178 | let sub_n = if ex { 179 | array/read_field(child_arr, idx) 180 | } else { 181 | null 182 | } 183 | let new_sub = assoc_node(sub_n, shift + 5, h, k, v, added_leaf_box) 184 | if ex { 185 | // replace existing child 186 | let new_child_arr = array/copy_array(child_arr) 187 | array/write_field(new_child_arr, idx, new_sub) 188 | new_bitmap_indexed_node(bm, new_child_arr) 189 | } else { 190 | // add a new child 191 | let new_len = array/count(child_arr) + 1 192 | let inserted = array/new_array(new_len) 193 | insert_child(child_arr, inserted, new_sub, idx) 194 | let new_bm = bm | bit 195 | new_bitmap_indexed_node(new_bm, inserted) 196 | } 197 | } 198 | } 199 | 200 | // Insert a single new_node into an array of children at position idx 201 | fn insert_child(old_arr, new_arr, new_node, idx) { 202 | let l_old = array/count(old_arr) 203 | copy_prefix(old_arr, new_arr, 0, idx) 204 | array/write_field(new_arr, idx, new_node) 205 | copy_suffix(old_arr, new_arr, idx, l_old) 206 | } 207 | 208 | fn copy_prefix(old_arr, new_arr, i, limit) { 209 | if i == limit { 210 | new_arr 211 | } else { 212 | array/write_field(new_arr, i, array/read_field(old_arr, i)) 213 | copy_prefix(old_arr, new_arr, i + 1, limit) 214 | } 215 | } 216 | 217 | fn copy_suffix(old_arr, new_arr, start, l_old) { 218 | let i = start 219 | copy_suffix_loop(old_arr, new_arr, i, l_old) 220 | } 221 | 222 | fn copy_suffix_loop(old_arr, new_arr, i, l_old) { 223 | if i == l_old { 224 | new_arr 225 | } else { 226 | array/write_field(new_arr, i + 1, array/read_field(old_arr, i)) 227 | copy_suffix_loop(old_arr, new_arr, i + 1, l_old) 228 | } 229 | } 230 | 231 | // Handling a single PairNode 232 | fn assoc_key_val_node(pair, shift, h, k, v, added_leaf_box) { 233 | let ek = pair.key 234 | let ev = pair.val 235 | let eh = builtin/hash(ek) 236 | if ek == k { 237 | // Overwrite existing 238 | new_pair_node(ek, v) 239 | } else { 240 | if eh == h { 241 | // Same hash -> turn into a CollisionNode 242 | let new_coll = new_collision_node(h, 2, make_kv_array(ek, ev)) 243 | array/write_field(new_coll.kv_arr, 2, k) 244 | array/write_field(new_coll.kv_arr, 3, v) 245 | array/write_field(added_leaf_box, 0, 1) 246 | new_coll 247 | } else { 248 | // Different hash -> create minimal bitmap node for the two distinct keys 249 | let new_n = assoc_bitmap_node(null, shift, eh, ek, ev, added_leaf_box) 250 | array/write_field(added_leaf_box, 0, 0) 251 | assoc_bitmap_node(new_n, shift, h, k, v, added_leaf_box) 252 | } 253 | } 254 | } 255 | 256 | // Handling a CollisionNode 257 | fn assoc_collision_node(n, shift, h, k, v, added_leaf_box) { 258 | // If n turned out not to be a CollisionNode for some reason, delegate 259 | if is_collision_node(n) == false { 260 | assoc_bitmap_node(n, shift, h, k, v, added_leaf_box) 261 | } else { 262 | let nh = collision_hash(n) 263 | if nh == h { 264 | let ccount = collision_count(n) 265 | let idx = find_collision_key_index(n, ccount, k) 266 | if idx >= 0 { 267 | // Overwrite existing 268 | let new_coll = new_collision_node(nh, ccount, array/copy_array(n.kv_arr)) 269 | array/write_field(new_coll.kv_arr, 2 * idx + 1, v) 270 | new_coll 271 | } else { 272 | // Grow collision array 273 | let new_count = ccount + 1 274 | let old_kv = n.kv_arr 275 | let old_len = array/count(old_kv) 276 | let new_size = old_len + 2 277 | let new_kv = array/new_array(new_size) 278 | copy_collision_kv(old_kv, new_kv, 0, old_len) 279 | array/write_field(new_kv, 2 * ccount, k) 280 | array/write_field(new_kv, 2 * ccount + 1, v) 281 | let new_coll = new_collision_node(nh, new_count, new_kv) 282 | array/write_field(added_leaf_box, 0, 1) 283 | new_coll 284 | } 285 | } else { 286 | // Convert collision to a bitmap node if hash differs 287 | convert_collision_to_bitmap(n, shift, h, k, v, added_leaf_box) 288 | } 289 | } 290 | } 291 | 292 | fn find_collision_key_index(n, ccount, k) { 293 | find_collision_key_index_loop(n, k, 0, ccount) 294 | } 295 | 296 | fn find_collision_key_index_loop(n, k, i, ccount) { 297 | if i == ccount { 298 | -1 299 | } else { 300 | let kk = collision_key(n, i) 301 | if kk == k { 302 | i 303 | } else { 304 | find_collision_key_index_loop(n, k, i + 1, ccount) 305 | } 306 | } 307 | } 308 | 309 | fn copy_collision_kv(old_kv, new_kv, i, limit) { 310 | if i == limit { 311 | new_kv 312 | } else { 313 | array/write_field(new_kv, i, array/read_field(old_kv, i)) 314 | copy_collision_kv(old_kv, new_kv, i + 1, limit) 315 | } 316 | } 317 | 318 | // Convert collisions to a bitmap node when a new hash doesn't match 319 | fn convert_collision_to_bitmap(n, shift, h, k, v, added_leaf_box) { 320 | let ccount = collision_count(n) 321 | if ccount == 1 { 322 | // easy case: single pair -> create 2 children in a bitmap 323 | let k0 = collision_key(n, 0) 324 | let v0 = collision_val(n, 0) 325 | let h0 = collision_hash(n) 326 | let new_n = assoc_bitmap_node(null, shift, h0, k0, v0, added_leaf_box) 327 | array/write_field(added_leaf_box, 0, 0) 328 | assoc_bitmap_node(new_n, shift, h, k, v, added_leaf_box) 329 | } else { 330 | // build a small bitmap node with up to 2 children 331 | let node_h = collision_hash(n) 332 | let sub_index1 = mask(node_h, shift) 333 | let sub_index2 = mask(h, shift) 334 | let bit1 = bitpos(sub_index1) 335 | let bit2 = bitpos(sub_index2) 336 | let combo = bit1 | bit2 337 | let child_size = if bit1 == bit2 { 1 } else { 2 } 338 | let child_arr = array/new_array(child_size) 339 | 340 | if sub_index1 == sub_index2 { 341 | // same bit -> keep collisions together 342 | let new_coll = new_collision_node(node_h, collision_count(n), extract_pairs(n)) 343 | array/write_field(box_, 0, 1) 344 | let updated = assoc_collision_node(new_coll, shift + 5, builtin/hash(k), k, v, box_) 345 | array/write_field(child_arr, 0, updated) 346 | child_arr 347 | } else { 348 | // distinct bits -> 2 children 349 | if s1 < s2 { 350 | array/write_field(child_arr, 0, n) 351 | let nc = new_collision_node(builtin/hash(k), 1, make_kv_array(k, v)) 352 | array/write_field(child_arr, 1, nc) 353 | array/write_field(box_, 0, 0) 354 | child_arr 355 | } else { 356 | let nc = new_collision_node(builtin/hash(k), 1, make_kv_array(k, v)) 357 | array/write_field(child_arr, 0, nc) 358 | array/write_field(child_arr, 1, n) 359 | array/write_field(box_, 0, 0) 360 | child_arr 361 | } 362 | } 363 | new_bitmap_indexed_node(combo, child_arr) 364 | } 365 | } 366 | 367 | 368 | fn extract_pairs(n) { 369 | let ccount = collision_count(n) 370 | let pc = 2 * ccount 371 | let out = array/new_array(pc) 372 | copy_collision_array_no_hdr(n, out, ccount, 0) 373 | } 374 | 375 | fn copy_collision_array_no_hdr(n, out, ccount, i) { 376 | if i == ccount { 377 | out 378 | } else { 379 | array/write_field(out, 2 * i, collision_key(n, i)) 380 | array/write_field(out, 2 * i + 1, collision_val(n, i)) 381 | copy_collision_array_no_hdr(n, out, ccount, i + 1) 382 | } 383 | } 384 | 385 | // lookup 386 | fn _get(pm, k) { 387 | if pm.root == null { 388 | null 389 | } else { 390 | let h = builtin/hash(k) 391 | find_node(pm.root, k, h, 0) 392 | } 393 | } 394 | 395 | extend PersistentMap with Indexed { 396 | fn get(map, index) { 397 | _get(map, index) 398 | } 399 | } 400 | 401 | 402 | fn find_node(n, k, h, shift) { 403 | if n == null { 404 | null 405 | } else if is_bitmap_indexed_node(n) { 406 | find_bitmap_node(n, k, h, shift) 407 | } else if is_collision_node(n) { 408 | find_collision_node(n, k, h) 409 | } else if is_pair_node(n) { 410 | let ck = n.key 411 | let cv = n.val 412 | if k == ck { 413 | cv 414 | } else { 415 | null 416 | } 417 | } else { 418 | // fallback 419 | null 420 | } 421 | } 422 | 423 | fn find_bitmap_node(n, k, h, shift) { 424 | let bm = node_bitmap(n) 425 | let bit = bitpos(mask(h, shift)) 426 | if (bm & bit) == 0 { 427 | null 428 | } else { 429 | let idx = index_from_bitmap(bm, bit) 430 | let child = array/read_field(n.children, idx) 431 | find_node(child, k, h, shift + 5) 432 | } 433 | } 434 | 435 | fn find_collision_node(n, k, h) { 436 | let nh = collision_hash(n) 437 | if nh == h { 438 | let ccount = collision_count(n) 439 | lookup_collision(n, k, 0, ccount) 440 | } else { 441 | null 442 | } 443 | } 444 | 445 | fn lookup_collision(n, k, i, ccount) { 446 | if i == ccount { 447 | null 448 | } else { 449 | let ck = collision_key(n, i) 450 | let cv = collision_val(n, i) 451 | if ck == k { 452 | cv 453 | } else { 454 | lookup_collision(n, k, i + 1, ccount) 455 | } 456 | } 457 | } 458 | 459 | fn insert_many(pm, i, max) { 460 | if i == max { 461 | pm 462 | } else { 463 | let pm2 = assoc(pm, i, i) 464 | insert_many(pm2, i + 1, max) 465 | } 466 | } 467 | 468 | fn _keys(pm) { 469 | // TODO: Make this a vec not an array 470 | let keys_array = array/new_array(pm.count) 471 | if pm.root != null { 472 | collect_keys(pm.root, keys_array, 0) 473 | } 474 | vec/from_array(keys_array) 475 | } 476 | 477 | fn collect_keys(n, keys_array, idx) { 478 | if is_bitmap_indexed_node(n) { 479 | let children = node_children_array(n) 480 | let len = array/count(children) 481 | collect_keys_bitmap(children, keys_array, idx, len, 0) 482 | } else if is_collision_node(n) { 483 | let ccount = collision_count(n) 484 | collect_keys_collision(n, keys_array, idx, ccount) 485 | } else if is_pair_node(n) { 486 | array/write_field(keys_array, idx, n.key) 487 | idx + 1 488 | } else { 489 | idx 490 | } 491 | } 492 | 493 | fn collect_keys_bitmap(children, keys_array, idx, len, i) { 494 | if i == len { 495 | idx 496 | } else { 497 | let child = array/read_field(children, i) 498 | let new_idx = collect_keys(child, keys_array, idx) 499 | collect_keys_bitmap(children, keys_array, new_idx, len, i + 1) 500 | } 501 | } 502 | 503 | fn collect_keys_collision(n, keys_array, idx, ccount) { 504 | if ccount == 0 { 505 | idx 506 | } else { 507 | let key = collision_key(n, 0) 508 | array/write_field(keys_array, idx, key) 509 | collect_keys_collision(n, keys_array, idx + 1, ccount - 1) 510 | } 511 | } 512 | 513 | extend PersistentMap with Keys { 514 | fn keys(map) { 515 | _keys(map) 516 | } 517 | } 518 | 519 | 520 | extend PersistentMap with Format { 521 | fn format(map, depth) { 522 | if map.count == 0 { 523 | "{}" 524 | } else { 525 | let keys_vec = keys(map) 526 | format_entries(map, keys_vec, 0, "{") 527 | } 528 | } 529 | } 530 | 531 | extend PersistentMap with Associable { 532 | fn assoc(map, key, value) { 533 | assoc(map, key, value) 534 | } 535 | } 536 | 537 | fn format_entries(map, keys_vec, idx, acc) { 538 | if idx >= count(keys_vec) { 539 | acc ++ "}" 540 | } else { 541 | let key = get(keys_vec, idx) 542 | let val = get(map, key) 543 | 544 | let key_str = format(key, 1) 545 | let val_str = format(val, 1) 546 | 547 | let entry = key_str ++ ": " ++ val_str 548 | 549 | // Add comma if not the last entry 550 | let new_acc = if idx == 0 { 551 | acc ++ entry 552 | } else { 553 | acc ++ ", " ++ entry 554 | } 555 | 556 | format_entries(map, keys_vec, idx + 1, new_acc) 557 | } 558 | } 559 | 560 | fn main() { 561 | 562 | let pm = map() 563 | println(pm) 564 | 565 | let pm2 = assoc(pm, "foo", 1) 566 | println(get(pm2, "foo")) 567 | println(count(pm2)) 568 | 569 | let pm3 = assoc(pm2, "bar", 100) 570 | println(get(pm3, "foo")) 571 | println(get(pm3, "bar")) 572 | println(count(pm3)) 573 | 574 | let pm4 = assoc(pm3, "bar", 101) 575 | println(get(pm4, "bar")) 576 | println(count(pm4)) 577 | 578 | let pm5 = assoc(pm4, "baz", 999) 579 | let pm6 = assoc(pm5, "qux", 777) 580 | println(get(pm6, "baz")) 581 | println(get(pm6, "qux")) 582 | println(count(pm6)) 583 | 584 | let val_missing = get(pm6, "no-such-key") 585 | println(val_missing) 586 | 587 | let times = 10 588 | let pm7 = insert_many(pm6, 0, times) 589 | println(count(pm7)) 590 | 591 | println(get(pm7, 0)) 592 | println(get(pm7, 9)) 593 | println(get(pm7, 999)) 594 | let keys_vec = keys(pm6) 595 | println(keys_vec) // Print all keys 596 | println(format(pm6)) // Print all entries 597 | "done" 598 | } 599 | 600 | 601 | 602 | // Expect 603 | // {} 604 | // 1 605 | // 1 606 | // 1 607 | // 100 608 | // 2 609 | // 101 610 | // 2 611 | // 999 612 | // 777 613 | // 4 614 | // null 615 | // 14 616 | // 0 617 | // 9 618 | // null 619 | // ["baz", "qux", "bar", "foo"] 620 | // {"baz": 999, "qux": 777, "bar": 101, "foo": 1} 621 | // done 622 | 623 | -------------------------------------------------------------------------------- /resources/persistent_vector.bg: -------------------------------------------------------------------------------- 1 | namespace persistent_vector 2 | import "raw_mutable_array" as array 3 | import "beagle.primitive" as primitive 4 | import "beagle.builtin" as builtin 5 | 6 | 7 | struct PersistentVector { 8 | count 9 | shift 10 | root 11 | tail 12 | } 13 | 14 | let EMPTY = PersistentVector { 15 | count: 0 16 | shift: 5 17 | root: array/new_array(0) 18 | tail: array/new_array(0) 19 | } 20 | 21 | fn tailOffset(vec) { 22 | if vec.count < 32 { 23 | 0 24 | } else { 25 | ((vec.count - 1) >>> 5) << 5 26 | } 27 | } 28 | 29 | fn get_node_for_level(node, level, index) { 30 | // So I'm guessing my parsing code is parsing 31 | // this incorrectly if I don't assign a variable... 32 | if level == 0 { 33 | node 34 | } else { 35 | let new_node = array/read_field(node, (index >>> level) & 31) 36 | get_node_for_level(new_node, level - 5, index) 37 | } 38 | } 39 | 40 | fn get(vec, index) { 41 | if index < 0 || index >= vec.count { 42 | primitive/panic("Index out of bounds") 43 | } 44 | 45 | if index >= tailOffset(vec) { 46 | array/read_field(vec.tail, index & 31) 47 | } else { 48 | let node = get_node_for_level(vec.root, vec.shift, index) 49 | array/read_field(node, index & 31) 50 | } 51 | } 52 | 53 | fn push(vec, value) { 54 | let tail_length = array/count(vec.tail) 55 | if tail_length < 32 { 56 | // Tail has space, create a new tail array with one extra slot 57 | let new_tail_size = tail_length + 1 58 | let new_tail = array/new_array(new_tail_size) 59 | // Copy existing tail into new tail 60 | array/copy_from_array_to(vec.tail, new_tail) 61 | // Add the new value 62 | array/write_field(new_tail, tail_length, value) 63 | // Return a new PersistentVector with the updated tail 64 | PersistentVector { 65 | count: vec.count + 1 66 | shift: vec.shift 67 | root: vec.root 68 | tail: new_tail 69 | } 70 | } else { 71 | // Tail is full, need to push into the tree 72 | let tail_node = vec.tail 73 | // Start a new tail with the new value 74 | let new_tail = array/new_array(1) 75 | array/write_field(new_tail, 0, value) 76 | let cnt_shifted = vec.count >>> 5 77 | let shifted_one = 1 << vec.shift 78 | 79 | if cnt_shifted > shifted_one { 80 | 81 | // Need to create a new root and increase shift 82 | let new_root_array = array/new_array(32) 83 | array/write_field(new_root_array, 0, vec.root) 84 | let new_path_node = new_path(vec, vec.shift, tail_node) 85 | array/write_field(new_root_array, 1, new_path_node) 86 | let new_root = new_root_array 87 | // Return a new PersistentVector with the increased shift and new root 88 | PersistentVector { 89 | count: vec.count + 1 90 | shift: vec.shift + 5 91 | root: new_root 92 | tail: new_tail 93 | } 94 | } else { 95 | // Insert into existing tree 96 | 97 | let new_root = push_tail(vec, vec.shift, vec.root, tail_node) 98 | // Return a new PersistentVector with the same shift and the new root 99 | PersistentVector { 100 | count: vec.count + 1 101 | shift: vec.shift 102 | root: new_root 103 | tail: new_tail 104 | } 105 | } 106 | } 107 | } 108 | 109 | fn new_path(vec, level, node) { 110 | if level == 0 { 111 | node 112 | } else { 113 | let path = array/new_array(32) 114 | array/write_field(path, 0, new_path(vec, level - 5, node)) 115 | path 116 | } 117 | } 118 | 119 | 120 | fn push_tail(vec, level, parent, tail_node) { 121 | let new_array = array/new_array(32) 122 | if array/is_array(parent) == false { 123 | primitive/panic("Parent is not an array") 124 | } 125 | let new_parent = array/copy_from_array_to(parent, new_array) 126 | let sub_index = ((vec.count - 1) >>> level) & 31 127 | 128 | let updated_node = if level == 5 { 129 | // Level is 5, insert the tail node here 130 | array/write_field(new_parent, sub_index, tail_node) 131 | new_parent 132 | } else { 133 | let child = array/read_field(parent, sub_index) 134 | let new_child = if child != null { 135 | push_tail(vec, level - 5, child, tail_node) 136 | } else { 137 | new_path(vec, level - 5, tail_node) 138 | } 139 | array/write_field(new_parent, sub_index, new_child) 140 | new_parent 141 | } 142 | updated_node 143 | } 144 | 145 | fn assoc(vec, index, value) { 146 | if index < 0 || index >= vec.count { 147 | primitive/panic("Index out of bounds") 148 | } 149 | 150 | if index >= tailOffset(vec) { 151 | // Update the tail 152 | let new_tail = array/copy_array(vec.tail) 153 | array/write_field(new_tail, index & 31, value) 154 | PersistentVector { 155 | count: vec.count 156 | shift: vec.shift 157 | root: vec.root 158 | tail: new_tail 159 | } 160 | } else { 161 | // Update the tree 162 | let new_root = doAssoc(vec, vec.shift, vec.root, index, value) 163 | PersistentVector { 164 | count: vec.count 165 | shift: vec.shift 166 | root: new_root 167 | tail: vec.tail 168 | } 169 | } 170 | } 171 | 172 | fn doAssoc(vec, level, node, index, value) { 173 | let new_node = array/copy_array(node) 174 | if level == 0 { 175 | array/write_field(new_node, index & 31, value) 176 | new_node 177 | } else { 178 | let sub_index = (index >>> level) & 31 179 | let child = array/read_field(node, sub_index) 180 | let new_child = doAssoc(vec, level - 5, child, index, value) 181 | array/write_field(new_node, sub_index, new_child) 182 | new_node 183 | } 184 | } 185 | 186 | 187 | fn fill_vec_n_helper(vec, i, n) { 188 | if i == n { 189 | vec 190 | } else { 191 | let new_vec = push(vec, i) 192 | fill_vec_n_helper(new_vec, i + 1, n) 193 | } 194 | } 195 | 196 | fn fill_vec_n(n) { 197 | fill_vec_n_helper(EMPTY, 0, n) 198 | } 199 | 200 | fn get_and_verify(vec, index) { 201 | let value = get(vec, index) 202 | if value != index { 203 | println("Error: Expected ") 204 | println(index) 205 | println(" but got ") 206 | println(value) 207 | primitive/panic("Value mismatch") 208 | } 209 | if index != vec.count - 1 { 210 | get_and_verify(vec, index + 1) 211 | } else { 212 | "done" 213 | } 214 | } 215 | 216 | fn make_vec_of_every_size_and_verify(n) { 217 | if n == 0 { 218 | "worked!" 219 | } else { 220 | let vec = fill_vec_n(n) 221 | get_and_verify(vec, 0) 222 | make_vec_of_every_size_and_verify(n - 1) 223 | } 224 | } 225 | 226 | fn count(vec) { 227 | vec.count 228 | } 229 | 230 | 231 | // TODO: I have a problem with arguments being passed properly. 232 | // If you swap arr and vec here, I overwrite x2. 233 | // I need to preserve that value, but I don't feel like doing that right now 234 | // so I'm just going to move them for the moment. 235 | fn to_array_helper(arr, i, vec) { 236 | if i == count(vec) { 237 | arr 238 | } else { 239 | array/write_field(arr, i, get(vec, i)) 240 | to_array_helper(arr, i + 1, vec) 241 | } 242 | } 243 | 244 | fn to_array(vec) { 245 | let arr = array/new_array(count(vec)) 246 | to_array_helper(arr, 0, vec) 247 | } 248 | 249 | fn vec() { 250 | EMPTY 251 | } 252 | 253 | fn from_array(arr) { 254 | from_array_helper(vec(), arr, 0, array/count(arr)) 255 | } 256 | 257 | fn from_array_helper(v, arr, i, n) { 258 | if i == n { 259 | v 260 | } else { 261 | let new_vec = push(v, array/read_field(arr, i)) 262 | from_array_helper(new_vec, arr, i + 1, n) 263 | } 264 | } 265 | 266 | 267 | 268 | extend PersistentVector with Indexed { 269 | fn get(vec, index) { 270 | get(vec, index) 271 | } 272 | } 273 | 274 | extend PersistentVector with Push { 275 | fn push(vec, value) { 276 | push(vec, value) 277 | } 278 | } 279 | 280 | extend PersistentVector with Format { 281 | fn format(vec, depth) { 282 | if vec.count == 0 { 283 | "[]" 284 | } else { 285 | format_vector(vec, 0, "[") 286 | } 287 | } 288 | } 289 | 290 | fn format_vector(vec, idx, acc) { 291 | if idx >= vec.count { 292 | acc ++ "]" 293 | } else { 294 | let val = get(vec, idx) 295 | let val_str = format(val, 1) 296 | 297 | let new_acc = if idx == 0 { 298 | acc ++ val_str 299 | } else { 300 | acc ++ ", " ++ val_str 301 | } 302 | 303 | format_vector(vec, idx + 1, new_acc) 304 | } 305 | } 306 | 307 | extend PersistentVector with Length { 308 | fn length(vec) { 309 | count(vec) 310 | } 311 | } 312 | 313 | 314 | extend PersistentVector with Associable { 315 | fn assoc(vec, index, value) { 316 | assoc(vec, index, value) 317 | } 318 | } 319 | 320 | 321 | 322 | fn main() { 323 | let vec1 = vec() 324 | println(vec1) 325 | gc() 326 | println(vec1) 327 | println(make_vec_of_every_size_and_verify(1000)) 328 | gc() 329 | gc() 330 | println(make_vec_of_every_size_and_verify(1000)) 331 | let vec2 = fill_vec_n(10); 332 | let array2 = to_array(vec2) 333 | println(array2) 334 | "done" 335 | } 336 | 337 | 338 | // Expect 339 | // [] 340 | // [] 341 | // worked! 342 | // worked! 343 | // [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ] 344 | // done 345 | -------------------------------------------------------------------------------- /resources/property_access_test.bg: -------------------------------------------------------------------------------- 1 | namespace property_access_test 2 | 3 | struct Thing { 4 | a 5 | b 6 | } 7 | 8 | struct TreeNode { 9 | left 10 | right 11 | } 12 | 13 | fn get_a(thing) { 14 | thing.a 15 | } 16 | 17 | fn itemCheck(node) { 18 | if node.left == null { 19 | 1 20 | } else { 21 | 1 + itemCheck(node.left) + itemCheck(node.right) 22 | } 23 | } 24 | 25 | fn bottomUpTree(depth) { 26 | if depth > 0 { 27 | TreeNode { 28 | left: bottomUpTree(depth - 1), 29 | right: bottomUpTree(depth - 1) 30 | } 31 | } else { 32 | TreeNode { 33 | left: null, 34 | right: null 35 | } 36 | } 37 | } 38 | 39 | fn get_b(thing) { 40 | thing.b 41 | } 42 | 43 | fn main() { 44 | let thing = Thing { 45 | a: 1, 46 | b: 2 47 | } 48 | 49 | let tree = bottomUpTree(2) 50 | 51 | let a = get_a(thing) 52 | println(a) 53 | let b = get_b(thing) 54 | println(b) 55 | let a = get_a(thing) 56 | println(a) 57 | let b = get_b(thing) 58 | println(b) 59 | 60 | println(itemCheck(tree)) 61 | "done" 62 | } 63 | 64 | // Expect 65 | // 1 66 | // 2 67 | // 1 68 | // 2 69 | // 7 70 | // done -------------------------------------------------------------------------------- /resources/protocol_test.bg: -------------------------------------------------------------------------------- 1 | namespace protocol_test 2 | 3 | import "persistent_vector" as vector 4 | import "beagle.builtin" as builtin 5 | import "beagle.primitive" as primitive 6 | 7 | 8 | struct Point { 9 | x 10 | y 11 | } 12 | 13 | struct Size { 14 | width 15 | height 16 | } 17 | 18 | protocol Axis { 19 | fn get_x(self) 20 | fn get_y(self) 21 | } 22 | 23 | 24 | extend Point with Axis { 25 | fn get_x(self) { 26 | self.x 27 | } 28 | 29 | fn get_y(self) { 30 | self.y 31 | } 32 | } 33 | 34 | extend Size with Axis { 35 | fn get_x(self) { 36 | self.width 37 | } 38 | 39 | fn get_y(self) { 40 | self.height 41 | } 42 | } 43 | 44 | 45 | protocol Format { 46 | fn format(self) { 47 | "no format" 48 | } 49 | } 50 | 51 | extend String with Format { 52 | fn format(self) { 53 | self 54 | } 55 | } 56 | 57 | 58 | fn main() { 59 | println(vector/PersistentVector.name) 60 | println(Indexed) 61 | let x = [2]; 62 | println(x[0]) 63 | println(primitive/read_struct_id(x) == vector/PersistentVector.id) 64 | println(instance_of(x, vector/PersistentVector)) 65 | println(instance_of(2, vector/PersistentVector)) 66 | 67 | 68 | let point = Point { 69 | x: 1, 70 | y: 2 71 | } 72 | 73 | let size = Size { 74 | width: 3, 75 | height: 4 76 | } 77 | 78 | println(get_x(point)) 79 | println(get_y(point)) 80 | println(get_x(size)) 81 | println(get_y(size)) 82 | println(format("test")) 83 | println(format(2)) 84 | "done" 85 | } 86 | 87 | // Expect 88 | // persistent_vector/PersistentVector 89 | // Protocol { name: "beagle.core/Indexed" } 90 | // 2 91 | // true 92 | // true 93 | // false 94 | // 1 95 | // 2 96 | // 3 97 | // 4 98 | // test 99 | // no format 100 | // done 101 | -------------------------------------------------------------------------------- /resources/raw_mutable_array.bg: -------------------------------------------------------------------------------- 1 | namespace raw_mutable_array 2 | import "beagle.builtin" as builtin 3 | import "beagle.primitive" as primitive 4 | 5 | 6 | fn allocate_array_unsafe(size) { 7 | let array = builtin/allocate(size) 8 | builtin/fill_object_fields(array, null) 9 | } 10 | 11 | fn read_field_unsafe(array, index) { 12 | primitive/read_field(array, index) 13 | } 14 | 15 | fn write_field_unsafe(array, index, value) { 16 | primitive/write_field(array, index, value) 17 | } 18 | 19 | fn is_array(array) { 20 | primitive/is_object(array) && primitive/read_type_id(array) == 1 21 | } 22 | 23 | fn panic_if_not_array(message, array) { 24 | if is_array(array) == false { 25 | println(message) 26 | println(array) 27 | primitive/panic("Not an array") 28 | } 29 | } 30 | 31 | fn write_field(array, index, value) { 32 | panic_if_not_array("write", array) 33 | if index < 0 || index >= primitive/size(array) { 34 | println(array) 35 | println(index) 36 | println(primitive/size(array)) 37 | primitive/panic("Index out of bounds") 38 | } 39 | write_field_unsafe(array, index, value) 40 | } 41 | 42 | fn read_field(array, index) { 43 | panic_if_not_array("read", array) 44 | if index < 0 || index >= primitive/size(array) { 45 | primitive/panic("Index out of bounds") 46 | } 47 | read_field_unsafe(array, index) 48 | } 49 | 50 | fn new_array(size) { 51 | if size < 0 || size > 256 { 52 | primitive/panic("Negative size") 53 | } 54 | let array = allocate_array_unsafe(size) 55 | primitive/write_type_id(array, 1) 56 | array 57 | } 58 | 59 | 60 | struct ExampleStruct { 61 | value 62 | } 63 | 64 | fn copy_array(array) { 65 | panic_if_not_array("copy", array) 66 | if array == null { 67 | primitive/panic("Array is null") 68 | } 69 | let new_array = builtin/copy_object(array) 70 | new_array 71 | } 72 | 73 | 74 | fn unsafe_copy_from_array_to(from, to) { 75 | builtin/copy_from_to_object(from, to) 76 | } 77 | 78 | fn copy_from_array_to(from, to) { 79 | panic_if_not_array("copy from", from) 80 | panic_if_not_array("copy to", to) 81 | if primitive/size(from) > primitive/size(to) { 82 | primitive/panic("Array from size must be less than or equal to array to size") 83 | } 84 | unsafe_copy_from_array_to(from, to) 85 | } 86 | 87 | 88 | fn allocate_array_and_return() { 89 | let example_struct = ExampleStruct { 90 | value: "Example Struct Value2" 91 | } 92 | let array = new_array(1) 93 | write_field(array, 0, example_struct) 94 | array 95 | } 96 | 97 | fn count(array) { 98 | if array == null { 99 | 0 100 | } else { 101 | panic_if_not_array("count", array) 102 | primitive/size(array) 103 | } 104 | } 105 | 106 | fn mod(a, b) { 107 | a - (a / b) * b 108 | } 109 | 110 | fn main() { 111 | let array = new_array(10) 112 | primitive/write_type_id(array, 1) 113 | primitive/write_field(array, 0, 1) 114 | primitive/write_field(array, 1, 2) 115 | primitive/write_field(array, 2, 3) 116 | primitive/write_field(array, 3, 4) 117 | primitive/write_field(array, 4, 5) 118 | primitive/write_field(array, 5, 6) 119 | primitive/write_field(array, 6, 7) 120 | primitive/write_field(array, 7, 8) 121 | primitive/write_field(array, 8, 9) 122 | primitive/write_field(array, 9, 10) 123 | 124 | println(is_array(array)) 125 | 126 | 127 | println(count(array)) 128 | println(count(null)) 129 | 130 | let copied_array = copy_array(array) 131 | write_field(copied_array, 0, 42); 132 | gc() 133 | gc() 134 | gc() 135 | println(array) 136 | println(copied_array) 137 | 138 | 139 | 140 | let my_array = allocate_array_and_return() 141 | println(primitive/read_field(array, 0)) 142 | println(array) 143 | println(primitive/size(array)) 144 | 145 | let example_struct = ExampleStruct { 146 | value: "Example Struct Value" 147 | } 148 | println(is_array(example_struct)) 149 | 150 | let array = builtin/allocate(1) 151 | primitive/write_type_id(array, 1) 152 | primitive/write_field(array, 0, example_struct) 153 | println(primitive/read_field(array, 0).value) 154 | println(array) 155 | gc() 156 | gc() 157 | gc() 158 | println(array) 159 | println(my_array) 160 | 161 | 162 | let my_small_array = new_array(1); 163 | write_field(my_small_array, 0, 42) 164 | 165 | let my_larger_array = new_array(2); 166 | write_field(my_larger_array, 0, 1) 167 | 168 | copy_from_array_to(my_small_array, my_larger_array) 169 | write_field(my_larger_array, 1, 44) 170 | write_field(my_small_array, 0, 43) 171 | println(my_small_array) 172 | println(my_larger_array) 173 | 174 | read_field(array, 0) 175 | 176 | "done" 177 | } 178 | 179 | // Expect 180 | // true 181 | // 10 182 | // 0 183 | // [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] 184 | // [ 42, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] 185 | // 1 186 | // [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] 187 | // 10 188 | // false 189 | // Example Struct Value 190 | // [ ExampleStruct { value: "Example Struct Value" } ] 191 | // [ ExampleStruct { value: "Example Struct Value" } ] 192 | // [ ExampleStruct { value: "Example Struct Value2" } ] 193 | // [ 43 ] 194 | // [ 42, 44 ] 195 | // done 196 | -------------------------------------------------------------------------------- /resources/register_allocation_test.bg: -------------------------------------------------------------------------------- 1 | namespace register_allocation_test 2 | 3 | // no-std 4 | fn is_bitmap_indexed_node(n) { 5 | false 6 | } 7 | 8 | fn is_collision_node(n) { 9 | true 10 | } 11 | 12 | fn is_pair_node(n) { 13 | false 14 | } 15 | 16 | fn assoc_bitmap_node(n, shift, h, k, v, added_leaf_box) { 17 | println("assoc_bitmap_node") 18 | } 19 | 20 | fn assoc_collision_node(n, shift, h, k, v, added_leaf_box) { 21 | println("assoc_collision_node") 22 | } 23 | 24 | fn assoc_key_val_node(n, shift, h, k, v, added_leaf_box) { 25 | println("assoc_key_val_node") 26 | println(added_leaf_box) 27 | } 28 | 29 | fn assoc_node(n, shift, h, k, v, added_leaf_box) { 30 | if n == null { 31 | // brand-new node 32 | assoc_bitmap_node(null, shift, h, k, v, added_leaf_box) 33 | } else if is_bitmap_indexed_node(n) { 34 | assoc_bitmap_node(n, shift, h, k, v, added_leaf_box) 35 | } else if is_collision_node(n) { 36 | // It is now failing here 37 | assoc_collision_node(n, shift, h, k, v, added_leaf_box) 38 | } else if is_pair_node(n) { 39 | assoc_key_val_node(n, shift, h, k, v, added_leaf_box) 40 | } else { 41 | // Fallback (should rarely occur if all logic is correct) 42 | assoc_key_val_node(n, shift, h, k, v, added_leaf_box) 43 | } 44 | } 45 | 46 | fn main() { 47 | let n = 1 48 | let shift = 0 49 | let h = 0 50 | let k = 0 51 | let v = 0 52 | let added_leaf_box = 42 53 | assoc_node(n, shift, h, k, v, added_leaf_box) 54 | } -------------------------------------------------------------------------------- /resources/repl_test.bg: -------------------------------------------------------------------------------- 1 | namespace repl_test 2 | 3 | import "beagle.builtin" as builtin 4 | 5 | fn repl() { 6 | let x = builtin/wait_for_input() 7 | println(eval(x)) 8 | repl() 9 | } 10 | 11 | fn main() { 12 | thread(repl) 13 | } -------------------------------------------------------------------------------- /resources/simple_call_test.bg: -------------------------------------------------------------------------------- 1 | namespace simple_call_test 2 | 3 | 4 | fn many_arguments(a, b, c, d, e, f) { 5 | a - b - c - d - e - f 6 | } 7 | 8 | fn swap_argments(f, e, d, c, b, a) { 9 | many_arguments(a, b, c, d, e, f) 10 | } 11 | 12 | 13 | fn main() { 14 | // Should be -9 15 | // Right now I am not accounting for 16 | // arguments being live 17 | println(swap_argments(1, 2, 3, 4, 5, 6)) 18 | } -------------------------------------------------------------------------------- /resources/slow_json_parser.bg: -------------------------------------------------------------------------------- 1 | namespace slow_json_parser 2 | 3 | import "beagle.primitive" as primitive 4 | import "persistent_map" as pm 5 | import "persistent_vector" as pv 6 | 7 | struct Tokenizer { 8 | input 9 | position 10 | length 11 | } 12 | 13 | fn make_tokenizer(input) { 14 | Tokenizer { 15 | input: input, 16 | position: 0, 17 | length: length(input) 18 | } 19 | } 20 | 21 | fn peek(tokenizer) { 22 | if tokenizer.position >= tokenizer.length { 23 | "" 24 | } else { 25 | let result = tokenizer.input[tokenizer.position] 26 | result 27 | } 28 | } 29 | 30 | fn consume(tokenizer) { 31 | primitive/set!(tokenizer.position, tokenizer.position + 1) 32 | tokenizer 33 | } 34 | 35 | fn skip_whitespace(tokenizer) { 36 | if tokenizer.position >= tokenizer.length { 37 | tokenizer 38 | } else { 39 | let c = peek(tokenizer) 40 | if c == " " || c == "\n" || c == "\t" || c == "\r" { 41 | skip_whitespace(consume(tokenizer)) 42 | } else { 43 | tokenizer 44 | } 45 | } 46 | } 47 | 48 | fn find_string_end(tokenizer) { 49 | if tokenizer.position >= tokenizer.length { 50 | tokenizer.position 51 | } else { 52 | let c = peek(tokenizer) 53 | if c == "\"" { 54 | tokenizer.position 55 | } else { 56 | find_string_end(consume(tokenizer)) 57 | } 58 | } 59 | } 60 | 61 | fn parse_string(tokenizer) { 62 | consume(tokenizer) 63 | let start_pos = tokenizer.position 64 | let end_pos = find_string_end(tokenizer) 65 | let str_val = substring(tokenizer.input, start_pos, end_pos - start_pos) 66 | if tokenizer.position < tokenizer.length { 67 | consume(tokenizer) 68 | } 69 | str_val 70 | } 71 | 72 | fn is_digit_char(c) { 73 | c == "0" || 74 | c == "1" || 75 | c == "2" || 76 | c == "3" || 77 | c == "4" || 78 | c == "5" || 79 | c == "6" || 80 | c == "7" || 81 | c == "8" || 82 | c == "9" 83 | } 84 | 85 | fn find_number_end(tokenizer) { 86 | if tokenizer.position >= tokenizer.length { 87 | tokenizer.position 88 | } else { 89 | let c = peek(tokenizer) 90 | if is_digit_char(c) || c == "." || c == "-" { 91 | find_number_end(consume(tokenizer)) 92 | } else { 93 | tokenizer.position 94 | } 95 | } 96 | } 97 | 98 | fn parse_number(tokenizer) { 99 | let start_pos = tokenizer.position 100 | let end_pos = find_number_end(tokenizer) 101 | let number_string = substring(tokenizer.input, start_pos, end_pos - start_pos) 102 | to_number(number_string) 103 | } 104 | 105 | fn find_word_end(tokenizer) { 106 | if tokenizer.position >= tokenizer.length { 107 | tokenizer.position 108 | } else { 109 | let c = peek(tokenizer) 110 | if c >= "a" && c <= "z" { 111 | find_word_end(consume(tokenizer)) 112 | } else { 113 | tokenizer.position 114 | } 115 | } 116 | } 117 | 118 | fn parse_true(tokenizer) { 119 | consume(tokenizer) 120 | true 121 | } 122 | 123 | fn parse_false(tokenizer) { 124 | primitive/set!(tokenizer.position, tokenizer.position + 5) 125 | false 126 | } 127 | 128 | fn parse_null(tokenizer) { 129 | primitive/set!(tokenizer.position, tokenizer.position + 4) 130 | null 131 | } 132 | 133 | fn parse_value(tokenizer) { 134 | let tokenizer = skip_whitespace(tokenizer) 135 | let c = peek(tokenizer) 136 | if c == "{" { 137 | parse_object(tokenizer) 138 | } else if c == "[" { 139 | parse_array(tokenizer) 140 | } else if c == "\"" { 141 | parse_string(tokenizer) 142 | } else if c == "t" { 143 | parse_true(tokenizer) 144 | } else if c == "f" { 145 | parse_false(tokenizer) 146 | } else if c == "n" { 147 | parse_null(tokenizer) 148 | } else { 149 | parse_number(tokenizer) 150 | } 151 | } 152 | 153 | fn parse_object(tokenizer) { 154 | consume(tokenizer) 155 | let tokenizer = skip_whitespace(tokenizer) 156 | if peek(tokenizer) == "}" { 157 | consume(tokenizer) 158 | pm/map() 159 | } else { 160 | parse_object_loop(tokenizer, pm/map()) 161 | } 162 | } 163 | 164 | fn parse_object_loop(tokenizer, out_map) { 165 | let tokenizer = skip_whitespace(tokenizer) 166 | let key = parse_string(tokenizer) 167 | let after_key = skip_whitespace(tokenizer) 168 | consume(after_key) 169 | let after_colon = skip_whitespace(after_key) 170 | let val = parse_value(after_colon) 171 | let new_map = pm/assoc(out_map, key, val) 172 | let after_val = skip_whitespace(after_colon) 173 | let c = peek(after_val) 174 | if c == "," { 175 | consume(after_val) 176 | parse_object_loop(skip_whitespace(after_val), new_map) 177 | } else { 178 | if c == "}" { 179 | consume(after_val) 180 | } 181 | new_map 182 | } 183 | } 184 | 185 | fn parse_array(tokenizer) { 186 | consume(tokenizer) 187 | let tokenizer = skip_whitespace(tokenizer) 188 | if peek(tokenizer) == "]" { 189 | consume(tokenizer) 190 | pv/vec() 191 | } else { 192 | parse_array_loop(tokenizer, pv/vec()) 193 | } 194 | } 195 | 196 | fn parse_array_loop(tokenizer, out_vec) { 197 | let tokenizer = skip_whitespace(tokenizer) 198 | let val = parse_value(tokenizer) 199 | let new_vec = pv/push(out_vec, val) 200 | let after_val = skip_whitespace(tokenizer) 201 | let c = peek(after_val) 202 | if c == "," { 203 | consume(after_val) 204 | parse_array_loop(skip_whitespace(after_val), new_vec) 205 | } else { 206 | if c == "]" { 207 | consume(after_val) 208 | } 209 | new_vec 210 | } 211 | } 212 | 213 | fn parse(input) { 214 | let tokenizer = make_tokenizer(input) 215 | parse_value(tokenizer) 216 | } 217 | 218 | 219 | fn main() { 220 | let input = "{\"a\": [1, 2, 3], \"b\": {\"c\": 4, \"d\": 5}, \"e\": true, \"f\": false, \"g\": null}" 221 | let output = parse(input) 222 | println(output) 223 | "done" 224 | } -------------------------------------------------------------------------------- /resources/socket_test.bg: -------------------------------------------------------------------------------- 1 | namespace socket_test 2 | 3 | import "beagle.ffi" as ffi 4 | import "beagle.primitive" as primitive 5 | 6 | let libc = ffi/load_library("/usr/lib/libSystem.dylib") 7 | 8 | // Function bindings 9 | let socket = ffi/get_function( 10 | libc, 11 | "socket", 12 | [ffi/Type.I32, ffi/Type.I32, ffi/Type.I32], 13 | ffi/Type.I32 14 | ) 15 | 16 | let bind = ffi/get_function( 17 | libc, 18 | "bind", 19 | [ffi/Type.I32, ffi/Type.Pointer, ffi/Type.U32], 20 | ffi/Type.I32 21 | ) 22 | 23 | let listen = ffi/get_function( 24 | libc, 25 | "listen", 26 | [ffi/Type.I32, ffi/Type.I32], 27 | ffi/Type.I32 28 | ) 29 | 30 | let accept = ffi/get_function( 31 | libc, 32 | "accept", 33 | [ffi/Type.I32, ffi/Type.Pointer, ffi/Type.Pointer], 34 | ffi/Type.I32 35 | ) 36 | 37 | let recv = ffi/get_function( 38 | libc, 39 | "recv", 40 | [ffi/Type.I32, ffi/Type.Pointer, ffi/Type.U32, ffi/Type.I32], 41 | ffi/Type.I32 42 | ) 43 | 44 | let htons = ffi/get_function( 45 | libc, 46 | "htons", 47 | [ffi/Type.U16], 48 | ffi/Type.U16 49 | ) 50 | 51 | // Constants 52 | let AF_INET = 2 53 | let SOCK_STREAM = 1 54 | let IPPROTO_TCP = 6 55 | let INADDR_ANY = 0 56 | let PORT = 12345 57 | let BACKLOG = 5 58 | let BUFFER_SIZE = 1024 59 | 60 | struct ServerState { 61 | server_fd 62 | sockaddr_in 63 | sockaddr_in_size 64 | buffer 65 | } 66 | 67 | fn init_server(port) { 68 | // Create a socket 69 | println(AF_INET) 70 | println(SOCK_STREAM) 71 | println(IPPROTO_TCP) 72 | let server_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) 73 | if server_fd < 0 { 74 | println("Failed to create socket") 75 | // primitive/exit(1) 76 | } 77 | println("Socket created") 78 | 79 | // Set up sockaddr_in structure 80 | let sockaddr_in_size = 16 // 2 bytes family, 2 bytes port, 4 bytes address, 8 bytes padding 81 | let sockaddr_in = ffi/allocate(sockaddr_in_size) 82 | 83 | println("allocated sockaddr_in") 84 | 85 | // Set family to AF_INET 86 | ffi/set_i16(sockaddr_in, 0, AF_INET) 87 | 88 | println("set family") 89 | 90 | println(libc) 91 | println(htons) 92 | 93 | println("here") 94 | 95 | // Set port (convert to network byte order) 96 | ffi/set_i16(sockaddr_in, 2, htons(port)) 97 | 98 | println("set port") 99 | 100 | // Set address to INADDR_ANY 101 | ffi/set_i32(sockaddr_in, 4, INADDR_ANY) 102 | 103 | println("set address") 104 | 105 | // Bind the socket 106 | if bind(server_fd, sockaddr_in, sockaddr_in_size) < 0 { 107 | println("Failed to bind socket") 108 | // primitive/exit(1) 109 | } 110 | println("Socket bound to port ", port) 111 | 112 | // Start listening 113 | if listen(server_fd, BACKLOG) < 0 { 114 | println("Failed to listen on socket") 115 | // primitive/exit(1) 116 | } 117 | println("Listening on port ", port) 118 | 119 | // Initialize buffer for receiving data 120 | let buffer = ffi/allocate(BUFFER_SIZE) 121 | 122 | ServerState { 123 | server_fd: server_fd, 124 | sockaddr_in: sockaddr_in, 125 | sockaddr_in_size: sockaddr_in_size, 126 | buffer: buffer 127 | } 128 | } 129 | 130 | fn handle_client(state, client_fd, on_data) { 131 | // Receive data 132 | let bytes_received = recv(client_fd, state.buffer, BUFFER_SIZE, 0) 133 | if bytes_received > 0 { 134 | let data = ffi/get_string(state.buffer, 0, bytes_received) 135 | on_data(data) 136 | } else { 137 | println("Failed to receive data or connection closed") 138 | } 139 | } 140 | 141 | fn accept_connections(state, on_data) { 142 | println("Waiting for a connection...") 143 | let client_addr = ffi/allocate(state.sockaddr_in_size) 144 | let addr_len = ffi/allocate(4) 145 | let client_fd = accept(state.server_fd, client_addr, addr_len) 146 | if client_fd < 0 { 147 | println("Failed to accept connection") 148 | accept_connections(state, on_data) 149 | } else { 150 | println("Connection accepted") 151 | handle_client(state, client_fd, on_data) 152 | accept_connections(state, on_data) 153 | } 154 | } 155 | 156 | 157 | fn server(port, on_data) { 158 | println("starting") 159 | let server_state = init_server(port) 160 | println("server initialized") 161 | accept_connections(server_state, on_data) 162 | } 163 | 164 | fn main() { 165 | let server_state = init_server(PORT) 166 | accept_connections(server_state, fn(data) { 167 | print("Received data: ") 168 | println(data) 169 | }) 170 | } -------------------------------------------------------------------------------- /resources/std.bg: -------------------------------------------------------------------------------- 1 | namespace beagle.core; 2 | import "beagle.primitive" as primitive 3 | import "beagle.builtin" as builtin 4 | // TODO: I need to figure out how I deal with the circular nature 5 | // of having persistent vector not be in this namespace 6 | // One answer is to have dynamic imports 7 | // so that after I define the things persistent vector depends on 8 | // I can import it dynamically 9 | 10 | // I need to think about execution order as well 11 | // If I import a namespace, does that namespaces top level 12 | // get called before I compile the current namespace 13 | // It seems like it should. 14 | // But I need to do that on the calling thread 15 | // so I will need some back and forth going on there 16 | 17 | // TODO: This is hack because 0 is making the fast path cache activate 18 | // fix by changing id or filling the cache 19 | struct NEVER{} 20 | 21 | struct Struct { 22 | name 23 | id 24 | } 25 | 26 | // TODO: This is a bit gross 27 | // I definitely need something like 28 | // javascript Symbol 29 | // or some way to really uniquely 30 | // identify a type 31 | let String = Struct { 32 | name: "String", 33 | id: -1 34 | } 35 | 36 | struct Protocol { 37 | name 38 | } 39 | 40 | protocol Indexed { 41 | fn get(coll, index) 42 | } 43 | 44 | protocol Push { 45 | fn push(coll, value) 46 | } 47 | 48 | protocol Length { 49 | fn length(coll) 50 | } 51 | 52 | protocol Format { 53 | fn format(self, depth) { 54 | to_string(self) 55 | } 56 | } 57 | 58 | protocol Associable { 59 | fn assoc(coll, key, value) 60 | } 61 | 62 | protocol Keys { 63 | fn keys(coll) 64 | } 65 | 66 | extend String with Format { 67 | fn format(self, depth) { 68 | if depth > 0 { 69 | "\"" ++ self ++ "\"" 70 | } else { 71 | self 72 | } 73 | } 74 | } 75 | 76 | // This may seem weird 77 | // but we need our runtime to know 78 | // how to call a function if it is a closure 79 | // or a regular function 80 | // this makes that idea simple 81 | fn __call_fn(f) { f() } 82 | 83 | struct Atom { 84 | value 85 | } 86 | 87 | fn deref(atom) { 88 | // assert!(atom is Atom) 89 | primitive/deref(atom) 90 | } 91 | 92 | fn swap!(atom, f) { 93 | let initial = deref(atom); 94 | let new = f(initial); 95 | let result = compare_and_swap!(atom, initial, new); 96 | if result { 97 | new 98 | } else { 99 | swap!(atom, f) 100 | } 101 | } 102 | 103 | fn reset!(atom, value) { 104 | // assert!(atom is Atom) 105 | primitive/reset!(atom, value) 106 | } 107 | 108 | fn compare_and_swap!(atom, old, new) { 109 | // assert!(atom is Atom) 110 | primitive/compare_and_swap!(atom, old, new) 111 | } 112 | 113 | fn atom(value) { 114 | // TODO: Allow punning 115 | Atom { value: value } 116 | } 117 | 118 | // TODO: If I move the || on the next line, 119 | // my parser is unhappy 120 | // TODO: Need to make sure type is instance_of Struct 121 | // but how do I do that in a reasonable way? 122 | fn instance_of(value, type) { 123 | (primitive/is_object(value) && 124 | ( 125 | primitive/read_struct_id(value) == type.id || 126 | (type.id == -1 && primitive/read_type_id(value) == 2) 127 | )) || (type.id == -1 && primitive/is_string_constant(value)) 128 | } 129 | 130 | 131 | extend String with Indexed { 132 | fn get(str, i) { 133 | builtin/get_string_index(str, i) 134 | } 135 | } 136 | 137 | extend String with Length { 138 | fn length(str) { 139 | builtin/get_string_length(str) 140 | } 141 | } 142 | 143 | 144 | // // TODO: Need a way to re-export rather than wrap 145 | // fn get(coll, i) { 146 | // persistent_vector/get(coll, i) 147 | // } 148 | 149 | // fn push(coll, value) { 150 | // persistent_vector/push(coll, value) 151 | // } 152 | 153 | fn println(value) { 154 | _println(format(value, 0)) 155 | } 156 | 157 | fn print(value) { 158 | _print(format(value, 0)) 159 | } 160 | 161 | 162 | struct __Box__ { 163 | value 164 | } -------------------------------------------------------------------------------- /resources/string.bg: -------------------------------------------------------------------------------- 1 | namespace string 2 | 3 | import "persistent_vector" as vector 4 | import "beagle.builtin" as builtin 5 | 6 | 7 | 8 | fn main() { 9 | let x = "test" 10 | println(x) 11 | 12 | println(x[0]) 13 | // TODO: I want to make indexed operations 14 | // work on strings. 15 | // Right now, I am assuming persistent vectors 16 | // I should definitely make this extensible. 17 | // Given that, I think I need impl and/or protocols 18 | // Protocols honestly probably make sense 19 | } 20 | 21 | -------------------------------------------------------------------------------- /resources/test_gc.bg: -------------------------------------------------------------------------------- 1 | namespace test_gc 2 | 3 | 4 | struct TreeNode { 5 | left 6 | right 7 | } 8 | 9 | 10 | 11 | fn create_garbage() { 12 | let x = TreeNode { 13 | left: 1, 14 | right: 2 15 | } 16 | println("After first allocate") 17 | 18 | let y = TreeNode { 19 | left: 3, 20 | right: 4 21 | } 22 | 23 | println("Garbage created") 24 | 25 | let z = TreeNode { 26 | left: 5, 27 | right: 6 28 | } 29 | z 30 | } 31 | 32 | fn main() { 33 | create_garbage() 34 | println("Garbage created") 35 | gc(); 36 | println("Garbage collected") 37 | } -------------------------------------------------------------------------------- /resources/thread.bg: -------------------------------------------------------------------------------- 1 | namespace thread 2 | 3 | import "beagle.primitive" as primitive 4 | 5 | struct Thing { 6 | x 7 | } 8 | 9 | struct TreeNode { 10 | left 11 | right 12 | } 13 | 14 | fn print_endlessly(message, x) { 15 | // println(message) 16 | // println(x) 17 | print_endlessly(message, x) 18 | } 19 | 20 | fn testGc(n) { 21 | if n > 10 { 22 | 0 23 | } else { 24 | let x = TreeNode { 25 | left: null 26 | right: null 27 | } 28 | 1 + testGc(n + 1) 29 | 30 | let y = TreeNode { 31 | left: x, 32 | right: x 33 | } 34 | 35 | 1 + testGc(n + 1) 36 | 37 | let z = TreeNode { 38 | left: y, 39 | right: y, 40 | } 41 | // println(x) 42 | // println(y) 43 | // println(z) 44 | 0 45 | } 46 | } 47 | 48 | fn call_function() { 49 | // Doing this because locals live for the whole function 50 | let x = Thing { x: 42 }; 51 | 52 | // TODO: This closure is breaking stuff right now 53 | let f = fn() { 54 | // primitive/breakpoint() 55 | testGc(0) 56 | testGc(0) 57 | testGc(0) 58 | testGc(0) 59 | }; 60 | thread(f); 61 | // f() 62 | } 63 | 64 | fn swap_counter_in_many_threads(counter, n) { 65 | let f = fn() { 66 | swap!(counter, fn(x) { x + 1 }) 67 | }; 68 | 69 | if n > 0 { 70 | thread(f); 71 | // f() 72 | swap_counter_in_many_threads(counter, n - 1) 73 | } 74 | } 75 | 76 | // You can see that thing get's replaced by one of these nodes 77 | // This is because the gc is not working correctly with concurrency 78 | fn main() { 79 | 80 | let counter = atom(0); 81 | swap_counter_in_many_threads(counter, 10) 82 | call_function() 83 | swap!(counter, fn(x) { x + 1 }) 84 | call_function() 85 | swap!(counter, fn(x) { x + 1 }) 86 | call_function() 87 | swap!(counter, fn(x) { x + 1 }) 88 | call_function() 89 | swap!(counter, fn(x) { x + 1 }) 90 | call_function() 91 | swap!(counter, fn(x) { x + 1 }) 92 | call_function() 93 | swap!(counter, fn(x) { x + 1 }) 94 | call_function() 95 | swap!(counter, fn(x) { x + 1 }) 96 | call_function() 97 | swap!(counter, fn(x) { x + 1 }) 98 | call_function() 99 | swap!(counter, fn(x) { x + 1 }) 100 | call_function() 101 | swap!(counter, fn(x) { x + 1 }) 102 | call_function() 103 | swap!(counter, fn(x) { x + 1 }) 104 | testGc(0) 105 | gc() 106 | testGc(0) 107 | gc() 108 | testGc(0) 109 | gc() 110 | testGc(0) 111 | gc() 112 | testGc(0) 113 | testGc(0) 114 | swap!(counter, fn(x) { x + 1 }) 115 | println(deref(counter)) 116 | 117 | "done" 118 | } 119 | 120 | // thread-safe 121 | // Expect 122 | // 22 123 | // done -------------------------------------------------------------------------------- /resources/throw_error.bg: -------------------------------------------------------------------------------- 1 | namespace throw_error 2 | 3 | fn main() { 4 | 2 + "test" 5 | } -------------------------------------------------------------------------------- /resources/top_level.bg: -------------------------------------------------------------------------------- 1 | namespace top_level 2 | 3 | let x = 2 4 | 5 | println(x) 6 | 7 | fn main() { 8 | println("Hello, world!") 9 | println(x) 10 | "done" 11 | } 12 | 13 | // Expect 14 | // 2 15 | // Hello, world! 16 | // 2 17 | // done -------------------------------------------------------------------------------- /src/code_memory.rs: -------------------------------------------------------------------------------- 1 | use mmap_rs::{Mmap, MmapOptions, Reserved, ReservedMut}; 2 | 3 | pub struct CodeAllocator { 4 | unused_pages: Reserved, 5 | current_page: Option, 6 | current_offset: usize, 7 | pending_pages: Option, 8 | #[allow(dead_code)] 9 | used_pages: Option, 10 | } 11 | 12 | impl Default for CodeAllocator { 13 | fn default() -> Self { 14 | Self::new() 15 | } 16 | } 17 | 18 | impl CodeAllocator { 19 | pub fn new() -> Self { 20 | let unused_pages = MmapOptions::new(MmapOptions::page_size() * 100000) 21 | .unwrap() 22 | .reserve() 23 | .unwrap(); 24 | Self { 25 | unused_pages, 26 | used_pages: None, 27 | pending_pages: None, 28 | current_page: None, 29 | current_offset: 0, 30 | } 31 | } 32 | 33 | pub fn take_page(&mut self) -> ReservedMut { 34 | let page = self 35 | .unused_pages 36 | .split_to(MmapOptions::page_size()) 37 | .unwrap(); 38 | page.make_mut().unwrap() 39 | } 40 | 41 | pub fn mark_page_as_pending(&mut self) { 42 | if self.current_offset == 0 { 43 | return; 44 | } 45 | if let Some(page) = self.current_page.take() { 46 | let page = page.make_read_only().unwrap(); 47 | if let Some(mut pending) = self.pending_pages.take() { 48 | pending.merge(page).unwrap(); 49 | self.pending_pages = Some(pending); 50 | } else { 51 | self.pending_pages = Some(page); 52 | } 53 | } 54 | 55 | self.current_page = None; 56 | self.current_offset = 0; 57 | } 58 | 59 | pub fn write_bytes(&mut self, bytes: &[u8]) -> *const u8 { 60 | if self.current_page.is_none() { 61 | self.current_page = Some(self.take_page()); 62 | } 63 | unsafe { 64 | let bytes_remaining = MmapOptions::page_size() - self.current_offset; 65 | if bytes.len() < bytes_remaining { 66 | let start = self 67 | .current_page 68 | .as_mut() 69 | .unwrap() 70 | .as_mut_ptr() 71 | .add(self.current_offset); 72 | std::ptr::copy_nonoverlapping(bytes.as_ptr(), start, bytes.len()); 73 | self.current_offset += bytes.len(); 74 | 75 | // I think this should align 16 bytes 76 | // but I might be wrong and I need to fix something going wrong 77 | // I know that if I had any functions to my lldb libs 78 | // I get some weird error. I thought it might be alignment 79 | // but it could be that compilation is crossing a page 80 | // boundary and this code isn't dealing with that 81 | // correctly 82 | if self.current_offset % 2 != 0 { 83 | self.current_offset += 1; 84 | } 85 | 86 | start 87 | } else { 88 | let (first, second) = bytes.split_at(bytes_remaining); 89 | let start = self 90 | .current_page 91 | .as_mut() 92 | .unwrap() 93 | .as_mut_ptr() 94 | .add(self.current_offset); 95 | 96 | std::ptr::copy_nonoverlapping(first.as_ptr(), start, first.len()); 97 | self.current_offset += first.len(); 98 | self.mark_page_as_pending(); 99 | self.write_bytes(second); 100 | start 101 | } 102 | } 103 | } 104 | 105 | pub fn make_executable(&mut self) { 106 | self.mark_page_as_pending(); 107 | 108 | let pending = self.pending_pages.take().unwrap(); 109 | let pending = pending.make_exec().unwrap(); 110 | let pending: Mmap = pending.try_into().unwrap(); 111 | if let Some(mut used) = self.used_pages.take() { 112 | used.merge(pending).unwrap(); 113 | self.used_pages = Some(used); 114 | } else { 115 | self.used_pages = Some(pending); 116 | } 117 | // let used = self.used_pages.take(); 118 | // let used = used.unwrap().make_exec().unwrap(); 119 | // self.used_pages = Some(used); 120 | 121 | assert!(self.current_page.is_none()); 122 | assert!(self.pending_pages.is_none()); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/common.rs: -------------------------------------------------------------------------------- 1 | use bincode::{Decode, Encode}; 2 | 3 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Encode, Decode)] 4 | pub struct Label { 5 | pub index: usize, 6 | } 7 | -------------------------------------------------------------------------------- /src/gc/generation_v2.rs: -------------------------------------------------------------------------------- 1 | use std::{error::Error, ffi::c_void, io}; 2 | 3 | use libc::{mprotect, vm_page_size}; 4 | 5 | use crate::types::{BuiltInTypes, HeapObject, Word}; 6 | 7 | use super::{ 8 | AllocateAction, Allocator, AllocatorOptions, STACK_SIZE, StackMap, 9 | mark_and_sweep_v2::MarkAndSweepV2, 10 | }; 11 | 12 | const DEFAULT_PAGE_COUNT: usize = 1024; 13 | // Aribtary number that should be changed when I have 14 | // better options for gc 15 | const MAX_PAGE_COUNT: usize = 1000000; 16 | 17 | struct Space { 18 | start: *const u8, 19 | page_count: usize, 20 | allocation_offset: usize, 21 | } 22 | 23 | unsafe impl Send for Space {} 24 | unsafe impl Sync for Space {} 25 | 26 | impl Space { 27 | #[allow(unused)] 28 | fn word_count(&self) -> usize { 29 | (self.page_count * unsafe { vm_page_size }) / 8 30 | } 31 | 32 | fn byte_count(&self) -> usize { 33 | self.page_count * unsafe { vm_page_size } 34 | } 35 | 36 | fn contains(&self, pointer: *const u8) -> bool { 37 | let start = self.start as usize; 38 | let end = start + self.byte_count(); 39 | let pointer = pointer as usize; 40 | pointer >= start && pointer < end 41 | } 42 | 43 | #[allow(unused)] 44 | fn copy_data_to_offset(&mut self, data: &[u8]) -> isize { 45 | unsafe { 46 | let start = self.start.add(self.allocation_offset); 47 | let new_pointer = start as isize; 48 | self.allocation_offset += data.len(); 49 | if self.allocation_offset % 8 != 0 { 50 | panic!("Heap offset is not aligned"); 51 | } 52 | std::ptr::copy_nonoverlapping(data.as_ptr(), start as *mut u8, data.len()); 53 | new_pointer 54 | } 55 | } 56 | 57 | fn write_object(&mut self, offset: usize, size: Word) -> *const u8 { 58 | let mut heap_object = HeapObject::from_untagged(unsafe { self.start.add(offset) }); 59 | 60 | assert!(self.contains(heap_object.get_pointer())); 61 | heap_object.write_header(size); 62 | 63 | heap_object.get_pointer() 64 | } 65 | 66 | fn allocate(&mut self, size: Word) -> *const u8 { 67 | let offset = self.allocation_offset; 68 | let full_size = size.to_bytes() + HeapObject::header_size(); 69 | let pointer = self.write_object(offset, size); 70 | self.increment_current_offset(full_size); 71 | pointer 72 | } 73 | 74 | fn increment_current_offset(&mut self, size: usize) { 75 | self.allocation_offset += size; 76 | } 77 | 78 | fn clear(&mut self) { 79 | self.allocation_offset = 0; 80 | } 81 | 82 | fn new(default_page_count: usize) -> Self { 83 | let pre_allocated_space = unsafe { 84 | libc::mmap( 85 | std::ptr::null_mut(), 86 | vm_page_size * MAX_PAGE_COUNT, 87 | libc::PROT_NONE, 88 | libc::MAP_PRIVATE | libc::MAP_ANONYMOUS, 89 | -1, 90 | 0, 91 | ) 92 | }; 93 | Self::commit_memory( 94 | pre_allocated_space, 95 | default_page_count * unsafe { vm_page_size }, 96 | ) 97 | .unwrap(); 98 | Self { 99 | start: pre_allocated_space as *const u8, 100 | page_count: default_page_count, 101 | allocation_offset: 0, 102 | } 103 | } 104 | 105 | fn commit_memory(addr: *mut c_void, size: usize) -> Result<(), io::Error> { 106 | unsafe { 107 | if mprotect(addr, size, libc::PROT_READ | libc::PROT_WRITE) != 0 { 108 | Err(io::Error::last_os_error()) 109 | } else { 110 | Ok(()) 111 | } 112 | } 113 | } 114 | 115 | #[allow(unused)] 116 | fn double_committed_memory(&mut self) { 117 | let new_page_count = self.page_count * 2; 118 | Self::commit_memory( 119 | self.start as *mut c_void, 120 | new_page_count * unsafe { vm_page_size }, 121 | ) 122 | .unwrap(); 123 | self.page_count = new_page_count; 124 | } 125 | 126 | fn can_allocate(&self, size: Word) -> bool { 127 | let size = size.to_bytes() + HeapObject::header_size(); 128 | let new_offset = self.allocation_offset + size; 129 | if new_offset > self.byte_count() { 130 | return false; 131 | } 132 | true 133 | } 134 | } 135 | 136 | pub struct GenerationV2 { 137 | young: Space, 138 | old: MarkAndSweepV2, 139 | copied: Vec, 140 | gc_count: usize, 141 | full_gc_frequency: usize, 142 | // TODO: This may not be the most efficient way 143 | // but given the way I'm dealing with mutability 144 | // right now it should work fine. 145 | // There should be very few atoms 146 | // But I will probably want to revist this 147 | additional_roots: Vec, 148 | namespace_roots: Vec<(usize, usize)>, 149 | relocated_namespace_roots: Vec<(usize, Vec<(usize, usize)>)>, 150 | temporary_roots: Vec>, 151 | atomic_pause: [u8; 8], 152 | options: AllocatorOptions, 153 | } 154 | 155 | impl Allocator for GenerationV2 { 156 | fn new(options: AllocatorOptions) -> Self { 157 | let young = Space::new(DEFAULT_PAGE_COUNT * 10); 158 | let old = MarkAndSweepV2::new_with_page_count(DEFAULT_PAGE_COUNT * 100, options); 159 | Self { 160 | young, 161 | old, 162 | copied: vec![], 163 | gc_count: 0, 164 | full_gc_frequency: 100, 165 | additional_roots: vec![], 166 | namespace_roots: vec![], 167 | relocated_namespace_roots: vec![], 168 | temporary_roots: vec![], 169 | atomic_pause: [0; 8], 170 | options, 171 | } 172 | } 173 | 174 | fn try_allocate( 175 | &mut self, 176 | words: usize, 177 | kind: BuiltInTypes, 178 | ) -> Result> { 179 | let pointer = self.allocate_inner(words, kind)?; 180 | Ok(pointer) 181 | } 182 | 183 | fn gc(&mut self, stack_map: &super::StackMap, stack_pointers: &[(usize, usize)]) { 184 | // TODO: Need to figure out when to do a Major GC 185 | if !self.options.gc { 186 | return; 187 | } 188 | if self.gc_count % self.full_gc_frequency == 0 { 189 | self.gc_count = 0; 190 | self.full_gc(stack_map, stack_pointers); 191 | } else { 192 | self.minor_gc(stack_map, stack_pointers); 193 | } 194 | self.gc_count += 1; 195 | } 196 | 197 | fn grow(&mut self) { 198 | self.old.grow(); 199 | } 200 | 201 | fn gc_add_root(&mut self, old: usize) { 202 | self.additional_roots.push(old); 203 | } 204 | 205 | #[allow(unused)] 206 | fn get_pause_pointer(&self) -> usize { 207 | self.atomic_pause.as_ptr() as usize 208 | } 209 | 210 | fn add_namespace_root(&mut self, namespace_id: usize, root: usize) { 211 | self.namespace_roots.push((namespace_id, root)); 212 | } 213 | 214 | fn get_namespace_relocations(&mut self) -> Vec<(usize, Vec<(usize, usize)>)> { 215 | std::mem::take(&mut self.relocated_namespace_roots) 216 | } 217 | 218 | fn get_allocation_options(&self) -> AllocatorOptions { 219 | self.options 220 | } 221 | 222 | fn register_temporary_root(&mut self, root: usize) -> usize { 223 | debug_assert!(self.temporary_roots.len() < 10, "Too many temporary roots"); 224 | for (i, temp_root) in self.temporary_roots.iter_mut().enumerate() { 225 | if temp_root.is_none() { 226 | *temp_root = Some(root); 227 | return i; 228 | } 229 | } 230 | self.temporary_roots.push(Some(root)); 231 | self.temporary_roots.len() - 1 232 | } 233 | 234 | fn unregister_temporary_root(&mut self, id: usize) -> usize { 235 | let value = self.temporary_roots[id]; 236 | self.temporary_roots[id] = None; 237 | value.unwrap() 238 | } 239 | } 240 | 241 | impl GenerationV2 { 242 | fn allocate_inner( 243 | &mut self, 244 | words: usize, 245 | _kind: BuiltInTypes, 246 | ) -> Result> { 247 | let size = Word::from_word(words); 248 | if self.young.can_allocate(size) { 249 | Ok(AllocateAction::Allocated(self.young.allocate(size))) 250 | } else { 251 | Ok(AllocateAction::Gc) 252 | } 253 | } 254 | 255 | fn get_live_stack<'a>(stack_base: usize, stack_pointer: usize) -> &'a mut [usize] { 256 | let stack_end = stack_base; 257 | // let current_stack_pointer = current_stack_pointer & !0b111; 258 | let distance_till_end = stack_end - stack_pointer; 259 | let num_64_till_end = (distance_till_end / 8) + 1; 260 | let len = STACK_SIZE / 8; 261 | let stack_begin = stack_end - STACK_SIZE; 262 | let stack = 263 | unsafe { std::slice::from_raw_parts_mut(stack_begin as *mut usize, STACK_SIZE / 8) }; 264 | 265 | (&mut stack[len - num_64_till_end..]) as _ 266 | } 267 | 268 | fn minor_gc(&mut self, stack_map: &StackMap, stack_pointers: &[(usize, usize)]) { 269 | // TODO: I don't deal with temporary roots updating 270 | unsafe { self.copy_all(self.temporary_roots.iter().flatten().cloned().collect()) }; 271 | 272 | let additional_roots = std::mem::take(&mut self.additional_roots); 273 | for old in additional_roots.into_iter() { 274 | self.move_objects_referenced_from_old_to_old(&mut HeapObject::from_tagged(old)); 275 | } 276 | 277 | let namespace_roots = std::mem::take(&mut self.namespace_roots); 278 | // There has to be a better answer than this. But it does seem to work. 279 | for (namespace_id, root) in namespace_roots.into_iter() { 280 | if !BuiltInTypes::is_heap_pointer(root) { 281 | continue; 282 | } 283 | let mut heap_object = HeapObject::from_tagged(root); 284 | if self.young.contains(heap_object.get_pointer()) && heap_object.marked() { 285 | // We have already copied this object, so the first field points to the new location 286 | let new_pointer = heap_object.get_field(0); 287 | self.namespace_roots.push((namespace_id, new_pointer)); 288 | self.relocated_namespace_roots 289 | .push((namespace_id, vec![(root, new_pointer)])); 290 | } else if self.young.contains(heap_object.get_pointer()) { 291 | let new_pointer = unsafe { self.copy(root) }; 292 | self.relocated_namespace_roots 293 | .push((namespace_id, vec![(root, new_pointer)])); 294 | self.namespace_roots.push((namespace_id, new_pointer)); 295 | self.move_objects_referenced_from_old_to_old(&mut HeapObject::from_tagged( 296 | new_pointer, 297 | )); 298 | } else { 299 | self.namespace_roots.push((namespace_id, root)); 300 | self.move_objects_referenced_from_old_to_old(&mut heap_object); 301 | } 302 | } 303 | 304 | self.old.clear_namespace_roots(); 305 | for (namespace_id, root) in self.namespace_roots.iter() { 306 | self.old.add_namespace_root(*namespace_id, *root); 307 | } 308 | 309 | let start = std::time::Instant::now(); 310 | for (stack_base, stack_pointer) in stack_pointers.iter() { 311 | let roots = self.gather_roots(*stack_base, stack_map, *stack_pointer); 312 | let new_roots: Vec = roots.iter().map(|x| x.1).collect(); 313 | let new_roots = unsafe { self.copy_all(new_roots) }; 314 | 315 | self.copy_remaining(); 316 | 317 | let stack_buffer = Self::get_live_stack(*stack_base, *stack_pointer); 318 | for (i, (stack_offset, _)) in roots.iter().enumerate() { 319 | debug_assert!( 320 | BuiltInTypes::untag(new_roots[i]) % 8 == 0, 321 | "Pointer is not aligned" 322 | ); 323 | stack_buffer[*stack_offset] = new_roots[i]; 324 | } 325 | } 326 | self.young.clear(); 327 | if self.options.print_stats { 328 | println!("Minor GC took {:?}", start.elapsed()); 329 | } 330 | } 331 | 332 | fn full_gc(&mut self, stack_map: &super::StackMap, stack_pointers: &[(usize, usize)]) { 333 | self.minor_gc(stack_map, stack_pointers); 334 | self.old.gc(stack_map, stack_pointers); 335 | } 336 | 337 | unsafe fn copy_all(&mut self, roots: Vec) -> Vec { 338 | unsafe { 339 | let mut new_roots = vec![]; 340 | for root in roots.iter() { 341 | new_roots.push(self.copy(*root)); 342 | } 343 | 344 | self.copy_remaining(); 345 | 346 | new_roots 347 | } 348 | } 349 | 350 | fn copy_remaining(&mut self) { 351 | while let Some(mut object) = self.copied.pop() { 352 | if object.marked() { 353 | panic!("We are copying to this space, nothing should be marked"); 354 | } 355 | 356 | for datum in object.get_fields_mut() { 357 | if BuiltInTypes::is_heap_pointer(*datum) { 358 | *datum = unsafe { self.copy(*datum) }; 359 | } 360 | } 361 | } 362 | } 363 | 364 | unsafe fn copy(&mut self, root: usize) -> usize { 365 | unsafe { 366 | let heap_object = HeapObject::from_tagged(root); 367 | 368 | if !self.young.contains(heap_object.get_pointer()) { 369 | return root; 370 | } 371 | 372 | // if it is marked we have already copied it 373 | // We now know that the first field is a pointer 374 | if heap_object.marked() { 375 | let first_field = heap_object.get_field(0); 376 | assert!(BuiltInTypes::is_heap_pointer(first_field)); 377 | assert!( 378 | !self 379 | .young 380 | .contains(BuiltInTypes::untag(first_field) as *const u8) 381 | ); 382 | return first_field; 383 | } 384 | 385 | let data = heap_object.get_full_object_data(); 386 | let new_pointer = self.old.copy_data_to_offset(data); 387 | debug_assert!(new_pointer as usize % 8 == 0, "Pointer is not aligned"); 388 | // update header of original object to now be the forwarding pointer 389 | let tagged_new = BuiltInTypes::get_kind(root).tag(new_pointer as isize) as usize; 390 | 391 | if heap_object.is_zero_size() || heap_object.is_opaque_object() { 392 | return tagged_new; 393 | } 394 | let first_field = heap_object.get_field(0); 395 | if let Some(heap_object) = HeapObject::try_from_tagged(first_field) { 396 | if self.young.contains(heap_object.get_pointer()) { 397 | self.copy(first_field); 398 | } 399 | } 400 | 401 | heap_object.write_field(0, tagged_new); 402 | heap_object.mark(); 403 | self.copied.push(HeapObject::from_untagged(new_pointer)); 404 | tagged_new 405 | } 406 | } 407 | 408 | fn move_objects_referenced_from_old_to_old(&mut self, old_object: &mut HeapObject) { 409 | if self.young.contains(old_object.get_pointer()) { 410 | return; 411 | } 412 | let data = old_object.get_fields_mut(); 413 | for datum in data.iter_mut() { 414 | if BuiltInTypes::is_heap_pointer(*datum) { 415 | let untagged = BuiltInTypes::untag(*datum); 416 | if !self.young.contains(untagged as *const u8) { 417 | continue; 418 | } 419 | let new_pointer = unsafe { self.copy(*datum) }; 420 | *datum = new_pointer; 421 | } 422 | } 423 | } 424 | 425 | // Stolen from simple mark and sweep 426 | pub fn gather_roots( 427 | &mut self, 428 | stack_base: usize, 429 | stack_map: &StackMap, 430 | stack_pointer: usize, 431 | ) -> Vec<(usize, usize)> { 432 | // I'm adding to the end of the stack I've allocated so I only need to go from the end 433 | // til the current stack 434 | let stack = Self::get_live_stack(stack_base, stack_pointer); 435 | 436 | let mut roots: Vec<(usize, usize)> = Vec::with_capacity(36); 437 | 438 | let mut i = 0; 439 | while i < stack.len() { 440 | let value = stack[i]; 441 | 442 | if let Some(details) = stack_map.find_stack_data(value) { 443 | let mut frame_size = details.max_stack_size + details.number_of_locals; 444 | if frame_size % 2 != 0 { 445 | frame_size += 1; 446 | } 447 | 448 | let bottom_of_frame = i + frame_size + 1; 449 | let _top_of_frame = i + 1; 450 | 451 | let active_frame = details.current_stack_size + details.number_of_locals; 452 | 453 | i = bottom_of_frame; 454 | 455 | for (j, slot) in stack 456 | .iter() 457 | .enumerate() 458 | .take(bottom_of_frame) 459 | .skip(bottom_of_frame - active_frame) 460 | { 461 | if BuiltInTypes::is_heap_pointer(*slot) { 462 | let untagged = BuiltInTypes::untag(*slot); 463 | debug_assert!(untagged % 8 == 0, "Pointer is not aligned"); 464 | if !self.young.contains(untagged as *const u8) { 465 | continue; 466 | } 467 | roots.push((j, *slot)); 468 | } 469 | } 470 | continue; 471 | } 472 | i += 1; 473 | } 474 | 475 | roots 476 | } 477 | } 478 | -------------------------------------------------------------------------------- /src/gc/mark_and_sweep_v2.rs: -------------------------------------------------------------------------------- 1 | use std::{error::Error, ffi::c_void, io}; 2 | 3 | use libc::{mprotect, vm_page_size}; 4 | 5 | use crate::types::{BuiltInTypes, HeapObject, Word}; 6 | 7 | use super::{AllocateAction, Allocator, AllocatorOptions, STACK_SIZE, StackMap}; 8 | 9 | const DEFAULT_PAGE_COUNT: usize = 1024; 10 | // Aribtary number that should be changed when I have 11 | // better options for gc 12 | const MAX_PAGE_COUNT: usize = 1000000; 13 | 14 | struct Space { 15 | start: *const u8, 16 | page_count: usize, 17 | highmark: usize, 18 | #[allow(unused)] 19 | protected: bool, 20 | } 21 | 22 | unsafe impl Send for Space {} 23 | unsafe impl Sync for Space {} 24 | 25 | impl Space { 26 | #[allow(unused)] 27 | fn word_count(&self) -> usize { 28 | (self.page_count * unsafe { vm_page_size }) / 8 29 | } 30 | 31 | fn byte_count(&self) -> usize { 32 | self.page_count * unsafe { vm_page_size } 33 | } 34 | 35 | fn contains(&self, pointer: *const u8) -> bool { 36 | let start = self.start as usize; 37 | let end = start + self.byte_count(); 38 | let pointer = pointer as usize; 39 | pointer >= start && pointer < end 40 | } 41 | 42 | fn copy_data_to_offset(&mut self, offset: usize, data: &[u8]) -> isize { 43 | unsafe { 44 | let start = self.start.add(offset); 45 | let new_pointer = start as isize; 46 | std::ptr::copy_nonoverlapping(data.as_ptr(), start as *mut u8, data.len()); 47 | new_pointer 48 | } 49 | } 50 | 51 | fn write_object(&mut self, offset: usize, size: Word) -> *const u8 { 52 | let mut heap_object = HeapObject::from_untagged(unsafe { self.start.add(offset) }); 53 | 54 | assert!(self.contains(heap_object.get_pointer())); 55 | heap_object.write_header(size); 56 | 57 | heap_object.get_pointer() 58 | } 59 | 60 | #[allow(unused)] 61 | fn protect(&mut self) { 62 | unsafe { 63 | mprotect( 64 | self.start as *mut _, 65 | self.byte_count() - 1024, 66 | libc::PROT_NONE, 67 | ) 68 | }; 69 | self.protected = true; 70 | } 71 | 72 | #[allow(unused)] 73 | fn unprotect(&mut self) { 74 | unsafe { 75 | mprotect( 76 | self.start as *mut _, 77 | self.byte_count() - 1024, 78 | libc::PROT_READ | libc::PROT_WRITE, 79 | ) 80 | }; 81 | 82 | self.protected = false; 83 | } 84 | 85 | fn new(default_page_count: usize) -> Self { 86 | let pre_allocated_space = unsafe { 87 | libc::mmap( 88 | std::ptr::null_mut(), 89 | vm_page_size * MAX_PAGE_COUNT, 90 | libc::PROT_NONE, 91 | libc::MAP_PRIVATE | libc::MAP_ANONYMOUS, 92 | -1, 93 | 0, 94 | ) 95 | }; 96 | 97 | Self::commit_memory( 98 | pre_allocated_space, 99 | default_page_count * unsafe { vm_page_size }, 100 | ) 101 | .unwrap(); 102 | 103 | Self { 104 | start: pre_allocated_space as *const u8, 105 | page_count: default_page_count, 106 | highmark: 0, 107 | protected: false, 108 | } 109 | } 110 | 111 | fn commit_memory(addr: *mut c_void, size: usize) -> Result<(), io::Error> { 112 | unsafe { 113 | if mprotect(addr, size, libc::PROT_READ | libc::PROT_WRITE) != 0 { 114 | Err(io::Error::last_os_error()) 115 | } else { 116 | Ok(()) 117 | } 118 | } 119 | } 120 | 121 | fn double_committed_memory(&mut self) { 122 | let new_page_count = self.page_count * 2; 123 | Self::commit_memory( 124 | self.start as *mut c_void, 125 | new_page_count * unsafe { vm_page_size }, 126 | ) 127 | .unwrap(); 128 | self.page_count = new_page_count; 129 | } 130 | 131 | fn update_highmark(&mut self, highmark: usize) { 132 | if highmark > self.highmark { 133 | self.highmark = highmark; 134 | } 135 | } 136 | } 137 | 138 | #[derive(Copy, Clone, PartialEq, Eq, Debug)] 139 | struct FreeListEntry { 140 | offset: usize, 141 | size: usize, 142 | } 143 | 144 | impl FreeListEntry { 145 | pub fn end(&self) -> usize { 146 | self.offset + self.size 147 | } 148 | 149 | pub fn can_hold(&self, size: usize) -> bool { 150 | self.size >= size 151 | } 152 | 153 | pub fn contains(&self, offset: usize) -> bool { 154 | self.offset <= offset && offset < self.end() 155 | } 156 | } 157 | 158 | pub struct FreeList { 159 | ranges: Vec, // always sorted by start 160 | } 161 | 162 | impl FreeList { 163 | fn new(starting_range: FreeListEntry) -> Self { 164 | FreeList { 165 | ranges: vec![starting_range], 166 | } 167 | } 168 | 169 | fn insert(&mut self, range: FreeListEntry) { 170 | let mut i = match self 171 | .ranges 172 | .binary_search_by_key(&range.offset, |r| r.offset) 173 | { 174 | Ok(i) | Err(i) => i, 175 | }; 176 | 177 | // Coalesce with previous if adjacent 178 | if i > 0 && self.ranges[i - 1].end() == range.offset { 179 | i -= 1; 180 | self.ranges[i].size += range.size; 181 | } else { 182 | self.ranges.insert(i, range); 183 | } 184 | 185 | // Coalesce with next if adjacent 186 | if i + 1 < self.ranges.len() && self.ranges[i].end() == self.ranges[i + 1].offset { 187 | self.ranges[i].size += self.ranges[i + 1].size; 188 | self.ranges.remove(i + 1); 189 | } 190 | } 191 | 192 | fn allocate(&mut self, size: usize) -> Option { 193 | for (i, r) in self.ranges.iter_mut().enumerate() { 194 | if r.can_hold(size) { 195 | let addr = r.offset; 196 | if addr % 8 != 0 { 197 | panic!("Heap offset is not aligned"); 198 | } 199 | r.offset += size; 200 | r.size -= size; 201 | 202 | if r.size == 0 { 203 | self.ranges.remove(i); 204 | } 205 | 206 | return Some(addr); 207 | } 208 | } 209 | None 210 | } 211 | 212 | fn iter(&self) -> impl Iterator { 213 | self.ranges.iter() 214 | } 215 | 216 | fn find_entry_contains(&self, offset: usize) -> Option<&FreeListEntry> { 217 | self.ranges.iter().find(|&entry| entry.contains(offset)) 218 | } 219 | } 220 | 221 | pub struct MarkAndSweepV2 { 222 | space: Space, 223 | free_list: FreeList, 224 | namespace_roots: Vec<(usize, usize)>, 225 | options: AllocatorOptions, 226 | temporary_roots: Vec>, 227 | } 228 | 229 | // TODO: I got an issue with my freelist 230 | impl MarkAndSweepV2 { 231 | fn can_allocate(&self, words: usize) -> bool { 232 | let words = Word::from_word(words); 233 | let size = words.to_bytes() + HeapObject::header_size(); 234 | let spot = self 235 | .free_list 236 | .iter() 237 | .enumerate() 238 | .find(|(_, x)| x.size >= size); 239 | spot.is_some() 240 | } 241 | 242 | fn allocate_inner( 243 | &mut self, 244 | words: Word, 245 | data: Option<&[u8]>, 246 | ) -> Result> { 247 | let size_bytes = words.to_bytes() + HeapObject::header_size(); 248 | 249 | let offset = self.free_list.allocate(size_bytes); 250 | if let Some(offset) = offset { 251 | self.space.update_highmark(offset); 252 | let pointer = self.space.write_object(offset, words); 253 | if let Some(data) = data { 254 | self.space.copy_data_to_offset(offset, data); 255 | } 256 | return Ok(AllocateAction::Allocated(pointer)); 257 | } 258 | 259 | Ok(AllocateAction::Gc) 260 | } 261 | 262 | #[allow(unused)] 263 | pub fn copy_data_to_offset(&mut self, data: &[u8]) -> *const u8 { 264 | // TODO: I could amortize this by copying lazily and coalescing 265 | // the copies together if they are continuouss 266 | let pointer = self 267 | .allocate_inner(Word::from_bytes(data.len() - 8), Some(data)) 268 | .unwrap(); 269 | 270 | if let AllocateAction::Allocated(pointer) = pointer { 271 | pointer 272 | } else { 273 | self.grow(); 274 | self.copy_data_to_offset(data) 275 | } 276 | } 277 | 278 | fn mark_and_sweep(&mut self, stack_map: &StackMap, stack_pointers: &[(usize, usize)]) { 279 | let start = std::time::Instant::now(); 280 | for (stack_base, stack_pointer) in stack_pointers { 281 | self.mark(*stack_base, stack_map, *stack_pointer); 282 | } 283 | self.sweep(); 284 | if self.options.print_stats { 285 | println!("Mark and sweep took {:?}", start.elapsed()); 286 | } 287 | } 288 | 289 | fn mark(&self, stack_base: usize, stack_map: &super::StackMap, stack_pointer: usize) { 290 | // I'm adding to the end of the stack I've allocated so I only need to go from the end 291 | // til the current stack 292 | let stack_end = stack_base; 293 | // let current_stack_pointer = current_stack_pointer & !0b111; 294 | let distance_till_end = stack_end - stack_pointer; 295 | let num_64_till_end = (distance_till_end / 8) + 1; 296 | let stack_begin = stack_end - STACK_SIZE; 297 | let stack = 298 | unsafe { std::slice::from_raw_parts(stack_begin as *const usize, STACK_SIZE / 8) }; 299 | let stack = &stack[stack.len() - num_64_till_end..]; 300 | 301 | let mut to_mark: Vec = Vec::with_capacity(128); 302 | 303 | for (_, root) in self.namespace_roots.iter() { 304 | if !BuiltInTypes::is_heap_pointer(*root) { 305 | continue; 306 | } 307 | to_mark.push(HeapObject::from_tagged(*root)); 308 | } 309 | 310 | let mut i = 0; 311 | while i < stack.len() { 312 | let value = stack[i]; 313 | 314 | if let Some(details) = stack_map.find_stack_data(value) { 315 | let mut frame_size = details.max_stack_size + details.number_of_locals; 316 | if frame_size % 2 != 0 { 317 | frame_size += 1; 318 | } 319 | 320 | let bottom_of_frame = i + frame_size + 1; 321 | let _top_of_frame = i + 1; 322 | 323 | let active_frame = details.current_stack_size + details.number_of_locals; 324 | 325 | i = bottom_of_frame; 326 | 327 | for slot in stack 328 | .iter() 329 | .take(bottom_of_frame) 330 | .skip(bottom_of_frame - active_frame) 331 | { 332 | if BuiltInTypes::is_heap_pointer(*slot) { 333 | // println!("{} {}", slot, BuiltInTypes::untag(*slot)); 334 | let untagged = BuiltInTypes::untag(*slot); 335 | if untagged % 8 != 0 { 336 | panic!("Not aligned"); 337 | } 338 | to_mark.push(HeapObject::from_tagged(*slot)); 339 | } 340 | } 341 | continue; 342 | } 343 | i += 1; 344 | } 345 | 346 | while let Some(object) = to_mark.pop() { 347 | if object.marked() { 348 | continue; 349 | } 350 | 351 | object.mark(); 352 | for object in object.get_heap_references() { 353 | to_mark.push(object); 354 | } 355 | } 356 | } 357 | 358 | fn sweep(&mut self) { 359 | let mut offset = 0; 360 | 361 | loop { 362 | if offset > self.space.highmark { 363 | break; 364 | } 365 | if let Some(entry) = self.free_list.find_entry_contains(offset) { 366 | offset = entry.end(); 367 | continue; 368 | } 369 | let heap_object = HeapObject::from_untagged(unsafe { self.space.start.add(offset) }); 370 | 371 | if heap_object.marked() { 372 | heap_object.unmark(); 373 | offset += heap_object.full_size(); 374 | offset = (offset + 7) & !7; 375 | continue; 376 | } 377 | let size = heap_object.full_size(); 378 | let entry = FreeListEntry { offset, size }; 379 | self.free_list.insert(entry); 380 | offset += size; 381 | offset = (offset + 7) & !7; 382 | if offset % 8 != 0 { 383 | panic!("Heap offset is not aligned"); 384 | } 385 | 386 | if offset > self.space.byte_count() { 387 | panic!("Heap offset is out of bounds"); 388 | } 389 | } 390 | } 391 | 392 | #[allow(unused)] 393 | pub fn clear_namespace_roots(&mut self) { 394 | self.namespace_roots.clear(); 395 | } 396 | 397 | pub fn new_with_page_count(page_count: usize, options: AllocatorOptions) -> Self { 398 | let space = Space::new(page_count); 399 | let size = space.byte_count(); 400 | Self { 401 | space, 402 | free_list: FreeList::new(FreeListEntry { offset: 0, size }), 403 | namespace_roots: vec![], 404 | options, 405 | temporary_roots: vec![], 406 | } 407 | } 408 | } 409 | 410 | impl Allocator for MarkAndSweepV2 { 411 | fn new(options: AllocatorOptions) -> Self { 412 | let page_count = DEFAULT_PAGE_COUNT; 413 | Self::new_with_page_count(page_count, options) 414 | } 415 | 416 | fn try_allocate( 417 | &mut self, 418 | words: usize, 419 | _kind: crate::types::BuiltInTypes, 420 | ) -> Result> { 421 | if self.can_allocate(words) { 422 | self.allocate_inner(Word::from_word(words), None) 423 | } else { 424 | Ok(AllocateAction::Gc) 425 | } 426 | } 427 | 428 | fn gc(&mut self, stack_map: &StackMap, stack_pointers: &[(usize, usize)]) { 429 | self.mark_and_sweep(stack_map, stack_pointers); 430 | } 431 | 432 | fn grow(&mut self) { 433 | let current_max_offset = self.space.byte_count(); 434 | self.space.double_committed_memory(); 435 | let after_max_offset = self.space.byte_count(); 436 | self.free_list.insert(FreeListEntry { 437 | offset: current_max_offset, 438 | size: after_max_offset - current_max_offset, 439 | }); 440 | } 441 | 442 | fn gc_add_root(&mut self, _old: usize) {} 443 | 444 | fn add_namespace_root(&mut self, namespace_id: usize, root: usize) { 445 | self.namespace_roots.push((namespace_id, root)); 446 | } 447 | 448 | fn register_temporary_root(&mut self, root: usize) -> usize { 449 | debug_assert!(self.temporary_roots.len() < 10, "Too many temporary roots"); 450 | for (i, temp_root) in self.temporary_roots.iter_mut().enumerate() { 451 | if temp_root.is_none() { 452 | *temp_root = Some(root); 453 | return i; 454 | } 455 | } 456 | self.temporary_roots.push(Some(root)); 457 | self.temporary_roots.len() - 1 458 | } 459 | 460 | fn unregister_temporary_root(&mut self, id: usize) -> usize { 461 | let value = self.temporary_roots[id]; 462 | self.temporary_roots[id] = None; 463 | value.unwrap() 464 | } 465 | 466 | fn get_namespace_relocations(&mut self) -> Vec<(usize, Vec<(usize, usize)>)> { 467 | // This mark and sweep doesn't relocate 468 | // so we don't have any relocations 469 | vec![] 470 | } 471 | 472 | fn get_allocation_options(&self) -> AllocatorOptions { 473 | self.options 474 | } 475 | } 476 | -------------------------------------------------------------------------------- /src/gc/mod.rs: -------------------------------------------------------------------------------- 1 | use std::{error::Error, thread::ThreadId}; 2 | 3 | use bincode::{Decode, Encode}; 4 | use nanoserde::SerJson; 5 | 6 | use crate::{CommandLineArguments, types::BuiltInTypes}; 7 | 8 | pub mod compacting; 9 | pub mod compacting_v2; 10 | pub mod generation_v2; 11 | pub mod mark_and_sweep_v2; 12 | pub mod mutex_allocator; 13 | pub mod simple_generation; 14 | pub mod simple_mark_and_sweep; 15 | 16 | #[derive(Debug, Encode, Decode, SerJson, Clone)] 17 | pub struct StackMapDetails { 18 | pub function_name: Option, 19 | pub number_of_locals: usize, 20 | pub current_stack_size: usize, 21 | pub max_stack_size: usize, 22 | } 23 | 24 | pub const STACK_SIZE: usize = 1024 * 1024 * 128; 25 | 26 | #[derive(Debug, Clone)] 27 | pub struct StackMap { 28 | details: Vec<(usize, StackMapDetails)>, 29 | } 30 | 31 | impl Default for StackMap { 32 | fn default() -> Self { 33 | Self::new() 34 | } 35 | } 36 | 37 | impl StackMap { 38 | pub fn new() -> Self { 39 | Self { details: vec![] } 40 | } 41 | 42 | pub fn find_stack_data(&self, pointer: usize) -> Option<&StackMapDetails> { 43 | for (key, value) in self.details.iter() { 44 | if *key == pointer.saturating_sub(4) { 45 | return Some(value); 46 | } 47 | } 48 | None 49 | } 50 | 51 | pub fn extend(&mut self, translated_stack_map: Vec<(usize, StackMapDetails)>) { 52 | self.details.extend(translated_stack_map); 53 | } 54 | } 55 | 56 | #[derive(Debug, Clone, Copy)] 57 | pub struct AllocatorOptions { 58 | pub gc: bool, 59 | pub print_stats: bool, 60 | pub gc_always: bool, 61 | } 62 | 63 | pub enum AllocateAction { 64 | Allocated(*const u8), 65 | Gc, 66 | } 67 | 68 | pub fn get_allocate_options(command_line_arguments: &CommandLineArguments) -> AllocatorOptions { 69 | AllocatorOptions { 70 | gc: !command_line_arguments.no_gc, 71 | print_stats: command_line_arguments.show_gc_times, 72 | gc_always: command_line_arguments.gc_always, 73 | } 74 | } 75 | 76 | pub trait Allocator { 77 | fn new(options: AllocatorOptions) -> Self; 78 | 79 | // TODO: I probably want something like kind, but not actually kind 80 | // I might need to allocate things differently based on type 81 | fn try_allocate( 82 | &mut self, 83 | words: usize, 84 | kind: BuiltInTypes, 85 | ) -> Result>; 86 | 87 | fn gc(&mut self, stack_map: &StackMap, stack_pointers: &[(usize, usize)]); 88 | 89 | fn grow(&mut self); 90 | fn gc_add_root(&mut self, old: usize); 91 | fn register_temporary_root(&mut self, root: usize) -> usize; 92 | fn unregister_temporary_root(&mut self, id: usize) -> usize; 93 | fn add_namespace_root(&mut self, namespace_id: usize, root: usize); 94 | // TODO: Get rid of allocation 95 | fn get_namespace_relocations(&mut self) -> Vec<(usize, Vec<(usize, usize)>)>; 96 | 97 | #[allow(unused)] 98 | fn get_pause_pointer(&self) -> usize { 99 | 0 100 | } 101 | 102 | fn register_thread(&mut self, _thread_id: ThreadId) {} 103 | 104 | fn remove_thread(&mut self, _thread_id: ThreadId) {} 105 | 106 | fn register_parked_thread(&mut self, _thread_id: ThreadId, _stack_pointer: usize) {} 107 | 108 | fn get_allocation_options(&self) -> AllocatorOptions; 109 | } 110 | -------------------------------------------------------------------------------- /src/gc/mutex_allocator.rs: -------------------------------------------------------------------------------- 1 | use std::{error::Error, sync::Mutex}; 2 | 3 | use crate::types::BuiltInTypes; 4 | 5 | use super::{AllocateAction, Allocator, AllocatorOptions, StackMap}; 6 | 7 | pub struct MutexAllocator { 8 | alloc: Alloc, 9 | mutex: Mutex<()>, 10 | options: AllocatorOptions, 11 | registered_threads: usize, 12 | } 13 | 14 | impl Allocator for MutexAllocator { 15 | fn new(options: AllocatorOptions) -> Self { 16 | MutexAllocator { 17 | alloc: Alloc::new(options), 18 | mutex: Mutex::new(()), 19 | options, 20 | registered_threads: 0, 21 | } 22 | } 23 | fn try_allocate( 24 | &mut self, 25 | bytes: usize, 26 | kind: BuiltInTypes, 27 | ) -> Result> { 28 | if self.registered_threads == 0 { 29 | return self.alloc.try_allocate(bytes, kind); 30 | } 31 | 32 | let lock = self.mutex.lock().unwrap(); 33 | let result = self.alloc.try_allocate(bytes, kind); 34 | drop(lock); 35 | result 36 | } 37 | 38 | fn gc(&mut self, stack_map: &StackMap, stack_pointers: &[(usize, usize)]) { 39 | if self.registered_threads == 0 { 40 | return self.alloc.gc(stack_map, stack_pointers); 41 | } 42 | let lock = self.mutex.lock().unwrap(); 43 | self.alloc.gc(stack_map, stack_pointers); 44 | drop(lock) 45 | } 46 | 47 | fn grow(&mut self) { 48 | if self.registered_threads == 0 { 49 | return self.alloc.grow(); 50 | } 51 | let lock = self.mutex.lock().unwrap(); 52 | self.alloc.grow(); 53 | drop(lock) 54 | } 55 | 56 | fn gc_add_root(&mut self, old: usize) { 57 | if self.registered_threads == 0 { 58 | return self.alloc.gc_add_root(old); 59 | } 60 | let lock = self.mutex.lock().unwrap(); 61 | self.alloc.gc_add_root(old); 62 | drop(lock) 63 | } 64 | 65 | fn register_temporary_root(&mut self, root: usize) -> usize { 66 | if self.registered_threads == 0 { 67 | return self.alloc.register_temporary_root(root); 68 | } 69 | let lock = self.mutex.lock().unwrap(); 70 | let result = self.alloc.register_temporary_root(root); 71 | drop(lock); 72 | result 73 | } 74 | 75 | fn unregister_temporary_root(&mut self, id: usize) -> usize { 76 | if self.registered_threads == 0 { 77 | return self.alloc.unregister_temporary_root(id); 78 | } 79 | let lock = self.mutex.lock().unwrap(); 80 | let result = self.alloc.unregister_temporary_root(id); 81 | drop(lock); 82 | result 83 | } 84 | 85 | fn add_namespace_root(&mut self, namespace_id: usize, root: usize) { 86 | if self.registered_threads == 0 { 87 | return self.alloc.add_namespace_root(namespace_id, root); 88 | } 89 | let lock = self.mutex.lock().unwrap(); 90 | self.alloc.add_namespace_root(namespace_id, root); 91 | drop(lock) 92 | } 93 | 94 | fn get_namespace_relocations(&mut self) -> Vec<(usize, Vec<(usize, usize)>)> { 95 | if self.registered_threads == 0 { 96 | return self.alloc.get_namespace_relocations(); 97 | } 98 | let lock = self.mutex.lock().unwrap(); 99 | let result = self.alloc.get_namespace_relocations(); 100 | drop(lock); 101 | result 102 | } 103 | 104 | fn get_allocation_options(&self) -> AllocatorOptions { 105 | self.options 106 | } 107 | 108 | fn register_thread(&mut self, _thread_id: std::thread::ThreadId) { 109 | self.registered_threads += 1; 110 | } 111 | 112 | fn remove_thread(&mut self, _thread_id: std::thread::ThreadId) { 113 | self.registered_threads -= 1; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/machine_code/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod arm_codegen; 2 | -------------------------------------------------------------------------------- /src/primitives.rs: -------------------------------------------------------------------------------- 1 | use std::ops::Deref; 2 | 3 | use crate::{ 4 | ast::{Ast, AstCompiler}, 5 | ir::{Condition, Value}, 6 | types::{BuiltInTypes, Header}, 7 | }; 8 | 9 | // TODO: I'd rather this be on Ir I think? 10 | impl AstCompiler<'_> { 11 | pub fn compile_inline_primitive_function(&mut self, name: &str, args: Vec) -> Value { 12 | match name { 13 | "beagle.primitive/deref" => { 14 | // self.ir.breakpoint(); 15 | let pointer = args[0]; 16 | let untagged = self.ir.untag(pointer); 17 | // TODO: I need a raw add that doesn't check for tags 18 | let offset = self.ir.add_int(untagged, Value::RawValue(8)); 19 | let reg = self.ir.volatile_register(); 20 | self.ir.atomic_load(reg.into(), offset) 21 | } 22 | "beagle.primitive/reset!" => { 23 | let pointer = args[0]; 24 | let untagged = self.ir.untag(pointer); 25 | // TODO: I need a raw add that doesn't check for tags 26 | let offset = self.ir.add_int(untagged, Value::RawValue(8)); 27 | let value = args[1]; 28 | self.call_builtin("beagle.builtin/gc_add_root", vec![pointer]); 29 | self.ir.atomic_store(offset, value); 30 | args[1] 31 | } 32 | "beagle.primitive/compare_and_swap!" => { 33 | // self.ir.breakpoint(); 34 | let pointer = args[0]; 35 | let untagged = self.ir.untag(pointer); 36 | let offset = self.ir.add_int(untagged, Value::RawValue(8)); 37 | let expected = args[1]; 38 | let new = args[2]; 39 | let expected_and_result = self.ir.assign_new_force(expected); 40 | self.ir 41 | .compare_and_swap(expected_and_result.into(), new, offset); 42 | // TODO: I should do a conditional move here instead of a jump 43 | let label = self.ir.label("compare_and_swap"); 44 | let result = self.ir.assign_new(Value::True); 45 | self.ir 46 | .jump_if(label, Condition::Equal, expected_and_result, expected); 47 | self.ir.assign(result, Value::False); 48 | self.ir.write_label(label); 49 | result.into() 50 | } 51 | "beagle.primitive/breakpoint!" => { 52 | self.ir.breakpoint(); 53 | Value::Null 54 | } 55 | "beagle.primitive/write_type_id" => { 56 | let pointer = args[0]; 57 | let pointer = self.ir.untag(pointer); 58 | let type_id = args[1]; 59 | let untagged_type_id = self.ir.untag(type_id); 60 | self.ir.write_type_id(pointer, untagged_type_id); 61 | Value::Null 62 | } 63 | "beagle.primitive/read_type_id" => { 64 | let pointer = args[0]; 65 | let pointer = self.ir.untag(pointer); 66 | let header = self.ir.load_from_heap(pointer, 0); 67 | // mask and shift so we get the size 68 | let size_offset = Header::type_id_offset(); 69 | let value = self 70 | .ir 71 | .shift_right_imm_raw(header, (size_offset * 8) as i32); 72 | 73 | let value = self.ir.and_imm(value, 0x0000_0000_0000_FFFF); 74 | self.ir.tag(value, BuiltInTypes::Int.get_tag()) 75 | } 76 | "beagle.primitive/read_struct_id" => { 77 | let pointer = args[0]; 78 | let pointer = self.ir.untag(pointer); 79 | self.ir.read_struct_id(pointer) 80 | } 81 | "beagle.primitive/write_field" => { 82 | // self.ir.breakpoint(); 83 | let pointer = args[0]; 84 | let untagged = self.ir.untag(pointer); 85 | let field = args[1]; 86 | let field = self.ir.add_int(field, Value::TaggedConstant(1)); 87 | let field = self.ir.mul(field, Value::RawValue(8)); 88 | // let untagged_field = self.ir.untag(field); 89 | let value = args[2]; 90 | self.ir.heap_store_with_reg_offset(untagged, value, field); 91 | self.call_builtin("beagle.builtin/gc_add_root", vec![pointer]); 92 | Value::Null 93 | } 94 | "beagle.primitive/read_field" => { 95 | let pointer = args[0]; 96 | let pointer = self.ir.untag(pointer); 97 | let field = args[1]; 98 | let field = self.ir.add_int(field, Value::TaggedConstant(1)); 99 | let field = self.ir.mul(field, Value::RawValue(8)); 100 | // self.ir.breakpoint(); 101 | self.ir.heap_load_with_reg_offset(pointer, field) 102 | } 103 | "beagle.primitive/breakpoint" => { 104 | self.ir.breakpoint(); 105 | Value::Null 106 | } 107 | "beagle.primitive/size" => { 108 | let pointer = args[0]; 109 | let pointer = self.ir.untag(pointer); 110 | let header = self.ir.load_from_heap(pointer, 0); 111 | // mask and shift so we get the size 112 | let size_offset = Header::size_offset(); 113 | let value = self.ir.shift_right_imm(header, (size_offset * 8) as i32); 114 | 115 | let value = self.ir.and_imm(value, 0x0000_0000_0000_FFFF); 116 | self.ir.tag(value, BuiltInTypes::Int.get_tag()) 117 | } 118 | "beagle.primitive/panic" => { 119 | let message = args[0]; 120 | // print the message then call throw_error 121 | self.call_builtin("beagle.core/_println", vec![message]); 122 | self.call_builtin("beagle.builtin/throw_error", vec![]); 123 | Value::Null 124 | } 125 | "beagle.primitive/is_object" => { 126 | let pointer = args[0]; 127 | // check the tag of the pointer 128 | let tag = self.ir.get_tag(pointer); 129 | let heap_object_tag = self 130 | .ir 131 | .assign_new(Value::RawValue(BuiltInTypes::HeapObject.get_tag() as usize)); 132 | self.ir 133 | .compare(tag, heap_object_tag.into(), Condition::Equal) 134 | } 135 | "beagle.primitive/is_string_constant" => { 136 | let pointer = args[0]; 137 | // check the tag of the pointer 138 | let tag = self.ir.get_tag(pointer); 139 | let heap_object_tag = self 140 | .ir 141 | .assign_new(Value::RawValue(BuiltInTypes::String.get_tag() as usize)); 142 | self.ir 143 | .compare(tag, heap_object_tag.into(), Condition::Equal) 144 | } 145 | "beagle.primitive/set!" => { 146 | let pointer = args[0]; 147 | let value = args[1]; 148 | self.ir.heap_store(pointer, value); 149 | Value::Null 150 | } 151 | _ => panic!("Unknown inline primitive function {}", name), 152 | } 153 | } 154 | 155 | pub fn should_not_evaluate_arguments(&self, name: &str) -> bool { 156 | if name == "beagle.primitive/set!" { 157 | return true; 158 | } 159 | false 160 | } 161 | 162 | pub fn compile_macro_like_primitive(&mut self, name: String, args: Vec) -> Value { 163 | if name != "beagle.primitive/set!" { 164 | panic!("Unknown macro-like primitive {}", name); 165 | } 166 | 167 | if args.len() != 2 { 168 | // TODO: Error handling properly 169 | panic!("set! expects 2 arguments, got {}", args.len()); 170 | } 171 | 172 | let property_access = &args[0]; 173 | if !matches!(property_access, Ast::PropertyAccess { .. }) { 174 | panic!("set! expects a property access as the first argument"); 175 | }; 176 | 177 | let Ast::PropertyAccess { 178 | object, 179 | property, 180 | token_range: _, 181 | } = property_access 182 | else { 183 | panic!("set! expects a property access as the first argument"); 184 | }; 185 | 186 | let object = object.deref(); 187 | if !matches!(object, Ast::Identifier { .. }) { 188 | panic!("set! expects an identifier as the first argument for now"); 189 | } 190 | 191 | let property = property.deref(); 192 | if !matches!(property, Ast::Identifier { .. }) { 193 | panic!("set! expects an identifier as the second argument for now"); 194 | } 195 | 196 | let object = self.call_compile(object); 197 | let value = self.call_compile(&args[1]); 198 | 199 | let object = self.ir.assign_new(object); 200 | self.call_builtin("beagle.builtin/gc_add_root", vec![object.into()]); 201 | let untagged_object = self.ir.untag(object.into()); 202 | // self.ir.breakpoint(); 203 | let struct_id = self.ir.read_struct_id(untagged_object); 204 | let property_location = Value::RawValue(self.compiler.add_property_lookup()); 205 | let property_location = self.ir.assign_new(property_location); 206 | let property_value = self.ir.load_from_heap(property_location.into(), 0); 207 | let result = self.ir.assign_new(0); 208 | 209 | let exit_property_access = self.ir.label("exit_property_access"); 210 | let slow_property_path = self.ir.label("slow_property_path"); 211 | // self.ir.jump(slow_property_path); 212 | self.ir.jump_if( 213 | slow_property_path, 214 | Condition::NotEqual, 215 | struct_id, 216 | property_value, 217 | ); 218 | 219 | let property_offset = self.ir.load_from_heap(property_location.into(), 1); 220 | self.ir 221 | .write_field_dynamic(untagged_object, property_offset, value); 222 | 223 | self.ir.jump(exit_property_access); 224 | 225 | self.ir.write_label(slow_property_path); 226 | let property = if let Ast::Identifier(name, _) = property { 227 | name.clone() 228 | } else { 229 | panic!("Expected identifier") 230 | }; 231 | let constant_ptr = self.string_constant(property); 232 | let constant_ptr = self.ir.assign_new(constant_ptr); 233 | let call_result = self.call_builtin( 234 | "beagle.builtin/write_field", 235 | vec![ 236 | object.into(), 237 | constant_ptr.into(), 238 | property_location.into(), 239 | value, 240 | ], 241 | ); 242 | 243 | self.ir.assign(result, call_result); 244 | 245 | self.ir.write_label(exit_property_access); 246 | 247 | Value::Null 248 | } 249 | } 250 | -------------------------------------------------------------------------------- /src/register_allocation/linear_scan.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | use std::collections::{BTreeMap, HashMap}; 3 | 4 | use crate::ir::{Instruction, Value, VirtualRegister}; 5 | 6 | pub struct LinearScan { 7 | pub lifetimes: HashMap, 8 | pub instructions: Vec, 9 | // Needs to be a btreemap so I iterate in a defined order 10 | // so that things are deterministic 11 | pub allocated_registers: BTreeMap, 12 | pub free_registers: Vec, 13 | pub location: HashMap, 14 | pub stack_slot: usize, 15 | pub max_registers: usize, 16 | } 17 | 18 | fn physical(index: usize) -> VirtualRegister { 19 | VirtualRegister { 20 | argument: None, 21 | index, 22 | volatile: true, 23 | is_physical: true, 24 | } 25 | } 26 | 27 | impl LinearScan { 28 | pub fn new(instructions: Vec, num_locals: usize) -> Self { 29 | let lifetimes = Self::get_register_lifetime(&instructions); 30 | let physical_registers: Vec = (19..=28).map(physical).collect(); 31 | let max_registers = physical_registers.len(); 32 | 33 | LinearScan { 34 | lifetimes, 35 | instructions, 36 | allocated_registers: BTreeMap::new(), 37 | free_registers: physical_registers, 38 | max_registers, 39 | location: HashMap::new(), 40 | stack_slot: num_locals, 41 | } 42 | } 43 | 44 | fn get_register_lifetime( 45 | instructions: &[Instruction], 46 | ) -> HashMap { 47 | let mut result: HashMap = HashMap::new(); 48 | for (index, instruction) in instructions.iter().enumerate().rev() { 49 | for register in instruction.get_registers() { 50 | if let Some((_start, end)) = result.get(®ister) { 51 | result.insert(register, (index, *end)); 52 | } else { 53 | result.insert(register, (index, index)); 54 | } 55 | } 56 | } 57 | result 58 | } 59 | 60 | // LinearScanRegisterAllocation 61 | // active <- {} 62 | // foreach live interval i, in order of increasing start point 63 | // ExpireOldIntervals(i) 64 | // if length(active) == R then 65 | // SpillAtInterval(i) 66 | // else 67 | // register[i] <- a register removed from pool of free registers 68 | // add i to active, sorted by increasing end point 69 | 70 | // ExpireOldIntervals(i) 71 | // foreach interval j in active, in order of increasing end point 72 | // if endpoint[j] >= startpoint[i] then # > が正しい気がする。 73 | // return 74 | // remove j from active 75 | // add register[j] to pool of free registers 76 | 77 | // SpillAtInterval(i) 78 | // spill <- last interval in active 79 | // if endpoint[spill] > endpoint[i] then 80 | // register[i] <- register[spill] 81 | // location[spill] <- new stack location 82 | // remove spill from active 83 | // add i to active, sorted by increasing end point 84 | // else 85 | // location[i] <- new stack location 86 | 87 | pub fn allocate(&mut self) { 88 | let mut intervals = self 89 | .lifetimes 90 | .iter() 91 | .map(|(register, (start, end))| (*start, *end, *register)) 92 | .collect::>(); 93 | 94 | intervals.sort_by_key(|(start, _, _)| *start); 95 | 96 | let mut active: Vec<(usize, usize, VirtualRegister)> = Vec::new(); 97 | for i in intervals.iter() { 98 | let (start, end, register) = i; 99 | self.expire_old_intervals(*i, &mut active); 100 | if active.len() == self.max_registers { 101 | self.spill_at_interval(*start, *end, *register, &mut active); 102 | } else { 103 | if register.argument.is_some() { 104 | let new_register = VirtualRegister { 105 | argument: register.argument, 106 | index: register.index, 107 | volatile: false, 108 | is_physical: true, 109 | }; 110 | self.allocated_registers.insert(*register, new_register); 111 | } else { 112 | let physical_register = self.free_registers.pop().unwrap(); 113 | self.allocated_registers 114 | .insert(*register, physical_register); 115 | } 116 | active.push(*i); 117 | active.sort_by_key(|(end, _, _)| *end); 118 | } 119 | } 120 | self.replace_spilled_registers_with_spill(); 121 | self.replace_virtual_with_allocated(); 122 | self.replace_calls_with_call_with_save(); 123 | } 124 | 125 | fn expire_old_intervals( 126 | &mut self, 127 | i: (usize, usize, VirtualRegister), 128 | active: &mut Vec<(usize, usize, VirtualRegister)>, 129 | ) { 130 | let mut active_copy = active.clone(); 131 | active_copy.sort_by_key(|(_, end, _)| *end); 132 | for j in active_copy.iter() { 133 | let (_, end, _) = j; 134 | if *end >= i.0 { 135 | return; 136 | } 137 | active.retain(|x| x != j); 138 | let register_to_free = *self.allocated_registers.get(&j.2).unwrap(); 139 | self.free_register(register_to_free); 140 | } 141 | } 142 | 143 | fn spill_at_interval( 144 | &mut self, 145 | _start: usize, 146 | end: usize, 147 | register: VirtualRegister, 148 | active: &mut Vec<(usize, usize, VirtualRegister)>, 149 | ) { 150 | let spill = *active.last().unwrap(); 151 | if spill.1 > end { 152 | let physical_register = *self.allocated_registers.get(&spill.2).unwrap(); 153 | self.allocated_registers.insert(register, physical_register); 154 | let stack_location = self.new_stack_location(); 155 | assert!(!self.location.contains_key(&spill.2)); 156 | self.location.insert(spill.2, stack_location); 157 | active.retain(|x| *x != spill); 158 | self.free_register(physical_register); 159 | active.sort_by_key(|(_, end, _)| *end); 160 | } else { 161 | assert!(!self.location.contains_key(®ister)); 162 | let stack_location = self.new_stack_location(); 163 | self.location.insert(register, stack_location); 164 | } 165 | } 166 | 167 | fn free_register(&mut self, register: VirtualRegister) { 168 | if register.argument.is_some() { 169 | return; 170 | } 171 | self.free_registers.push(register); 172 | } 173 | 174 | fn new_stack_location(&mut self) -> usize { 175 | let result = self.stack_slot; 176 | self.stack_slot += 1; 177 | result 178 | } 179 | 180 | fn replace_spilled_registers_with_spill(&mut self) { 181 | for instruction in self.instructions.iter_mut() { 182 | for register in instruction.get_registers() { 183 | if let Some(stack_offset) = self.location.get(®ister) { 184 | instruction.replace_register(register, Value::Spill(register, *stack_offset)); 185 | } 186 | } 187 | } 188 | } 189 | 190 | fn replace_virtual_with_allocated(&mut self) { 191 | for instruction in self.instructions.iter_mut() { 192 | for register in instruction.get_registers() { 193 | if let Some(physical_register) = self.allocated_registers.get(®ister) { 194 | instruction.replace_register(register, Value::Register(*physical_register)); 195 | } 196 | } 197 | } 198 | } 199 | 200 | fn replace_calls_with_call_with_save(&mut self) { 201 | for (i, instruction) in self.instructions.iter_mut().enumerate() { 202 | if let Instruction::Call(dest, f, args, builtin) = &instruction.clone() { 203 | // println!("{}", instruction.pretty_print()); 204 | // We want to get all ranges that are valid at this point 205 | // if they are not spilled (meaning there isn't an entry in location) 206 | // we want to add them to the list of saves 207 | let mut saves = Vec::new(); 208 | for (original_register, (start, end)) in self.lifetimes.iter() { 209 | if *start < i && *end > i + 1 && !self.location.contains_key(original_register) 210 | { 211 | let register = self.allocated_registers.get(original_register).unwrap(); 212 | // if register.index == 20 { 213 | // println!("20"); 214 | // } 215 | if let Value::Register(dest) = dest { 216 | if dest == register { 217 | continue; 218 | } 219 | } 220 | saves.push(Value::Register(*register)); 221 | } 222 | } 223 | *instruction = Instruction::CallWithSaves(*dest, *f, args.clone(), *builtin, saves); 224 | } else if let Instruction::Recurse(dest, args) = instruction { 225 | let mut saves = Vec::new(); 226 | for (original_register, (start, end)) in self.lifetimes.iter() { 227 | if *start < i && *end > i + 1 && !self.location.contains_key(original_register) 228 | { 229 | let register = self.allocated_registers.get(original_register).unwrap(); 230 | if let Value::Register(dest) = dest { 231 | if dest == register { 232 | continue; 233 | } 234 | } 235 | saves.push(Value::Register(*register)); 236 | } 237 | } 238 | *instruction = Instruction::RecurseWithSaves(*dest, args.clone(), saves); 239 | } 240 | } 241 | } 242 | } 243 | 244 | #[test] 245 | fn test_example() { 246 | use crate::{ir::Ir, pretty_print::PrettyPrint}; 247 | let mut ir = Ir::new(0); 248 | let r0 = ir.assign_new(0); 249 | let r1 = ir.assign_new(0); 250 | let r2 = ir.assign_new(0); 251 | let r3 = ir.assign_new(0); 252 | let r4 = ir.assign_new(0); 253 | let r5 = ir.assign_new(0); 254 | let r6 = ir.assign_new(0); 255 | let r7 = ir.assign_new(0); 256 | let r8 = ir.assign_new(0); 257 | let r9 = ir.assign_new(0); 258 | let r10 = ir.assign_new(0); 259 | let add1 = ir.add_int(r1, r2); 260 | let add2 = ir.add_int(r3, r4); 261 | let add3 = ir.add_int(r5, r6); 262 | let add4 = ir.add_int(r7, r8); 263 | let add5 = ir.add_int(r9, r10); 264 | let add6 = ir.add_int(r0, r1); 265 | let add7 = ir.add_int(r2, r3); 266 | let add8 = ir.add_int(r4, r5); 267 | let add9 = ir.add_int(r6, r7); 268 | let add10 = ir.add_int(r8, r9); 269 | let add11 = ir.add_int(r10, r0); 270 | let add12 = ir.add_int(add1, add2); 271 | let add13 = ir.add_int(add3, add4); 272 | let add14 = ir.add_int(add5, add6); 273 | let add15 = ir.add_int(add7, add8); 274 | let add16 = ir.add_int(add9, add10); 275 | let add17 = ir.add_int(add11, add12); 276 | let add18 = ir.add_int(r0, r1); 277 | let add19 = ir.add_int(r2, r3); 278 | let add20 = ir.add_int(r4, r5); 279 | let add21 = ir.add_int(r6, r7); 280 | let add22 = ir.add_int(r8, r9); 281 | let add23 = ir.add_int(r10, r0); 282 | let add24 = ir.add_int(add1, add2); 283 | let add25 = ir.add_int(add24, add13); 284 | let add26 = ir.add_int(add25, add14); 285 | let add27 = ir.add_int(add26, add15); 286 | let add28 = ir.add_int(add27, add16); 287 | let add29 = ir.add_int(add28, add17); 288 | let add30 = ir.add_int(add29, add18); 289 | let add31 = ir.add_int(add30, add19); 290 | let add32 = ir.add_int(add31, add20); 291 | let add33 = ir.add_int(add32, add21); 292 | let add34 = ir.add_int(add33, add22); 293 | let add35 = ir.add_int(add34, add23); 294 | ir.ret(add35); 295 | 296 | let mut linear_scan = LinearScan::new(ir.instructions.clone(), 0); 297 | linear_scan.allocate(); 298 | println!("{:#?}", linear_scan.allocated_registers); 299 | println!("======="); 300 | println!("{:#?}", linear_scan.location); 301 | 302 | println!("{}", linear_scan.instructions.pretty_print()); 303 | } 304 | -------------------------------------------------------------------------------- /src/register_allocation/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod linear_scan; 2 | pub mod simple; 3 | --------------------------------------------------------------------------------