Initial commit
75704b0e772e56d6566a0771c76faac8556e520fb695390092799a7a69920eff
Checked in sources for the self-hosting Radiance compiler. Licensed under the MIT license.
.gitignore
added
+29 -0
| 1 | + | # Notes and random stuff |
|
| 2 | + | TODO |
|
| 3 | + | DEBUG |
|
| 4 | + | NOTES |
|
| 5 | + | AGENTS.md |
|
| 6 | + | CLAUDE.md |
|
| 7 | + | /notes |
|
| 8 | + | /tools |
|
| 9 | + | /.research |
|
| 10 | + | /.claude |
|
| 11 | + | ||
| 12 | + | # Compiler/build output |
|
| 13 | + | /bin |
|
| 14 | + | *.rv64 |
|
| 15 | + | *.rv64.ro.data |
|
| 16 | + | *.rv64.rw.data |
|
| 17 | + | *.rv64.debug |
|
| 18 | + | *.o |
|
| 19 | + | ||
| 20 | + | # Seed binary (tracked, but not stage intermediates) |
|
| 21 | + | !seed/ |
|
| 22 | + | !seed/* |
|
| 23 | + | seed/radiance.rv64.s* |
|
| 24 | + | ||
| 25 | + | # Backups we don't want to check-in |
|
| 26 | + | *.backup |
|
| 27 | + | ||
| 28 | + | # Library test files |
|
| 29 | + | lib/std.test* |
.gitsigners
added
+1 -0
| 1 | + | alexis ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICpDRmIwBm4ajzW+METm9tBdK4CG2/v0qmO4bPfi+s+c alexis@radiant.computer |
LICENSE
added
+19 -0
| 1 | + | Copyright (c) 2025-2026 Radiant Computer (https://radiant.computer) |
|
| 2 | + | ||
| 3 | + | Permission is hereby granted, free of charge, to any person obtaining a copy of |
|
| 4 | + | this software and associated documentation files (the "Software"), to deal in |
|
| 5 | + | the Software without restriction, including without limitation the rights to |
|
| 6 | + | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies |
|
| 7 | + | of the Software, and to permit persons to whom the Software is furnished to do |
|
| 8 | + | so, subject to the following conditions: |
|
| 9 | + | ||
| 10 | + | The above copyright notice and this permission notice shall be included in all |
|
| 11 | + | copies or substantial portions of the Software. |
|
| 12 | + | ||
| 13 | + | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|
| 14 | + | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|
| 15 | + | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|
| 16 | + | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|
| 17 | + | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|
| 18 | + | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
|
| 19 | + | SOFTWARE. |
Makefile
added
+122 -0
| 1 | + | # Radiance build file. |
|
| 2 | + | ||
| 3 | + | # Core standard library modules. |
|
| 4 | + | STD := -pkg std $(patsubst %,-mod %,$(shell cat std.lib)) |
|
| 5 | + | ||
| 6 | + | # Full standard library with test modules. |
|
| 7 | + | STD_TEST := $(STD) $(patsubst %,-mod %,$(shell cat std.lib.test)) |
|
| 8 | + | ||
| 9 | + | # Source files. |
|
| 10 | + | STD_LIB := $(shell find lib -name '*.rad') |
|
| 11 | + | RAD_BIN := bin/radiance.rv64.dev |
|
| 12 | + | ||
| 13 | + | # Emulator command used to invoke the self-hosted compiler. |
|
| 14 | + | EMU := $(or $(RAD_EMULATOR),emulator) |
|
| 15 | + | EMU_FLAGS := -memory-size=385024 \ |
|
| 16 | + | -data-size=348160 \ |
|
| 17 | + | -stack-size=512 |
|
| 18 | + | RADIANCE := $(EMU) $(EMU_FLAGS) -run $(RAD_BIN) |
|
| 19 | + | ||
| 20 | + | # Verify the emulator binary exists. |
|
| 21 | + | EMU_PATH := $(shell command -v $(EMU) 2>/dev/null) |
|
| 22 | + | ||
| 23 | + | default: emulator $(RAD_BIN) |
|
| 24 | + | test: emulator std-test lower-test asm-test |
|
| 25 | + | ||
| 26 | + | # Emulator command check |
|
| 27 | + | ||
| 28 | + | emulator: |
|
| 29 | + | ifeq ($(EMU_PATH),) |
|
| 30 | + | $(error Emulator not found. Install it or set RAD_EMULATOR to its path) |
|
| 31 | + | endif |
|
| 32 | + | ||
| 33 | + | # Compiler build |
|
| 34 | + | ||
| 35 | + | SEED := seed/radiance.rv64 |
|
| 36 | + | SEED_OPTS := $(STD) -pkg radiance -mod compiler/radiance.rad -entry radiance |
|
| 37 | + | ||
| 38 | + | $(RAD_BIN): $(STD_LIB) compiler/radiance.rad |
|
| 39 | + | @echo "radiance $(SEED) => $@" |
|
| 40 | + | @$(EMU) $(EMU_FLAGS) -run $(SEED) $(SEED_OPTS) -o $@ |
|
| 41 | + | ||
| 42 | + | # Standard Library Tests |
|
| 43 | + | ||
| 44 | + | STD_LIB_TEST := lib/std.test.rv64 |
|
| 45 | + | ||
| 46 | + | std-test: $(STD_LIB_TEST) |
|
| 47 | + | @echo |
|
| 48 | + | @$(EMU) $(EMU_FLAGS) -run $(STD_LIB_TEST) |
|
| 49 | + | ||
| 50 | + | $(STD_LIB_TEST): $(STD_LIB) $(RAD_BIN) |
|
| 51 | + | @echo "radiance -test $(STD_TEST) -entry std -o $@" |
|
| 52 | + | @$(RADIANCE) -test $(STD_TEST) -entry std -o $@ |
|
| 53 | + | ||
| 54 | + | clean-std-test: |
|
| 55 | + | @rm -f lib/std.test.rv64 \ |
|
| 56 | + | lib/std.test.rv64.debug \ |
|
| 57 | + | lib/std.test.rv64.s \ |
|
| 58 | + | lib/std.test.rv64.o \ |
|
| 59 | + | lib/std.test.rv64.rw.data \ |
|
| 60 | + | lib/std.test.rv64.ro.data |
|
| 61 | + | ||
| 62 | + | # Lowering Tests |
|
| 63 | + | ||
| 64 | + | LOWER_TEST := test/lower/lower-test.rv64 |
|
| 65 | + | LOWER_TEST_RUN := test/lower/run |
|
| 66 | + | ||
| 67 | + | lower-test: $(LOWER_TEST) |
|
| 68 | + | @echo |
|
| 69 | + | @$(LOWER_TEST_RUN) |
|
| 70 | + | ||
| 71 | + | $(LOWER_TEST): test/lower/lower-test.rad $(STD_LIB) $(RAD_BIN) |
|
| 72 | + | @echo "radiance $(STD) -pkg lower -mod test/lower/lower-test.rad -entry lower -o $@" |
|
| 73 | + | @$(RADIANCE) $(STD) -pkg lower -mod test/lower/lower-test.rad -entry lower -o $@ |
|
| 74 | + | ||
| 75 | + | clean-lower-test: |
|
| 76 | + | @rm -f $(LOWER_TEST) \ |
|
| 77 | + | test/lower/lower-test.rv64.debug \ |
|
| 78 | + | test/lower/lower-test.rv64.s \ |
|
| 79 | + | test/lower/lower-test.rv64.o \ |
|
| 80 | + | test/lower/lower-test.rv64.rw.data \ |
|
| 81 | + | test/lower/lower-test.rv64.ro.data |
|
| 82 | + | ||
| 83 | + | # Code generation tests. |
|
| 84 | + | ||
| 85 | + | ASM_TEST_DIR := lib/std/arch/rv64/tests |
|
| 86 | + | ASM_TEST_SRC := $(wildcard $(ASM_TEST_DIR)/*.rad) |
|
| 87 | + | ASM_TEST_BIN := $(ASM_TEST_SRC:.rad=.rv64) |
|
| 88 | + | ASM_TEST_RUN := test/asm/run |
|
| 89 | + | ||
| 90 | + | asm-test: $(ASM_TEST_BIN) $(RAD_BIN) |
|
| 91 | + | @echo |
|
| 92 | + | @$(ASM_TEST_RUN) $(ASM_TEST_SRC) |
|
| 93 | + | ||
| 94 | + | $(ASM_TEST_DIR)/%.rv64: $(ASM_TEST_DIR)/%.rad $(RAD_BIN) |
|
| 95 | + | @echo "radiance $< => $@" |
|
| 96 | + | @$(RADIANCE) -pkg test -mod $< -o $@ |
|
| 97 | + | ||
| 98 | + | clean-asm-test: |
|
| 99 | + | @rm -f $(ASM_TEST_BIN) \ |
|
| 100 | + | $(wildcard $(ASM_TEST_DIR)/*.rv64.debug) \ |
|
| 101 | + | $(wildcard $(ASM_TEST_DIR)/*.rv64.s) \ |
|
| 102 | + | $(wildcard $(ASM_TEST_DIR)/*.rv64) \ |
|
| 103 | + | $(wildcard $(ASM_TEST_DIR)/*.rv64.rw.data) \ |
|
| 104 | + | $(wildcard $(ASM_TEST_DIR)/*.rv64.ro.data) |
|
| 105 | + | ||
| 106 | + | seed: |
|
| 107 | + | seed/update |
|
| 108 | + | ||
| 109 | + | clean-rad: |
|
| 110 | + | rm -f $(RAD_BIN) $(RAD_BIN).ro.data $(RAD_BIN).rw.data |
|
| 111 | + | rm -f seed/radiance.rv64.s[0-9]* |
|
| 112 | + | ||
| 113 | + | clean: clean-std-test clean-lower-test clean-asm-test clean-rad |
|
| 114 | + | ||
| 115 | + | t: test |
|
| 116 | + | c: clean |
|
| 117 | + | ||
| 118 | + | .PHONY: test clean default std-test lower-test asm-test seed \ |
|
| 119 | + | clean-std-test clean-lower-test clean-asm-test clean-rad emulator |
|
| 120 | + | .SUFFIXES: |
|
| 121 | + | .DELETE_ON_ERROR: |
|
| 122 | + | .SILENT: |
README
added
+90 -0
| 1 | + | ||
| 2 | + | \ | / |
|
| 3 | + | - - |
|
| 4 | + | / | \ |
|
| 5 | + | ||
| 6 | + | RADIANCE |
|
| 7 | + | ||
| 8 | + | Radiance is a small statically-typed systems language designed for the Radiant |
|
| 9 | + | computer system (https://radiant.computer). It currently targets RISC-V (RV64). |
|
| 10 | + | ||
| 11 | + | The compiler is self-hosted: written in Radiance, it compiles to RISC-V and |
|
| 12 | + | runs inside a RISC-V emulator on x86-64 platforms. |
|
| 13 | + | ||
| 14 | + | REQUIREMENTS |
|
| 15 | + | ||
| 16 | + | * Linux on x86-64 |
|
| 17 | + | * Make |
|
| 18 | + | * Radiant's RISC-V Emulator |
|
| 19 | + | (https://code.radiant.computer/emulator/) |
|
| 20 | + | ||
| 21 | + | BUILDING |
|
| 22 | + | ||
| 23 | + | The compiler is self-hosted, so building from source requires bootstrapping |
|
| 24 | + | from a seed binary. A known-good compiler is checked into the repository. |
|
| 25 | + | ||
| 26 | + | The Radiant emulator is required to run the compiler and build process |
|
| 27 | + | on x86-64. The build process will look for it in $PATH, unless $RAD_EMULATOR |
|
| 28 | + | is set to its location: |
|
| 29 | + | ||
| 30 | + | export RAD_EMULATOR=~/bin/emulator |
|
| 31 | + | ||
| 32 | + | Then, build the compiler: |
|
| 33 | + | ||
| 34 | + | make |
|
| 35 | + | ||
| 36 | + | This uses the emulator to run the seed binary (`seed/radiance.rv64`) which |
|
| 37 | + | compiles the self-hosted compiler source to produce `bin/radiance.rv64.dev`. |
|
| 38 | + | ||
| 39 | + | To update the seed to a new fixed point: |
|
| 40 | + | ||
| 41 | + | make seed |
|
| 42 | + | ||
| 43 | + | This iterates self-compilation stages until two consecutive stages produce |
|
| 44 | + | identical output, proving the compiler faithfully reproduces itself. See |
|
| 45 | + | `seed/README` for full details on the seed workflow, verification, and |
|
| 46 | + | how to handle breaking changes. |
|
| 47 | + | ||
| 48 | + | USAGE |
|
| 49 | + | ||
| 50 | + | Compile a Radiance program: |
|
| 51 | + | ||
| 52 | + | emulator -run bin/radiance.rv64.dev \ |
|
| 53 | + | -pkg example -mod example.rad -o example.rv64 |
|
| 54 | + | ||
| 55 | + | Run a compiled binary: |
|
| 56 | + | ||
| 57 | + | emulator -run example.rv64 |
|
| 58 | + | ||
| 59 | + | TESTING |
|
| 60 | + | ||
| 61 | + | Run all tests: |
|
| 62 | + | ||
| 63 | + | make test |
|
| 64 | + | ||
| 65 | + | Individual test suites: |
|
| 66 | + | ||
| 67 | + | make std-test # Standard library tests |
|
| 68 | + | make lower-test # IL lowering tests |
|
| 69 | + | make asm-test # RV64 code generation tests |
|
| 70 | + | ||
| 71 | + | PROJECT STRUCTURE |
|
| 72 | + | ||
| 73 | + | seed/ Seed compiler binary and update tooling |
|
| 74 | + | compiler/ Self-hosted compiler entry point |
|
| 75 | + | lib/std/ Standard library |
|
| 76 | + | lib/std/lang/ Compiler modules |
|
| 77 | + | lib/std/arch/ Architecture backends |
|
| 78 | + | test/ Test harnesses |
|
| 79 | + | scripts/ Utility scripts |
|
| 80 | + | vim/ Vim syntax files |
|
| 81 | + | ||
| 82 | + | EDITOR SUPPORT |
|
| 83 | + | ||
| 84 | + | Vim syntax files for Radiance (.rad) and RIL (.ril) are in the vim/ folder. |
|
| 85 | + | Copy them to ~/.vim/syntax/ for syntax highlighting. |
|
| 86 | + | ||
| 87 | + | LICENSE |
|
| 88 | + | ||
| 89 | + | Licensed under the MIT License, |
|
| 90 | + | Copyright (c) 2025-2026 Radiant Computer (https://radiant.computer) |
compiler/radiance.rad
added
+867 -0
| 1 | + | //! Radiance compiler front-end. |
|
| 2 | + | use std::mem; |
|
| 3 | + | use std::fmt; |
|
| 4 | + | use std::io; |
|
| 5 | + | use std::lang::alloc; |
|
| 6 | + | use std::lang::ast; |
|
| 7 | + | use std::lang::parser; |
|
| 8 | + | use std::lang::resolver; |
|
| 9 | + | use std::lang::module; |
|
| 10 | + | use std::lang::strings; |
|
| 11 | + | use std::lang::package; |
|
| 12 | + | use std::lang::il; |
|
| 13 | + | use std::lang::lower; |
|
| 14 | + | use std::arch::rv64; |
|
| 15 | + | use std::arch::rv64::printer; |
|
| 16 | + | use std::lang::sexpr; |
|
| 17 | + | use std::sys; |
|
| 18 | + | use std::sys::unix; |
|
| 19 | + | ||
| 20 | + | /// Maximum number of modules we can load per package. |
|
| 21 | + | const MAX_LOADED_MODULES: u32 = 64; |
|
| 22 | + | /// Maximum number of packages we can compile. |
|
| 23 | + | const MAX_PACKAGES: u32 = 4; |
|
| 24 | + | /// Total module entries across all packages. |
|
| 25 | + | const MAX_TOTAL_MODULES: u32 = 192; |
|
| 26 | + | /// Source code buffer arena (2 MB). |
|
| 27 | + | const MAX_SOURCES_SIZE: u32 = 2097152; |
|
| 28 | + | /// Maximum number of test functions we can discover. |
|
| 29 | + | const MAX_TESTS: u32 = 1024; |
|
| 30 | + | ||
| 31 | + | /// Temporary arena size (32 MB) - retains all parsed AST until resolution. |
|
| 32 | + | /// Used for: AST during parsing, then codegen scratch space. |
|
| 33 | + | const TEMP_ARENA_SIZE: u32 = 33554432; |
|
| 34 | + | /// Main arena size (128 MB) - lives throughout compilation. |
|
| 35 | + | /// Used for: resolver data, types, symbols, lowered IL. |
|
| 36 | + | const MAIN_ARENA_SIZE: u32 = 134217728; |
|
| 37 | + | ||
| 38 | + | /// Temporary storage arena - reusable between phases. |
|
| 39 | + | static TEMP_ARENA: [u8; TEMP_ARENA_SIZE] = undefined; |
|
| 40 | + | /// Main storage arena - persists throughout compilation. |
|
| 41 | + | static MAIN_ARENA: [u8; MAIN_ARENA_SIZE] = undefined; |
|
| 42 | + | ||
| 43 | + | /// Module source code. |
|
| 44 | + | static MODULE_SOURCES: [u8; MAX_SOURCES_SIZE] = undefined; |
|
| 45 | + | /// Module entries for all packages. |
|
| 46 | + | static MODULE_ENTRIES: [module::ModuleEntry; MAX_TOTAL_MODULES] = undefined; |
|
| 47 | + | /// String pool. |
|
| 48 | + | static STRING_POOL: strings::Pool = strings::Pool { table: undefined, count: 0 }; |
|
| 49 | + | ||
| 50 | + | /// Package scope. |
|
| 51 | + | static RESOLVER_PKG_SCOPE: resolver::Scope = undefined; |
|
| 52 | + | /// Errors emitted by resolver. |
|
| 53 | + | static RESOLVER_ERRORS: [resolver::Error; resolver::MAX_ERRORS] = undefined; |
|
| 54 | + | ||
| 55 | + | /// Code generation storage. |
|
| 56 | + | static CODEGEN_DATA_SYMS: [rv64::DataSym; rv64::MAX_DATA_SYMS] = undefined; |
|
| 57 | + | ||
| 58 | + | /// Debug info file extension. |
|
| 59 | + | const DEBUG_EXT: *[u8] = ".debug"; |
|
| 60 | + | ||
| 61 | + | /// Read-only data file extension. |
|
| 62 | + | const RO_DATA_EXT: *[u8] = ".ro.data"; |
|
| 63 | + | /// Read-write data file extension. |
|
| 64 | + | const RW_DATA_EXT: *[u8] = ".rw.data"; |
|
| 65 | + | /// Maximum rodata size (1MB). |
|
| 66 | + | const MAX_RO_DATA_SIZE: u32 = 1048576; |
|
| 67 | + | /// Maximum rwdata size (1MB). |
|
| 68 | + | const MAX_RW_DATA_SIZE: u32 = 1048576; |
|
| 69 | + | /// Maximum path length. |
|
| 70 | + | const MAX_PATH_LEN: u32 = 256; |
|
| 71 | + | /// Read-only data buffer. |
|
| 72 | + | static RO_DATA_BUF: [u8; MAX_RO_DATA_SIZE] = undefined; |
|
| 73 | + | /// Read-write data buffer. |
|
| 74 | + | static RW_DATA_BUF: [u8; MAX_RW_DATA_SIZE] = undefined; |
|
| 75 | + | ||
| 76 | + | /// Usage string. |
|
| 77 | + | const USAGE: *[u8] = |
|
| 78 | + | "usage: radiance -pkg <name> -mod <input>.. [-pkg <name> -mod <input>..] -entry <pkg> -o <output>\n"; |
|
| 79 | + | ||
| 80 | + | /// Compiler error. |
|
| 81 | + | union Error { |
|
| 82 | + | Other, |
|
| 83 | + | } |
|
| 84 | + | ||
| 85 | + | /// What to dump during compilation. |
|
| 86 | + | union Dump { |
|
| 87 | + | /// Don't dump anything. |
|
| 88 | + | None, |
|
| 89 | + | /// Dump the parsed AST before semantic analysis. |
|
| 90 | + | Ast, |
|
| 91 | + | /// Dump the module graph. |
|
| 92 | + | Graph, |
|
| 93 | + | /// Dump the IL. |
|
| 94 | + | Il, |
|
| 95 | + | /// Dump the generated assembly. |
|
| 96 | + | Asm, |
|
| 97 | + | } |
|
| 98 | + | ||
| 99 | + | /// A discovered test function. |
|
| 100 | + | record TestDesc { |
|
| 101 | + | /// Full module qualified path segments (eg. ["std", "tests"]). |
|
| 102 | + | modPath: *[*[u8]], |
|
| 103 | + | /// Test function name (eg. "testFoo"). |
|
| 104 | + | fnName: *[u8], |
|
| 105 | + | } |
|
| 106 | + | ||
| 107 | + | /// Compilation context. |
|
| 108 | + | record CompileContext { |
|
| 109 | + | /// Array of packages to compile. |
|
| 110 | + | packages: [package::Package; MAX_PACKAGES], |
|
| 111 | + | /// Number of packages. |
|
| 112 | + | packageCount: u32, |
|
| 113 | + | /// Index of entry package. |
|
| 114 | + | entryPkgIdx: ?u32, |
|
| 115 | + | /// Global module graph shared by all packages. |
|
| 116 | + | graph: module::ModuleGraph, |
|
| 117 | + | /// Resolver configuration. |
|
| 118 | + | config: resolver::Config, |
|
| 119 | + | /// What to dump during compilation. |
|
| 120 | + | dump: Dump, |
|
| 121 | + | /// Output path for binary. |
|
| 122 | + | outputPath: ?*[u8], |
|
| 123 | + | /// Whether to emit debug info (.debug file). |
|
| 124 | + | debug: bool, |
|
| 125 | + | } |
|
| 126 | + | ||
| 127 | + | /// Root module info for a package. |
|
| 128 | + | record RootModule { |
|
| 129 | + | entry: *module::ModuleEntry, |
|
| 130 | + | ast: *mut ast::Node, |
|
| 131 | + | } |
|
| 132 | + | ||
| 133 | + | /// Print a log line for the given package. |
|
| 134 | + | fn pkgLog(pkg: *package::Package, msg: *[*[u8]]) { |
|
| 135 | + | io::printError("radiance: "); |
|
| 136 | + | io::printError(pkg.name); |
|
| 137 | + | io::printError(": "); |
|
| 138 | + | ||
| 139 | + | for part, i in msg { |
|
| 140 | + | io::printError(part); |
|
| 141 | + | if i < msg.len - 1 { |
|
| 142 | + | io::printError(" "); |
|
| 143 | + | } |
|
| 144 | + | } |
|
| 145 | + | io::printError("\n"); |
|
| 146 | + | } |
|
| 147 | + | ||
| 148 | + | /// Register, load, and parse `path` within `pkg`. |
|
| 149 | + | fn processModule( |
|
| 150 | + | pkg: *mut package::Package, |
|
| 151 | + | graph: *mut module::ModuleGraph, |
|
| 152 | + | path: *[u8], |
|
| 153 | + | nodeArena: *mut ast::NodeArena, |
|
| 154 | + | sourceArena: *mut alloc::Arena |
|
| 155 | + | ) throws (Error) { |
|
| 156 | + | pkgLog(pkg, &["parsing", "(", path, ")", ".."]); |
|
| 157 | + | ||
| 158 | + | let moduleId = try package::registerModule(pkg, graph, path) catch { |
|
| 159 | + | io::printError("radiance: error registering module\n"); |
|
| 160 | + | throw Error::Other; |
|
| 161 | + | }; |
|
| 162 | + | // Read file into remaining arena space. |
|
| 163 | + | let buffer = alloc::remainingBuf(sourceArena); |
|
| 164 | + | if buffer.len == 0 { |
|
| 165 | + | io::printError("radiance: fatal: source arena exhausted\n"); |
|
| 166 | + | throw Error::Other; |
|
| 167 | + | } |
|
| 168 | + | let source = unix::readFile(path, buffer) else { |
|
| 169 | + | io::printError("radiance: error reading file\n"); |
|
| 170 | + | throw Error::Other; |
|
| 171 | + | }; |
|
| 172 | + | if source.len == buffer.len { |
|
| 173 | + | io::printError("radiance: fatal: source arena too small, file truncated: "); |
|
| 174 | + | io::printError(path); |
|
| 175 | + | io::printError("\n"); |
|
| 176 | + | throw Error::Other; |
|
| 177 | + | } |
|
| 178 | + | // Commit only what was read. |
|
| 179 | + | alloc::commit(sourceArena, source.len); |
|
| 180 | + | ||
| 181 | + | let ast = try parser::parse(source, nodeArena, &mut STRING_POOL) catch { |
|
| 182 | + | io::printError("radiance: error parsing module\n"); |
|
| 183 | + | throw Error::Other; |
|
| 184 | + | }; |
|
| 185 | + | try module::setAst(graph, moduleId, ast) catch { |
|
| 186 | + | io::printError("radiance: error setting AST\n"); |
|
| 187 | + | throw Error::Other; |
|
| 188 | + | }; |
|
| 189 | + | try module::setSource(graph, moduleId, source) catch { |
|
| 190 | + | io::printError("radiance: error setting source\n"); |
|
| 191 | + | throw Error::Other; |
|
| 192 | + | }; |
|
| 193 | + | } |
|
| 194 | + | ||
| 195 | + | /// Parse CLI arguments and return compilation context. |
|
| 196 | + | fn processCommand( |
|
| 197 | + | args: *[*[u8]], |
|
| 198 | + | arena: *mut ast::NodeArena |
|
| 199 | + | ) -> CompileContext throws (Error) { |
|
| 200 | + | let mut buildTest = false; |
|
| 201 | + | let mut debugEnabled = false; |
|
| 202 | + | let mut outputPath: ?*[u8] = nil; |
|
| 203 | + | let mut dump = Dump::None; |
|
| 204 | + | let mut entryPkgName: ?*[u8] = nil; |
|
| 205 | + | ||
| 206 | + | // Per-package module path tracking. |
|
| 207 | + | let mut moduleCounts: [u32; MAX_PACKAGES] = undefined; |
|
| 208 | + | let mut modulePaths: [[*[u8]; MAX_LOADED_MODULES]; MAX_PACKAGES] = undefined; |
|
| 209 | + | let mut pkgNames: [*[u8]; MAX_PACKAGES] = undefined; |
|
| 210 | + | let mut pkgCount: u32 = 0; |
|
| 211 | + | let mut currentPkgIdx: ?u32 = nil; |
|
| 212 | + | ||
| 213 | + | for i in 0..MAX_PACKAGES { |
|
| 214 | + | moduleCounts[i] = 0; |
|
| 215 | + | } |
|
| 216 | + | if args.len == 0 { |
|
| 217 | + | io::printError(USAGE); |
|
| 218 | + | throw Error::Other; |
|
| 219 | + | } |
|
| 220 | + | let mut idx: u32 = 0; |
|
| 221 | + | ||
| 222 | + | while idx < args.len { |
|
| 223 | + | let arg = args[idx]; |
|
| 224 | + | if mem::eq(arg, "-pkg") { |
|
| 225 | + | idx = idx + 1; |
|
| 226 | + | if idx >= args.len { |
|
| 227 | + | io::printError("radiance: `-pkg` requires a package name\n"); |
|
| 228 | + | throw Error::Other; |
|
| 229 | + | } |
|
| 230 | + | if pkgCount >= MAX_PACKAGES { |
|
| 231 | + | io::printError("radiance: too many packages specified\n"); |
|
| 232 | + | throw Error::Other; |
|
| 233 | + | } |
|
| 234 | + | pkgNames[pkgCount] = args[idx]; |
|
| 235 | + | currentPkgIdx = pkgCount; |
|
| 236 | + | pkgCount = pkgCount + 1; |
|
| 237 | + | } else if mem::eq(arg, "-mod") { |
|
| 238 | + | idx = idx + 1; |
|
| 239 | + | if idx >= args.len { |
|
| 240 | + | io::printError("radiance: `-mod` requires a module path\n"); |
|
| 241 | + | throw Error::Other; |
|
| 242 | + | } |
|
| 243 | + | let pkgIdx = currentPkgIdx else { |
|
| 244 | + | io::printError("radiance: `-mod` must follow a `-pkg` argument\n"); |
|
| 245 | + | throw Error::Other; |
|
| 246 | + | }; |
|
| 247 | + | if moduleCounts[pkgIdx] >= MAX_LOADED_MODULES { |
|
| 248 | + | io::printError("radiance: too many modules specified for package\n"); |
|
| 249 | + | throw Error::Other; |
|
| 250 | + | } |
|
| 251 | + | modulePaths[pkgIdx][moduleCounts[pkgIdx]] = args[idx]; |
|
| 252 | + | moduleCounts[pkgIdx] = moduleCounts[pkgIdx] + 1; |
|
| 253 | + | } else if mem::eq(arg, "-entry") { |
|
| 254 | + | idx = idx + 1; |
|
| 255 | + | if idx >= args.len { |
|
| 256 | + | io::printError("radiance: `-entry` requires a package name\n"); |
|
| 257 | + | throw Error::Other; |
|
| 258 | + | } |
|
| 259 | + | entryPkgName = args[idx]; |
|
| 260 | + | } else if mem::eq(arg, "-test") { |
|
| 261 | + | buildTest = true; |
|
| 262 | + | } else if mem::eq(arg, "-debug") { |
|
| 263 | + | debugEnabled = true; |
|
| 264 | + | } else if mem::eq(arg, "-o") { |
|
| 265 | + | idx = idx + 1; |
|
| 266 | + | if idx >= args.len { |
|
| 267 | + | io::printError("radiance: `-o` requires an output path\n"); |
|
| 268 | + | throw Error::Other; |
|
| 269 | + | } |
|
| 270 | + | outputPath = args[idx]; |
|
| 271 | + | } else if mem::eq(arg, "-dump") { |
|
| 272 | + | idx = idx + 1; |
|
| 273 | + | if idx >= args.len { |
|
| 274 | + | io::printError("radiance: `-dump` requires a mode (eg. ast)\n"); |
|
| 275 | + | throw Error::Other; |
|
| 276 | + | } |
|
| 277 | + | let mode = args[idx]; |
|
| 278 | + | if mem::eq(mode, "ast") { |
|
| 279 | + | dump = Dump::Ast; |
|
| 280 | + | } else if mem::eq(mode, "graph") { |
|
| 281 | + | dump = Dump::Graph; |
|
| 282 | + | } else if mem::eq(mode, "il") { |
|
| 283 | + | dump = Dump::Il; |
|
| 284 | + | } else if mem::eq(mode, "asm") { |
|
| 285 | + | dump = Dump::Asm; |
|
| 286 | + | } else { |
|
| 287 | + | io::printError("radiance: unknown dump mode `"); |
|
| 288 | + | io::printError(mode); |
|
| 289 | + | io::printError("` (expected: ast, graph, il, asm)\n"); |
|
| 290 | + | throw Error::Other; |
|
| 291 | + | } |
|
| 292 | + | } else { |
|
| 293 | + | io::printError("radiance: unknown argument `"); |
|
| 294 | + | io::printError(arg); |
|
| 295 | + | io::printError("`\n"); |
|
| 296 | + | throw Error::Other; |
|
| 297 | + | } |
|
| 298 | + | idx = idx + 1; |
|
| 299 | + | } |
|
| 300 | + | if pkgCount == 0 { |
|
| 301 | + | io::printError("radiance: no package specified\n"); |
|
| 302 | + | throw Error::Other; |
|
| 303 | + | } |
|
| 304 | + | ||
| 305 | + | // Determine entry package index. |
|
| 306 | + | let mut entryPkgIdx: ?u32 = nil; |
|
| 307 | + | if pkgCount == 1 { |
|
| 308 | + | // Single package: it is the entry. |
|
| 309 | + | entryPkgIdx = 0; |
|
| 310 | + | } else { |
|
| 311 | + | // Multiple packages: need -entry. |
|
| 312 | + | let entryName = entryPkgName else { |
|
| 313 | + | io::printError("radiance: `-entry` required when multiple packages specified\n"); |
|
| 314 | + | throw Error::Other; |
|
| 315 | + | }; |
|
| 316 | + | for i in 0..pkgCount { |
|
| 317 | + | if mem::eq(pkgNames[i], entryName) { |
|
| 318 | + | entryPkgIdx = i; |
|
| 319 | + | break; |
|
| 320 | + | } |
|
| 321 | + | } |
|
| 322 | + | if entryPkgIdx == nil { |
|
| 323 | + | io::printError("radiance: fatal: entry package `"); |
|
| 324 | + | io::printError(entryName); |
|
| 325 | + | io::printError("` not found\n"); |
|
| 326 | + | ||
| 327 | + | throw Error::Other; |
|
| 328 | + | } |
|
| 329 | + | } |
|
| 330 | + | let graph = module::moduleGraph(&mut MODULE_ENTRIES[..], &mut STRING_POOL, arena); |
|
| 331 | + | let mut ctx = CompileContext { |
|
| 332 | + | packages: undefined, |
|
| 333 | + | packageCount: pkgCount, |
|
| 334 | + | entryPkgIdx, |
|
| 335 | + | graph, |
|
| 336 | + | config: resolver::Config { buildTest }, |
|
| 337 | + | dump, |
|
| 338 | + | outputPath, |
|
| 339 | + | debug: debugEnabled, |
|
| 340 | + | }; |
|
| 341 | + | // Initialize and parse all packages. |
|
| 342 | + | let mut sourceArena = alloc::new(&mut MODULE_SOURCES[..]); |
|
| 343 | + | for i in 0..pkgCount { |
|
| 344 | + | try! package::init(&mut ctx.packages[i], i as u16, pkgNames[i], &mut STRING_POOL); |
|
| 345 | + | ||
| 346 | + | for j in 0..moduleCounts[i] { |
|
| 347 | + | let path = modulePaths[i][j]; |
|
| 348 | + | try processModule(&mut ctx.packages[i], &mut ctx.graph, path, arena, &mut sourceArena); |
|
| 349 | + | } |
|
| 350 | + | } |
|
| 351 | + | return ctx; |
|
| 352 | + | } |
|
| 353 | + | ||
| 354 | + | /// Get the entry package from the context. |
|
| 355 | + | fn getEntryPackage(ctx: *CompileContext) -> *package::Package throws (Error) { |
|
| 356 | + | let entryIdx = ctx.entryPkgIdx else { |
|
| 357 | + | io::printError("radiance: no entry package specified\n"); |
|
| 358 | + | throw Error::Other; |
|
| 359 | + | }; |
|
| 360 | + | return &ctx.packages[entryIdx]; |
|
| 361 | + | } |
|
| 362 | + | ||
| 363 | + | /// Get root module info from a package. |
|
| 364 | + | fn getRootModule(pkg: *package::Package, graph: *module::ModuleGraph) -> RootModule throws (Error) { |
|
| 365 | + | let rootId = pkg.rootModuleId else { |
|
| 366 | + | io::printError("radiance: no root module found\n"); |
|
| 367 | + | throw Error::Other; |
|
| 368 | + | }; |
|
| 369 | + | let rootEntry = module::get(graph, rootId) else { |
|
| 370 | + | io::printError("radiance: root module entry not found\n"); |
|
| 371 | + | throw Error::Other; |
|
| 372 | + | }; |
|
| 373 | + | let rootAst = rootEntry.ast else { |
|
| 374 | + | io::printError("radiance: root module has no AST\n"); |
|
| 375 | + | throw Error::Other; |
|
| 376 | + | }; |
|
| 377 | + | return RootModule { entry: rootEntry, ast: rootAst }; |
|
| 378 | + | } |
|
| 379 | + | ||
| 380 | + | /// Dump the module graph. |
|
| 381 | + | fn dumpGraph(ctx: *CompileContext) throws (Error) { |
|
| 382 | + | let mut arena = alloc::new(&mut MAIN_ARENA[..]); |
|
| 383 | + | module::printer::printGraph(&ctx.graph, &mut arena); |
|
| 384 | + | } |
|
| 385 | + | ||
| 386 | + | /// Dump the parsed AST. |
|
| 387 | + | fn dumpAst(ctx: *CompileContext) throws (Error) { |
|
| 388 | + | let pkg = try getEntryPackage(ctx); |
|
| 389 | + | let root = try getRootModule(pkg, &ctx.graph); |
|
| 390 | + | let mut arena = alloc::new(&mut MAIN_ARENA[..]); |
|
| 391 | + | ||
| 392 | + | ast::printer::printTree(root.ast, &mut arena); |
|
| 393 | + | } |
|
| 394 | + | ||
| 395 | + | /// Lower all packages into a single IL program. |
|
| 396 | + | /// Dependencies are lowered first, then the entry package. |
|
| 397 | + | fn lowerAllPackages( |
|
| 398 | + | ctx: *mut CompileContext, |
|
| 399 | + | res: *mut resolver::Resolver |
|
| 400 | + | ) -> il::Program throws (Error) { |
|
| 401 | + | let entryIdx = ctx.entryPkgIdx else { |
|
| 402 | + | panic "lowerAllPackages: no entry package"; |
|
| 403 | + | }; |
|
| 404 | + | let entryPkg = &ctx.packages[entryIdx]; |
|
| 405 | + | ||
| 406 | + | // Create the lowerer accumulator using entry package's name. |
|
| 407 | + | let options = lower::LowerOptions { debug: ctx.debug, buildTest: ctx.config.buildTest }; |
|
| 408 | + | let mut low = lower::lowerer(res, &ctx.graph, entryPkg.name, &mut res.arena, options); |
|
| 409 | + | ||
| 410 | + | // Lower all packages except entry. |
|
| 411 | + | for i in 0..ctx.packageCount { |
|
| 412 | + | if i != entryIdx { |
|
| 413 | + | try lowerPackage(ctx, res, &mut low, &mut ctx.packages[i], false); |
|
| 414 | + | } |
|
| 415 | + | } |
|
| 416 | + | // Lower entry package. |
|
| 417 | + | let defaultFn = try lowerPackage(ctx, res, &mut low, &mut ctx.packages[entryIdx], true); |
|
| 418 | + | ||
| 419 | + | // Finalize and return the unified program. |
|
| 420 | + | return lower::finalize(&low, defaultFn); |
|
| 421 | + | } |
|
| 422 | + | ||
| 423 | + | /// Lower all modules in a package into the lowerer accumulator. |
|
| 424 | + | fn lowerPackage( |
|
| 425 | + | ctx: *CompileContext, |
|
| 426 | + | res: *mut resolver::Resolver, |
|
| 427 | + | low: *mut lower::Lowerer, |
|
| 428 | + | pkg: *mut package::Package, |
|
| 429 | + | isEntry: bool |
|
| 430 | + | ) -> ?u32 throws (Error) { |
|
| 431 | + | let rootId = pkg.rootModuleId else { |
|
| 432 | + | io::printError("radiance: no root module found\n"); |
|
| 433 | + | throw Error::Other; |
|
| 434 | + | }; |
|
| 435 | + | // Set lowerer's package context for qualified name generation. |
|
| 436 | + | // TODO: We shouldn't have to call this manually. |
|
| 437 | + | lower::setPackage(low, &ctx.graph, pkg.name); |
|
| 438 | + | ||
| 439 | + | return try lowerModuleTreeInto(ctx, low, &ctx.graph, rootId, isEntry, pkg); |
|
| 440 | + | } |
|
| 441 | + | ||
| 442 | + | /// Recursively lower a module and all its children into the accumulator. |
|
| 443 | + | fn lowerModuleTreeInto( |
|
| 444 | + | ctx: *CompileContext, |
|
| 445 | + | low: *mut lower::Lowerer, |
|
| 446 | + | graph: *module::ModuleGraph, |
|
| 447 | + | modId: u16, |
|
| 448 | + | isRoot: bool, |
|
| 449 | + | pkg: *package::Package |
|
| 450 | + | ) -> ?u32 throws (Error) { |
|
| 451 | + | let entry = module::get(graph, modId) else { |
|
| 452 | + | io::printError("radiance: module entry not found\n"); |
|
| 453 | + | throw Error::Other; |
|
| 454 | + | }; |
|
| 455 | + | let modAst = entry.ast else { |
|
| 456 | + | io::printError("radiance: module has no AST\n"); |
|
| 457 | + | throw Error::Other; |
|
| 458 | + | }; |
|
| 459 | + | pkgLog(pkg, &["lowering", "(", entry.filePath, ")", ".."]); |
|
| 460 | + | ||
| 461 | + | let defaultFn = try lower::lowerModule(low, modId, modAst, isRoot) catch err { |
|
| 462 | + | io::printError("radiance: internal error during lowering: "); |
|
| 463 | + | lower::printError(err); |
|
| 464 | + | io::printError("\n"); |
|
| 465 | + | throw Error::Other; |
|
| 466 | + | }; |
|
| 467 | + | // Recurse into children. |
|
| 468 | + | for i in 0..entry.childrenLen { |
|
| 469 | + | let childId = module::childAt(entry, i); |
|
| 470 | + | try lowerModuleTreeInto(ctx, low, graph, childId, false, pkg); |
|
| 471 | + | } |
|
| 472 | + | return defaultFn; |
|
| 473 | + | } |
|
| 474 | + | ||
| 475 | + | /// Build a scope access chain: a::b::c from a slice of identifiers. |
|
| 476 | + | fn synthScopeAccess(arena: *mut ast::NodeArena, path: *[*[u8]]) -> *ast::Node { |
|
| 477 | + | let mut result = ast::synthNode( |
|
| 478 | + | arena, |
|
| 479 | + | ast::NodeValue::Ident(strings::intern(&mut STRING_POOL, path[0])) |
|
| 480 | + | ); |
|
| 481 | + | for i in 1..path.len { |
|
| 482 | + | let child = ast::synthNode( |
|
| 483 | + | arena, |
|
| 484 | + | ast::NodeValue::Ident(strings::intern(&mut STRING_POOL, path[i])) |
|
| 485 | + | ); |
|
| 486 | + | result = ast::synthNode(arena, ast::NodeValue::ScopeAccess(ast::Access { |
|
| 487 | + | parent: result, child, |
|
| 488 | + | })); |
|
| 489 | + | } |
|
| 490 | + | return result; |
|
| 491 | + | } |
|
| 492 | + | ||
| 493 | + | /// Check if a function declaration has the `@test` attribute and return its name if so. |
|
| 494 | + | fn getTestFnName(decl: *ast::FnDecl) -> ?*[u8] { |
|
| 495 | + | let attrs = decl.attrs else { return nil; }; |
|
| 496 | + | if not ast::attributesContains(&attrs, ast::Attribute::Test) { |
|
| 497 | + | return nil; |
|
| 498 | + | } |
|
| 499 | + | let case ast::NodeValue::Ident(name) = decl.name.value |
|
| 500 | + | else return nil; |
|
| 501 | + | ||
| 502 | + | return name; |
|
| 503 | + | } |
|
| 504 | + | ||
| 505 | + | /// Scan a single module's AST for `@test` functions and append them to `tests`. |
|
| 506 | + | fn collectModuleTests( |
|
| 507 | + | entry: *module::ModuleEntry, |
|
| 508 | + | tests: *mut [TestDesc], |
|
| 509 | + | testCount: *mut u32 |
|
| 510 | + | ) { |
|
| 511 | + | let modAst = entry.ast else { |
|
| 512 | + | return; |
|
| 513 | + | }; |
|
| 514 | + | let case ast::NodeValue::Block(block) = modAst.value else { |
|
| 515 | + | return; |
|
| 516 | + | }; |
|
| 517 | + | let modPath = module::moduleQualifiedPath(entry); |
|
| 518 | + | ||
| 519 | + | for i in 0..block.statements.len { |
|
| 520 | + | let stmt = block.statements.list[i]; |
|
| 521 | + | if let case ast::NodeValue::FnDecl(decl) = stmt.value { |
|
| 522 | + | if let fnName = getTestFnName(&decl) { |
|
| 523 | + | if *testCount < tests.len { |
|
| 524 | + | tests[*testCount] = TestDesc { modPath, fnName }; |
|
| 525 | + | *testCount = *testCount + 1; |
|
| 526 | + | } else { |
|
| 527 | + | panic "collectModuleTests: too many tests"; |
|
| 528 | + | } |
|
| 529 | + | } |
|
| 530 | + | } |
|
| 531 | + | } |
|
| 532 | + | } |
|
| 533 | + | ||
| 534 | + | /// Synthesize a `testing::test("mod", "name", mod::fn)` call for one test. |
|
| 535 | + | fn synthTestCall(arena: *mut ast::NodeArena, desc: *TestDesc) -> *ast::Node { |
|
| 536 | + | let callee = synthScopeAccess(arena, &["testing", "test"]); |
|
| 537 | + | let modStr = il::formatQualifiedName( |
|
| 538 | + | &mut arena.arena, |
|
| 539 | + | desc.modPath[..desc.modPath.len - 1], |
|
| 540 | + | desc.modPath[desc.modPath.len - 1] |
|
| 541 | + | ); |
|
| 542 | + | let modArg = ast::synthNode(arena, ast::NodeValue::String(modStr)); |
|
| 543 | + | let nameArg = ast::synthNode(arena, ast::NodeValue::String(desc.fnName)); |
|
| 544 | + | ||
| 545 | + | // Intra-package path: skip the package name prefix. |
|
| 546 | + | let mut funcPath: [*[u8]; 16] = undefined; |
|
| 547 | + | for j in 1..desc.modPath.len { |
|
| 548 | + | funcPath[j - 1] = desc.modPath[j]; |
|
| 549 | + | } |
|
| 550 | + | funcPath[desc.modPath.len - 1] = desc.fnName; |
|
| 551 | + | let funcArg = synthScopeAccess(arena, &funcPath[..desc.modPath.len]); |
|
| 552 | + | ||
| 553 | + | let mut args = ast::nodeList(arena, 3); |
|
| 554 | + | ast::nodeListPush(&mut args, modArg); |
|
| 555 | + | ast::nodeListPush(&mut args, nameArg); |
|
| 556 | + | ast::nodeListPush(&mut args, funcArg); |
|
| 557 | + | ||
| 558 | + | return ast::synthNode(arena, ast::NodeValue::Call(ast::Call { callee, args })); |
|
| 559 | + | } |
|
| 560 | + | ||
| 561 | + | /// Inject a test runner into the entry package's root module. |
|
| 562 | + | /// |
|
| 563 | + | /// Scans all parsed modules for `@test fn` declarations, then appends |
|
| 564 | + | /// a synthetic entry point to the root module's AST block: |
|
| 565 | + | /// |
|
| 566 | + | /// ``` |
|
| 567 | + | /// @default fn #testMain() -> i32 { |
|
| 568 | + | /// return testing::runAllTests(&[ |
|
| 569 | + | /// testing::test("std::tests", "testFoo", tests::testFoo), |
|
| 570 | + | /// ... |
|
| 571 | + | /// ]); |
|
| 572 | + | /// } |
|
| 573 | + | /// ``` |
|
| 574 | + | /// |
|
| 575 | + | /// Uses `#`-prefixed names to avoid conflicts with user code. |
|
| 576 | + | fn generateTestRunner( |
|
| 577 | + | ctx: *mut CompileContext, |
|
| 578 | + | arena: *mut ast::NodeArena |
|
| 579 | + | ) throws (Error) { |
|
| 580 | + | let entryPkg = try getEntryPackage(ctx); |
|
| 581 | + | let root = try getRootModule(entryPkg, &ctx.graph); |
|
| 582 | + | ||
| 583 | + | // Collect all test functions across all modules. |
|
| 584 | + | let mut tests: [TestDesc; MAX_TESTS] = undefined; |
|
| 585 | + | let mut testCount: u32 = 0; |
|
| 586 | + | ||
| 587 | + | for modIdx in 0..ctx.graph.entriesLen { |
|
| 588 | + | if let entry = module::get(&ctx.graph, modIdx as u16) { |
|
| 589 | + | collectModuleTests(entry, &mut tests[..], &mut testCount); |
|
| 590 | + | } |
|
| 591 | + | } |
|
| 592 | + | if testCount == 0 { |
|
| 593 | + | io::printError("radiance: fatal: no test functions found\n"); |
|
| 594 | + | throw Error::Other; |
|
| 595 | + | } |
|
| 596 | + | let mut countBuf: [u8; 10] = undefined; |
|
| 597 | + | let countStr = fmt::formatU32(testCount, &mut countBuf[..]); |
|
| 598 | + | pkgLog(entryPkg, &["found", countStr, "test(s)"]); |
|
| 599 | + | ||
| 600 | + | // Synthesize the `@default` function and append to the root module. |
|
| 601 | + | let fnDecl = synthTestMainFn(arena, &tests[..testCount]); |
|
| 602 | + | ||
| 603 | + | injectIntoBlock(root.ast, arena, fnDecl); |
|
| 604 | + | } |
|
| 605 | + | ||
| 606 | + | /// Synthesize the test entry point. |
|
| 607 | + | fn synthTestMainFn(arena: *mut ast::NodeArena, tests: *[TestDesc]) -> *ast::Node { |
|
| 608 | + | // Build array literal: `[testing::test(...), ...]`. |
|
| 609 | + | let mut elements = ast::nodeList(arena, tests.len); |
|
| 610 | + | for i in 0..tests.len { |
|
| 611 | + | ast::nodeListPush(&mut elements, synthTestCall(arena, &tests[i])); |
|
| 612 | + | } |
|
| 613 | + | let arrayLit = ast::synthNode(arena, ast::NodeValue::ArrayLit(elements)); |
|
| 614 | + | ||
| 615 | + | // Build: `&[...]`. |
|
| 616 | + | let testsRef = ast::synthNode(arena, ast::NodeValue::AddressOf(ast::AddressOf { |
|
| 617 | + | target: arrayLit, mutable: false, |
|
| 618 | + | })); |
|
| 619 | + | ||
| 620 | + | // Build: `testing::runAllTests(&[...])`. |
|
| 621 | + | let runFn = synthScopeAccess(arena, &["testing", "runAllTests"]); |
|
| 622 | + | let mut callArgs = ast::nodeList(arena, 1); |
|
| 623 | + | ast::nodeListPush(&mut callArgs, testsRef); |
|
| 624 | + | let callExpr = ast::synthNode(arena, ast::NodeValue::Call(ast::Call { |
|
| 625 | + | callee: runFn, args: callArgs, |
|
| 626 | + | })); |
|
| 627 | + | ||
| 628 | + | // Build: `return testing::runAllTests(&[...]);` |
|
| 629 | + | let retStmt = ast::synthNode(arena, ast::NodeValue::Return { value: callExpr }); |
|
| 630 | + | let mut bodyStmts = ast::nodeList(arena, 1); |
|
| 631 | + | ast::nodeListPush(&mut bodyStmts, retStmt); |
|
| 632 | + | let fnBody = ast::synthNode(arena, ast::NodeValue::Block(ast::Block { statements: bodyStmts })); |
|
| 633 | + | ||
| 634 | + | // Build: `fn #testMain() -> i32` |
|
| 635 | + | let fnName = ast::synthNode(arena, ast::NodeValue::Ident(strings::intern(&mut STRING_POOL, "#testMain"))); |
|
| 636 | + | let returnType = ast::synthNode(arena, ast::NodeValue::TypeSig(ast::TypeSig::Integer { |
|
| 637 | + | width: 4, sign: ast::Signedness::Signed, |
|
| 638 | + | })); |
|
| 639 | + | let fnSig = ast::FnSig { |
|
| 640 | + | params: ast::nodeList(arena, 0), |
|
| 641 | + | returnType, |
|
| 642 | + | throwList: ast::nodeList(arena, 0), |
|
| 643 | + | }; |
|
| 644 | + | ||
| 645 | + | // `@default` attribute. |
|
| 646 | + | let attrNode = ast::synthNode(arena, ast::NodeValue::Attribute(ast::Attribute::Default)); |
|
| 647 | + | let mut attrList = ast::nodeList(arena, 1); |
|
| 648 | + | ast::nodeListPush(&mut attrList, attrNode); |
|
| 649 | + | let fnAttrs = ast::Attributes { list: attrList }; |
|
| 650 | + | ||
| 651 | + | return ast::synthNode(arena, ast::NodeValue::FnDecl(ast::FnDecl { |
|
| 652 | + | name: fnName, sig: fnSig, body: fnBody, attrs: fnAttrs, |
|
| 653 | + | })); |
|
| 654 | + | } |
|
| 655 | + | ||
| 656 | + | /// Append a declaration to a block node's statement list. |
|
| 657 | + | fn injectIntoBlock( |
|
| 658 | + | blockNode: *mut ast::Node, |
|
| 659 | + | arena: *mut ast::NodeArena, |
|
| 660 | + | decl: *ast::Node |
|
| 661 | + | ) { |
|
| 662 | + | let case ast::NodeValue::Block(block) = blockNode.value else { |
|
| 663 | + | panic "injectIntoBlock: expected Block node"; |
|
| 664 | + | }; |
|
| 665 | + | let mut stmts = block.statements; |
|
| 666 | + | ast::nodeListGrow(&mut stmts, arena, 1); |
|
| 667 | + | ast::nodeListPush(&mut stmts, decl); |
|
| 668 | + | ||
| 669 | + | blockNode.value = ast::NodeValue::Block(ast::Block { statements: stmts }); |
|
| 670 | + | } |
|
| 671 | + | ||
| 672 | + | /// Write code buffer to file as raw bytes. |
|
| 673 | + | fn writeCode(code: *[u32], path: *[u8]) -> bool { |
|
| 674 | + | // Convert `u32` slice to `u8` slice (little-endian). |
|
| 675 | + | let byteLen = code.len * 4; |
|
| 676 | + | let bytePtr = code.ptr as *u8; |
|
| 677 | + | let bytes = @sliceOf(bytePtr, byteLen); |
|
| 678 | + | ||
| 679 | + | return unix::writeFile(path, bytes); |
|
| 680 | + | } |
|
| 681 | + | ||
| 682 | + | /// Write a data section to a file at `basePath` + `ext`. |
|
| 683 | + | /// If the data is empty, no file is written. |
|
| 684 | + | fn writeDataWithExt( |
|
| 685 | + | data: *[u8], |
|
| 686 | + | basePath: *[u8], |
|
| 687 | + | ext: *[u8] |
|
| 688 | + | ) throws (Error) { |
|
| 689 | + | if data.len == 0 { |
|
| 690 | + | return; |
|
| 691 | + | } |
|
| 692 | + | let mut path: [u8; MAX_PATH_LEN] = undefined; |
|
| 693 | + | let mut pos: u32 = 0; |
|
| 694 | + | ||
| 695 | + | pos = pos + try! mem::copy(&mut path[pos..], basePath); |
|
| 696 | + | pos = pos + try! mem::copy(&mut path[pos..], ext); |
|
| 697 | + | path[pos] = 0; // Null-terminate for syscall. |
|
| 698 | + | ||
| 699 | + | if not unix::writeFile(&path[..pos], data) { |
|
| 700 | + | io::printError("radiance: fatal: failed to write data file\n"); |
|
| 701 | + | throw Error::Other; |
|
| 702 | + | } |
|
| 703 | + | } |
|
| 704 | + | ||
| 705 | + | /// Serialize debug entries and write the `.debug` file. |
|
| 706 | + | /// Resolves module IDs to file paths via the module graph. |
|
| 707 | + | /// Format per entry is `{pc: u32, offset: u32, filePath: [u8], NULL}`. |
|
| 708 | + | fn writeDebugInfo( |
|
| 709 | + | entries: *[rv64::emit::DebugEntry], |
|
| 710 | + | graph: *module::ModuleGraph, |
|
| 711 | + | basePath: *[u8], |
|
| 712 | + | arena: *mut alloc::Arena |
|
| 713 | + | ) throws (Error) { |
|
| 714 | + | if entries.len == 0 { |
|
| 715 | + | return; |
|
| 716 | + | } |
|
| 717 | + | // Use remaining arena space as serialization buffer. |
|
| 718 | + | let buf = alloc::remainingBuf(arena); |
|
| 719 | + | let mut pos: u32 = 0; |
|
| 720 | + | ||
| 721 | + | for i in 0..entries.len { |
|
| 722 | + | let entry = &entries[i]; |
|
| 723 | + | let modEntry = module::get(graph, entry.moduleId) else { |
|
| 724 | + | panic "writeDebugInfo: module not found for debug entry"; |
|
| 725 | + | }; |
|
| 726 | + | pos = pos + try! mem::copy(buf[pos..], @sliceOf(&entry.pc as *u8, 4)); |
|
| 727 | + | pos = pos + try! mem::copy(buf[pos..], @sliceOf(&entry.offset as *u8, 4)); |
|
| 728 | + | pos = pos + try! mem::copy(buf[pos..], modEntry.filePath); |
|
| 729 | + | ||
| 730 | + | buf[pos] = 0; |
|
| 731 | + | pos = pos + 1; |
|
| 732 | + | } |
|
| 733 | + | try writeDataWithExt(buf[..pos], basePath, DEBUG_EXT); |
|
| 734 | + | } |
|
| 735 | + | ||
| 736 | + | /// Run the resolver on the parsed modules. |
|
| 737 | + | fn runResolver(ctx: *mut CompileContext, nodeCount: u32) -> resolver::Resolver throws (Error) { |
|
| 738 | + | let mut mainArena = alloc::new(&mut MAIN_ARENA[..]); |
|
| 739 | + | let entryPkg = try getEntryPackage(ctx); |
|
| 740 | + | ||
| 741 | + | pkgLog(entryPkg, &["resolving", ".."]); |
|
| 742 | + | ||
| 743 | + | let nodeDataSize = nodeCount * @sizeOf(resolver::NodeData); |
|
| 744 | + | let nodeDataPtr = try! alloc::alloc(&mut mainArena, nodeDataSize, @alignOf(resolver::NodeData)); |
|
| 745 | + | let nodeData = @sliceOf(nodeDataPtr as *mut resolver::NodeData, nodeCount); |
|
| 746 | + | let storage = resolver::ResolverStorage { |
|
| 747 | + | arena: mainArena, |
|
| 748 | + | nodeData, |
|
| 749 | + | pkgScope: &mut RESOLVER_PKG_SCOPE, |
|
| 750 | + | errors: &mut RESOLVER_ERRORS[..], |
|
| 751 | + | }; |
|
| 752 | + | let mut res = resolver::resolver(storage, ctx.config); |
|
| 753 | + | ||
| 754 | + | // Build package inputs. |
|
| 755 | + | let mut packages: [resolver::Pkg; MAX_PACKAGES] = undefined; |
|
| 756 | + | for i in 0..ctx.packageCount { |
|
| 757 | + | let pkg = &ctx.packages[i]; |
|
| 758 | + | let root = try getRootModule(pkg, &ctx.graph); |
|
| 759 | + | ||
| 760 | + | packages[i] = resolver::Pkg { |
|
| 761 | + | rootEntry: root.entry, |
|
| 762 | + | rootAst: root.ast, |
|
| 763 | + | }; |
|
| 764 | + | } |
|
| 765 | + | ||
| 766 | + | // Resolve all packages. |
|
| 767 | + | // TODO: Fix this error printing dance. |
|
| 768 | + | let diags = try resolver::resolve(&mut res, &ctx.graph, &packages[..ctx.packageCount]) catch { |
|
| 769 | + | let diags = resolver::Diagnostics { errors: res.errors }; |
|
| 770 | + | resolver::printer::printDiagnostics(&diags, &res); |
|
| 771 | + | throw Error::Other; |
|
| 772 | + | }; |
|
| 773 | + | if not resolver::success(&diags) { |
|
| 774 | + | resolver::printer::printDiagnostics(&diags, &res); |
|
| 775 | + | io::print("radiance: failed: "); |
|
| 776 | + | io::printU32(diags.errors.listLen); |
|
| 777 | + | io::printLn(" errors"); |
|
| 778 | + | throw Error::Other; |
|
| 779 | + | } |
|
| 780 | + | return res; |
|
| 781 | + | } |
|
| 782 | + | ||
| 783 | + | /// Lower, optionally dump, and optionally generate binary output. |
|
| 784 | + | fn compile(ctx: *mut CompileContext, res: *mut resolver::Resolver) throws (Error) { |
|
| 785 | + | let entryPkg = try getEntryPackage(ctx); |
|
| 786 | + | ||
| 787 | + | // Lower all packages into a single unified IL program. |
|
| 788 | + | // Dependencies must be lowered before the packages that use them. |
|
| 789 | + | let program = try lowerAllPackages(ctx, res); |
|
| 790 | + | let mut out = sexpr::Output::Stdout; |
|
| 791 | + | ||
| 792 | + | if ctx.dump == Dump::Il { |
|
| 793 | + | il::printer::printProgram(&mut out, &mut res.arena, &program); |
|
| 794 | + | io::print("\n"); |
|
| 795 | + | return; |
|
| 796 | + | } |
|
| 797 | + | if ctx.dump == Dump::Asm { |
|
| 798 | + | // TODO: Why do we generate code in two places? |
|
| 799 | + | let result = rv64::generate(&program, rv64::Storage { dataSyms: &mut CODEGEN_DATA_SYMS[..] }, &mut RO_DATA_BUF[..], &mut RW_DATA_BUF[..], &mut res.arena, false); |
|
| 800 | + | printer::printCodeTo(&mut out, entryPkg.name, result.code, result.funcs, &mut res.arena); |
|
| 801 | + | io::print("\n"); |
|
| 802 | + | return; |
|
| 803 | + | } |
|
| 804 | + | // Generate binary output if path specified. |
|
| 805 | + | let outPath = ctx.outputPath else { |
|
| 806 | + | return; |
|
| 807 | + | }; |
|
| 808 | + | // Check that we have an entry point. |
|
| 809 | + | if program.defaultFnIdx == nil { |
|
| 810 | + | io::printError("radiance: fatal: no default function found\n"); |
|
| 811 | + | throw Error::Other; |
|
| 812 | + | } |
|
| 813 | + | pkgLog(entryPkg, &["generating code", "(", outPath, ")", ".."]); |
|
| 814 | + | ||
| 815 | + | let storage = rv64::Storage { dataSyms: &mut CODEGEN_DATA_SYMS[..] }; |
|
| 816 | + | let result = rv64::generate(&program, storage, &mut RO_DATA_BUF[..], &mut RW_DATA_BUF[..], &mut res.arena, ctx.debug); |
|
| 817 | + | if not writeCode(result.code, outPath) { |
|
| 818 | + | io::printError("radiance: fatal: failed to write output file\n"); |
|
| 819 | + | throw Error::Other; |
|
| 820 | + | } |
|
| 821 | + | // Write data files. |
|
| 822 | + | try writeDataWithExt(&RO_DATA_BUF[..result.roDataSize], outPath, RO_DATA_EXT); |
|
| 823 | + | try writeDataWithExt(&RW_DATA_BUF[..result.rwDataSize], outPath, RW_DATA_EXT); |
|
| 824 | + | ||
| 825 | + | // Write debug info file if enabled. |
|
| 826 | + | if ctx.debug { |
|
| 827 | + | try writeDebugInfo(result.debugEntries, &ctx.graph, outPath, &mut res.arena); |
|
| 828 | + | } |
|
| 829 | + | pkgLog(entryPkg, &["ok", "(", outPath, ")"]); |
|
| 830 | + | } |
|
| 831 | + | ||
| 832 | + | @default fn main(env: *sys::Env) -> i32 { |
|
| 833 | + | let mut arena = ast::nodeArena(&mut TEMP_ARENA[..]); |
|
| 834 | + | let mut ctx = try processCommand(env.args, &mut arena) catch { |
|
| 835 | + | return 1; |
|
| 836 | + | }; |
|
| 837 | + | match ctx.dump { |
|
| 838 | + | case Dump::Ast => { |
|
| 839 | + | try dumpAst(&ctx) catch { |
|
| 840 | + | return 1; |
|
| 841 | + | }; |
|
| 842 | + | return 0; |
|
| 843 | + | } |
|
| 844 | + | case Dump::Graph => { |
|
| 845 | + | try dumpGraph(&ctx) catch { |
|
| 846 | + | return 1; |
|
| 847 | + | }; |
|
| 848 | + | return 0; |
|
| 849 | + | } |
|
| 850 | + | else => {} |
|
| 851 | + | } |
|
| 852 | + | // Generate test runner if in test mode. |
|
| 853 | + | if ctx.config.buildTest { |
|
| 854 | + | try generateTestRunner(&mut ctx, &mut arena) catch { |
|
| 855 | + | return 1; |
|
| 856 | + | }; |
|
| 857 | + | } |
|
| 858 | + | // Run resolution phase. |
|
| 859 | + | let mut res = try runResolver(&mut ctx, arena.nextId) catch { |
|
| 860 | + | return 1; |
|
| 861 | + | }; |
|
| 862 | + | // Lower, dump, and/or generate output. |
|
| 863 | + | try compile(&mut ctx, &mut res) catch { |
|
| 864 | + | return 1; |
|
| 865 | + | }; |
|
| 866 | + | return 0; |
|
| 867 | + | } |
lib/examples/env.rad
added
+14 -0
| 1 | + | use std::io; |
|
| 2 | + | use std::sys; |
|
| 3 | + | ||
| 4 | + | @default fn main(env: *sys::Env) -> i32 { |
|
| 5 | + | let args = env.args; |
|
| 6 | + | ||
| 7 | + | for arg, i in args { |
|
| 8 | + | io::printU32(i); |
|
| 9 | + | io::print(": "); |
|
| 10 | + | io::print(arg); |
|
| 11 | + | io::print("\n"); |
|
| 12 | + | } |
|
| 13 | + | return 0; |
|
| 14 | + | } |
lib/std.rad
added
+20 -0
| 1 | + | //! The Radiance Standard Library. |
|
| 2 | + | // TODO: The order of these module declarations matter, but they shouldn't. |
|
| 3 | + | ||
| 4 | + | // Nb: `testing` must come before all other modules so that `@test mod` |
|
| 5 | + | // submodules can `use std::testing` during resolution. |
|
| 6 | + | @test pub mod testing; |
|
| 7 | + | ||
| 8 | + | pub mod io; |
|
| 9 | + | pub mod collections; |
|
| 10 | + | pub mod lang; |
|
| 11 | + | pub mod sys; |
|
| 12 | + | pub mod debug; |
|
| 13 | + | pub mod arch; |
|
| 14 | + | pub mod fmt; |
|
| 15 | + | pub mod mem; |
|
| 16 | + | pub mod vec; |
|
| 17 | + | pub mod intrinsics; |
|
| 18 | + | ||
| 19 | + | // Test modules. |
|
| 20 | + | @test pub mod tests; |
lib/std/arch.rad
added
+2 -0
| 1 | + | //! RISC-V instruction set architecture encodings. |
|
| 2 | + | pub mod rv64; |
lib/std/arch/rv64.rad
added
+369 -0
| 1 | + | //! RV64 code generation backend. |
|
| 2 | + | //! |
|
| 3 | + | //! Generates RISC-V 64-bit machine code from IL (intermediate language). |
|
| 4 | + | //! |
|
| 5 | + | //! # Submodules |
|
| 6 | + | //! |
|
| 7 | + | //! * encode: Instruction encoding functions |
|
| 8 | + | //! * decode: Instruction decoding (for disassembly/printing) |
|
| 9 | + | //! * emit: Binary emission context and branch patching |
|
| 10 | + | //! * isel: Instruction selection (IL to RV64 instructions) |
|
| 11 | + | //! * printer: Assembly text output |
|
| 12 | + | ||
| 13 | + | pub mod encode; |
|
| 14 | + | pub mod decode; |
|
| 15 | + | pub mod emit; |
|
| 16 | + | pub mod isel; |
|
| 17 | + | pub mod printer; |
|
| 18 | + | ||
| 19 | + | @test mod tests; |
|
| 20 | + | ||
| 21 | + | use std::debug; |
|
| 22 | + | use std::mem; |
|
| 23 | + | use std::lang::il; |
|
| 24 | + | use std::lang::alloc; |
|
| 25 | + | use std::lang::gen::labels; |
|
| 26 | + | use std::lang::gen::regalloc; |
|
| 27 | + | ||
| 28 | + | //////////////// |
|
| 29 | + | // Registers // |
|
| 30 | + | //////////////// |
|
| 31 | + | ||
| 32 | + | /// RISC-V general-purpose register. |
|
| 33 | + | pub record Reg { n: u8 } |
|
| 34 | + | ||
| 35 | + | pub const ZERO: Reg = { n: 0 }; /// Hard-wired zero. |
|
| 36 | + | pub const RA: Reg = { n: 1 }; /// Return address. |
|
| 37 | + | pub const SP: Reg = { n: 2 }; /// Stack pointer. |
|
| 38 | + | pub const GP: Reg = { n: 3 }; /// Global pointer. |
|
| 39 | + | pub const TP: Reg = { n: 4 }; /// Thread pointer. |
|
| 40 | + | pub const T0: Reg = { n: 5 }; /// Temporary/alternate link register. |
|
| 41 | + | pub const T1: Reg = { n: 6 }; /// Temporary. |
|
| 42 | + | pub const T2: Reg = { n: 7 }; /// Temporary. |
|
| 43 | + | pub const S0: Reg = { n: 8 }; /// Saved register/frame pointer. |
|
| 44 | + | pub const FP: Reg = { n: 8 }; /// Frame pointer (alias for S0). |
|
| 45 | + | pub const S1: Reg = { n: 9 }; /// Saved register. |
|
| 46 | + | pub const A0: Reg = { n: 10 }; /// Function argument/return. |
|
| 47 | + | pub const A1: Reg = { n: 11 }; /// Function argument/return. |
|
| 48 | + | pub const A2: Reg = { n: 12 }; /// Function argument. |
|
| 49 | + | pub const A3: Reg = { n: 13 }; /// Function argument. |
|
| 50 | + | pub const A4: Reg = { n: 14 }; /// Function argument. |
|
| 51 | + | pub const A5: Reg = { n: 15 }; /// Function argument. |
|
| 52 | + | pub const A6: Reg = { n: 16 }; /// Function argument. |
|
| 53 | + | pub const A7: Reg = { n: 17 }; /// Function argument. |
|
| 54 | + | pub const S2: Reg = { n: 18 }; /// Saved register. |
|
| 55 | + | pub const S3: Reg = { n: 19 }; /// Saved register. |
|
| 56 | + | pub const S4: Reg = { n: 20 }; /// Saved register. |
|
| 57 | + | pub const S5: Reg = { n: 21 }; /// Saved register. |
|
| 58 | + | pub const S6: Reg = { n: 22 }; /// Saved register. |
|
| 59 | + | pub const S7: Reg = { n: 23 }; /// Saved register. |
|
| 60 | + | pub const S8: Reg = { n: 24 }; /// Saved register. |
|
| 61 | + | pub const S9: Reg = { n: 25 }; /// Saved register. |
|
| 62 | + | pub const S10: Reg = { n: 26 }; /// Saved register. |
|
| 63 | + | pub const S11: Reg = { n: 27 }; /// Saved register. |
|
| 64 | + | pub const T3: Reg = { n: 28 }; /// Temporary. |
|
| 65 | + | pub const T4: Reg = { n: 29 }; /// Temporary. |
|
| 66 | + | pub const T5: Reg = { n: 30 }; /// Temporary. |
|
| 67 | + | pub const T6: Reg = { n: 31 }; /// Temporary. |
|
| 68 | + | ||
| 69 | + | /// Create a register from a number. Panics if `n > 31`. |
|
| 70 | + | pub fn reg(n: u8) -> Reg { |
|
| 71 | + | debug::assert(n < 32); |
|
| 72 | + | return Reg { n }; |
|
| 73 | + | } |
|
| 74 | + | ||
| 75 | + | //////////////////////////// |
|
| 76 | + | // Architecture constants // |
|
| 77 | + | //////////////////////////// |
|
| 78 | + | ||
| 79 | + | /// Total number of general-purpose registers. |
|
| 80 | + | pub const NUM_REGISTERS: u8 = 32; |
|
| 81 | + | /// Word size in bytes (32-bit). |
|
| 82 | + | pub const WORD_SIZE: i32 = 4; |
|
| 83 | + | /// Doubleword size in bytes (64-bit). |
|
| 84 | + | pub const DWORD_SIZE: i32 = 8; |
|
| 85 | + | /// Instruction size in bytes. |
|
| 86 | + | pub const INSTR_SIZE: i32 = 4; |
|
| 87 | + | /// Stack alignment requirement in bytes. |
|
| 88 | + | pub const STACK_ALIGNMENT: i32 = 16; |
|
| 89 | + | ||
| 90 | + | ///////////////////////// |
|
| 91 | + | // Codegen Allocation // |
|
| 92 | + | ///////////////////////// |
|
| 93 | + | ||
| 94 | + | /// Argument registers for function calls. |
|
| 95 | + | pub const ARG_REGS: [Reg; 8] = [A0, A1, A2, A3, A4, A5, A6, A7]; |
|
| 96 | + | ||
| 97 | + | /// Scratch register for code gen. Never allocated to user values. |
|
| 98 | + | pub const SCRATCH1: Reg = { n: 30 }; |
|
| 99 | + | ||
| 100 | + | /// Second scratch register for operations needing two temporaries. |
|
| 101 | + | pub const SCRATCH2: Reg = { n: 31 }; |
|
| 102 | + | ||
| 103 | + | /// Dedicated scratch for address offset adjustment. Never allocated to user |
|
| 104 | + | /// values and never used for operand materialization, so it can never |
|
| 105 | + | /// conflict with `rd`, `rs`, or `base` in load/store helpers. |
|
| 106 | + | pub const ADDR_SCRATCH: Reg = { n: 29 }; |
|
| 107 | + | ||
| 108 | + | /// Callee-saved registers that need save/restore if used. |
|
| 109 | + | pub const CALLEE_SAVED: [Reg; 11] = [S1, S2, S3, S4, S5, S6, S7, S8, S9, S10, S11]; |
|
| 110 | + | ||
| 111 | + | /// Maximum 12-bit signed immediate value. |
|
| 112 | + | pub const MAX_IMM: i32 = 2047; |
|
| 113 | + | ||
| 114 | + | /// Minimum 12-bit signed immediate value. |
|
| 115 | + | pub const MIN_IMM: i32 = -2048; |
|
| 116 | + | ||
| 117 | + | /// Allocatable register numbers for register allocation. |
|
| 118 | + | const ALLOCATABLE_REGS: [u8; 23] = [ |
|
| 119 | + | 5, 6, 7, 28, // T0-T3 |
|
| 120 | + | 10, 11, 12, 13, 14, 15, 16, 17, // A0-A7 |
|
| 121 | + | 9, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, // S1-S11 |
|
| 122 | + | ]; |
|
| 123 | + | ||
| 124 | + | /// Argument register numbers for register allocation. |
|
| 125 | + | const ARG_REG_NUMS: [u8; 8] = [10, 11, 12, 13, 14, 15, 16, 17]; // A0-A7 |
|
| 126 | + | ||
| 127 | + | /// Callee-saved register numbers for frame layout. |
|
| 128 | + | const CALLEE_SAVED_NUMS: [u8; 11] = [9, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27]; // S1-S11 |
|
| 129 | + | ||
| 130 | + | /// Get target configuration for register allocation. |
|
| 131 | + | // TODO: This should be a constant variable. |
|
| 132 | + | pub fn targetConfig() -> regalloc::TargetConfig { |
|
| 133 | + | return regalloc::TargetConfig { |
|
| 134 | + | // TODO: Use inline slice when we move to self-hosted compiler. |
|
| 135 | + | allocatable: &ALLOCATABLE_REGS[..], |
|
| 136 | + | argRegs: &ARG_REG_NUMS[..], |
|
| 137 | + | calleeSaved: &CALLEE_SAVED_NUMS[..], |
|
| 138 | + | slotSize: DWORD_SIZE, |
|
| 139 | + | }; |
|
| 140 | + | } |
|
| 141 | + | ||
| 142 | + | /////////////////////// |
|
| 143 | + | // Codegen Constants // |
|
| 144 | + | /////////////////////// |
|
| 145 | + | ||
| 146 | + | /// Maximum number of data symbols. |
|
| 147 | + | pub const MAX_DATA_SYMS: u32 = 8192; |
|
| 148 | + | ||
| 149 | + | /// Base address where read-only data is loaded. |
|
| 150 | + | pub const RO_DATA_BASE: u32 = 0x10000; |
|
| 151 | + | ||
| 152 | + | /// Base address where read-write data is loaded. |
|
| 153 | + | pub const RW_DATA_BASE: u32 = 0xFFFFF0; |
|
| 154 | + | ||
| 155 | + | /// Data symbol entry mapping name to address. |
|
| 156 | + | pub record DataSym { |
|
| 157 | + | /// Symbol name. |
|
| 158 | + | name: *[u8], |
|
| 159 | + | /// Absolute address, including data base address. |
|
| 160 | + | addr: u32, |
|
| 161 | + | } |
|
| 162 | + | ||
| 163 | + | /// Storage buffers passed from driver for code generation. |
|
| 164 | + | pub record Storage { |
|
| 165 | + | /// Buffer for data symbols. |
|
| 166 | + | dataSyms: *mut [DataSym], |
|
| 167 | + | } |
|
| 168 | + | ||
| 169 | + | /// Result of code generation. |
|
| 170 | + | pub record Program { |
|
| 171 | + | /// Slice of emitted code. |
|
| 172 | + | code: *[u32], |
|
| 173 | + | /// Slice of function addresses (name + start index). |
|
| 174 | + | funcs: *[emit::FuncAddr], |
|
| 175 | + | /// Number of read-only data bytes emitted. |
|
| 176 | + | roDataSize: u32, |
|
| 177 | + | /// Number of read-write data bytes emitted. |
|
| 178 | + | rwDataSize: u32, |
|
| 179 | + | /// Debug entries mapping PCs to source locations. Empty when debug is off. |
|
| 180 | + | debugEntries: *[emit::DebugEntry], |
|
| 181 | + | } |
|
| 182 | + | ||
| 183 | + | /// Lay out data symbols for a single section. |
|
| 184 | + | /// Initialized data is placed first, then uninitialized, so that only |
|
| 185 | + | /// initialized data needs to be written to the output file. |
|
| 186 | + | /// Returns the updated offset past all placed symbols. |
|
| 187 | + | fn layoutSection( |
|
| 188 | + | program: *il::Program, |
|
| 189 | + | syms: *mut [DataSym], |
|
| 190 | + | count: *mut u32, |
|
| 191 | + | base: u32, |
|
| 192 | + | readOnly: bool |
|
| 193 | + | ) -> u32 { |
|
| 194 | + | let mut offset: u32 = 0; |
|
| 195 | + | ||
| 196 | + | // Initialized data first. |
|
| 197 | + | for i in 0..program.data.len { |
|
| 198 | + | let data = &program.data[i]; |
|
| 199 | + | if data.readOnly == readOnly and not data.isUndefined { |
|
| 200 | + | offset = mem::alignUp(offset, data.alignment); |
|
| 201 | + | syms[*count] = DataSym { name: data.name, addr: base + offset }; |
|
| 202 | + | *count = *count + 1; |
|
| 203 | + | offset = offset + data.size; |
|
| 204 | + | } |
|
| 205 | + | } |
|
| 206 | + | // Uninitialized data after. |
|
| 207 | + | for i in 0..program.data.len { |
|
| 208 | + | let data = &program.data[i]; |
|
| 209 | + | if data.readOnly == readOnly and data.isUndefined { |
|
| 210 | + | offset = mem::alignUp(offset, data.alignment); |
|
| 211 | + | syms[*count] = DataSym { name: data.name, addr: base + offset }; |
|
| 212 | + | *count = *count + 1; |
|
| 213 | + | offset = offset + data.size; |
|
| 214 | + | } |
|
| 215 | + | } |
|
| 216 | + | return offset; |
|
| 217 | + | } |
|
| 218 | + | ||
| 219 | + | /// Emit data bytes for a single section (read-only or read-write) into `buf`. |
|
| 220 | + | /// Iterates initialized data in the IL program, serializing each data item. |
|
| 221 | + | /// Returns the total number of bytes written. |
|
| 222 | + | pub fn emitSection( |
|
| 223 | + | program: *il::Program, |
|
| 224 | + | dataSyms: *[DataSym], |
|
| 225 | + | fnLabels: *labels::Labels, |
|
| 226 | + | codeBase: u32, |
|
| 227 | + | buf: *mut [u8], |
|
| 228 | + | readOnly: bool |
|
| 229 | + | ) -> u32 { |
|
| 230 | + | let mut offset: u32 = 0; |
|
| 231 | + | ||
| 232 | + | for i in 0..program.data.len { |
|
| 233 | + | let data = &program.data[i]; |
|
| 234 | + | if data.readOnly == readOnly and not data.isUndefined { |
|
| 235 | + | offset = mem::alignUp(offset, data.alignment); |
|
| 236 | + | if offset + data.size > buf.len { |
|
| 237 | + | panic "emitSection: buffer overflow"; |
|
| 238 | + | } |
|
| 239 | + | for j in 0..data.values.len { |
|
| 240 | + | let v = &data.values[j]; |
|
| 241 | + | for _ in 0..v.count { |
|
| 242 | + | match v.item { |
|
| 243 | + | case il::DataItem::Val { typ, val } => { |
|
| 244 | + | let size = il::typeSize(typ); |
|
| 245 | + | let valPtr = &val as *u8; |
|
| 246 | + | try! mem::copy(buf[offset..], @sliceOf(valPtr, size)); |
|
| 247 | + | offset = offset + size; |
|
| 248 | + | }, |
|
| 249 | + | case il::DataItem::Sym(name) => { |
|
| 250 | + | let addr = lookupDataSymAddr(dataSyms, name) else { |
|
| 251 | + | panic "emitSection: data symbol not found"; |
|
| 252 | + | }; |
|
| 253 | + | // Write 8-byte pointer: low 4 bytes are the |
|
| 254 | + | // address, high 4 bytes are zero. |
|
| 255 | + | // TODO: Use `u64` once it's supported. |
|
| 256 | + | let lo: u32 = addr; |
|
| 257 | + | let hi: u32 = 0; |
|
| 258 | + | let loPtr = &lo as *u8; |
|
| 259 | + | let hiPtr = &hi as *u8; |
|
| 260 | + | ||
| 261 | + | try! mem::copy(buf[offset..], @sliceOf(loPtr, 4)); |
|
| 262 | + | try! mem::copy(buf[(offset + 4)..], @sliceOf(hiPtr, 4)); |
|
| 263 | + | ||
| 264 | + | offset = offset + 8; |
|
| 265 | + | }, |
|
| 266 | + | case il::DataItem::Fn(name) => { |
|
| 267 | + | let addr = codeBase + labels::funcOffset(fnLabels, name) as u32; |
|
| 268 | + | let lo: u32 = addr; |
|
| 269 | + | let hi: u32 = 0; |
|
| 270 | + | let loPtr = &lo as *u8; |
|
| 271 | + | let hiPtr = &hi as *u8; |
|
| 272 | + | ||
| 273 | + | try! mem::copy(buf[offset..], @sliceOf(loPtr, 4)); |
|
| 274 | + | try! mem::copy(buf[(offset + 4)..], @sliceOf(hiPtr, 4)); |
|
| 275 | + | ||
| 276 | + | offset = offset + 8; |
|
| 277 | + | }, |
|
| 278 | + | case il::DataItem::Str(s) => { |
|
| 279 | + | try! mem::copy(buf[offset..], s); |
|
| 280 | + | offset = offset + s.len; |
|
| 281 | + | }, |
|
| 282 | + | case il::DataItem::Undef => { |
|
| 283 | + | buf[offset] = 0; |
|
| 284 | + | offset = offset + 1; |
|
| 285 | + | }, |
|
| 286 | + | } |
|
| 287 | + | } |
|
| 288 | + | } |
|
| 289 | + | } |
|
| 290 | + | } |
|
| 291 | + | return offset; |
|
| 292 | + | } |
|
| 293 | + | ||
| 294 | + | /// Resolve a data symbol to its final absolute address. |
|
| 295 | + | fn lookupDataSymAddr(dataSyms: *[DataSym], name: *[u8]) -> ?u32 { |
|
| 296 | + | for i in 0..dataSyms.len { |
|
| 297 | + | let sym = dataSyms[i]; |
|
| 298 | + | if mem::eq(sym.name, name) { |
|
| 299 | + | return sym.addr; |
|
| 300 | + | } |
|
| 301 | + | } |
|
| 302 | + | return nil; |
|
| 303 | + | } |
|
| 304 | + | ||
| 305 | + | /// Generate code for an IL program. |
|
| 306 | + | pub fn generate( |
|
| 307 | + | program: *il::Program, |
|
| 308 | + | storage: Storage, |
|
| 309 | + | roDataBuf: *mut [u8], |
|
| 310 | + | rwDataBuf: *mut [u8], |
|
| 311 | + | arena: *mut alloc::Arena, |
|
| 312 | + | debug: bool |
|
| 313 | + | ) -> Program { |
|
| 314 | + | let mut e = try! emit::emitter(arena, debug); |
|
| 315 | + | let config = targetConfig(); |
|
| 316 | + | ||
| 317 | + | // Build data map. |
|
| 318 | + | let mut dataSymCount: u32 = 0; |
|
| 319 | + | ||
| 320 | + | let roLayoutSize = layoutSection(program, storage.dataSyms, &mut dataSymCount, RO_DATA_BASE, true); |
|
| 321 | + | layoutSection(program, storage.dataSyms, &mut dataSymCount, RW_DATA_BASE, false); |
|
| 322 | + | ||
| 323 | + | // Code base address: code follows read-only data, aligned to dword boundary. |
|
| 324 | + | let codeBase = mem::alignUp(RO_DATA_BASE + roLayoutSize, DWORD_SIZE as u32); |
|
| 325 | + | ||
| 326 | + | // Emit placeholder entry jump to default function if there is one. |
|
| 327 | + | // We'll patch this at the end once we know where the function is. |
|
| 328 | + | let mut defaultName: ?*[u8] = nil; |
|
| 329 | + | if let defIdx = program.defaultFnIdx { |
|
| 330 | + | defaultName = program.fns[defIdx].name; |
|
| 331 | + | emit::emit(&mut e, encode::nop()); // Placeholder for two-instruction jump. |
|
| 332 | + | emit::emit(&mut e, encode::nop()); // |
|
| 333 | + | } |
|
| 334 | + | ||
| 335 | + | // Generate code for all functions. |
|
| 336 | + | let dataSyms = storage.dataSyms[..dataSymCount]; |
|
| 337 | + | ||
| 338 | + | for i in 0..program.fns.len { |
|
| 339 | + | let func = program.fns[i]; |
|
| 340 | + | if not func.isExtern { |
|
| 341 | + | let ralloc = try! regalloc::allocate(func, &config, arena); |
|
| 342 | + | isel::selectFn(&mut e, dataSyms, &ralloc, func); |
|
| 343 | + | } |
|
| 344 | + | } |
|
| 345 | + | // Patch entry jump now that we know where the default function is. |
|
| 346 | + | // Nb. we use a two-instruction jump even if it isn't always needed. |
|
| 347 | + | if let target = defaultName { |
|
| 348 | + | let offset = emit::branchOffsetToFunc(&e, 0, target); |
|
| 349 | + | let s = emit::splitImm(offset); |
|
| 350 | + | ||
| 351 | + | emit::patch(&mut e, 0, encode::auipc(SCRATCH1, s.hi)); |
|
| 352 | + | emit::patch(&mut e, 1, encode::jalr(ZERO, SCRATCH1, s.lo)); |
|
| 353 | + | } |
|
| 354 | + | // Patch function calls and address loads now that all functions are emitted. |
|
| 355 | + | emit::patchCalls(&mut e); |
|
| 356 | + | emit::patchAddrLoads(&mut e); |
|
| 357 | + | ||
| 358 | + | // Emit data sections. |
|
| 359 | + | let roDataSize = emitSection(program, dataSyms, &e.labels, codeBase, roDataBuf, true); |
|
| 360 | + | let rwDataSize = emitSection(program, dataSyms, &e.labels, codeBase, rwDataBuf, false); |
|
| 361 | + | ||
| 362 | + | return Program { |
|
| 363 | + | code: emit::getCode(&e), |
|
| 364 | + | funcs: emit::getFuncs(&e), |
|
| 365 | + | roDataSize, |
|
| 366 | + | rwDataSize, |
|
| 367 | + | debugEntries: emit::getDebugEntries(&e), |
|
| 368 | + | }; |
|
| 369 | + | } |
lib/std/arch/rv64/decode.rad
added
+374 -0
| 1 | + | //! RV64 instruction decoder. |
|
| 2 | + | //! |
|
| 3 | + | //! Decodes 32-bit instruction words into structured representations. |
|
| 4 | + | ||
| 5 | + | use super::encode; |
|
| 6 | + | ||
| 7 | + | /////////////////////// |
|
| 8 | + | // Field Extraction // |
|
| 9 | + | /////////////////////// |
|
| 10 | + | ||
| 11 | + | /// Extract opcode (bits 6:0). |
|
| 12 | + | pub fn opcode(instr: u32) -> u32 { |
|
| 13 | + | return instr & 0x7F; |
|
| 14 | + | } |
|
| 15 | + | ||
| 16 | + | /// Extract rd (bits 11:7). |
|
| 17 | + | pub fn rd(instr: u32) -> u8 { |
|
| 18 | + | return ((instr >> 7) & 0x1F) as u8; |
|
| 19 | + | } |
|
| 20 | + | ||
| 21 | + | /// Extract funct3 (bits 14:12). |
|
| 22 | + | pub fn funct3(instr: u32) -> u32 { |
|
| 23 | + | return (instr >> 12) & 0x07; |
|
| 24 | + | } |
|
| 25 | + | ||
| 26 | + | /// Extract rs1 (bits 19:15). |
|
| 27 | + | pub fn rs1(instr: u32) -> u8 { |
|
| 28 | + | return ((instr >> 15) & 0x1F) as u8; |
|
| 29 | + | } |
|
| 30 | + | ||
| 31 | + | /// Extract rs2 (bits 24:20). |
|
| 32 | + | pub fn rs2(instr: u32) -> u8 { |
|
| 33 | + | return ((instr >> 20) & 0x1F) as u8; |
|
| 34 | + | } |
|
| 35 | + | ||
| 36 | + | /// Extract funct7 (bits 31:25). |
|
| 37 | + | pub fn funct7(instr: u32) -> u32 { |
|
| 38 | + | return instr >> 25; |
|
| 39 | + | } |
|
| 40 | + | ||
| 41 | + | ////////////////////////// |
|
| 42 | + | // Immediate Extraction // |
|
| 43 | + | ////////////////////////// |
|
| 44 | + | ||
| 45 | + | /// Extract I-type immediate (sign-extended). |
|
| 46 | + | pub fn immI(instr: u32) -> i32 { |
|
| 47 | + | let imm = instr >> 20; |
|
| 48 | + | if (imm & 0x800) != 0 { |
|
| 49 | + | return (imm | 0xFFFFF000) as i32; |
|
| 50 | + | } |
|
| 51 | + | return imm as i32; |
|
| 52 | + | } |
|
| 53 | + | ||
| 54 | + | /// Extract S-type immediate (sign-extended). |
|
| 55 | + | pub fn immS(instr: u32) -> i32 { |
|
| 56 | + | let lo = (instr >> 7) & 0x1F; |
|
| 57 | + | let hi = (instr >> 25) & 0x7F; |
|
| 58 | + | let imm = (hi << 5) | lo; |
|
| 59 | + | if (imm & 0x800) != 0 { |
|
| 60 | + | return (imm | 0xFFFFF000) as i32; |
|
| 61 | + | } |
|
| 62 | + | return imm as i32; |
|
| 63 | + | } |
|
| 64 | + | ||
| 65 | + | /// Extract B-type immediate (sign-extended). |
|
| 66 | + | pub fn immB(instr: u32) -> i32 { |
|
| 67 | + | let imm11 = (instr >> 7) & 0x1; |
|
| 68 | + | let imm4_1 = (instr >> 8) & 0xF; |
|
| 69 | + | let imm10_5 = (instr >> 25) & 0x3F; |
|
| 70 | + | let imm12 = (instr >> 31) & 0x1; |
|
| 71 | + | let imm = (imm12 << 12) | (imm11 << 11) | (imm10_5 << 5) | (imm4_1 << 1); |
|
| 72 | + | if (imm & 0x1000) != 0 { |
|
| 73 | + | return (imm | 0xFFFFE000) as i32; |
|
| 74 | + | } |
|
| 75 | + | return imm as i32; |
|
| 76 | + | } |
|
| 77 | + | ||
| 78 | + | /// Extract J-type immediate (sign-extended). |
|
| 79 | + | pub fn immJ(instr: u32) -> i32 { |
|
| 80 | + | let imm19_12 = (instr >> 12) & 0xFF; |
|
| 81 | + | let imm11 = (instr >> 20) & 0x1; |
|
| 82 | + | let imm10_1 = (instr >> 21) & 0x3FF; |
|
| 83 | + | let imm20 = (instr >> 31) & 0x1; |
|
| 84 | + | let imm = (imm20 << 20) | (imm19_12 << 12) | (imm11 << 11) | (imm10_1 << 1); |
|
| 85 | + | if (imm & 0x100000) != 0 { |
|
| 86 | + | return (imm | 0xFFE00000) as i32; |
|
| 87 | + | } |
|
| 88 | + | return imm as i32; |
|
| 89 | + | } |
|
| 90 | + | ||
| 91 | + | /// Extract U-type immediate (upper 20 bits, sign-extended). |
|
| 92 | + | pub fn immU(instr: u32) -> i32 { |
|
| 93 | + | let imm = instr >> 12; |
|
| 94 | + | if (imm & 0x80000) != 0 { |
|
| 95 | + | return (imm | 0xFFF00000) as i32; |
|
| 96 | + | } |
|
| 97 | + | return imm as i32; |
|
| 98 | + | } |
|
| 99 | + | ||
| 100 | + | ///////////////////////// |
|
| 101 | + | // Decoded Instruction // |
|
| 102 | + | ///////////////////////// |
|
| 103 | + | ||
| 104 | + | /// Decoded RV64 instruction. |
|
| 105 | + | pub union Instr { |
|
| 106 | + | // R-type ALU. |
|
| 107 | + | Add { rd: super::Reg, rs1: super::Reg, rs2: super::Reg }, |
|
| 108 | + | Sub { rd: super::Reg, rs1: super::Reg, rs2: super::Reg }, |
|
| 109 | + | Sll { rd: super::Reg, rs1: super::Reg, rs2: super::Reg }, |
|
| 110 | + | Slt { rd: super::Reg, rs1: super::Reg, rs2: super::Reg }, |
|
| 111 | + | Sltu { rd: super::Reg, rs1: super::Reg, rs2: super::Reg }, |
|
| 112 | + | Xor { rd: super::Reg, rs1: super::Reg, rs2: super::Reg }, |
|
| 113 | + | Srl { rd: super::Reg, rs1: super::Reg, rs2: super::Reg }, |
|
| 114 | + | Sra { rd: super::Reg, rs1: super::Reg, rs2: super::Reg }, |
|
| 115 | + | Or { rd: super::Reg, rs1: super::Reg, rs2: super::Reg }, |
|
| 116 | + | And { rd: super::Reg, rs1: super::Reg, rs2: super::Reg }, |
|
| 117 | + | ||
| 118 | + | // R-type M extension. |
|
| 119 | + | Mul { rd: super::Reg, rs1: super::Reg, rs2: super::Reg }, |
|
| 120 | + | Mulh { rd: super::Reg, rs1: super::Reg, rs2: super::Reg }, |
|
| 121 | + | Mulhsu { rd: super::Reg, rs1: super::Reg, rs2: super::Reg }, |
|
| 122 | + | Mulhu { rd: super::Reg, rs1: super::Reg, rs2: super::Reg }, |
|
| 123 | + | Div { rd: super::Reg, rs1: super::Reg, rs2: super::Reg }, |
|
| 124 | + | Divu { rd: super::Reg, rs1: super::Reg, rs2: super::Reg }, |
|
| 125 | + | Rem { rd: super::Reg, rs1: super::Reg, rs2: super::Reg }, |
|
| 126 | + | Remu { rd: super::Reg, rs1: super::Reg, rs2: super::Reg }, |
|
| 127 | + | ||
| 128 | + | // R-type RV64 word operations. |
|
| 129 | + | Addw { rd: super::Reg, rs1: super::Reg, rs2: super::Reg }, |
|
| 130 | + | Subw { rd: super::Reg, rs1: super::Reg, rs2: super::Reg }, |
|
| 131 | + | Sllw { rd: super::Reg, rs1: super::Reg, rs2: super::Reg }, |
|
| 132 | + | Srlw { rd: super::Reg, rs1: super::Reg, rs2: super::Reg }, |
|
| 133 | + | Sraw { rd: super::Reg, rs1: super::Reg, rs2: super::Reg }, |
|
| 134 | + | Mulw { rd: super::Reg, rs1: super::Reg, rs2: super::Reg }, |
|
| 135 | + | Divw { rd: super::Reg, rs1: super::Reg, rs2: super::Reg }, |
|
| 136 | + | Divuw { rd: super::Reg, rs1: super::Reg, rs2: super::Reg }, |
|
| 137 | + | Remw { rd: super::Reg, rs1: super::Reg, rs2: super::Reg }, |
|
| 138 | + | Remuw { rd: super::Reg, rs1: super::Reg, rs2: super::Reg }, |
|
| 139 | + | ||
| 140 | + | // I-type ALU. |
|
| 141 | + | Addi { rd: super::Reg, rs1: super::Reg, imm: i32 }, |
|
| 142 | + | Slti { rd: super::Reg, rs1: super::Reg, imm: i32 }, |
|
| 143 | + | Sltiu { rd: super::Reg, rs1: super::Reg, imm: i32 }, |
|
| 144 | + | Xori { rd: super::Reg, rs1: super::Reg, imm: i32 }, |
|
| 145 | + | Ori { rd: super::Reg, rs1: super::Reg, imm: i32 }, |
|
| 146 | + | Andi { rd: super::Reg, rs1: super::Reg, imm: i32 }, |
|
| 147 | + | Slli { rd: super::Reg, rs1: super::Reg, shamt: i32 }, |
|
| 148 | + | Srli { rd: super::Reg, rs1: super::Reg, shamt: i32 }, |
|
| 149 | + | Srai { rd: super::Reg, rs1: super::Reg, shamt: i32 }, |
|
| 150 | + | ||
| 151 | + | // I-type RV64 word immediate operations. |
|
| 152 | + | Addiw { rd: super::Reg, rs1: super::Reg, imm: i32 }, |
|
| 153 | + | Slliw { rd: super::Reg, rs1: super::Reg, shamt: i32 }, |
|
| 154 | + | Srliw { rd: super::Reg, rs1: super::Reg, shamt: i32 }, |
|
| 155 | + | Sraiw { rd: super::Reg, rs1: super::Reg, shamt: i32 }, |
|
| 156 | + | ||
| 157 | + | // Load instructions. |
|
| 158 | + | Lb { rd: super::Reg, rs1: super::Reg, imm: i32 }, |
|
| 159 | + | Lh { rd: super::Reg, rs1: super::Reg, imm: i32 }, |
|
| 160 | + | Lw { rd: super::Reg, rs1: super::Reg, imm: i32 }, |
|
| 161 | + | Ld { rd: super::Reg, rs1: super::Reg, imm: i32 }, |
|
| 162 | + | Lbu { rd: super::Reg, rs1: super::Reg, imm: i32 }, |
|
| 163 | + | Lhu { rd: super::Reg, rs1: super::Reg, imm: i32 }, |
|
| 164 | + | Lwu { rd: super::Reg, rs1: super::Reg, imm: i32 }, |
|
| 165 | + | ||
| 166 | + | // Store instructions. |
|
| 167 | + | Sb { rs2: super::Reg, rs1: super::Reg, imm: i32 }, |
|
| 168 | + | Sh { rs2: super::Reg, rs1: super::Reg, imm: i32 }, |
|
| 169 | + | Sw { rs2: super::Reg, rs1: super::Reg, imm: i32 }, |
|
| 170 | + | Sd { rs2: super::Reg, rs1: super::Reg, imm: i32 }, |
|
| 171 | + | ||
| 172 | + | // Branch instructions. |
|
| 173 | + | Beq { rs1: super::Reg, rs2: super::Reg, imm: i32 }, |
|
| 174 | + | Bne { rs1: super::Reg, rs2: super::Reg, imm: i32 }, |
|
| 175 | + | Blt { rs1: super::Reg, rs2: super::Reg, imm: i32 }, |
|
| 176 | + | Bge { rs1: super::Reg, rs2: super::Reg, imm: i32 }, |
|
| 177 | + | Bltu { rs1: super::Reg, rs2: super::Reg, imm: i32 }, |
|
| 178 | + | Bgeu { rs1: super::Reg, rs2: super::Reg, imm: i32 }, |
|
| 179 | + | ||
| 180 | + | // Jump instructions. |
|
| 181 | + | Jal { rd: super::Reg, imm: i32 }, |
|
| 182 | + | Jalr { rd: super::Reg, rs1: super::Reg, imm: i32 }, |
|
| 183 | + | ||
| 184 | + | // Upper immediate. |
|
| 185 | + | Lui { rd: super::Reg, imm: i32 }, |
|
| 186 | + | Auipc { rd: super::Reg, imm: i32 }, |
|
| 187 | + | ||
| 188 | + | // System. |
|
| 189 | + | Ecall, |
|
| 190 | + | Ebreak, |
|
| 191 | + | ||
| 192 | + | // Unknown/invalid instruction. |
|
| 193 | + | Unknown { bits: u32 }, |
|
| 194 | + | } |
|
| 195 | + | ||
| 196 | + | /// Decode a 32-bit instruction word into an [`Instr`]. |
|
| 197 | + | pub fn decode(instr: u32) -> Instr { |
|
| 198 | + | let op = opcode(instr); |
|
| 199 | + | let f3 = funct3(instr); |
|
| 200 | + | let f7 = funct7(instr); |
|
| 201 | + | let rd = super::reg(rd(instr)); |
|
| 202 | + | let rs1 = super::reg(rs1(instr)); |
|
| 203 | + | let rs2 = super::reg(rs2(instr)); |
|
| 204 | + | ||
| 205 | + | match op { |
|
| 206 | + | case encode::OP_LUI => { |
|
| 207 | + | return Instr::Lui { rd, imm: immU(instr) }; |
|
| 208 | + | }, |
|
| 209 | + | case encode::OP_AUIPC => { |
|
| 210 | + | return Instr::Auipc { rd, imm: immU(instr) }; |
|
| 211 | + | }, |
|
| 212 | + | case encode::OP_JAL => { |
|
| 213 | + | return Instr::Jal { rd, imm: immJ(instr) }; |
|
| 214 | + | }, |
|
| 215 | + | case encode::OP_JALR => { |
|
| 216 | + | return Instr::Jalr { rd, rs1, imm: immI(instr) }; |
|
| 217 | + | }, |
|
| 218 | + | case encode::OP_BRANCH => { |
|
| 219 | + | let imm = immB(instr); |
|
| 220 | + | match f3 { |
|
| 221 | + | case encode::F3_BEQ => return Instr::Beq { rs1, rs2, imm }, |
|
| 222 | + | case encode::F3_BNE => return Instr::Bne { rs1, rs2, imm }, |
|
| 223 | + | case encode::F3_BLT => return Instr::Blt { rs1, rs2, imm }, |
|
| 224 | + | case encode::F3_BGE => return Instr::Bge { rs1, rs2, imm }, |
|
| 225 | + | case encode::F3_BLTU => return Instr::Bltu { rs1, rs2, imm }, |
|
| 226 | + | case encode::F3_BGEU => return Instr::Bgeu { rs1, rs2, imm }, |
|
| 227 | + | else => return Instr::Unknown { bits: instr }, |
|
| 228 | + | } |
|
| 229 | + | }, |
|
| 230 | + | case encode::OP_LOAD => { |
|
| 231 | + | let imm = immI(instr); |
|
| 232 | + | match f3 { |
|
| 233 | + | case encode::F3_BYTE => return Instr::Lb { rd, rs1, imm }, |
|
| 234 | + | case encode::F3_HALF => return Instr::Lh { rd, rs1, imm }, |
|
| 235 | + | case encode::F3_WORD => return Instr::Lw { rd, rs1, imm }, |
|
| 236 | + | case encode::F3_DWORD => return Instr::Ld { rd, rs1, imm }, |
|
| 237 | + | case encode::F3_BYTE_U => return Instr::Lbu { rd, rs1, imm }, |
|
| 238 | + | case encode::F3_HALF_U => return Instr::Lhu { rd, rs1, imm }, |
|
| 239 | + | case encode::F3_WORD_U => return Instr::Lwu { rd, rs1, imm }, |
|
| 240 | + | else => return Instr::Unknown { bits: instr }, |
|
| 241 | + | } |
|
| 242 | + | }, |
|
| 243 | + | case encode::OP_STORE => { |
|
| 244 | + | let imm = immS(instr); |
|
| 245 | + | match f3 { |
|
| 246 | + | case encode::F3_BYTE => return Instr::Sb { rs2, rs1, imm }, |
|
| 247 | + | case encode::F3_HALF => return Instr::Sh { rs2, rs1, imm }, |
|
| 248 | + | case encode::F3_WORD => return Instr::Sw { rs2, rs1, imm }, |
|
| 249 | + | case encode::F3_DWORD => return Instr::Sd { rs2, rs1, imm }, |
|
| 250 | + | else => return Instr::Unknown { bits: instr }, |
|
| 251 | + | } |
|
| 252 | + | }, |
|
| 253 | + | case encode::OP_IMM => { |
|
| 254 | + | let imm = immI(instr); |
|
| 255 | + | match f3 { |
|
| 256 | + | case encode::F3_ADD => return Instr::Addi { rd, rs1, imm }, |
|
| 257 | + | case encode::F3_SLT => return Instr::Slti { rd, rs1, imm }, |
|
| 258 | + | case encode::F3_SLTU => return Instr::Sltiu { rd, rs1, imm }, |
|
| 259 | + | case encode::F3_XOR => return Instr::Xori { rd, rs1, imm }, |
|
| 260 | + | case encode::F3_OR => return Instr::Ori { rd, rs1, imm }, |
|
| 261 | + | case encode::F3_AND => return Instr::Andi { rd, rs1, imm }, |
|
| 262 | + | case encode::F3_SLL => return Instr::Slli { rd, rs1, shamt: imm & 0x3F }, |
|
| 263 | + | case encode::F3_SRL => { |
|
| 264 | + | let shamt = imm & 0x3F; |
|
| 265 | + | if (imm & 0x400) != 0 { |
|
| 266 | + | return Instr::Srai { rd, rs1, shamt }; |
|
| 267 | + | } else { |
|
| 268 | + | return Instr::Srli { rd, rs1, shamt }; |
|
| 269 | + | } |
|
| 270 | + | }, |
|
| 271 | + | else => return Instr::Unknown { bits: instr }, |
|
| 272 | + | } |
|
| 273 | + | }, |
|
| 274 | + | case encode::OP_OP => { |
|
| 275 | + | if f7 == encode::F7_MUL { |
|
| 276 | + | match f3 { |
|
| 277 | + | case encode::F3_ADD => return Instr::Mul { rd, rs1, rs2 }, |
|
| 278 | + | case encode::F3_SLL => return Instr::Mulh { rd, rs1, rs2 }, |
|
| 279 | + | case encode::F3_SLT => return Instr::Mulhsu { rd, rs1, rs2 }, |
|
| 280 | + | case encode::F3_SLTU => return Instr::Mulhu { rd, rs1, rs2 }, |
|
| 281 | + | case encode::F3_XOR => return Instr::Div { rd, rs1, rs2 }, |
|
| 282 | + | case encode::F3_SRL => return Instr::Divu { rd, rs1, rs2 }, |
|
| 283 | + | case encode::F3_OR => return Instr::Rem { rd, rs1, rs2 }, |
|
| 284 | + | case encode::F3_AND => return Instr::Remu { rd, rs1, rs2 }, |
|
| 285 | + | else => return Instr::Unknown { bits: instr }, |
|
| 286 | + | } |
|
| 287 | + | } else { |
|
| 288 | + | match f3 { |
|
| 289 | + | case encode::F3_ADD => { |
|
| 290 | + | if f7 == encode::F7_SUB { |
|
| 291 | + | return Instr::Sub { rd, rs1, rs2 }; |
|
| 292 | + | } else { |
|
| 293 | + | return Instr::Add { rd, rs1, rs2 }; |
|
| 294 | + | } |
|
| 295 | + | }, |
|
| 296 | + | case encode::F3_SLL => return Instr::Sll { rd, rs1, rs2 }, |
|
| 297 | + | case encode::F3_SLT => return Instr::Slt { rd, rs1, rs2 }, |
|
| 298 | + | case encode::F3_SLTU => return Instr::Sltu { rd, rs1, rs2 }, |
|
| 299 | + | case encode::F3_XOR => return Instr::Xor { rd, rs1, rs2 }, |
|
| 300 | + | case encode::F3_SRL => { |
|
| 301 | + | if f7 == encode::F7_SRA { |
|
| 302 | + | return Instr::Sra { rd, rs1, rs2 }; |
|
| 303 | + | } else { |
|
| 304 | + | return Instr::Srl { rd, rs1, rs2 }; |
|
| 305 | + | } |
|
| 306 | + | }, |
|
| 307 | + | case encode::F3_OR => return Instr::Or { rd, rs1, rs2 }, |
|
| 308 | + | case encode::F3_AND => return Instr::And { rd, rs1, rs2 }, |
|
| 309 | + | else => return Instr::Unknown { bits: instr }, |
|
| 310 | + | } |
|
| 311 | + | } |
|
| 312 | + | }, |
|
| 313 | + | case encode::OP_IMM32 => { |
|
| 314 | + | let imm = immI(instr); |
|
| 315 | + | match f3 { |
|
| 316 | + | case encode::F3_ADD => return Instr::Addiw { rd, rs1, imm }, |
|
| 317 | + | case encode::F3_SLL => return Instr::Slliw { rd, rs1, shamt: imm & 0x1F }, |
|
| 318 | + | case encode::F3_SRL => { |
|
| 319 | + | let shamt = imm & 0x1F; |
|
| 320 | + | if (imm & 0x400) != 0 { |
|
| 321 | + | return Instr::Sraiw { rd, rs1, shamt }; |
|
| 322 | + | } else { |
|
| 323 | + | return Instr::Srliw { rd, rs1, shamt }; |
|
| 324 | + | } |
|
| 325 | + | }, |
|
| 326 | + | else => return Instr::Unknown { bits: instr }, |
|
| 327 | + | } |
|
| 328 | + | }, |
|
| 329 | + | case encode::OP_OP32 => { |
|
| 330 | + | if f7 == encode::F7_MUL { |
|
| 331 | + | match f3 { |
|
| 332 | + | case encode::F3_ADD => return Instr::Mulw { rd, rs1, rs2 }, |
|
| 333 | + | case encode::F3_XOR => return Instr::Divw { rd, rs1, rs2 }, |
|
| 334 | + | case encode::F3_SRL => return Instr::Divuw { rd, rs1, rs2 }, |
|
| 335 | + | case encode::F3_OR => return Instr::Remw { rd, rs1, rs2 }, |
|
| 336 | + | case encode::F3_AND => return Instr::Remuw { rd, rs1, rs2 }, |
|
| 337 | + | else => return Instr::Unknown { bits: instr }, |
|
| 338 | + | } |
|
| 339 | + | } else { |
|
| 340 | + | match f3 { |
|
| 341 | + | case encode::F3_ADD => { |
|
| 342 | + | if f7 == encode::F7_SUB { |
|
| 343 | + | return Instr::Subw { rd, rs1, rs2 }; |
|
| 344 | + | } else { |
|
| 345 | + | return Instr::Addw { rd, rs1, rs2 }; |
|
| 346 | + | } |
|
| 347 | + | }, |
|
| 348 | + | case encode::F3_SLL => return Instr::Sllw { rd, rs1, rs2 }, |
|
| 349 | + | case encode::F3_SRL => { |
|
| 350 | + | if f7 == encode::F7_SRA { |
|
| 351 | + | return Instr::Sraw { rd, rs1, rs2 }; |
|
| 352 | + | } else { |
|
| 353 | + | return Instr::Srlw { rd, rs1, rs2 }; |
|
| 354 | + | } |
|
| 355 | + | }, |
|
| 356 | + | else => return Instr::Unknown { bits: instr }, |
|
| 357 | + | } |
|
| 358 | + | } |
|
| 359 | + | }, |
|
| 360 | + | case encode::OP_SYSTEM => { |
|
| 361 | + | let imm = immI(instr); |
|
| 362 | + | if imm == 0 { |
|
| 363 | + | return Instr::Ecall; |
|
| 364 | + | } else if imm == 1 { |
|
| 365 | + | return Instr::Ebreak; |
|
| 366 | + | } else { |
|
| 367 | + | return Instr::Unknown { bits: instr }; |
|
| 368 | + | } |
|
| 369 | + | }, |
|
| 370 | + | else => { |
|
| 371 | + | return Instr::Unknown { bits: instr }; |
|
| 372 | + | } |
|
| 373 | + | } |
|
| 374 | + | } |
lib/std/arch/rv64/emit.rad
added
+670 -0
| 1 | + | //! RV64 binary emission. |
|
| 2 | + | //! |
|
| 3 | + | //! Emits RV64 machine code as `u32` list. |
|
| 4 | + | ||
| 5 | + | use std::debug; |
|
| 6 | + | use std::lang::il; |
|
| 7 | + | use std::lang::alloc; |
|
| 8 | + | use std::lang::gen::labels; |
|
| 9 | + | use std::collections::dict; |
|
| 10 | + | use std::mem; |
|
| 11 | + | ||
| 12 | + | use super::encode; |
|
| 13 | + | ||
| 14 | + | /// Maximum number of instructions in code buffer. |
|
| 15 | + | const MAX_INSTRS: u32 = 2097152; |
|
| 16 | + | /// Maximum code length before byte offset overflows 32-bits. |
|
| 17 | + | /// Computed as `0x7FFFFFFF` / [`super::INSTR_SIZE`]. |
|
| 18 | + | const MAX_CODE_LEN: u32 = 0x3FFFFFFE; |
|
| 19 | + | /// Maximum number of pending branches awaiting patching. |
|
| 20 | + | const MAX_PENDING: u32 = 65536; |
|
| 21 | + | /// Maximum number of function entries. |
|
| 22 | + | const MAX_FUNCS: u32 = 4096; |
|
| 23 | + | /// Maximum number of debug entries. |
|
| 24 | + | const MAX_DEBUG_ENTRIES: u32 = 524288; |
|
| 25 | + | ||
| 26 | + | ////////////////////// |
|
| 27 | + | // Emission Context // |
|
| 28 | + | ////////////////////// |
|
| 29 | + | ||
| 30 | + | /// Branch/jump that needs offset patching after all blocks are emitted. |
|
| 31 | + | pub record PendingBranch { |
|
| 32 | + | /// Index into code buffer where the branch instruction is. |
|
| 33 | + | index: u32, |
|
| 34 | + | /// Target block index. |
|
| 35 | + | target: u32, |
|
| 36 | + | /// Type of branch for re-encoding. |
|
| 37 | + | kind: BranchKind, |
|
| 38 | + | } |
|
| 39 | + | ||
| 40 | + | /// Type of branch instruction. |
|
| 41 | + | pub union BranchKind { |
|
| 42 | + | /// Conditional branch (B-type encoding). |
|
| 43 | + | Cond { op: il::CmpOp, rs1: super::Reg, rs2: super::Reg }, |
|
| 44 | + | /// Inverted conditional branch (B-type encoding with negated condition). |
|
| 45 | + | InvertedCond { op: il::CmpOp, rs1: super::Reg, rs2: super::Reg }, |
|
| 46 | + | /// Unconditional jump (J-type encoding). |
|
| 47 | + | Jump, |
|
| 48 | + | } |
|
| 49 | + | ||
| 50 | + | /// Function call that needs offset patching. |
|
| 51 | + | pub record PendingCall { |
|
| 52 | + | /// Index in code buffer where the call was emitted. |
|
| 53 | + | index: u32, |
|
| 54 | + | /// Target function name. |
|
| 55 | + | target: *[u8], |
|
| 56 | + | } |
|
| 57 | + | ||
| 58 | + | /// Function address load that needs offset patching. |
|
| 59 | + | /// Used when taking a function's address as a value. |
|
| 60 | + | pub record PendingAddrLoad { |
|
| 61 | + | /// Index in code buffer where the load was emitted. |
|
| 62 | + | index: u32, |
|
| 63 | + | /// Target function name. |
|
| 64 | + | target: *[u8], |
|
| 65 | + | /// Destination register. |
|
| 66 | + | rd: super::Reg, |
|
| 67 | + | } |
|
| 68 | + | ||
| 69 | + | /// Function address entry for printing. |
|
| 70 | + | pub record FuncAddr { |
|
| 71 | + | /// Function name. |
|
| 72 | + | name: *[u8], |
|
| 73 | + | /// Instruction index where this function starts. |
|
| 74 | + | index: u32, |
|
| 75 | + | } |
|
| 76 | + | ||
| 77 | + | /// Debug entry mapping an instruction to a source location. |
|
| 78 | + | pub record DebugEntry { |
|
| 79 | + | /// Byte offset of the instruction from the start of the program. |
|
| 80 | + | pc: u32, |
|
| 81 | + | /// Module identifier. |
|
| 82 | + | moduleId: u16, |
|
| 83 | + | /// Byte offset into the module's source file. |
|
| 84 | + | offset: u32, |
|
| 85 | + | } |
|
| 86 | + | ||
| 87 | + | /// Adjusted base register and offset for addressing. |
|
| 88 | + | pub record AdjustedOffset { |
|
| 89 | + | /// Base register. |
|
| 90 | + | base: super::Reg, |
|
| 91 | + | /// Byte offset from register. |
|
| 92 | + | offset: i32, |
|
| 93 | + | } |
|
| 94 | + | ||
| 95 | + | /// Callee-saved register with its stack offset. |
|
| 96 | + | pub record SavedReg { |
|
| 97 | + | /// Register to save/restore. |
|
| 98 | + | reg: super::Reg, |
|
| 99 | + | /// Offset from SP. |
|
| 100 | + | offset: i32, |
|
| 101 | + | } |
|
| 102 | + | ||
| 103 | + | /// Emission context. Tracks state during code generation. |
|
| 104 | + | pub record Emitter { |
|
| 105 | + | /// Emitted instructions storage. |
|
| 106 | + | code: *mut [u32], |
|
| 107 | + | /// Current number of emitted instructions. |
|
| 108 | + | codeLen: u32, |
|
| 109 | + | /// Local branches needing offset patching. |
|
| 110 | + | pendingBranches: *mut [PendingBranch], |
|
| 111 | + | /// Number of pending local branches. |
|
| 112 | + | pendingBranchesLen: u32, |
|
| 113 | + | /// Function calls needing offset patching. |
|
| 114 | + | pendingCalls: *mut [PendingCall], |
|
| 115 | + | /// Number of pending calls. |
|
| 116 | + | pendingCallsLen: u32, |
|
| 117 | + | /// Function address loads needing offset patching. |
|
| 118 | + | pendingAddrLoads: *mut [PendingAddrLoad], |
|
| 119 | + | /// Number of pending address loads. |
|
| 120 | + | pendingAddrLoadsLen: u32, |
|
| 121 | + | /// Block label tracking. |
|
| 122 | + | labels: labels::Labels, |
|
| 123 | + | /// Function start positions for printing. |
|
| 124 | + | funcs: *mut [FuncAddr], |
|
| 125 | + | /// Number of recorded functions. |
|
| 126 | + | funcsLen: u32, |
|
| 127 | + | /// Debug entries mapping PCs to source locations. |
|
| 128 | + | debugEntries: *mut [DebugEntry], |
|
| 129 | + | /// Number of debug entries recorded. |
|
| 130 | + | debugEntriesLen: u32, |
|
| 131 | + | } |
|
| 132 | + | ||
| 133 | + | /// Computed stack frame layout for a function. |
|
| 134 | + | pub record Frame { |
|
| 135 | + | /// Total frame size in bytes (aligned). |
|
| 136 | + | totalSize: i32, |
|
| 137 | + | /// Callee-saved registers and their offsets. |
|
| 138 | + | // TODO: Use constant length when language supports it. |
|
| 139 | + | savedRegs: [SavedReg; 11], |
|
| 140 | + | /// Number of saved registers. |
|
| 141 | + | savedRegsLen: u32, |
|
| 142 | + | /// Epilogue block index for return jumps. |
|
| 143 | + | epilogueBlock: u32, |
|
| 144 | + | } |
|
| 145 | + | ||
| 146 | + | /// Compute frame layout from local size and used callee-saved registers. |
|
| 147 | + | pub fn computeFrame(localSize: i32, usedCalleeSaved: u32, epilogueBlock: u32) -> Frame { |
|
| 148 | + | let mut frame = Frame { |
|
| 149 | + | totalSize: 0, |
|
| 150 | + | savedRegs: undefined, |
|
| 151 | + | savedRegsLen: 0, |
|
| 152 | + | epilogueBlock, |
|
| 153 | + | }; |
|
| 154 | + | // TODO: Skip frame allocation for leaf functions with no locals. |
|
| 155 | + | // Compute total frame size. Includes RA and SP registers. |
|
| 156 | + | let savedRegs = mem::popCount(usedCalleeSaved) + 2; |
|
| 157 | + | let totalSize = mem::alignUpI32( |
|
| 158 | + | localSize + savedRegs * super::DWORD_SIZE, |
|
| 159 | + | super::STACK_ALIGNMENT |
|
| 160 | + | ); |
|
| 161 | + | frame.totalSize = totalSize; |
|
| 162 | + | ||
| 163 | + | // Build list of callee-saved registers with offsets. |
|
| 164 | + | let mut offset = totalSize - (super::DWORD_SIZE * 3); |
|
| 165 | + | for i in 0..super::CALLEE_SAVED.len { |
|
| 166 | + | // Check if this register is in use. |
|
| 167 | + | if (usedCalleeSaved & (1 << i)) != 0 { |
|
| 168 | + | frame.savedRegs[frame.savedRegsLen] = SavedReg { |
|
| 169 | + | reg: super::CALLEE_SAVED[i], |
|
| 170 | + | offset, |
|
| 171 | + | }; |
|
| 172 | + | frame.savedRegsLen = frame.savedRegsLen + 1; |
|
| 173 | + | offset = offset - super::DWORD_SIZE; |
|
| 174 | + | } |
|
| 175 | + | } |
|
| 176 | + | return frame; |
|
| 177 | + | } |
|
| 178 | + | ||
| 179 | + | /// Create a new emitter. |
|
| 180 | + | pub fn emitter(arena: *mut alloc::Arena, debug: bool) -> Emitter throws (alloc::AllocError) { |
|
| 181 | + | let code = try alloc::allocSlice(arena, @sizeOf(u32), @alignOf(u32), MAX_INSTRS); |
|
| 182 | + | let pendingBranches = try alloc::allocSlice(arena, @sizeOf(PendingBranch), @alignOf(PendingBranch), MAX_PENDING); |
|
| 183 | + | let pendingCalls = try alloc::allocSlice(arena, @sizeOf(PendingCall), @alignOf(PendingCall), MAX_PENDING); |
|
| 184 | + | let pendingAddrLoads = try alloc::allocSlice(arena, @sizeOf(PendingAddrLoad), @alignOf(PendingAddrLoad), MAX_PENDING); |
|
| 185 | + | let blockOffsets = try alloc::allocSlice(arena, @sizeOf(i32), @alignOf(i32), labels::MAX_BLOCKS_PER_FN); |
|
| 186 | + | let funcEntries = try alloc::allocSlice(arena, @sizeOf(dict::Entry), @alignOf(dict::Entry), labels::FUNC_TABLE_SIZE); |
|
| 187 | + | let funcs = try alloc::allocSlice(arena, @sizeOf(FuncAddr), @alignOf(FuncAddr), MAX_FUNCS); |
|
| 188 | + | ||
| 189 | + | let mut debugEntries: *mut [DebugEntry] = &mut []; |
|
| 190 | + | if debug { |
|
| 191 | + | debugEntries = try alloc::allocSlice( |
|
| 192 | + | arena, @sizeOf(DebugEntry), @alignOf(DebugEntry), MAX_DEBUG_ENTRIES |
|
| 193 | + | ) as *mut [DebugEntry]; |
|
| 194 | + | } |
|
| 195 | + | return Emitter { |
|
| 196 | + | code: code as *mut [u32], |
|
| 197 | + | codeLen: 0, |
|
| 198 | + | pendingBranches: pendingBranches as *mut [PendingBranch], |
|
| 199 | + | pendingBranchesLen: 0, |
|
| 200 | + | pendingCalls: pendingCalls as *mut [PendingCall], |
|
| 201 | + | pendingCallsLen: 0, |
|
| 202 | + | pendingAddrLoads: pendingAddrLoads as *mut [PendingAddrLoad], |
|
| 203 | + | pendingAddrLoadsLen: 0, |
|
| 204 | + | labels: labels::init(blockOffsets as *mut [i32], funcEntries as *mut [dict::Entry]), |
|
| 205 | + | funcs: funcs as *mut [FuncAddr], |
|
| 206 | + | funcsLen: 0, |
|
| 207 | + | debugEntries, |
|
| 208 | + | debugEntriesLen: 0, |
|
| 209 | + | }; |
|
| 210 | + | } |
|
| 211 | + | ||
| 212 | + | /////////////////////// |
|
| 213 | + | // Emission Helpers // |
|
| 214 | + | /////////////////////// |
|
| 215 | + | ||
| 216 | + | /// Emit a single instruction. |
|
| 217 | + | pub fn emit(e: *mut Emitter, instr: u32) { |
|
| 218 | + | if e.codeLen >= e.code.len { |
|
| 219 | + | panic "emit: code buffer full"; |
|
| 220 | + | } |
|
| 221 | + | e.code[e.codeLen] = instr; |
|
| 222 | + | e.codeLen = e.codeLen + 1; |
|
| 223 | + | } |
|
| 224 | + | ||
| 225 | + | /// Compute branch offset to a function by name. |
|
| 226 | + | pub fn branchOffsetToFunc(e: *Emitter, srcIndex: u32, name: *[u8]) -> i32 { |
|
| 227 | + | return labels::branchToFunc(&e.labels, srcIndex, name, super::INSTR_SIZE); |
|
| 228 | + | } |
|
| 229 | + | ||
| 230 | + | /// Patch an instruction at a given index. |
|
| 231 | + | pub fn patch(e: *mut Emitter, index: u32, instr: u32) { |
|
| 232 | + | e.code[index] = instr; |
|
| 233 | + | } |
|
| 234 | + | ||
| 235 | + | /// Record a block's address for branch resolution. |
|
| 236 | + | pub fn recordBlock(e: *mut Emitter, blockIdx: u32) { |
|
| 237 | + | debug::assert(e.codeLen <= MAX_CODE_LEN); |
|
| 238 | + | labels::recordBlock(&mut e.labels, blockIdx, e.codeLen as i32 * super::INSTR_SIZE); |
|
| 239 | + | } |
|
| 240 | + | ||
| 241 | + | /// Record a function's code offset for call resolution. |
|
| 242 | + | pub fn recordFuncOffset(e: *mut Emitter, name: *[u8]) { |
|
| 243 | + | debug::assert(e.codeLen <= MAX_CODE_LEN); |
|
| 244 | + | dict::insert(&mut e.labels.funcs, name, e.codeLen as i32 * super::INSTR_SIZE); |
|
| 245 | + | } |
|
| 246 | + | ||
| 247 | + | /// Record a function's start position for printing. |
|
| 248 | + | pub fn recordFunc(e: *mut Emitter, name: *[u8]) { |
|
| 249 | + | if e.funcsLen >= e.funcs.len { |
|
| 250 | + | panic "recordFunc: funcs buffer full"; |
|
| 251 | + | } |
|
| 252 | + | e.funcs[e.funcsLen] = FuncAddr { name, index: e.codeLen }; |
|
| 253 | + | e.funcsLen = e.funcsLen + 1; |
|
| 254 | + | } |
|
| 255 | + | ||
| 256 | + | /// Record a local branch needing later patching. |
|
| 257 | + | /// Emits two placeholder instructions that will be patched later. |
|
| 258 | + | pub fn recordBranch(e: *mut Emitter, targetBlock: u32, kind: BranchKind) { |
|
| 259 | + | if e.pendingBranchesLen >= e.pendingBranches.len { |
|
| 260 | + | panic "recordBranch: buffer full"; |
|
| 261 | + | } |
|
| 262 | + | e.pendingBranches[e.pendingBranchesLen] = PendingBranch { |
|
| 263 | + | index: e.codeLen, |
|
| 264 | + | target: targetBlock, |
|
| 265 | + | kind: kind, |
|
| 266 | + | }; |
|
| 267 | + | e.pendingBranchesLen = e.pendingBranchesLen + 1; |
|
| 268 | + | ||
| 269 | + | emit(e, encode::nop()); // Placeholder for branch/auipc. |
|
| 270 | + | emit(e, encode::nop()); // Placeholder for nop/jal/jalr. |
|
| 271 | + | } |
|
| 272 | + | ||
| 273 | + | /// Record a function call needing later patching. |
|
| 274 | + | /// Emits placeholder instructions that will be patched later. |
|
| 275 | + | /// Uses two slots to support long-distance calls. |
|
| 276 | + | pub fn recordCall(e: *mut Emitter, target: *[u8]) { |
|
| 277 | + | if e.pendingCallsLen >= e.pendingCalls.len { |
|
| 278 | + | panic "recordCall: buffer full"; |
|
| 279 | + | } |
|
| 280 | + | e.pendingCalls[e.pendingCallsLen] = PendingCall { |
|
| 281 | + | index: e.codeLen, |
|
| 282 | + | target, |
|
| 283 | + | }; |
|
| 284 | + | e.pendingCallsLen = e.pendingCallsLen + 1; |
|
| 285 | + | ||
| 286 | + | emit(e, encode::nop()); // Placeholder for AUIPC. |
|
| 287 | + | emit(e, encode::nop()); // Placeholder for JALR. |
|
| 288 | + | } |
|
| 289 | + | ||
| 290 | + | /// Record a function address load needing later patching. |
|
| 291 | + | /// Emits placeholder instructions that will be patched to load the function's address. |
|
| 292 | + | /// Uses two slots to compute long-distance addresses. |
|
| 293 | + | pub fn recordAddrLoad(e: *mut Emitter, target: *[u8], rd: super::Reg) { |
|
| 294 | + | if e.pendingAddrLoadsLen >= e.pendingAddrLoads.len { |
|
| 295 | + | panic "recordAddrLoad: buffer full"; |
|
| 296 | + | } |
|
| 297 | + | e.pendingAddrLoads[e.pendingAddrLoadsLen] = PendingAddrLoad { |
|
| 298 | + | index: e.codeLen, |
|
| 299 | + | target, |
|
| 300 | + | rd: rd, |
|
| 301 | + | }; |
|
| 302 | + | e.pendingAddrLoadsLen = e.pendingAddrLoadsLen + 1; |
|
| 303 | + | ||
| 304 | + | emit(e, encode::nop()); // Placeholder for AUIPC. |
|
| 305 | + | emit(e, encode::nop()); // Placeholder for ADDI. |
|
| 306 | + | } |
|
| 307 | + | ||
| 308 | + | /// Patch local branches and clear the pending list. |
|
| 309 | + | /// |
|
| 310 | + | /// Called after each function. |
|
| 311 | + | /// |
|
| 312 | + | /// Uses two-instruction sequences: short branches use `branch` and `nop`, |
|
| 313 | + | /// long branches use inverted branch and `jal` or `auipc` and `jalr`. |
|
| 314 | + | pub fn patchLocalBranches(e: *mut Emitter) { |
|
| 315 | + | for i in 0..e.pendingBranchesLen { |
|
| 316 | + | let p = e.pendingBranches[i]; |
|
| 317 | + | let offset = labels::branchToBlock(&e.labels, p.index, p.target, super::INSTR_SIZE); |
|
| 318 | + | match p.kind { |
|
| 319 | + | case BranchKind::Cond { op, rs1, rs2 } => { |
|
| 320 | + | if encode::isBranchImm(offset) { |
|
| 321 | + | // Short: direct branch. |
|
| 322 | + | patch(e, p.index, encodeCondBranch(op, rs1, rs2, offset)); |
|
| 323 | + | patch(e, p.index + 1, encode::nop()); |
|
| 324 | + | } else { |
|
| 325 | + | // Long: inverted branch skips `jal`; `jal` goes to target. |
|
| 326 | + | let adj = offset - super::INSTR_SIZE; |
|
| 327 | + | if not encode::isJumpImm(adj) { |
|
| 328 | + | panic "patchLocalBranches: branch offset too large"; |
|
| 329 | + | } |
|
| 330 | + | patch(e, p.index, encodeInvertedBranch(op, rs1, rs2, super::INSTR_SIZE * 2)); |
|
| 331 | + | patch(e, p.index + 1, encode::jal(super::ZERO, adj)); |
|
| 332 | + | } |
|
| 333 | + | }, |
|
| 334 | + | case BranchKind::InvertedCond { op, rs1, rs2 } => { |
|
| 335 | + | if encode::isBranchImm(offset) { |
|
| 336 | + | // Short: direct inverted branch. |
|
| 337 | + | patch(e, p.index, encodeInvertedBranch(op, rs1, rs2, offset)); |
|
| 338 | + | patch(e, p.index + 1, encode::nop()); |
|
| 339 | + | } else { |
|
| 340 | + | // Long: non-inverted branch skips `jal`, jal goes to target. |
|
| 341 | + | let adj = offset - super::INSTR_SIZE; |
|
| 342 | + | if not encode::isJumpImm(adj) { |
|
| 343 | + | panic "patchLocalBranches: branch offset too large"; |
|
| 344 | + | } |
|
| 345 | + | patch(e, p.index, encodeCondBranch(op, rs1, rs2, super::INSTR_SIZE * 2)); |
|
| 346 | + | patch(e, p.index + 1, encode::jal(super::ZERO, adj)); |
|
| 347 | + | } |
|
| 348 | + | }, |
|
| 349 | + | case BranchKind::Jump => { |
|
| 350 | + | if encode::isJumpImm(offset) { |
|
| 351 | + | patch(e, p.index, encode::jal(super::ZERO, offset)); |
|
| 352 | + | patch(e, p.index + 1, encode::nop()); |
|
| 353 | + | } else { |
|
| 354 | + | let s = splitImm(offset); |
|
| 355 | + | patch(e, p.index, encode::auipc(super::SCRATCH1, s.hi)); |
|
| 356 | + | patch(e, p.index + 1, encode::jalr(super::ZERO, super::SCRATCH1, s.lo)); |
|
| 357 | + | } |
|
| 358 | + | }, |
|
| 359 | + | } |
|
| 360 | + | } |
|
| 361 | + | e.pendingBranchesLen = 0; |
|
| 362 | + | } |
|
| 363 | + | ||
| 364 | + | /// Encode a conditional branch instruction. |
|
| 365 | + | fn encodeCondBranch(op: il::CmpOp, rs1: super::Reg, rs2: super::Reg, offset: i32) -> u32 { |
|
| 366 | + | match op { |
|
| 367 | + | case il::CmpOp::Eq => return encode::beq(rs1, rs2, offset), |
|
| 368 | + | case il::CmpOp::Ne => return encode::bne(rs1, rs2, offset), |
|
| 369 | + | case il::CmpOp::Slt => return encode::blt(rs1, rs2, offset), |
|
| 370 | + | case il::CmpOp::Ult => return encode::bltu(rs1, rs2, offset), |
|
| 371 | + | } |
|
| 372 | + | } |
|
| 373 | + | ||
| 374 | + | /// Encode an inverted conditional branch instruction. |
|
| 375 | + | fn encodeInvertedBranch(op: il::CmpOp, rs1: super::Reg, rs2: super::Reg, offset: i32) -> u32 { |
|
| 376 | + | match op { |
|
| 377 | + | case il::CmpOp::Eq => return encode::bne(rs1, rs2, offset), |
|
| 378 | + | case il::CmpOp::Ne => return encode::beq(rs1, rs2, offset), |
|
| 379 | + | case il::CmpOp::Slt => return encode::bge(rs1, rs2, offset), |
|
| 380 | + | case il::CmpOp::Ult => return encode::bgeu(rs1, rs2, offset), |
|
| 381 | + | } |
|
| 382 | + | } |
|
| 383 | + | ||
| 384 | + | /// Patch all pending function calls. |
|
| 385 | + | /// Called after all functions have been generated. |
|
| 386 | + | pub fn patchCalls(e: *mut Emitter) { |
|
| 387 | + | for i in 0..e.pendingCallsLen { |
|
| 388 | + | let p = e.pendingCalls[i]; |
|
| 389 | + | let offset = branchOffsetToFunc(e, p.index, p.target); |
|
| 390 | + | let s = splitImm(offset); |
|
| 391 | + | ||
| 392 | + | // `AUIPC scratch, hi(offset)`. |
|
| 393 | + | patch(e, p.index, encode::auipc(super::SCRATCH1, s.hi)); |
|
| 394 | + | // `JALR ra, scratch, lo(offset)`. |
|
| 395 | + | patch(e, p.index + 1, encode::jalr(super::RA, super::SCRATCH1, s.lo)); |
|
| 396 | + | } |
|
| 397 | + | } |
|
| 398 | + | ||
| 399 | + | /// Patch all pending function address loads. |
|
| 400 | + | /// Called after all functions have been generated. |
|
| 401 | + | /// Uses PC-relative addresses up to 2GB away. |
|
| 402 | + | pub fn patchAddrLoads(e: *mut Emitter) { |
|
| 403 | + | for i in 0..e.pendingAddrLoadsLen { |
|
| 404 | + | let p = e.pendingAddrLoads[i]; |
|
| 405 | + | let offset = branchOffsetToFunc(e, p.index, p.target); |
|
| 406 | + | let s = splitImm(offset); |
|
| 407 | + | ||
| 408 | + | // `AUIPC rd, hi(offset)`. |
|
| 409 | + | patch(e, p.index, encode::auipc(p.rd, s.hi)); |
|
| 410 | + | // `ADDI rd, rd, lo(offset)`. |
|
| 411 | + | patch(e, p.index + 1, encode::addi(p.rd, p.rd, s.lo)); |
|
| 412 | + | } |
|
| 413 | + | } |
|
| 414 | + | ||
| 415 | + | ///////////////////////// |
|
| 416 | + | // Immediate Handling // |
|
| 417 | + | ///////////////////////// |
|
| 418 | + | ||
| 419 | + | /// Split immediate into `hi` and `lo` bits. |
|
| 420 | + | pub record SplitImm { |
|
| 421 | + | /// Upper 20 bits. |
|
| 422 | + | hi: i32, |
|
| 423 | + | /// Lower 12 bits. |
|
| 424 | + | lo: i32, |
|
| 425 | + | } |
|
| 426 | + | ||
| 427 | + | /// Split a 32-bit immediate for `AUIPC, ADDI` / `JALR` sequences. |
|
| 428 | + | /// Handles sign extension: if *lo* is negative, increment *hi*. |
|
| 429 | + | pub fn splitImm(imm: i32) -> SplitImm { |
|
| 430 | + | let lo = imm & 0xFFF; |
|
| 431 | + | let mut hi = (imm >> 12) & 0xFFFFF; |
|
| 432 | + | // If `lo`'s sign bit is set, it will be sign-extended to negative. |
|
| 433 | + | // Compensate by incrementing `hi`. |
|
| 434 | + | if (lo & 0x800) != 0 { |
|
| 435 | + | hi = hi + 1; |
|
| 436 | + | return SplitImm { hi, lo: lo | 0xFFFFF000 as i32 }; |
|
| 437 | + | } |
|
| 438 | + | return SplitImm { hi, lo }; |
|
| 439 | + | } |
|
| 440 | + | ||
| 441 | + | /// Adjust a large offset by loading *hi* bits into [`super::ADDR_SCRATCH`]. |
|
| 442 | + | /// Returns adjusted base register and remaining offset. |
|
| 443 | + | /// |
|
| 444 | + | /// When the offset fits a 12-bit signed immediate, returns it unchanged. |
|
| 445 | + | /// Otherwise uses [`super::ADDR_SCRATCH`] for the LUI+ADD decomposition. |
|
| 446 | + | fn adjustOffset(e: *mut Emitter, base: super::Reg, offset: i32) -> AdjustedOffset { |
|
| 447 | + | if offset >= super::MIN_IMM and offset <= super::MAX_IMM { |
|
| 448 | + | return AdjustedOffset { base, offset }; |
|
| 449 | + | } |
|
| 450 | + | let s = splitImm(offset); |
|
| 451 | + | emit(e, encode::lui(super::ADDR_SCRATCH, s.hi)); |
|
| 452 | + | emit(e, encode::add(super::ADDR_SCRATCH, super::ADDR_SCRATCH, base)); |
|
| 453 | + | ||
| 454 | + | return AdjustedOffset { base: super::ADDR_SCRATCH, offset: s.lo }; |
|
| 455 | + | } |
|
| 456 | + | ||
| 457 | + | /// Load an immediate value into a register. |
|
| 458 | + | /// Uses `LI` pseudo-instruction: `LUI, ADDIW` for large values. |
|
| 459 | + | /// On RV64, `addiw` ensures the result is correctly sign-extended to 64 bits |
|
| 460 | + | /// after a `lui` upper-immediate load. |
|
| 461 | + | pub fn loadImm(e: *mut Emitter, rd: super::Reg, imm: i32) { |
|
| 462 | + | if imm >= super::MIN_IMM and imm <= super::MAX_IMM { |
|
| 463 | + | emit(e, encode::addi(rd, super::ZERO, imm)); |
|
| 464 | + | } else { |
|
| 465 | + | let s = splitImm(imm); |
|
| 466 | + | emit(e, encode::lui(rd, s.hi)); |
|
| 467 | + | ||
| 468 | + | if s.lo != 0 { |
|
| 469 | + | emit(e, encode::addiw(rd, rd, s.lo)); |
|
| 470 | + | } |
|
| 471 | + | } |
|
| 472 | + | } |
|
| 473 | + | ||
| 474 | + | /// Emit add-immediate, handling large immediates. |
|
| 475 | + | pub fn emitAddImm(e: *mut Emitter, rd: super::Reg, rs: super::Reg, imm: i32) { |
|
| 476 | + | if imm >= super::MIN_IMM and imm <= super::MAX_IMM { |
|
| 477 | + | emit(e, encode::addi(rd, rs, imm)); |
|
| 478 | + | } else { |
|
| 479 | + | loadImm(e, super::SCRATCH1, imm); |
|
| 480 | + | emit(e, encode::add(rd, rs, super::SCRATCH1)); |
|
| 481 | + | } |
|
| 482 | + | } |
|
| 483 | + | ||
| 484 | + | //////////////////////// |
|
| 485 | + | // Load/Store Helpers // |
|
| 486 | + | //////////////////////// |
|
| 487 | + | ||
| 488 | + | /// Emit unsigned load with automatic offset adjustment. |
|
| 489 | + | pub fn emitLoad(e: *mut Emitter, rd: super::Reg, base: super::Reg, offset: i32, typ: il::Type) { |
|
| 490 | + | let adj = adjustOffset(e, base, offset); |
|
| 491 | + | match typ { |
|
| 492 | + | case il::Type::W8 => emit(e, encode::lbu(rd, adj.base, adj.offset)), |
|
| 493 | + | case il::Type::W16 => emit(e, encode::lhu(rd, adj.base, adj.offset)), |
|
| 494 | + | case il::Type::W32 => emit(e, encode::lw(rd, adj.base, adj.offset)), |
|
| 495 | + | case il::Type::W64 => emit(e, encode::ld(rd, adj.base, adj.offset)), |
|
| 496 | + | } |
|
| 497 | + | } |
|
| 498 | + | ||
| 499 | + | /// Emit signed load with automatic offset adjustment. |
|
| 500 | + | pub fn emitSload(e: *mut Emitter, rd: super::Reg, base: super::Reg, offset: i32, typ: il::Type) { |
|
| 501 | + | let adj = adjustOffset(e, base, offset); |
|
| 502 | + | match typ { |
|
| 503 | + | case il::Type::W8 => emit(e, encode::lb(rd, adj.base, adj.offset)), |
|
| 504 | + | case il::Type::W16 => emit(e, encode::lh(rd, adj.base, adj.offset)), |
|
| 505 | + | case il::Type::W32 => emit(e, encode::lw(rd, adj.base, adj.offset)), |
|
| 506 | + | case il::Type::W64 => emit(e, encode::ld(rd, adj.base, adj.offset)), |
|
| 507 | + | } |
|
| 508 | + | } |
|
| 509 | + | ||
| 510 | + | /// Emit store with automatic offset adjustment. |
|
| 511 | + | pub fn emitStore(e: *mut Emitter, rs: super::Reg, base: super::Reg, offset: i32, typ: il::Type) { |
|
| 512 | + | let adj = adjustOffset(e, base, offset); |
|
| 513 | + | match typ { |
|
| 514 | + | case il::Type::W8 => emit(e, encode::sb(rs, adj.base, adj.offset)), |
|
| 515 | + | case il::Type::W16 => emit(e, encode::sh(rs, adj.base, adj.offset)), |
|
| 516 | + | case il::Type::W32 => emit(e, encode::sw(rs, adj.base, adj.offset)), |
|
| 517 | + | case il::Type::W64 => emit(e, encode::sd(rs, adj.base, adj.offset)), |
|
| 518 | + | } |
|
| 519 | + | } |
|
| 520 | + | ||
| 521 | + | /// Emit 64-bit load with automatic offset adjustment. |
|
| 522 | + | pub fn emitLd(e: *mut Emitter, rd: super::Reg, base: super::Reg, offset: i32) { |
|
| 523 | + | let adj = adjustOffset(e, base, offset); |
|
| 524 | + | emit(e, encode::ld(rd, adj.base, adj.offset)); |
|
| 525 | + | } |
|
| 526 | + | ||
| 527 | + | /// Emit 64-bit store with automatic offset adjustment. |
|
| 528 | + | pub fn emitSd(e: *mut Emitter, rs: super::Reg, base: super::Reg, offset: i32) { |
|
| 529 | + | let adj = adjustOffset(e, base, offset); |
|
| 530 | + | emit(e, encode::sd(rs, adj.base, adj.offset)); |
|
| 531 | + | } |
|
| 532 | + | ||
| 533 | + | /// Emit 32-bit load with automatic offset adjustment. |
|
| 534 | + | pub fn emitLw(e: *mut Emitter, rd: super::Reg, base: super::Reg, offset: i32) { |
|
| 535 | + | let adj = adjustOffset(e, base, offset); |
|
| 536 | + | emit(e, encode::lw(rd, adj.base, adj.offset)); |
|
| 537 | + | } |
|
| 538 | + | ||
| 539 | + | /// Emit 32-bit store with automatic offset adjustment. |
|
| 540 | + | pub fn emitSw(e: *mut Emitter, rs: super::Reg, base: super::Reg, offset: i32) { |
|
| 541 | + | let adj = adjustOffset(e, base, offset); |
|
| 542 | + | emit(e, encode::sw(rs, adj.base, adj.offset)); |
|
| 543 | + | } |
|
| 544 | + | ||
| 545 | + | /// Emit 8-bit load with automatic offset adjustment. |
|
| 546 | + | pub fn emitLb(e: *mut Emitter, rd: super::Reg, base: super::Reg, offset: i32) { |
|
| 547 | + | let adj = adjustOffset(e, base, offset); |
|
| 548 | + | emit(e, encode::lb(rd, adj.base, adj.offset)); |
|
| 549 | + | } |
|
| 550 | + | ||
| 551 | + | /// Emit 8-bit store with automatic offset adjustment. |
|
| 552 | + | pub fn emitSb(e: *mut Emitter, rs: super::Reg, base: super::Reg, offset: i32) { |
|
| 553 | + | let adj = adjustOffset(e, base, offset); |
|
| 554 | + | emit(e, encode::sb(rs, adj.base, adj.offset)); |
|
| 555 | + | } |
|
| 556 | + | ||
| 557 | + | ////////////////////////// |
|
| 558 | + | // Prologue / Epilogue // |
|
| 559 | + | ////////////////////////// |
|
| 560 | + | ||
| 561 | + | /// Emit function prologue. |
|
| 562 | + | /// Allocates stack frame, saves RA/FP, saves callee-saved registers. |
|
| 563 | + | pub fn emitPrologue(e: *mut Emitter, frame: *Frame) { |
|
| 564 | + | // Fast path: leaf function with no locals. |
|
| 565 | + | if frame.totalSize == 0 { |
|
| 566 | + | return; |
|
| 567 | + | } |
|
| 568 | + | let totalSize = frame.totalSize; |
|
| 569 | + | ||
| 570 | + | // Allocate stack frame. |
|
| 571 | + | let negFrame = 0 - totalSize; |
|
| 572 | + | if negFrame >= super::MIN_IMM { |
|
| 573 | + | emit(e, encode::addi(super::SP, super::SP, negFrame)); |
|
| 574 | + | } else { |
|
| 575 | + | loadImm(e, super::SCRATCH1, totalSize); |
|
| 576 | + | emit(e, encode::sub(super::SP, super::SP, super::SCRATCH1)); |
|
| 577 | + | } |
|
| 578 | + | // Save return address and frame pointer. |
|
| 579 | + | emitSd(e, super::RA, super::SP, totalSize - super::DWORD_SIZE); |
|
| 580 | + | emitSd(e, super::FP, super::SP, totalSize - super::DWORD_SIZE * 2); |
|
| 581 | + | ||
| 582 | + | // Set up frame pointer. |
|
| 583 | + | emitAddImm(e, super::FP, super::SP, totalSize); |
|
| 584 | + | ||
| 585 | + | // Save callee-saved registers. |
|
| 586 | + | for i in 0..frame.savedRegsLen { |
|
| 587 | + | let sr = frame.savedRegs[i]; |
|
| 588 | + | emitSd(e, sr.reg, super::SP, sr.offset); |
|
| 589 | + | } |
|
| 590 | + | } |
|
| 591 | + | ||
| 592 | + | /// Emit a return: jump to epilogue. |
|
| 593 | + | pub fn emitReturn(e: *mut Emitter, frame: *Frame) { |
|
| 594 | + | recordBranch(e, frame.epilogueBlock, BranchKind::Jump); |
|
| 595 | + | } |
|
| 596 | + | ||
| 597 | + | /// Emit function epilogue. |
|
| 598 | + | /// Restores callee-saved registers, `RA/FP`, deallocates frame, returns. |
|
| 599 | + | pub fn emitEpilogue(e: *mut Emitter, frame: *Frame) { |
|
| 600 | + | // Record epilogue block address for return jumps. |
|
| 601 | + | recordBlock(e, frame.epilogueBlock); |
|
| 602 | + | ||
| 603 | + | // Fast path: leaf function with no locals. |
|
| 604 | + | if frame.totalSize == 0 { |
|
| 605 | + | emit(e, encode::ret()); |
|
| 606 | + | return; |
|
| 607 | + | } |
|
| 608 | + | let totalSize = frame.totalSize; |
|
| 609 | + | ||
| 610 | + | // Restore SP to post-prologue value. Required if we performed dynamic |
|
| 611 | + | // stack allocation, as SP may have moved. |
|
| 612 | + | // |
|
| 613 | + | // Since we set FP to `SP + totalSize`, we now set SP to `FP - totalSize`. |
|
| 614 | + | emitAddImm(e, super::SP, super::FP, 0 - totalSize); |
|
| 615 | + | ||
| 616 | + | // Restore callee-saved registers. |
|
| 617 | + | for i in 0..frame.savedRegsLen { |
|
| 618 | + | let sr = frame.savedRegs[i]; |
|
| 619 | + | emitLd(e, sr.reg, super::SP, sr.offset); |
|
| 620 | + | } |
|
| 621 | + | // Restore frame pointer and return address. |
|
| 622 | + | emitLd(e, super::FP, super::SP, totalSize - super::DWORD_SIZE * 2); |
|
| 623 | + | emitLd(e, super::RA, super::SP, totalSize - super::DWORD_SIZE); |
|
| 624 | + | ||
| 625 | + | // Deallocate stack frame. |
|
| 626 | + | emitAddImm(e, super::SP, super::SP, totalSize); |
|
| 627 | + | emit(e, encode::ret()); |
|
| 628 | + | } |
|
| 629 | + | ||
| 630 | + | ////////////////// |
|
| 631 | + | // Code Access // |
|
| 632 | + | ////////////////// |
|
| 633 | + | ||
| 634 | + | /// Get emitted code as a slice. |
|
| 635 | + | pub fn getCode(e: *Emitter) -> *[u32] { |
|
| 636 | + | return e.code[..e.codeLen]; |
|
| 637 | + | } |
|
| 638 | + | ||
| 639 | + | /// Get function addresses for printing. |
|
| 640 | + | pub fn getFuncs(e: *Emitter) -> *[FuncAddr] { |
|
| 641 | + | return e.funcs[..e.funcsLen]; |
|
| 642 | + | } |
|
| 643 | + | ||
| 644 | + | /// Record a debug entry mapping the current PC to a source location. |
|
| 645 | + | /// Deduplicates consecutive entries with the same location. |
|
| 646 | + | pub fn recordSrcLoc(e: *mut Emitter, loc: il::SrcLoc) { |
|
| 647 | + | let pc = e.codeLen * super::INSTR_SIZE as u32; |
|
| 648 | + | ||
| 649 | + | // Skip if this is the same location as the previous entry. |
|
| 650 | + | if e.debugEntriesLen > 0 { |
|
| 651 | + | let prev = &e.debugEntries[e.debugEntriesLen - 1]; |
|
| 652 | + | if prev.offset == loc.offset and prev.moduleId == loc.moduleId { |
|
| 653 | + | return; |
|
| 654 | + | } |
|
| 655 | + | } |
|
| 656 | + | if e.debugEntriesLen >= e.debugEntries.len { |
|
| 657 | + | panic "recordSrcLoc: debug entry buffer full"; |
|
| 658 | + | } |
|
| 659 | + | e.debugEntries[e.debugEntriesLen] = DebugEntry { |
|
| 660 | + | pc, |
|
| 661 | + | moduleId: loc.moduleId, |
|
| 662 | + | offset: loc.offset, |
|
| 663 | + | }; |
|
| 664 | + | e.debugEntriesLen = e.debugEntriesLen + 1; |
|
| 665 | + | } |
|
| 666 | + | ||
| 667 | + | /// Get debug entries as a slice. |
|
| 668 | + | pub fn getDebugEntries(e: *Emitter) -> *[DebugEntry] { |
|
| 669 | + | return e.debugEntries[..e.debugEntriesLen]; |
|
| 670 | + | } |
lib/std/arch/rv64/encode.rad
added
+618 -0
| 1 | + | //! RISC-V RV64I+M instruction encoding. |
|
| 2 | + | //! |
|
| 3 | + | //! Provides type-safe functions for encoding RV64 instructions. |
|
| 4 | + | ||
| 5 | + | use std::debug; |
|
| 6 | + | ||
| 7 | + | ////////////////////// |
|
| 8 | + | // Opcode Constants // |
|
| 9 | + | ////////////////////// |
|
| 10 | + | ||
| 11 | + | pub const OP_LOAD: u32 = 0x03; |
|
| 12 | + | pub const OP_STORE: u32 = 0x23; |
|
| 13 | + | pub const OP_BRANCH: u32 = 0x63; |
|
| 14 | + | pub const OP_JALR: u32 = 0x67; |
|
| 15 | + | pub const OP_JAL: u32 = 0x6F; |
|
| 16 | + | pub const OP_OP: u32 = 0x33; |
|
| 17 | + | pub const OP_IMM: u32 = 0x13; |
|
| 18 | + | pub const OP_AUIPC: u32 = 0x17; |
|
| 19 | + | pub const OP_LUI: u32 = 0x37; |
|
| 20 | + | pub const OP_SYSTEM: u32 = 0x73; |
|
| 21 | + | pub const OP_OP32: u32 = 0x3B; // RV64: 32-bit operations |
|
| 22 | + | pub const OP_IMM32: u32 = 0x1B; // RV64: 32-bit immediate operations |
|
| 23 | + | ||
| 24 | + | ////////////////////// |
|
| 25 | + | // Funct3 Constants // |
|
| 26 | + | ////////////////////// |
|
| 27 | + | ||
| 28 | + | // Memory operations |
|
| 29 | + | ||
| 30 | + | pub const F3_BYTE: u32 = 0x0; // LB/SB |
|
| 31 | + | pub const F3_HALF: u32 = 0x1; // LH/SH |
|
| 32 | + | pub const F3_WORD: u32 = 0x2; // LW/SW |
|
| 33 | + | pub const F3_DWORD: u32 = 0x3; // LD/SD (RV64) |
|
| 34 | + | pub const F3_BYTE_U: u32 = 0x4; // LBU |
|
| 35 | + | pub const F3_HALF_U: u32 = 0x5; // LHU |
|
| 36 | + | pub const F3_WORD_U: u32 = 0x6; // LWU (RV64) |
|
| 37 | + | ||
| 38 | + | // ALU operations |
|
| 39 | + | ||
| 40 | + | pub const F3_ADD: u32 = 0x0; // ADD/SUB/ADDI |
|
| 41 | + | pub const F3_SLL: u32 = 0x1; // SLL/SLLI |
|
| 42 | + | pub const F3_SLT: u32 = 0x2; // SLT/SLTI |
|
| 43 | + | pub const F3_SLTU: u32 = 0x3; // SLTU/SLTIU |
|
| 44 | + | pub const F3_XOR: u32 = 0x4; // XOR/XORI |
|
| 45 | + | pub const F3_SRL: u32 = 0x5; // SRL/SRA/SRLI/SRAI |
|
| 46 | + | pub const F3_OR: u32 = 0x6; // OR/ORI |
|
| 47 | + | pub const F3_AND: u32 = 0x7; // AND/ANDI |
|
| 48 | + | ||
| 49 | + | // Branch operations |
|
| 50 | + | ||
| 51 | + | pub const F3_BEQ: u32 = 0x0; |
|
| 52 | + | pub const F3_BNE: u32 = 0x1; |
|
| 53 | + | pub const F3_BLT: u32 = 0x4; |
|
| 54 | + | pub const F3_BGE: u32 = 0x5; |
|
| 55 | + | pub const F3_BLTU: u32 = 0x6; |
|
| 56 | + | pub const F3_BGEU: u32 = 0x7; |
|
| 57 | + | ||
| 58 | + | ////////////////////// |
|
| 59 | + | // Funct7 Constants // |
|
| 60 | + | ////////////////////// |
|
| 61 | + | ||
| 62 | + | pub const F7_NORMAL: u32 = 0b0000000; |
|
| 63 | + | pub const F7_SUB: u32 = 0b0100000; // Bit 5 set |
|
| 64 | + | pub const F7_SRA: u32 = 0b0100000; // Bit 5 set |
|
| 65 | + | pub const F7_MUL: u32 = 0b0000001; // Bit 0 set |
|
| 66 | + | ||
| 67 | + | ///////////////////////// |
|
| 68 | + | // Validation Helpers // |
|
| 69 | + | ///////////////////////// |
|
| 70 | + | ||
| 71 | + | /// Returns `true` if the value fits in a signed 12-bit immediate. |
|
| 72 | + | pub fn isSmallImm(value: i32) -> bool { |
|
| 73 | + | return value >= super::MIN_IMM and value <= super::MAX_IMM; |
|
| 74 | + | } |
|
| 75 | + | ||
| 76 | + | /// Returns `true` if the value is valid for branch immediates. |
|
| 77 | + | /// Branch immediates are 13-bit signed, even aligned. |
|
| 78 | + | pub fn isBranchImm(value: i32) -> bool { |
|
| 79 | + | return value >= -(1 << 12) and value <= ((1 << 12) - 2) and (value & 1) == 0; |
|
| 80 | + | } |
|
| 81 | + | ||
| 82 | + | /// Returns `true` if the value is valid for jump immediates (JAL). |
|
| 83 | + | /// Jump immediates are 21-bit signed, even aligned. |
|
| 84 | + | pub fn isJumpImm(value: i32) -> bool { |
|
| 85 | + | return value >= -(1 << 20) and value <= ((1 << 20) - 2) and (value & 1) == 0; |
|
| 86 | + | } |
|
| 87 | + | ||
| 88 | + | ////////////////////// |
|
| 89 | + | // Format Encoders // |
|
| 90 | + | ////////////////////// |
|
| 91 | + | ||
| 92 | + | /// Encode an R-type instruction. |
|
| 93 | + | fn encodeR(opcode: u32, rd: super::Reg, rs1: super::Reg, rs2: super::Reg, funct3: u32, funct7: u32) -> u32 { |
|
| 94 | + | return (opcode & 0x7F) |
|
| 95 | + | | ((rd.n as u32 & 0x1F) << 7) |
|
| 96 | + | | ((funct3 & 0x07) << 12) |
|
| 97 | + | | ((rs1.n as u32 & 0x1F) << 15) |
|
| 98 | + | | ((rs2.n as u32 & 0x1F) << 20) |
|
| 99 | + | | ((funct7 & 0x7F) << 25); |
|
| 100 | + | } |
|
| 101 | + | ||
| 102 | + | /// Encode an I-type instruction. |
|
| 103 | + | fn encodeI(opcode: u32, rd: super::Reg, rs1: super::Reg, funct3: u32, imm: i32) -> u32 { |
|
| 104 | + | debug::assert(isSmallImm(imm)); |
|
| 105 | + | ||
| 106 | + | return (opcode & 0x7F) |
|
| 107 | + | | ((rd.n as u32 & 0x1F) << 7) |
|
| 108 | + | | ((funct3 & 0x07) << 12) |
|
| 109 | + | | ((rs1.n as u32 & 0x1F) << 15) |
|
| 110 | + | | ((imm as u32 & 0xFFF) << 20); |
|
| 111 | + | } |
|
| 112 | + | ||
| 113 | + | /// Encode an S-type instruction. |
|
| 114 | + | fn encodeS(opcode: u32, rs1: super::Reg, rs2: super::Reg, funct3: u32, imm: i32) -> u32 { |
|
| 115 | + | debug::assert(isSmallImm(imm)); |
|
| 116 | + | ||
| 117 | + | return (opcode & 0x7F) |
|
| 118 | + | | ((imm as u32 & 0x1F) << 7) |
|
| 119 | + | | ((funct3 & 0x07) << 12) |
|
| 120 | + | | ((rs1.n as u32 & 0x1F) << 15) |
|
| 121 | + | | ((rs2.n as u32 & 0x1F) << 20) |
|
| 122 | + | | ((imm as u32 >> 5 & 0x7F) << 25); |
|
| 123 | + | } |
|
| 124 | + | ||
| 125 | + | /// Encode a B-type (branch) instruction. |
|
| 126 | + | fn encodeB(opcode: u32, rs1: super::Reg, rs2: super::Reg, funct3: u32, imm: i32) -> u32 { |
|
| 127 | + | debug::assert(isBranchImm(imm)); |
|
| 128 | + | ||
| 129 | + | let imm11 = (imm as u32 >> 11) & 0x1; |
|
| 130 | + | let imm4_1 = (imm as u32 >> 1) & 0xF; |
|
| 131 | + | let imm10_5 = (imm as u32 >> 5) & 0x3F; |
|
| 132 | + | let imm12 = (imm as u32 >> 12) & 0x1; |
|
| 133 | + | ||
| 134 | + | return (opcode & 0x7F) |
|
| 135 | + | | (imm11 << 7) |
|
| 136 | + | | (imm4_1 << 8) |
|
| 137 | + | | ((funct3 & 0x07) << 12) |
|
| 138 | + | | ((rs1.n as u32 & 0x1F) << 15) |
|
| 139 | + | | ((rs2.n as u32 & 0x1F) << 20) |
|
| 140 | + | | (imm10_5 << 25) |
|
| 141 | + | | (imm12 << 31); |
|
| 142 | + | } |
|
| 143 | + | ||
| 144 | + | /// Encode a U-type instruction. |
|
| 145 | + | fn encodeU(opcode: u32, rd: super::Reg, imm: i32) -> u32 { |
|
| 146 | + | return (opcode & 0x7F) |
|
| 147 | + | | ((rd.n as u32 & 0x1F) << 7) |
|
| 148 | + | | ((imm as u32 & 0xFFFFF) << 12); |
|
| 149 | + | } |
|
| 150 | + | ||
| 151 | + | /// Encode a J-type (jump) instruction. |
|
| 152 | + | fn encodeJ(opcode: u32, rd: super::Reg, imm: i32) -> u32 { |
|
| 153 | + | debug::assert(isJumpImm(imm)); |
|
| 154 | + | ||
| 155 | + | let imm20 = (imm as u32 >> 20) & 0x1; |
|
| 156 | + | let imm10_1 = (imm as u32 >> 1) & 0x3FF; |
|
| 157 | + | let imm11 = (imm as u32 >> 11) & 0x1; |
|
| 158 | + | let imm19_12 = (imm as u32 >> 12) & 0xFF; |
|
| 159 | + | ||
| 160 | + | return (opcode & 0x7F) |
|
| 161 | + | | ((rd.n as u32 & 0x1F) << 7) |
|
| 162 | + | | (imm19_12 << 12) |
|
| 163 | + | | (imm11 << 20) |
|
| 164 | + | | (imm10_1 << 21) |
|
| 165 | + | | (imm20 << 31); |
|
| 166 | + | } |
|
| 167 | + | ||
| 168 | + | //////////////////////////// |
|
| 169 | + | // ALU Immediate (I-type) // |
|
| 170 | + | //////////////////////////// |
|
| 171 | + | ||
| 172 | + | /// Add immediate: `rd = rs1 + imm`. |
|
| 173 | + | pub fn addi(rd: super::Reg, rs1: super::Reg, imm: i32) -> u32 { |
|
| 174 | + | return encodeI(OP_IMM, rd, rs1, F3_ADD, imm); |
|
| 175 | + | } |
|
| 176 | + | ||
| 177 | + | /// Set less than immediate (signed): `rd = (rs1 < imm) ? 1 : 0`. |
|
| 178 | + | pub fn slti(rd: super::Reg, rs1: super::Reg, imm: i32) -> u32 { |
|
| 179 | + | return encodeI(OP_IMM, rd, rs1, F3_SLT, imm); |
|
| 180 | + | } |
|
| 181 | + | ||
| 182 | + | /// Set less than immediate unsigned: `rd = (rs1 < imm) ? 1 : 0`. |
|
| 183 | + | pub fn sltiu(rd: super::Reg, rs1: super::Reg, imm: i32) -> u32 { |
|
| 184 | + | return encodeI(OP_IMM, rd, rs1, F3_SLTU, imm); |
|
| 185 | + | } |
|
| 186 | + | ||
| 187 | + | /// XOR immediate: `rd = rs1 ^ imm`. |
|
| 188 | + | pub fn xori(rd: super::Reg, rs1: super::Reg, imm: i32) -> u32 { |
|
| 189 | + | return encodeI(OP_IMM, rd, rs1, F3_XOR, imm); |
|
| 190 | + | } |
|
| 191 | + | ||
| 192 | + | /// OR immediate: `rd = rs1 | imm`. |
|
| 193 | + | pub fn ori(rd: super::Reg, rs1: super::Reg, imm: i32) -> u32 { |
|
| 194 | + | return encodeI(OP_IMM, rd, rs1, F3_OR, imm); |
|
| 195 | + | } |
|
| 196 | + | ||
| 197 | + | /// AND immediate: `rd = rs1 & imm`. |
|
| 198 | + | pub fn andi(rd: super::Reg, rs1: super::Reg, imm: i32) -> u32 { |
|
| 199 | + | return encodeI(OP_IMM, rd, rs1, F3_AND, imm); |
|
| 200 | + | } |
|
| 201 | + | ||
| 202 | + | /// Shift left logical immediate: `rd = rs1 << shamt`. |
|
| 203 | + | pub fn slli(rd: super::Reg, rs1: super::Reg, shamt: i32) -> u32 { |
|
| 204 | + | debug::assert(shamt >= 0 and shamt < 64); |
|
| 205 | + | return encodeI(OP_IMM, rd, rs1, F3_SLL, shamt & 0x3F); |
|
| 206 | + | } |
|
| 207 | + | ||
| 208 | + | /// Shift right logical immediate: `rd = rs1 >> shamt` (zero-extend). |
|
| 209 | + | pub fn srli(rd: super::Reg, rs1: super::Reg, shamt: i32) -> u32 { |
|
| 210 | + | debug::assert(shamt >= 0 and shamt < 64); |
|
| 211 | + | return encodeI(OP_IMM, rd, rs1, F3_SRL, shamt & 0x3F); |
|
| 212 | + | } |
|
| 213 | + | ||
| 214 | + | /// Shift right arithmetic immediate: `rd = rs1 >> shamt` (sign-extend). |
|
| 215 | + | pub fn srai(rd: super::Reg, rs1: super::Reg, shamt: i32) -> u32 { |
|
| 216 | + | debug::assert(shamt >= 0 and shamt < 64); |
|
| 217 | + | // SRAI has bit 10 set in immediate field (becomes bit 30 in instruction) |
|
| 218 | + | return encodeI(OP_IMM, rd, rs1, F3_SRL, (shamt & 0x3F) | 0b10000000000); |
|
| 219 | + | } |
|
| 220 | + | ||
| 221 | + | /////////////////////////// |
|
| 222 | + | // ALU Register (R-type) // |
|
| 223 | + | /////////////////////////// |
|
| 224 | + | ||
| 225 | + | /// Add: `rd = rs1 + rs2`. |
|
| 226 | + | pub fn add(rd: super::Reg, rs1: super::Reg, rs2: super::Reg) -> u32 { |
|
| 227 | + | return encodeR(OP_OP, rd, rs1, rs2, F3_ADD, F7_NORMAL); |
|
| 228 | + | } |
|
| 229 | + | ||
| 230 | + | /// Subtract: `rd = rs1 - rs2`. |
|
| 231 | + | pub fn sub(rd: super::Reg, rs1: super::Reg, rs2: super::Reg) -> u32 { |
|
| 232 | + | return encodeR(OP_OP, rd, rs1, rs2, F3_ADD, F7_SUB); |
|
| 233 | + | } |
|
| 234 | + | ||
| 235 | + | /// Shift left logical: `rd = rs1 << rs2[5:0]`. |
|
| 236 | + | pub fn sll(rd: super::Reg, rs1: super::Reg, rs2: super::Reg) -> u32 { |
|
| 237 | + | return encodeR(OP_OP, rd, rs1, rs2, F3_SLL, F7_NORMAL); |
|
| 238 | + | } |
|
| 239 | + | ||
| 240 | + | /// Set less than (signed): `rd = (rs1 < rs2) ? 1 : 0`. |
|
| 241 | + | pub fn slt(rd: super::Reg, rs1: super::Reg, rs2: super::Reg) -> u32 { |
|
| 242 | + | return encodeR(OP_OP, rd, rs1, rs2, F3_SLT, F7_NORMAL); |
|
| 243 | + | } |
|
| 244 | + | ||
| 245 | + | /// Set less than unsigned: `rd = (rs1 < rs2) ? 1 : 0`. |
|
| 246 | + | pub fn sltu(rd: super::Reg, rs1: super::Reg, rs2: super::Reg) -> u32 { |
|
| 247 | + | return encodeR(OP_OP, rd, rs1, rs2, F3_SLTU, F7_NORMAL); |
|
| 248 | + | } |
|
| 249 | + | ||
| 250 | + | /// XOR: `rd = rs1 ^ rs2`. |
|
| 251 | + | pub fn xor(rd: super::Reg, rs1: super::Reg, rs2: super::Reg) -> u32 { |
|
| 252 | + | return encodeR(OP_OP, rd, rs1, rs2, F3_XOR, F7_NORMAL); |
|
| 253 | + | } |
|
| 254 | + | ||
| 255 | + | /// Shift right logical: `rd = rs1 >> rs2[5:0]` (zero-extend). |
|
| 256 | + | pub fn srl(rd: super::Reg, rs1: super::Reg, rs2: super::Reg) -> u32 { |
|
| 257 | + | return encodeR(OP_OP, rd, rs1, rs2, F3_SRL, F7_NORMAL); |
|
| 258 | + | } |
|
| 259 | + | ||
| 260 | + | /// Shift right arithmetic: `rd = rs1 >> rs2[5:0]` (sign-extend). |
|
| 261 | + | pub fn sra(rd: super::Reg, rs1: super::Reg, rs2: super::Reg) -> u32 { |
|
| 262 | + | return encodeR(OP_OP, rd, rs1, rs2, F3_SRL, F7_SRA); |
|
| 263 | + | } |
|
| 264 | + | ||
| 265 | + | /// OR: `rd = rs1 | rs2`. |
|
| 266 | + | pub fn or_(rd: super::Reg, rs1: super::Reg, rs2: super::Reg) -> u32 { |
|
| 267 | + | return encodeR(OP_OP, rd, rs1, rs2, F3_OR, F7_NORMAL); |
|
| 268 | + | } |
|
| 269 | + | ||
| 270 | + | /// AND: `rd = rs1 & rs2`. |
|
| 271 | + | pub fn and_(rd: super::Reg, rs1: super::Reg, rs2: super::Reg) -> u32 { |
|
| 272 | + | return encodeR(OP_OP, rd, rs1, rs2, F3_AND, F7_NORMAL); |
|
| 273 | + | } |
|
| 274 | + | ||
| 275 | + | ///////////////// |
|
| 276 | + | // M Extension // |
|
| 277 | + | ///////////////// |
|
| 278 | + | ||
| 279 | + | /// Multiply: `rd = (rs1 * rs2)[63:0]`. |
|
| 280 | + | pub fn mul(rd: super::Reg, rs1: super::Reg, rs2: super::Reg) -> u32 { |
|
| 281 | + | return encodeR(OP_OP, rd, rs1, rs2, F3_ADD, F7_MUL); |
|
| 282 | + | } |
|
| 283 | + | ||
| 284 | + | /// Multiply high (signed x signed): `rd = (rs1 * rs2)[127:64]`. |
|
| 285 | + | pub fn mulh(rd: super::Reg, rs1: super::Reg, rs2: super::Reg) -> u32 { |
|
| 286 | + | return encodeR(OP_OP, rd, rs1, rs2, F3_SLL, F7_MUL); |
|
| 287 | + | } |
|
| 288 | + | ||
| 289 | + | /// Multiply high (signed x unsigned): `rd = (rs1 * rs2)[127:64]`. |
|
| 290 | + | pub fn mulhsu(rd: super::Reg, rs1: super::Reg, rs2: super::Reg) -> u32 { |
|
| 291 | + | return encodeR(OP_OP, rd, rs1, rs2, F3_SLT, F7_MUL); |
|
| 292 | + | } |
|
| 293 | + | ||
| 294 | + | /// Multiply high (unsigned x unsigned): `rd = (rs1 * rs2)[127:64]`. |
|
| 295 | + | pub fn mulhu(rd: super::Reg, rs1: super::Reg, rs2: super::Reg) -> u32 { |
|
| 296 | + | return encodeR(OP_OP, rd, rs1, rs2, F3_SLTU, F7_MUL); |
|
| 297 | + | } |
|
| 298 | + | ||
| 299 | + | /// Divide (signed): `rd = rs1 / rs2`. |
|
| 300 | + | pub fn div(rd: super::Reg, rs1: super::Reg, rs2: super::Reg) -> u32 { |
|
| 301 | + | return encodeR(OP_OP, rd, rs1, rs2, F3_XOR, F7_MUL); |
|
| 302 | + | } |
|
| 303 | + | ||
| 304 | + | /// Divide (unsigned): `rd = rs1 / rs2`. |
|
| 305 | + | pub fn divu(rd: super::Reg, rs1: super::Reg, rs2: super::Reg) -> u32 { |
|
| 306 | + | return encodeR(OP_OP, rd, rs1, rs2, F3_SRL, F7_MUL); |
|
| 307 | + | } |
|
| 308 | + | ||
| 309 | + | /// Remainder (signed): `rd = rs1 % rs2`. |
|
| 310 | + | pub fn rem(rd: super::Reg, rs1: super::Reg, rs2: super::Reg) -> u32 { |
|
| 311 | + | return encodeR(OP_OP, rd, rs1, rs2, F3_OR, F7_MUL); |
|
| 312 | + | } |
|
| 313 | + | ||
| 314 | + | /// Remainder (unsigned): `rd = rs1 % rs2`. |
|
| 315 | + | pub fn remu(rd: super::Reg, rs1: super::Reg, rs2: super::Reg) -> u32 { |
|
| 316 | + | return encodeR(OP_OP, rd, rs1, rs2, F3_AND, F7_MUL); |
|
| 317 | + | } |
|
| 318 | + | ||
| 319 | + | ////////////////////////// |
|
| 320 | + | // RV64 Word Operations // |
|
| 321 | + | ////////////////////////// |
|
| 322 | + | ||
| 323 | + | /// Add immediate word (32-bit, sign-extended): `rd = sign_ext((rs1 + imm)[31:0])`. |
|
| 324 | + | pub fn addiw(rd: super::Reg, rs1: super::Reg, imm: i32) -> u32 { |
|
| 325 | + | return encodeI(OP_IMM32, rd, rs1, F3_ADD, imm); |
|
| 326 | + | } |
|
| 327 | + | ||
| 328 | + | /// Shift left logical immediate word: `rd = sign_ext((rs1 << shamt)[31:0])`. |
|
| 329 | + | pub fn slliw(rd: super::Reg, rs1: super::Reg, shamt: i32) -> u32 { |
|
| 330 | + | debug::assert(shamt >= 0 and shamt < 32); |
|
| 331 | + | return encodeI(OP_IMM32, rd, rs1, F3_SLL, shamt & 0x1F); |
|
| 332 | + | } |
|
| 333 | + | ||
| 334 | + | /// Shift right logical immediate word: `rd = sign_ext((rs1[31:0] >> shamt))`. |
|
| 335 | + | pub fn srliw(rd: super::Reg, rs1: super::Reg, shamt: i32) -> u32 { |
|
| 336 | + | debug::assert(shamt >= 0 and shamt < 32); |
|
| 337 | + | return encodeI(OP_IMM32, rd, rs1, F3_SRL, shamt & 0x1F); |
|
| 338 | + | } |
|
| 339 | + | ||
| 340 | + | /// Shift right arithmetic immediate word: `rd = sign_ext((rs1[31:0] >> shamt))` (sign-extended). |
|
| 341 | + | pub fn sraiw(rd: super::Reg, rs1: super::Reg, shamt: i32) -> u32 { |
|
| 342 | + | debug::assert(shamt >= 0 and shamt < 32); |
|
| 343 | + | return encodeI(OP_IMM32, rd, rs1, F3_SRL, (shamt & 0x1F) | 0b10000000000); |
|
| 344 | + | } |
|
| 345 | + | ||
| 346 | + | /// Add word: `rd = sign_ext((rs1 + rs2)[31:0])`. |
|
| 347 | + | pub fn addw(rd: super::Reg, rs1: super::Reg, rs2: super::Reg) -> u32 { |
|
| 348 | + | return encodeR(OP_OP32, rd, rs1, rs2, F3_ADD, F7_NORMAL); |
|
| 349 | + | } |
|
| 350 | + | ||
| 351 | + | /// Subtract word: `rd = sign_ext((rs1 - rs2)[31:0])`. |
|
| 352 | + | pub fn subw(rd: super::Reg, rs1: super::Reg, rs2: super::Reg) -> u32 { |
|
| 353 | + | return encodeR(OP_OP32, rd, rs1, rs2, F3_ADD, F7_SUB); |
|
| 354 | + | } |
|
| 355 | + | ||
| 356 | + | /// Shift left logical word: `rd = sign_ext((rs1 << rs2[4:0])[31:0])`. |
|
| 357 | + | pub fn sllw(rd: super::Reg, rs1: super::Reg, rs2: super::Reg) -> u32 { |
|
| 358 | + | return encodeR(OP_OP32, rd, rs1, rs2, F3_SLL, F7_NORMAL); |
|
| 359 | + | } |
|
| 360 | + | ||
| 361 | + | /// Shift right logical word: `rd = sign_ext((rs1[31:0] >> rs2[4:0]))`. |
|
| 362 | + | pub fn srlw(rd: super::Reg, rs1: super::Reg, rs2: super::Reg) -> u32 { |
|
| 363 | + | return encodeR(OP_OP32, rd, rs1, rs2, F3_SRL, F7_NORMAL); |
|
| 364 | + | } |
|
| 365 | + | ||
| 366 | + | /// Shift right arithmetic word: `rd = sign_ext((rs1[31:0] >> rs2[4:0]))` (sign-extended). |
|
| 367 | + | pub fn sraw(rd: super::Reg, rs1: super::Reg, rs2: super::Reg) -> u32 { |
|
| 368 | + | return encodeR(OP_OP32, rd, rs1, rs2, F3_SRL, F7_SRA); |
|
| 369 | + | } |
|
| 370 | + | ||
| 371 | + | /// Multiply word: `rd = sign_ext((rs1 * rs2)[31:0])`. |
|
| 372 | + | pub fn mulw(rd: super::Reg, rs1: super::Reg, rs2: super::Reg) -> u32 { |
|
| 373 | + | return encodeR(OP_OP32, rd, rs1, rs2, F3_ADD, F7_MUL); |
|
| 374 | + | } |
|
| 375 | + | ||
| 376 | + | /// Divide word (signed): `rd = sign_ext(rs1[31:0] / rs2[31:0])`. |
|
| 377 | + | pub fn divw(rd: super::Reg, rs1: super::Reg, rs2: super::Reg) -> u32 { |
|
| 378 | + | return encodeR(OP_OP32, rd, rs1, rs2, F3_XOR, F7_MUL); |
|
| 379 | + | } |
|
| 380 | + | ||
| 381 | + | /// Divide word (unsigned): `rd = sign_ext(rs1[31:0] / rs2[31:0])`. |
|
| 382 | + | pub fn divuw(rd: super::Reg, rs1: super::Reg, rs2: super::Reg) -> u32 { |
|
| 383 | + | return encodeR(OP_OP32, rd, rs1, rs2, F3_SRL, F7_MUL); |
|
| 384 | + | } |
|
| 385 | + | ||
| 386 | + | /// Remainder word (signed): `rd = sign_ext(rs1[31:0] % rs2[31:0])`. |
|
| 387 | + | pub fn remw(rd: super::Reg, rs1: super::Reg, rs2: super::Reg) -> u32 { |
|
| 388 | + | return encodeR(OP_OP32, rd, rs1, rs2, F3_OR, F7_MUL); |
|
| 389 | + | } |
|
| 390 | + | ||
| 391 | + | /// Remainder word (unsigned): `rd = sign_ext(rs1[31:0] % rs2[31:0])`. |
|
| 392 | + | pub fn remuw(rd: super::Reg, rs1: super::Reg, rs2: super::Reg) -> u32 { |
|
| 393 | + | return encodeR(OP_OP32, rd, rs1, rs2, F3_AND, F7_MUL); |
|
| 394 | + | } |
|
| 395 | + | ||
| 396 | + | /////////////////// |
|
| 397 | + | // Load (I-type) // |
|
| 398 | + | /////////////////// |
|
| 399 | + | ||
| 400 | + | /// Load byte (sign-extend): `rd = mem[rs1 + imm][7:0]`. |
|
| 401 | + | pub fn lb(rd: super::Reg, rs1: super::Reg, imm: i32) -> u32 { |
|
| 402 | + | return encodeI(OP_LOAD, rd, rs1, F3_BYTE, imm); |
|
| 403 | + | } |
|
| 404 | + | ||
| 405 | + | /// Load halfword (sign-extend): `rd = mem[rs1 + imm][15:0]`. |
|
| 406 | + | pub fn lh(rd: super::Reg, rs1: super::Reg, imm: i32) -> u32 { |
|
| 407 | + | return encodeI(OP_LOAD, rd, rs1, F3_HALF, imm); |
|
| 408 | + | } |
|
| 409 | + | ||
| 410 | + | /// Load word (sign-extend to 64-bit): `rd = sign_ext(mem[rs1 + imm][31:0])`. |
|
| 411 | + | pub fn lw(rd: super::Reg, rs1: super::Reg, imm: i32) -> u32 { |
|
| 412 | + | return encodeI(OP_LOAD, rd, rs1, F3_WORD, imm); |
|
| 413 | + | } |
|
| 414 | + | ||
| 415 | + | /// Load byte unsigned (zero-extend): `rd = mem[rs1 + imm][7:0]`. |
|
| 416 | + | pub fn lbu(rd: super::Reg, rs1: super::Reg, imm: i32) -> u32 { |
|
| 417 | + | return encodeI(OP_LOAD, rd, rs1, F3_BYTE_U, imm); |
|
| 418 | + | } |
|
| 419 | + | ||
| 420 | + | /// Load halfword unsigned (zero-extend): `rd = mem[rs1 + imm][15:0]`. |
|
| 421 | + | pub fn lhu(rd: super::Reg, rs1: super::Reg, imm: i32) -> u32 { |
|
| 422 | + | return encodeI(OP_LOAD, rd, rs1, F3_HALF_U, imm); |
|
| 423 | + | } |
|
| 424 | + | ||
| 425 | + | /// Load word unsigned (zero-extend to 64-bit): `rd = mem[rs1 + imm][31:0]`. |
|
| 426 | + | pub fn lwu(rd: super::Reg, rs1: super::Reg, imm: i32) -> u32 { |
|
| 427 | + | return encodeI(OP_LOAD, rd, rs1, F3_WORD_U, imm); |
|
| 428 | + | } |
|
| 429 | + | ||
| 430 | + | /// Load doubleword: `rd = mem[rs1 + imm][63:0]`. |
|
| 431 | + | pub fn ld(rd: super::Reg, rs1: super::Reg, imm: i32) -> u32 { |
|
| 432 | + | return encodeI(OP_LOAD, rd, rs1, F3_DWORD, imm); |
|
| 433 | + | } |
|
| 434 | + | ||
| 435 | + | //////////////////// |
|
| 436 | + | // Store (S-type) // |
|
| 437 | + | //////////////////// |
|
| 438 | + | ||
| 439 | + | /// Store byte: `mem[rs1 + imm] = rs2[7:0]`. |
|
| 440 | + | pub fn sb(rs2: super::Reg, rs1: super::Reg, imm: i32) -> u32 { |
|
| 441 | + | return encodeS(OP_STORE, rs1, rs2, F3_BYTE, imm); |
|
| 442 | + | } |
|
| 443 | + | ||
| 444 | + | /// Store halfword: `mem[rs1 + imm] = rs2[15:0]`. |
|
| 445 | + | pub fn sh(rs2: super::Reg, rs1: super::Reg, imm: i32) -> u32 { |
|
| 446 | + | return encodeS(OP_STORE, rs1, rs2, F3_HALF, imm); |
|
| 447 | + | } |
|
| 448 | + | ||
| 449 | + | /// Store word: `mem[rs1 + imm] = rs2[31:0]`. |
|
| 450 | + | pub fn sw(rs2: super::Reg, rs1: super::Reg, imm: i32) -> u32 { |
|
| 451 | + | return encodeS(OP_STORE, rs1, rs2, F3_WORD, imm); |
|
| 452 | + | } |
|
| 453 | + | ||
| 454 | + | /// Store doubleword: `mem[rs1 + imm] = rs2[63:0]`. |
|
| 455 | + | pub fn sd(rs2: super::Reg, rs1: super::Reg, imm: i32) -> u32 { |
|
| 456 | + | return encodeS(OP_STORE, rs1, rs2, F3_DWORD, imm); |
|
| 457 | + | } |
|
| 458 | + | ||
| 459 | + | ///////////////////// |
|
| 460 | + | // Branch (B-type) // |
|
| 461 | + | ///////////////////// |
|
| 462 | + | ||
| 463 | + | /// Branch if equal: `if (rs1 == rs2) pc += imm`. |
|
| 464 | + | pub fn beq(rs1: super::Reg, rs2: super::Reg, imm: i32) -> u32 { |
|
| 465 | + | return encodeB(OP_BRANCH, rs1, rs2, F3_BEQ, imm); |
|
| 466 | + | } |
|
| 467 | + | ||
| 468 | + | /// Branch if not equal: `if (rs1 != rs2) pc += imm`. |
|
| 469 | + | pub fn bne(rs1: super::Reg, rs2: super::Reg, imm: i32) -> u32 { |
|
| 470 | + | return encodeB(OP_BRANCH, rs1, rs2, F3_BNE, imm); |
|
| 471 | + | } |
|
| 472 | + | ||
| 473 | + | /// Branch if less than (signed): `if (rs1 < rs2) pc += imm`. |
|
| 474 | + | pub fn blt(rs1: super::Reg, rs2: super::Reg, imm: i32) -> u32 { |
|
| 475 | + | return encodeB(OP_BRANCH, rs1, rs2, F3_BLT, imm); |
|
| 476 | + | } |
|
| 477 | + | ||
| 478 | + | /// Branch if greater or equal (signed): `if (rs1 >= rs2) pc += imm`. |
|
| 479 | + | pub fn bge(rs1: super::Reg, rs2: super::Reg, imm: i32) -> u32 { |
|
| 480 | + | return encodeB(OP_BRANCH, rs1, rs2, F3_BGE, imm); |
|
| 481 | + | } |
|
| 482 | + | ||
| 483 | + | /// Branch if less than unsigned: `if (rs1 < rs2) pc += imm`. |
|
| 484 | + | pub fn bltu(rs1: super::Reg, rs2: super::Reg, imm: i32) -> u32 { |
|
| 485 | + | return encodeB(OP_BRANCH, rs1, rs2, F3_BLTU, imm); |
|
| 486 | + | } |
|
| 487 | + | ||
| 488 | + | /// Branch if greater or equal unsigned: `if (rs1 >= rs2) pc += imm`. |
|
| 489 | + | pub fn bgeu(rs1: super::Reg, rs2: super::Reg, imm: i32) -> u32 { |
|
| 490 | + | return encodeB(OP_BRANCH, rs1, rs2, F3_BGEU, imm); |
|
| 491 | + | } |
|
| 492 | + | ||
| 493 | + | ////////// |
|
| 494 | + | // Jump // |
|
| 495 | + | ////////// |
|
| 496 | + | ||
| 497 | + | /// Jump and link: `rd = pc + 4; pc += imm`. |
|
| 498 | + | pub fn jal(rd: super::Reg, imm: i32) -> u32 { |
|
| 499 | + | return encodeJ(OP_JAL, rd, imm); |
|
| 500 | + | } |
|
| 501 | + | ||
| 502 | + | /// Jump and link register: `rd = pc + 4; pc = rs1 + imm`. |
|
| 503 | + | pub fn jalr(rd: super::Reg, rs1: super::Reg, imm: i32) -> u32 { |
|
| 504 | + | return encodeI(OP_JALR, rd, rs1, 0, imm); |
|
| 505 | + | } |
|
| 506 | + | ||
| 507 | + | ///////////////////// |
|
| 508 | + | // Upper Immediate // |
|
| 509 | + | ///////////////////// |
|
| 510 | + | ||
| 511 | + | /// Load upper immediate: `rd = imm << 12`. |
|
| 512 | + | pub fn lui(rd: super::Reg, imm: i32) -> u32 { |
|
| 513 | + | return encodeU(OP_LUI, rd, imm); |
|
| 514 | + | } |
|
| 515 | + | ||
| 516 | + | /// Add upper immediate to PC: `rd = pc + (imm << 12)`. |
|
| 517 | + | pub fn auipc(rd: super::Reg, imm: i32) -> u32 { |
|
| 518 | + | return encodeU(OP_AUIPC, rd, imm); |
|
| 519 | + | } |
|
| 520 | + | ||
| 521 | + | //////////// |
|
| 522 | + | // System // |
|
| 523 | + | //////////// |
|
| 524 | + | ||
| 525 | + | /// Environment call (system call). |
|
| 526 | + | pub fn ecall() -> u32 { |
|
| 527 | + | return encodeI(OP_SYSTEM, super::ZERO, super::ZERO, 0, 0); |
|
| 528 | + | } |
|
| 529 | + | ||
| 530 | + | /// Environment break (debugger breakpoint). |
|
| 531 | + | pub fn ebreak() -> u32 { |
|
| 532 | + | return encodeI(OP_SYSTEM, super::ZERO, super::ZERO, 0, 1); |
|
| 533 | + | } |
|
| 534 | + | ||
| 535 | + | ///////////////////////// |
|
| 536 | + | // Pseudo-instructions // |
|
| 537 | + | ///////////////////////// |
|
| 538 | + | ||
| 539 | + | /// No operation: `addi zero, zero, 0`. |
|
| 540 | + | pub fn nop() -> u32 { |
|
| 541 | + | return addi(super::ZERO, super::ZERO, 0); |
|
| 542 | + | } |
|
| 543 | + | ||
| 544 | + | /// Move: `rd = rs` (`addi rd, rs, 0`). |
|
| 545 | + | pub fn mv(rd: super::Reg, rs: super::Reg) -> u32 { |
|
| 546 | + | return addi(rd, rs, 0); |
|
| 547 | + | } |
|
| 548 | + | ||
| 549 | + | /// Bitwise NOT: `rd = ~rs` (`xori rd, rs, -1`). |
|
| 550 | + | pub fn not_(rd: super::Reg, rs: super::Reg) -> u32 { |
|
| 551 | + | return xori(rd, rs, -1); |
|
| 552 | + | } |
|
| 553 | + | ||
| 554 | + | /// Negate: `rd = -rs` (`sub rd, zero, rs`). |
|
| 555 | + | pub fn neg(rd: super::Reg, rs: super::Reg) -> u32 { |
|
| 556 | + | return sub(rd, super::ZERO, rs); |
|
| 557 | + | } |
|
| 558 | + | ||
| 559 | + | /// Return: `jalr zero, ra, 0`. |
|
| 560 | + | pub fn ret() -> u32 { |
|
| 561 | + | return jalr(super::ZERO, super::RA, 0); |
|
| 562 | + | } |
|
| 563 | + | ||
| 564 | + | /// Jump (unconditional): `jal zero, imm`. |
|
| 565 | + | pub fn j(imm: i32) -> u32 { |
|
| 566 | + | return jal(super::ZERO, imm); |
|
| 567 | + | } |
|
| 568 | + | ||
| 569 | + | /// Branch if less than or equal (signed): `if (rs1 <= rs2) pc += imm`. |
|
| 570 | + | /// Implemented as `bge rs2, rs1, imm` (swap operands). |
|
| 571 | + | pub fn ble(rs1: super::Reg, rs2: super::Reg, imm: i32) -> u32 { |
|
| 572 | + | return bge(rs2, rs1, imm); |
|
| 573 | + | } |
|
| 574 | + | ||
| 575 | + | /// Branch if greater than (signed): `if (rs1 > rs2) pc += imm`. |
|
| 576 | + | /// Implemented as `blt rs2, rs1, imm` (swap operands). |
|
| 577 | + | pub fn bgt(rs1: super::Reg, rs2: super::Reg, imm: i32) -> u32 { |
|
| 578 | + | return blt(rs2, rs1, imm); |
|
| 579 | + | } |
|
| 580 | + | ||
| 581 | + | /// Branch if less than or equal unsigned: `if (rs1 <= rs2) pc += imm`. |
|
| 582 | + | /// Implemented as `bgeu rs2, rs1, imm` (swap operands). |
|
| 583 | + | pub fn bleu(rs1: super::Reg, rs2: super::Reg, imm: i32) -> u32 { |
|
| 584 | + | return bgeu(rs2, rs1, imm); |
|
| 585 | + | } |
|
| 586 | + | ||
| 587 | + | /// Branch if greater than unsigned: `if (rs1 > rs2) pc += imm`. |
|
| 588 | + | /// Implemented as `bltu rs2, rs1, imm` (swap operands). |
|
| 589 | + | pub fn bgtu(rs1: super::Reg, rs2: super::Reg, imm: i32) -> u32 { |
|
| 590 | + | return bltu(rs2, rs1, imm); |
|
| 591 | + | } |
|
| 592 | + | ||
| 593 | + | /// Set if equal to zero: `rd = (rs == 0) ? 1 : 0`. |
|
| 594 | + | /// Implemented as `sltiu rd, rs, 1`. |
|
| 595 | + | pub fn seqz(rd: super::Reg, rs: super::Reg) -> u32 { |
|
| 596 | + | return sltiu(rd, rs, 1); |
|
| 597 | + | } |
|
| 598 | + | ||
| 599 | + | /// Set if not equal to zero: `rd = (rs != 0) ? 1 : 0`. |
|
| 600 | + | /// Implemented as `sltu rd, zero, rs`. |
|
| 601 | + | pub fn snez(rd: super::Reg, rs: super::Reg) -> u32 { |
|
| 602 | + | return sltu(rd, super::ZERO, rs); |
|
| 603 | + | } |
|
| 604 | + | ||
| 605 | + | /// Branch if equal to zero: `if (rs == 0) pc += imm`. |
|
| 606 | + | pub fn beqz(rs: super::Reg, imm: i32) -> u32 { |
|
| 607 | + | return beq(rs, super::ZERO, imm); |
|
| 608 | + | } |
|
| 609 | + | ||
| 610 | + | /// Branch if not equal to zero: `if (rs != 0) pc += imm`. |
|
| 611 | + | pub fn bnez(rs: super::Reg, imm: i32) -> u32 { |
|
| 612 | + | return bne(rs, super::ZERO, imm); |
|
| 613 | + | } |
|
| 614 | + | ||
| 615 | + | /// Call: `jal ra, imm`. |
|
| 616 | + | pub fn call(imm: i32) -> u32 { |
|
| 617 | + | return jal(super::RA, imm); |
|
| 618 | + | } |
lib/std/arch/rv64/isel.rad
added
+1030 -0
| 1 | + | //! RV64 instruction selection. |
|
| 2 | + | //! |
|
| 3 | + | //! Walks IL and selects RV64 instructions for each operation. |
|
| 4 | + | //! |
|
| 5 | + | //! *Register resolution hierarchy* |
|
| 6 | + | //! |
|
| 7 | + | //! getReg(ssa) -> Reg |
|
| 8 | + | //! Primitive physical register lookup. Panics if the register is spilled. |
|
| 9 | + | //! Used as a building block by the functions below. |
|
| 10 | + | //! |
|
| 11 | + | //! getSrcReg(ssa, scratch) -> Reg |
|
| 12 | + | //! Source register for an [`il::Reg`] operand. Returns the physical register, |
|
| 13 | + | //! or loads a spilled value into `scratch`. Used for instruction fields |
|
| 14 | + | //! typed as [`il::Reg`] (e.g. base addresses in Load/Store/Blit). |
|
| 15 | + | //! |
|
| 16 | + | //! getDstReg(ssa, scratch) -> Reg |
|
| 17 | + | //! Destination register for an instruction result. Returns the physical |
|
| 18 | + | //! register, or records a pending spill and returns `scratch`. The pending |
|
| 19 | + | //! spill is flushed by [`selectBlock`] after each instruction. |
|
| 20 | + | //! |
|
| 21 | + | //! resolveVal(scratch, val) -> Reg |
|
| 22 | + | //! Resolve an [`il::Val`] to whatever register holds it. Delegates to [`getSrcReg`] |
|
| 23 | + | //! for register values; materializes immediates and symbols into `scratch`. |
|
| 24 | + | //! Used for operands that can be consumed from any register. |
|
| 25 | + | //! |
|
| 26 | + | //! loadVal(rd, val) -> Reg |
|
| 27 | + | //! Force an [`il::Val`] into a specific register `rd`. Built on [`resolveVal`] + [`emitMv`]. |
|
| 28 | + | //! Used when the instruction requires the value in `rd` (e.g. `sub rd, rd, rs2`). |
|
| 29 | + | ||
| 30 | + | use std::mem; |
|
| 31 | + | use std::debug; |
|
| 32 | + | use std::lang::il; |
|
| 33 | + | use std::lang::gen::regalloc; |
|
| 34 | + | use std::lang::gen::labels; |
|
| 35 | + | ||
| 36 | + | use super::encode; |
|
| 37 | + | use super::emit; |
|
| 38 | + | ||
| 39 | + | /////////////// |
|
| 40 | + | // Constants // |
|
| 41 | + | /////////////// |
|
| 42 | + | ||
| 43 | + | /// Shift amount for byte sign/zero extension (64 - 8). |
|
| 44 | + | const SHIFT_W8: i32 = 56; |
|
| 45 | + | /// Shift amount for halfword sign/zero extension (64 - 16). |
|
| 46 | + | const SHIFT_W16: i32 = 48; |
|
| 47 | + | /// Shift amount for word sign/zero extension (64 - 32). |
|
| 48 | + | const SHIFT_W32: i32 = 32; |
|
| 49 | + | /// Mask for extracting byte value. |
|
| 50 | + | const MASK_W8: i32 = 0xFF; |
|
| 51 | + | /// Maximum number of block arguments supported. |
|
| 52 | + | const MAX_BLOCK_ARGS: u32 = 16; |
|
| 53 | + | ||
| 54 | + | /// Binary operation. |
|
| 55 | + | union BinOp { Add, And, Or, Xor } |
|
| 56 | + | /// Shift operation. |
|
| 57 | + | union ShiftOp { Sll, Srl, Sra } |
|
| 58 | + | /// Compare operation. |
|
| 59 | + | union CmpOp { Slt, Ult } |
|
| 60 | + | ||
| 61 | + | /// Selector errors. |
|
| 62 | + | pub union SelectorError { |
|
| 63 | + | Internal, |
|
| 64 | + | } |
|
| 65 | + | ||
| 66 | + | /// A pending spill store to be flushed after instruction selection. |
|
| 67 | + | record PendingSpill { |
|
| 68 | + | /// The SSA register that was spilled. |
|
| 69 | + | ssa: il::Reg, |
|
| 70 | + | /// The physical register holding the value to store. |
|
| 71 | + | rd: super::Reg, |
|
| 72 | + | } |
|
| 73 | + | ||
| 74 | + | //////////////////// |
|
| 75 | + | // Selector State // |
|
| 76 | + | //////////////////// |
|
| 77 | + | ||
| 78 | + | /// Instruction selector state. |
|
| 79 | + | pub record Selector { |
|
| 80 | + | /// Emitter for outputting instructions. |
|
| 81 | + | e: *mut emit::Emitter, |
|
| 82 | + | /// Register allocation result. |
|
| 83 | + | ralloc: *regalloc::AllocResult, |
|
| 84 | + | /// Data symbols for resolving symbol addresses. |
|
| 85 | + | dataSyms: *[super::DataSym], |
|
| 86 | + | /// Total stack frame size. |
|
| 87 | + | frameSize: i32, |
|
| 88 | + | /// Running offset into the reserve region of the frame. |
|
| 89 | + | /// Tracks current position within the pre-allocated reserve slots. |
|
| 90 | + | reserveOffset: i32, |
|
| 91 | + | /// Pending spill store, auto-committed after each instruction. |
|
| 92 | + | pendingSpill: ?PendingSpill, |
|
| 93 | + | /// Next synthetic block index for skip-branch targets. |
|
| 94 | + | nextSynthBlock: u32, |
|
| 95 | + | } |
|
| 96 | + | ||
| 97 | + | ///////////////////////// |
|
| 98 | + | // Register Allocation // |
|
| 99 | + | ///////////////////////// |
|
| 100 | + | ||
| 101 | + | /// Get the physical register for an already-allocated SSA register. |
|
| 102 | + | fn getReg(s: *Selector, ssa: il::Reg) -> super::Reg { |
|
| 103 | + | let phys = s.ralloc.assignments[ssa.n] else { |
|
| 104 | + | panic "getReg: spilled register has no physical assignment"; |
|
| 105 | + | }; |
|
| 106 | + | return super::Reg { n: phys }; |
|
| 107 | + | } |
|
| 108 | + | ||
| 109 | + | /// Compute the FP-relative offset for a spill slot. |
|
| 110 | + | /// Spill slots live at the bottom of the frame. |
|
| 111 | + | /// Since `FP = SP + totalFrameSize`, the offset from FP is `slot - totalSize`. |
|
| 112 | + | fn spillOffset(s: *Selector, slot: i32) -> i32 { |
|
| 113 | + | return slot - s.frameSize; |
|
| 114 | + | } |
|
| 115 | + | ||
| 116 | + | /// Get the destination register for an SSA register. |
|
| 117 | + | /// If the register is spilled, records a pending spill and returns the scratch |
|
| 118 | + | /// register. The pending spill is auto-committed by [`selectBlock`] after each |
|
| 119 | + | /// instruction. If not spilled, returns the physical register. |
|
| 120 | + | fn getDstReg(s: *mut Selector, ssa: il::Reg, scratch: super::Reg) -> super::Reg { |
|
| 121 | + | if let _ = regalloc::spill::spillSlot(&s.ralloc.spill, ssa) { |
|
| 122 | + | s.pendingSpill = PendingSpill { ssa, rd: scratch }; |
|
| 123 | + | return scratch; |
|
| 124 | + | } |
|
| 125 | + | return getReg(s, ssa); |
|
| 126 | + | } |
|
| 127 | + | ||
| 128 | + | /// Get the source register for an SSA register. |
|
| 129 | + | /// If the register is spilled, loads the value from the spill slot into the |
|
| 130 | + | /// scratch register and returns it. Otherwise returns the physical register. |
|
| 131 | + | fn getSrcReg(s: *mut Selector, ssa: il::Reg, scratch: super::Reg) -> super::Reg { |
|
| 132 | + | if let slot = regalloc::spill::spillSlot(&s.ralloc.spill, ssa) { |
|
| 133 | + | emit::emitLd(s.e, scratch, super::FP, spillOffset(s, slot)); |
|
| 134 | + | return scratch; |
|
| 135 | + | } |
|
| 136 | + | return getReg(s, ssa); |
|
| 137 | + | } |
|
| 138 | + | ||
| 139 | + | /// Look up symbol address in data map. |
|
| 140 | + | fn lookupDataSym(s: *Selector, name: *[u8]) -> u32 throws (SelectorError) { |
|
| 141 | + | for i in 0..s.dataSyms.len { |
|
| 142 | + | let sym = s.dataSyms[i]; |
|
| 143 | + | ||
| 144 | + | if mem::eq(sym.name, name) { |
|
| 145 | + | return sym.addr; |
|
| 146 | + | } |
|
| 147 | + | } |
|
| 148 | + | throw SelectorError::Internal; |
|
| 149 | + | } |
|
| 150 | + | ||
| 151 | + | /// Resolve an IL value to the physical register holding it. |
|
| 152 | + | /// For non-spilled register values, returns the physical register directly. |
|
| 153 | + | /// For immediates, symbols, and spilled registers, materializes into `scratch`. |
|
| 154 | + | fn resolveVal(s: *mut Selector, scratch: super::Reg, val: il::Val) -> super::Reg { |
|
| 155 | + | match val { |
|
| 156 | + | case il::Val::Reg(r) => { |
|
| 157 | + | return getSrcReg(s, r, scratch); |
|
| 158 | + | }, |
|
| 159 | + | case il::Val::Imm(imm) => { |
|
| 160 | + | emit::loadImm(s.e, scratch, imm); |
|
| 161 | + | return scratch; |
|
| 162 | + | }, |
|
| 163 | + | case il::Val::DataSym(name) => { |
|
| 164 | + | let addr = try lookupDataSym(s, name) catch { |
|
| 165 | + | panic "resolveVal: data symbol not found"; |
|
| 166 | + | }; |
|
| 167 | + | emit::loadImm(s.e, scratch, addr as i32); |
|
| 168 | + | return scratch; |
|
| 169 | + | }, |
|
| 170 | + | case il::Val::FnAddr(name) => { |
|
| 171 | + | emit::recordAddrLoad(s.e, name, scratch); |
|
| 172 | + | return scratch; |
|
| 173 | + | }, |
|
| 174 | + | case il::Val::Undef => { |
|
| 175 | + | return scratch; |
|
| 176 | + | } |
|
| 177 | + | } |
|
| 178 | + | } |
|
| 179 | + | ||
| 180 | + | /// Load an IL value into a specific physical register. |
|
| 181 | + | /// Like [`resolveVal`], but ensures the value ends up in `rd`. |
|
| 182 | + | fn loadVal(s: *mut Selector, rd: super::Reg, val: il::Val) -> super::Reg { |
|
| 183 | + | let rs = resolveVal(s, rd, val); |
|
| 184 | + | emitMv(s, rd, rs); |
|
| 185 | + | return rd; |
|
| 186 | + | } |
|
| 187 | + | ||
| 188 | + | /// Emit a move instruction if source and destination differ. |
|
| 189 | + | fn emitMv(s: *mut Selector, rd: super::Reg, rs: super::Reg) { |
|
| 190 | + | if rd.n != rs.n { |
|
| 191 | + | emit::emit(s.e, encode::mv(rd, rs)); |
|
| 192 | + | } |
|
| 193 | + | } |
|
| 194 | + | ||
| 195 | + | /// Emit zero-extension from a sub-word type to the full register width. |
|
| 196 | + | fn emitZext(e: *mut emit::Emitter, rd: super::Reg, rs: super::Reg, typ: il::Type) { |
|
| 197 | + | match typ { |
|
| 198 | + | case il::Type::W8 => emit::emit(e, encode::andi(rd, rs, MASK_W8)), |
|
| 199 | + | case il::Type::W16 => { |
|
| 200 | + | emit::emit(e, encode::slli(rd, rs, SHIFT_W16)); |
|
| 201 | + | emit::emit(e, encode::srli(rd, rd, SHIFT_W16)); |
|
| 202 | + | }, |
|
| 203 | + | case il::Type::W32 => { |
|
| 204 | + | emit::emit(e, encode::slli(rd, rs, SHIFT_W32)); |
|
| 205 | + | emit::emit(e, encode::srli(rd, rd, SHIFT_W32)); |
|
| 206 | + | }, |
|
| 207 | + | case il::Type::W64 => {} |
|
| 208 | + | } |
|
| 209 | + | } |
|
| 210 | + | ||
| 211 | + | /// Emit sign-extension from a sub-word type to the full register width. |
|
| 212 | + | fn emitSext(e: *mut emit::Emitter, rd: super::Reg, rs: super::Reg, typ: il::Type) { |
|
| 213 | + | match typ { |
|
| 214 | + | case il::Type::W8 => { |
|
| 215 | + | emit::emit(e, encode::slli(rd, rs, SHIFT_W8)); |
|
| 216 | + | emit::emit(e, encode::srai(rd, rd, SHIFT_W8)); |
|
| 217 | + | }, |
|
| 218 | + | case il::Type::W16 => { |
|
| 219 | + | emit::emit(e, encode::slli(rd, rs, SHIFT_W16)); |
|
| 220 | + | emit::emit(e, encode::srai(rd, rd, SHIFT_W16)); |
|
| 221 | + | }, |
|
| 222 | + | case il::Type::W32 => { |
|
| 223 | + | emit::emit(e, encode::addiw(rd, rs, 0)); |
|
| 224 | + | }, |
|
| 225 | + | case il::Type::W64 => {} |
|
| 226 | + | } |
|
| 227 | + | } |
|
| 228 | + | ||
| 229 | + | //////////////////////// |
|
| 230 | + | // Instruction Select // |
|
| 231 | + | //////////////////////// |
|
| 232 | + | ||
| 233 | + | /// Pre-scan all blocks for constant-sized reserve instructions. |
|
| 234 | + | /// Returns the total size needed for all static reserves, respecting alignment. |
|
| 235 | + | fn computeReserveSize(func: *il::Fn) -> i32 { |
|
| 236 | + | let mut offset: i32 = 0; |
|
| 237 | + | for b in 0..func.blocks.len { |
|
| 238 | + | let block = &func.blocks[b]; |
|
| 239 | + | for i in 0..block.instrs.len { |
|
| 240 | + | match block.instrs.list[i] { |
|
| 241 | + | case il::Instr::Reserve { size, alignment, .. } => { |
|
| 242 | + | if let case il::Val::Imm(sz) = size { |
|
| 243 | + | offset = mem::alignUpI32(offset, alignment as i32); |
|
| 244 | + | offset = offset + sz; |
|
| 245 | + | } |
|
| 246 | + | }, |
|
| 247 | + | else => {}, |
|
| 248 | + | } |
|
| 249 | + | } |
|
| 250 | + | } |
|
| 251 | + | return offset; |
|
| 252 | + | } |
|
| 253 | + | ||
| 254 | + | /// Select instructions for a function. |
|
| 255 | + | pub fn selectFn( |
|
| 256 | + | e: *mut emit::Emitter, |
|
| 257 | + | dataSyms: *[super::DataSym], |
|
| 258 | + | ralloc: *regalloc::AllocResult, |
|
| 259 | + | func: *il::Fn |
|
| 260 | + | ) { |
|
| 261 | + | // Reset block offsets for this function. |
|
| 262 | + | labels::resetBlocks(&mut e.labels); |
|
| 263 | + | // Pre-scan for constant-sized reserves to promote to fixed frame slots. |
|
| 264 | + | let reserveSize = computeReserveSize(func); |
|
| 265 | + | // Compute frame layout from spill slots, reserve slots, and used callee-saved registers. |
|
| 266 | + | let frame = emit::computeFrame( |
|
| 267 | + | ralloc.spill.frameSize + reserveSize, |
|
| 268 | + | ralloc.usedCalleeSaved, |
|
| 269 | + | func.blocks.len |
|
| 270 | + | ); |
|
| 271 | + | // Synthetic block indices start after real blocks and the epilogue block. |
|
| 272 | + | let mut s = Selector { |
|
| 273 | + | e, ralloc, dataSyms, frameSize: frame.totalSize, |
|
| 274 | + | reserveOffset: 0, pendingSpill: nil, |
|
| 275 | + | nextSynthBlock: func.blocks.len + 1, |
|
| 276 | + | }; |
|
| 277 | + | // Record function name for printing. |
|
| 278 | + | emit::recordFunc(s.e, func.name); |
|
| 279 | + | // Record function code offset for call patching. |
|
| 280 | + | emit::recordFuncOffset(s.e, func.name); |
|
| 281 | + | // Emit prologue. |
|
| 282 | + | emit::emitPrologue(s.e, &frame); |
|
| 283 | + | ||
| 284 | + | // Move function params from arg registers to assigned registers. |
|
| 285 | + | // Cross-call params may have been assigned to callee-saved registers |
|
| 286 | + | // instead of their natural arg registers. Spilled params are stored |
|
| 287 | + | // directly to their spill slots. |
|
| 288 | + | for i in 0..func.params.len { |
|
| 289 | + | if i < super::ARG_REGS.len { |
|
| 290 | + | let param = func.params[i].value; |
|
| 291 | + | let argReg = super::ARG_REGS[i]; |
|
| 292 | + | ||
| 293 | + | if let slot = regalloc::spill::spillSlot(&ralloc.spill, param) { |
|
| 294 | + | // Spilled parameter: store arg register to spill slot. |
|
| 295 | + | emit::emitSd(s.e, argReg, super::FP, spillOffset(&s, slot)); |
|
| 296 | + | } else if let assigned = ralloc.assignments[param.n] { |
|
| 297 | + | emitMv(&mut s, super::Reg { n: assigned }, argReg); |
|
| 298 | + | } |
|
| 299 | + | } |
|
| 300 | + | } |
|
| 301 | + | ||
| 302 | + | // Emit each block. |
|
| 303 | + | for i in 0..func.blocks.len { |
|
| 304 | + | selectBlock(&mut s, i, &func.blocks[i], &frame, func); |
|
| 305 | + | } |
|
| 306 | + | // Emit epilogue. |
|
| 307 | + | emit::emitEpilogue(s.e, &frame); |
|
| 308 | + | // Patch local branches now that all blocks are emitted. |
|
| 309 | + | emit::patchLocalBranches(s.e); |
|
| 310 | + | } |
|
| 311 | + | ||
| 312 | + | /// Select instructions for a block. |
|
| 313 | + | fn selectBlock(s: *mut Selector, blockIdx: u32, block: *il::Block, frame: *emit::Frame, func: *il::Fn) { |
|
| 314 | + | // Record block address for branch patching. |
|
| 315 | + | emit::recordBlock(s.e, blockIdx); |
|
| 316 | + | ||
| 317 | + | // Block parameters are handled at jump sites (in `Jmp`/`Br`). |
|
| 318 | + | // By the time we enter the block, the arguments have already been |
|
| 319 | + | // moved to the parameter registers by the predecessor's terminator. |
|
| 320 | + | ||
| 321 | + | // Process each instruction, auto-committing any pending spill after each. |
|
| 322 | + | let hasLocs = block.locs.len > 0; |
|
| 323 | + | for i in 0..block.instrs.len { |
|
| 324 | + | // Record debug location before emitting machine instructions. |
|
| 325 | + | if hasLocs { |
|
| 326 | + | emit::recordSrcLoc(s.e, block.locs[i]); |
|
| 327 | + | } |
|
| 328 | + | s.pendingSpill = nil; |
|
| 329 | + | selectInstr(s, block.instrs.list[i], frame, func); |
|
| 330 | + | ||
| 331 | + | // Flush the pending spill store, if any. |
|
| 332 | + | if let p = s.pendingSpill { |
|
| 333 | + | if let slot = regalloc::spill::spillSlot(&s.ralloc.spill, p.ssa) { |
|
| 334 | + | emit::emitSd(s.e, p.rd, super::FP, spillOffset(s, slot)); |
|
| 335 | + | } |
|
| 336 | + | s.pendingSpill = nil; |
|
| 337 | + | } |
|
| 338 | + | } |
|
| 339 | + | } |
|
| 340 | + | ||
| 341 | + | /// Select instructions for a single IL instruction. |
|
| 342 | + | fn selectInstr(s: *mut Selector, instr: il::Instr, frame: *emit::Frame, func: *il::Fn) { |
|
| 343 | + | match instr { |
|
| 344 | + | case il::Instr::BinOp { op, typ, dst, a, b } => { |
|
| 345 | + | let rd = getDstReg(s, dst, super::SCRATCH1); |
|
| 346 | + | let rs1 = resolveVal(s, super::SCRATCH1, a); |
|
| 347 | + | selectAluBinOp(s, op, typ, rd, rs1, b); |
|
| 348 | + | }, |
|
| 349 | + | case il::Instr::UnOp { op, typ, dst, a } => { |
|
| 350 | + | let rd = getDstReg(s, dst, super::SCRATCH1); |
|
| 351 | + | let rs = resolveVal(s, super::SCRATCH1, a); |
|
| 352 | + | selectAluUnOp(s, op, typ, rd, rs); |
|
| 353 | + | }, |
|
| 354 | + | case il::Instr::Load { typ, dst, src, offset } => { |
|
| 355 | + | let rd = getDstReg(s, dst, super::SCRATCH1); |
|
| 356 | + | let base = getSrcReg(s, src, super::SCRATCH2); |
|
| 357 | + | emit::emitLoad(s.e, rd, base, offset, typ); |
|
| 358 | + | }, |
|
| 359 | + | case il::Instr::Sload { typ, dst, src, offset } => { |
|
| 360 | + | let rd = getDstReg(s, dst, super::SCRATCH1); |
|
| 361 | + | let base = getSrcReg(s, src, super::SCRATCH2); |
|
| 362 | + | emit::emitSload(s.e, rd, base, offset, typ); |
|
| 363 | + | }, |
|
| 364 | + | case il::Instr::Store { typ, src, dst, offset } => { |
|
| 365 | + | let base = getSrcReg(s, dst, super::SCRATCH2); |
|
| 366 | + | let rs = resolveVal(s, super::SCRATCH1, src); |
|
| 367 | + | emit::emitStore(s.e, rs, base, offset, typ); |
|
| 368 | + | }, |
|
| 369 | + | case il::Instr::Copy { dst, val } => { |
|
| 370 | + | let rd = getDstReg(s, dst, super::SCRATCH1); |
|
| 371 | + | let rs = resolveVal(s, super::SCRATCH1, val); |
|
| 372 | + | emitMv(s, rd, rs); |
|
| 373 | + | }, |
|
| 374 | + | case il::Instr::Reserve { dst, size, alignment } => { |
|
| 375 | + | match size { |
|
| 376 | + | case il::Val::Imm(sz) => { |
|
| 377 | + | // Constant-sized reserve: use pre-allocated frame slot. |
|
| 378 | + | let rd = getDstReg(s, dst, super::SCRATCH1); |
|
| 379 | + | let aligned: i32 = mem::alignUpI32(s.reserveOffset, alignment as i32); |
|
| 380 | + | let fpOffset: i32 = s.ralloc.spill.frameSize + aligned - s.frameSize; |
|
| 381 | + | ||
| 382 | + | emit::emitAddImm(s.e, rd, super::FP, fpOffset); |
|
| 383 | + | s.reserveOffset = aligned + sz; |
|
| 384 | + | }, |
|
| 385 | + | case il::Val::Reg(r) => { |
|
| 386 | + | // Dynamic-sized reserve: runtime SP adjustment. |
|
| 387 | + | let rd = getDstReg(s, dst, super::SCRATCH1); |
|
| 388 | + | let rs = getSrcReg(s, r, super::SCRATCH2); |
|
| 389 | + | ||
| 390 | + | emit::emit(s.e, encode::sub(super::SP, super::SP, rs)); |
|
| 391 | + | ||
| 392 | + | if alignment > 1 { |
|
| 393 | + | let mask = 0 - alignment as i32; |
|
| 394 | + | debug::assert(encode::isSmallImm(mask)); |
|
| 395 | + | ||
| 396 | + | emit::emit(s.e, encode::andi(super::SP, super::SP, mask)); |
|
| 397 | + | } |
|
| 398 | + | emit::emit(s.e, encode::mv(rd, super::SP)); |
|
| 399 | + | }, |
|
| 400 | + | else => |
|
| 401 | + | panic "selectInstr: invalid reserve operand", |
|
| 402 | + | } |
|
| 403 | + | }, |
|
| 404 | + | case il::Instr::Blit { dst, src, size } => { |
|
| 405 | + | let bothSpilled = regalloc::spill::isSpilled(&s.ralloc.spill, dst) |
|
| 406 | + | and regalloc::spill::isSpilled(&s.ralloc.spill, src); |
|
| 407 | + | ||
| 408 | + | // When both are spilled, offsets must fit 12-bit immediates |
|
| 409 | + | // since we can't advance base registers (they live in spill |
|
| 410 | + | // slots, not real registers we can mutate). |
|
| 411 | + | if bothSpilled and size as i32 > super::MAX_IMM { |
|
| 412 | + | panic "selectInstr: blit both-spilled with large size"; |
|
| 413 | + | } |
|
| 414 | + | ||
| 415 | + | // Resolve dst/src base registers. |
|
| 416 | + | let mut rdst = super::SCRATCH2; |
|
| 417 | + | let mut rsrc = super::SCRATCH1; |
|
| 418 | + | let mut srcReload: ?i32 = nil; |
|
| 419 | + | ||
| 420 | + | if bothSpilled { |
|
| 421 | + | let dstSlot = regalloc::spill::spillSlot(&s.ralloc.spill, dst) else { |
|
| 422 | + | panic "selectInstr: blit dst not spilled"; |
|
| 423 | + | }; |
|
| 424 | + | let srcSlot = regalloc::spill::spillSlot(&s.ralloc.spill, src) else { |
|
| 425 | + | panic "selectInstr: blit src not spilled"; |
|
| 426 | + | }; |
|
| 427 | + | emit::emitLd(s.e, super::SCRATCH2, super::FP, spillOffset(s, dstSlot)); |
|
| 428 | + | srcReload = spillOffset(s, srcSlot); |
|
| 429 | + | } else { |
|
| 430 | + | rdst = getSrcReg(s, dst, super::SCRATCH2); |
|
| 431 | + | rsrc = getSrcReg(s, src, super::SCRATCH2); |
|
| 432 | + | } |
|
| 433 | + | let mut offset: i32 = 0; |
|
| 434 | + | let mut remaining = size as i32; |
|
| 435 | + | ||
| 436 | + | // Copy loop: 8 bytes, then 4 bytes, then 1 byte at a time. |
|
| 437 | + | // Before each load/store pair, check whether the offset is |
|
| 438 | + | // about to exceed the 12-bit signed immediate range. When |
|
| 439 | + | // it does, advance the base registers by the accumulated |
|
| 440 | + | // offset and reset to zero. |
|
| 441 | + | while remaining >= super::DWORD_SIZE { |
|
| 442 | + | if offset > super::MAX_IMM - super::DWORD_SIZE { |
|
| 443 | + | emit::emitAddImm(s.e, rsrc, rsrc, offset); |
|
| 444 | + | if rdst.n != rsrc.n { |
|
| 445 | + | emit::emitAddImm(s.e, rdst, rdst, offset); |
|
| 446 | + | } |
|
| 447 | + | offset = 0; |
|
| 448 | + | } |
|
| 449 | + | if let off = srcReload { |
|
| 450 | + | emit::emitLd(s.e, super::SCRATCH1, super::FP, off); |
|
| 451 | + | emit::emitLd(s.e, super::SCRATCH1, super::SCRATCH1, offset); |
|
| 452 | + | } else { |
|
| 453 | + | emit::emitLd(s.e, super::SCRATCH1, rsrc, offset); |
|
| 454 | + | } |
|
| 455 | + | emit::emitSd(s.e, super::SCRATCH1, rdst, offset); |
|
| 456 | + | offset = offset + super::DWORD_SIZE; |
|
| 457 | + | remaining = remaining - super::DWORD_SIZE; |
|
| 458 | + | } |
|
| 459 | + | if remaining >= super::WORD_SIZE { |
|
| 460 | + | if offset > super::MAX_IMM - super::WORD_SIZE { |
|
| 461 | + | emit::emitAddImm(s.e, rsrc, rsrc, offset); |
|
| 462 | + | if rdst.n != rsrc.n { |
|
| 463 | + | emit::emitAddImm(s.e, rdst, rdst, offset); |
|
| 464 | + | } |
|
| 465 | + | offset = 0; |
|
| 466 | + | } |
|
| 467 | + | if let off = srcReload { |
|
| 468 | + | emit::emitLd(s.e, super::SCRATCH1, super::FP, off); |
|
| 469 | + | emit::emitLw(s.e, super::SCRATCH1, super::SCRATCH1, offset); |
|
| 470 | + | } else { |
|
| 471 | + | emit::emitLw(s.e, super::SCRATCH1, rsrc, offset); |
|
| 472 | + | } |
|
| 473 | + | emit::emitSw(s.e, super::SCRATCH1, rdst, offset); |
|
| 474 | + | offset = offset + super::WORD_SIZE; |
|
| 475 | + | remaining = remaining - super::WORD_SIZE; |
|
| 476 | + | } |
|
| 477 | + | while remaining > 0 { |
|
| 478 | + | if offset > super::MAX_IMM - 1 { |
|
| 479 | + | emit::emitAddImm(s.e, rsrc, rsrc, offset); |
|
| 480 | + | if rdst.n != rsrc.n { |
|
| 481 | + | emit::emitAddImm(s.e, rdst, rdst, offset); |
|
| 482 | + | } |
|
| 483 | + | offset = 0; |
|
| 484 | + | } |
|
| 485 | + | if let off = srcReload { |
|
| 486 | + | emit::emitLd(s.e, super::SCRATCH1, super::FP, off); |
|
| 487 | + | emit::emitLb(s.e, super::SCRATCH1, super::SCRATCH1, offset); |
|
| 488 | + | } else { |
|
| 489 | + | emit::emitLb(s.e, super::SCRATCH1, rsrc, offset); |
|
| 490 | + | } |
|
| 491 | + | emit::emitSb(s.e, super::SCRATCH1, rdst, offset); |
|
| 492 | + | offset = offset + 1; |
|
| 493 | + | remaining = remaining - 1; |
|
| 494 | + | } |
|
| 495 | + | // Restore base registers if they were advanced (never happens |
|
| 496 | + | // in the both-spilled case since size <= MAX_IMM). |
|
| 497 | + | if not bothSpilled { |
|
| 498 | + | let advanced = size as i32 - offset; |
|
| 499 | + | if advanced != 0 { |
|
| 500 | + | emit::emitAddImm(s.e, rsrc, rsrc, 0 - advanced); |
|
| 501 | + | if rdst.n != rsrc.n { |
|
| 502 | + | emit::emitAddImm(s.e, rdst, rdst, 0 - advanced); |
|
| 503 | + | } |
|
| 504 | + | } |
|
| 505 | + | } |
|
| 506 | + | }, |
|
| 507 | + | case il::Instr::Zext { typ, dst, val } => { |
|
| 508 | + | let rd = getDstReg(s, dst, super::SCRATCH1); |
|
| 509 | + | let rs = resolveVal(s, super::SCRATCH1, val); |
|
| 510 | + | emitZext(s.e, rd, rs, typ); |
|
| 511 | + | }, |
|
| 512 | + | case il::Instr::Sext { typ, dst, val } => { |
|
| 513 | + | let rd = getDstReg(s, dst, super::SCRATCH1); |
|
| 514 | + | let rs = resolveVal(s, super::SCRATCH1, val); |
|
| 515 | + | emitSext(s.e, rd, rs, typ); |
|
| 516 | + | }, |
|
| 517 | + | case il::Instr::Ret { val } => { |
|
| 518 | + | if let v = val { |
|
| 519 | + | let rs = resolveVal(s, super::SCRATCH1, v); |
|
| 520 | + | emitMv(s, super::A0, rs); |
|
| 521 | + | } |
|
| 522 | + | emit::emitReturn(s.e, frame); |
|
| 523 | + | }, |
|
| 524 | + | case il::Instr::Jmp { target, args } => { |
|
| 525 | + | // Move arguments to target block's parameter registers. |
|
| 526 | + | emitBlockArgs(s, func, target, args); |
|
| 527 | + | emit::recordBranch(s.e, target, emit::BranchKind::Jump); |
|
| 528 | + | }, |
|
| 529 | + | case il::Instr::Br { op, typ, a, b, thenTarget, thenArgs, elseTarget, elseArgs } => { |
|
| 530 | + | let rs1 = resolveVal(s, super::SCRATCH1, a); |
|
| 531 | + | let rs2 = resolveVal(s, super::SCRATCH2, b); |
|
| 532 | + | ||
| 533 | + | // Normalize sub-word operands so that both registers have the same |
|
| 534 | + | // canonical representation. Without this, eg. `-1 : i8 ` loaded as |
|
| 535 | + | // `0xFFFFFFFFFFFFFFFF` and `255 : i8` loaded as `0xFF` would compare |
|
| 536 | + | // unequal even though they are the same 8-bit pattern. |
|
| 537 | + | if let case il::CmpOp::Slt = op { |
|
| 538 | + | emitSext(s.e, rs1, rs1, typ); |
|
| 539 | + | emitSext(s.e, rs2, rs2, typ); |
|
| 540 | + | } else { |
|
| 541 | + | emitZext(s.e, rs1, rs1, typ); |
|
| 542 | + | emitZext(s.e, rs2, rs2, typ); |
|
| 543 | + | } |
|
| 544 | + | // Block-argument moves must only execute on the taken path. |
|
| 545 | + | // When `thenArgs` is non-empty, invert the branch so that the |
|
| 546 | + | // then-moves land on the fall-through (taken) side. |
|
| 547 | + | if thenArgs.len > 0 and elseArgs.len > 0 { |
|
| 548 | + | panic "selectInstr: both `then` and `else` have block arguments"; |
|
| 549 | + | } else if thenArgs.len > 0 { |
|
| 550 | + | emit::recordBranch(s.e, elseTarget, emit::BranchKind::InvertedCond { op, rs1, rs2 }); |
|
| 551 | + | emitBlockArgs(s, func, thenTarget, thenArgs); |
|
| 552 | + | emit::recordBranch(s.e, thenTarget, emit::BranchKind::Jump); |
|
| 553 | + | } else { |
|
| 554 | + | emit::recordBranch(s.e, thenTarget, emit::BranchKind::Cond { op, rs1, rs2 }); |
|
| 555 | + | emitBlockArgs(s, func, elseTarget, elseArgs); |
|
| 556 | + | emit::recordBranch(s.e, elseTarget, emit::BranchKind::Jump); |
|
| 557 | + | } |
|
| 558 | + | }, |
|
| 559 | + | case il::Instr::Switch { val, defaultTarget, defaultArgs, cases } => { |
|
| 560 | + | let rs1 = resolveVal(s, super::SCRATCH1, val); |
|
| 561 | + | // When a case has block args, invert the branch to skip past |
|
| 562 | + | // the arg moves. |
|
| 563 | + | for c in cases { |
|
| 564 | + | emit::loadImm(s.e, super::SCRATCH2, c.value); |
|
| 565 | + | ||
| 566 | + | if c.args.len > 0 { |
|
| 567 | + | let skip = s.nextSynthBlock; |
|
| 568 | + | s.nextSynthBlock = skip + 1; |
|
| 569 | + | ||
| 570 | + | emit::recordBranch(s.e, skip, emit::BranchKind::InvertedCond { |
|
| 571 | + | op: il::CmpOp::Eq, rs1, rs2: super::SCRATCH2, |
|
| 572 | + | }); |
|
| 573 | + | emitBlockArgs(s, func, c.target, c.args); |
|
| 574 | + | emit::recordBranch(s.e, c.target, emit::BranchKind::Jump); |
|
| 575 | + | emit::recordBlock(s.e, skip); |
|
| 576 | + | } else { |
|
| 577 | + | emit::recordBranch(s.e, c.target, emit::BranchKind::Cond { |
|
| 578 | + | op: il::CmpOp::Eq, rs1, rs2: super::SCRATCH2, |
|
| 579 | + | }); |
|
| 580 | + | } |
|
| 581 | + | } |
|
| 582 | + | // Fall through to default. |
|
| 583 | + | emitBlockArgs(s, func, defaultTarget, defaultArgs); |
|
| 584 | + | emit::recordBranch(s.e, defaultTarget, emit::BranchKind::Jump); |
|
| 585 | + | }, |
|
| 586 | + | case il::Instr::Unreachable => { |
|
| 587 | + | emit::emit(s.e, encode::ebreak()); |
|
| 588 | + | }, |
|
| 589 | + | case il::Instr::Call { retTy, dst, func, args } => { |
|
| 590 | + | // For indirect calls, save target to scratch register before arg |
|
| 591 | + | // setup can clobber it. |
|
| 592 | + | if let case il::Val::Reg(r) = func { |
|
| 593 | + | let target = getSrcReg(s, r, super::SCRATCH2); |
|
| 594 | + | emitMv(s, super::SCRATCH2, target); |
|
| 595 | + | } |
|
| 596 | + | // Move arguments to A0-A7 using parallel move resolution. |
|
| 597 | + | if args.len > super::ARG_REGS.len { |
|
| 598 | + | panic "selectInstr: too many call arguments"; |
|
| 599 | + | } |
|
| 600 | + | emitParallelMoves(s, &super::ARG_REGS[..], args); |
|
| 601 | + | ||
| 602 | + | // Emit call. |
|
| 603 | + | match func { |
|
| 604 | + | case il::Val::FnAddr(name) => { |
|
| 605 | + | emit::recordCall(s.e, name); |
|
| 606 | + | }, |
|
| 607 | + | case il::Val::Reg(_) => { |
|
| 608 | + | emit::emit(s.e, encode::jalr(super::RA, super::SCRATCH2, 0)); |
|
| 609 | + | }, |
|
| 610 | + | else => { |
|
| 611 | + | panic "selectInstr: invalid call target"; |
|
| 612 | + | } |
|
| 613 | + | } |
|
| 614 | + | // Move result from A0. |
|
| 615 | + | if let d = dst { |
|
| 616 | + | let rd = getDstReg(s, d, super::SCRATCH1); |
|
| 617 | + | emitMv(s, rd, super::A0); |
|
| 618 | + | } |
|
| 619 | + | }, |
|
| 620 | + | case il::Instr::Ecall { dst, num, a0, a1, a2, a3 } => { |
|
| 621 | + | // Move arguments using parallel move. |
|
| 622 | + | // TODO: Can't use slice literals here because the lowerer doesn't |
|
| 623 | + | // support const-evaluating struct/union values in them. |
|
| 624 | + | let ecallDsts: [super::Reg; 5] = [super::A7, super::A0, super::A1, super::A2, super::A3]; |
|
| 625 | + | let ecallArgs: [il::Val; 5] = [num, a0, a1, a2, a3]; |
|
| 626 | + | ||
| 627 | + | emitParallelMoves(s, &ecallDsts[..], &ecallArgs[..]); |
|
| 628 | + | emit::emit(s.e, encode::ecall()); |
|
| 629 | + | ||
| 630 | + | // Result in A0. |
|
| 631 | + | let ecallRd = getDstReg(s, dst, super::SCRATCH1); |
|
| 632 | + | emitMv(s, ecallRd, super::A0); |
|
| 633 | + | }, |
|
| 634 | + | case il::Instr::Ebreak => { |
|
| 635 | + | emit::emit(s.e, encode::ebreak()); |
|
| 636 | + | }, |
|
| 637 | + | } |
|
| 638 | + | } |
|
| 639 | + | ||
| 640 | + | /// Emit runtime trap for division/modulo by zero. |
|
| 641 | + | fn emitTrapIfZero(s: *mut Selector, rs: super::Reg) { |
|
| 642 | + | emit::emit(s.e, encode::bne(rs, super::ZERO, super::INSTR_SIZE * 2)); |
|
| 643 | + | emit::emit(s.e, encode::ebreak()); |
|
| 644 | + | } |
|
| 645 | + | ||
| 646 | + | /// Select a binary ALU operation, dispatching to the appropriate |
|
| 647 | + | /// instruction pattern based on the operation kind and type. |
|
| 648 | + | fn selectAluBinOp(s: *mut Selector, op: il::BinOp, typ: il::Type, rd: super::Reg, rs1: super::Reg, b: il::Val) { |
|
| 649 | + | match op { |
|
| 650 | + | case il::BinOp::Add => { |
|
| 651 | + | if typ == il::Type::W32 { |
|
| 652 | + | selectBinOpW(s, rd, rs1, b, super::SCRATCH2); |
|
| 653 | + | } else { |
|
| 654 | + | selectBinOp(s, rd, rs1, b, BinOp::Add, super::SCRATCH2); |
|
| 655 | + | } |
|
| 656 | + | } |
|
| 657 | + | case il::BinOp::Sub => { |
|
| 658 | + | let rs2 = resolveVal(s, super::SCRATCH2, b); |
|
| 659 | + | if typ == il::Type::W32 { |
|
| 660 | + | emit::emit(s.e, encode::subw(rd, rs1, rs2)); |
|
| 661 | + | } else { |
|
| 662 | + | emit::emit(s.e, encode::sub(rd, rs1, rs2)); |
|
| 663 | + | } |
|
| 664 | + | } |
|
| 665 | + | case il::BinOp::Mul => { |
|
| 666 | + | let rs2 = resolveVal(s, super::SCRATCH2, b); |
|
| 667 | + | if typ == il::Type::W32 { |
|
| 668 | + | emit::emit(s.e, encode::mulw(rd, rs1, rs2)); |
|
| 669 | + | } else { |
|
| 670 | + | emit::emit(s.e, encode::mul(rd, rs1, rs2)); |
|
| 671 | + | } |
|
| 672 | + | } |
|
| 673 | + | case il::BinOp::Sdiv => { |
|
| 674 | + | let rs2 = resolveVal(s, super::SCRATCH2, b); |
|
| 675 | + | emitTrapIfZero(s, rs2); |
|
| 676 | + | if typ == il::Type::W32 { |
|
| 677 | + | emit::emit(s.e, encode::divw(rd, rs1, rs2)); |
|
| 678 | + | } else { |
|
| 679 | + | emit::emit(s.e, encode::div(rd, rs1, rs2)); |
|
| 680 | + | } |
|
| 681 | + | } |
|
| 682 | + | case il::BinOp::Udiv => { |
|
| 683 | + | let rs2 = resolveVal(s, super::SCRATCH2, b); |
|
| 684 | + | emitTrapIfZero(s, rs2); |
|
| 685 | + | if typ == il::Type::W32 { |
|
| 686 | + | emit::emit(s.e, encode::divuw(rd, rs1, rs2)); |
|
| 687 | + | } else { |
|
| 688 | + | emit::emit(s.e, encode::divu(rd, rs1, rs2)); |
|
| 689 | + | } |
|
| 690 | + | } |
|
| 691 | + | case il::BinOp::Srem => { |
|
| 692 | + | let rs2 = resolveVal(s, super::SCRATCH2, b); |
|
| 693 | + | emitTrapIfZero(s, rs2); |
|
| 694 | + | if typ == il::Type::W32 { |
|
| 695 | + | emit::emit(s.e, encode::remw(rd, rs1, rs2)); |
|
| 696 | + | } else { |
|
| 697 | + | emit::emit(s.e, encode::rem(rd, rs1, rs2)); |
|
| 698 | + | } |
|
| 699 | + | } |
|
| 700 | + | case il::BinOp::Urem => { |
|
| 701 | + | let rs2 = resolveVal(s, super::SCRATCH2, b); |
|
| 702 | + | emitTrapIfZero(s, rs2); |
|
| 703 | + | if typ == il::Type::W32 { |
|
| 704 | + | emit::emit(s.e, encode::remuw(rd, rs1, rs2)); |
|
| 705 | + | } else { |
|
| 706 | + | emit::emit(s.e, encode::remu(rd, rs1, rs2)); |
|
| 707 | + | } |
|
| 708 | + | } |
|
| 709 | + | case il::BinOp::And => |
|
| 710 | + | selectBinOp(s, rd, rs1, b, BinOp::And, super::SCRATCH2), |
|
| 711 | + | case il::BinOp::Or => |
|
| 712 | + | selectBinOp(s, rd, rs1, b, BinOp::Or, super::SCRATCH2), |
|
| 713 | + | case il::BinOp::Xor => |
|
| 714 | + | selectBinOp(s, rd, rs1, b, BinOp::Xor, super::SCRATCH2), |
|
| 715 | + | case il::BinOp::Shl => |
|
| 716 | + | selectShift(s, rd, rs1, b, ShiftOp::Sll, typ, super::SCRATCH2), |
|
| 717 | + | case il::BinOp::Sshr => |
|
| 718 | + | selectShift(s, rd, rs1, b, ShiftOp::Sra, typ, super::SCRATCH2), |
|
| 719 | + | case il::BinOp::Ushr => |
|
| 720 | + | selectShift(s, rd, rs1, b, ShiftOp::Srl, typ, super::SCRATCH2), |
|
| 721 | + | case il::BinOp::Eq => { |
|
| 722 | + | let rs2 = resolveVal(s, super::SCRATCH2, b); |
|
| 723 | + | // Canonicalize both operands to the declared width |
|
| 724 | + | // so high-bit junk doesn't affect the result. |
|
| 725 | + | emitZext(s.e, rs1, rs1, typ); |
|
| 726 | + | emitZext(s.e, rs2, rs2, typ); |
|
| 727 | + | emit::emit(s.e, encode::xor(rd, rs1, rs2)); |
|
| 728 | + | emit::emit(s.e, encode::sltiu(rd, rd, 1)); |
|
| 729 | + | } |
|
| 730 | + | case il::BinOp::Ne => { |
|
| 731 | + | let rs2 = resolveVal(s, super::SCRATCH2, b); |
|
| 732 | + | // Canonicalize both operands to the declared width |
|
| 733 | + | // so high-bit junk doesn't affect the result. |
|
| 734 | + | emitZext(s.e, rs1, rs1, typ); |
|
| 735 | + | emitZext(s.e, rs2, rs2, typ); |
|
| 736 | + | emit::emit(s.e, encode::xor(rd, rs1, rs2)); |
|
| 737 | + | emit::emit(s.e, encode::sltu(rd, super::ZERO, rd)); |
|
| 738 | + | } |
|
| 739 | + | case il::BinOp::Slt => |
|
| 740 | + | selectCmp(s, rd, rs1, b, CmpOp::Slt, super::SCRATCH2), |
|
| 741 | + | case il::BinOp::Ult => |
|
| 742 | + | selectCmp(s, rd, rs1, b, CmpOp::Ult, super::SCRATCH2), |
|
| 743 | + | case il::BinOp::Sge => { |
|
| 744 | + | let rs2 = resolveVal(s, super::SCRATCH2, b); |
|
| 745 | + | emit::emit(s.e, encode::slt(rd, rs1, rs2)); |
|
| 746 | + | emit::emit(s.e, encode::xori(rd, rd, 1)); // flip. |
|
| 747 | + | } |
|
| 748 | + | case il::BinOp::Uge => { |
|
| 749 | + | let rs2 = resolveVal(s, super::SCRATCH2, b); |
|
| 750 | + | emit::emit(s.e, encode::sltu(rd, rs1, rs2)); |
|
| 751 | + | emit::emit(s.e, encode::xori(rd, rd, 1)); // flip. |
|
| 752 | + | } |
|
| 753 | + | } |
|
| 754 | + | } |
|
| 755 | + | ||
| 756 | + | /// Select a unary ALU operation. |
|
| 757 | + | fn selectAluUnOp(s: *mut Selector, op: il::UnOp, typ: il::Type, rd: super::Reg, rs: super::Reg) { |
|
| 758 | + | match op { |
|
| 759 | + | case il::UnOp::Neg => { |
|
| 760 | + | if typ == il::Type::W32 { |
|
| 761 | + | emit::emit(s.e, encode::subw(rd, super::ZERO, rs)); |
|
| 762 | + | } else { |
|
| 763 | + | emit::emit(s.e, encode::neg(rd, rs)); |
|
| 764 | + | } |
|
| 765 | + | } |
|
| 766 | + | case il::UnOp::Not => |
|
| 767 | + | emit::emit(s.e, encode::not_(rd, rs)), |
|
| 768 | + | } |
|
| 769 | + | } |
|
| 770 | + | ||
| 771 | + | /// Select 32-bit addition with immediate optimization. |
|
| 772 | + | /// Uses `addiw`/`addw` to operate on 32 bits and sign-extend the result. |
|
| 773 | + | fn selectBinOpW(s: *mut Selector, rd: super::Reg, rs1: super::Reg, b: il::Val, scratch: super::Reg) { |
|
| 774 | + | if let case il::Val::Imm(imm) = b { |
|
| 775 | + | if encode::isSmallImm(imm) { |
|
| 776 | + | emit::emit(s.e, encode::addiw(rd, rs1, imm)); |
|
| 777 | + | return; |
|
| 778 | + | } |
|
| 779 | + | } |
|
| 780 | + | let rs2 = resolveVal(s, scratch, b); |
|
| 781 | + | emit::emit(s.e, encode::addw(rd, rs1, rs2)); |
|
| 782 | + | } |
|
| 783 | + | ||
| 784 | + | /// Select binary operation with immediate optimization. |
|
| 785 | + | fn selectBinOp(s: *mut Selector, rd: super::Reg, rs1: super::Reg, b: il::Val, op: BinOp, scratch: super::Reg) { |
|
| 786 | + | // Try immediate optimization first. |
|
| 787 | + | if let case il::Val::Imm(imm) = b { |
|
| 788 | + | if encode::isSmallImm(imm) { |
|
| 789 | + | match op { |
|
| 790 | + | case BinOp::Add => emit::emit(s.e, encode::addi(rd, rs1, imm)), |
|
| 791 | + | case BinOp::And => emit::emit(s.e, encode::andi(rd, rs1, imm)), |
|
| 792 | + | case BinOp::Or => emit::emit(s.e, encode::ori(rd, rs1, imm)), |
|
| 793 | + | case BinOp::Xor => emit::emit(s.e, encode::xori(rd, rs1, imm)), |
|
| 794 | + | } |
|
| 795 | + | return; |
|
| 796 | + | } |
|
| 797 | + | } |
|
| 798 | + | // Fallback: load into register. |
|
| 799 | + | let rs2 = resolveVal(s, scratch, b); |
|
| 800 | + | match op { |
|
| 801 | + | case BinOp::Add => emit::emit(s.e, encode::add(rd, rs1, rs2)), |
|
| 802 | + | case BinOp::And => emit::emit(s.e, encode::and_(rd, rs1, rs2)), |
|
| 803 | + | case BinOp::Or => emit::emit(s.e, encode::or_(rd, rs1, rs2)), |
|
| 804 | + | case BinOp::Xor => emit::emit(s.e, encode::xor(rd, rs1, rs2)), |
|
| 805 | + | } |
|
| 806 | + | } |
|
| 807 | + | ||
| 808 | + | /// Select shift operation with immediate optimization. |
|
| 809 | + | /// For 32-bit operations, uses the `*w` variants that operate on the lower 32 bits |
|
| 810 | + | /// and sign-extend the result. |
|
| 811 | + | fn selectShift(s: *mut Selector, rd: super::Reg, rs1: super::Reg, b: il::Val, op: ShiftOp, typ: il::Type, scratch: super::Reg) { |
|
| 812 | + | let isW32: bool = typ == il::Type::W32; |
|
| 813 | + | ||
| 814 | + | // Try immediate optimization first. |
|
| 815 | + | if let case il::Val::Imm(shamt) = b { |
|
| 816 | + | // Keep immediate forms only for encodable shift amounts. |
|
| 817 | + | // Otherwise fall back to register shifts, which naturally mask the count. |
|
| 818 | + | if shamt >= 0 and ((isW32 and shamt < 32) or (not isW32 and shamt < 64)) { |
|
| 819 | + | if isW32 { |
|
| 820 | + | match op { |
|
| 821 | + | case ShiftOp::Sll => emit::emit(s.e, encode::slliw(rd, rs1, shamt)), |
|
| 822 | + | case ShiftOp::Srl => emit::emit(s.e, encode::srliw(rd, rs1, shamt)), |
|
| 823 | + | case ShiftOp::Sra => emit::emit(s.e, encode::sraiw(rd, rs1, shamt)), |
|
| 824 | + | } |
|
| 825 | + | } else { |
|
| 826 | + | match op { |
|
| 827 | + | case ShiftOp::Sll => emit::emit(s.e, encode::slli(rd, rs1, shamt)), |
|
| 828 | + | case ShiftOp::Srl => emit::emit(s.e, encode::srli(rd, rs1, shamt)), |
|
| 829 | + | case ShiftOp::Sra => emit::emit(s.e, encode::srai(rd, rs1, shamt)), |
|
| 830 | + | } |
|
| 831 | + | } |
|
| 832 | + | return; |
|
| 833 | + | } |
|
| 834 | + | } |
|
| 835 | + | // Fallback: load into register. |
|
| 836 | + | let rs2 = resolveVal(s, scratch, b); |
|
| 837 | + | if isW32 { |
|
| 838 | + | match op { |
|
| 839 | + | case ShiftOp::Sll => emit::emit(s.e, encode::sllw(rd, rs1, rs2)), |
|
| 840 | + | case ShiftOp::Srl => emit::emit(s.e, encode::srlw(rd, rs1, rs2)), |
|
| 841 | + | case ShiftOp::Sra => emit::emit(s.e, encode::sraw(rd, rs1, rs2)), |
|
| 842 | + | } |
|
| 843 | + | } else { |
|
| 844 | + | match op { |
|
| 845 | + | case ShiftOp::Sll => emit::emit(s.e, encode::sll(rd, rs1, rs2)), |
|
| 846 | + | case ShiftOp::Srl => emit::emit(s.e, encode::srl(rd, rs1, rs2)), |
|
| 847 | + | case ShiftOp::Sra => emit::emit(s.e, encode::sra(rd, rs1, rs2)), |
|
| 848 | + | } |
|
| 849 | + | } |
|
| 850 | + | } |
|
| 851 | + | ||
| 852 | + | /// Resolve parallel moves from IL values to physical destination registers. |
|
| 853 | + | /// |
|
| 854 | + | /// The parallel move problem arises when moving values between registers where |
|
| 855 | + | /// there may be dependencies (e.g. moving A0 to A1 and A1 to A0 simultaneously). |
|
| 856 | + | /// |
|
| 857 | + | /// This algorithm: |
|
| 858 | + | /// 1. Identifies "ready" moves. |
|
| 859 | + | /// 2. Executes ready moves. |
|
| 860 | + | /// 3. Breaks cycles using scratch register. |
|
| 861 | + | /// |
|
| 862 | + | /// Entries with `ZERO` destination are skipped, as they are handled by caller. |
|
| 863 | + | fn emitParallelMoves(s: *mut Selector, dsts: *[super::Reg], args: *[il::Val]) { |
|
| 864 | + | let n: u32 = args.len; |
|
| 865 | + | if n == 0 { |
|
| 866 | + | return; |
|
| 867 | + | } |
|
| 868 | + | if n > MAX_BLOCK_ARGS { |
|
| 869 | + | panic "emitParallelMoves: too many arguments"; |
|
| 870 | + | } |
|
| 871 | + | // Source registers for each arg. |
|
| 872 | + | let mut srcRegs: [super::Reg; MAX_BLOCK_ARGS] = [super::ZERO; MAX_BLOCK_ARGS]; |
|
| 873 | + | // If this is a register-to-register move. |
|
| 874 | + | let mut isRegMove: [bool; MAX_BLOCK_ARGS] = [false; MAX_BLOCK_ARGS]; |
|
| 875 | + | // If this move still needs to be executed. |
|
| 876 | + | let mut pending: [bool; MAX_BLOCK_ARGS] = [false; MAX_BLOCK_ARGS]; |
|
| 877 | + | // Number of pending moves. |
|
| 878 | + | let mut numPending: u32 = 0; |
|
| 879 | + | ||
| 880 | + | for i in 0..n { |
|
| 881 | + | let dst = dsts[i]; |
|
| 882 | + | if dst != super::ZERO { // Skip entries with no destination. |
|
| 883 | + | match args[i] { |
|
| 884 | + | case il::Val::Reg(r) => { |
|
| 885 | + | if let _ = regalloc::spill::spillSlot(&s.ralloc.spill, r) { |
|
| 886 | + | // Spilled value needs load, not a register move. |
|
| 887 | + | pending[i] = true; |
|
| 888 | + | numPending = numPending + 1; |
|
| 889 | + | } else { |
|
| 890 | + | let src = getReg(s, r); |
|
| 891 | + | if src != dst { |
|
| 892 | + | // Register-to-register move needed. |
|
| 893 | + | srcRegs[i] = src; |
|
| 894 | + | isRegMove[i] = true; |
|
| 895 | + | pending[i] = true; |
|
| 896 | + | numPending = numPending + 1; |
|
| 897 | + | } else { |
|
| 898 | + | // No move needed. |
|
| 899 | + | } |
|
| 900 | + | } |
|
| 901 | + | }, |
|
| 902 | + | case il::Val::Imm(_), il::Val::DataSym(_), il::Val::FnAddr(_) => { |
|
| 903 | + | pending[i] = true; |
|
| 904 | + | numPending = numPending + 1; |
|
| 905 | + | }, |
|
| 906 | + | case il::Val::Undef => { |
|
| 907 | + | // Undefined values don't need any move. |
|
| 908 | + | } |
|
| 909 | + | } |
|
| 910 | + | } else { |
|
| 911 | + | // Nothing to do. |
|
| 912 | + | } |
|
| 913 | + | } |
|
| 914 | + | ||
| 915 | + | // Execute parallel move algorithm. |
|
| 916 | + | while numPending > 0 { |
|
| 917 | + | let mut found = false; |
|
| 918 | + | ||
| 919 | + | // Find a ready move: one whose destination is not a source of any |
|
| 920 | + | // pending register move. |
|
| 921 | + | for i in 0..n { |
|
| 922 | + | if pending[i] { |
|
| 923 | + | let dst = dsts[i]; |
|
| 924 | + | let mut isReady = true; |
|
| 925 | + | ||
| 926 | + | // Check if `dst` is used as source by any other pending register move. |
|
| 927 | + | for j in 0..n { |
|
| 928 | + | if j != i and pending[j] and isRegMove[j] and srcRegs[j] == dst { |
|
| 929 | + | isReady = false; |
|
| 930 | + | break; |
|
| 931 | + | } |
|
| 932 | + | } |
|
| 933 | + | if isReady { |
|
| 934 | + | // Execute this move. |
|
| 935 | + | if isRegMove[i] { |
|
| 936 | + | emitMv(s, dst, srcRegs[i]); |
|
| 937 | + | } else { |
|
| 938 | + | // Load immediate, symbol, or spilled value. |
|
| 939 | + | loadVal(s, dst, args[i]); |
|
| 940 | + | } |
|
| 941 | + | found = true; |
|
| 942 | + | pending[i] = false; |
|
| 943 | + | numPending = numPending - 1; |
|
| 944 | + | ||
| 945 | + | break; |
|
| 946 | + | } |
|
| 947 | + | } |
|
| 948 | + | } |
|
| 949 | + | ||
| 950 | + | if not found { |
|
| 951 | + | // No ready move, we have a cycle among register moves. |
|
| 952 | + | // Break it by saving one source to scratch. |
|
| 953 | + | for i in 0..n { |
|
| 954 | + | if pending[i] and isRegMove[i] { |
|
| 955 | + | let src = srcRegs[i]; |
|
| 956 | + | // Save this source to scratch. |
|
| 957 | + | emitMv(s, super::SCRATCH1, src); |
|
| 958 | + | // Update all pending moves that use this source. |
|
| 959 | + | for j in 0..n { |
|
| 960 | + | if pending[j] and isRegMove[j] and srcRegs[j] == src { |
|
| 961 | + | srcRegs[j] = super::SCRATCH1; |
|
| 962 | + | } |
|
| 963 | + | } |
|
| 964 | + | break; |
|
| 965 | + | } |
|
| 966 | + | } |
|
| 967 | + | } |
|
| 968 | + | } |
|
| 969 | + | } |
|
| 970 | + | ||
| 971 | + | /// Emit moves from block arguments to target block's parameter registers. |
|
| 972 | + | /// |
|
| 973 | + | /// Handles spilled destinations directly, then delegates to [`emitParallelMoves`] |
|
| 974 | + | /// for the remaining register-to-register parallel move resolution. |
|
| 975 | + | fn emitBlockArgs(s: *mut Selector, func: *il::Fn, target: u32, args: *mut [il::Val]) { |
|
| 976 | + | if args.len == 0 { |
|
| 977 | + | return; |
|
| 978 | + | } |
|
| 979 | + | let block = &func.blocks[target]; |
|
| 980 | + | if args.len != block.params.len { |
|
| 981 | + | panic "emitBlockArgs: argument/parameter count mismatch"; |
|
| 982 | + | } |
|
| 983 | + | if args.len > MAX_BLOCK_ARGS { |
|
| 984 | + | panic "emitBlockArgs: too many block arguments"; |
|
| 985 | + | } |
|
| 986 | + | ||
| 987 | + | // Destination registers for each arg. |
|
| 988 | + | // Zero means the destination is spilled or skipped. |
|
| 989 | + | let mut dsts: [super::Reg; MAX_BLOCK_ARGS] = [super::ZERO; MAX_BLOCK_ARGS]; |
|
| 990 | + | ||
| 991 | + | for i in 0..args.len { |
|
| 992 | + | let param = block.params[i].value; |
|
| 993 | + | ||
| 994 | + | // Spilled destinations: store directly to spill slot. |
|
| 995 | + | // These don't participate in the parallel move algorithm. |
|
| 996 | + | if let slot = regalloc::spill::spillSlot(&s.ralloc.spill, param) { |
|
| 997 | + | let a = args[i]; |
|
| 998 | + | ||
| 999 | + | if let case il::Val::Undef = a { |
|
| 1000 | + | // Undefined values don't need any move. |
|
| 1001 | + | } else { |
|
| 1002 | + | let rs = resolveVal(s, super::SCRATCH1, a); |
|
| 1003 | + | emit::emitSd(s.e, rs, super::FP, spillOffset(s, slot)); |
|
| 1004 | + | } |
|
| 1005 | + | } else { |
|
| 1006 | + | dsts[i] = getReg(s, param); |
|
| 1007 | + | } |
|
| 1008 | + | } |
|
| 1009 | + | emitParallelMoves(s, &dsts[..], args); |
|
| 1010 | + | } |
|
| 1011 | + | ||
| 1012 | + | /// Select comparison with immediate optimization. |
|
| 1013 | + | fn selectCmp(s: *mut Selector, rd: super::Reg, rs1: super::Reg, b: il::Val, op: CmpOp, scratch: super::Reg) { |
|
| 1014 | + | // Try immediate optimization first. |
|
| 1015 | + | if let case il::Val::Imm(imm) = b { |
|
| 1016 | + | if encode::isSmallImm(imm) { |
|
| 1017 | + | match op { |
|
| 1018 | + | case CmpOp::Slt => emit::emit(s.e, encode::slti(rd, rs1, imm)), |
|
| 1019 | + | case CmpOp::Ult => emit::emit(s.e, encode::sltiu(rd, rs1, imm)), |
|
| 1020 | + | } |
|
| 1021 | + | return; |
|
| 1022 | + | } |
|
| 1023 | + | } |
|
| 1024 | + | // Fallback: load into register. |
|
| 1025 | + | let rs2 = resolveVal(s, scratch, b); |
|
| 1026 | + | match op { |
|
| 1027 | + | case CmpOp::Slt => emit::emit(s.e, encode::slt(rd, rs1, rs2)), |
|
| 1028 | + | case CmpOp::Ult => emit::emit(s.e, encode::sltu(rd, rs1, rs2)), |
|
| 1029 | + | } |
|
| 1030 | + | } |
lib/std/arch/rv64/printer.rad
added
+349 -0
| 1 | + | //! RV64 instruction printer. |
|
| 2 | + | //! |
|
| 3 | + | //! Prints 32-bit instructions in assembly text format. |
|
| 4 | + | ||
| 5 | + | use std::fmt; |
|
| 6 | + | use std::mem; |
|
| 7 | + | use std::lang::alloc; |
|
| 8 | + | use std::lang::sexpr; |
|
| 9 | + | ||
| 10 | + | use super::decode; |
|
| 11 | + | use super::emit; |
|
| 12 | + | ||
| 13 | + | ///////////////////// |
|
| 14 | + | // Register Names // |
|
| 15 | + | ///////////////////// |
|
| 16 | + | ||
| 17 | + | /// ABI register names. |
|
| 18 | + | const REG_NAMES: [*[u8]; 32] = [ |
|
| 19 | + | "zero", "ra", "sp", "gp", "tp", "t0", "t1", "t2", |
|
| 20 | + | "fp", "s1", "a0", "a1", "a2", "a3", "a4", "a5", |
|
| 21 | + | "a6", "a7", "s2", "s3", "s4", "s5", "s6", "s7", |
|
| 22 | + | "s8", "s9", "s10", "s11", "t3", "t4", "t5", "t6" |
|
| 23 | + | ]; |
|
| 24 | + | ||
| 25 | + | /// Get register name from number. |
|
| 26 | + | fn regName(n: u8) -> *[u8] { |
|
| 27 | + | if n >= 32 { |
|
| 28 | + | return "?"; |
|
| 29 | + | } |
|
| 30 | + | return REG_NAMES[n as u32]; |
|
| 31 | + | } |
|
| 32 | + | ||
| 33 | + | /// Get register name from Reg. |
|
| 34 | + | fn regNameR(r: super::Reg) -> *[u8] { |
|
| 35 | + | return regName(r.n); |
|
| 36 | + | } |
|
| 37 | + | ||
| 38 | + | /////////////////////// |
|
| 39 | + | // Output Helpers // |
|
| 40 | + | /////////////////////// |
|
| 41 | + | ||
| 42 | + | /// Write a string to output. |
|
| 43 | + | fn write(out: *mut sexpr::Output, s: *[u8]) { |
|
| 44 | + | sexpr::write(out, s); |
|
| 45 | + | } |
|
| 46 | + | ||
| 47 | + | /// Format `i32` into arena. |
|
| 48 | + | fn formatI32(a: *mut alloc::Arena, val: i32) -> *[u8] { |
|
| 49 | + | let mut digits: [u8; 12] = undefined; |
|
| 50 | + | let text = fmt::formatI32(val, &mut digits[..]); |
|
| 51 | + | let slice = try! alloc::allocSlice(a, 1, 1, text.len) as *mut [u8]; |
|
| 52 | + | try! mem::copy(slice, text); |
|
| 53 | + | ||
| 54 | + | return slice; |
|
| 55 | + | } |
|
| 56 | + | ||
| 57 | + | /// Format `u32` into arena. |
|
| 58 | + | fn formatU32(a: *mut alloc::Arena, val: u32) -> *[u8] { |
|
| 59 | + | let mut digits: [u8; 10] = undefined; |
|
| 60 | + | let text = fmt::formatU32(val, &mut digits[..]); |
|
| 61 | + | let slice = try! alloc::allocSlice(a, 1, 1, text.len) as *mut [u8]; |
|
| 62 | + | try! mem::copy(slice, text); |
|
| 63 | + | ||
| 64 | + | return slice; |
|
| 65 | + | } |
|
| 66 | + | ||
| 67 | + | /////////////////////////////// |
|
| 68 | + | // Instruction Printing // |
|
| 69 | + | /////////////////////////////// |
|
| 70 | + | ||
| 71 | + | /// Mnemonic column width for alignment. |
|
| 72 | + | const MNEMONIC_WIDTH: u32 = 8; |
|
| 73 | + | ||
| 74 | + | /// Print spaces for indentation. |
|
| 75 | + | fn writeIndent(out: *mut sexpr::Output) { |
|
| 76 | + | write(out, " "); |
|
| 77 | + | } |
|
| 78 | + | ||
| 79 | + | /// Write text wrapped in parentheses. |
|
| 80 | + | fn writeParens(out: *mut sexpr::Output, s: *[u8]) { |
|
| 81 | + | write(out, "("); |
|
| 82 | + | write(out, s); |
|
| 83 | + | write(out, ")"); |
|
| 84 | + | } |
|
| 85 | + | ||
| 86 | + | /// Write strings separated by ", ". |
|
| 87 | + | fn writeDelim(out: *mut sexpr::Output, parts: *[*[u8]]) { |
|
| 88 | + | for i in 0..parts.len { |
|
| 89 | + | if i > 0 { |
|
| 90 | + | write(out, ", "); |
|
| 91 | + | } |
|
| 92 | + | write(out, parts[i]); |
|
| 93 | + | } |
|
| 94 | + | } |
|
| 95 | + | ||
| 96 | + | /// Write mnemonic with padding for alignment. |
|
| 97 | + | fn writeMnem(out: *mut sexpr::Output, m: *[u8]) { |
|
| 98 | + | write(out, m); |
|
| 99 | + | let mut i = m.len; |
|
| 100 | + | while i < MNEMONIC_WIDTH { |
|
| 101 | + | write(out, " "); |
|
| 102 | + | i = i + 1; |
|
| 103 | + | } |
|
| 104 | + | } |
|
| 105 | + | ||
| 106 | + | //////////////////////////////// |
|
| 107 | + | // Instruction Format Helpers // |
|
| 108 | + | //////////////////////////////// |
|
| 109 | + | ||
| 110 | + | /// R-type: `op rd, rs1, rs2`. |
|
| 111 | + | fn fmtR(out: *mut sexpr::Output, m: *[u8], rd: super::Reg, rs1: super::Reg, rs2: super::Reg) { |
|
| 112 | + | writeMnem(out, m); |
|
| 113 | + | writeDelim(out, &[regNameR(rd), regNameR(rs1), regNameR(rs2)]); |
|
| 114 | + | } |
|
| 115 | + | ||
| 116 | + | /// I-type: `op rd, rs1, imm`. |
|
| 117 | + | fn fmtI(out: *mut sexpr::Output, a: *mut alloc::Arena, m: *[u8], rd: super::Reg, rs1: super::Reg, imm: i32) { |
|
| 118 | + | writeMnem(out, m); |
|
| 119 | + | writeDelim(out, &[regNameR(rd), regNameR(rs1), formatI32(a, imm)]); |
|
| 120 | + | } |
|
| 121 | + | ||
| 122 | + | /// 2-reg: `op rd, rs`. |
|
| 123 | + | fn fmt2R(out: *mut sexpr::Output, m: *[u8], rd: super::Reg, rs: super::Reg) { |
|
| 124 | + | writeMnem(out, m); |
|
| 125 | + | writeDelim(out, &[regNameR(rd), regNameR(rs)]); |
|
| 126 | + | } |
|
| 127 | + | ||
| 128 | + | /// reg + imm: `op rd, imm`. |
|
| 129 | + | fn fmtRI(out: *mut sexpr::Output, a: *mut alloc::Arena, m: *[u8], rd: super::Reg, imm: i32) { |
|
| 130 | + | writeMnem(out, m); |
|
| 131 | + | writeDelim(out, &[regNameR(rd), formatI32(a, imm)]); |
|
| 132 | + | } |
|
| 133 | + | ||
| 134 | + | /// imm only: `op imm`. |
|
| 135 | + | fn fmtImm(out: *mut sexpr::Output, a: *mut alloc::Arena, m: *[u8], imm: i32) { |
|
| 136 | + | writeMnem(out, m); |
|
| 137 | + | write(out, formatI32(a, imm)); |
|
| 138 | + | } |
|
| 139 | + | ||
| 140 | + | /// 1-reg: `op rs`. |
|
| 141 | + | fn fmt1R(out: *mut sexpr::Output, m: *[u8], rs: super::Reg) { |
|
| 142 | + | writeMnem(out, m); |
|
| 143 | + | write(out, regNameR(rs)); |
|
| 144 | + | } |
|
| 145 | + | ||
| 146 | + | /// Load: `op rd, imm(rs1)`. |
|
| 147 | + | fn fmtLoad(out: *mut sexpr::Output, a: *mut alloc::Arena, m: *[u8], rd: super::Reg, rs1: super::Reg, imm: i32) { |
|
| 148 | + | writeMnem(out, m); |
|
| 149 | + | writeDelim(out, &[regNameR(rd), formatI32(a, imm)]); |
|
| 150 | + | writeParens(out, regNameR(rs1)); |
|
| 151 | + | } |
|
| 152 | + | ||
| 153 | + | /// Store: `op rs2, imm(rs1)`. |
|
| 154 | + | fn fmtStore(out: *mut sexpr::Output, a: *mut alloc::Arena, m: *[u8], rs2: super::Reg, rs1: super::Reg, imm: i32) { |
|
| 155 | + | writeMnem(out, m); |
|
| 156 | + | writeDelim(out, &[regNameR(rs2), formatI32(a, imm)]); |
|
| 157 | + | writeParens(out, regNameR(rs1)); |
|
| 158 | + | } |
|
| 159 | + | ||
| 160 | + | /// Branch: `op rs1, rs2, imm`. |
|
| 161 | + | fn fmtB(out: *mut sexpr::Output, a: *mut alloc::Arena, m: *[u8], rs1: super::Reg, rs2: super::Reg, imm: i32) { |
|
| 162 | + | writeMnem(out, m); |
|
| 163 | + | writeDelim(out, &[regNameR(rs1), regNameR(rs2), formatI32(a, imm)]); |
|
| 164 | + | } |
|
| 165 | + | ||
| 166 | + | /// Branch zero: `op rs1, imm`. |
|
| 167 | + | fn fmtBz(out: *mut sexpr::Output, a: *mut alloc::Arena, m: *[u8], rs1: super::Reg, imm: i32) { |
|
| 168 | + | writeMnem(out, m); |
|
| 169 | + | writeDelim(out, &[regNameR(rs1), formatI32(a, imm)]); |
|
| 170 | + | } |
|
| 171 | + | ||
| 172 | + | /// Print a single instruction to output buffer. |
|
| 173 | + | pub fn printInstr(out: *mut sexpr::Output, a: *mut alloc::Arena, instr: u32) { |
|
| 174 | + | let decoded = decode::decode(instr); |
|
| 175 | + | ||
| 176 | + | match decoded { |
|
| 177 | + | case decode::Instr::Lui { rd, imm } => fmtRI(out, a, "lui", rd, imm), |
|
| 178 | + | case decode::Instr::Auipc { rd, imm } => fmtRI(out, a, "auipc", rd, imm), |
|
| 179 | + | case decode::Instr::Jal { rd, imm } => { |
|
| 180 | + | if rd.n == 0 { |
|
| 181 | + | fmtImm(out, a, "j", imm); |
|
| 182 | + | } else { |
|
| 183 | + | fmtRI(out, a, "jal", rd, imm); |
|
| 184 | + | } |
|
| 185 | + | }, |
|
| 186 | + | case decode::Instr::Jalr { rd, rs1, imm } => { |
|
| 187 | + | if rd.n == 0 and rs1.n == 1 and imm == 0 { |
|
| 188 | + | write(out, "ret"); |
|
| 189 | + | } else if rd.n == 0 and imm == 0 { |
|
| 190 | + | fmt1R(out, "jr", rs1); |
|
| 191 | + | } else { |
|
| 192 | + | fmtI(out, a, "jalr", rd, rs1, imm); |
|
| 193 | + | } |
|
| 194 | + | }, |
|
| 195 | + | case decode::Instr::Beq { rs1, rs2, imm } => { |
|
| 196 | + | if rs2.n == 0 { |
|
| 197 | + | fmtBz(out, a, "beqz", rs1, imm); |
|
| 198 | + | } else { |
|
| 199 | + | fmtB(out, a, "beq", rs1, rs2, imm); |
|
| 200 | + | } |
|
| 201 | + | }, |
|
| 202 | + | case decode::Instr::Bne { rs1, rs2, imm } => { |
|
| 203 | + | if rs2.n == 0 { |
|
| 204 | + | fmtBz(out, a, "bnez", rs1, imm); |
|
| 205 | + | } else { |
|
| 206 | + | fmtB(out, a, "bne", rs1, rs2, imm); |
|
| 207 | + | } |
|
| 208 | + | }, |
|
| 209 | + | case decode::Instr::Blt { rs1, rs2, imm } => fmtB(out, a, "blt", rs1, rs2, imm), |
|
| 210 | + | case decode::Instr::Bge { rs1, rs2, imm } => fmtB(out, a, "bge", rs1, rs2, imm), |
|
| 211 | + | case decode::Instr::Bltu { rs1, rs2, imm } => fmtB(out, a, "bltu", rs1, rs2, imm), |
|
| 212 | + | case decode::Instr::Bgeu { rs1, rs2, imm } => fmtB(out, a, "bgeu", rs1, rs2, imm), |
|
| 213 | + | case decode::Instr::Lb { rd, rs1, imm } => fmtLoad(out, a, "lb", rd, rs1, imm), |
|
| 214 | + | case decode::Instr::Lh { rd, rs1, imm } => fmtLoad(out, a, "lh", rd, rs1, imm), |
|
| 215 | + | case decode::Instr::Lw { rd, rs1, imm } => fmtLoad(out, a, "lw", rd, rs1, imm), |
|
| 216 | + | case decode::Instr::Ld { rd, rs1, imm } => fmtLoad(out, a, "ld", rd, rs1, imm), |
|
| 217 | + | case decode::Instr::Lbu { rd, rs1, imm } => fmtLoad(out, a, "lbu", rd, rs1, imm), |
|
| 218 | + | case decode::Instr::Lhu { rd, rs1, imm } => fmtLoad(out, a, "lhu", rd, rs1, imm), |
|
| 219 | + | case decode::Instr::Lwu { rd, rs1, imm } => fmtLoad(out, a, "lwu", rd, rs1, imm), |
|
| 220 | + | case decode::Instr::Sb { rs2, rs1, imm } => fmtStore(out, a, "sb", rs2, rs1, imm), |
|
| 221 | + | case decode::Instr::Sh { rs2, rs1, imm } => fmtStore(out, a, "sh", rs2, rs1, imm), |
|
| 222 | + | case decode::Instr::Sw { rs2, rs1, imm } => fmtStore(out, a, "sw", rs2, rs1, imm), |
|
| 223 | + | case decode::Instr::Sd { rs2, rs1, imm } => fmtStore(out, a, "sd", rs2, rs1, imm), |
|
| 224 | + | case decode::Instr::Addi { rd, rs1, imm } => { |
|
| 225 | + | if rd.n == 0 and rs1.n == 0 and imm == 0 { |
|
| 226 | + | write(out, "nop"); |
|
| 227 | + | } else if imm == 0 { |
|
| 228 | + | fmt2R(out, "mv", rd, rs1); |
|
| 229 | + | } else if rs1.n == 0 { |
|
| 230 | + | fmtRI(out, a, "li", rd, imm); |
|
| 231 | + | } else { |
|
| 232 | + | fmtI(out, a, "addi", rd, rs1, imm); |
|
| 233 | + | } |
|
| 234 | + | }, |
|
| 235 | + | case decode::Instr::Slti { rd, rs1, imm } => fmtI(out, a, "slti", rd, rs1, imm), |
|
| 236 | + | case decode::Instr::Sltiu { rd, rs1, imm } => { |
|
| 237 | + | if imm == 1 { |
|
| 238 | + | fmt2R(out, "seqz", rd, rs1); |
|
| 239 | + | } else { |
|
| 240 | + | fmtI(out, a, "sltiu", rd, rs1, imm); |
|
| 241 | + | } |
|
| 242 | + | }, |
|
| 243 | + | case decode::Instr::Xori { rd, rs1, imm } => { |
|
| 244 | + | if imm == -1 { |
|
| 245 | + | fmt2R(out, "not", rd, rs1); |
|
| 246 | + | } else { |
|
| 247 | + | fmtI(out, a, "xori", rd, rs1, imm); |
|
| 248 | + | } |
|
| 249 | + | }, |
|
| 250 | + | case decode::Instr::Ori { rd, rs1, imm } => fmtI(out, a, "ori", rd, rs1, imm), |
|
| 251 | + | case decode::Instr::Andi { rd, rs1, imm } => fmtI(out, a, "andi", rd, rs1, imm), |
|
| 252 | + | case decode::Instr::Slli { rd, rs1, shamt } => fmtI(out, a, "slli", rd, rs1, shamt), |
|
| 253 | + | case decode::Instr::Srli { rd, rs1, shamt } => fmtI(out, a, "srli", rd, rs1, shamt), |
|
| 254 | + | case decode::Instr::Srai { rd, rs1, shamt } => fmtI(out, a, "srai", rd, rs1, shamt), |
|
| 255 | + | case decode::Instr::Add { rd, rs1, rs2 } => fmtR(out, "add", rd, rs1, rs2), |
|
| 256 | + | case decode::Instr::Sub { rd, rs1, rs2 } => { |
|
| 257 | + | if rs1.n == 0 { |
|
| 258 | + | fmt2R(out, "neg", rd, rs2); |
|
| 259 | + | } else { |
|
| 260 | + | fmtR(out, "sub", rd, rs1, rs2); |
|
| 261 | + | } |
|
| 262 | + | }, |
|
| 263 | + | case decode::Instr::Sll { rd, rs1, rs2 } => fmtR(out, "sll", rd, rs1, rs2), |
|
| 264 | + | case decode::Instr::Slt { rd, rs1, rs2 } => fmtR(out, "slt", rd, rs1, rs2), |
|
| 265 | + | case decode::Instr::Sltu { rd, rs1, rs2 } => { |
|
| 266 | + | if rs1.n == 0 { |
|
| 267 | + | fmt2R(out, "snez", rd, rs2); |
|
| 268 | + | } else { |
|
| 269 | + | fmtR(out, "sltu", rd, rs1, rs2); |
|
| 270 | + | } |
|
| 271 | + | }, |
|
| 272 | + | case decode::Instr::Xor { rd, rs1, rs2 } => fmtR(out, "xor", rd, rs1, rs2), |
|
| 273 | + | case decode::Instr::Srl { rd, rs1, rs2 } => fmtR(out, "srl", rd, rs1, rs2), |
|
| 274 | + | case decode::Instr::Sra { rd, rs1, rs2 } => fmtR(out, "sra", rd, rs1, rs2), |
|
| 275 | + | case decode::Instr::Or { rd, rs1, rs2 } => fmtR(out, "or", rd, rs1, rs2), |
|
| 276 | + | case decode::Instr::And { rd, rs1, rs2 } => fmtR(out, "and", rd, rs1, rs2), |
|
| 277 | + | case decode::Instr::Mul { rd, rs1, rs2 } => fmtR(out, "mul", rd, rs1, rs2), |
|
| 278 | + | case decode::Instr::Mulh { rd, rs1, rs2 } => fmtR(out, "mulh", rd, rs1, rs2), |
|
| 279 | + | case decode::Instr::Mulhsu { rd, rs1, rs2 } => fmtR(out, "mulhsu", rd, rs1, rs2), |
|
| 280 | + | case decode::Instr::Mulhu { rd, rs1, rs2 } => fmtR(out, "mulhu", rd, rs1, rs2), |
|
| 281 | + | case decode::Instr::Div { rd, rs1, rs2 } => fmtR(out, "div", rd, rs1, rs2), |
|
| 282 | + | case decode::Instr::Divu { rd, rs1, rs2 } => fmtR(out, "divu", rd, rs1, rs2), |
|
| 283 | + | case decode::Instr::Rem { rd, rs1, rs2 } => fmtR(out, "rem", rd, rs1, rs2), |
|
| 284 | + | case decode::Instr::Remu { rd, rs1, rs2 } => fmtR(out, "remu", rd, rs1, rs2), |
|
| 285 | + | case decode::Instr::Addiw { rd, rs1, imm } => { |
|
| 286 | + | if imm == 0 { |
|
| 287 | + | fmt2R(out, "sext.w", rd, rs1); |
|
| 288 | + | } else { |
|
| 289 | + | fmtI(out, a, "addiw", rd, rs1, imm); |
|
| 290 | + | } |
|
| 291 | + | }, |
|
| 292 | + | case decode::Instr::Slliw { rd, rs1, shamt } => fmtI(out, a, "slliw", rd, rs1, shamt), |
|
| 293 | + | case decode::Instr::Srliw { rd, rs1, shamt } => fmtI(out, a, "srliw", rd, rs1, shamt), |
|
| 294 | + | case decode::Instr::Sraiw { rd, rs1, shamt } => fmtI(out, a, "sraiw", rd, rs1, shamt), |
|
| 295 | + | case decode::Instr::Addw { rd, rs1, rs2 } => fmtR(out, "addw", rd, rs1, rs2), |
|
| 296 | + | case decode::Instr::Subw { rd, rs1, rs2 } => fmtR(out, "subw", rd, rs1, rs2), |
|
| 297 | + | case decode::Instr::Sllw { rd, rs1, rs2 } => fmtR(out, "sllw", rd, rs1, rs2), |
|
| 298 | + | case decode::Instr::Srlw { rd, rs1, rs2 } => fmtR(out, "srlw", rd, rs1, rs2), |
|
| 299 | + | case decode::Instr::Sraw { rd, rs1, rs2 } => fmtR(out, "sraw", rd, rs1, rs2), |
|
| 300 | + | case decode::Instr::Mulw { rd, rs1, rs2 } => fmtR(out, "mulw", rd, rs1, rs2), |
|
| 301 | + | case decode::Instr::Divw { rd, rs1, rs2 } => fmtR(out, "divw", rd, rs1, rs2), |
|
| 302 | + | case decode::Instr::Divuw { rd, rs1, rs2 } => fmtR(out, "divuw", rd, rs1, rs2), |
|
| 303 | + | case decode::Instr::Remw { rd, rs1, rs2 } => fmtR(out, "remw", rd, rs1, rs2), |
|
| 304 | + | case decode::Instr::Remuw { rd, rs1, rs2 } => fmtR(out, "remuw", rd, rs1, rs2), |
|
| 305 | + | case decode::Instr::Ecall => write(out, "ecall"), |
|
| 306 | + | case decode::Instr::Ebreak => write(out, "ebreak"), |
|
| 307 | + | case decode::Instr::Unknown { bits } => { |
|
| 308 | + | write(out, "unknown"); |
|
| 309 | + | writeParens(out, formatU32(a, bits)); |
|
| 310 | + | }, |
|
| 311 | + | } |
|
| 312 | + | } |
|
| 313 | + | ||
| 314 | + | /// Print code with labels to the given output. |
|
| 315 | + | pub fn printCodeTo(out: *mut sexpr::Output, pkgName: *[u8], code: *[u32], funcs: *[emit::FuncAddr], arena: *mut alloc::Arena) { |
|
| 316 | + | // Package header. |
|
| 317 | + | write(out, "# package `"); |
|
| 318 | + | write(out, pkgName); |
|
| 319 | + | write(out, "`\n\n"); |
|
| 320 | + | ||
| 321 | + | for i in 0..code.len { |
|
| 322 | + | if let name = findFunc(funcs, i) { |
|
| 323 | + | write(out, "\n# "); |
|
| 324 | + | write(out, name); |
|
| 325 | + | write(out, "\n\n"); |
|
| 326 | + | } |
|
| 327 | + | printInstr(out, arena, code[i]); |
|
| 328 | + | write(out, "\n"); |
|
| 329 | + | } |
|
| 330 | + | } |
|
| 331 | + | ||
| 332 | + | /// Find function at given instruction index. |
|
| 333 | + | fn findFunc(funcs: *[emit::FuncAddr], index: u32) -> ?*[u8] { |
|
| 334 | + | for i in 0..funcs.len { |
|
| 335 | + | if funcs[i].index == index { |
|
| 336 | + | return funcs[i].name; |
|
| 337 | + | } |
|
| 338 | + | } |
|
| 339 | + | return nil; |
|
| 340 | + | } |
|
| 341 | + | ||
| 342 | + | /// Print code with labels to buffer, returns slice. |
|
| 343 | + | pub fn printCode(pkgName: *[u8], code: *[u32], funcs: *[emit::FuncAddr], arena: *mut alloc::Arena, buf: *mut [u8]) -> *[u8] { |
|
| 344 | + | let mut pos: u32 = 0; |
|
| 345 | + | let mut out = sexpr::Output::Buffer { buf, pos: &mut pos }; |
|
| 346 | + | printCodeTo(&mut out, pkgName, code, funcs, arena); |
|
| 347 | + | ||
| 348 | + | return buf[..pos]; |
|
| 349 | + | } |
lib/std/arch/rv64/tests.rad
added
+508 -0
| 1 | + | //! RV64I+M instruction encoding tests. |
|
| 2 | + | //! |
|
| 3 | + | //! These tests verify that instruction encodings match the RISC-V specification |
|
| 4 | + | //! by comparing against known-good values. |
|
| 5 | + | ||
| 6 | + | use std::testing; |
|
| 7 | + | use super::encode; |
|
| 8 | + | ||
| 9 | + | /// Helper to check encoding equals expected value. |
|
| 10 | + | fn expectEncoding(actual: u32, expected: u32) throws (testing::TestError) { |
|
| 11 | + | try testing::expect(actual == expected); |
|
| 12 | + | } |
|
| 13 | + | ||
| 14 | + | /////////////////////// |
|
| 15 | + | // R-type ALU tests // |
|
| 16 | + | /////////////////////// |
|
| 17 | + | ||
| 18 | + | @test fn testEncodeAdd() throws (testing::TestError) { |
|
| 19 | + | let enc = encode::add(super::reg(1), super::reg(2), super::reg(3)); |
|
| 20 | + | try expectEncoding(enc, 0x003100B3); |
|
| 21 | + | } |
|
| 22 | + | ||
| 23 | + | @test fn testEncodeSub() throws (testing::TestError) { |
|
| 24 | + | let enc = encode::sub(super::reg(5), super::reg(6), super::reg(7)); |
|
| 25 | + | try expectEncoding(enc, 0x407302B3); |
|
| 26 | + | } |
|
| 27 | + | ||
| 28 | + | @test fn testEncodeSll() throws (testing::TestError) { |
|
| 29 | + | let enc = encode::sll(super::reg(1), super::reg(2), super::reg(3)); |
|
| 30 | + | try expectEncoding(enc, 0x003110B3); |
|
| 31 | + | } |
|
| 32 | + | ||
| 33 | + | @test fn testEncodeSlt() throws (testing::TestError) { |
|
| 34 | + | let enc = encode::slt(super::reg(1), super::reg(2), super::reg(3)); |
|
| 35 | + | try expectEncoding(enc, 0x003120B3); |
|
| 36 | + | } |
|
| 37 | + | ||
| 38 | + | @test fn testEncodeSltu() throws (testing::TestError) { |
|
| 39 | + | let enc = encode::sltu(super::reg(1), super::reg(2), super::reg(3)); |
|
| 40 | + | try expectEncoding(enc, 0x003130B3); |
|
| 41 | + | } |
|
| 42 | + | ||
| 43 | + | @test fn testEncodeXor() throws (testing::TestError) { |
|
| 44 | + | let enc = encode::xor(super::reg(1), super::reg(2), super::reg(3)); |
|
| 45 | + | try expectEncoding(enc, 0x003140B3); |
|
| 46 | + | } |
|
| 47 | + | ||
| 48 | + | @test fn testEncodeSrl() throws (testing::TestError) { |
|
| 49 | + | let enc = encode::srl(super::reg(1), super::reg(2), super::reg(3)); |
|
| 50 | + | try expectEncoding(enc, 0x003150B3); |
|
| 51 | + | } |
|
| 52 | + | ||
| 53 | + | @test fn testEncodeSra() throws (testing::TestError) { |
|
| 54 | + | let enc = encode::sra(super::reg(1), super::reg(2), super::reg(3)); |
|
| 55 | + | try expectEncoding(enc, 0x403150B3); |
|
| 56 | + | } |
|
| 57 | + | ||
| 58 | + | @test fn testEncodeOr() throws (testing::TestError) { |
|
| 59 | + | let enc = encode::or_(super::reg(1), super::reg(2), super::reg(3)); |
|
| 60 | + | try expectEncoding(enc, 0x003160B3); |
|
| 61 | + | } |
|
| 62 | + | ||
| 63 | + | @test fn testEncodeAnd() throws (testing::TestError) { |
|
| 64 | + | let enc = encode::and_(super::reg(1), super::reg(2), super::reg(3)); |
|
| 65 | + | try expectEncoding(enc, 0x003170B3); |
|
| 66 | + | } |
|
| 67 | + | ||
| 68 | + | /////////////////////// |
|
| 69 | + | // I-type ALU tests // |
|
| 70 | + | /////////////////////// |
|
| 71 | + | ||
| 72 | + | @test fn testEncodeAddi() throws (testing::TestError) { |
|
| 73 | + | let enc = encode::addi(super::reg(1), super::ZERO, 42); |
|
| 74 | + | try expectEncoding(enc, 0x02A00093); |
|
| 75 | + | } |
|
| 76 | + | ||
| 77 | + | @test fn testEncodeAddiNegative() throws (testing::TestError) { |
|
| 78 | + | let enc = encode::addi(super::reg(1), super::ZERO, -1); |
|
| 79 | + | try expectEncoding(enc, 0xFFF00093); |
|
| 80 | + | } |
|
| 81 | + | ||
| 82 | + | @test fn testEncodeSlti() throws (testing::TestError) { |
|
| 83 | + | let enc = encode::slti(super::reg(1), super::reg(2), 100); |
|
| 84 | + | try expectEncoding(enc, 0x06412093); |
|
| 85 | + | } |
|
| 86 | + | ||
| 87 | + | @test fn testEncodeSltiu() throws (testing::TestError) { |
|
| 88 | + | let enc = encode::sltiu(super::reg(1), super::reg(2), 100); |
|
| 89 | + | try expectEncoding(enc, 0x06413093); |
|
| 90 | + | } |
|
| 91 | + | ||
| 92 | + | @test fn testEncodeXori() throws (testing::TestError) { |
|
| 93 | + | let enc = encode::xori(super::reg(1), super::reg(2), 0xFF); |
|
| 94 | + | try expectEncoding(enc, 0x0FF14093); |
|
| 95 | + | } |
|
| 96 | + | ||
| 97 | + | @test fn testEncodeOri() throws (testing::TestError) { |
|
| 98 | + | let enc = encode::ori(super::reg(1), super::reg(2), 0xFF); |
|
| 99 | + | try expectEncoding(enc, 0x0FF16093); |
|
| 100 | + | } |
|
| 101 | + | ||
| 102 | + | @test fn testEncodeAndi() throws (testing::TestError) { |
|
| 103 | + | let enc = encode::andi(super::reg(1), super::reg(2), 0xFF); |
|
| 104 | + | try expectEncoding(enc, 0x0FF17093); |
|
| 105 | + | } |
|
| 106 | + | ||
| 107 | + | @test fn testEncodeSlli() throws (testing::TestError) { |
|
| 108 | + | let enc = encode::slli(super::reg(1), super::reg(2), 5); |
|
| 109 | + | try expectEncoding(enc, 0x00511093); |
|
| 110 | + | } |
|
| 111 | + | ||
| 112 | + | @test fn testEncodeSrli() throws (testing::TestError) { |
|
| 113 | + | let enc = encode::srli(super::reg(1), super::reg(2), 5); |
|
| 114 | + | try expectEncoding(enc, 0x00515093); |
|
| 115 | + | } |
|
| 116 | + | ||
| 117 | + | @test fn testEncodeSrai() throws (testing::TestError) { |
|
| 118 | + | let enc = encode::srai(super::reg(1), super::reg(2), 5); |
|
| 119 | + | try expectEncoding(enc, 0x40515093); |
|
| 120 | + | } |
|
| 121 | + | ||
| 122 | + | ////////////////// |
|
| 123 | + | // Load tests // |
|
| 124 | + | ////////////////// |
|
| 125 | + | ||
| 126 | + | @test fn testEncodeLb() throws (testing::TestError) { |
|
| 127 | + | let enc = encode::lb(super::reg(1), super::reg(2), 8); |
|
| 128 | + | try expectEncoding(enc, 0x00810083); |
|
| 129 | + | } |
|
| 130 | + | ||
| 131 | + | @test fn testEncodeLh() throws (testing::TestError) { |
|
| 132 | + | let enc = encode::lh(super::reg(1), super::reg(2), 8); |
|
| 133 | + | try expectEncoding(enc, 0x00811083); |
|
| 134 | + | } |
|
| 135 | + | ||
| 136 | + | @test fn testEncodeLw() throws (testing::TestError) { |
|
| 137 | + | let enc = encode::lw(super::reg(1), super::reg(2), 8); |
|
| 138 | + | try expectEncoding(enc, 0x00812083); |
|
| 139 | + | } |
|
| 140 | + | ||
| 141 | + | @test fn testEncodeLbu() throws (testing::TestError) { |
|
| 142 | + | let enc = encode::lbu(super::reg(1), super::reg(2), 8); |
|
| 143 | + | try expectEncoding(enc, 0x00814083); |
|
| 144 | + | } |
|
| 145 | + | ||
| 146 | + | @test fn testEncodeLhu() throws (testing::TestError) { |
|
| 147 | + | let enc = encode::lhu(super::reg(1), super::reg(2), 8); |
|
| 148 | + | try expectEncoding(enc, 0x00815083); |
|
| 149 | + | } |
|
| 150 | + | ||
| 151 | + | @test fn testEncodeLwu() throws (testing::TestError) { |
|
| 152 | + | let enc = encode::lwu(super::reg(1), super::reg(2), 8); |
|
| 153 | + | try expectEncoding(enc, 0x00816083); |
|
| 154 | + | } |
|
| 155 | + | ||
| 156 | + | @test fn testEncodeLd() throws (testing::TestError) { |
|
| 157 | + | let enc = encode::ld(super::reg(1), super::reg(2), 8); |
|
| 158 | + | try expectEncoding(enc, 0x00813083); |
|
| 159 | + | } |
|
| 160 | + | ||
| 161 | + | ////////////////// |
|
| 162 | + | // Store tests // |
|
| 163 | + | ////////////////// |
|
| 164 | + | ||
| 165 | + | @test fn testEncodeSb() throws (testing::TestError) { |
|
| 166 | + | let enc = encode::sb(super::reg(3), super::reg(2), 8); |
|
| 167 | + | try expectEncoding(enc, 0x00310423); |
|
| 168 | + | } |
|
| 169 | + | ||
| 170 | + | @test fn testEncodeSh() throws (testing::TestError) { |
|
| 171 | + | let enc = encode::sh(super::reg(3), super::reg(2), 8); |
|
| 172 | + | try expectEncoding(enc, 0x00311423); |
|
| 173 | + | } |
|
| 174 | + | ||
| 175 | + | @test fn testEncodeSw() throws (testing::TestError) { |
|
| 176 | + | let enc = encode::sw(super::reg(3), super::reg(2), 8); |
|
| 177 | + | try expectEncoding(enc, 0x00312423); |
|
| 178 | + | } |
|
| 179 | + | ||
| 180 | + | @test fn testEncodeSd() throws (testing::TestError) { |
|
| 181 | + | let enc = encode::sd(super::reg(3), super::reg(2), 8); |
|
| 182 | + | try expectEncoding(enc, 0x00313423); |
|
| 183 | + | } |
|
| 184 | + | ||
| 185 | + | @test fn testEncodeSwNegativeOffset() throws (testing::TestError) { |
|
| 186 | + | let enc = encode::sw(super::reg(3), super::reg(2), -4); |
|
| 187 | + | try expectEncoding(enc, 0xFE312E23); |
|
| 188 | + | } |
|
| 189 | + | ||
| 190 | + | ////////////////// |
|
| 191 | + | // Branch tests // |
|
| 192 | + | ////////////////// |
|
| 193 | + | ||
| 194 | + | @test fn testEncodeBeq() throws (testing::TestError) { |
|
| 195 | + | let enc = encode::beq(super::reg(1), super::reg(2), 8); |
|
| 196 | + | try expectEncoding(enc, 0x00208463); |
|
| 197 | + | } |
|
| 198 | + | ||
| 199 | + | @test fn testEncodeBne() throws (testing::TestError) { |
|
| 200 | + | let enc = encode::bne(super::reg(1), super::reg(2), 8); |
|
| 201 | + | try expectEncoding(enc, 0x00209463); |
|
| 202 | + | } |
|
| 203 | + | ||
| 204 | + | @test fn testEncodeBlt() throws (testing::TestError) { |
|
| 205 | + | let enc = encode::blt(super::reg(1), super::reg(2), 8); |
|
| 206 | + | try expectEncoding(enc, 0x0020C463); |
|
| 207 | + | } |
|
| 208 | + | ||
| 209 | + | @test fn testEncodeBge() throws (testing::TestError) { |
|
| 210 | + | let enc = encode::bge(super::reg(1), super::reg(2), 8); |
|
| 211 | + | try expectEncoding(enc, 0x0020D463); |
|
| 212 | + | } |
|
| 213 | + | ||
| 214 | + | @test fn testEncodeBltu() throws (testing::TestError) { |
|
| 215 | + | let enc = encode::bltu(super::reg(1), super::reg(2), 8); |
|
| 216 | + | try expectEncoding(enc, 0x0020E463); |
|
| 217 | + | } |
|
| 218 | + | ||
| 219 | + | @test fn testEncodeBgeu() throws (testing::TestError) { |
|
| 220 | + | let enc = encode::bgeu(super::reg(1), super::reg(2), 8); |
|
| 221 | + | try expectEncoding(enc, 0x0020F463); |
|
| 222 | + | } |
|
| 223 | + | ||
| 224 | + | @test fn testEncodeBranchNegative() throws (testing::TestError) { |
|
| 225 | + | let enc = encode::beq(super::reg(1), super::reg(2), -8); |
|
| 226 | + | try expectEncoding(enc, 0xFE208CE3); |
|
| 227 | + | } |
|
| 228 | + | ||
| 229 | + | ////////////////// |
|
| 230 | + | // Jump tests // |
|
| 231 | + | ////////////////// |
|
| 232 | + | ||
| 233 | + | @test fn testEncodeJal() throws (testing::TestError) { |
|
| 234 | + | let enc = encode::jal(super::reg(1), 8); |
|
| 235 | + | try expectEncoding(enc, 0x008000EF); |
|
| 236 | + | } |
|
| 237 | + | ||
| 238 | + | @test fn testEncodeJalr() throws (testing::TestError) { |
|
| 239 | + | let enc = encode::jalr(super::reg(1), super::reg(2), 8); |
|
| 240 | + | try expectEncoding(enc, 0x008100E7); |
|
| 241 | + | } |
|
| 242 | + | ||
| 243 | + | /////////////////////////// |
|
| 244 | + | // Upper immediate tests // |
|
| 245 | + | /////////////////////////// |
|
| 246 | + | ||
| 247 | + | @test fn testEncodeLui() throws (testing::TestError) { |
|
| 248 | + | let enc = encode::lui(super::reg(1), 0x12345); |
|
| 249 | + | try expectEncoding(enc, 0x123450B7); |
|
| 250 | + | } |
|
| 251 | + | ||
| 252 | + | @test fn testEncodeAuipc() throws (testing::TestError) { |
|
| 253 | + | let enc = encode::auipc(super::reg(1), 0x12345); |
|
| 254 | + | try expectEncoding(enc, 0x12345097); |
|
| 255 | + | } |
|
| 256 | + | ||
| 257 | + | ////////////////// |
|
| 258 | + | // System tests // |
|
| 259 | + | ////////////////// |
|
| 260 | + | ||
| 261 | + | @test fn testEncodeEcall() throws (testing::TestError) { |
|
| 262 | + | let enc = encode::ecall(); |
|
| 263 | + | try expectEncoding(enc, 0x00000073); |
|
| 264 | + | } |
|
| 265 | + | ||
| 266 | + | @test fn testEncodeEbreak() throws (testing::TestError) { |
|
| 267 | + | let enc = encode::ebreak(); |
|
| 268 | + | try expectEncoding(enc, 0x00100073); |
|
| 269 | + | } |
|
| 270 | + | ||
| 271 | + | ///////////////////// |
|
| 272 | + | // M extension tests |
|
| 273 | + | ///////////////////// |
|
| 274 | + | ||
| 275 | + | @test fn testEncodeMul() throws (testing::TestError) { |
|
| 276 | + | let enc = encode::mul(super::reg(1), super::reg(2), super::reg(3)); |
|
| 277 | + | try expectEncoding(enc, 0x023100B3); |
|
| 278 | + | } |
|
| 279 | + | ||
| 280 | + | @test fn testEncodeMulh() throws (testing::TestError) { |
|
| 281 | + | let enc = encode::mulh(super::reg(1), super::reg(2), super::reg(3)); |
|
| 282 | + | try expectEncoding(enc, 0x023110B3); |
|
| 283 | + | } |
|
| 284 | + | ||
| 285 | + | @test fn testEncodeMulhsu() throws (testing::TestError) { |
|
| 286 | + | let enc = encode::mulhsu(super::reg(1), super::reg(2), super::reg(3)); |
|
| 287 | + | try expectEncoding(enc, 0x023120B3); |
|
| 288 | + | } |
|
| 289 | + | ||
| 290 | + | @test fn testEncodeMulhu() throws (testing::TestError) { |
|
| 291 | + | let enc = encode::mulhu(super::reg(1), super::reg(2), super::reg(3)); |
|
| 292 | + | try expectEncoding(enc, 0x023130B3); |
|
| 293 | + | } |
|
| 294 | + | ||
| 295 | + | @test fn testEncodeDiv() throws (testing::TestError) { |
|
| 296 | + | let enc = encode::div(super::reg(1), super::reg(2), super::reg(3)); |
|
| 297 | + | try expectEncoding(enc, 0x023140B3); |
|
| 298 | + | } |
|
| 299 | + | ||
| 300 | + | @test fn testEncodeDivu() throws (testing::TestError) { |
|
| 301 | + | let enc = encode::divu(super::reg(1), super::reg(2), super::reg(3)); |
|
| 302 | + | try expectEncoding(enc, 0x023150B3); |
|
| 303 | + | } |
|
| 304 | + | ||
| 305 | + | @test fn testEncodeRem() throws (testing::TestError) { |
|
| 306 | + | let enc = encode::rem(super::reg(1), super::reg(2), super::reg(3)); |
|
| 307 | + | try expectEncoding(enc, 0x023160B3); |
|
| 308 | + | } |
|
| 309 | + | ||
| 310 | + | @test fn testEncodeRemu() throws (testing::TestError) { |
|
| 311 | + | let enc = encode::remu(super::reg(1), super::reg(2), super::reg(3)); |
|
| 312 | + | try expectEncoding(enc, 0x023170B3); |
|
| 313 | + | } |
|
| 314 | + | ||
| 315 | + | //////////////////////////// |
|
| 316 | + | // RV64 Word operation tests |
|
| 317 | + | //////////////////////////// |
|
| 318 | + | ||
| 319 | + | @test fn testEncodeAddiw() throws (testing::TestError) { |
|
| 320 | + | let enc = encode::addiw(super::reg(1), super::reg(2), 42); |
|
| 321 | + | try expectEncoding(enc, 0x02A1009B); |
|
| 322 | + | } |
|
| 323 | + | ||
| 324 | + | @test fn testEncodeSlliw() throws (testing::TestError) { |
|
| 325 | + | let enc = encode::slliw(super::reg(1), super::reg(2), 5); |
|
| 326 | + | try expectEncoding(enc, 0x0051109B); |
|
| 327 | + | } |
|
| 328 | + | ||
| 329 | + | @test fn testEncodeSrliw() throws (testing::TestError) { |
|
| 330 | + | let enc = encode::srliw(super::reg(1), super::reg(2), 5); |
|
| 331 | + | try expectEncoding(enc, 0x0051509B); |
|
| 332 | + | } |
|
| 333 | + | ||
| 334 | + | @test fn testEncodeSraiw() throws (testing::TestError) { |
|
| 335 | + | let enc = encode::sraiw(super::reg(1), super::reg(2), 5); |
|
| 336 | + | try expectEncoding(enc, 0x4051509B); |
|
| 337 | + | } |
|
| 338 | + | ||
| 339 | + | @test fn testEncodeAddw() throws (testing::TestError) { |
|
| 340 | + | let enc = encode::addw(super::reg(1), super::reg(2), super::reg(3)); |
|
| 341 | + | try expectEncoding(enc, 0x003100BB); |
|
| 342 | + | } |
|
| 343 | + | ||
| 344 | + | @test fn testEncodeSubw() throws (testing::TestError) { |
|
| 345 | + | let enc = encode::subw(super::reg(1), super::reg(2), super::reg(3)); |
|
| 346 | + | try expectEncoding(enc, 0x403100BB); |
|
| 347 | + | } |
|
| 348 | + | ||
| 349 | + | @test fn testEncodeSllw() throws (testing::TestError) { |
|
| 350 | + | let enc = encode::sllw(super::reg(1), super::reg(2), super::reg(3)); |
|
| 351 | + | try expectEncoding(enc, 0x003110BB); |
|
| 352 | + | } |
|
| 353 | + | ||
| 354 | + | @test fn testEncodeSrlw() throws (testing::TestError) { |
|
| 355 | + | let enc = encode::srlw(super::reg(1), super::reg(2), super::reg(3)); |
|
| 356 | + | try expectEncoding(enc, 0x003150BB); |
|
| 357 | + | } |
|
| 358 | + | ||
| 359 | + | @test fn testEncodeSraw() throws (testing::TestError) { |
|
| 360 | + | let enc = encode::sraw(super::reg(1), super::reg(2), super::reg(3)); |
|
| 361 | + | try expectEncoding(enc, 0x403150BB); |
|
| 362 | + | } |
|
| 363 | + | ||
| 364 | + | @test fn testEncodeMulw() throws (testing::TestError) { |
|
| 365 | + | let enc = encode::mulw(super::reg(1), super::reg(2), super::reg(3)); |
|
| 366 | + | try expectEncoding(enc, 0x023100BB); |
|
| 367 | + | } |
|
| 368 | + | ||
| 369 | + | @test fn testEncodeDivw() throws (testing::TestError) { |
|
| 370 | + | let enc = encode::divw(super::reg(1), super::reg(2), super::reg(3)); |
|
| 371 | + | try expectEncoding(enc, 0x023140BB); |
|
| 372 | + | } |
|
| 373 | + | ||
| 374 | + | @test fn testEncodeDivuw() throws (testing::TestError) { |
|
| 375 | + | let enc = encode::divuw(super::reg(1), super::reg(2), super::reg(3)); |
|
| 376 | + | try expectEncoding(enc, 0x023150BB); |
|
| 377 | + | } |
|
| 378 | + | ||
| 379 | + | @test fn testEncodeRemw() throws (testing::TestError) { |
|
| 380 | + | let enc = encode::remw(super::reg(1), super::reg(2), super::reg(3)); |
|
| 381 | + | try expectEncoding(enc, 0x023160BB); |
|
| 382 | + | } |
|
| 383 | + | ||
| 384 | + | @test fn testEncodeRemuw() throws (testing::TestError) { |
|
| 385 | + | let enc = encode::remuw(super::reg(1), super::reg(2), super::reg(3)); |
|
| 386 | + | try expectEncoding(enc, 0x023170BB); |
|
| 387 | + | } |
|
| 388 | + | ||
| 389 | + | ///////////////////////////////// |
|
| 390 | + | // RV64 6-bit shift amount tests |
|
| 391 | + | ///////////////////////////////// |
|
| 392 | + | ||
| 393 | + | @test fn testEncodeSlli64() throws (testing::TestError) { |
|
| 394 | + | let enc = encode::slli(super::reg(1), super::reg(2), 32); |
|
| 395 | + | try expectEncoding(enc, 0x02011093); |
|
| 396 | + | } |
|
| 397 | + | ||
| 398 | + | @test fn testEncodeSrli64() throws (testing::TestError) { |
|
| 399 | + | let enc = encode::srli(super::reg(1), super::reg(2), 32); |
|
| 400 | + | try expectEncoding(enc, 0x02015093); |
|
| 401 | + | } |
|
| 402 | + | ||
| 403 | + | @test fn testEncodeSrai64() throws (testing::TestError) { |
|
| 404 | + | let enc = encode::srai(super::reg(1), super::reg(2), 32); |
|
| 405 | + | try expectEncoding(enc, 0x42015093); |
|
| 406 | + | } |
|
| 407 | + | ||
| 408 | + | /////////////////////////// |
|
| 409 | + | // Pseudo-instruction tests |
|
| 410 | + | /////////////////////////// |
|
| 411 | + | ||
| 412 | + | @test fn testEncodeNop() throws (testing::TestError) { |
|
| 413 | + | let enc = encode::nop(); |
|
| 414 | + | try expectEncoding(enc, 0x00000013); |
|
| 415 | + | } |
|
| 416 | + | ||
| 417 | + | @test fn testEncodeMv() throws (testing::TestError) { |
|
| 418 | + | let enc = encode::mv(super::reg(1), super::reg(2)); |
|
| 419 | + | try expectEncoding(enc, 0x00010093); |
|
| 420 | + | } |
|
| 421 | + | ||
| 422 | + | @test fn testEncodeNot() throws (testing::TestError) { |
|
| 423 | + | let enc = encode::not_(super::reg(1), super::reg(2)); |
|
| 424 | + | try expectEncoding(enc, 0xFFF14093); |
|
| 425 | + | } |
|
| 426 | + | ||
| 427 | + | @test fn testEncodeNeg() throws (testing::TestError) { |
|
| 428 | + | let enc = encode::neg(super::reg(1), super::reg(2)); |
|
| 429 | + | try expectEncoding(enc, 0x402000B3); |
|
| 430 | + | } |
|
| 431 | + | ||
| 432 | + | @test fn testEncodeRet() throws (testing::TestError) { |
|
| 433 | + | let enc = encode::ret(); |
|
| 434 | + | try expectEncoding(enc, 0x00008067); |
|
| 435 | + | } |
|
| 436 | + | ||
| 437 | + | @test fn testEncodeJ() throws (testing::TestError) { |
|
| 438 | + | let enc = encode::j(8); |
|
| 439 | + | try expectEncoding(enc, 0x0080006F); |
|
| 440 | + | } |
|
| 441 | + | ||
| 442 | + | @test fn testEncodeBle() throws (testing::TestError) { |
|
| 443 | + | let enc = encode::ble(super::reg(1), super::reg(2), 8); |
|
| 444 | + | try expectEncoding(enc, 0x00115463); |
|
| 445 | + | } |
|
| 446 | + | ||
| 447 | + | @test fn testEncodeBgt() throws (testing::TestError) { |
|
| 448 | + | let enc = encode::bgt(super::reg(1), super::reg(2), 8); |
|
| 449 | + | try expectEncoding(enc, 0x00114463); |
|
| 450 | + | } |
|
| 451 | + | ||
| 452 | + | @test fn testEncodeSeqz() throws (testing::TestError) { |
|
| 453 | + | let enc = encode::seqz(super::reg(1), super::reg(2)); |
|
| 454 | + | try expectEncoding(enc, 0x00113093); |
|
| 455 | + | } |
|
| 456 | + | ||
| 457 | + | @test fn testEncodeSnez() throws (testing::TestError) { |
|
| 458 | + | let enc = encode::snez(super::reg(1), super::reg(2)); |
|
| 459 | + | try expectEncoding(enc, 0x002030B3); |
|
| 460 | + | } |
|
| 461 | + | ||
| 462 | + | @test fn testEncodeBeqz() throws (testing::TestError) { |
|
| 463 | + | let enc = encode::beqz(super::reg(1), 8); |
|
| 464 | + | try expectEncoding(enc, 0x00008463); |
|
| 465 | + | } |
|
| 466 | + | ||
| 467 | + | @test fn testEncodeBnez() throws (testing::TestError) { |
|
| 468 | + | let enc = encode::bnez(super::reg(1), 8); |
|
| 469 | + | try expectEncoding(enc, 0x00009463); |
|
| 470 | + | } |
|
| 471 | + | ||
| 472 | + | @test fn testEncodeCall() throws (testing::TestError) { |
|
| 473 | + | let enc = encode::call(8); |
|
| 474 | + | try expectEncoding(enc, 0x008000EF); |
|
| 475 | + | } |
|
| 476 | + | ||
| 477 | + | ///////////////////////////// |
|
| 478 | + | // Validation helper tests // |
|
| 479 | + | ///////////////////////////// |
|
| 480 | + | ||
| 481 | + | @test fn testIsSmallImm() throws (testing::TestError) { |
|
| 482 | + | try testing::expect(encode::isSmallImm(0)); |
|
| 483 | + | try testing::expect(encode::isSmallImm(2047)); |
|
| 484 | + | try testing::expect(encode::isSmallImm(-2048)); |
|
| 485 | + | try testing::expect(encode::isSmallImm(-1)); |
|
| 486 | + | try testing::expectNot(encode::isSmallImm(2048)); |
|
| 487 | + | try testing::expectNot(encode::isSmallImm(-2049)); |
|
| 488 | + | } |
|
| 489 | + | ||
| 490 | + | @test fn testIsBranchImm() throws (testing::TestError) { |
|
| 491 | + | try testing::expect(encode::isBranchImm(0)); |
|
| 492 | + | try testing::expect(encode::isBranchImm(8)); |
|
| 493 | + | try testing::expect(encode::isBranchImm(-8)); |
|
| 494 | + | try testing::expect(encode::isBranchImm(4094)); |
|
| 495 | + | try testing::expect(encode::isBranchImm(-4096)); |
|
| 496 | + | try testing::expectNot(encode::isBranchImm(1)); // Must be even |
|
| 497 | + | try testing::expectNot(encode::isBranchImm(4096)); // Out of range |
|
| 498 | + | } |
|
| 499 | + | ||
| 500 | + | @test fn testIsJumpImm() throws (testing::TestError) { |
|
| 501 | + | try testing::expect(encode::isJumpImm(0)); |
|
| 502 | + | try testing::expect(encode::isJumpImm(8)); |
|
| 503 | + | try testing::expect(encode::isJumpImm(-8)); |
|
| 504 | + | try testing::expect(encode::isJumpImm(1048574)); // Max positive even |
|
| 505 | + | try testing::expect(encode::isJumpImm(-1048576)); // Min negative |
|
| 506 | + | try testing::expectNot(encode::isJumpImm(1)); // Must be even |
|
| 507 | + | try testing::expectNot(encode::isJumpImm(1048576)); // Out of range |
|
| 508 | + | } |
lib/std/arch/rv64/tests/abi.sizes.rad
added
+226 -0
| 1 | + | //! Test size-based ABI: small aggregates (<= 8 bytes) are passed/returned |
|
| 2 | + | //! by value in registers, while larger aggregates use hidden pointers. |
|
| 3 | + | ||
| 4 | + | record Byte { |
|
| 5 | + | x: u8, |
|
| 6 | + | } |
|
| 7 | + | ||
| 8 | + | record Short { |
|
| 9 | + | x: u16, |
|
| 10 | + | } |
|
| 11 | + | ||
| 12 | + | record Word { |
|
| 13 | + | x: u32, |
|
| 14 | + | } |
|
| 15 | + | ||
| 16 | + | record Pair { |
|
| 17 | + | a: u32, |
|
| 18 | + | b: u32, |
|
| 19 | + | } |
|
| 20 | + | ||
| 21 | + | record Triple { |
|
| 22 | + | x: u32, |
|
| 23 | + | y: u32, |
|
| 24 | + | z: u32, |
|
| 25 | + | } |
|
| 26 | + | ||
| 27 | + | /// Return a 1-byte record by value. |
|
| 28 | + | fn makeByte(x: u8) -> Byte { |
|
| 29 | + | return Byte { x }; |
|
| 30 | + | } |
|
| 31 | + | ||
| 32 | + | /// Return a 2-byte record by value. |
|
| 33 | + | fn makeShort(x: u16) -> Short { |
|
| 34 | + | return Short { x }; |
|
| 35 | + | } |
|
| 36 | + | ||
| 37 | + | /// Return a 4-byte record by value. |
|
| 38 | + | fn makeWord(x: u32) -> Word { |
|
| 39 | + | return Word { x }; |
|
| 40 | + | } |
|
| 41 | + | ||
| 42 | + | /// Return an 8-byte record by value (boundary case). |
|
| 43 | + | fn makePair(a: u32, b: u32) -> Pair { |
|
| 44 | + | return Pair { a, b }; |
|
| 45 | + | } |
|
| 46 | + | ||
| 47 | + | /// Return a 12-byte record by reference (too large for register). |
|
| 48 | + | fn makeTriple(x: u32, y: u32, z: u32) -> Triple { |
|
| 49 | + | return Triple { x, y, z }; |
|
| 50 | + | } |
|
| 51 | + | ||
| 52 | + | /// Accept a small aggregate as parameter and return it. |
|
| 53 | + | fn identity(p: Pair) -> Pair { |
|
| 54 | + | return p; |
|
| 55 | + | } |
|
| 56 | + | ||
| 57 | + | /// Accept multiple small aggregate args. |
|
| 58 | + | fn addPairs(a: Pair, b: Pair) -> Pair { |
|
| 59 | + | return Pair { a: a.a + b.a, b: a.b + b.b }; |
|
| 60 | + | } |
|
| 61 | + | ||
| 62 | + | /// Chain calls: result of one is arg to another. |
|
| 63 | + | fn doublePair(p: Pair) -> Pair { |
|
| 64 | + | return addPairs(p, p); |
|
| 65 | + | } |
|
| 66 | + | ||
| 67 | + | /// Test 1-byte record. |
|
| 68 | + | fn testByte() -> i32 { |
|
| 69 | + | let b = makeByte(42); |
|
| 70 | + | if b.x != 42 { |
|
| 71 | + | return 1; |
|
| 72 | + | } |
|
| 73 | + | return 0; |
|
| 74 | + | } |
|
| 75 | + | ||
| 76 | + | /// Test 2-byte record. |
|
| 77 | + | fn testShort() -> i32 { |
|
| 78 | + | let s = makeShort(1234); |
|
| 79 | + | if s.x != 1234 { |
|
| 80 | + | return 1; |
|
| 81 | + | } |
|
| 82 | + | return 0; |
|
| 83 | + | } |
|
| 84 | + | ||
| 85 | + | /// Test 4-byte record. |
|
| 86 | + | fn testWord() -> i32 { |
|
| 87 | + | let w = makeWord(0xDEAD); |
|
| 88 | + | if w.x != 0xDEAD { |
|
| 89 | + | return 1; |
|
| 90 | + | } |
|
| 91 | + | return 0; |
|
| 92 | + | } |
|
| 93 | + | ||
| 94 | + | /// Test 8-byte record (boundary case, should be by value). |
|
| 95 | + | fn testPair() -> i32 { |
|
| 96 | + | let p = makePair(10, 20); |
|
| 97 | + | if p.a != 10 { |
|
| 98 | + | return 1; |
|
| 99 | + | } |
|
| 100 | + | if p.b != 20 { |
|
| 101 | + | return 2; |
|
| 102 | + | } |
|
| 103 | + | return 0; |
|
| 104 | + | } |
|
| 105 | + | ||
| 106 | + | /// Test 12-byte record (should be by reference). |
|
| 107 | + | fn testTriple() -> i32 { |
|
| 108 | + | let t = makeTriple(100, 200, 300); |
|
| 109 | + | if t.x != 100 { |
|
| 110 | + | return 1; |
|
| 111 | + | } |
|
| 112 | + | if t.y != 200 { |
|
| 113 | + | return 2; |
|
| 114 | + | } |
|
| 115 | + | if t.z != 300 { |
|
| 116 | + | return 3; |
|
| 117 | + | } |
|
| 118 | + | return 0; |
|
| 119 | + | } |
|
| 120 | + | ||
| 121 | + | /// Test passing small aggregate as parameter and returning it. |
|
| 122 | + | fn testIdentity() -> i32 { |
|
| 123 | + | let p = makePair(5, 6); |
|
| 124 | + | let q = identity(p); |
|
| 125 | + | if q.a != 5 { |
|
| 126 | + | return 1; |
|
| 127 | + | } |
|
| 128 | + | if q.b != 6 { |
|
| 129 | + | return 2; |
|
| 130 | + | } |
|
| 131 | + | return 0; |
|
| 132 | + | } |
|
| 133 | + | ||
| 134 | + | /// Test multiple small aggregate args. |
|
| 135 | + | fn testAddPairs() -> i32 { |
|
| 136 | + | let a = makePair(1, 2); |
|
| 137 | + | let b = makePair(3, 4); |
|
| 138 | + | let c = addPairs(a, b); |
|
| 139 | + | if c.a != 4 { |
|
| 140 | + | return 1; |
|
| 141 | + | } |
|
| 142 | + | if c.b != 6 { |
|
| 143 | + | return 2; |
|
| 144 | + | } |
|
| 145 | + | return 0; |
|
| 146 | + | } |
|
| 147 | + | ||
| 148 | + | /// Test chained calls. |
|
| 149 | + | fn testDoublePair() -> i32 { |
|
| 150 | + | let p = makePair(10, 20); |
|
| 151 | + | let d = doublePair(p); |
|
| 152 | + | if d.a != 20 { |
|
| 153 | + | return 1; |
|
| 154 | + | } |
|
| 155 | + | if d.b != 40 { |
|
| 156 | + | return 2; |
|
| 157 | + | } |
|
| 158 | + | return 0; |
|
| 159 | + | } |
|
| 160 | + | ||
| 161 | + | /// Test multiple returns don't interfere. |
|
| 162 | + | fn testMultipleReturns() -> i32 { |
|
| 163 | + | let p1 = makePair(1, 2); |
|
| 164 | + | let p2 = makePair(3, 4); |
|
| 165 | + | if p1.a != 1 { |
|
| 166 | + | return 1; |
|
| 167 | + | } |
|
| 168 | + | if p1.b != 2 { |
|
| 169 | + | return 2; |
|
| 170 | + | } |
|
| 171 | + | if p2.a != 3 { |
|
| 172 | + | return 3; |
|
| 173 | + | } |
|
| 174 | + | if p2.b != 4 { |
|
| 175 | + | return 4; |
|
| 176 | + | } |
|
| 177 | + | return 0; |
|
| 178 | + | } |
|
| 179 | + | ||
| 180 | + | @default fn main() -> i32 { |
|
| 181 | + | let r1 = testByte(); |
|
| 182 | + | if r1 != 0 { |
|
| 183 | + | return 10 + r1; |
|
| 184 | + | } |
|
| 185 | + | ||
| 186 | + | let r2 = testShort(); |
|
| 187 | + | if r2 != 0 { |
|
| 188 | + | return 20 + r2; |
|
| 189 | + | } |
|
| 190 | + | ||
| 191 | + | let r3 = testWord(); |
|
| 192 | + | if r3 != 0 { |
|
| 193 | + | return 30 + r3; |
|
| 194 | + | } |
|
| 195 | + | ||
| 196 | + | let r4 = testPair(); |
|
| 197 | + | if r4 != 0 { |
|
| 198 | + | return 40 + r4; |
|
| 199 | + | } |
|
| 200 | + | ||
| 201 | + | let r5 = testTriple(); |
|
| 202 | + | if r5 != 0 { |
|
| 203 | + | return 50 + r5; |
|
| 204 | + | } |
|
| 205 | + | ||
| 206 | + | let r6 = testIdentity(); |
|
| 207 | + | if r6 != 0 { |
|
| 208 | + | return 60 + r6; |
|
| 209 | + | } |
|
| 210 | + | ||
| 211 | + | let r7 = testAddPairs(); |
|
| 212 | + | if r7 != 0 { |
|
| 213 | + | return 70 + r7; |
|
| 214 | + | } |
|
| 215 | + | ||
| 216 | + | let r8 = testDoublePair(); |
|
| 217 | + | if r8 != 0 { |
|
| 218 | + | return 80 + r8; |
|
| 219 | + | } |
|
| 220 | + | ||
| 221 | + | let r9 = testMultipleReturns(); |
|
| 222 | + | if r9 != 0 { |
|
| 223 | + | return 90 + r9; |
|
| 224 | + | } |
|
| 225 | + | return 0; |
|
| 226 | + | } |
lib/std/arch/rv64/tests/aggregate.return.rad
added
+230 -0
| 1 | + | //! Test that returning aggregate types via hidden return buffer does not |
|
| 2 | + | //! corrupt the caller's stack. Modifying a returned aggregate must not affect |
|
| 3 | + | //! other locals or previously returned values. |
|
| 4 | + | ||
| 5 | + | record Pair { |
|
| 6 | + | a: u32, |
|
| 7 | + | b: u32, |
|
| 8 | + | } |
|
| 9 | + | ||
| 10 | + | record Triple { |
|
| 11 | + | x: u32, |
|
| 12 | + | y: u32, |
|
| 13 | + | z: u32, |
|
| 14 | + | } |
|
| 15 | + | ||
| 16 | + | /// Return a Pair aggregate (non-throwing, aggregate return buffer). |
|
| 17 | + | fn makePair(a: u32, b: u32) -> Pair { |
|
| 18 | + | return Pair { a, b }; |
|
| 19 | + | } |
|
| 20 | + | ||
| 21 | + | /// Return a Triple aggregate. |
|
| 22 | + | fn makeTriple(x: u32, y: u32, z: u32) -> Triple { |
|
| 23 | + | return Triple { x, y, z }; |
|
| 24 | + | } |
|
| 25 | + | ||
| 26 | + | /// Return aggregate from a throwing function (result return buffer). |
|
| 27 | + | fn makePairOrFail(a: u32, b: u32, fail: bool) -> Pair throws (u32) { |
|
| 28 | + | if fail { |
|
| 29 | + | throw 99; |
|
| 30 | + | } |
|
| 31 | + | return Pair { a, b }; |
|
| 32 | + | } |
|
| 33 | + | ||
| 34 | + | /// Modify a returned aggregate and verify caller's locals are intact. |
|
| 35 | + | fn testReturnDoesNotCorruptLocals() -> i32 { |
|
| 36 | + | let sentinel: u32 = 0xDEAD; |
|
| 37 | + | let p: Pair = makePair(10, 20); |
|
| 38 | + | let sentinel2: u32 = 0xBEEF; |
|
| 39 | + | ||
| 40 | + | if p.a != 10 { |
|
| 41 | + | return 1; |
|
| 42 | + | } |
|
| 43 | + | if p.b != 20 { |
|
| 44 | + | return 2; |
|
| 45 | + | } |
|
| 46 | + | if sentinel != 0xDEAD { |
|
| 47 | + | return 3; |
|
| 48 | + | } |
|
| 49 | + | if sentinel2 != 0xBEEF { |
|
| 50 | + | return 4; |
|
| 51 | + | } |
|
| 52 | + | return 0; |
|
| 53 | + | } |
|
| 54 | + | ||
| 55 | + | /// Two consecutive aggregate returns must not interfere. |
|
| 56 | + | fn testMultipleReturns() -> i32 { |
|
| 57 | + | let p1: Pair = makePair(1, 2); |
|
| 58 | + | let p2: Pair = makePair(3, 4); |
|
| 59 | + | ||
| 60 | + | if p1.a != 1 { |
|
| 61 | + | return 1; |
|
| 62 | + | } |
|
| 63 | + | if p1.b != 2 { |
|
| 64 | + | return 2; |
|
| 65 | + | } |
|
| 66 | + | if p2.a != 3 { |
|
| 67 | + | return 3; |
|
| 68 | + | } |
|
| 69 | + | if p2.b != 4 { |
|
| 70 | + | return 4; |
|
| 71 | + | } |
|
| 72 | + | return 0; |
|
| 73 | + | } |
|
| 74 | + | ||
| 75 | + | /// Mutating a returned aggregate must not corrupt the stack. |
|
| 76 | + | fn testMutateReturnedAggregate() -> i32 { |
|
| 77 | + | let before: u32 = 42; |
|
| 78 | + | let mut p: Pair = makePair(10, 20); |
|
| 79 | + | let after: u32 = 99; |
|
| 80 | + | ||
| 81 | + | p.a = 100; |
|
| 82 | + | p.b = 200; |
|
| 83 | + | ||
| 84 | + | if before != 42 { |
|
| 85 | + | return 1; |
|
| 86 | + | } |
|
| 87 | + | if after != 99 { |
|
| 88 | + | return 2; |
|
| 89 | + | } |
|
| 90 | + | if p.a != 100 { |
|
| 91 | + | return 3; |
|
| 92 | + | } |
|
| 93 | + | if p.b != 200 { |
|
| 94 | + | return 4; |
|
| 95 | + | } |
|
| 96 | + | return 0; |
|
| 97 | + | } |
|
| 98 | + | ||
| 99 | + | /// Aggregate return inside a loop must not corrupt accumulator state. |
|
| 100 | + | fn testAggregateReturnInLoop() -> i32 { |
|
| 101 | + | let mut total: u32 = 0; |
|
| 102 | + | let mut idx: u32 = 0; |
|
| 103 | + | while idx < 5 { |
|
| 104 | + | let p: Pair = makePair(idx, idx + 10); |
|
| 105 | + | total = total + p.a + p.b; |
|
| 106 | + | idx = idx + 1; |
|
| 107 | + | } |
|
| 108 | + | // total = sum(i + i+10 for i in 0..5) = sum(2i+10) = 2*(0+1+2+3+4) + 50 = 20 + 50 = 70 |
|
| 109 | + | if total != 70 { |
|
| 110 | + | return 1; |
|
| 111 | + | } |
|
| 112 | + | return 0; |
|
| 113 | + | } |
|
| 114 | + | ||
| 115 | + | /// Throwing function returning aggregate (result buffer) must not corrupt stack. |
|
| 116 | + | fn testThrowingAggregateReturn() -> i32 { |
|
| 117 | + | let sentinel: u32 = 0xCAFE; |
|
| 118 | + | let p: Pair = try! makePairOrFail(10, 20, false); |
|
| 119 | + | let sentinel2: u32 = 0xFACE; |
|
| 120 | + | ||
| 121 | + | if p.a != 10 { |
|
| 122 | + | return 1; |
|
| 123 | + | } |
|
| 124 | + | if p.b != 20 { |
|
| 125 | + | return 2; |
|
| 126 | + | } |
|
| 127 | + | if sentinel != 0xCAFE { |
|
| 128 | + | return 3; |
|
| 129 | + | } |
|
| 130 | + | if sentinel2 != 0xFACE { |
|
| 131 | + | return 4; |
|
| 132 | + | } |
|
| 133 | + | return 0; |
|
| 134 | + | } |
|
| 135 | + | ||
| 136 | + | /// Catching a thrown error from aggregate-returning function. |
|
| 137 | + | fn testThrowingAggregateCatch() -> i32 { |
|
| 138 | + | let sentinel: u32 = 0xBEEF; |
|
| 139 | + | try makePairOrFail(10, 20, true) catch {}; |
|
| 140 | + | if sentinel != 0xBEEF { |
|
| 141 | + | return 1; |
|
| 142 | + | } |
|
| 143 | + | return 0; |
|
| 144 | + | } |
|
| 145 | + | ||
| 146 | + | /// Triple (12 bytes) aggregate return. |
|
| 147 | + | fn testTripleReturn() -> i32 { |
|
| 148 | + | let t: Triple = makeTriple(100, 200, 300); |
|
| 149 | + | if t.x != 100 { |
|
| 150 | + | return 1; |
|
| 151 | + | } |
|
| 152 | + | if t.y != 200 { |
|
| 153 | + | return 2; |
|
| 154 | + | } |
|
| 155 | + | if t.z != 300 { |
|
| 156 | + | return 3; |
|
| 157 | + | } |
|
| 158 | + | return 0; |
|
| 159 | + | } |
|
| 160 | + | ||
| 161 | + | /// Multiple aggregate calls interleaved with scalar work. |
|
| 162 | + | fn testInterleavedCalls() -> i32 { |
|
| 163 | + | let p1: Pair = makePair(5, 6); |
|
| 164 | + | let scalar: u32 = p1.a + p1.b; |
|
| 165 | + | let t: Triple = makeTriple(scalar, scalar + 1, scalar + 2); |
|
| 166 | + | let p2: Pair = makePair(t.x, t.z); |
|
| 167 | + | ||
| 168 | + | if scalar != 11 { |
|
| 169 | + | return 1; |
|
| 170 | + | } |
|
| 171 | + | if t.x != 11 { |
|
| 172 | + | return 2; |
|
| 173 | + | } |
|
| 174 | + | if t.y != 12 { |
|
| 175 | + | return 3; |
|
| 176 | + | } |
|
| 177 | + | if t.z != 13 { |
|
| 178 | + | return 4; |
|
| 179 | + | } |
|
| 180 | + | if p2.a != 11 { |
|
| 181 | + | return 5; |
|
| 182 | + | } |
|
| 183 | + | if p2.b != 13 { |
|
| 184 | + | return 6; |
|
| 185 | + | } |
|
| 186 | + | return 0; |
|
| 187 | + | } |
|
| 188 | + | ||
| 189 | + | @default fn main() -> i32 { |
|
| 190 | + | let r1: i32 = testReturnDoesNotCorruptLocals(); |
|
| 191 | + | if r1 != 0 { |
|
| 192 | + | return 10 + r1; |
|
| 193 | + | } |
|
| 194 | + | ||
| 195 | + | let r2: i32 = testMultipleReturns(); |
|
| 196 | + | if r2 != 0 { |
|
| 197 | + | return 20 + r2; |
|
| 198 | + | } |
|
| 199 | + | ||
| 200 | + | let r3: i32 = testMutateReturnedAggregate(); |
|
| 201 | + | if r3 != 0 { |
|
| 202 | + | return 30 + r3; |
|
| 203 | + | } |
|
| 204 | + | ||
| 205 | + | let r4: i32 = testAggregateReturnInLoop(); |
|
| 206 | + | if r4 != 0 { |
|
| 207 | + | return 40 + r4; |
|
| 208 | + | } |
|
| 209 | + | ||
| 210 | + | let r5: i32 = testThrowingAggregateReturn(); |
|
| 211 | + | if r5 != 0 { |
|
| 212 | + | return 50 + r5; |
|
| 213 | + | } |
|
| 214 | + | ||
| 215 | + | let r6: i32 = testThrowingAggregateCatch(); |
|
| 216 | + | if r6 != 0 { |
|
| 217 | + | return 60 + r6; |
|
| 218 | + | } |
|
| 219 | + | ||
| 220 | + | let r7: i32 = testTripleReturn(); |
|
| 221 | + | if r7 != 0 { |
|
| 222 | + | return 70 + r7; |
|
| 223 | + | } |
|
| 224 | + | ||
| 225 | + | let r8: i32 = testInterleavedCalls(); |
|
| 226 | + | if r8 != 0 { |
|
| 227 | + | return 80 + r8; |
|
| 228 | + | } |
|
| 229 | + | return 0; |
|
| 230 | + | } |
lib/std/arch/rv64/tests/arith.assignment.rad
added
+27 -0
| 1 | + | @default fn main() -> i32 { |
|
| 2 | + | let mut a: i32 = 6; |
|
| 3 | + | let mut b: i32 = 3; |
|
| 4 | + | let mut c: i32 = 0; |
|
| 5 | + | let mut d: i32 = 0; |
|
| 6 | + | ||
| 7 | + | // Test addition. |
|
| 8 | + | c = a + b; // c = 9 |
|
| 9 | + | ||
| 10 | + | // Test subtraction. |
|
| 11 | + | c = c - b; // c = 6 |
|
| 12 | + | ||
| 13 | + | // Test multiplication. |
|
| 14 | + | c = c * b; // c = 18 |
|
| 15 | + | ||
| 16 | + | // Test division. |
|
| 17 | + | c = c / b; // c = 6 |
|
| 18 | + | ||
| 19 | + | // Test mixed operations. |
|
| 20 | + | d = a * b + c / b; // d = 18 + 2 = 20 |
|
| 21 | + | d = d + a * b / a; // d = 20 + 3 = 23 |
|
| 22 | + | d = d + (b * a - c); // d = 23 + (18 - 6) = 35 |
|
| 23 | + | d = d + (c - a / b); // d = 35 + (6 - 2) = 39 |
|
| 24 | + | d = d + 3; // d = 42 |
|
| 25 | + | ||
| 26 | + | return (d) - 42; |
|
| 27 | + | } |
lib/std/arch/rv64/tests/arith.basic.rad
added
+5 -0
| 1 | + | @default fn main() -> i32 { |
|
| 2 | + | // This expression will require multiple registers |
|
| 3 | + | // to evaluate the nested operations. |
|
| 4 | + | return (1 + (2 + (3 + (4 + (5 + 6))))) - 21; |
|
| 5 | + | } |
lib/std/arch/rv64/tests/arith.modulo.rad
added
+6 -0
| 1 | + | @default fn main() -> i32 { |
|
| 2 | + | let a: i32 = 17; |
|
| 3 | + | let b: i32 = 5; |
|
| 4 | + | ||
| 5 | + | return (a % b) - 2; |
|
| 6 | + | } |
lib/std/arch/rv64/tests/arith.subword.rad
added
+213 -0
| 1 | + | //! Test sub-word arithmetic normalization. |
|
| 2 | + | //! Verifies that W8/W16 arithmetic results are properly sign/zero-extended, |
|
| 3 | + | //! and that W32 operations use the correct *w instruction variants. |
|
| 4 | + | ||
| 5 | + | fn testI8Overflow() -> bool { |
|
| 6 | + | let a: i8 = 120; |
|
| 7 | + | let b: i8 = 10; |
|
| 8 | + | let result: i8 = a + b; |
|
| 9 | + | // 120 + 10 = 130, which wraps to -126 in i8 |
|
| 10 | + | if result != -126 { |
|
| 11 | + | return false; |
|
| 12 | + | } |
|
| 13 | + | return true; |
|
| 14 | + | } |
|
| 15 | + | ||
| 16 | + | fn testU8Overflow() -> bool { |
|
| 17 | + | let a: u8 = 250; |
|
| 18 | + | let b: u8 = 10; |
|
| 19 | + | let result: u8 = a + b; |
|
| 20 | + | // 250 + 10 = 260, which wraps to 4 in u8 |
|
| 21 | + | if result != 4 { |
|
| 22 | + | return false; |
|
| 23 | + | } |
|
| 24 | + | return true; |
|
| 25 | + | } |
|
| 26 | + | ||
| 27 | + | fn testI8Subtraction() -> bool { |
|
| 28 | + | let a: i8 = -100; |
|
| 29 | + | let b: i8 = 50; |
|
| 30 | + | let result: i8 = a - b; |
|
| 31 | + | // -100 - 50 = -150, which wraps to 106 in i8 |
|
| 32 | + | if result != 106 { |
|
| 33 | + | return false; |
|
| 34 | + | } |
|
| 35 | + | return true; |
|
| 36 | + | } |
|
| 37 | + | ||
| 38 | + | fn testU8Subtraction() -> bool { |
|
| 39 | + | let a: u8 = 5; |
|
| 40 | + | let b: u8 = 10; |
|
| 41 | + | let result: u8 = a - b; |
|
| 42 | + | // 5 - 10 = -5, which wraps to 251 in u8 |
|
| 43 | + | if result != 251 { |
|
| 44 | + | return false; |
|
| 45 | + | } |
|
| 46 | + | return true; |
|
| 47 | + | } |
|
| 48 | + | ||
| 49 | + | fn testI16Overflow() -> bool { |
|
| 50 | + | let a: i16 = 32000; |
|
| 51 | + | let b: i16 = 1000; |
|
| 52 | + | let result: i16 = a + b; |
|
| 53 | + | // 32000 + 1000 = 33000, which wraps to -32536 in i16 |
|
| 54 | + | if result != -32536 { |
|
| 55 | + | return false; |
|
| 56 | + | } |
|
| 57 | + | return true; |
|
| 58 | + | } |
|
| 59 | + | ||
| 60 | + | fn testU16Overflow() -> bool { |
|
| 61 | + | let a: u16 = 65530; |
|
| 62 | + | let b: u16 = 10; |
|
| 63 | + | let result: u16 = a + b; |
|
| 64 | + | // 65530 + 10 = 65540, which wraps to 4 in u16 |
|
| 65 | + | if result != 4 { |
|
| 66 | + | return false; |
|
| 67 | + | } |
|
| 68 | + | return true; |
|
| 69 | + | } |
|
| 70 | + | ||
| 71 | + | fn testI8Multiply() -> bool { |
|
| 72 | + | let a: i8 = 15; |
|
| 73 | + | let b: i8 = 10; |
|
| 74 | + | let result: i8 = a * b; |
|
| 75 | + | // 15 * 10 = 150, which wraps to -106 in i8 |
|
| 76 | + | if result != -106 { |
|
| 77 | + | return false; |
|
| 78 | + | } |
|
| 79 | + | return true; |
|
| 80 | + | } |
|
| 81 | + | ||
| 82 | + | fn testU8Multiply() -> bool { |
|
| 83 | + | let a: u8 = 20; |
|
| 84 | + | let b: u8 = 15; |
|
| 85 | + | let result: u8 = a * b; |
|
| 86 | + | // 20 * 15 = 300, which wraps to 44 in u8 |
|
| 87 | + | if result != 44 { |
|
| 88 | + | return false; |
|
| 89 | + | } |
|
| 90 | + | return true; |
|
| 91 | + | } |
|
| 92 | + | ||
| 93 | + | fn testI8Comparison() -> bool { |
|
| 94 | + | let a: i8 = 127; |
|
| 95 | + | let b: i8 = a + 1; |
|
| 96 | + | // b should be -128 after overflow |
|
| 97 | + | if b >= 0 { |
|
| 98 | + | return false; |
|
| 99 | + | } |
|
| 100 | + | if b != -128 { |
|
| 101 | + | return false; |
|
| 102 | + | } |
|
| 103 | + | return true; |
|
| 104 | + | } |
|
| 105 | + | ||
| 106 | + | fn testU8Comparison() -> bool { |
|
| 107 | + | let a: u8 = 255; |
|
| 108 | + | let b: u8 = a + 1 as u8; |
|
| 109 | + | // b should be 0 after overflow |
|
| 110 | + | if b != 0 { |
|
| 111 | + | return false; |
|
| 112 | + | } |
|
| 113 | + | return true; |
|
| 114 | + | } |
|
| 115 | + | ||
| 116 | + | fn testNarrowingCast() -> bool { |
|
| 117 | + | let x: i32 = 1000; |
|
| 118 | + | let narrow: u8 = x as u8; |
|
| 119 | + | // 1000 = 0x3E8, lower byte = 0xE8 = 232 |
|
| 120 | + | if narrow != 232 { |
|
| 121 | + | return false; |
|
| 122 | + | } |
|
| 123 | + | return true; |
|
| 124 | + | } |
|
| 125 | + | ||
| 126 | + | fn testW32ShiftRight() -> bool { |
|
| 127 | + | let max: u32 = 0xFFFFFFFF; |
|
| 128 | + | let shifted: u32 = max >> 16; |
|
| 129 | + | // Should be 0xFFFF = 65535 |
|
| 130 | + | if shifted != 65535 { |
|
| 131 | + | return false; |
|
| 132 | + | } |
|
| 133 | + | return true; |
|
| 134 | + | } |
|
| 135 | + | ||
| 136 | + | fn testI8NegOverflow() -> bool { |
|
| 137 | + | let min: i8 = -128; |
|
| 138 | + | let negated: i8 = -min; |
|
| 139 | + | // RV64 should preserve i8 wrap behavior: -(-128) == -128 |
|
| 140 | + | if negated != -128 { |
|
| 141 | + | return false; |
|
| 142 | + | } |
|
| 143 | + | return true; |
|
| 144 | + | } |
|
| 145 | + | ||
| 146 | + | fn testU8BitNot() -> bool { |
|
| 147 | + | let x: u8 = 0; |
|
| 148 | + | let inverted: u8 = ~x; |
|
| 149 | + | // 8-bit not of 0 is 0xFF. |
|
| 150 | + | if inverted != 255 { |
|
| 151 | + | return false; |
|
| 152 | + | } |
|
| 153 | + | return true; |
|
| 154 | + | } |
|
| 155 | + | ||
| 156 | + | fn testW32ShiftLargeImmediate() -> bool { |
|
| 157 | + | let x: u32 = 1; |
|
| 158 | + | let shifted: u32 = x << 40; |
|
| 159 | + | // Shift amount should be masked to 5 bits for 32-bit shifts: 40 % 32 = 8. |
|
| 160 | + | if shifted != 256 { |
|
| 161 | + | return false; |
|
| 162 | + | } |
|
| 163 | + | return true; |
|
| 164 | + | } |
|
| 165 | + | ||
| 166 | + | @default fn main() -> i32 { |
|
| 167 | + | if not testI8Overflow() { |
|
| 168 | + | return 1; |
|
| 169 | + | } |
|
| 170 | + | if not testU8Overflow() { |
|
| 171 | + | return 2; |
|
| 172 | + | } |
|
| 173 | + | if not testI8Subtraction() { |
|
| 174 | + | return 3; |
|
| 175 | + | } |
|
| 176 | + | if not testU8Subtraction() { |
|
| 177 | + | return 4; |
|
| 178 | + | } |
|
| 179 | + | if not testI16Overflow() { |
|
| 180 | + | return 5; |
|
| 181 | + | } |
|
| 182 | + | if not testU16Overflow() { |
|
| 183 | + | return 6; |
|
| 184 | + | } |
|
| 185 | + | if not testI8Multiply() { |
|
| 186 | + | return 7; |
|
| 187 | + | } |
|
| 188 | + | if not testU8Multiply() { |
|
| 189 | + | return 8; |
|
| 190 | + | } |
|
| 191 | + | if not testI8Comparison() { |
|
| 192 | + | return 9; |
|
| 193 | + | } |
|
| 194 | + | if not testU8Comparison() { |
|
| 195 | + | return 10; |
|
| 196 | + | } |
|
| 197 | + | if not testNarrowingCast() { |
|
| 198 | + | return 11; |
|
| 199 | + | } |
|
| 200 | + | if not testW32ShiftRight() { |
|
| 201 | + | return 12; |
|
| 202 | + | } |
|
| 203 | + | if not testI8NegOverflow() { |
|
| 204 | + | return 13; |
|
| 205 | + | } |
|
| 206 | + | if not testU8BitNot() { |
|
| 207 | + | return 14; |
|
| 208 | + | } |
|
| 209 | + | if not testW32ShiftLargeImmediate() { |
|
| 210 | + | return 15; |
|
| 211 | + | } |
|
| 212 | + | return 0; |
|
| 213 | + | } |
lib/std/arch/rv64/tests/arith.sum.rad
added
+5 -0
| 1 | + | @default fn main() -> i32 { |
|
| 2 | + | // This expression will require multiple registers |
|
| 3 | + | // to evaluate the nested operations. |
|
| 4 | + | return -21 + (1 + (2 + (3 + (4 + (5 + 6))))); |
|
| 5 | + | } |
lib/std/arch/rv64/tests/array.assign.rad
added
+11 -0
| 1 | + | //! returns: 2 |
|
| 2 | + | //! Test array assignment between variables. |
|
| 3 | + | ||
| 4 | + | @default fn main() -> i32 { |
|
| 5 | + | let mut a: [i32; 5] = [1, 2, 3, 4, 5]; |
|
| 6 | + | let mut b: [i32; 5] = [8, 9, 7, 6, 0]; |
|
| 7 | + | ||
| 8 | + | b = a; |
|
| 9 | + | ||
| 10 | + | return b[0] + b[3] - b[2]; |
|
| 11 | + | } |
lib/std/arch/rv64/tests/array.bounds.check.rad
added
+14 -0
| 1 | + | //! returns: 133 |
|
| 2 | + | /// Test array bounds checking with runtime EBREAK. |
|
| 3 | + | /// Uses a function call to prevent the index from being a compile-time constant. |
|
| 4 | + | ||
| 5 | + | fn getIndex(i: u32) -> u32 { |
|
| 6 | + | return i; |
|
| 7 | + | } |
|
| 8 | + | ||
| 9 | + | @default fn main() -> i32 { |
|
| 10 | + | let arr: [i32; 3] = [1, 2, 3]; |
|
| 11 | + | let value: i32 = arr[getIndex(3)]; |
|
| 12 | + | ||
| 13 | + | return value; |
|
| 14 | + | } |
lib/std/arch/rv64/tests/array.index.assign.rad
added
+24 -0
| 1 | + | //! Test array index assignment and reading. |
|
| 2 | + | ||
| 3 | + | @default fn main() -> i32 { |
|
| 4 | + | let mut arr: [i32; 5] = [1, 2, 3, 4, 5]; |
|
| 5 | + | ||
| 6 | + | arr[0] = 10; |
|
| 7 | + | arr[1] = 20; |
|
| 8 | + | arr[2] = 30; |
|
| 9 | + | arr[3] = 40; |
|
| 10 | + | arr[4] = 50; |
|
| 11 | + | ||
| 12 | + | let mut sum: i32 = 0; |
|
| 13 | + | ||
| 14 | + | sum = sum + arr[0]; |
|
| 15 | + | sum = sum + arr[1]; |
|
| 16 | + | sum = sum + arr[2]; |
|
| 17 | + | sum = sum + arr[3]; |
|
| 18 | + | sum = sum - arr[4]; |
|
| 19 | + | ||
| 20 | + | if sum != 50 { |
|
| 21 | + | return 1; |
|
| 22 | + | } |
|
| 23 | + | return 0; |
|
| 24 | + | } |
lib/std/arch/rv64/tests/array.index.rad
added
+13 -0
| 1 | + | // Basic array test: literals and indexing. |
|
| 2 | + | ||
| 3 | + | @default fn main() -> i32 { |
|
| 4 | + | let mut arr: [i32; 4] = [10, 20, 30, 40]; |
|
| 5 | + | let mut sum: i32 = 0; |
|
| 6 | + | ||
| 7 | + | sum = sum + arr[0]; |
|
| 8 | + | sum = sum + arr[1]; |
|
| 9 | + | sum = sum + arr[2]; |
|
| 10 | + | sum = sum + arr[3]; |
|
| 11 | + | ||
| 12 | + | return (sum) - 100; |
|
| 13 | + | } |
lib/std/arch/rv64/tests/array.length.rad
added
+13 -0
| 1 | + | // Test the `len` field on arrays. |
|
| 2 | + | ||
| 3 | + | @default fn main() -> i32 { |
|
| 4 | + | let mut ary: [i32; 7] = [1, 2, 3, 4, 5, 6, 7]; |
|
| 5 | + | let mut sum: i32 = 0; |
|
| 6 | + | let mut i: u32 = 0; |
|
| 7 | + | ||
| 8 | + | while (i < ary.len) { |
|
| 9 | + | sum = sum + ary[i]; |
|
| 10 | + | i = i + 1; |
|
| 11 | + | } |
|
| 12 | + | return (sum) - 28; |
|
| 13 | + | } |
lib/std/arch/rv64/tests/array.math.rad
added
+52 -0
| 1 | + | //! returns: 42 |
|
| 2 | + | //! Test array math and nested array operations. |
|
| 3 | + | ||
| 4 | + | fn sumMatrix(mat: [[i32; 3]; 2]) -> i32 { |
|
| 5 | + | let mut sum: i32 = 0; |
|
| 6 | + | for vec in mat { |
|
| 7 | + | for x in vec { |
|
| 8 | + | sum = sum + x; |
|
| 9 | + | } |
|
| 10 | + | } |
|
| 11 | + | return sum; |
|
| 12 | + | } |
|
| 13 | + | ||
| 14 | + | fn sumMatrixTransposed(mat: [[i32; 2]; 3]) -> i32 { |
|
| 15 | + | let mut sum: i32 = 0; |
|
| 16 | + | for vec in (mat) { |
|
| 17 | + | for x in (vec) { |
|
| 18 | + | sum = sum + x; |
|
| 19 | + | } |
|
| 20 | + | } |
|
| 21 | + | return sum; |
|
| 22 | + | } |
|
| 23 | + | ||
| 24 | + | ||
| 25 | + | fn transpose(matrix: [[i32; 3]; 2]) -> [[i32; 2]; 3] { |
|
| 26 | + | let mut result: [[i32; 2]; 3] = [[0, 0], [0, 0], [0, 0]]; |
|
| 27 | + | let mut i: u32 = 0; |
|
| 28 | + | ||
| 29 | + | while (i < matrix.len) { |
|
| 30 | + | let mut j: u32 = 0; |
|
| 31 | + | while (j < matrix[i].len) { |
|
| 32 | + | result[j][i] = matrix[i][j]; |
|
| 33 | + | j = j + 1; |
|
| 34 | + | } |
|
| 35 | + | i = i + 1; |
|
| 36 | + | } |
|
| 37 | + | return result; |
|
| 38 | + | } |
|
| 39 | + | ||
| 40 | + | @default fn main() -> i32 { |
|
| 41 | + | // Initialize a 2x3 matrix. |
|
| 42 | + | let mut matrix: [[i32; 3]; 2] = [ |
|
| 43 | + | [1, 2, 3], |
|
| 44 | + | [4, 5, 6] |
|
| 45 | + | ]; |
|
| 46 | + | // Transpose the matrix to 3x2. |
|
| 47 | + | let mut transposed: [[i32; 2]; 3] = transpose(matrix); |
|
| 48 | + | // Sum of all elements in transposed should still be 21. |
|
| 49 | + | let mut sum: i32 = sumMatrixTransposed(transposed); |
|
| 50 | + | // Return twice the sum (21 * 2 = 42). |
|
| 51 | + | return sumMatrix(matrix) + sum; |
|
| 52 | + | } |
lib/std/arch/rv64/tests/array.nested.assign.rad
added
+19 -0
| 1 | + | //! returns: 24 |
|
| 2 | + | //! Test nested array element assignment. |
|
| 3 | + | ||
| 4 | + | @default fn main() -> i32 { |
|
| 5 | + | let mut m: [[i32; 3]; 2] = [ |
|
| 6 | + | [1, 2, 3], |
|
| 7 | + | [4, 5, 6] |
|
| 8 | + | ]; |
|
| 9 | + | m[1][1] = 7; |
|
| 10 | + | m[0][2] = 4; |
|
| 11 | + | ||
| 12 | + | return |
|
| 13 | + | m[0][0] + |
|
| 14 | + | m[0][1] + |
|
| 15 | + | m[0][2] + |
|
| 16 | + | m[1][0] + |
|
| 17 | + | m[1][1] + |
|
| 18 | + | m[1][2]; |
|
| 19 | + | } |
lib/std/arch/rv64/tests/array.nested.rad
added
+18 -0
| 1 | + | //! returns: 21 |
|
| 2 | + | //! Test nested array copy and access. |
|
| 3 | + | ||
| 4 | + | @default fn main() -> i32 { |
|
| 5 | + | let mut m1: [[i32; 3]; 2] = [ |
|
| 6 | + | [1, 2, 3], |
|
| 7 | + | [4, 5, 6] |
|
| 8 | + | ]; |
|
| 9 | + | let mut m2: [[i32; 3]; 2] = m1; |
|
| 10 | + | ||
| 11 | + | return |
|
| 12 | + | m1[0][0] + |
|
| 13 | + | m2[0][1] + |
|
| 14 | + | m1[0][2] + |
|
| 15 | + | m2[1][0] + |
|
| 16 | + | m1[1][1] + |
|
| 17 | + | m2[1][2]; |
|
| 18 | + | } |
lib/std/arch/rv64/tests/array.record.elements.rad
added
+72 -0
| 1 | + | //! returns: 50 |
|
| 2 | + | //! Test arrays with record elements. |
|
| 3 | + | ||
| 4 | + | record Point { |
|
| 5 | + | x: i32, |
|
| 6 | + | y: i32, |
|
| 7 | + | } |
|
| 8 | + | ||
| 9 | + | fn calculateDistance(p1: Point, p2: Point) -> i32 { |
|
| 10 | + | // Simple Manhattan distance |
|
| 11 | + | let mut dx: i32 = p2.x - p1.x; |
|
| 12 | + | if (dx < 0) { |
|
| 13 | + | dx = -dx; |
|
| 14 | + | } |
|
| 15 | + | let mut dy: i32 = p2.y - p1.y; |
|
| 16 | + | if (dy < 0) { |
|
| 17 | + | dy = -dy; |
|
| 18 | + | } |
|
| 19 | + | return dx + dy; |
|
| 20 | + | } |
|
| 21 | + | ||
| 22 | + | fn sumPoints(points: [Point; 4]) -> i32 { |
|
| 23 | + | let mut sum: i32 = 0; |
|
| 24 | + | let mut i: u32 = 0; |
|
| 25 | + | ||
| 26 | + | while (i < 4) { |
|
| 27 | + | sum = sum + points[i].x + points[i].y; |
|
| 28 | + | i = i + 1; |
|
| 29 | + | } |
|
| 30 | + | return sum; |
|
| 31 | + | } |
|
| 32 | + | ||
| 33 | + | fn shiftPoints(points: [Point; 4], dx: i32, dy: i32) -> [Point; 4] { |
|
| 34 | + | let mut result: [Point; 4] = [ |
|
| 35 | + | Point { x: 0, y: 0 }, |
|
| 36 | + | Point { x: 0, y: 0 }, |
|
| 37 | + | Point { x: 0, y: 0 }, |
|
| 38 | + | Point { x: 0, y: 0 } |
|
| 39 | + | ]; |
|
| 40 | + | let mut i: u32 = 0; |
|
| 41 | + | while (i < 4) { |
|
| 42 | + | result[i] = Point { |
|
| 43 | + | x: points[i].x + dx, |
|
| 44 | + | y: points[i].y + dy |
|
| 45 | + | }; |
|
| 46 | + | i = i + 1; |
|
| 47 | + | } |
|
| 48 | + | return result; |
|
| 49 | + | } |
|
| 50 | + | ||
| 51 | + | @default fn main() -> i32 { |
|
| 52 | + | // Create an array of Point structs |
|
| 53 | + | let mut points: [Point; 4] = [ |
|
| 54 | + | Point { x: 1, y: 2 }, |
|
| 55 | + | Point { x: 3, y: 4 }, |
|
| 56 | + | Point { x: 5, y: 6 }, |
|
| 57 | + | Point { x: 7, y: 8 } |
|
| 58 | + | ]; |
|
| 59 | + | ||
| 60 | + | // Calculate sum of all x and y values. |
|
| 61 | + | let mut total: i32 = sumPoints(points); |
|
| 62 | + | // Create a shifted version of the points. |
|
| 63 | + | let mut shifted: [Point; 4] = shiftPoints(points, 1, 1); |
|
| 64 | + | // Calculate distance between original and shifted array elements. |
|
| 65 | + | let mut distance: i32 = 0; |
|
| 66 | + | let mut i: u32 = 0; |
|
| 67 | + | while (i < points.len) { |
|
| 68 | + | distance = distance + calculateDistance(points[i], shifted[i]); |
|
| 69 | + | i = i + 1; |
|
| 70 | + | } |
|
| 71 | + | return total + distance + shifted[3].x - points[0].y; |
|
| 72 | + | } |
lib/std/arch/rv64/tests/array.repeat.edge.rad
added
+24 -0
| 1 | + | //! returns: 1 |
|
| 2 | + | //! Test edge cases for array repeat syntax. |
|
| 3 | + | ||
| 4 | + | /// Test large array initialization with repeat syntax. |
|
| 5 | + | fn testLargeArray() -> bool { |
|
| 6 | + | let arr: [i32; 256] = [7; 256]; |
|
| 7 | + | for x in arr { |
|
| 8 | + | if x != 7 { |
|
| 9 | + | return false; |
|
| 10 | + | } |
|
| 11 | + | } |
|
| 12 | + | return true; |
|
| 13 | + | } |
|
| 14 | + | ||
| 15 | + | /// Test repeat syntax with expression values. |
|
| 16 | + | fn testWithExpressions() -> bool { |
|
| 17 | + | let arr: [i32; 3] = [3 + 2; 3]; |
|
| 18 | + | ||
| 19 | + | return arr[0] == 5 and arr[1] == 5 and arr[2] == 5; |
|
| 20 | + | } |
|
| 21 | + | ||
| 22 | + | @default fn main() -> bool { |
|
| 23 | + | return testLargeArray() and testWithExpressions(); |
|
| 24 | + | } |
lib/std/arch/rv64/tests/array.repeat.rad
added
+33 -0
| 1 | + | //! returns: 1 |
|
| 2 | + | //! Test basic array repeat syntax. |
|
| 3 | + | ||
| 4 | + | /// Test basic repeat initialization. |
|
| 5 | + | fn testBasic() -> bool { |
|
| 6 | + | let arr: [i32; 3] = [42; 3]; |
|
| 7 | + | return arr[0] == 42 and arr[1] == 42 and arr[2] == 42; |
|
| 8 | + | } |
|
| 9 | + | ||
| 10 | + | /// Test repeat initialization with zero. |
|
| 11 | + | fn testZero() -> bool { |
|
| 12 | + | let arr: [i32; 5] = [0; 5]; |
|
| 13 | + | return arr[0] == 0 and arr[1] == 0 and arr[2] == 0 and arr[3] == 0 and arr[4] == 0; |
|
| 14 | + | } |
|
| 15 | + | ||
| 16 | + | /// Test repeat initialization with booleans. |
|
| 17 | + | fn testBoolean() -> bool { |
|
| 18 | + | let arr: [bool; 4] = [true; 4]; |
|
| 19 | + | return arr[0] and arr[1] and arr[2] and arr[3]; |
|
| 20 | + | } |
|
| 21 | + | ||
| 22 | + | /// Test repeat initialization with single element. |
|
| 23 | + | fn testSingle() -> bool { |
|
| 24 | + | let arr: [i32; 1] = [99; 1]; |
|
| 25 | + | return arr[0] == 99; |
|
| 26 | + | } |
|
| 27 | + | ||
| 28 | + | @default fn main() -> bool { |
|
| 29 | + | return testBasic() |
|
| 30 | + | and testZero() |
|
| 31 | + | and testBoolean() |
|
| 32 | + | and testSingle(); |
|
| 33 | + | } |
lib/std/arch/rv64/tests/array.return.rad
added
+16 -0
| 1 | + | // Test calling functions that return array values. |
|
| 2 | + | ||
| 3 | + | fn compute(ary: [i32; 4]) -> [i32; 4] { |
|
| 4 | + | return [ |
|
| 5 | + | ary[0] * 1, |
|
| 6 | + | ary[1] * 2, |
|
| 7 | + | ary[2] * 3, |
|
| 8 | + | ary[3] * 4 |
|
| 9 | + | ]; |
|
| 10 | + | } |
|
| 11 | + | ||
| 12 | + | @default fn main() -> i32 { |
|
| 13 | + | let mut ary: [i32; 4] = compute([1, 2, 3, 4]); |
|
| 14 | + | ||
| 15 | + | return (ary[0] + ary[1] + ary[2] + ary[3]) - 30; |
|
| 16 | + | } |
lib/std/arch/rv64/tests/array.slice.empty.rad
added
+7 -0
| 1 | + | fn length(slice: *[u8]) -> u32 { |
|
| 2 | + | return slice.len; |
|
| 3 | + | } |
|
| 4 | + | ||
| 5 | + | @default fn main() -> u32 { |
|
| 6 | + | return length(&[]); |
|
| 7 | + | } |
lib/std/arch/rv64/tests/array.slice.gen.end.rad
added
+6 -0
| 1 | + | @default fn main() -> i32 { |
|
| 2 | + | let mut arr: [i32; 4] = [1, 2, 3, 4]; |
|
| 3 | + | let mut slice: *[i32] = &arr[..2]; |
|
| 4 | + | ||
| 5 | + | return 0; |
|
| 6 | + | } |
lib/std/arch/rv64/tests/array.slice.gen.index.rad
added
+6 -0
| 1 | + | @default fn main() -> i32 { |
|
| 2 | + | let mut arr: [i32; 4] = [1, 2, 3, 4]; |
|
| 3 | + | let mut slice: *[i32] = &arr[1..]; |
|
| 4 | + | ||
| 5 | + | return (slice[1]) - 3; |
|
| 6 | + | } |
lib/std/arch/rv64/tests/array.slice.gen.open.rad
added
+6 -0
| 1 | + | @default fn main() -> i32 { |
|
| 2 | + | let mut arr: [i32; 4] = [1, 2, 3, 4]; |
|
| 3 | + | let mut slice: *[i32] = &arr[..]; |
|
| 4 | + | ||
| 5 | + | return 0; |
|
| 6 | + | } |
lib/std/arch/rv64/tests/array.slice.gen.start.end.rad
added
+6 -0
| 1 | + | @default fn main() -> i32 { |
|
| 2 | + | let mut arr: [i32; 4] = [1, 2, 3, 4]; |
|
| 3 | + | let mut slice: *[i32] = &arr[1..3]; |
|
| 4 | + | ||
| 5 | + | return 0; |
|
| 6 | + | } |
lib/std/arch/rv64/tests/array.slice.gen.start.rad
added
+6 -0
| 1 | + | @default fn main() -> i32 { |
|
| 2 | + | let mut arr: [i32; 4] = [1, 2, 3, 4]; |
|
| 3 | + | let mut slice: *[i32] = &arr[2..]; |
|
| 4 | + | ||
| 5 | + | return 0; |
|
| 6 | + | } |
lib/std/arch/rv64/tests/array.slice.rad
added
+44 -0
| 1 | + | //! Test array slicing with various ranges. |
|
| 2 | + | ||
| 3 | + | @default fn main() -> i32 { |
|
| 4 | + | let arr: [i32; 5] = [1, 2, 3, 4, 5]; |
|
| 5 | + | ||
| 6 | + | let slice1: *[i32] = &arr[..]; |
|
| 7 | + | let slice2: *[i32] = &arr[1..4]; |
|
| 8 | + | let slice3: *[i32] = &arr[..3]; |
|
| 9 | + | let slice4: *[i32] = &arr[2..]; |
|
| 10 | + | ||
| 11 | + | if slice1.len != 5 { |
|
| 12 | + | return 1; |
|
| 13 | + | } |
|
| 14 | + | if slice2.len != 3 { |
|
| 15 | + | return 2; |
|
| 16 | + | } |
|
| 17 | + | if slice3.len != 3 { |
|
| 18 | + | return 3; |
|
| 19 | + | } |
|
| 20 | + | if slice4.len != 3 { |
|
| 21 | + | return 4; |
|
| 22 | + | } |
|
| 23 | + | ||
| 24 | + | let slice5: *[i32] = slice3; |
|
| 25 | + | ||
| 26 | + | if slice5.len != 3 { |
|
| 27 | + | return 5; |
|
| 28 | + | } |
|
| 29 | + | if slice5.ptr != slice3.ptr { |
|
| 30 | + | return 6; |
|
| 31 | + | } |
|
| 32 | + | ||
| 33 | + | let sum1: i32 = slice1[0] + slice1[4]; // 6 |
|
| 34 | + | let sum2: i32 = slice2[0] + slice2[1]; // 5 |
|
| 35 | + | let sum3: i32 = slice3[0] + slice3[2]; // 4 |
|
| 36 | + | let sum4: i32 = slice4[0] + slice4[2]; // 8 |
|
| 37 | + | ||
| 38 | + | let total: i32 = sum1 + sum2 + sum3 + sum4; |
|
| 39 | + | if total != 23 { |
|
| 40 | + | return 7; |
|
| 41 | + | } |
|
| 42 | + | ||
| 43 | + | return 0; |
|
| 44 | + | } |
lib/std/arch/rv64/tests/as.precedence.rad
added
+14 -0
| 1 | + | ||
| 2 | + | ||
| 3 | + | @default fn main() -> i32 { |
|
| 4 | + | let p1: u8 = 1; |
|
| 5 | + | let p2: u8 = 2; |
|
| 6 | + | ||
| 7 | + | // Simple cast. |
|
| 8 | + | let x: u32 = &p1 as u32; |
|
| 9 | + | ||
| 10 | + | // Cast with subtraction. |
|
| 11 | + | let y: u32 = &p1 as u32 - (&p2) as u32; |
|
| 12 | + | ||
| 13 | + | return 0; |
|
| 14 | + | } |
lib/std/arch/rv64/tests/assign.mutable.rad
added
+218 -0
| 1 | + | //! returns: 0 |
|
| 2 | + | //! Test suite for stack memory allocation and aliasing behavior. |
|
| 3 | + | //! Tests variable aliasing, mutations, and stack frame management. |
|
| 4 | + | ||
| 5 | + | record Person { |
|
| 6 | + | age: i32, |
|
| 7 | + | score: i32, |
|
| 8 | + | } |
|
| 9 | + | ||
| 10 | + | record Container { |
|
| 11 | + | values: [i32; 3], |
|
| 12 | + | count: i32, |
|
| 13 | + | } |
|
| 14 | + | ||
| 15 | + | /// Basic variable aliasing and mutation. |
|
| 16 | + | fn testBasicAliasing() -> i32 { |
|
| 17 | + | let mut x: i32 = 10; |
|
| 18 | + | let mut y: i32 = x; // y aliases x's value |
|
| 19 | + | ||
| 20 | + | x = 20; // Modify x |
|
| 21 | + | y = y + 5; // Modify y independently |
|
| 22 | + | ||
| 23 | + | return x + y; // Should be 20 + 15 = 35 |
|
| 24 | + | } |
|
| 25 | + | ||
| 26 | + | /// Struct aliasing and field mutation. |
|
| 27 | + | fn testStructAliasing() -> i32 { |
|
| 28 | + | let mut p1: Person = Person { age: 25, score: 100 }; |
|
| 29 | + | let mut p2: Person = p1; // Copy record |
|
| 30 | + | ||
| 31 | + | p1.age = 30; // Modify original |
|
| 32 | + | p2.score = 0; // Modify copy |
|
| 33 | + | ||
| 34 | + | return p1.age + p1.score + p2.age + p2.score; // 30+100+25+0 = 155 |
|
| 35 | + | } |
|
| 36 | + | ||
| 37 | + | /// Array aliasing and element mutation. |
|
| 38 | + | fn testArrayAliasing() -> i32 { |
|
| 39 | + | let mut arr1: [i32; 3] = [1, 2, 3]; |
|
| 40 | + | let mut arr2: [i32; 3] = arr1; // Copy array |
|
| 41 | + | arr1[0] = 10; // Modify original |
|
| 42 | + | arr2[1] = 20; // Modify copy |
|
| 43 | + | // Arrays should be independent |
|
| 44 | + | return arr1[0] + arr1[1] + arr2[0] + arr2[1]; // 10+2+1+20 = 33 |
|
| 45 | + | } |
|
| 46 | + | ||
| 47 | + | /// Test 4: Nested record with array mutation. |
|
| 48 | + | fn testNestedStructArrayMutation() -> i32 { |
|
| 49 | + | let mut container1: Container = Container { |
|
| 50 | + | values: [1, 2, 3], |
|
| 51 | + | count: 3 |
|
| 52 | + | }; |
|
| 53 | + | let mut container2: Container = container1; // Copy entire record |
|
| 54 | + | ||
| 55 | + | container1.values[0] = 50; // Modify nested array in original |
|
| 56 | + | container1.count = 5; // Modify field in original |
|
| 57 | + | ||
| 58 | + | container2.values[2] = 60; // Modify nested array in copy |
|
| 59 | + | container2.count = 7; // Modify field in copy |
|
| 60 | + | ||
| 61 | + | return container1.values[0] + container1.count + |
|
| 62 | + | container2.values[2] + container2.count; // 50+5+60+7 = 122 |
|
| 63 | + | } |
|
| 64 | + | ||
| 65 | + | /// Variable overwriting with different types/scopes. |
|
| 66 | + | fn testVariableOverwriting() -> i32 { |
|
| 67 | + | let mut result: i32 = 0; |
|
| 68 | + | let mut temp: i32 = 10; |
|
| 69 | + | result = temp; // First assignment |
|
| 70 | + | ||
| 71 | + | temp = 20; // Overwrite temp |
|
| 72 | + | result = result + temp; // result = 10 + 20 = 30 |
|
| 73 | + | ||
| 74 | + | // Create new scope with same variable name |
|
| 75 | + | if true { |
|
| 76 | + | let mut temp: i32 = 50; // Shadow outer temp |
|
| 77 | + | result = result + temp; // result = 30 + 50 = 80 |
|
| 78 | + | temp = 60; // Modify inner temp |
|
| 79 | + | result = result + temp; // result = 80 + 60 = 140 |
|
| 80 | + | } |
|
| 81 | + | result = result + temp; // Should use outer temp (20) |
|
| 82 | + | ||
| 83 | + | return result; // 140 + 20 = 160 |
|
| 84 | + | } |
|
| 85 | + | ||
| 86 | + | /// Array of structs with aliasing. |
|
| 87 | + | fn testArrayOfStructsAliasing() -> i32 { |
|
| 88 | + | let mut people: [Person; 2] = [ |
|
| 89 | + | Person { age: 10, score: 20 }, |
|
| 90 | + | Person { age: 15, score: 25 } |
|
| 91 | + | ]; |
|
| 92 | + | let mut backup: [Person; 2] = people; // Copy array of structs |
|
| 93 | + | ||
| 94 | + | people[0].age = 12; // Modify original |
|
| 95 | + | people[1].score = 30; // Modify original |
|
| 96 | + | ||
| 97 | + | backup[0].score = 22; // Modify copy |
|
| 98 | + | backup[1].age = 18; // Modify copy |
|
| 99 | + | ||
| 100 | + | return people[0].age + people[0].score + people[1].age + people[1].score + |
|
| 101 | + | backup[0].age + backup[0].score + backup[1].age + backup[1].score; |
|
| 102 | + | } |
|
| 103 | + | ||
| 104 | + | /// Complex mutation chain. |
|
| 105 | + | fn testMutationChain() -> i32 { |
|
| 106 | + | let mut a: i32 = 1; |
|
| 107 | + | let mut b: i32 = 2; |
|
| 108 | + | let mut c: i32 = 3; |
|
| 109 | + | ||
| 110 | + | a = b; // a = 2 |
|
| 111 | + | b = c; // b = 3 |
|
| 112 | + | c = a + b; // c = 2 + 3 = 5 |
|
| 113 | + | a = c - b; // a = 5 - 3 = 2 |
|
| 114 | + | b = a * c; // b = 2 * 5 = 10 |
|
| 115 | + | ||
| 116 | + | return a + b + c; // 2 + 10 + 5 = 17 |
|
| 117 | + | } |
|
| 118 | + | ||
| 119 | + | /// Struct field reassignment with aliasing. |
|
| 120 | + | fn testStructFieldReassignment() -> i32 { |
|
| 121 | + | let mut original: Person = Person { age: 30, score: 40 }; |
|
| 122 | + | let mut copied: Person = original; |
|
| 123 | + | ||
| 124 | + | // Swap fields in original |
|
| 125 | + | let mut temp: i32 = original.age; |
|
| 126 | + | original.age = original.score; |
|
| 127 | + | original.score = temp; |
|
| 128 | + | ||
| 129 | + | // Different swap in copy |
|
| 130 | + | temp = copied.score; |
|
| 131 | + | copied.score = copied.age; |
|
| 132 | + | copied.age = temp; |
|
| 133 | + | ||
| 134 | + | return original.age + original.score + copied.age + copied.score; |
|
| 135 | + | // 40+30 + 40+30 = 140 |
|
| 136 | + | } |
|
| 137 | + | ||
| 138 | + | /// Array index mutation with aliasing. |
|
| 139 | + | fn testArrayIndexMutation() -> i32 { |
|
| 140 | + | let mut indices: [u32; 3] = [0, 1, 2]; |
|
| 141 | + | let mut values: [i32; 3] = [10, 20, 30]; |
|
| 142 | + | let mut backup: [i32; 3] = values; |
|
| 143 | + | ||
| 144 | + | // Use indices to modify values |
|
| 145 | + | values[indices[0]] = 25; // values[0] = 25 |
|
| 146 | + | values[indices[1]] = 35; // values[1] = 35 |
|
| 147 | + | ||
| 148 | + | // Modify indices |
|
| 149 | + | indices[0] = 2; |
|
| 150 | + | indices[2] = 0; |
|
| 151 | + | ||
| 152 | + | // Use new indices on backup |
|
| 153 | + | backup[indices[0]] = 40; // backup[2] = 40 |
|
| 154 | + | backup[indices[2]] = 45; // backup[0] = 45 |
|
| 155 | + | ||
| 156 | + | return values[0] + values[1] + values[2] + |
|
| 157 | + | backup[0] + backup[1] + backup[2]; |
|
| 158 | + | // 25+35+30 + 45+20+40 = 195 |
|
| 159 | + | } |
|
| 160 | + | ||
| 161 | + | /// Multiple assignment levels. |
|
| 162 | + | fn testMultipleAssignmentLevels() -> i32 { |
|
| 163 | + | let mut level1: Person = Person { age: 10, score: 20 }; |
|
| 164 | + | let mut level2: Person = level1; |
|
| 165 | + | let mut level3: Person = level2; |
|
| 166 | + | let mut level4: Person = level3; |
|
| 167 | + | ||
| 168 | + | // Modify each level |
|
| 169 | + | level1.age = 11; |
|
| 170 | + | level2.age = 12; |
|
| 171 | + | level3.age = 13; |
|
| 172 | + | level4.age = 14; |
|
| 173 | + | ||
| 174 | + | level1.score = 21; |
|
| 175 | + | level2.score = 22; |
|
| 176 | + | level3.score = 23; |
|
| 177 | + | level4.score = 24; |
|
| 178 | + | ||
| 179 | + | return (level1.age + level1.score) + |
|
| 180 | + | (level2.age + level2.score) + |
|
| 181 | + | (level3.age + level3.score) + |
|
| 182 | + | (level4.age + level4.score); |
|
| 183 | + | // (11+21) + (12+22) + (13+23) + (14+24) = 32+34+36+38 = 140 |
|
| 184 | + | } |
|
| 185 | + | ||
| 186 | + | @default fn main() -> i32 { |
|
| 187 | + | if (testBasicAliasing() != 35) { |
|
| 188 | + | return 1; // Failure |
|
| 189 | + | } |
|
| 190 | + | if (testStructAliasing() != 155) { |
|
| 191 | + | return 1; // Failure |
|
| 192 | + | } |
|
| 193 | + | if (testArrayAliasing() != 33) { |
|
| 194 | + | return 1; // Failure |
|
| 195 | + | } |
|
| 196 | + | if (testNestedStructArrayMutation() != 122) { |
|
| 197 | + | return 1; // Failure |
|
| 198 | + | } |
|
| 199 | + | if (testVariableOverwriting() != 160) { |
|
| 200 | + | return 1; // Failure |
|
| 201 | + | } |
|
| 202 | + | if (testArrayOfStructsAliasing() != 152) { |
|
| 203 | + | return 1; // Failure |
|
| 204 | + | } |
|
| 205 | + | if (testMutationChain() != 17) { |
|
| 206 | + | return 1; // Failure |
|
| 207 | + | } |
|
| 208 | + | if (testStructFieldReassignment() != 140) { |
|
| 209 | + | return 1; // Failure |
|
| 210 | + | } |
|
| 211 | + | if (testArrayIndexMutation() != 195) { |
|
| 212 | + | return 1; // Failure |
|
| 213 | + | } |
|
| 214 | + | if (testMultipleAssignmentLevels() != 140) { |
|
| 215 | + | return 1; // Failure |
|
| 216 | + | } |
|
| 217 | + | return 0; // Success |
|
| 218 | + | } |
lib/std/arch/rv64/tests/assign.rad
added
+8 -0
| 1 | + | @default fn main() -> i32 { |
|
| 2 | + | let mut i: i32 = 36; |
|
| 3 | + | i = i + 1; // 37 |
|
| 4 | + | i = i + 2; // 39 |
|
| 5 | + | i = i + 3; // 42 |
|
| 6 | + | ||
| 7 | + | return i - 42; |
|
| 8 | + | } |
lib/std/arch/rv64/tests/assign.shadow.mutable.rad
added
+25 -0
| 1 | + | //! returns: 0 |
|
| 2 | + | ||
| 3 | + | fn shadowedMutation() -> i32 { |
|
| 4 | + | let mut result: i32 = 0; |
|
| 5 | + | let mut temp: i32 = 10; |
|
| 6 | + | result = temp; |
|
| 7 | + | ||
| 8 | + | temp = 20; |
|
| 9 | + | result = result + temp; |
|
| 10 | + | ||
| 11 | + | if true { |
|
| 12 | + | let mut temp: i32 = 50; |
|
| 13 | + | result = result + temp; |
|
| 14 | + | temp = 60; |
|
| 15 | + | result = result + temp; |
|
| 16 | + | } |
|
| 17 | + | // Must refer to outer `temp` (20), not inner shadowed `temp` (60). |
|
| 18 | + | result = result + temp; |
|
| 19 | + | ||
| 20 | + | return result; |
|
| 21 | + | } |
|
| 22 | + | ||
| 23 | + | @default fn main() -> i32 { |
|
| 24 | + | return shadowedMutation() - 160; |
|
| 25 | + | } |
lib/std/arch/rv64/tests/binop.bitwise.rad
added
+71 -0
| 1 | + | @default fn main() -> i32 { |
|
| 2 | + | // Test `&` (bitwise AND). |
|
| 3 | + | // `12 = 0b1100`, `10 = 0b1010`, result = `0b1000 = 8`. |
|
| 4 | + | let a: i32 = 12 & 10; |
|
| 5 | + | if a != 8 { |
|
| 6 | + | return 1; |
|
| 7 | + | } |
|
| 8 | + | ||
| 9 | + | // Test `|` (bitwise OR). |
|
| 10 | + | // `12 = 0b1100`, `10 = 0b1010`, result = `0b1110 = 14`. |
|
| 11 | + | let b: i32 = 12 | 10; |
|
| 12 | + | if b != 14 { |
|
| 13 | + | return 2; |
|
| 14 | + | } |
|
| 15 | + | ||
| 16 | + | // Test `^` (bitwise XOR). |
|
| 17 | + | // `12 = 0b1100`, `10 = 0b1010`, result = `0b0110 = 6`. |
|
| 18 | + | let c: i32 = 12 ^ 10; |
|
| 19 | + | if c != 6 { |
|
| 20 | + | return 3; |
|
| 21 | + | } |
|
| 22 | + | ||
| 23 | + | // Test `<<` (shift left). |
|
| 24 | + | // `3 << 4 = 48`. |
|
| 25 | + | let d: i32 = 3 << 4; |
|
| 26 | + | if d != 48 { |
|
| 27 | + | return 4; |
|
| 28 | + | } |
|
| 29 | + | ||
| 30 | + | // Test `>>` (shift right, arithmetic for signed). |
|
| 31 | + | // `48 >> 2 = 12`. |
|
| 32 | + | let e: i32 = 48 >> 2; |
|
| 33 | + | if e != 12 { |
|
| 34 | + | return 5; |
|
| 35 | + | } |
|
| 36 | + | ||
| 37 | + | // Test `>>` with negative (arithmetic shift preserves sign). |
|
| 38 | + | // `-16 >> 2 = -4` (sign bit extended). |
|
| 39 | + | let f: i32 = -16 >> 2; |
|
| 40 | + | if f != -4 { |
|
| 41 | + | return 6; |
|
| 42 | + | } |
|
| 43 | + | ||
| 44 | + | // Test `~` (bitwise NOT). |
|
| 45 | + | // `~0 = -1` (all bits set). |
|
| 46 | + | let g: i32 = ~0; |
|
| 47 | + | if g != -1 { |
|
| 48 | + | return 7; |
|
| 49 | + | } |
|
| 50 | + | ||
| 51 | + | // Test `~` with positive value. |
|
| 52 | + | // `~1 = -2` (in two's complement). |
|
| 53 | + | let h: i32 = ~1; |
|
| 54 | + | if h != -2 { |
|
| 55 | + | return 8; |
|
| 56 | + | } |
|
| 57 | + | ||
| 58 | + | // Combined operations. |
|
| 59 | + | // `(5 | 3) & 6 = 7 & 6 = 6`. |
|
| 60 | + | let i: i32 = (5 | 3) & 6; |
|
| 61 | + | if i != 6 { |
|
| 62 | + | return 9; |
|
| 63 | + | } |
|
| 64 | + | ||
| 65 | + | // XOR self is `0`. |
|
| 66 | + | let j: i32 = 42; |
|
| 67 | + | if (j ^ j) != 0 { |
|
| 68 | + | return 10; |
|
| 69 | + | } |
|
| 70 | + | return 0; |
|
| 71 | + | } |
lib/std/arch/rv64/tests/binop.cmp.rad
added
+60 -0
| 1 | + | @default fn main() -> i32 { |
|
| 2 | + | let a: i32 = 5; |
|
| 3 | + | let b: i32 = 5; |
|
| 4 | + | let c: i32 = 6; |
|
| 5 | + | ||
| 6 | + | if not (a == b) { |
|
| 7 | + | return 1; |
|
| 8 | + | } |
|
| 9 | + | if a == c { |
|
| 10 | + | return 2; |
|
| 11 | + | } |
|
| 12 | + | ||
| 13 | + | if a != b { |
|
| 14 | + | return 3; |
|
| 15 | + | } |
|
| 16 | + | if not (a != c) { |
|
| 17 | + | return 4; |
|
| 18 | + | } |
|
| 19 | + | ||
| 20 | + | if not (a < c) { |
|
| 21 | + | return 5; |
|
| 22 | + | } |
|
| 23 | + | if a < b { |
|
| 24 | + | return 6; |
|
| 25 | + | } |
|
| 26 | + | if c < a { |
|
| 27 | + | return 7; |
|
| 28 | + | } |
|
| 29 | + | ||
| 30 | + | if not (c > a) { |
|
| 31 | + | return 8; |
|
| 32 | + | } |
|
| 33 | + | if a > b { |
|
| 34 | + | return 9; |
|
| 35 | + | } |
|
| 36 | + | if a > c { |
|
| 37 | + | return 10; |
|
| 38 | + | } |
|
| 39 | + | ||
| 40 | + | if not (a <= b) { |
|
| 41 | + | return 11; |
|
| 42 | + | } |
|
| 43 | + | if not (a <= c) { |
|
| 44 | + | return 12; |
|
| 45 | + | } |
|
| 46 | + | if c <= a { |
|
| 47 | + | return 13; |
|
| 48 | + | } |
|
| 49 | + | ||
| 50 | + | if not (a >= b) { |
|
| 51 | + | return 14; |
|
| 52 | + | } |
|
| 53 | + | if not (c >= a) { |
|
| 54 | + | return 15; |
|
| 55 | + | } |
|
| 56 | + | if a >= c { |
|
| 57 | + | return 16; |
|
| 58 | + | } |
|
| 59 | + | return 0; |
|
| 60 | + | } |
lib/std/arch/rv64/tests/bool.comparison.array.rad
added
+36 -0
| 1 | + | //! returns: 1 |
|
| 2 | + | ||
| 3 | + | fn arrayEqual() -> bool { |
|
| 4 | + | let a1: [u32; 3] = [9, 42, 3]; |
|
| 5 | + | let a2: [u32; 3] = [9, 42, 3]; |
|
| 6 | + | ||
| 7 | + | return a1 == a2; |
|
| 8 | + | } |
|
| 9 | + | ||
| 10 | + | fn arrayNotEqual() -> bool { |
|
| 11 | + | let a1: [u32; 3] = [42, 8, 3]; |
|
| 12 | + | let a2: [u32; 3] = [42, 9, 3]; |
|
| 13 | + | ||
| 14 | + | return a1 != a2; |
|
| 15 | + | } |
|
| 16 | + | ||
| 17 | + | fn arrayPointerNotEqual() -> bool { |
|
| 18 | + | // Same array values, but different addresses. |
|
| 19 | + | let a1: [u32; 3] = [9, 42, 3]; |
|
| 20 | + | let a2: [u32; 3] = [9, 42, 3]; |
|
| 21 | + | ||
| 22 | + | return &a1 != &a2; |
|
| 23 | + | } |
|
| 24 | + | ||
| 25 | + | fn arrayPointerEqual() -> bool { |
|
| 26 | + | let a1: [u32; 3] = [9, 42, 3]; |
|
| 27 | + | ||
| 28 | + | return &a1 == &a1; |
|
| 29 | + | } |
|
| 30 | + | ||
| 31 | + | @default fn main() -> bool { |
|
| 32 | + | return arrayEqual() |
|
| 33 | + | and arrayNotEqual() |
|
| 34 | + | and arrayPointerEqual() |
|
| 35 | + | and arrayPointerNotEqual(); |
|
| 36 | + | } |
lib/std/arch/rv64/tests/bool.comparison.nested.gen.rad
added
+60 -0
| 1 | + | //! Test nested record equality. |
|
| 2 | + | ||
| 3 | + | record Point { |
|
| 4 | + | x: i32, |
|
| 5 | + | y: i32, |
|
| 6 | + | } |
|
| 7 | + | ||
| 8 | + | record Circle { |
|
| 9 | + | center: Point, |
|
| 10 | + | radius: u32, |
|
| 11 | + | } |
|
| 12 | + | ||
| 13 | + | fn testNestedEqual() -> bool { |
|
| 14 | + | let c1: Circle = Circle { |
|
| 15 | + | center: Point { x: 10, y: 20 }, |
|
| 16 | + | radius: 5, |
|
| 17 | + | }; |
|
| 18 | + | let c2: Circle = Circle { |
|
| 19 | + | center: Point { x: 10, y: 20 }, |
|
| 20 | + | radius: 5, |
|
| 21 | + | }; |
|
| 22 | + | return c1 == c2; |
|
| 23 | + | } |
|
| 24 | + | ||
| 25 | + | fn testNestedNotEqualCenter() -> bool { |
|
| 26 | + | let c1: Circle = Circle { |
|
| 27 | + | center: Point { x: 10, y: 20 }, |
|
| 28 | + | radius: 5, |
|
| 29 | + | }; |
|
| 30 | + | let c2: Circle = Circle { |
|
| 31 | + | center: Point { x: 15, y: 20 }, |
|
| 32 | + | radius: 5, |
|
| 33 | + | }; |
|
| 34 | + | return c1 != c2; |
|
| 35 | + | } |
|
| 36 | + | ||
| 37 | + | fn testNestedNotEqualRadius() -> bool { |
|
| 38 | + | let c1: Circle = Circle { |
|
| 39 | + | center: Point { x: 10, y: 20 }, |
|
| 40 | + | radius: 5, |
|
| 41 | + | }; |
|
| 42 | + | let c2: Circle = Circle { |
|
| 43 | + | center: Point { x: 10, y: 20 }, |
|
| 44 | + | radius: 10, |
|
| 45 | + | }; |
|
| 46 | + | return c1 != c2; |
|
| 47 | + | } |
|
| 48 | + | ||
| 49 | + | @default fn main() -> i32 { |
|
| 50 | + | if not testNestedEqual() { |
|
| 51 | + | return 1; |
|
| 52 | + | } |
|
| 53 | + | if not testNestedNotEqualCenter() { |
|
| 54 | + | return 2; |
|
| 55 | + | } |
|
| 56 | + | if not testNestedNotEqualRadius() { |
|
| 57 | + | return 3; |
|
| 58 | + | } |
|
| 59 | + | return 0; |
|
| 60 | + | } |
lib/std/arch/rv64/tests/bool.comparison.opt.rad
added
+52 -0
| 1 | + | //! returns: 1 |
|
| 2 | + | ||
| 3 | + | fn testNilComparisons() -> bool { |
|
| 4 | + | let opt1: ?i32 = 42; |
|
| 5 | + | let opt2: ?u8 = 9; |
|
| 6 | + | let opt3: ?bool = nil; |
|
| 7 | + | ||
| 8 | + | return |
|
| 9 | + | nil != opt1 and |
|
| 10 | + | opt1 != nil and |
|
| 11 | + | nil != opt2 and |
|
| 12 | + | opt2 != nil and |
|
| 13 | + | nil == opt3 and |
|
| 14 | + | opt3 == nil; |
|
| 15 | + | } |
|
| 16 | + | ||
| 17 | + | fn testSelfComparisons() -> bool { |
|
| 18 | + | let opt1: ?i32 = 42; |
|
| 19 | + | let opt2: ?u8 = 9; |
|
| 20 | + | ||
| 21 | + | return |
|
| 22 | + | opt1 == opt1 and |
|
| 23 | + | opt2 == opt2; |
|
| 24 | + | } |
|
| 25 | + | ||
| 26 | + | fn testOptEqValue() -> bool { |
|
| 27 | + | let opt1: ?i32 = 42; |
|
| 28 | + | let opt2: ?u8 = 9; |
|
| 29 | + | ||
| 30 | + | return |
|
| 31 | + | opt1 == 42 and |
|
| 32 | + | opt2 == 9 and |
|
| 33 | + | opt1 != 9 and |
|
| 34 | + | opt2 != 42; |
|
| 35 | + | } |
|
| 36 | + | ||
| 37 | + | fn testValueEqOpt() -> bool { |
|
| 38 | + | let opt1: ?i32 = 42; |
|
| 39 | + | let opt2: ?u8 = 9; |
|
| 40 | + | ||
| 41 | + | return |
|
| 42 | + | 42 == opt1 and |
|
| 43 | + | 9 == opt2; |
|
| 44 | + | } |
|
| 45 | + | ||
| 46 | + | @default fn main() -> bool { |
|
| 47 | + | return |
|
| 48 | + | testNilComparisons() and |
|
| 49 | + | testSelfComparisons() and |
|
| 50 | + | testOptEqValue() and |
|
| 51 | + | testValueEqOpt(); |
|
| 52 | + | } |
lib/std/arch/rv64/tests/bool.comparison.record.gen.rad
added
+64 -0
| 1 | + | ||
| 2 | + | record Point { |
|
| 3 | + | x: i32, |
|
| 4 | + | y: i32, |
|
| 5 | + | } |
|
| 6 | + | ||
| 7 | + | record Rect { |
|
| 8 | + | width: u32, |
|
| 9 | + | height: u32, |
|
| 10 | + | } |
|
| 11 | + | ||
| 12 | + | fn testPointEqual() -> bool { |
|
| 13 | + | let p1: Point = Point { x: 10, y: 20 }; |
|
| 14 | + | let p2: Point = Point { x: 10, y: 20 }; |
|
| 15 | + | ||
| 16 | + | return p1 == p2; |
|
| 17 | + | } |
|
| 18 | + | ||
| 19 | + | fn testPointNotEqualX() -> bool { |
|
| 20 | + | let p1: Point = Point { x: 10, y: 20 }; |
|
| 21 | + | let p2: Point = Point { x: 15, y: 20 }; |
|
| 22 | + | ||
| 23 | + | return p1 != p2; |
|
| 24 | + | } |
|
| 25 | + | ||
| 26 | + | fn testPointNotEqualY() -> bool { |
|
| 27 | + | let p1: Point = Point { x: 10, y: 20 }; |
|
| 28 | + | let p2: Point = Point { x: 10, y: 25 }; |
|
| 29 | + | ||
| 30 | + | return p1 != p2; |
|
| 31 | + | } |
|
| 32 | + | ||
| 33 | + | fn testRectEqual() -> bool { |
|
| 34 | + | let r1: Rect = Rect { width: 100, height: 50 }; |
|
| 35 | + | let r2: Rect = Rect { width: 100, height: 50 }; |
|
| 36 | + | ||
| 37 | + | return r1 == r2; |
|
| 38 | + | } |
|
| 39 | + | ||
| 40 | + | fn testRectNotEqual() -> bool { |
|
| 41 | + | let r1: Rect = Rect { width: 100, height: 50 }; |
|
| 42 | + | let r2: Rect = Rect { width: 100, height: 60 }; |
|
| 43 | + | ||
| 44 | + | return r1 != r2; |
|
| 45 | + | } |
|
| 46 | + | ||
| 47 | + | @default fn main() -> i32 { |
|
| 48 | + | if not testPointEqual() { |
|
| 49 | + | return 1; |
|
| 50 | + | } |
|
| 51 | + | if not testPointNotEqualX() { |
|
| 52 | + | return 2; |
|
| 53 | + | } |
|
| 54 | + | if not testPointNotEqualY() { |
|
| 55 | + | return 3; |
|
| 56 | + | } |
|
| 57 | + | if not testRectEqual() { |
|
| 58 | + | return 4; |
|
| 59 | + | } |
|
| 60 | + | if not testRectNotEqual() { |
|
| 61 | + | return 5; |
|
| 62 | + | } |
|
| 63 | + | return 0; |
|
| 64 | + | } |
lib/std/arch/rv64/tests/bool.comparison.record.rad
added
+51 -0
| 1 | + | //! returns: 1 |
|
| 2 | + | ||
| 3 | + | // A packed record. 8 bytes. |
|
| 4 | + | record Packed { |
|
| 5 | + | x: i32, |
|
| 6 | + | y: i32 |
|
| 7 | + | } |
|
| 8 | + | ||
| 9 | + | // A record with padding. 17 bytes. |
|
| 10 | + | record Padded { |
|
| 11 | + | a: u8, |
|
| 12 | + | b: u32, |
|
| 13 | + | c: u16, |
|
| 14 | + | d: u32, |
|
| 15 | + | e: u8, |
|
| 16 | + | } |
|
| 17 | + | ||
| 18 | + | fn packedStructEqual() -> bool { |
|
| 19 | + | let mut s1: Packed = Packed { x: 39, y: 189 }; |
|
| 20 | + | let mut s2: Packed = Packed { x: 39, y: 189 }; |
|
| 21 | + | ||
| 22 | + | return s1 == s2; |
|
| 23 | + | } |
|
| 24 | + | ||
| 25 | + | fn packedStructNotEqual() -> bool { |
|
| 26 | + | let mut s1: Packed = Packed { x: 39, y: 189 }; |
|
| 27 | + | let mut s2: Packed = Packed { x: 39, y: 289 }; |
|
| 28 | + | ||
| 29 | + | return s1 != s2; |
|
| 30 | + | } |
|
| 31 | + | ||
| 32 | + | fn paddedStructEqual() -> bool { |
|
| 33 | + | let mut s1: Padded = Padded { a: 48, b: 183, c: 493, d: 8192, e: 99 }; |
|
| 34 | + | let mut s2: Padded = Padded { a: 48, b: 183, c: 493, d: 8192, e: 99 }; |
|
| 35 | + | ||
| 36 | + | return s1 == s2; |
|
| 37 | + | } |
|
| 38 | + | ||
| 39 | + | fn paddedStructNotEqual() -> bool { |
|
| 40 | + | let mut s1: Padded = Padded { a: 48, b: 183, c: 493, d: 8192, e: 99 }; |
|
| 41 | + | let mut s2: Padded = Padded { a: 48, b: 283, c: 493, d: 8192, e: 99 }; |
|
| 42 | + | ||
| 43 | + | return s1 != s2; |
|
| 44 | + | } |
|
| 45 | + | ||
| 46 | + | @default fn main() -> bool { |
|
| 47 | + | return packedStructEqual() |
|
| 48 | + | and packedStructNotEqual() |
|
| 49 | + | and paddedStructEqual() |
|
| 50 | + | and paddedStructNotEqual(); |
|
| 51 | + | } |
lib/std/arch/rv64/tests/bool.comparison.slice.gen.rad
added
+8 -0
| 1 | + | //! returns: 1 |
|
| 2 | + | //! Test generated slice equality comparison. |
|
| 3 | + | ||
| 4 | + | @default fn main() -> bool { |
|
| 5 | + | let a1: [u32; 2] = [42, 3]; |
|
| 6 | + | ||
| 7 | + | return &a1[..] == &a1[..]; |
|
| 8 | + | } |
lib/std/arch/rv64/tests/bool.comparison.slice.rad
added
+203 -0
| 1 | + | fn assert(cond: bool) { |
|
| 2 | + | if not cond { |
|
| 3 | + | panic; |
|
| 4 | + | } |
|
| 5 | + | } |
|
| 6 | + | ||
| 7 | + | fn memEq(a: *[u8], b: *[u8]) -> bool { |
|
| 8 | + | if a.len != b.len { |
|
| 9 | + | return false; |
|
| 10 | + | } |
|
| 11 | + | for i in 0..a.len { |
|
| 12 | + | if a[i] != b[i] { |
|
| 13 | + | return false; |
|
| 14 | + | } |
|
| 15 | + | } |
|
| 16 | + | return true; |
|
| 17 | + | } |
|
| 18 | + | ||
| 19 | + | fn sliceU8(input: *[u8]) -> *[u8] { |
|
| 20 | + | return input; |
|
| 21 | + | } |
|
| 22 | + | ||
| 23 | + | fn sliceI32(input: *[i32]) -> *[i32] { |
|
| 24 | + | return input; |
|
| 25 | + | } |
|
| 26 | + | ||
| 27 | + | fn sliceEqualU32(a: *[u32], b: *[u32]) -> bool { |
|
| 28 | + | if a.len != b.len { |
|
| 29 | + | return false; |
|
| 30 | + | } |
|
| 31 | + | for i in 0..a.len { |
|
| 32 | + | if a[i] != b[i] { |
|
| 33 | + | return false; |
|
| 34 | + | } |
|
| 35 | + | } |
|
| 36 | + | return true; |
|
| 37 | + | } |
|
| 38 | + | ||
| 39 | + | fn sliceEqualI32(a: *[i32], b: *[i32]) -> bool { |
|
| 40 | + | if a.len != b.len { |
|
| 41 | + | return false; |
|
| 42 | + | } |
|
| 43 | + | for i in 0..a.len { |
|
| 44 | + | if a[i] != b[i] { |
|
| 45 | + | return false; |
|
| 46 | + | } |
|
| 47 | + | } |
|
| 48 | + | return true; |
|
| 49 | + | } |
|
| 50 | + | ||
| 51 | + | fn sliceEqual1() -> bool { |
|
| 52 | + | let a1: [u32; 3] = [9, 42, 3]; |
|
| 53 | + | ||
| 54 | + | let s1: *[u32] = &a1[..]; |
|
| 55 | + | let s2: *[u32] = &a1[..]; |
|
| 56 | + | ||
| 57 | + | return s1 == s2 and sliceEqualU32(s1, s2); |
|
| 58 | + | } |
|
| 59 | + | ||
| 60 | + | fn sliceEqual2() -> bool { |
|
| 61 | + | let a1: [u32; 3] = [9, 42, 3]; |
|
| 62 | + | ||
| 63 | + | let s1: *[u32] = &a1[1..]; |
|
| 64 | + | let s2: *[u32] = &a1[1..]; |
|
| 65 | + | ||
| 66 | + | return s1 == s2 and sliceEqualU32(s1, s2); |
|
| 67 | + | } |
|
| 68 | + | ||
| 69 | + | fn sliceEqual3() -> bool { |
|
| 70 | + | let a1: [u32; 3] = [9, 42, 3]; |
|
| 71 | + | ||
| 72 | + | let s1: *[u32] = &a1[..1]; |
|
| 73 | + | let s2: *[u32] = &a1[..1]; |
|
| 74 | + | ||
| 75 | + | return s1 == s2 and sliceEqualU32(s1, s2); |
|
| 76 | + | } |
|
| 77 | + | ||
| 78 | + | fn sliceEqual4() -> bool { |
|
| 79 | + | let a1: [u32; 3] = [9, 42, 3]; |
|
| 80 | + | ||
| 81 | + | let s1: *[u32] = &a1[2..]; |
|
| 82 | + | let s2: *[u32] = &a1[2..]; |
|
| 83 | + | ||
| 84 | + | return s1 == s2 and sliceEqualU32(s1, s2); |
|
| 85 | + | } |
|
| 86 | + | ||
| 87 | + | fn sliceNotEqualSameArray1() -> bool { |
|
| 88 | + | let a1: [u32; 3] = [42, 8, 3]; |
|
| 89 | + | ||
| 90 | + | let s1: *[u32] = &a1[..2]; |
|
| 91 | + | let s2: *[u32] = &a1[1..3]; |
|
| 92 | + | ||
| 93 | + | return s1 != s2 and not sliceEqualU32(s1, s2); |
|
| 94 | + | } |
|
| 95 | + | ||
| 96 | + | fn sliceNotEqualSameArray2() -> bool { |
|
| 97 | + | let a1: [u32; 3] = [42, 8, 3]; |
|
| 98 | + | ||
| 99 | + | let s1: *[u32] = &a1[..2]; |
|
| 100 | + | let s2: *[u32] = &a1[..3]; |
|
| 101 | + | ||
| 102 | + | return s1 != s2 and not sliceEqualU32(s1, s2); |
|
| 103 | + | } |
|
| 104 | + | ||
| 105 | + | fn sliceEqualDifferentArray() -> bool { |
|
| 106 | + | let a1: [u32; 3] = [42, 8, 3]; |
|
| 107 | + | let a2: [u32; 3] = [42, 8, 3]; |
|
| 108 | + | ||
| 109 | + | let s1: *[u32] = &a1[..]; |
|
| 110 | + | let s2: *[u32] = &a2[..]; |
|
| 111 | + | ||
| 112 | + | return sliceEqualU32(s1, s2) and s1 != s2; |
|
| 113 | + | } |
|
| 114 | + | ||
| 115 | + | fn sliceEqualString1() -> bool { |
|
| 116 | + | let s1: *[u8] = "ABC"; |
|
| 117 | + | let s2: *[u8] = "ABC"; |
|
| 118 | + | ||
| 119 | + | return memEq(s1, s2); |
|
| 120 | + | } |
|
| 121 | + | ||
| 122 | + | fn sliceEqualString2() -> bool { |
|
| 123 | + | let s1: *[u8] = "ABC"; |
|
| 124 | + | let s2: *[u8] = "DEF"; |
|
| 125 | + | ||
| 126 | + | return not memEq(s1, s2); |
|
| 127 | + | } |
|
| 128 | + | ||
| 129 | + | fn sliceEqualString3() -> bool { |
|
| 130 | + | let a1: [u8; 3] = ['A', 'B', 'C']; |
|
| 131 | + | let s1: *[u8] = &a1[..]; |
|
| 132 | + | let s2: *[u8] = "ABC"; |
|
| 133 | + | ||
| 134 | + | return memEq(s1, s2) |
|
| 135 | + | and memEq(s1, "ABC") |
|
| 136 | + | and memEq(&a1[..], "ABC"); |
|
| 137 | + | } |
|
| 138 | + | ||
| 139 | + | fn sliceEqualString4() -> bool { |
|
| 140 | + | let s1: *[u8] = "ABC"; |
|
| 141 | + | let s2: *[u8] = &['A', 'B', 'C']; |
|
| 142 | + | ||
| 143 | + | return memEq(s1, s2); |
|
| 144 | + | } |
|
| 145 | + | ||
| 146 | + | fn sliceEqualString5() -> bool { |
|
| 147 | + | let s1: *[u8] = "ABC"; |
|
| 148 | + | ||
| 149 | + | // Sub-slicing a slice currently trips a separate compiler bug. |
|
| 150 | + | // Keep this check focused on slice equality behavior itself. |
|
| 151 | + | return memEq(s1, "ABC"); |
|
| 152 | + | } |
|
| 153 | + | ||
| 154 | + | fn sliceNotEqualU8() -> bool { |
|
| 155 | + | let a: [u8; 3] = [1, 2, 3]; |
|
| 156 | + | let b: [u8; 3] = [1, 2, 4]; |
|
| 157 | + | let s: *[u8] = &[1, 2, 3]; |
|
| 158 | + | ||
| 159 | + | return not memEq(s, &[1, 2, 4]) |
|
| 160 | + | and not memEq(s, &[1, 0, 3]) |
|
| 161 | + | and not memEq("ABC", "ABCD") |
|
| 162 | + | and not memEq("ABC", "ABD") |
|
| 163 | + | and not memEq(&a[..], &a[1..]) |
|
| 164 | + | and not memEq(&a[..], &b[..]) |
|
| 165 | + | and not memEq(&a[..], &[1, 3, 3]); |
|
| 166 | + | } |
|
| 167 | + | ||
| 168 | + | fn sliceNotEqualU16() -> bool { |
|
| 169 | + | let a: [u16; 3] = [1, 2, 3]; |
|
| 170 | + | let b: [u16; 3] = [1, 2, 4]; |
|
| 171 | + | let s: *[u16] = &[1, 2, 3]; |
|
| 172 | + | ||
| 173 | + | return s != &[1, 2, 4] |
|
| 174 | + | and s != &[1, 0, 3] |
|
| 175 | + | and &a[..] != &a[1..] |
|
| 176 | + | and &a[..] != &b[..] |
|
| 177 | + | and &a[..] != &[1, 3, 3]; |
|
| 178 | + | } |
|
| 179 | + | ||
| 180 | + | fn sliceReturnEqual() -> bool { |
|
| 181 | + | return memEq(sliceU8("ABC"), "ABC") |
|
| 182 | + | and sliceEqualI32(sliceI32(&[1, 2, 3]), &[1, 2, 3]); |
|
| 183 | + | } |
|
| 184 | + | ||
| 185 | + | @default fn main() -> i32 { |
|
| 186 | + | assert(sliceEqual1()); |
|
| 187 | + | assert(sliceEqual2()); |
|
| 188 | + | assert(sliceEqual3()); |
|
| 189 | + | assert(sliceEqual4()); |
|
| 190 | + | assert(sliceEqualString1()); |
|
| 191 | + | assert(sliceEqualString2()); |
|
| 192 | + | assert(sliceEqualString3()); |
|
| 193 | + | assert(sliceEqualString4()); |
|
| 194 | + | assert(sliceEqualString5()); |
|
| 195 | + | assert(sliceNotEqualSameArray1()); |
|
| 196 | + | assert(sliceNotEqualSameArray2()); |
|
| 197 | + | assert(sliceEqualDifferentArray()); |
|
| 198 | + | assert(sliceNotEqualU8()); |
|
| 199 | + | assert(sliceNotEqualU16()); |
|
| 200 | + | assert(sliceReturnEqual()); |
|
| 201 | + | ||
| 202 | + | return 0; |
|
| 203 | + | } |
lib/std/arch/rv64/tests/bool.comparison.slice.record.gen.rad
added
+86 -0
| 1 | + | //! returns: 0 |
|
| 2 | + | ||
| 3 | + | record Point { |
|
| 4 | + | x: i32, |
|
| 5 | + | y: i32, |
|
| 6 | + | } |
|
| 7 | + | ||
| 8 | + | record Line { |
|
| 9 | + | points: *[Point], |
|
| 10 | + | } |
|
| 11 | + | ||
| 12 | + | fn pointsEqual(a: *[Point], b: *[Point]) -> bool { |
|
| 13 | + | if a.len != b.len { |
|
| 14 | + | return false; |
|
| 15 | + | } |
|
| 16 | + | for i in 0..a.len { |
|
| 17 | + | if a[i].x != b[i].x or a[i].y != b[i].y { |
|
| 18 | + | return false; |
|
| 19 | + | } |
|
| 20 | + | } |
|
| 21 | + | return true; |
|
| 22 | + | } |
|
| 23 | + | ||
| 24 | + | fn lineEqual(a: Line, b: Line) -> bool { |
|
| 25 | + | return pointsEqual(a.points, b.points); |
|
| 26 | + | } |
|
| 27 | + | ||
| 28 | + | fn testSliceStructEqual() -> bool { |
|
| 29 | + | let a: [Point; 2] = [Point { x: 1, y: 2 }, Point { x: 3, y: 4 }]; |
|
| 30 | + | let b: [Point; 2] = [Point { x: 1, y: 2 }, Point { x: 3, y: 4 }]; |
|
| 31 | + | ||
| 32 | + | return pointsEqual(&a[..], &b[..]); |
|
| 33 | + | } |
|
| 34 | + | ||
| 35 | + | fn testSliceStructNotEqualContent() -> bool { |
|
| 36 | + | let a: [Point; 2] = [Point { x: 1, y: 2 }, Point { x: 3, y: 4 }]; |
|
| 37 | + | let b: [Point; 2] = [Point { x: 1, y: 2 }, Point { x: 3, y: 5 }]; |
|
| 38 | + | ||
| 39 | + | return not pointsEqual(&a[..], &b[..]); |
|
| 40 | + | } |
|
| 41 | + | ||
| 42 | + | fn testSliceStructNotEqualLength() -> bool { |
|
| 43 | + | let a: [Point; 2] = [Point { x: 1, y: 2 }, Point { x: 3, y: 4 }]; |
|
| 44 | + | let b: [Point; 1] = [Point { x: 1, y: 2 }]; |
|
| 45 | + | ||
| 46 | + | return not pointsEqual(&a[..], &b[..]); |
|
| 47 | + | } |
|
| 48 | + | ||
| 49 | + | fn testStructWithSliceFieldEqual() -> bool { |
|
| 50 | + | let pts1: [Point; 2] = [Point { x: 1, y: 2 }, Point { x: 3, y: 4 }]; |
|
| 51 | + | let pts2: [Point; 2] = [Point { x: 1, y: 2 }, Point { x: 3, y: 4 }]; |
|
| 52 | + | ||
| 53 | + | let line1: Line = Line { points: &pts1[..] }; |
|
| 54 | + | let line2: Line = Line { points: &pts2[..] }; |
|
| 55 | + | ||
| 56 | + | return lineEqual(line1, line2); |
|
| 57 | + | } |
|
| 58 | + | ||
| 59 | + | fn testStructWithSliceFieldNotEqual() -> bool { |
|
| 60 | + | let pts1: [Point; 2] = [Point { x: 1, y: 2 }, Point { x: 3, y: 4 }]; |
|
| 61 | + | let pts2: [Point; 2] = [Point { x: 1, y: 2 }, Point { x: 3, y: 5 }]; |
|
| 62 | + | ||
| 63 | + | let line1: Line = Line { points: &pts1[..] }; |
|
| 64 | + | let line2: Line = Line { points: &pts2[..] }; |
|
| 65 | + | ||
| 66 | + | return not lineEqual(line1, line2); |
|
| 67 | + | } |
|
| 68 | + | ||
| 69 | + | @default fn main() -> i32 { |
|
| 70 | + | if not testSliceStructEqual() { |
|
| 71 | + | return 1; |
|
| 72 | + | } |
|
| 73 | + | if not testSliceStructNotEqualContent() { |
|
| 74 | + | return 2; |
|
| 75 | + | } |
|
| 76 | + | if not testSliceStructNotEqualLength() { |
|
| 77 | + | return 3; |
|
| 78 | + | } |
|
| 79 | + | if not testStructWithSliceFieldEqual() { |
|
| 80 | + | return 4; |
|
| 81 | + | } |
|
| 82 | + | if not testStructWithSliceFieldNotEqual() { |
|
| 83 | + | return 5; |
|
| 84 | + | } |
|
| 85 | + | return 0; |
|
| 86 | + | } |
lib/std/arch/rv64/tests/bool.comparison.slice.union.gen.rad
added
+112 -0
| 1 | + | //! returns: 0 |
|
| 2 | + | ||
| 3 | + | record Point { |
|
| 4 | + | x: i32, |
|
| 5 | + | y: i32, |
|
| 6 | + | } |
|
| 7 | + | ||
| 8 | + | record Size { |
|
| 9 | + | width: u32, |
|
| 10 | + | height: u32, |
|
| 11 | + | } |
|
| 12 | + | ||
| 13 | + | union Shape { |
|
| 14 | + | points(*[Point]), |
|
| 15 | + | sizes(*[Size]), |
|
| 16 | + | } |
|
| 17 | + | ||
| 18 | + | fn pointsEqual(a: *[Point], b: *[Point]) -> bool { |
|
| 19 | + | if a.len != b.len { |
|
| 20 | + | return false; |
|
| 21 | + | } |
|
| 22 | + | for i in 0..a.len { |
|
| 23 | + | if a[i].x != b[i].x or a[i].y != b[i].y { |
|
| 24 | + | return false; |
|
| 25 | + | } |
|
| 26 | + | } |
|
| 27 | + | return true; |
|
| 28 | + | } |
|
| 29 | + | ||
| 30 | + | fn sizesEqual(a: *[Size], b: *[Size]) -> bool { |
|
| 31 | + | if a.len != b.len { |
|
| 32 | + | return false; |
|
| 33 | + | } |
|
| 34 | + | for i in 0..a.len { |
|
| 35 | + | if a[i].width != b[i].width or a[i].height != b[i].height { |
|
| 36 | + | return false; |
|
| 37 | + | } |
|
| 38 | + | } |
|
| 39 | + | return true; |
|
| 40 | + | } |
|
| 41 | + | ||
| 42 | + | fn shapeEqual(a: Shape, b: Shape) -> bool { |
|
| 43 | + | if let case Shape::points(pointsA) = a { |
|
| 44 | + | if let case Shape::points(pointsB) = b { |
|
| 45 | + | return pointsEqual(pointsA, pointsB); |
|
| 46 | + | } |
|
| 47 | + | return false; |
|
| 48 | + | } |
|
| 49 | + | if let case Shape::sizes(sizesA) = a { |
|
| 50 | + | if let case Shape::sizes(sizesB) = b { |
|
| 51 | + | return sizesEqual(sizesA, sizesB); |
|
| 52 | + | } |
|
| 53 | + | return false; |
|
| 54 | + | } |
|
| 55 | + | return false; |
|
| 56 | + | } |
|
| 57 | + | ||
| 58 | + | fn testEnumSliceEqual() -> bool { |
|
| 59 | + | let pts1: [Point; 2] = [Point { x: 1, y: 2 }, Point { x: 3, y: 4 }]; |
|
| 60 | + | let pts2: [Point; 2] = [Point { x: 1, y: 2 }, Point { x: 3, y: 4 }]; |
|
| 61 | + | ||
| 62 | + | let s1: Shape = Shape::points(&pts1[..]); |
|
| 63 | + | let s2: Shape = Shape::points(&pts2[..]); |
|
| 64 | + | ||
| 65 | + | return shapeEqual(s1, s2); |
|
| 66 | + | } |
|
| 67 | + | ||
| 68 | + | fn testEnumSliceNotEqual() -> bool { |
|
| 69 | + | let pts1: [Point; 2] = [Point { x: 1, y: 2 }, Point { x: 3, y: 4 }]; |
|
| 70 | + | let pts2: [Point; 2] = [Point { x: 1, y: 2 }, Point { x: 3, y: 5 }]; |
|
| 71 | + | ||
| 72 | + | let s1: Shape = Shape::points(&pts1[..]); |
|
| 73 | + | let s2: Shape = Shape::points(&pts2[..]); |
|
| 74 | + | ||
| 75 | + | return not shapeEqual(s1, s2); |
|
| 76 | + | } |
|
| 77 | + | ||
| 78 | + | fn testEnumSizesSliceEqual() -> bool { |
|
| 79 | + | let sizes1: [Size; 2] = [Size { width: 10, height: 20 }, Size { width: 30, height: 40 }]; |
|
| 80 | + | let sizes2: [Size; 2] = [Size { width: 10, height: 20 }, Size { width: 30, height: 40 }]; |
|
| 81 | + | ||
| 82 | + | let s1: Shape = Shape::sizes(&sizes1[..]); |
|
| 83 | + | let s2: Shape = Shape::sizes(&sizes2[..]); |
|
| 84 | + | ||
| 85 | + | return shapeEqual(s1, s2); |
|
| 86 | + | } |
|
| 87 | + | ||
| 88 | + | fn testEnumDifferentVariants() -> bool { |
|
| 89 | + | let pts: [Point; 2] = [Point { x: 1, y: 2 }, Point { x: 3, y: 4 }]; |
|
| 90 | + | let sizes: [Size; 2] = [Size { width: 10, height: 20 }, Size { width: 30, height: 40 }]; |
|
| 91 | + | ||
| 92 | + | let s1: Shape = Shape::points(&pts[..]); |
|
| 93 | + | let s2: Shape = Shape::sizes(&sizes[..]); |
|
| 94 | + | ||
| 95 | + | return not shapeEqual(s1, s2); |
|
| 96 | + | } |
|
| 97 | + | ||
| 98 | + | @default fn main() -> i32 { |
|
| 99 | + | if not testEnumSliceEqual() { |
|
| 100 | + | return 1; |
|
| 101 | + | } |
|
| 102 | + | if not testEnumSliceNotEqual() { |
|
| 103 | + | return 2; |
|
| 104 | + | } |
|
| 105 | + | if not testEnumSizesSliceEqual() { |
|
| 106 | + | return 3; |
|
| 107 | + | } |
|
| 108 | + | if not testEnumDifferentVariants() { |
|
| 109 | + | return 4; |
|
| 110 | + | } |
|
| 111 | + | return 0; |
|
| 112 | + | } |
lib/std/arch/rv64/tests/bool.comparison.union.ctor.rad
added
+51 -0
| 1 | + | //! Test comparing a void union variable against a constructor literal. |
|
| 2 | + | ||
| 3 | + | union Op { |
|
| 4 | + | Add, |
|
| 5 | + | Sub, |
|
| 6 | + | Mul, |
|
| 7 | + | } |
|
| 8 | + | ||
| 9 | + | fn isAdd(op: Op) -> bool { |
|
| 10 | + | return op == Op::Add; |
|
| 11 | + | } |
|
| 12 | + | ||
| 13 | + | fn isSub(op: Op) -> bool { |
|
| 14 | + | return op == Op::Sub; |
|
| 15 | + | } |
|
| 16 | + | ||
| 17 | + | fn isMul(op: Op) -> bool { |
|
| 18 | + | return op == Op::Mul; |
|
| 19 | + | } |
|
| 20 | + | ||
| 21 | + | @default fn main() -> i32 { |
|
| 22 | + | let a: Op = Op::Add; |
|
| 23 | + | let s: Op = Op::Sub; |
|
| 24 | + | let m: Op = Op::Mul; |
|
| 25 | + | ||
| 26 | + | // isAdd tests |
|
| 27 | + | if not isAdd(a) { |
|
| 28 | + | return 1; |
|
| 29 | + | } |
|
| 30 | + | if isAdd(s) { |
|
| 31 | + | return 2; |
|
| 32 | + | } |
|
| 33 | + | if isAdd(m) { |
|
| 34 | + | return 3; |
|
| 35 | + | } |
|
| 36 | + | // isSub tests |
|
| 37 | + | if not isSub(s) { |
|
| 38 | + | return 4; |
|
| 39 | + | } |
|
| 40 | + | if isSub(a) { |
|
| 41 | + | return 5; |
|
| 42 | + | } |
|
| 43 | + | // isMul tests |
|
| 44 | + | if not isMul(m) { |
|
| 45 | + | return 6; |
|
| 46 | + | } |
|
| 47 | + | if isMul(a) { |
|
| 48 | + | return 7; |
|
| 49 | + | } |
|
| 50 | + | return 0; |
|
| 51 | + | } |
lib/std/arch/rv64/tests/bool.comparison.union.gen.rad
added
+70 -0
| 1 | + | ||
| 2 | + | union Status { |
|
| 3 | + | pending, |
|
| 4 | + | running(u32), |
|
| 5 | + | completed(bool), |
|
| 6 | + | } |
|
| 7 | + | ||
| 8 | + | fn testSameVariantNoPayload() -> bool { |
|
| 9 | + | let a: Status = Status::pending; |
|
| 10 | + | let b: Status = Status::pending; |
|
| 11 | + | ||
| 12 | + | return a == b; |
|
| 13 | + | } |
|
| 14 | + | ||
| 15 | + | fn testDifferentVariants() -> bool { |
|
| 16 | + | let a: Status = Status::pending; |
|
| 17 | + | let b: Status = Status::running(42); |
|
| 18 | + | ||
| 19 | + | return a != b; |
|
| 20 | + | } |
|
| 21 | + | ||
| 22 | + | fn testSameVariantSamePayload() -> bool { |
|
| 23 | + | let a: Status = Status::running(42); |
|
| 24 | + | let b: Status = Status::running(42); |
|
| 25 | + | ||
| 26 | + | return a == b; |
|
| 27 | + | } |
|
| 28 | + | ||
| 29 | + | fn testSameVariantDifferentPayload() -> bool { |
|
| 30 | + | let a: Status = Status::running(42); |
|
| 31 | + | let b: Status = Status::running(99); |
|
| 32 | + | ||
| 33 | + | return a != b; |
|
| 34 | + | } |
|
| 35 | + | ||
| 36 | + | fn testBoolPayloadSame() -> bool { |
|
| 37 | + | let a: Status = Status::completed(true); |
|
| 38 | + | let b: Status = Status::completed(true); |
|
| 39 | + | ||
| 40 | + | return a == b; |
|
| 41 | + | } |
|
| 42 | + | ||
| 43 | + | fn testBoolPayloadDifferent() -> bool { |
|
| 44 | + | let a: Status = Status::completed(true); |
|
| 45 | + | let b: Status = Status::completed(false); |
|
| 46 | + | ||
| 47 | + | return a != b; |
|
| 48 | + | } |
|
| 49 | + | ||
| 50 | + | @default fn main() -> i32 { |
|
| 51 | + | if not testSameVariantNoPayload() { |
|
| 52 | + | return 1; |
|
| 53 | + | } |
|
| 54 | + | if not testDifferentVariants() { |
|
| 55 | + | return 2; |
|
| 56 | + | } |
|
| 57 | + | if not testSameVariantSamePayload() { |
|
| 58 | + | return 3; |
|
| 59 | + | } |
|
| 60 | + | if not testSameVariantDifferentPayload() { |
|
| 61 | + | return 4; |
|
| 62 | + | } |
|
| 63 | + | if not testBoolPayloadSame() { |
|
| 64 | + | return 5; |
|
| 65 | + | } |
|
| 66 | + | if not testBoolPayloadDifferent() { |
|
| 67 | + | return 6; |
|
| 68 | + | } |
|
| 69 | + | return 0; |
|
| 70 | + | } |
lib/std/arch/rv64/tests/bool.comparison.union.record.gen.rad
added
+75 -0
| 1 | + | //! returns: 0 |
|
| 2 | + | ||
| 3 | + | record Point { |
|
| 4 | + | x: i32, |
|
| 5 | + | y: i32, |
|
| 6 | + | } |
|
| 7 | + | ||
| 8 | + | record Size { |
|
| 9 | + | width: u32, |
|
| 10 | + | height: u32, |
|
| 11 | + | } |
|
| 12 | + | ||
| 13 | + | union Shape { |
|
| 14 | + | circle(u32), |
|
| 15 | + | point(Point), |
|
| 16 | + | rect(Size), |
|
| 17 | + | } |
|
| 18 | + | ||
| 19 | + | fn test_same_variant_primitive() -> bool { |
|
| 20 | + | let s1: Shape = Shape::circle(10); |
|
| 21 | + | let s2: Shape = Shape::circle(10); |
|
| 22 | + | return s1 == s2; |
|
| 23 | + | } |
|
| 24 | + | ||
| 25 | + | fn test_different_variant() -> bool { |
|
| 26 | + | let s1: Shape = Shape::circle(10); |
|
| 27 | + | let s2: Shape = Shape::point(Point { x: 5, y: 5 }); |
|
| 28 | + | return s1 != s2; |
|
| 29 | + | } |
|
| 30 | + | ||
| 31 | + | fn test_same_variant_struct_equal() -> bool { |
|
| 32 | + | let s1: Shape = Shape::point(Point { x: 10, y: 20 }); |
|
| 33 | + | let s2: Shape = Shape::point(Point { x: 10, y: 20 }); |
|
| 34 | + | return s1 == s2; |
|
| 35 | + | } |
|
| 36 | + | ||
| 37 | + | fn test_same_variant_struct_not_equal() -> bool { |
|
| 38 | + | let s1: Shape = Shape::point(Point { x: 10, y: 20 }); |
|
| 39 | + | let s2: Shape = Shape::point(Point { x: 10, y: 25 }); |
|
| 40 | + | return s1 != s2; |
|
| 41 | + | } |
|
| 42 | + | ||
| 43 | + | fn test_rect_variant_equal() -> bool { |
|
| 44 | + | let s1: Shape = Shape::rect(Size { width: 100, height: 50 }); |
|
| 45 | + | let s2: Shape = Shape::rect(Size { width: 100, height: 50 }); |
|
| 46 | + | return s1 == s2; |
|
| 47 | + | } |
|
| 48 | + | ||
| 49 | + | fn test_rect_variant_not_equal() -> bool { |
|
| 50 | + | let s1: Shape = Shape::rect(Size { width: 100, height: 50 }); |
|
| 51 | + | let s2: Shape = Shape::rect(Size { width: 100, height: 60 }); |
|
| 52 | + | return s1 != s2; |
|
| 53 | + | } |
|
| 54 | + | ||
| 55 | + | @default fn main() -> i32 { |
|
| 56 | + | if not test_same_variant_primitive() { |
|
| 57 | + | return 1; |
|
| 58 | + | } |
|
| 59 | + | if not test_different_variant() { |
|
| 60 | + | return 2; |
|
| 61 | + | } |
|
| 62 | + | if not test_same_variant_struct_equal() { |
|
| 63 | + | return 3; |
|
| 64 | + | } |
|
| 65 | + | if not test_same_variant_struct_not_equal() { |
|
| 66 | + | return 4; |
|
| 67 | + | } |
|
| 68 | + | if not test_rect_variant_equal() { |
|
| 69 | + | return 5; |
|
| 70 | + | } |
|
| 71 | + | if not test_rect_variant_not_equal() { |
|
| 72 | + | return 6; |
|
| 73 | + | } |
|
| 74 | + | return 0; |
|
| 75 | + | } |
lib/std/arch/rv64/tests/bool.comparison.union.simple.gen.rad
added
+20 -0
| 1 | + | //! Test equality on simple enum without payloads. |
|
| 2 | + | union Color { |
|
| 3 | + | red, |
|
| 4 | + | green, |
|
| 5 | + | blue, |
|
| 6 | + | } |
|
| 7 | + | ||
| 8 | + | @default fn main() -> i32 { |
|
| 9 | + | let r1: Color = Color::red; |
|
| 10 | + | let r2: Color = Color::red; |
|
| 11 | + | let g: Color = Color::green; |
|
| 12 | + | ||
| 13 | + | if r1 != r2 { |
|
| 14 | + | return 1; |
|
| 15 | + | } |
|
| 16 | + | if r1 == g { |
|
| 17 | + | return 2; |
|
| 18 | + | } |
|
| 19 | + | return 0; |
|
| 20 | + | } |
lib/std/arch/rv64/tests/bool.operators.complex.rad
added
+15 -0
| 1 | + | //! returns: 1 |
|
| 2 | + | //! Test complex boolean operator expressions. |
|
| 3 | + | ||
| 4 | + | @default fn main() -> bool { |
|
| 5 | + | let a: i32 = 5; |
|
| 6 | + | let b: i32 = 10; |
|
| 7 | + | let c: i32 = 15; |
|
| 8 | + | ||
| 9 | + | let expr1: bool = (a < b) and (b < c); |
|
| 10 | + | let expr2: bool = (a < b) or (b > c); |
|
| 11 | + | let expr3: bool = (a == b) or (b != c); |
|
| 12 | + | let expr4: bool = (a >= b) and (b <= c); |
|
| 13 | + | ||
| 14 | + | return expr1 and expr2 and expr3 and not expr4; |
|
| 15 | + | } |
lib/std/arch/rv64/tests/bool.operators.rad
added
+55 -0
| 1 | + | //! returns: 1 |
|
| 2 | + | //! Test boolean operators with bool-returning main. |
|
| 3 | + | ||
| 4 | + | @default fn main() -> bool { |
|
| 5 | + | let t: bool = true; |
|
| 6 | + | let f: bool = false; |
|
| 7 | + | ||
| 8 | + | // Basic identity. |
|
| 9 | + | if not t { |
|
| 10 | + | return false; |
|
| 11 | + | } |
|
| 12 | + | if f { |
|
| 13 | + | return false; |
|
| 14 | + | } |
|
| 15 | + | ||
| 16 | + | // Negation. |
|
| 17 | + | if not (not f) { |
|
| 18 | + | return false; |
|
| 19 | + | } |
|
| 20 | + | if not t { |
|
| 21 | + | return false; |
|
| 22 | + | } |
|
| 23 | + | ||
| 24 | + | // And. |
|
| 25 | + | if not (t and t) { |
|
| 26 | + | return false; |
|
| 27 | + | } |
|
| 28 | + | if t and f { |
|
| 29 | + | return false; |
|
| 30 | + | } |
|
| 31 | + | if f and t { |
|
| 32 | + | return false; |
|
| 33 | + | } |
|
| 34 | + | ||
| 35 | + | // Or. |
|
| 36 | + | if not (t or f) { |
|
| 37 | + | return false; |
|
| 38 | + | } |
|
| 39 | + | if not (f or t) { |
|
| 40 | + | return false; |
|
| 41 | + | } |
|
| 42 | + | if f or f { |
|
| 43 | + | return false; |
|
| 44 | + | } |
|
| 45 | + | ||
| 46 | + | // Mixed. |
|
| 47 | + | if not ((t and t) or f) { |
|
| 48 | + | return false; |
|
| 49 | + | } |
|
| 50 | + | if not ((f or t) and (t or f)) { |
|
| 51 | + | return false; |
|
| 52 | + | } |
|
| 53 | + | ||
| 54 | + | return true; |
|
| 55 | + | } |
lib/std/arch/rv64/tests/bool.short.circuit.rad
added
+127 -0
| 1 | + | //! Test short-circuiting behavior of 'and' and 'or' operators. |
|
| 2 | + | fn modify(counter: *mut i32, ret: bool) -> bool { |
|
| 3 | + | *counter = *counter + 1; |
|
| 4 | + | return ret; |
|
| 5 | + | } |
|
| 6 | + | ||
| 7 | + | @default fn main() -> i32 { |
|
| 8 | + | // Test 'and' short-circuits when first operand is false. |
|
| 9 | + | let mut x1: i32 = 0; |
|
| 10 | + | let r1: bool = modify(&mut x1, false) and modify(&mut x1, true); |
|
| 11 | + | if r1 { |
|
| 12 | + | return 1; |
|
| 13 | + | } |
|
| 14 | + | if x1 != 1 { |
|
| 15 | + | return 2; |
|
| 16 | + | } |
|
| 17 | + | ||
| 18 | + | // Test 'and' does not short-circuit when first operand is true. |
|
| 19 | + | let mut x2: i32 = 0; |
|
| 20 | + | let r2: bool = modify(&mut x2, true) and modify(&mut x2, true); |
|
| 21 | + | if not r2 { |
|
| 22 | + | return 3; |
|
| 23 | + | } |
|
| 24 | + | if x2 != 2 { |
|
| 25 | + | return 4; |
|
| 26 | + | } |
|
| 27 | + | ||
| 28 | + | // Test 'and' evaluates both when first is true, second is false. |
|
| 29 | + | let mut x3: i32 = 0; |
|
| 30 | + | let r3: bool = modify(&mut x3, true) and modify(&mut x3, false); |
|
| 31 | + | if r3 { |
|
| 32 | + | return 5; |
|
| 33 | + | } |
|
| 34 | + | if x3 != 2 { |
|
| 35 | + | return 6; |
|
| 36 | + | } |
|
| 37 | + | ||
| 38 | + | // Test 'or' short-circuits when first operand is true. |
|
| 39 | + | let mut x4: i32 = 0; |
|
| 40 | + | let r4: bool = modify(&mut x4, true) or modify(&mut x4, true); |
|
| 41 | + | if x4 != 1 { |
|
| 42 | + | return 7; |
|
| 43 | + | } |
|
| 44 | + | if not r4 { |
|
| 45 | + | return 8; |
|
| 46 | + | } |
|
| 47 | + | ||
| 48 | + | // Test 'or' does not short-circuit when first operand is false. |
|
| 49 | + | let mut x5: i32 = 0; |
|
| 50 | + | let r5: bool = modify(&mut x5, false) or modify(&mut x5, true); |
|
| 51 | + | if x5 != 2 { |
|
| 52 | + | return 9; |
|
| 53 | + | } |
|
| 54 | + | if not r5 { |
|
| 55 | + | return 10; |
|
| 56 | + | } |
|
| 57 | + | ||
| 58 | + | let mut y5: i32 = 0; |
|
| 59 | + | let t5: bool = modify(&mut y5, false) or modify(&mut y5, false); |
|
| 60 | + | if y5 != 2 { |
|
| 61 | + | return 11; |
|
| 62 | + | } |
|
| 63 | + | if t5 { |
|
| 64 | + | return 12; |
|
| 65 | + | } |
|
| 66 | + | ||
| 67 | + | // Test complex short-circuiting with nested expressions. |
|
| 68 | + | let mut a6: i32 = 0; |
|
| 69 | + | let r6: bool = |
|
| 70 | + | modify(&mut a6, false) and |
|
| 71 | + | (modify(&mut a6, true) or modify(&mut a6, false)); |
|
| 72 | + | if r6 { |
|
| 73 | + | return 13; |
|
| 74 | + | } |
|
| 75 | + | if a6 != 1 { |
|
| 76 | + | return 14; |
|
| 77 | + | } |
|
| 78 | + | ||
| 79 | + | let mut b6: i32 = 0; |
|
| 80 | + | let t6: bool = modify(&mut b6, true) and |
|
| 81 | + | (modify(&mut b6, false) or modify(&mut b6, false)); |
|
| 82 | + | if t6 { |
|
| 83 | + | return 15; |
|
| 84 | + | } |
|
| 85 | + | if b6 != 3 { |
|
| 86 | + | return 16; |
|
| 87 | + | } |
|
| 88 | + | ||
| 89 | + | // Test nested or short-circuit. |
|
| 90 | + | let mut a7: i32 = 0; |
|
| 91 | + | let r7: bool = modify(&mut a7, false) or |
|
| 92 | + | (modify(&mut a7, true) and modify(&mut a7, true)); |
|
| 93 | + | if not r7 { |
|
| 94 | + | return 17; |
|
| 95 | + | } |
|
| 96 | + | if a7 != 3 { |
|
| 97 | + | return 18; |
|
| 98 | + | } |
|
| 99 | + | ||
| 100 | + | // Test chained 'and' short-circuit. |
|
| 101 | + | let mut a8: i32 = 0; |
|
| 102 | + | let mut r8: bool = |
|
| 103 | + | modify(&mut a8, true) and |
|
| 104 | + | modify(&mut a8, false) and |
|
| 105 | + | modify(&mut a8, false); |
|
| 106 | + | if r8 { |
|
| 107 | + | return 19; |
|
| 108 | + | } |
|
| 109 | + | if a8 != 2 { |
|
| 110 | + | return 20; |
|
| 111 | + | } |
|
| 112 | + | ||
| 113 | + | // Test chained 'or' short-circuit. |
|
| 114 | + | let mut a9: i32 = 0; |
|
| 115 | + | let mut r9: bool = |
|
| 116 | + | modify(&mut a9, false) or |
|
| 117 | + | modify(&mut a9, true) or |
|
| 118 | + | modify(&mut a9, false); |
|
| 119 | + | if not r9 { |
|
| 120 | + | return 21; |
|
| 121 | + | } |
|
| 122 | + | if a9 != 2 { |
|
| 123 | + | return 22; |
|
| 124 | + | } |
|
| 125 | + | ||
| 126 | + | return 0; |
|
| 127 | + | } |
lib/std/arch/rv64/tests/bool.simple.rad
added
+13 -0
| 1 | + | ||
| 2 | + | @default fn main() -> i32 { |
|
| 3 | + | let mut a: bool = true; |
|
| 4 | + | let mut b: bool = false; |
|
| 5 | + | ||
| 6 | + | let cond: bool = a and not b; |
|
| 7 | + | ||
| 8 | + | if (cond) { |
|
| 9 | + | return (42) - 42; |
|
| 10 | + | } |
|
| 11 | + | ||
| 12 | + | return (0) - 42; |
|
| 13 | + | } |
lib/std/arch/rv64/tests/bool.values.rad
added
+65 -0
| 1 | + | // Tests for boolean values assigned from `and`/`or` expressions. |
|
| 2 | + | ||
| 3 | + | @default fn main() -> i32 { |
|
| 4 | + | let t: bool = true; |
|
| 5 | + | let f: bool = false; |
|
| 6 | + | ||
| 7 | + | // Assign `and` results to variables. |
|
| 8 | + | let a: bool = t and t; |
|
| 9 | + | if not a { |
|
| 10 | + | return 1; |
|
| 11 | + | } |
|
| 12 | + | ||
| 13 | + | let b: bool = t and f; |
|
| 14 | + | if b { |
|
| 15 | + | return 2; |
|
| 16 | + | } |
|
| 17 | + | ||
| 18 | + | let c: bool = f and t; |
|
| 19 | + | if c { |
|
| 20 | + | return 3; |
|
| 21 | + | } |
|
| 22 | + | ||
| 23 | + | let d: bool = f and f; |
|
| 24 | + | if d { |
|
| 25 | + | return 4; |
|
| 26 | + | } |
|
| 27 | + | ||
| 28 | + | // Assign `or` results to variables. |
|
| 29 | + | let e: bool = t or f; |
|
| 30 | + | if not e { |
|
| 31 | + | return 5; |
|
| 32 | + | } |
|
| 33 | + | ||
| 34 | + | let g: bool = f or t; |
|
| 35 | + | if not g { |
|
| 36 | + | return 6; |
|
| 37 | + | } |
|
| 38 | + | ||
| 39 | + | let h: bool = t or t; |
|
| 40 | + | if not h { |
|
| 41 | + | return 7; |
|
| 42 | + | } |
|
| 43 | + | ||
| 44 | + | let i: bool = f or f; |
|
| 45 | + | if i { |
|
| 46 | + | return 8; |
|
| 47 | + | } |
|
| 48 | + | ||
| 49 | + | // Nested expressions. |
|
| 50 | + | let j: bool = (t and t) or f; |
|
| 51 | + | if not j { |
|
| 52 | + | return 9; |
|
| 53 | + | } |
|
| 54 | + | ||
| 55 | + | let k: bool = f or (t and t); |
|
| 56 | + | if not k { |
|
| 57 | + | return 10; |
|
| 58 | + | } |
|
| 59 | + | ||
| 60 | + | let l: bool = (t or f) and (f or t); |
|
| 61 | + | if not l { |
|
| 62 | + | return 11; |
|
| 63 | + | } |
|
| 64 | + | return 0; |
|
| 65 | + | } |
lib/std/arch/rv64/tests/builtin.size.align.rad
added
+68 -0
| 1 | + | record Foo { |
|
| 2 | + | x: u8, |
|
| 3 | + | y: u32, |
|
| 4 | + | } |
|
| 5 | + | ||
| 6 | + | fn assert(cond: bool) { |
|
| 7 | + | if not cond { |
|
| 8 | + | panic; |
|
| 9 | + | } |
|
| 10 | + | } |
|
| 11 | + | ||
| 12 | + | @default fn main() -> u8 { |
|
| 13 | + | assert(@sizeOf(u8) == 1); |
|
| 14 | + | assert(@alignOf(u8) == 1); |
|
| 15 | + | ||
| 16 | + | assert(@sizeOf(u16) == 2); |
|
| 17 | + | assert(@alignOf(u16) == 2); |
|
| 18 | + | ||
| 19 | + | assert(@sizeOf(u32) == 4); |
|
| 20 | + | assert(@alignOf(u32) == 4); |
|
| 21 | + | ||
| 22 | + | assert(@sizeOf(i32) == 4); |
|
| 23 | + | assert(@alignOf(i32) == 4); |
|
| 24 | + | ||
| 25 | + | assert(@sizeOf(Foo) == 8); |
|
| 26 | + | assert(@alignOf(Foo) == 4); |
|
| 27 | + | ||
| 28 | + | assert(@sizeOf([u16; 3]) == 6); |
|
| 29 | + | assert(@alignOf([u16; 3]) == 2); |
|
| 30 | + | ||
| 31 | + | assert(@sizeOf(?u8) == 2); |
|
| 32 | + | assert(@alignOf(?u8) == 1); |
|
| 33 | + | ||
| 34 | + | assert(@sizeOf(?*u32) == 8); |
|
| 35 | + | assert(@alignOf(?*u32) == 8); |
|
| 36 | + | ||
| 37 | + | assert(@sizeOf(?*mut u32) == 8); |
|
| 38 | + | assert(@alignOf(?*mut u32) == 8); |
|
| 39 | + | ||
| 40 | + | assert(@sizeOf(?*[u16]) == 24); |
|
| 41 | + | assert(@alignOf(?*[u16]) == 8); |
|
| 42 | + | ||
| 43 | + | assert(@sizeOf(?*mut [u8]) == 24); |
|
| 44 | + | assert(@alignOf(?*mut [u8]) == 8); |
|
| 45 | + | ||
| 46 | + | assert(@sizeOf(?Foo) == 12); |
|
| 47 | + | assert(@alignOf(?Foo) == 4); |
|
| 48 | + | ||
| 49 | + | assert(@sizeOf(?[u16; 3]) == 8); |
|
| 50 | + | assert(@alignOf(?[u16; 3]) == 2); |
|
| 51 | + | ||
| 52 | + | assert(@sizeOf(*u32) == 8); |
|
| 53 | + | assert(@alignOf(*u32) == 8); |
|
| 54 | + | ||
| 55 | + | assert(@sizeOf(*mut u32) == 8); |
|
| 56 | + | assert(@alignOf(*mut u32) == 8); |
|
| 57 | + | ||
| 58 | + | assert(@sizeOf(*[u16]) == 16); |
|
| 59 | + | assert(@alignOf(*[u16]) == 8); |
|
| 60 | + | ||
| 61 | + | assert(@sizeOf(*mut [u8]) == 16); |
|
| 62 | + | assert(@alignOf(*mut [u8]) == 8); |
|
| 63 | + | ||
| 64 | + | assert(@sizeOf(void) == 0); |
|
| 65 | + | assert(@alignOf(void) == 0); |
|
| 66 | + | ||
| 67 | + | return 0; |
|
| 68 | + | } |
lib/std/arch/rv64/tests/builtin.sliceof.mut.rad
added
+26 -0
| 1 | + | // Test the @sliceOf builtin with mutable slice. |
|
| 2 | + | ||
| 3 | + | @default fn main() -> i32 { |
|
| 4 | + | let mut arr: [i32; 4] = [10, 20, 30, 40]; |
|
| 5 | + | ||
| 6 | + | // Get a mutable pointer to the first element |
|
| 7 | + | let ptr: *mut i32 = &mut arr[0]; |
|
| 8 | + | ||
| 9 | + | // Create a mutable slice from the pointer and length |
|
| 10 | + | let slice: *mut [i32] = @sliceOf(ptr, 4); |
|
| 11 | + | ||
| 12 | + | // Double each element via the slice |
|
| 13 | + | slice[0] = slice[0] * 2; |
|
| 14 | + | slice[1] = slice[1] * 2; |
|
| 15 | + | slice[2] = slice[2] * 2; |
|
| 16 | + | slice[3] = slice[3] * 2; |
|
| 17 | + | ||
| 18 | + | // Sum the modified elements |
|
| 19 | + | let mut sum: i32 = 0; |
|
| 20 | + | sum = sum + slice[0]; |
|
| 21 | + | sum = sum + slice[1]; |
|
| 22 | + | sum = sum + slice[2]; |
|
| 23 | + | sum = sum + slice[3]; |
|
| 24 | + | ||
| 25 | + | return (sum) - 200; |
|
| 26 | + | } |
lib/std/arch/rv64/tests/builtin.sliceof.rad
added
+21 -0
| 1 | + | //! returns: 100 |
|
| 2 | + | // Test @sliceOf builtin: create a slice from a pointer and length. |
|
| 3 | + | ||
| 4 | + | @default fn main() -> i32 { |
|
| 5 | + | let mut arr: [i32; 4] = [10, 20, 30, 40]; |
|
| 6 | + | ||
| 7 | + | // Get a pointer to the first element |
|
| 8 | + | let ptr: *i32 = &arr[0]; |
|
| 9 | + | ||
| 10 | + | // Create a slice from the pointer and length |
|
| 11 | + | let slice: *[i32] = @sliceOf(ptr, 4); |
|
| 12 | + | ||
| 13 | + | // Access elements via the slice and sum them |
|
| 14 | + | let mut sum: i32 = 0; |
|
| 15 | + | sum = sum + slice[0]; |
|
| 16 | + | sum = sum + slice[1]; |
|
| 17 | + | sum = sum + slice[2]; |
|
| 18 | + | sum = sum + slice[3]; |
|
| 19 | + | ||
| 20 | + | return sum; |
|
| 21 | + | } |
lib/std/arch/rv64/tests/call.arg.clobber.rad
added
+29 -0
| 1 | + | //! Test: call argument setup must not clobber source registers. |
|
| 2 | + | //! |
|
| 3 | + | //! With 6+ simultaneously live values, the register allocator assigns |
|
| 4 | + | //! some to argument registers (A0-A7). The call instruction setup must |
|
| 5 | + | //! use parallel move resolution to avoid overwriting sources. |
|
| 6 | + | ||
| 7 | + | fn sum6(a: i32, b: i32, c: i32, d: i32, e: i32, f: i32) -> i32 { |
|
| 8 | + | return a + b + c + d + e + f; |
|
| 9 | + | } |
|
| 10 | + | ||
| 11 | + | fn test(x: i32) -> i32 { |
|
| 12 | + | let a: i32 = x + 1; |
|
| 13 | + | let b: i32 = x + 2; |
|
| 14 | + | let c: i32 = x + 3; |
|
| 15 | + | let d: i32 = x + 4; |
|
| 16 | + | let e: i32 = x + 5; |
|
| 17 | + | let f: i32 = x + 6; |
|
| 18 | + | ||
| 19 | + | return sum6(a, b, c, d, e, f); |
|
| 20 | + | } |
|
| 21 | + | ||
| 22 | + | @default fn main() -> i32 { |
|
| 23 | + | let r: i32 = test(10); |
|
| 24 | + | // Expected: 11+12+13+14+15+16 = 81 |
|
| 25 | + | if r != 81 { |
|
| 26 | + | return 1; |
|
| 27 | + | } |
|
| 28 | + | return 0; |
|
| 29 | + | } |
lib/std/arch/rv64/tests/call.basic.rad
added
+10 -0
| 1 | + | // Helper function defined first. |
|
| 2 | + | fn add(a: i32, b: i32) -> i32 { |
|
| 3 | + | return a + b; |
|
| 4 | + | } |
|
| 5 | + | ||
| 6 | + | // Default function is not the first one. |
|
| 7 | + | // Tests both entry jump patching and call patching. |
|
| 8 | + | @default fn main() -> i32 { |
|
| 9 | + | return add(17, 25) - 42; |
|
| 10 | + | } |
lib/std/arch/rv64/tests/call.clobber.rad
added
+28 -0
| 1 | + | //! Test that values live across function calls are not clobbered. |
|
| 2 | + | fn modify(counter: *mut i32, ret: bool) -> bool { |
|
| 3 | + | *counter = *counter + 1; |
|
| 4 | + | return ret; |
|
| 5 | + | } |
|
| 6 | + | ||
| 7 | + | @default fn main() -> i32 { |
|
| 8 | + | let mut x: i32 = 0; |
|
| 9 | + | let r: bool = modify(&mut x, false); |
|
| 10 | + | if x != 1 { |
|
| 11 | + | return 1; |
|
| 12 | + | } |
|
| 13 | + | if r { |
|
| 14 | + | return 2; |
|
| 15 | + | } |
|
| 16 | + | ||
| 17 | + | // Pointer live across call. |
|
| 18 | + | let mut y: i32 = 42; |
|
| 19 | + | let p: *mut i32 = &mut y; |
|
| 20 | + | let r2: bool = modify(p, true); |
|
| 21 | + | if *p != 43 { |
|
| 22 | + | return 3; |
|
| 23 | + | } |
|
| 24 | + | if not r2 { |
|
| 25 | + | return 4; |
|
| 26 | + | } |
|
| 27 | + | return 0; |
|
| 28 | + | } |
lib/std/arch/rv64/tests/cast.same.size.rad
added
+62 -0
| 1 | + | @default fn main() -> i32 { |
|
| 2 | + | // Test i8 -> u8 cast: -1 should become 255. |
|
| 3 | + | let x: i8 = -1; |
|
| 4 | + | let a: u8 = x as u8; |
|
| 5 | + | if a != 255 { |
|
| 6 | + | return 1; |
|
| 7 | + | } |
|
| 8 | + | ||
| 9 | + | // Test u8 -> i8 cast: 255 should become -1. |
|
| 10 | + | let y: u8 = 255; |
|
| 11 | + | let b: i8 = y as i8; |
|
| 12 | + | if b != -1 { |
|
| 13 | + | return 2; |
|
| 14 | + | } |
|
| 15 | + | ||
| 16 | + | // Test i8 -> u8 with boundary value: -128 should become 128. |
|
| 17 | + | let z: i8 = -128; |
|
| 18 | + | let c: u8 = z as u8; |
|
| 19 | + | if c != 128 { |
|
| 20 | + | return 3; |
|
| 21 | + | } |
|
| 22 | + | ||
| 23 | + | // Test u8 -> i8 with boundary value: 128 should become -128. |
|
| 24 | + | let w: u8 = 128; |
|
| 25 | + | let d: i8 = w as i8; |
|
| 26 | + | if d != -128 { |
|
| 27 | + | return 4; |
|
| 28 | + | } |
|
| 29 | + | ||
| 30 | + | // Test i16 -> u16 cast: -1 should become 65535. |
|
| 31 | + | let p: i16 = -1; |
|
| 32 | + | let q: u16 = p as u16; |
|
| 33 | + | if q != 65535 { |
|
| 34 | + | return 5; |
|
| 35 | + | } |
|
| 36 | + | ||
| 37 | + | // Test u16 -> i16 cast: 65535 should become -1. |
|
| 38 | + | let r: u16 = 65535; |
|
| 39 | + | let s: i16 = r as i16; |
|
| 40 | + | if s != -1 { |
|
| 41 | + | return 6; |
|
| 42 | + | } |
|
| 43 | + | ||
| 44 | + | // Eq/Ne used as values must compare at the declared width. |
|
| 45 | + | let eqU8: bool = a == 255; |
|
| 46 | + | if not eqU8 { |
|
| 47 | + | return 7; |
|
| 48 | + | } |
|
| 49 | + | let eqI8: bool = b == -1; |
|
| 50 | + | if not eqI8 { |
|
| 51 | + | return 8; |
|
| 52 | + | } |
|
| 53 | + | let neU8: bool = a != 255; |
|
| 54 | + | if neU8 { |
|
| 55 | + | return 9; |
|
| 56 | + | } |
|
| 57 | + | let neI8: bool = b != -1; |
|
| 58 | + | if neI8 { |
|
| 59 | + | return 10; |
|
| 60 | + | } |
|
| 61 | + | return 0; |
|
| 62 | + | } |
lib/std/arch/rv64/tests/casting.numbers.rad
added
+96 -0
| 1 | + | //! Test integer casting: zero-extension, sign-extension, truncation. |
|
| 2 | + | ||
| 3 | + | fn zeroExtension() -> bool { |
|
| 4 | + | let x: u8 = 9; |
|
| 5 | + | let mut y: i32 = 42424242; |
|
| 6 | + | y = x as i32; |
|
| 7 | + | let z: u8 = y as u8; |
|
| 8 | + | ||
| 9 | + | return y == 9 and z == 9; |
|
| 10 | + | } |
|
| 11 | + | ||
| 12 | + | fn casting() -> bool { |
|
| 13 | + | let x: u8 = 5; |
|
| 14 | + | let y: i16 = 10; |
|
| 15 | + | let mut z: i32 = y as i32; |
|
| 16 | + | ||
| 17 | + | z = 11 as i32 + z; |
|
| 18 | + | ||
| 19 | + | return ((x as i32) + z) == 26; |
|
| 20 | + | } |
|
| 21 | + | ||
| 22 | + | fn signedCasting() -> bool { |
|
| 23 | + | let x: i8 = -5; |
|
| 24 | + | let y: i32 = x as i32; |
|
| 25 | + | let z: i8 = y as i8; |
|
| 26 | + | ||
| 27 | + | return y == -5 and z == -5; |
|
| 28 | + | } |
|
| 29 | + | ||
| 30 | + | fn unsignedToSigned() -> bool { |
|
| 31 | + | let x: u8 = 250; |
|
| 32 | + | let y: i32 = x as i32; |
|
| 33 | + | let z: u8 = y as u8; |
|
| 34 | + | ||
| 35 | + | return y == 250 and z == 250; |
|
| 36 | + | } |
|
| 37 | + | ||
| 38 | + | fn signed16bitCasting() -> bool { |
|
| 39 | + | let x: i16 = -1000; |
|
| 40 | + | let y: i32 = x as i32; |
|
| 41 | + | let z: i16 = y as i16; |
|
| 42 | + | ||
| 43 | + | return y == -1000 and z == -1000; |
|
| 44 | + | } |
|
| 45 | + | ||
| 46 | + | fn maxU8Casting() -> bool { |
|
| 47 | + | let x: u8 = 255; |
|
| 48 | + | let y: i32 = x as i32; |
|
| 49 | + | let z: u8 = y as u8; |
|
| 50 | + | ||
| 51 | + | return y == 255 and z == 255; |
|
| 52 | + | } |
|
| 53 | + | ||
| 54 | + | fn truncationTest() -> bool { |
|
| 55 | + | let x: i32 = 1000; |
|
| 56 | + | let y: u8 = x as u8; |
|
| 57 | + | let z: i32 = y as i32; |
|
| 58 | + | ||
| 59 | + | return y == 232 and z == 232; |
|
| 60 | + | } |
|
| 61 | + | ||
| 62 | + | fn sameSizeCasting() -> bool { |
|
| 63 | + | let x: u32 = 2147483648; |
|
| 64 | + | let y: i32 = x as i32; |
|
| 65 | + | let z: u32 = y as u32; |
|
| 66 | + | ||
| 67 | + | return y == -2147483648 and z == 2147483648; |
|
| 68 | + | } |
|
| 69 | + | ||
| 70 | + | @default fn main() -> i32 { |
|
| 71 | + | if not casting() { |
|
| 72 | + | return 1; |
|
| 73 | + | } |
|
| 74 | + | if not zeroExtension() { |
|
| 75 | + | return 2; |
|
| 76 | + | } |
|
| 77 | + | if not signedCasting() { |
|
| 78 | + | return 3; |
|
| 79 | + | } |
|
| 80 | + | if not unsignedToSigned() { |
|
| 81 | + | return 4; |
|
| 82 | + | } |
|
| 83 | + | if not signed16bitCasting() { |
|
| 84 | + | return 5; |
|
| 85 | + | } |
|
| 86 | + | if not maxU8Casting() { |
|
| 87 | + | return 6; |
|
| 88 | + | } |
|
| 89 | + | if not truncationTest() { |
|
| 90 | + | return 7; |
|
| 91 | + | } |
|
| 92 | + | if not sameSizeCasting() { |
|
| 93 | + | return 8; |
|
| 94 | + | } |
|
| 95 | + | return 0; |
|
| 96 | + | } |
lib/std/arch/rv64/tests/char.literal.rad
added
+13 -0
| 1 | + | //! Test character literals including escaped chars. |
|
| 2 | + | @default fn main() -> i32 { |
|
| 3 | + | if 'a' != 97 { |
|
| 4 | + | return 1; |
|
| 5 | + | } |
|
| 6 | + | if '\'' != 39 { |
|
| 7 | + | return 2; |
|
| 8 | + | } |
|
| 9 | + | if '\\' != 92 { |
|
| 10 | + | return 3; |
|
| 11 | + | } |
|
| 12 | + | return 0; |
|
| 13 | + | } |
lib/std/arch/rv64/tests/cond.assign.rad
added
+42 -0
| 1 | + | // Tests for block parameters (phi nodes). |
|
| 2 | + | // Block parameters are generated when different branches assign |
|
| 3 | + | // different values to a variable that's used after the merge point. |
|
| 4 | + | ||
| 5 | + | @default fn main() -> i32 { |
|
| 6 | + | // Simple `if-else` with variable assignment. |
|
| 7 | + | let t: bool = true; |
|
| 8 | + | let mut x: i32 = 0; |
|
| 9 | + | if t { |
|
| 10 | + | x = 1; |
|
| 11 | + | } else { |
|
| 12 | + | x = 2; |
|
| 13 | + | } |
|
| 14 | + | if x != 1 { |
|
| 15 | + | return 1; |
|
| 16 | + | } |
|
| 17 | + | ||
| 18 | + | // `if-else` with `false` condition. |
|
| 19 | + | let f: bool = false; |
|
| 20 | + | if f { |
|
| 21 | + | x = 10; |
|
| 22 | + | } else { |
|
| 23 | + | x = 20; |
|
| 24 | + | } |
|
| 25 | + | if x != 20 { |
|
| 26 | + | return 2; |
|
| 27 | + | } |
|
| 28 | + | ||
| 29 | + | // `if-else` chain. |
|
| 30 | + | let a: i32 = 5; |
|
| 31 | + | if a == 3 { |
|
| 32 | + | x = 100; |
|
| 33 | + | } else if a == 5 { |
|
| 34 | + | x = 200; |
|
| 35 | + | } else { |
|
| 36 | + | x = 300; |
|
| 37 | + | } |
|
| 38 | + | if x != 200 { |
|
| 39 | + | return 3; |
|
| 40 | + | } |
|
| 41 | + | return 0; |
|
| 42 | + | } |
lib/std/arch/rv64/tests/cond.for.else.break.rad
added
+15 -0
| 1 | + | ||
| 2 | + | @default fn main() -> i32 { |
|
| 3 | + | let mut i: i32 = 0; |
|
| 4 | + | let xs: [i32; 7] = [1, 2, 3, 4, 5, 6, 7]; |
|
| 5 | + | ||
| 6 | + | for x in (xs) { |
|
| 7 | + | i = i + x; |
|
| 8 | + | if (i >= 10) { |
|
| 9 | + | break; // This should skip the else clause. |
|
| 10 | + | } |
|
| 11 | + | } else { |
|
| 12 | + | i = i + 100; // This should NOT run because we break. |
|
| 13 | + | } |
|
| 14 | + | return (i) - 10; |
|
| 15 | + | } |
lib/std/arch/rv64/tests/cond.for.indexed.rad
added
+11 -0
| 1 | + | ||
| 2 | + | @default fn main() -> u32 { |
|
| 3 | + | let arr: [u32; 3] = [10, 20, 30]; |
|
| 4 | + | let mut sum: u32 = 0; |
|
| 5 | + | ||
| 6 | + | for value, index in arr { |
|
| 7 | + | sum = sum + value + index; |
|
| 8 | + | } |
|
| 9 | + | return (sum) - 63; |
|
| 10 | + | // (10+0) + (20+1) + (30+2) = 10 + 21 + 32 = 63 |
|
| 11 | + | } |
lib/std/arch/rv64/tests/cond.for.rad
added
+10 -0
| 1 | + | ||
| 2 | + | @default fn main() -> i32 { |
|
| 3 | + | let mut i: i32 = 0; |
|
| 4 | + | let xs: [i32; 7] = [1, 2, 3, 4, 5, 6, 7]; |
|
| 5 | + | ||
| 6 | + | for x in (xs) { |
|
| 7 | + | i = i + x; |
|
| 8 | + | } |
|
| 9 | + | return (i) - 28; |
|
| 10 | + | } |
lib/std/arch/rv64/tests/cond.for.range.indexed.rad
added
+22 -0
| 1 | + | ||
| 2 | + | @default fn main() -> i32 { |
|
| 3 | + | let mut sum: i32 = 0; |
|
| 4 | + | ||
| 5 | + | // Test range iteration with index. |
|
| 6 | + | // val: 5, 6, 7, 8 |
|
| 7 | + | // idx: 0, 1, 2, 3 |
|
| 8 | + | // sum: (5+0) + (6+1) + (7+2) + (8+3) = 5 + 7 + 9 + 11 = 32 |
|
| 9 | + | for val, idx in 5..9 { |
|
| 10 | + | sum = sum + val + idx as i32; |
|
| 11 | + | } |
|
| 12 | + | ||
| 13 | + | // Test that value starts from a non-zero offset while index starts at 0. |
|
| 14 | + | // val: 10, 11 |
|
| 15 | + | // idx: 0, 1 |
|
| 16 | + | // sum: 32 + (10-0) + (11-1) = 32 + 10 + 10 = 52 |
|
| 17 | + | for v, i in 10..12 { |
|
| 18 | + | sum = sum + v - i as i32; |
|
| 19 | + | } |
|
| 20 | + | ||
| 21 | + | return (sum) - 52; |
|
| 22 | + | } |
lib/std/arch/rv64/tests/cond.for.range.rad
added
+10 -0
| 1 | + | ||
| 2 | + | @default fn main() -> i32 { |
|
| 3 | + | let xs: [i32; 4] = [3, 4, 5, 6]; |
|
| 4 | + | let mut sum: i32 = 0; |
|
| 5 | + | ||
| 6 | + | for i in 0..xs.len { |
|
| 7 | + | sum = sum + xs[i]; |
|
| 8 | + | } |
|
| 9 | + | return (sum) - 18; |
|
| 10 | + | } |
lib/std/arch/rv64/tests/cond.for.unsigned.range.rad
added
+36 -0
| 1 | + | @default fn main() -> i32 { |
|
| 2 | + | // Unsigned range crossing signed boundary should iterate once. |
|
| 3 | + | let start: u32 = 2147483647; |
|
| 4 | + | let end: u32 = 2147483648; |
|
| 5 | + | let mut count: i32 = 0; |
|
| 6 | + | ||
| 7 | + | for i in start..end { |
|
| 8 | + | count = count + 1; |
|
| 9 | + | } |
|
| 10 | + | if count != 1 { |
|
| 11 | + | return 1; |
|
| 12 | + | } |
|
| 13 | + | ||
| 14 | + | // Unsigned range above signed boundary. |
|
| 15 | + | let start2: u32 = 2147483648; |
|
| 16 | + | let end2: u32 = 2147483651; |
|
| 17 | + | let mut count2: i32 = 0; |
|
| 18 | + | ||
| 19 | + | for i in start2..end2 { |
|
| 20 | + | count2 = count2 + 1; |
|
| 21 | + | } |
|
| 22 | + | if count2 != 3 { |
|
| 23 | + | return 2; |
|
| 24 | + | } |
|
| 25 | + | ||
| 26 | + | // Signed range still uses signed ordering. |
|
| 27 | + | let mut sum: i32 = 0; |
|
| 28 | + | for i in -2..2 { |
|
| 29 | + | sum = sum + i; |
|
| 30 | + | } |
|
| 31 | + | if sum != -2 { |
|
| 32 | + | return 3; |
|
| 33 | + | } |
|
| 34 | + | ||
| 35 | + | return 0; |
|
| 36 | + | } |
lib/std/arch/rv64/tests/cond.forever.break.continue.rad
added
+12 -0
| 1 | + | @default fn main() -> i32 { |
|
| 2 | + | let mut i: i32 = 0; |
|
| 3 | + | ||
| 4 | + | loop { |
|
| 5 | + | if (i < 42) { |
|
| 6 | + | i = i + 1; |
|
| 7 | + | continue; |
|
| 8 | + | } |
|
| 9 | + | break; |
|
| 10 | + | } |
|
| 11 | + | return (i) - 42; |
|
| 12 | + | } |
lib/std/arch/rv64/tests/cond.forever.break.rad
added
+15 -0
| 1 | + | @default fn main() -> i32 { |
|
| 2 | + | let mut i: i32 = 0; |
|
| 3 | + | ||
| 4 | + | loop { |
|
| 5 | + | loop { |
|
| 6 | + | if (i > 39) { |
|
| 7 | + | break; |
|
| 8 | + | } |
|
| 9 | + | i = i + 1; |
|
| 10 | + | } |
|
| 11 | + | i = i + 2; |
|
| 12 | + | break; |
|
| 13 | + | } |
|
| 14 | + | return (i) - 42; |
|
| 15 | + | } |
lib/std/arch/rv64/tests/cond.fused.rad
added
+56 -0
| 1 | + | // Tests for short-circuit boolean operators (`and`/`or`). |
|
| 2 | + | // These generate block parameters for short-circuit evaluation. |
|
| 3 | + | ||
| 4 | + | @default fn main() -> i32 { |
|
| 5 | + | let t: bool = true; |
|
| 6 | + | let f: bool = false; |
|
| 7 | + | ||
| 8 | + | // Boolean `and` (short-circuit). |
|
| 9 | + | if t and t { |
|
| 10 | + | // Ignore. |
|
| 11 | + | } else { |
|
| 12 | + | return 1; |
|
| 13 | + | } |
|
| 14 | + | if t and f { |
|
| 15 | + | return 2; |
|
| 16 | + | } |
|
| 17 | + | if f and t { |
|
| 18 | + | return 3; |
|
| 19 | + | } |
|
| 20 | + | if f and f { |
|
| 21 | + | return 4; |
|
| 22 | + | } |
|
| 23 | + | ||
| 24 | + | // Boolean `or` (short-circuit). |
|
| 25 | + | if t or f { |
|
| 26 | + | // Ignore. |
|
| 27 | + | } else { |
|
| 28 | + | return 5; |
|
| 29 | + | } |
|
| 30 | + | if f or t { |
|
| 31 | + | // Ignore. |
|
| 32 | + | } else { |
|
| 33 | + | return 6; |
|
| 34 | + | } |
|
| 35 | + | if t or t { |
|
| 36 | + | // Ignore. |
|
| 37 | + | } else { |
|
| 38 | + | return 7; |
|
| 39 | + | } |
|
| 40 | + | if f or f { |
|
| 41 | + | return 8; |
|
| 42 | + | } |
|
| 43 | + | ||
| 44 | + | // Combined. |
|
| 45 | + | if (t and t) or f { |
|
| 46 | + | // Ignore. |
|
| 47 | + | } else { |
|
| 48 | + | return 9; |
|
| 49 | + | } |
|
| 50 | + | if f or (t and t) { |
|
| 51 | + | // Ignore. |
|
| 52 | + | } else { |
|
| 53 | + | return 10; |
|
| 54 | + | } |
|
| 55 | + | return 0; |
|
| 56 | + | } |
lib/std/arch/rv64/tests/cond.if.case.rad
added
+97 -0
| 1 | + | //! returns: 1 |
|
| 2 | + | //! Test if-let-case with union variants. |
|
| 3 | + | ||
| 4 | + | union Response { |
|
| 5 | + | success(i32), |
|
| 6 | + | failure, |
|
| 7 | + | offline, |
|
| 8 | + | } |
|
| 9 | + | ||
| 10 | + | /// Test matching a payload variant. |
|
| 11 | + | fn testMatchPayload() -> bool { |
|
| 12 | + | let resp: Response = Response::success(42); |
|
| 13 | + | if let case Response::success(value) = resp { |
|
| 14 | + | return value == 42; |
|
| 15 | + | } |
|
| 16 | + | return false; |
|
| 17 | + | } |
|
| 18 | + | ||
| 19 | + | /// Test guard condition on a payload variant. |
|
| 20 | + | fn testGuardSuccess() -> bool { |
|
| 21 | + | let resp: Response = Response::success(12); |
|
| 22 | + | if let case Response::success(value) = resp; value > 10 { |
|
| 23 | + | return value == 12; |
|
| 24 | + | } |
|
| 25 | + | return false; |
|
| 26 | + | } |
|
| 27 | + | ||
| 28 | + | /// Test guard condition that falls through to else. |
|
| 29 | + | fn testGuardElse() -> bool { |
|
| 30 | + | let resp: Response = Response::success(5); |
|
| 31 | + | if let case Response::success(value) = resp; value > 10 { |
|
| 32 | + | return false; |
|
| 33 | + | } else { |
|
| 34 | + | return true; |
|
| 35 | + | } |
|
| 36 | + | } |
|
| 37 | + | ||
| 38 | + | /// Test matching a no-payload variant. |
|
| 39 | + | fn testNoPayloadVariant() -> bool { |
|
| 40 | + | let resp: Response = Response::failure; |
|
| 41 | + | if let case Response::failure = resp { |
|
| 42 | + | return true; |
|
| 43 | + | } |
|
| 44 | + | return false; |
|
| 45 | + | } |
|
| 46 | + | ||
| 47 | + | /// Test else-if chain with if-let-case. |
|
| 48 | + | fn testElseIfChain() -> bool { |
|
| 49 | + | let resp: Response = Response::failure; |
|
| 50 | + | if let case Response::success(_) = resp { |
|
| 51 | + | return false; |
|
| 52 | + | } else if let case Response::failure = resp { |
|
| 53 | + | return true; |
|
| 54 | + | } else { |
|
| 55 | + | return false; |
|
| 56 | + | } |
|
| 57 | + | } |
|
| 58 | + | ||
| 59 | + | /// Test matching a payload variant with placeholder. |
|
| 60 | + | fn testPlaceholderPayload() -> bool { |
|
| 61 | + | let resp: Response = Response::success(7); |
|
| 62 | + | if let case Response::success(_) = resp { |
|
| 63 | + | return true; |
|
| 64 | + | } |
|
| 65 | + | return false; |
|
| 66 | + | } |
|
| 67 | + | ||
| 68 | + | /// Test matching a literal value. |
|
| 69 | + | fn testLiteralMatch() -> bool { |
|
| 70 | + | let value: i32 = 5; |
|
| 71 | + | if let case 5 = value { |
|
| 72 | + | return true; |
|
| 73 | + | } |
|
| 74 | + | return false; |
|
| 75 | + | } |
|
| 76 | + | ||
| 77 | + | /// Test literal match that falls through to else. |
|
| 78 | + | fn testLiteralElse() -> bool { |
|
| 79 | + | let value: i32 = 5; |
|
| 80 | + | if let case 6 = value { |
|
| 81 | + | return false; |
|
| 82 | + | } else { |
|
| 83 | + | return true; |
|
| 84 | + | } |
|
| 85 | + | } |
|
| 86 | + | ||
| 87 | + | @default fn main() -> bool { |
|
| 88 | + | return |
|
| 89 | + | testMatchPayload() and |
|
| 90 | + | testGuardSuccess() and |
|
| 91 | + | testGuardElse() and |
|
| 92 | + | testNoPayloadVariant() and |
|
| 93 | + | testElseIfChain() and |
|
| 94 | + | testPlaceholderPayload() and |
|
| 95 | + | testLiteralMatch() and |
|
| 96 | + | testLiteralElse(); |
|
| 97 | + | } |
lib/std/arch/rv64/tests/cond.if.else.min.rad
added
+9 -0
| 1 | + | @default fn main() -> i32 { |
|
| 2 | + | let mut i: i32 = 0; |
|
| 3 | + | ||
| 4 | + | if (i == 1) { |
|
| 5 | + | return (1) - 42; |
|
| 6 | + | } else { |
|
| 7 | + | return (42) - 42; |
|
| 8 | + | } |
|
| 9 | + | } |
lib/std/arch/rv64/tests/cond.if.else.rad
added
+15 -0
| 1 | + | @default fn main() -> i32 { |
|
| 2 | + | let mut i: i32 = 41; |
|
| 3 | + | ||
| 4 | + | if (i == 42) { |
|
| 5 | + | return (1) - 42; |
|
| 6 | + | } else { |
|
| 7 | + | i = i + 1; |
|
| 8 | + | } |
|
| 9 | + | ||
| 10 | + | if (i == 42) { |
|
| 11 | + | return (42) - 42; |
|
| 12 | + | } else { |
|
| 13 | + | return (1) - 42; |
|
| 14 | + | } |
|
| 15 | + | } |
lib/std/arch/rv64/tests/cond.if.elseif.rad
added
+23 -0
| 1 | + | @default fn main() -> i32 { |
|
| 2 | + | let mut i: i32 = 40; |
|
| 3 | + | ||
| 4 | + | if (i == 42) { |
|
| 5 | + | return (1) - 42; |
|
| 6 | + | } else if (i == 41) { |
|
| 7 | + | return (1) - 42; |
|
| 8 | + | } else if (i == 40) { |
|
| 9 | + | i = i + 2; |
|
| 10 | + | } else { |
|
| 11 | + | i = i + 1; |
|
| 12 | + | } |
|
| 13 | + | ||
| 14 | + | if (i == 42) { |
|
| 15 | + | return (42) - 42; |
|
| 16 | + | } else if (i == 41) { |
|
| 17 | + | return (1) - 42; |
|
| 18 | + | } else if (i == 40) { |
|
| 19 | + | return (1) - 42; |
|
| 20 | + | } else { |
|
| 21 | + | return (0) - 42; |
|
| 22 | + | } |
|
| 23 | + | } |
lib/std/arch/rv64/tests/cond.if.noelse.rad
added
+10 -0
| 1 | + | ||
| 2 | + | @default fn main() -> i32 { |
|
| 3 | + | let mut x: i32 = 10; |
|
| 4 | + | ||
| 5 | + | if (x == 11) { |
|
| 6 | + | x = 20; |
|
| 7 | + | } |
|
| 8 | + | ||
| 9 | + | return (x) - 10; |
|
| 10 | + | } |
lib/std/arch/rv64/tests/cond.if.rad
added
+65 -0
| 1 | + | @default fn main() -> i32 { |
|
| 2 | + | // Basic `if` with `true`. |
|
| 3 | + | let t: bool = true; |
|
| 4 | + | if t { |
|
| 5 | + | // Ignore. |
|
| 6 | + | } else { |
|
| 7 | + | return 1; |
|
| 8 | + | } |
|
| 9 | + | ||
| 10 | + | // Basic `if` with `false`. |
|
| 11 | + | let f: bool = false; |
|
| 12 | + | if f { |
|
| 13 | + | return 2; |
|
| 14 | + | } |
|
| 15 | + | ||
| 16 | + | // Boolean `not`. |
|
| 17 | + | if not f { |
|
| 18 | + | } else { |
|
| 19 | + | return 3; |
|
| 20 | + | } |
|
| 21 | + | if not t { |
|
| 22 | + | return 4; |
|
| 23 | + | } |
|
| 24 | + | ||
| 25 | + | // Comparison in condition. |
|
| 26 | + | let a: i32 = 5; |
|
| 27 | + | let b: i32 = 10; |
|
| 28 | + | if a < b { |
|
| 29 | + | // Ignore. |
|
| 30 | + | } else { |
|
| 31 | + | return 5; |
|
| 32 | + | } |
|
| 33 | + | if a > b { |
|
| 34 | + | return 6; |
|
| 35 | + | } |
|
| 36 | + | if a == a { |
|
| 37 | + | // Ignore. |
|
| 38 | + | } else { |
|
| 39 | + | return 7; |
|
| 40 | + | } |
|
| 41 | + | if a != b { |
|
| 42 | + | // Ignore. |
|
| 43 | + | } else { |
|
| 44 | + | return 8; |
|
| 45 | + | } |
|
| 46 | + | ||
| 47 | + | // Nested if. |
|
| 48 | + | if t { |
|
| 49 | + | if f { |
|
| 50 | + | return 9; |
|
| 51 | + | } |
|
| 52 | + | } |
|
| 53 | + | ||
| 54 | + | // `if` in `else` branch. |
|
| 55 | + | if f { |
|
| 56 | + | return 10; |
|
| 57 | + | } else { |
|
| 58 | + | if t { |
|
| 59 | + | // Ignore. |
|
| 60 | + | } else { |
|
| 61 | + | return 11; |
|
| 62 | + | } |
|
| 63 | + | } |
|
| 64 | + | return 0; |
|
| 65 | + | } |
lib/std/arch/rv64/tests/cond.match.fallthrough.rad
added
+14 -0
| 1 | + | // Test that jump patching works to skip cases that were not matched. |
|
| 2 | + | @default fn main() -> i32 { |
|
| 3 | + | let mut x: i32 = 1; |
|
| 4 | + | ||
| 5 | + | match (x) { |
|
| 6 | + | case 1 => { |
|
| 7 | + | // This case matches, changes x to 2 and should jump to end. |
|
| 8 | + | x = 2; |
|
| 9 | + | }, |
|
| 10 | + | case 2 => { return (2) - 42; }, |
|
| 11 | + | else => { return (0) - 42; } |
|
| 12 | + | } |
|
| 13 | + | return (42) - 42; |
|
| 14 | + | } |
lib/std/arch/rv64/tests/cond.match.guard.rad
added
+76 -0
| 1 | + | ||
| 2 | + | union OptionNum { |
|
| 3 | + | Some(i32), |
|
| 4 | + | Other(i32), |
|
| 5 | + | None, |
|
| 6 | + | } |
|
| 7 | + | ||
| 8 | + | fn checkStmt(value: OptionNum) -> i32 { |
|
| 9 | + | let mut result: i32 = -100; |
|
| 10 | + | match value { |
|
| 11 | + | case OptionNum::Some(payload) if payload == 5 => { |
|
| 12 | + | result = payload; |
|
| 13 | + | } |
|
| 14 | + | case OptionNum::Some(payload) => { |
|
| 15 | + | result = -payload; |
|
| 16 | + | } |
|
| 17 | + | case OptionNum::Other(payload) if payload == 7 => { |
|
| 18 | + | result = payload * 2; |
|
| 19 | + | } |
|
| 20 | + | else => { |
|
| 21 | + | result = 0; |
|
| 22 | + | } |
|
| 23 | + | } |
|
| 24 | + | return result; |
|
| 25 | + | } |
|
| 26 | + | ||
| 27 | + | fn checkExpr(value: OptionNum) -> i32 { |
|
| 28 | + | match value { |
|
| 29 | + | case OptionNum::Some(payload) if payload == 5 => { |
|
| 30 | + | return payload + 1; |
|
| 31 | + | } |
|
| 32 | + | case OptionNum::Some(payload) => { |
|
| 33 | + | return payload + 2; |
|
| 34 | + | } |
|
| 35 | + | case OptionNum::Other(payload) if payload == 7 => { |
|
| 36 | + | return payload + 3; |
|
| 37 | + | } |
|
| 38 | + | else => { |
|
| 39 | + | return 0; |
|
| 40 | + | } |
|
| 41 | + | } |
|
| 42 | + | } |
|
| 43 | + | ||
| 44 | + | @default fn main() -> i32 { |
|
| 45 | + | if checkStmt(OptionNum::Some(5)) != 5 { |
|
| 46 | + | return 1; |
|
| 47 | + | } |
|
| 48 | + | if checkStmt(OptionNum::Some(2)) != -2 { |
|
| 49 | + | return 2; |
|
| 50 | + | } |
|
| 51 | + | if checkStmt(OptionNum::Other(7)) != 14 { |
|
| 52 | + | return 3; |
|
| 53 | + | } |
|
| 54 | + | if checkStmt(OptionNum::Other(3)) != 0 { |
|
| 55 | + | return 4; |
|
| 56 | + | } |
|
| 57 | + | if checkStmt(OptionNum::None) != 0 { |
|
| 58 | + | return 5; |
|
| 59 | + | } |
|
| 60 | + | if checkExpr(OptionNum::Some(5)) != 6 { |
|
| 61 | + | return 6; |
|
| 62 | + | } |
|
| 63 | + | if checkExpr(OptionNum::Some(9)) != 11 { |
|
| 64 | + | return 7; |
|
| 65 | + | } |
|
| 66 | + | if checkExpr(OptionNum::Other(7)) != 10 { |
|
| 67 | + | return 8; |
|
| 68 | + | } |
|
| 69 | + | if checkExpr(OptionNum::Other(3)) != 0 { |
|
| 70 | + | return 9; |
|
| 71 | + | } |
|
| 72 | + | if checkExpr(OptionNum::None) != 0 { |
|
| 73 | + | return 10; |
|
| 74 | + | } |
|
| 75 | + | return 0; |
|
| 76 | + | } |
lib/std/arch/rv64/tests/cond.match.guard.regalloc.rad
added
+62 -0
| 1 | + | //! Match guard register allocation. |
|
| 2 | + | //! Tests that a guarded match case with multiple union variants does not |
|
| 3 | + | //! clobber registers live across out-of-order blocks. The guard in |
|
| 4 | + | //! `case Kind::Name(name) if name.len > 0` creates a block ordering where |
|
| 5 | + | //! the case body has a lower index than its predecessor (the guard block). |
|
| 6 | + | //! Without RPO-based register allocation, the name payload pointer can be |
|
| 7 | + | //! overwritten by the bounds check in the case body. |
|
| 8 | + | ||
| 9 | + | union Kind { |
|
| 10 | + | Num(i32), |
|
| 11 | + | Name(*[u8]), |
|
| 12 | + | Pair { a: i32, b: i32 }, |
|
| 13 | + | } |
|
| 14 | + | ||
| 15 | + | record Node { |
|
| 16 | + | id: u32, |
|
| 17 | + | span1: u32, |
|
| 18 | + | span2: u32, |
|
| 19 | + | kind: Kind, |
|
| 20 | + | } |
|
| 21 | + | ||
| 22 | + | fn getPath(node: *Node, buf: *mut [*[u8]]) -> *[*[u8]] { |
|
| 23 | + | let mut out: *[*[u8]] = &[]; |
|
| 24 | + | ||
| 25 | + | match node.kind { |
|
| 26 | + | case Kind::Name(name) if name.len > 0 => { |
|
| 27 | + | buf[0] = name; |
|
| 28 | + | out = buf[..1]; |
|
| 29 | + | } |
|
| 30 | + | case Kind::Pair { a, b } => { |
|
| 31 | + | out = buf[..1]; |
|
| 32 | + | } |
|
| 33 | + | else => {} |
|
| 34 | + | } |
|
| 35 | + | return out; |
|
| 36 | + | } |
|
| 37 | + | ||
| 38 | + | @default fn main() -> i32 { |
|
| 39 | + | let n = Node { |
|
| 40 | + | id: 0, span1: 0, span2: 5, |
|
| 41 | + | kind: Kind::Name("Hello"), |
|
| 42 | + | }; |
|
| 43 | + | let mut buffer: [*[u8]; 4] = undefined; |
|
| 44 | + | let path = getPath(&n, &mut buffer[..]); |
|
| 45 | + | ||
| 46 | + | let pathLen = path.len as i32; |
|
| 47 | + | if pathLen > 1 { |
|
| 48 | + | return 1; |
|
| 49 | + | } |
|
| 50 | + | if pathLen <= 0 { |
|
| 51 | + | return 2; |
|
| 52 | + | } |
|
| 53 | + | ||
| 54 | + | let elemLen = path[0].len as i32; |
|
| 55 | + | if elemLen > 5 { |
|
| 56 | + | return 3; |
|
| 57 | + | } |
|
| 58 | + | if elemLen <= 4 { |
|
| 59 | + | return 4; |
|
| 60 | + | } |
|
| 61 | + | return 0; |
|
| 62 | + | } |
lib/std/arch/rv64/tests/cond.while.else.break.rad
added
+14 -0
| 1 | + | ||
| 2 | + | @default fn main() -> i32 { |
|
| 3 | + | let mut i: i32 = 0; |
|
| 4 | + | ||
| 5 | + | while (i < 100) { |
|
| 6 | + | i = i + 1; |
|
| 7 | + | if (i == 42) { |
|
| 8 | + | break; // This should skip the else clause. |
|
| 9 | + | } |
|
| 10 | + | } else { |
|
| 11 | + | i = i + 100; // This should NOT run because we break. |
|
| 12 | + | } |
|
| 13 | + | return (i) - 42; |
|
| 14 | + | } |
lib/std/arch/rv64/tests/cond.while.rad
added
+8 -0
| 1 | + | @default fn main() -> i32 { |
|
| 2 | + | let mut i: i32 = 0; |
|
| 3 | + | ||
| 4 | + | while (i < 42) { |
|
| 5 | + | i = i + 1; |
|
| 6 | + | } |
|
| 7 | + | return (i) - 42; |
|
| 8 | + | } |
lib/std/arch/rv64/tests/const.array.copy.mutate.rad
added
+20 -0
| 1 | + | //! returns: 22 |
|
| 2 | + | //! Test copying and mutating constant arrays. |
|
| 3 | + | ||
| 4 | + | const SEED: [i32; 4] = [1, 2, 3, 4]; |
|
| 5 | + | ||
| 6 | + | fn crunch() -> i32 { |
|
| 7 | + | let mut working: [i32; 4] = SEED; |
|
| 8 | + | ||
| 9 | + | working[0] = working[0] + 5; |
|
| 10 | + | working[3] = working[1] + working[2]; |
|
| 11 | + | ||
| 12 | + | return working[0] + working[3]; |
|
| 13 | + | } |
|
| 14 | + | ||
| 15 | + | @default fn main() -> i32 { |
|
| 16 | + | let first: i32 = crunch(); |
|
| 17 | + | let second: i32 = crunch(); |
|
| 18 | + | ||
| 19 | + | return first + second; |
|
| 20 | + | } |
lib/std/arch/rv64/tests/const.array.rad
added
+11 -0
| 1 | + | // Test array constants. |
|
| 2 | + | ||
| 3 | + | const NUMBERS: [i32; 4] = [1, 2, 3, 4]; |
|
| 4 | + | ||
| 5 | + | @default fn main() -> i32 { |
|
| 6 | + | let mut sum: i32 = 0; |
|
| 7 | + | for n in (NUMBERS) { |
|
| 8 | + | sum = sum + n; |
|
| 9 | + | } |
|
| 10 | + | return (sum) - 10; |
|
| 11 | + | } |
lib/std/arch/rv64/tests/const.basic.rad
added
+15 -0
| 1 | + | // Test constant declarations with literals only (no expressions). |
|
| 2 | + | const N: i32 = 3; |
|
| 3 | + | const MAX: i32 = 4; |
|
| 4 | + | const DEBUG: bool = true; |
|
| 5 | + | ||
| 6 | + | @default fn main() -> i32 { |
|
| 7 | + | let n: i32 = N; |
|
| 8 | + | let area: i32 = (n * 10); |
|
| 9 | + | let result: i32 = MAX * 2; |
|
| 10 | + | ||
| 11 | + | if (DEBUG) { |
|
| 12 | + | return (result + area) - 38; |
|
| 13 | + | } |
|
| 14 | + | return (0) - 38; |
|
| 15 | + | } |
lib/std/arch/rv64/tests/const.char.rad
added
+10 -0
| 1 | + | ||
| 2 | + | const LETTER: u8 = 'A'; |
|
| 3 | + | const DIGIT: u8 = '0'; |
|
| 4 | + | ||
| 5 | + | @default fn main() -> u8 { |
|
| 6 | + | if (DIGIT == '0') { |
|
| 7 | + | return (LETTER) - 65; |
|
| 8 | + | } |
|
| 9 | + | return (0) - 65; |
|
| 10 | + | } |
lib/std/arch/rv64/tests/const.fn.array.rad
added
+37 -0
| 1 | + | //! Test function pointers stored in const/static arrays. |
|
| 2 | + | //! Verifies that function references are properly emitted as |
|
| 3 | + | //! data items and can be called through the array. |
|
| 4 | + | //! returns: 0 |
|
| 5 | + | ||
| 6 | + | fn add(a: i32, b: i32) -> i32 { |
|
| 7 | + | return a + b; |
|
| 8 | + | } |
|
| 9 | + | ||
| 10 | + | fn sub(a: i32, b: i32) -> i32 { |
|
| 11 | + | return a - b; |
|
| 12 | + | } |
|
| 13 | + | ||
| 14 | + | fn mul(a: i32, b: i32) -> i32 { |
|
| 15 | + | return a * b; |
|
| 16 | + | } |
|
| 17 | + | ||
| 18 | + | const OPS: [fn(i32, i32) -> i32; 3] = [add, sub, mul]; |
|
| 19 | + | ||
| 20 | + | @default fn main() -> i32 { |
|
| 21 | + | // add(10, 3) == 13 |
|
| 22 | + | let r0 = OPS[0](10, 3); |
|
| 23 | + | if r0 != 13 { |
|
| 24 | + | return 1; |
|
| 25 | + | } |
|
| 26 | + | // sub(10, 3) == 7 |
|
| 27 | + | let r1 = OPS[1](10, 3); |
|
| 28 | + | if r1 != 7 { |
|
| 29 | + | return 2; |
|
| 30 | + | } |
|
| 31 | + | // mul(10, 3) == 30 |
|
| 32 | + | let r2 = OPS[2](10, 3); |
|
| 33 | + | if r2 != 30 { |
|
| 34 | + | return 3; |
|
| 35 | + | } |
|
| 36 | + | return 0; |
|
| 37 | + | } |
lib/std/arch/rv64/tests/const.record.array.rad
added
+53 -0
| 1 | + | //! returns: 129 |
|
| 2 | + | //! Test array of record constants. |
|
| 3 | + | ||
| 4 | + | record Point { |
|
| 5 | + | x: i32, |
|
| 6 | + | y: i32, |
|
| 7 | + | } |
|
| 8 | + | ||
| 9 | + | // A constant array of structs |
|
| 10 | + | const POINTS: [Point; 3] = [ |
|
| 11 | + | Point { x: 1, y: 2 }, |
|
| 12 | + | Point { x: 3, y: 4 }, |
|
| 13 | + | Point { x: 5, y: 6 } |
|
| 14 | + | ]; |
|
| 15 | + | ||
| 16 | + | fn sumPoints(points: [Point; 3]) -> i32 { |
|
| 17 | + | let mut sum: i32 = 0; |
|
| 18 | + | ||
| 19 | + | for p in (points) { |
|
| 20 | + | sum = sum + p.x + p.y; |
|
| 21 | + | } |
|
| 22 | + | return sum; |
|
| 23 | + | } |
|
| 24 | + | ||
| 25 | + | // Array of structs as a function parameter |
|
| 26 | + | fn checkPoints(points: [Point; 3]) -> bool { |
|
| 27 | + | return (points[0].x == POINTS[0].x and |
|
| 28 | + | points[1].y == POINTS[1].y and |
|
| 29 | + | points[2].x == POINTS[2].x); |
|
| 30 | + | } |
|
| 31 | + | ||
| 32 | + | @default fn main() -> i32 { |
|
| 33 | + | // Sum all x and y values: (1 + 2) + (3 + 4) + (5 + 6) = 21 |
|
| 34 | + | let total: i32 = sumPoints(POINTS); |
|
| 35 | + | ||
| 36 | + | // Create a modified array of points |
|
| 37 | + | let morePoints: [Point; 3] = [ |
|
| 38 | + | Point { x: 10, y: 11 }, |
|
| 39 | + | Point { x: 12, y: 13 }, |
|
| 40 | + | Point { x: 14, y: 15 } |
|
| 41 | + | ]; |
|
| 42 | + | ||
| 43 | + | // Sum of morePoints: (10 + 11) + (12 + 13) + (14 + 15) = 75 |
|
| 44 | + | let moreTotal: i32 = sumPoints(morePoints); |
|
| 45 | + | ||
| 46 | + | // Verify that checkPoints works |
|
| 47 | + | let isValid: bool = checkPoints(POINTS); |
|
| 48 | + | ||
| 49 | + | if (isValid) { |
|
| 50 | + | return total + (moreTotal - total) * 2; |
|
| 51 | + | } |
|
| 52 | + | return 1; |
|
| 53 | + | } |
lib/std/arch/rv64/tests/const.record.array.simple.rad
added
+24 -0
| 1 | + | // Test array of record constants. |
|
| 2 | + | ||
| 3 | + | record Point { |
|
| 4 | + | x: i32, |
|
| 5 | + | y: i32, |
|
| 6 | + | } |
|
| 7 | + | ||
| 8 | + | // Define an array of record constants |
|
| 9 | + | const POINTS: [Point; 3] = [ |
|
| 10 | + | Point { x: 1, y: 2 }, |
|
| 11 | + | Point { x: 3, y: 4 }, |
|
| 12 | + | Point { x: 5, y: 6 } |
|
| 13 | + | ]; |
|
| 14 | + | ||
| 15 | + | @default fn main() -> i32 { |
|
| 16 | + | let mut sum: i32 = 0; |
|
| 17 | + | ||
| 18 | + | // Add all points' x and y values |
|
| 19 | + | sum = sum + POINTS[0].x + POINTS[0].y; // 1 + 2 = 3 |
|
| 20 | + | sum = sum + POINTS[1].x + POINTS[1].y; // 3 + 4 = 7 |
|
| 21 | + | sum = sum + POINTS[2].x + POINTS[2].y; // 5 + 6 = 11 |
|
| 22 | + | ||
| 23 | + | return (sum) - 21; // 3 + 7 + 11 = 21 |
|
| 24 | + | } |
lib/std/arch/rv64/tests/const.record.ctor.rad
added
+11 -0
| 1 | + | //! Constant unlabeled record constructor. |
|
| 2 | + | record Pair(i32, i32); |
|
| 3 | + | ||
| 4 | + | const P: Pair = Pair(40, 2); |
|
| 5 | + | ||
| 6 | + | @default fn main() -> i32 { |
|
| 7 | + | if P != Pair(40, 2) { |
|
| 8 | + | return 1; |
|
| 9 | + | } |
|
| 10 | + | return 0; |
|
| 11 | + | } |
lib/std/arch/rv64/tests/const.record.fn.rad
added
+24 -0
| 1 | + | //! returns: 7 |
|
| 2 | + | //! Test record constants passed to functions. |
|
| 3 | + | ||
| 4 | + | record Point { |
|
| 5 | + | x: i32, |
|
| 6 | + | y: i32, |
|
| 7 | + | } |
|
| 8 | + | ||
| 9 | + | const ORIGIN: Point = Point { x: 3, y: 4 }; |
|
| 10 | + | ||
| 11 | + | fn getX(p: Point) -> i32 { |
|
| 12 | + | return p.x; |
|
| 13 | + | } |
|
| 14 | + | ||
| 15 | + | fn getY(p: Point) -> i32 { |
|
| 16 | + | return p.y; |
|
| 17 | + | } |
|
| 18 | + | ||
| 19 | + | @default fn main() -> i32 { |
|
| 20 | + | let x: i32 = getX(ORIGIN); |
|
| 21 | + | let y: i32 = getY(ORIGIN); |
|
| 22 | + | ||
| 23 | + | return x + y; |
|
| 24 | + | } |
lib/std/arch/rv64/tests/const.record.rad
added
+12 -0
| 1 | + | // Test record constants. |
|
| 2 | + | ||
| 3 | + | record Point { |
|
| 4 | + | x: i32, |
|
| 5 | + | y: i32, |
|
| 6 | + | } |
|
| 7 | + | ||
| 8 | + | const ORIGIN: Point = Point { x: 3, y: 4 }; |
|
| 9 | + | ||
| 10 | + | @default fn main() -> i32 { |
|
| 11 | + | return (ORIGIN.x + ORIGIN.y) - 7; |
|
| 12 | + | } |
lib/std/arch/rv64/tests/const.slice.param.rad
added
+16 -0
| 1 | + | //! returns: 36 |
|
| 2 | + | ||
| 3 | + | const DATA: [i32; 4] = [3, 5, 7, 9]; |
|
| 4 | + | ||
| 5 | + | fn sumPair(slice: *[i32]) -> i32 { |
|
| 6 | + | return slice[0] + slice[1]; |
|
| 7 | + | } |
|
| 8 | + | ||
| 9 | + | @default fn main() -> i32 { |
|
| 10 | + | let all: *[i32] = &DATA[..]; |
|
| 11 | + | let head: i32 = sumPair(all); |
|
| 12 | + | let tail: i32 = sumPair(all[2..]); |
|
| 13 | + | let mid: i32 = sumPair(all[1..3]); |
|
| 14 | + | ||
| 15 | + | return head + tail + mid; |
|
| 16 | + | } |
lib/std/arch/rv64/tests/const.union.payload.ctor.rad
added
+21 -0
| 1 | + | //! Constant union payload constructor call. |
|
| 2 | + | union Value { |
|
| 3 | + | Int(i32), |
|
| 4 | + | Bool(bool), |
|
| 5 | + | None, |
|
| 6 | + | } |
|
| 7 | + | ||
| 8 | + | const V: Value = Value::Int(42); |
|
| 9 | + | ||
| 10 | + | @default fn main() -> i32 { |
|
| 11 | + | match V { |
|
| 12 | + | case Value::Int(x) => { |
|
| 13 | + | if x != 42 { |
|
| 14 | + | return 1; |
|
| 15 | + | } |
|
| 16 | + | } |
|
| 17 | + | case Value::Bool(_) => return 2, |
|
| 18 | + | case Value::None => return 3, |
|
| 19 | + | } |
|
| 20 | + | return 0; |
|
| 21 | + | } |
lib/std/arch/rv64/tests/const.union.record.literal.rad
added
+19 -0
| 1 | + | //! Constant union record payload literal. |
|
| 2 | + | union Expr { |
|
| 3 | + | Nil, |
|
| 4 | + | Pair { first: i32, second: i32 }, |
|
| 5 | + | } |
|
| 6 | + | ||
| 7 | + | const E: Expr = Expr::Pair { first: 20, second: 22 }; |
|
| 8 | + | ||
| 9 | + | @default fn main() -> i32 { |
|
| 10 | + | match E { |
|
| 11 | + | case Expr::Nil => return 1, |
|
| 12 | + | case Expr::Pair { first, second } => { |
|
| 13 | + | if first + second != 42 { |
|
| 14 | + | return 2; |
|
| 15 | + | } |
|
| 16 | + | } |
|
| 17 | + | } |
|
| 18 | + | return 0; |
|
| 19 | + | } |
lib/std/arch/rv64/tests/data.array.rad
added
+33 -0
| 1 | + | //! Test read-only data access for arrays of various types. |
|
| 2 | + | const U8S: [u8; 2] = [10, 20]; |
|
| 3 | + | const U16S: [u16; 2] = [1000, 2000]; |
|
| 4 | + | const U32S: [u32; 2] = [100000, 200000]; |
|
| 5 | + | const I8S: [i8; 2] = [-10, 10]; |
|
| 6 | + | const I16S: [i16; 2] = [-1000, 1000]; |
|
| 7 | + | const I32S: [i32; 2] = [-100000, 100000]; |
|
| 8 | + | const BOOLS: [bool; 2] = [true, false]; |
|
| 9 | + | ||
| 10 | + | @default fn main() -> i32 { |
|
| 11 | + | if U8S[0] != 10 { return 1; } |
|
| 12 | + | if U8S[1] != 20 { return 2; } |
|
| 13 | + | ||
| 14 | + | if U16S[0] != 1000 { return 3; } |
|
| 15 | + | if U16S[1] != 2000 { return 4; } |
|
| 16 | + | ||
| 17 | + | if U32S[0] != 100000 { return 5; } |
|
| 18 | + | if U32S[1] != 200000 { return 6; } |
|
| 19 | + | ||
| 20 | + | if I8S[0] != -10 { return 7; } |
|
| 21 | + | if I8S[1] != 10 { return 8; } |
|
| 22 | + | ||
| 23 | + | if I16S[0] != -1000 { return 9; } |
|
| 24 | + | if I16S[1] != 1000 { return 10; } |
|
| 25 | + | ||
| 26 | + | if I32S[0] != -100000 { return 11; } |
|
| 27 | + | if I32S[1] != 100000 { return 12; } |
|
| 28 | + | ||
| 29 | + | if BOOLS[0] != true { return 13; } |
|
| 30 | + | if BOOLS[1] != false { return 14; } |
|
| 31 | + | ||
| 32 | + | return 0; |
|
| 33 | + | } |
lib/std/arch/rv64/tests/data.bool.rad
added
+12 -0
| 1 | + | //! Test read-only data access for `bool` type. |
|
| 2 | + | const T: bool = true; |
|
| 3 | + | const F: bool = false; |
|
| 4 | + | ||
| 5 | + | @default fn main() -> i32 { |
|
| 6 | + | if T != true { return 1; } |
|
| 7 | + | if F != false { return 2; } |
|
| 8 | + | if not T { return 3; } |
|
| 9 | + | if F { return 4; } |
|
| 10 | + | ||
| 11 | + | return 0; |
|
| 12 | + | } |
lib/std/arch/rv64/tests/data.i16.rad
added
+14 -0
| 1 | + | //! Test read-only data access for `i16` type. |
|
| 2 | + | const A: i16 = 0; |
|
| 3 | + | const B: i16 = 32767; |
|
| 4 | + | const C: i16 = -32768; |
|
| 5 | + | const D: i16 = -1; |
|
| 6 | + | ||
| 7 | + | @default fn main() -> i32 { |
|
| 8 | + | if A != 0 { return 1; } |
|
| 9 | + | if B != 32767 { return 2; } |
|
| 10 | + | if C != -32768 { return 3; } |
|
| 11 | + | if D != -1 { return 4; } |
|
| 12 | + | ||
| 13 | + | return 0; |
|
| 14 | + | } |
lib/std/arch/rv64/tests/data.i32.rad
added
+14 -0
| 1 | + | //! Test read-only data access for `i32` type. |
|
| 2 | + | const A: i32 = 0; |
|
| 3 | + | const B: i32 = 2147483647; |
|
| 4 | + | const C: i32 = -2147483648; |
|
| 5 | + | const D: i32 = -1; |
|
| 6 | + | ||
| 7 | + | @default fn main() -> i32 { |
|
| 8 | + | if A != 0 { return 1; } |
|
| 9 | + | if B != 2147483647 { return 2; } |
|
| 10 | + | if C != -2147483648 { return 3; } |
|
| 11 | + | if D != -1 { return 4; } |
|
| 12 | + | ||
| 13 | + | return 0; |
|
| 14 | + | } |
lib/std/arch/rv64/tests/data.i8.rad
added
+14 -0
| 1 | + | //! Test read-only data access for `i8` type. |
|
| 2 | + | const A: i8 = 0; |
|
| 3 | + | const B: i8 = 127; |
|
| 4 | + | const C: i8 = -128; |
|
| 5 | + | const D: i8 = -1; |
|
| 6 | + | ||
| 7 | + | @default fn main() -> i32 { |
|
| 8 | + | if A != 0 { return 1; } |
|
| 9 | + | if B != 127 { return 2; } |
|
| 10 | + | if C != -128 { return 3; } |
|
| 11 | + | if D != -1 { return 4; } |
|
| 12 | + | ||
| 13 | + | return 0; |
|
| 14 | + | } |
lib/std/arch/rv64/tests/data.record.rad
added
+31 -0
| 1 | + | //! Test read-only data access for `record` types. |
|
| 2 | + | record Point { |
|
| 3 | + | x: i32, |
|
| 4 | + | y: i32, |
|
| 5 | + | } |
|
| 6 | + | ||
| 7 | + | record Mixed { |
|
| 8 | + | a: u8, |
|
| 9 | + | b: u16, |
|
| 10 | + | c: u32, |
|
| 11 | + | d: bool, |
|
| 12 | + | } |
|
| 13 | + | ||
| 14 | + | const ORIGIN: Point = Point { x: 0, y: 0 }; |
|
| 15 | + | const P1: Point = Point { x: 100, y: -50 }; |
|
| 16 | + | const M1: Mixed = Mixed { a: 255, b: 1000, c: 100000, d: true }; |
|
| 17 | + | ||
| 18 | + | @default fn main() -> i32 { |
|
| 19 | + | if ORIGIN.x != 0 { return 1; } |
|
| 20 | + | if ORIGIN.y != 0 { return 2; } |
|
| 21 | + | ||
| 22 | + | if P1.x != 100 { return 3; } |
|
| 23 | + | if P1.y != -50 { return 4; } |
|
| 24 | + | ||
| 25 | + | if M1.a != 255 { return 5; } |
|
| 26 | + | if M1.b != 1000 { return 6; } |
|
| 27 | + | if M1.c != 100000 { return 7; } |
|
| 28 | + | if M1.d != true { return 8; } |
|
| 29 | + | ||
| 30 | + | return 0; |
|
| 31 | + | } |
lib/std/arch/rv64/tests/data.simple.rad
added
+19 -0
| 1 | + | //! Simple test for data symbol access. |
|
| 2 | + | const DATA: [u8; 4] = [10, 20, 30, 40]; |
|
| 3 | + | ||
| 4 | + | @default fn main() -> i32 { |
|
| 5 | + | if DATA[0] == 10 and |
|
| 6 | + | DATA[1] == 20 and |
|
| 7 | + | DATA[2] == 30 and |
|
| 8 | + | DATA[3] == 40 and DATA.len == 4 { |
|
| 9 | + | let ptr = &DATA[..]; |
|
| 10 | + | ||
| 11 | + | if ptr[0] == 10 and |
|
| 12 | + | ptr[1] == 20 and |
|
| 13 | + | ptr[2] == 30 and |
|
| 14 | + | ptr[3] == 40 and ptr.len == 4 { |
|
| 15 | + | return 0; |
|
| 16 | + | } |
|
| 17 | + | } |
|
| 18 | + | return 1; |
|
| 19 | + | } |
lib/std/arch/rv64/tests/data.u16.rad
added
+12 -0
| 1 | + | //! Test read-only data access for `u16` type. |
|
| 2 | + | const A: u16 = 0; |
|
| 3 | + | const B: u16 = 32767; |
|
| 4 | + | const C: u16 = 65535; |
|
| 5 | + | ||
| 6 | + | @default fn main() -> i32 { |
|
| 7 | + | if A != 0 { return 1; } |
|
| 8 | + | if B != 32767 { return 2; } |
|
| 9 | + | if C != 65535 { return 3; } |
|
| 10 | + | ||
| 11 | + | return 0; |
|
| 12 | + | } |
lib/std/arch/rv64/tests/data.u32.rad
added
+12 -0
| 1 | + | //! Test read-only data access for `u32` type. |
|
| 2 | + | const A: u32 = 0; |
|
| 3 | + | const B: u32 = 2147483647; |
|
| 4 | + | const C: u32 = 4294967295; |
|
| 5 | + | ||
| 6 | + | @default fn main() -> i32 { |
|
| 7 | + | if A != 0 { return 1; } |
|
| 8 | + | if B != 2147483647 { return 2; } |
|
| 9 | + | if C != 4294967295 { return 3; } |
|
| 10 | + | ||
| 11 | + | return 0; |
|
| 12 | + | } |
lib/std/arch/rv64/tests/data.u8.rad
added
+12 -0
| 1 | + | //! Test read-only data access for `u8` type. |
|
| 2 | + | const A: u8 = 0; |
|
| 3 | + | const B: u8 = 127; |
|
| 4 | + | const C: u8 = 255; |
|
| 5 | + | ||
| 6 | + | @default fn main() -> i32 { |
|
| 7 | + | if A != 0 { return 1; } |
|
| 8 | + | if B != 127 { return 2; } |
|
| 9 | + | if C != 255 { return 3; } |
|
| 10 | + | ||
| 11 | + | return 0; |
|
| 12 | + | } |
lib/std/arch/rv64/tests/data.union.rad
added
+48 -0
| 1 | + | //! Test read-only data access for `union` types. |
|
| 2 | + | union Color { |
|
| 3 | + | Red, |
|
| 4 | + | Green, |
|
| 5 | + | Blue, |
|
| 6 | + | } |
|
| 7 | + | ||
| 8 | + | union Value { |
|
| 9 | + | None, |
|
| 10 | + | Small, |
|
| 11 | + | Large, |
|
| 12 | + | } |
|
| 13 | + | ||
| 14 | + | const C1: Color = Color::Red; |
|
| 15 | + | const C2: Color = Color::Green; |
|
| 16 | + | const C3: Color = Color::Blue; |
|
| 17 | + | ||
| 18 | + | const V1: Value = Value::None; |
|
| 19 | + | const V2: Value = Value::Small; |
|
| 20 | + | const V3: Value = Value::Large; |
|
| 21 | + | ||
| 22 | + | @default fn main() -> i32 { |
|
| 23 | + | match C1 { |
|
| 24 | + | case Color::Red => {}, |
|
| 25 | + | else => { return 1; }, |
|
| 26 | + | } |
|
| 27 | + | match C2 { |
|
| 28 | + | case Color::Green => {}, |
|
| 29 | + | else => { return 2; }, |
|
| 30 | + | } |
|
| 31 | + | match C3 { |
|
| 32 | + | case Color::Blue => {}, |
|
| 33 | + | else => { return 3; }, |
|
| 34 | + | } |
|
| 35 | + | match V1 { |
|
| 36 | + | case Value::None => {}, |
|
| 37 | + | else => { return 4; }, |
|
| 38 | + | } |
|
| 39 | + | match V2 { |
|
| 40 | + | case Value::Small => {}, |
|
| 41 | + | else => { return 5; }, |
|
| 42 | + | } |
|
| 43 | + | match V3 { |
|
| 44 | + | case Value::Large => {}, |
|
| 45 | + | else => { return 6; }, |
|
| 46 | + | } |
|
| 47 | + | return 0; |
|
| 48 | + | } |
lib/std/arch/rv64/tests/debug.assert.false.rad
added
+5 -0
| 1 | + | //! returns: 133 |
|
| 2 | + | //! Test that panic triggers EBREAK (exit code 133). |
|
| 3 | + | @default fn main() -> i32 { |
|
| 4 | + | panic; |
|
| 5 | + | } |
lib/std/arch/rv64/tests/debug.assert.true.rad
added
+8 -0
| 1 | + | ||
| 2 | + | @default fn main() -> i32 { |
|
| 3 | + | if not (true) { panic "assert"; } |
|
| 4 | + | if not (1 == 1) { panic "assert"; } |
|
| 5 | + | if not (5 > 3) { panic "assert"; } |
|
| 6 | + | ||
| 7 | + | return 0; |
|
| 8 | + | } |
lib/std/arch/rv64/tests/debug.tag.rad
added
+34 -0
| 1 | + | //! returns: 0 |
|
| 2 | + | ||
| 3 | + | union Huge { |
|
| 4 | + | Small(u32), |
|
| 5 | + | Large { |
|
| 6 | + | payload: [u32; 32], |
|
| 7 | + | flag: u32, |
|
| 8 | + | }, |
|
| 9 | + | } |
|
| 10 | + | ||
| 11 | + | @default fn main() -> u32 { |
|
| 12 | + | let padding: [u32; 600] = [0; 600]; |
|
| 13 | + | if padding[0] != 0 { |
|
| 14 | + | return 99; |
|
| 15 | + | } |
|
| 16 | + | ||
| 17 | + | let big: Huge = Huge::Large { |
|
| 18 | + | payload: [0; 32], |
|
| 19 | + | flag: 7, |
|
| 20 | + | }; |
|
| 21 | + | ||
| 22 | + | match big { |
|
| 23 | + | case Huge::Large { payload, flag } => { |
|
| 24 | + | if flag == 7 { |
|
| 25 | + | return 0; |
|
| 26 | + | } else { |
|
| 27 | + | return 1; |
|
| 28 | + | } |
|
| 29 | + | } |
|
| 30 | + | case Huge::Small(_) => { |
|
| 31 | + | return 2; |
|
| 32 | + | } |
|
| 33 | + | } |
|
| 34 | + | } |
lib/std/arch/rv64/tests/edge.cases.2.rad
added
+21 -0
| 1 | + | //! Test scanner peek with optional return. |
|
| 2 | + | ||
| 3 | + | pub record Scanner { |
|
| 4 | + | source: *[u8], |
|
| 5 | + | } |
|
| 6 | + | ||
| 7 | + | fn peek(s: *Scanner) -> ?u8 { |
|
| 8 | + | if 0 + 0 >= s.source.len { |
|
| 9 | + | return nil; |
|
| 10 | + | } |
|
| 11 | + | return 'a'; |
|
| 12 | + | } |
|
| 13 | + | ||
| 14 | + | @default fn main() -> i32 { |
|
| 15 | + | let s: Scanner = Scanner { source: "xyz" }; |
|
| 16 | + | ||
| 17 | + | if let z = peek(&s) { |
|
| 18 | + | return 0; |
|
| 19 | + | } |
|
| 20 | + | return 1; |
|
| 21 | + | } |
lib/std/arch/rv64/tests/edge.cases.3.rad
added
+33 -0
| 1 | + | //! returns: 97 |
|
| 2 | + | //! Test scanner current character access. |
|
| 3 | + | ||
| 4 | + | pub record Scanner { |
|
| 5 | + | source: *[u8], |
|
| 6 | + | cursor: u32, |
|
| 7 | + | } |
|
| 8 | + | ||
| 9 | + | fn isEof(s: *Scanner) -> bool { |
|
| 10 | + | return s.cursor >= s.source.len; |
|
| 11 | + | } |
|
| 12 | + | ||
| 13 | + | pub fn current(s: *Scanner) -> ?u8 { |
|
| 14 | + | if isEof(s) { |
|
| 15 | + | return nil; |
|
| 16 | + | } |
|
| 17 | + | return s.source[s.cursor]; |
|
| 18 | + | } |
|
| 19 | + | ||
| 20 | + | fn peek(s: *Scanner) -> ?u8 { |
|
| 21 | + | if s.cursor + 1 >= s.source.len { |
|
| 22 | + | return nil; |
|
| 23 | + | } |
|
| 24 | + | return s.source[s.cursor + 1]; |
|
| 25 | + | } |
|
| 26 | + | ||
| 27 | + | @default fn main() -> u8 { |
|
| 28 | + | let s: Scanner = Scanner { source: "abc", cursor: 0 }; |
|
| 29 | + | if let c = current(&s) { |
|
| 30 | + | return c; |
|
| 31 | + | } |
|
| 32 | + | return 1; |
|
| 33 | + | } |
lib/std/arch/rv64/tests/edge.cases.4.rad
added
+62 -0
| 1 | + | //! returns: 0 |
|
| 2 | + | ||
| 3 | + | union Signedness { |
|
| 4 | + | Signed, |
|
| 5 | + | Unsigned, |
|
| 6 | + | } |
|
| 7 | + | ||
| 8 | + | union TypeSig { |
|
| 9 | + | Integer { |
|
| 10 | + | width: u8, |
|
| 11 | + | sign: Signedness, |
|
| 12 | + | }, |
|
| 13 | + | } |
|
| 14 | + | ||
| 15 | + | union NodeValue { |
|
| 16 | + | TypeSig(TypeSig), |
|
| 17 | + | } |
|
| 18 | + | ||
| 19 | + | record Node { |
|
| 20 | + | value: NodeValue, |
|
| 21 | + | } |
|
| 22 | + | ||
| 23 | + | fn node(nodes: *mut Node, count: *mut u32, value: NodeValue) -> *Node { |
|
| 24 | + | let index = *count; |
|
| 25 | + | let slot = nodes + index; |
|
| 26 | + | *slot = Node { value }; |
|
| 27 | + | *count = index + 1; |
|
| 28 | + | return slot; |
|
| 29 | + | } |
|
| 30 | + | ||
| 31 | + | fn nodeTypeInt(nodes: *mut Node, count: *mut u32, width: u8, sign: Signedness) -> *Node { |
|
| 32 | + | return node(nodes, count, NodeValue::TypeSig( |
|
| 33 | + | TypeSig::Integer { width, sign } |
|
| 34 | + | )); |
|
| 35 | + | } |
|
| 36 | + | ||
| 37 | + | @default fn main() -> i32 { |
|
| 38 | + | let direct = TypeSig::Integer { |
|
| 39 | + | width: 4, |
|
| 40 | + | sign: Signedness::Signed, |
|
| 41 | + | }; |
|
| 42 | + | let case TypeSig::Integer { width: w1, .. } = direct |
|
| 43 | + | else return 50; |
|
| 44 | + | ||
| 45 | + | if w1 != 4 { |
|
| 46 | + | return 51; |
|
| 47 | + | } |
|
| 48 | + | let mut nodes: [Node; 2] = undefined; |
|
| 49 | + | let nodes_ptr: *mut Node = &mut nodes[0]; |
|
| 50 | + | let mut count: u32 = 0; |
|
| 51 | + | let type_node = nodeTypeInt(nodes_ptr, &mut count, 4, Signedness::Signed); |
|
| 52 | + | let case NodeValue::TypeSig(sig) = type_node.value |
|
| 53 | + | else return 40; |
|
| 54 | + | ||
| 55 | + | let case TypeSig::Integer { width: w2, .. } = sig |
|
| 56 | + | else return 41; |
|
| 57 | + | ||
| 58 | + | if w2 != 4 { |
|
| 59 | + | return w2 as i32; |
|
| 60 | + | } |
|
| 61 | + | return 0; |
|
| 62 | + | } |
lib/std/arch/rv64/tests/edge.cases.5.rad
added
+64 -0
| 1 | + | ||
| 2 | + | union TestError { |
|
| 3 | + | Fail, |
|
| 4 | + | } |
|
| 5 | + | ||
| 6 | + | record Derives { |
|
| 7 | + | len: i32, |
|
| 8 | + | list: [[u8; 8]; 2], |
|
| 9 | + | } |
|
| 10 | + | ||
| 11 | + | record StructField { |
|
| 12 | + | typeSize: i32, |
|
| 13 | + | isSigned: bool, |
|
| 14 | + | value: ?i32, |
|
| 15 | + | } |
|
| 16 | + | ||
| 17 | + | record FieldNode { |
|
| 18 | + | value: FieldValue, |
|
| 19 | + | } |
|
| 20 | + | ||
| 21 | + | record FieldList { |
|
| 22 | + | len: i32, |
|
| 23 | + | list: [FieldNode; 2], |
|
| 24 | + | } |
|
| 25 | + | ||
| 26 | + | record StructDecl { |
|
| 27 | + | derives: Derives, |
|
| 28 | + | fields: FieldList, |
|
| 29 | + | } |
|
| 30 | + | ||
| 31 | + | record Node { |
|
| 32 | + | value: NodeValue, |
|
| 33 | + | } |
|
| 34 | + | ||
| 35 | + | union FieldValue { |
|
| 36 | + | StructField(StructField), |
|
| 37 | + | Other(i32), |
|
| 38 | + | } |
|
| 39 | + | ||
| 40 | + | union NodeValue { |
|
| 41 | + | StructDecl(StructDecl), |
|
| 42 | + | Other(i32), |
|
| 43 | + | } |
|
| 44 | + | ||
| 45 | + | fn expectIntType(a: i32, b: i32, c: bool, d: bool) throws (TestError) {} |
|
| 46 | + | ||
| 47 | + | fn crash() throws (TestError) { |
|
| 48 | + | let node = Node { value: NodeValue::Other(123) }; |
|
| 49 | + | let case NodeValue::StructDecl(decl) = node.value |
|
| 50 | + | else throw TestError::Fail; |
|
| 51 | + | ||
| 52 | + | let field1 = decl.fields.list[1]; |
|
| 53 | + | let case FieldValue::StructField(f1) = field1.value |
|
| 54 | + | else throw TestError::Fail; |
|
| 55 | + | ||
| 56 | + | try expectIntType(f1.typeSize, 4, f1.isSigned, true); |
|
| 57 | + | } |
|
| 58 | + | ||
| 59 | + | @default fn main() -> i32 { |
|
| 60 | + | try crash() catch { |
|
| 61 | + | return (1) - 1; |
|
| 62 | + | }; |
|
| 63 | + | return (0) - 1; |
|
| 64 | + | } |
lib/std/arch/rv64/tests/edge.cases.6.rad
added
+121 -0
| 1 | + | //! returns: 0 |
|
| 2 | + | //! Standalone repro: writing a record into a static slice clobbers the slice header. |
|
| 3 | + | ||
| 4 | + | record Entry { |
|
| 5 | + | a: u32, |
|
| 6 | + | b: u32, |
|
| 7 | + | c: u32, |
|
| 8 | + | d: u32, |
|
| 9 | + | e: u32, |
|
| 10 | + | f: u32, |
|
| 11 | + | g: u32, |
|
| 12 | + | h: u32, |
|
| 13 | + | i: u32, |
|
| 14 | + | j: u32, |
|
| 15 | + | k: u32, |
|
| 16 | + | l: u32, |
|
| 17 | + | m: u32, |
|
| 18 | + | n: u32, |
|
| 19 | + | o: u32, |
|
| 20 | + | p: u32, |
|
| 21 | + | q: u32, |
|
| 22 | + | r: u32, |
|
| 23 | + | s: u32, |
|
| 24 | + | t: u32, |
|
| 25 | + | } |
|
| 26 | + | ||
| 27 | + | record Analyzer { |
|
| 28 | + | pad0: u32, |
|
| 29 | + | pad1: u32, |
|
| 30 | + | entries: *mut [Entry], |
|
| 31 | + | len: u32, |
|
| 32 | + | } |
|
| 33 | + | ||
| 34 | + | static STORAGE: [Entry; 2] = [ |
|
| 35 | + | Entry { a: 0, b: 0, c: 0, d: 0, e: 0, f: 0, g: 0, h: 0, i: 0, j: 0, k: 0, l: 0, m: 0, n: 0, o: 0, p: 0, q: 0, r: 0, s: 0, t: 0 }, |
|
| 36 | + | Entry { a: 0, b: 0, c: 0, d: 0, e: 0, f: 0, g: 0, h: 0, i: 0, j: 0, k: 0, l: 0, m: 0, n: 0, o: 0, p: 0, q: 0, r: 0, s: 0, t: 0 }, |
|
| 37 | + | ]; |
|
| 38 | + | static DEFAULT_ENTRY: Entry = Entry { |
|
| 39 | + | a: 0xAAAA1111, |
|
| 40 | + | b: 0xBBBB2222, |
|
| 41 | + | c: 0xCCCC3333, |
|
| 42 | + | d: 0xDDDD4444, |
|
| 43 | + | e: 0xEEEE5555, |
|
| 44 | + | f: 0x12345601, |
|
| 45 | + | g: 0x12345602, |
|
| 46 | + | h: 0x12345603, |
|
| 47 | + | i: 0x12345604, |
|
| 48 | + | j: 0x12345605, |
|
| 49 | + | k: 0x12345606, |
|
| 50 | + | l: 0x12345607, |
|
| 51 | + | m: 0x12345608, |
|
| 52 | + | n: 0x12345609, |
|
| 53 | + | o: 0x1234560A, |
|
| 54 | + | p: 0x1234560B, |
|
| 55 | + | q: 0x1234560C, |
|
| 56 | + | r: 0x1234560D, |
|
| 57 | + | s: 0x1234560E, |
|
| 58 | + | t: 0x1234560F, |
|
| 59 | + | }; |
|
| 60 | + | static ANALYZER: Analyzer = undefined; |
|
| 61 | + | ||
| 62 | + | fn fillEntry(out: *mut Entry) { |
|
| 63 | + | *out = DEFAULT_ENTRY; |
|
| 64 | + | } |
|
| 65 | + | ||
| 66 | + | fn mkEntry() -> Entry { |
|
| 67 | + | return DEFAULT_ENTRY; |
|
| 68 | + | } |
|
| 69 | + | ||
| 70 | + | fn mkOptEntry(ok: bool) -> ?Entry { |
|
| 71 | + | if not ok { |
|
| 72 | + | return nil; |
|
| 73 | + | } |
|
| 74 | + | return mkEntry(); |
|
| 75 | + | } |
|
| 76 | + | ||
| 77 | + | fn init(entries: *mut [Entry]) { |
|
| 78 | + | ANALYZER = Analyzer { |
|
| 79 | + | pad0: 0xDEADAAA0, |
|
| 80 | + | pad1: 0xDEADAAA1, |
|
| 81 | + | entries, |
|
| 82 | + | len: 0, |
|
| 83 | + | }; |
|
| 84 | + | } |
|
| 85 | + | ||
| 86 | + | fn add() { |
|
| 87 | + | let idx = ANALYZER.len; |
|
| 88 | + | let entry = mkOptEntry(true) else { |
|
| 89 | + | return; |
|
| 90 | + | }; |
|
| 91 | + | ANALYZER.entries[idx] = entry; |
|
| 92 | + | ANALYZER.len = idx + 1; |
|
| 93 | + | } |
|
| 94 | + | ||
| 95 | + | fn checkHeader(expected: *[Entry]) -> i32 { |
|
| 96 | + | if ANALYZER.entries.ptr != expected.ptr or ANALYZER.entries.len != expected.len { |
|
| 97 | + | // Slice header got clobbered instead of the backing storage. |
|
| 98 | + | return 1; |
|
| 99 | + | } |
|
| 100 | + | if ANALYZER.len != 1 { |
|
| 101 | + | // Bookkeeping field was overwritten by the bad store. |
|
| 102 | + | return 2; |
|
| 103 | + | } |
|
| 104 | + | if ANALYZER.pad0 != 0xDEADAAA0 or ANALYZER.pad1 != 0xDEADAAA1 { |
|
| 105 | + | return 4; |
|
| 106 | + | } |
|
| 107 | + | ||
| 108 | + | let stored = STORAGE[0]; |
|
| 109 | + | if stored.a != 0xAAAA1111 or stored.b != 0xBBBB2222 or stored.c != 0xCCCC3333 or stored.d != 0xDDDD4444 or stored.e != 0xEEEE5555 or stored.f != 0x12345601 or stored.j != 0x12345605 or stored.t != 0x1234560F { |
|
| 110 | + | // The write never hit the backing buffer. |
|
| 111 | + | return 3; |
|
| 112 | + | } |
|
| 113 | + | return 0; |
|
| 114 | + | } |
|
| 115 | + | ||
| 116 | + | @default fn main() -> i32 { |
|
| 117 | + | let target = &mut STORAGE[..]; |
|
| 118 | + | init(target); |
|
| 119 | + | add(); |
|
| 120 | + | return checkHeader(target); |
|
| 121 | + | } |
lib/std/arch/rv64/tests/edge.cases.7.addr.bug.rad
added
+15 -0
| 1 | + | //! returns: 42 |
|
| 2 | + | ||
| 3 | + | record PtrHolder { |
|
| 4 | + | ptr: *mut i32, |
|
| 5 | + | } |
|
| 6 | + | ||
| 7 | + | static target: i32 = 10; |
|
| 8 | + | static holder: PtrHolder = undefined; |
|
| 9 | + | ||
| 10 | + | @default fn main() -> i32 { |
|
| 11 | + | holder.ptr = &mut target; |
|
| 12 | + | *holder.ptr = 42; |
|
| 13 | + | ||
| 14 | + | return target; |
|
| 15 | + | } |
lib/std/arch/rv64/tests/edge.cases.8.bug.rad
added
+27 -0
| 1 | + | //! When a nested record field from a static variable is passed by value, |
|
| 2 | + | //! we only the base address and ignores the offset, |
|
| 3 | + | //! causing the function to receive the wrong data. |
|
| 4 | + | ||
| 5 | + | record Inner { |
|
| 6 | + | value: i32, |
|
| 7 | + | } |
|
| 8 | + | ||
| 9 | + | record Outer { |
|
| 10 | + | padding: i32, |
|
| 11 | + | inner: Inner, |
|
| 12 | + | } |
|
| 13 | + | ||
| 14 | + | static global: Outer = undefined; |
|
| 15 | + | ||
| 16 | + | fn readInner(i: Inner) -> i32 { |
|
| 17 | + | return i.value; |
|
| 18 | + | } |
|
| 19 | + | ||
| 20 | + | @default fn main() -> i32 { |
|
| 21 | + | global = Outer { padding: 0, inner: Inner { value: 42 } }; |
|
| 22 | + | ||
| 23 | + | if readInner(global.inner) != 42 { |
|
| 24 | + | return 1; |
|
| 25 | + | } |
|
| 26 | + | return 0; |
|
| 27 | + | } |
lib/std/arch/rv64/tests/edge.cases.rad
added
+16 -0
| 1 | + | ||
| 2 | + | pub record Scanner { |
|
| 3 | + | source: *[u8], |
|
| 4 | + | } |
|
| 5 | + | ||
| 6 | + | fn peek(s: *Scanner) -> i32 { |
|
| 7 | + | if 0 + 0 > s.source.len { |
|
| 8 | + | return 1; |
|
| 9 | + | } |
|
| 10 | + | return 0; |
|
| 11 | + | } |
|
| 12 | + | ||
| 13 | + | @default fn main() -> i32 { |
|
| 14 | + | let s: Scanner = Scanner { source: "1" }; |
|
| 15 | + | return peek(&s); |
|
| 16 | + | } |
lib/std/arch/rv64/tests/error.basic.rad
added
+11 -0
| 1 | + | ||
| 2 | + | union MyError { A, B, C } |
|
| 3 | + | ||
| 4 | + | @default fn main() -> u32 { |
|
| 5 | + | let err: MyError = MyError::B; |
|
| 6 | + | ||
| 7 | + | if err == MyError::B { |
|
| 8 | + | return 0; |
|
| 9 | + | } |
|
| 10 | + | return 1; |
|
| 11 | + | } |
lib/std/arch/rv64/tests/error.catch.rad
added
+88 -0
| 1 | + | //! returns: 0 |
|
| 2 | + | ||
| 3 | + | fn assert(cond: bool) { |
|
| 4 | + | if not cond { |
|
| 5 | + | panic; |
|
| 6 | + | } |
|
| 7 | + | } |
|
| 8 | + | ||
| 9 | + | union TestError { Fail } |
|
| 10 | + | static PTR_VALUE: i32 = 7; |
|
| 11 | + | ||
| 12 | + | @default fn main() -> u32 { |
|
| 13 | + | // Catch block with early return |
|
| 14 | + | let val1: u32 = catchWithReturn(true); |
|
| 15 | + | assert(val1 == 42); |
|
| 16 | + | ||
| 17 | + | // Catch block with early return (success case) |
|
| 18 | + | let val2: u32 = catchWithReturn(false); |
|
| 19 | + | assert(val2 == 21); |
|
| 20 | + | ||
| 21 | + | // Catch block that discards error |
|
| 22 | + | let mut flag: u32 = 0; |
|
| 23 | + | try returnsErr() catch {}; |
|
| 24 | + | flag = 1; |
|
| 25 | + | assert(flag == 1); |
|
| 26 | + | ||
| 27 | + | // Catch block with return in function |
|
| 28 | + | let val3: u32 = returnsEarly(); |
|
| 29 | + | assert(val3 == 42); |
|
| 30 | + | ||
| 31 | + | // try? converts to optional on error |
|
| 32 | + | let ptr_ok: ?*i32 = try? returnsPtrOk(); |
|
| 33 | + | if let p = ptr_ok { |
|
| 34 | + | assert(*p == 7); |
|
| 35 | + | } else { |
|
| 36 | + | assert(false); |
|
| 37 | + | } |
|
| 38 | + | ||
| 39 | + | // try? converts to nil on error |
|
| 40 | + | let ptr_err: ?*i32 = try? returnsPtrErr(); |
|
| 41 | + | assert(ptr_err == nil); |
|
| 42 | + | ||
| 43 | + | // try? on success |
|
| 44 | + | let opt_ok: ?u32 = try? returnsOk(); |
|
| 45 | + | assert(opt_ok != nil); |
|
| 46 | + | if let v = opt_ok { |
|
| 47 | + | assert(v == 21); |
|
| 48 | + | } |
|
| 49 | + | ||
| 50 | + | // try? on error |
|
| 51 | + | let opt_err: ?u32 = try? returnsErr(); |
|
| 52 | + | assert(opt_err == nil); |
|
| 53 | + | ||
| 54 | + | return 0; |
|
| 55 | + | } |
|
| 56 | + | ||
| 57 | + | fn catchWithReturn(fail: bool) -> u32 { |
|
| 58 | + | if (fail) { |
|
| 59 | + | try returnsErr() catch { |
|
| 60 | + | return 42; |
|
| 61 | + | }; |
|
| 62 | + | return 0; |
|
| 63 | + | } |
|
| 64 | + | return try! returnsOk(); |
|
| 65 | + | } |
|
| 66 | + | ||
| 67 | + | fn returnsEarly() -> u32 { |
|
| 68 | + | try returnsErr() catch { |
|
| 69 | + | return 42; |
|
| 70 | + | }; |
|
| 71 | + | return 1; |
|
| 72 | + | } |
|
| 73 | + | ||
| 74 | + | fn returnsOk() -> u32 throws (TestError) { |
|
| 75 | + | return 21; |
|
| 76 | + | } |
|
| 77 | + | ||
| 78 | + | fn returnsErr() -> u32 throws (TestError) { |
|
| 79 | + | throw TestError::Fail; |
|
| 80 | + | } |
|
| 81 | + | ||
| 82 | + | fn returnsPtrOk() -> *i32 throws (TestError) { |
|
| 83 | + | return &PTR_VALUE; |
|
| 84 | + | } |
|
| 85 | + | ||
| 86 | + | fn returnsPtrErr() -> *i32 throws (TestError) { |
|
| 87 | + | throw TestError::Fail; |
|
| 88 | + | } |
lib/std/arch/rv64/tests/error.division.zero.rad
added
+10 -0
| 1 | + | //! returns: 133 |
|
| 2 | + | //! Test division by zero handling. |
|
| 3 | + | ||
| 4 | + | @default fn main() -> i32 { |
|
| 5 | + | let x: i32 = 10; |
|
| 6 | + | let y: i32 = 0; |
|
| 7 | + | let z: i32 = x / y; |
|
| 8 | + | ||
| 9 | + | return z; |
|
| 10 | + | } |
lib/std/arch/rv64/tests/error.modulo.zero.rad
added
+10 -0
| 1 | + | //! returns: 133 |
|
| 2 | + | //! Test modulo by zero handling. |
|
| 3 | + | ||
| 4 | + | @default fn main() -> i32 { |
|
| 5 | + | let x: i32 = 10; |
|
| 6 | + | let y: i32 = 0; |
|
| 7 | + | let z: i32 = x % y; |
|
| 8 | + | ||
| 9 | + | return z; |
|
| 10 | + | } |
lib/std/arch/rv64/tests/error.slice.bounds.rad
added
+10 -0
| 1 | + | //! returns: 133 |
|
| 2 | + | //! Test slice bounds checking with runtime EBREAK. |
|
| 3 | + | ||
| 4 | + | @default fn main() -> i32 { |
|
| 5 | + | let arr: [i32; 3] = [1, 2, 3]; |
|
| 6 | + | let slice: *[i32] = &arr[..]; |
|
| 7 | + | let value: i32 = slice[3]; |
|
| 8 | + | ||
| 9 | + | return value; |
|
| 10 | + | } |
lib/std/arch/rv64/tests/error.try.bang.success.rad
added
+26 -0
| 1 | + | //! returns: 0 |
|
| 2 | + | ||
| 3 | + | fn assert(cond: bool) { |
|
| 4 | + | if not cond { |
|
| 5 | + | panic; |
|
| 6 | + | } |
|
| 7 | + | } |
|
| 8 | + | ||
| 9 | + | union PanicError { Boom } |
|
| 10 | + | ||
| 11 | + | fn makeOk(value: u32) -> u32 throws (PanicError) { |
|
| 12 | + | return value; |
|
| 13 | + | } |
|
| 14 | + | ||
| 15 | + | @default fn main() -> u32 { |
|
| 16 | + | let first: u32 = try! makeOk(7); |
|
| 17 | + | let second: u32 = 2 * try! makeOk(11); |
|
| 18 | + | ||
| 19 | + | assert(first == 7); |
|
| 20 | + | assert(second == 22); |
|
| 21 | + | ||
| 22 | + | let combined: u32 = try! makeOk(first + second); |
|
| 23 | + | assert(combined == 29); |
|
| 24 | + | ||
| 25 | + | return 0; |
|
| 26 | + | } |
lib/std/arch/rv64/tests/error.try.catch.binding.rad
added
+93 -0
| 1 | + | ||
| 2 | + | ||
| 3 | + | union TestError { Boom, Bust } |
|
| 4 | + | ||
| 5 | + | @default fn main() -> u32 { |
|
| 6 | + | // Test catching and using the error value. |
|
| 7 | + | let mut caught: u32 = 0; |
|
| 8 | + | try failWithBoom() catch e { |
|
| 9 | + | if (e == TestError::Boom) { |
|
| 10 | + | caught = 1; |
|
| 11 | + | } else { |
|
| 12 | + | caught = 2; |
|
| 13 | + | } |
|
| 14 | + | }; |
|
| 15 | + | if not (caught == 1) { panic "assert"; } |
|
| 16 | + | ||
| 17 | + | // Test catching a different error variant. |
|
| 18 | + | caught = 0; |
|
| 19 | + | try failWithBust() catch e { |
|
| 20 | + | if (e == TestError::Bust) { |
|
| 21 | + | caught = 10; |
|
| 22 | + | } else { |
|
| 23 | + | caught = 20; |
|
| 24 | + | } |
|
| 25 | + | }; |
|
| 26 | + | if not (caught == 10) { panic "assert"; } |
|
| 27 | + | ||
| 28 | + | // Test that success path doesn't execute catch block. |
|
| 29 | + | caught = 0; |
|
| 30 | + | try succeed() catch e { |
|
| 31 | + | caught = 99; |
|
| 32 | + | }; |
|
| 33 | + | if not (caught == 0) { panic "assert"; } |
|
| 34 | + | ||
| 35 | + | // Test catch block that returns early from function. |
|
| 36 | + | let early: u32 = testCatchReturn(); |
|
| 37 | + | if not (early == 100) { panic "assert"; } |
|
| 38 | + | ||
| 39 | + | // Test using success value when catch diverges. |
|
| 40 | + | let success: u32 = testCatchDiverges(); |
|
| 41 | + | if not (success == 77) { panic "assert"; } |
|
| 42 | + | ||
| 43 | + | // Test error value is correctly bound. |
|
| 44 | + | let errVal: u32 = testErrorValue(); |
|
| 45 | + | if not (errVal == 1) { panic "assert"; } |
|
| 46 | + | ||
| 47 | + | return 0; |
|
| 48 | + | } |
|
| 49 | + | ||
| 50 | + | fn failWithBoom() throws (TestError) { |
|
| 51 | + | throw TestError::Boom; |
|
| 52 | + | } |
|
| 53 | + | ||
| 54 | + | fn failWithBust() throws (TestError) { |
|
| 55 | + | throw TestError::Bust; |
|
| 56 | + | } |
|
| 57 | + | ||
| 58 | + | fn succeed() -> u32 throws (TestError) { |
|
| 59 | + | return 42; |
|
| 60 | + | } |
|
| 61 | + | ||
| 62 | + | fn mayFail(fail: bool) -> u32 throws (TestError) { |
|
| 63 | + | if (fail) { |
|
| 64 | + | throw TestError::Boom; |
|
| 65 | + | } |
|
| 66 | + | return 77; |
|
| 67 | + | } |
|
| 68 | + | ||
| 69 | + | fn testCatchReturn() -> u32 { |
|
| 70 | + | let result: u32 = try mayFail(true) catch e { |
|
| 71 | + | return 100; |
|
| 72 | + | }; |
|
| 73 | + | return result; |
|
| 74 | + | } |
|
| 75 | + | ||
| 76 | + | // Test using the success value when catch diverges. |
|
| 77 | + | fn testCatchDiverges() -> u32 { |
|
| 78 | + | let value: u32 = try mayFail(false) catch e { |
|
| 79 | + | return 0; // Diverges, so success value is usable. |
|
| 80 | + | }; |
|
| 81 | + | return value; // Should return 77 from mayFail. |
|
| 82 | + | } |
|
| 83 | + | ||
| 84 | + | // Test that error binding is actually the error value. |
|
| 85 | + | fn testErrorValue() -> u32 { |
|
| 86 | + | let result: u32 = try mayFail(true) catch e { |
|
| 87 | + | if (e == TestError::Boom) { |
|
| 88 | + | return 1; |
|
| 89 | + | } |
|
| 90 | + | return 2; |
|
| 91 | + | }; |
|
| 92 | + | return 99; // Unreachable. |
|
| 93 | + | } |
lib/std/arch/rv64/tests/error.try.optional.rad
added
+111 -0
| 1 | + | //! Test try? expressions converting errors to optionals. |
|
| 2 | + | ||
| 3 | + | union TestError { Boom, Bust } |
|
| 4 | + | ||
| 5 | + | fn returnsOk() -> u32 throws (TestError) { |
|
| 6 | + | return 42; |
|
| 7 | + | } |
|
| 8 | + | ||
| 9 | + | fn returnsErr() -> u32 throws (TestError) { |
|
| 10 | + | throw TestError::Boom; |
|
| 11 | + | } |
|
| 12 | + | ||
| 13 | + | record Point { |
|
| 14 | + | x: u32, |
|
| 15 | + | y: u32, |
|
| 16 | + | } |
|
| 17 | + | ||
| 18 | + | fn returnsOkPoint() -> Point throws (TestError) { |
|
| 19 | + | return Point { x: 10, y: 20 }; |
|
| 20 | + | } |
|
| 21 | + | ||
| 22 | + | fn returnsErrPoint() -> Point throws (TestError) { |
|
| 23 | + | throw TestError::Boom; |
|
| 24 | + | } |
|
| 25 | + | ||
| 26 | + | fn maybeValue(i: u32) -> u32 throws (TestError) { |
|
| 27 | + | if i >= 3 { |
|
| 28 | + | throw TestError::Boom; |
|
| 29 | + | } |
|
| 30 | + | return i + 1; |
|
| 31 | + | } |
|
| 32 | + | ||
| 33 | + | fn nonThrowingCaller() -> ?u32 { |
|
| 34 | + | return try? returnsOk(); |
|
| 35 | + | } |
|
| 36 | + | ||
| 37 | + | @default fn main() -> i32 { |
|
| 38 | + | // Test 1: try? on success returns Some(value) |
|
| 39 | + | let result1: ?u32 = try? returnsOk(); |
|
| 40 | + | if result1 == nil { |
|
| 41 | + | return 1; |
|
| 42 | + | } |
|
| 43 | + | if let x = result1 { |
|
| 44 | + | if x != 42 { |
|
| 45 | + | return 2; |
|
| 46 | + | } |
|
| 47 | + | } |
|
| 48 | + | ||
| 49 | + | // Test 2: try? on error returns nil |
|
| 50 | + | let result2: ?u32 = try? returnsErr(); |
|
| 51 | + | if result2 != nil { |
|
| 52 | + | return 3; |
|
| 53 | + | } |
|
| 54 | + | ||
| 55 | + | // Test 3: try? with if-let pattern matching |
|
| 56 | + | if let value = try? returnsOk() { |
|
| 57 | + | if value != 42 { |
|
| 58 | + | return 4; |
|
| 59 | + | } |
|
| 60 | + | } else { |
|
| 61 | + | return 5; |
|
| 62 | + | } |
|
| 63 | + | ||
| 64 | + | // Test 4: try? with if-let on error path |
|
| 65 | + | if let value = try? returnsErr() { |
|
| 66 | + | return 6; |
|
| 67 | + | } |
|
| 68 | + | ||
| 69 | + | // Test 5: try? in non-throwing function |
|
| 70 | + | let result5: ?u32 = nonThrowingCaller(); |
|
| 71 | + | if result5 == nil { |
|
| 72 | + | return 7; |
|
| 73 | + | } |
|
| 74 | + | if let x = result5 { |
|
| 75 | + | if x != 42 { |
|
| 76 | + | return 8; |
|
| 77 | + | } |
|
| 78 | + | } |
|
| 79 | + | ||
| 80 | + | // Test 6: try? with record return type |
|
| 81 | + | let result8: ?Point = try? returnsOkPoint(); |
|
| 82 | + | if result8 == nil { |
|
| 83 | + | return 9; |
|
| 84 | + | } |
|
| 85 | + | if let p = result8 { |
|
| 86 | + | if p.x != 10 { |
|
| 87 | + | return 10; |
|
| 88 | + | } |
|
| 89 | + | if p.y != 20 { |
|
| 90 | + | return 11; |
|
| 91 | + | } |
|
| 92 | + | } |
|
| 93 | + | ||
| 94 | + | let result9: ?Point = try? returnsErrPoint(); |
|
| 95 | + | if result9 != nil { |
|
| 96 | + | return 12; |
|
| 97 | + | } |
|
| 98 | + | ||
| 99 | + | // Test 7: try? in while-let loop |
|
| 100 | + | let mut count: u32 = 0; |
|
| 101 | + | let mut iter: u32 = 0; |
|
| 102 | + | while let value = try? maybeValue(iter) { |
|
| 103 | + | count = count + value; |
|
| 104 | + | iter = iter + 1; |
|
| 105 | + | } |
|
| 106 | + | if count != 6 { |
|
| 107 | + | return 13; |
|
| 108 | + | } |
|
| 109 | + | ||
| 110 | + | return 0; |
|
| 111 | + | } |
lib/std/arch/rv64/tests/error.try.rad
added
+216 -0
| 1 | + | //! Test result-based error handling with try/throw. |
|
| 2 | + | ||
| 3 | + | union TestError { Boom, Bust } |
|
| 4 | + | ||
| 5 | + | record ResultSink { |
|
| 6 | + | last: u32, |
|
| 7 | + | count: u32, |
|
| 8 | + | } |
|
| 9 | + | ||
| 10 | + | fn returnsOk() -> u32 throws (TestError) { |
|
| 11 | + | return 21; |
|
| 12 | + | } |
|
| 13 | + | ||
| 14 | + | fn returnsErr(kind: u32) -> u32 throws (TestError) { |
|
| 15 | + | if kind == 0 { |
|
| 16 | + | throw TestError::Boom; |
|
| 17 | + | } |
|
| 18 | + | if kind == 1 { |
|
| 19 | + | throw TestError::Bust; |
|
| 20 | + | } |
|
| 21 | + | return 9; |
|
| 22 | + | } |
|
| 23 | + | ||
| 24 | + | fn returnsOkVoid() throws (TestError) {} |
|
| 25 | + | ||
| 26 | + | fn returnsErrVoid() throws (TestError) { |
|
| 27 | + | throw TestError::Bust; |
|
| 28 | + | } |
|
| 29 | + | ||
| 30 | + | fn sumThree() -> u32 throws (TestError) { |
|
| 31 | + | let a: u32 = try returnsOk(); |
|
| 32 | + | let b: u32 = try returnsErr(2); |
|
| 33 | + | let c: u32 = try returnsErr(2); |
|
| 34 | + | ||
| 35 | + | return a + b + c; |
|
| 36 | + | } |
|
| 37 | + | ||
| 38 | + | fn shortCircuit(flag: bool) -> u32 throws (TestError) { |
|
| 39 | + | let mut counter: u32 = 1; |
|
| 40 | + | ||
| 41 | + | if flag { |
|
| 42 | + | counter = counter + try returnsErr(2); |
|
| 43 | + | } else { |
|
| 44 | + | try returnsErr(0); |
|
| 45 | + | counter = counter + 100; |
|
| 46 | + | } |
|
| 47 | + | return counter; |
|
| 48 | + | } |
|
| 49 | + | ||
| 50 | + | fn aggregateTwo() -> u32 throws (TestError) { |
|
| 51 | + | return (try returnsOk()) + (try returnsErr(2)); |
|
| 52 | + | } |
|
| 53 | + | ||
| 54 | + | fn cascade(flag: bool) -> u32 throws (TestError) { |
|
| 55 | + | let base: u32 = try returnsOk(); |
|
| 56 | + | if flag { |
|
| 57 | + | try returnsErr(1); |
|
| 58 | + | } |
|
| 59 | + | return base + 1; |
|
| 60 | + | } |
|
| 61 | + | ||
| 62 | + | fn directThrow() -> u32 throws (TestError) { |
|
| 63 | + | throw TestError::Boom; |
|
| 64 | + | return 999; |
|
| 65 | + | } |
|
| 66 | + | ||
| 67 | + | fn maybeReturn(fail: bool) -> u32 throws (TestError) { |
|
| 68 | + | if fail { |
|
| 69 | + | throw TestError::Bust; |
|
| 70 | + | } |
|
| 71 | + | return 77; |
|
| 72 | + | } |
|
| 73 | + | ||
| 74 | + | fn catchReturn(flag: *mut u32, fail: bool) -> bool { |
|
| 75 | + | let value: u32 = try maybeReturn(fail) catch { |
|
| 76 | + | return true; |
|
| 77 | + | }; |
|
| 78 | + | *flag = value; |
|
| 79 | + | return false; |
|
| 80 | + | } |
|
| 81 | + | ||
| 82 | + | fn accumulateSteps(failMid: bool) -> u32 throws (TestError) { |
|
| 83 | + | let mut total: u32 = 0; |
|
| 84 | + | let mut idx: u32 = 0; |
|
| 85 | + | while idx < 3 { |
|
| 86 | + | if failMid { |
|
| 87 | + | if idx == 1 { |
|
| 88 | + | total = total + try returnsErr(0); |
|
| 89 | + | } else { |
|
| 90 | + | total = total + try returnsErr(2); |
|
| 91 | + | } |
|
| 92 | + | } else { |
|
| 93 | + | total = total + try returnsErr(2); |
|
| 94 | + | } |
|
| 95 | + | idx = idx + 1; |
|
| 96 | + | } |
|
| 97 | + | return total; |
|
| 98 | + | } |
|
| 99 | + | ||
| 100 | + | fn structSuccess(state: *mut ResultSink) -> u32 throws (TestError) { |
|
| 101 | + | let value: u32 = try returnsOk(); |
|
| 102 | + | state.last = value; |
|
| 103 | + | state.count = state.count + 1; |
|
| 104 | + | return value; |
|
| 105 | + | } |
|
| 106 | + | ||
| 107 | + | fn structFailure(state: *mut ResultSink) -> u32 throws (TestError) { |
|
| 108 | + | try returnsErr(1); |
|
| 109 | + | state.last = 999; |
|
| 110 | + | return 999; |
|
| 111 | + | } |
|
| 112 | + | ||
| 113 | + | @default fn main() -> i32 { |
|
| 114 | + | let mut sumFlag: u32 = 0; |
|
| 115 | + | let total: u32 = try! sumThree(); |
|
| 116 | + | if total != 39 { |
|
| 117 | + | return 1; |
|
| 118 | + | } |
|
| 119 | + | sumFlag = total; |
|
| 120 | + | ||
| 121 | + | let mut scOkFlag: u32 = 0; |
|
| 122 | + | let scVal: u32 = try! shortCircuit(true); |
|
| 123 | + | if scVal != 10 { |
|
| 124 | + | return 2; |
|
| 125 | + | } |
|
| 126 | + | scOkFlag = scVal; |
|
| 127 | + | ||
| 128 | + | let mut scErrFlag: u32 = 0; |
|
| 129 | + | try shortCircuit(false) catch {}; |
|
| 130 | + | if scErrFlag != 0 { |
|
| 131 | + | return 3; |
|
| 132 | + | } |
|
| 133 | + | ||
| 134 | + | let mut aggFlag: u32 = 0; |
|
| 135 | + | let aggVal: u32 = try! aggregateTwo(); |
|
| 136 | + | if aggVal != 30 { |
|
| 137 | + | return 4; |
|
| 138 | + | } |
|
| 139 | + | ||
| 140 | + | let mut casOkFlag: u32 = 0; |
|
| 141 | + | let casVal: u32 = try! cascade(false); |
|
| 142 | + | if casVal != 22 { |
|
| 143 | + | return 5; |
|
| 144 | + | } |
|
| 145 | + | ||
| 146 | + | let mut casErrFlag: u32 = 0; |
|
| 147 | + | try cascade(true) catch {}; |
|
| 148 | + | if casErrFlag != 0 { |
|
| 149 | + | return 6; |
|
| 150 | + | } |
|
| 151 | + | ||
| 152 | + | let mut loopOkFlag: u32 = 0; |
|
| 153 | + | let loopVal: u32 = try! accumulateSteps(false); |
|
| 154 | + | if loopVal != 27 { |
|
| 155 | + | return 7; |
|
| 156 | + | } |
|
| 157 | + | ||
| 158 | + | let mut loopErrFlag: u32 = 0; |
|
| 159 | + | try accumulateSteps(true) catch {}; |
|
| 160 | + | if loopErrFlag != 0 { |
|
| 161 | + | return 8; |
|
| 162 | + | } |
|
| 163 | + | ||
| 164 | + | let mut sink: ResultSink = ResultSink { last: 0, count: 0 }; |
|
| 165 | + | let sVal: u32 = try! structSuccess(&mut sink); |
|
| 166 | + | if sink.last != 21 { |
|
| 167 | + | return 9; |
|
| 168 | + | } |
|
| 169 | + | if sink.count != 1 { |
|
| 170 | + | return 10; |
|
| 171 | + | } |
|
| 172 | + | ||
| 173 | + | try structFailure(&mut sink) catch {}; |
|
| 174 | + | try! structSuccess(&mut sink); |
|
| 175 | + | try structFailure(&mut sink) catch {}; |
|
| 176 | + | if sink.count != 2 { |
|
| 177 | + | return 11; |
|
| 178 | + | } |
|
| 179 | + | ||
| 180 | + | let mut voidOkFlag: u32 = 0; |
|
| 181 | + | try! returnsOkVoid(); |
|
| 182 | + | voidOkFlag = 77; |
|
| 183 | + | if voidOkFlag != 77 { |
|
| 184 | + | return 12; |
|
| 185 | + | } |
|
| 186 | + | ||
| 187 | + | let mut voidErrFlag: u32 = 0; |
|
| 188 | + | try returnsErrVoid() catch {}; |
|
| 189 | + | if voidErrFlag != 0 { |
|
| 190 | + | return 13; |
|
| 191 | + | } |
|
| 192 | + | ||
| 193 | + | let result: ?u32 = try? directThrow(); |
|
| 194 | + | if result != nil { |
|
| 195 | + | return 14; |
|
| 196 | + | } |
|
| 197 | + | ||
| 198 | + | let mut catchReturnFlag: u32 = 0; |
|
| 199 | + | let catchReturnFail: bool = catchReturn(&mut catchReturnFlag, true); |
|
| 200 | + | if not catchReturnFail { |
|
| 201 | + | return 15; |
|
| 202 | + | } |
|
| 203 | + | if catchReturnFlag != 0 { |
|
| 204 | + | return 16; |
|
| 205 | + | } |
|
| 206 | + | ||
| 207 | + | let catchReturnOk: bool = catchReturn(&mut catchReturnFlag, false); |
|
| 208 | + | if catchReturnOk { |
|
| 209 | + | return 17; |
|
| 210 | + | } |
|
| 211 | + | if catchReturnFlag != 77 { |
|
| 212 | + | return 18; |
|
| 213 | + | } |
|
| 214 | + | ||
| 215 | + | return 0; |
|
| 216 | + | } |
lib/std/arch/rv64/tests/fn.block.scope.rad
added
+25 -0
| 1 | + | //! returns: 42 |
|
| 2 | + | @default fn main() -> i32 { |
|
| 3 | + | let mut x: i32 = 0; |
|
| 4 | + | let mut y: i32 = 42; |
|
| 5 | + | let mut z: i32 = 0; |
|
| 6 | + | { |
|
| 7 | + | let mut w: i32 = 1; |
|
| 8 | + | let mut x: i32 = 2; |
|
| 9 | + | let mut y: i32 = 3; |
|
| 10 | + | let mut z: i32 = 4; |
|
| 11 | + | } |
|
| 12 | + | { |
|
| 13 | + | let mut w: i32 = 5; |
|
| 14 | + | let mut x: i32 = 6; |
|
| 15 | + | let mut y: i32 = 7; |
|
| 16 | + | let mut z: i32 = 8; |
|
| 17 | + | } |
|
| 18 | + | { |
|
| 19 | + | let mut w: i32 = 9; |
|
| 20 | + | let mut x: i32 = 10; |
|
| 21 | + | let mut y: i32 = 11; |
|
| 22 | + | let mut z: i32 = 12; |
|
| 23 | + | } |
|
| 24 | + | return y; |
|
| 25 | + | } |
lib/std/arch/rv64/tests/fn.callback.nested.rad
added
+56 -0
| 1 | + | //! Test for callback with nested function call inside. |
|
| 2 | + | //! This exercises the register save/restore logic when a callback makes |
|
| 3 | + | //! a function call that uses caller-saved registers. |
|
| 4 | + | //! returns: 0 |
|
| 5 | + | ||
| 6 | + | record Reg { n: u32 } |
|
| 7 | + | ||
| 8 | + | union Val { |
|
| 9 | + | Reg(Reg), |
|
| 10 | + | Imm(i32), |
|
| 11 | + | } |
|
| 12 | + | ||
| 13 | + | fn maxRegNum(n: u32, current: u32) -> u32 { |
|
| 14 | + | if n + 1 > current { |
|
| 15 | + | return n + 1; |
|
| 16 | + | } |
|
| 17 | + | return current; |
|
| 18 | + | } |
|
| 19 | + | ||
| 20 | + | fn maxRegCallback(reg: Reg, ctx: *mut opaque) { |
|
| 21 | + | let max = ctx as *mut u32; |
|
| 22 | + | *max = maxRegNum(reg.n, *max); |
|
| 23 | + | } |
|
| 24 | + | ||
| 25 | + | fn withReg(val: Val, callback: fn(Reg, *mut opaque), ctx: *mut opaque) { |
|
| 26 | + | if let case Val::Reg(r) = val { |
|
| 27 | + | callback(r, ctx); |
|
| 28 | + | } |
|
| 29 | + | } |
|
| 30 | + | ||
| 31 | + | fn forEachVal(a: Val, b: Val, c: Val, callback: fn(Reg, *mut opaque), ctx: *mut opaque) { |
|
| 32 | + | withReg(a, callback, ctx); |
|
| 33 | + | withReg(b, callback, ctx); |
|
| 34 | + | withReg(c, callback, ctx); |
|
| 35 | + | } |
|
| 36 | + | ||
| 37 | + | @default fn main() -> i32 { |
|
| 38 | + | let mut maxReg: u32 = 0; |
|
| 39 | + | ||
| 40 | + | forEachVal( |
|
| 41 | + | Val::Reg(Reg { n: 0 }), |
|
| 42 | + | Val::Reg(Reg { n: 5 }), |
|
| 43 | + | Val::Reg(Reg { n: 2 }), |
|
| 44 | + | maxRegCallback, |
|
| 45 | + | &mut maxReg as *mut opaque |
|
| 46 | + | ); |
|
| 47 | + | ||
| 48 | + | // maxReg should be 6 (max register 5 + 1) |
|
| 49 | + | if maxReg == 6 { |
|
| 50 | + | return 0; |
|
| 51 | + | } |
|
| 52 | + | if maxReg > 255 { |
|
| 53 | + | return 255; |
|
| 54 | + | } |
|
| 55 | + | return maxReg as i32; |
|
| 56 | + | } |
lib/std/arch/rv64/tests/fn.default.rad
added
+6 -0
| 1 | + | // |
|
| 2 | + | // The simplest test is a `default` function returning a literal number. |
|
| 3 | + | // |
|
| 4 | + | @default fn main() -> i32 { |
|
| 5 | + | return (42) - 42; |
|
| 6 | + | } |
lib/std/arch/rv64/tests/fn.local.rad
added
+7 -0
| 1 | + | // |
|
| 2 | + | // Local variables. Tests the stack and the symbol table. |
|
| 3 | + | // |
|
| 4 | + | @default fn main() -> i32 { |
|
| 5 | + | let mut z: i32 = 42; |
|
| 6 | + | return (z) - 42; |
|
| 7 | + | } |
lib/std/arch/rv64/tests/fn.recursion.2.rad
added
+13 -0
| 1 | + | //! returns: 144 |
|
| 2 | + | //! Test recursive fibonacci computation. |
|
| 3 | + | ||
| 4 | + | fn fibonacci(n: i32) -> i32 { |
|
| 5 | + | if (n <= 1) { |
|
| 6 | + | return n; |
|
| 7 | + | } |
|
| 8 | + | return fibonacci(n - 1) + fibonacci(n - 2); |
|
| 9 | + | } |
|
| 10 | + | ||
| 11 | + | @default fn main() -> i32 { |
|
| 12 | + | return fibonacci(12); |
|
| 13 | + | } |
lib/std/arch/rv64/tests/fn.void.rad
added
+9 -0
| 1 | + | ||
| 2 | + | fn returnNothing() { |
|
| 3 | + | // This is a void function with no return type. |
|
| 4 | + | } |
|
| 5 | + | ||
| 6 | + | @default fn main() -> i32 { |
|
| 7 | + | returnNothing(); |
|
| 8 | + | return (42) - 42; |
|
| 9 | + | } |
lib/std/arch/rv64/tests/for.else.continue.rad
added
+53 -0
| 1 | + | //! Test `else continue` in a for loop with let-else. |
|
| 2 | + | ||
| 3 | + | record Field { |
|
| 4 | + | name: ?*[u8], |
|
| 5 | + | value: i32, |
|
| 6 | + | } |
|
| 7 | + | ||
| 8 | + | fn findField(fields: *[Field], target: *[u8]) -> ?i32 { |
|
| 9 | + | for i in 0..fields.len { |
|
| 10 | + | let name = fields[i].name |
|
| 11 | + | else continue; |
|
| 12 | + | if name.len == target.len { |
|
| 13 | + | let mut eq = true; |
|
| 14 | + | for j in 0..name.len { |
|
| 15 | + | if name[j] != target[j] { |
|
| 16 | + | eq = false; |
|
| 17 | + | } |
|
| 18 | + | } |
|
| 19 | + | if eq { |
|
| 20 | + | return fields[i].value; |
|
| 21 | + | } |
|
| 22 | + | } |
|
| 23 | + | } |
|
| 24 | + | return nil; |
|
| 25 | + | } |
|
| 26 | + | ||
| 27 | + | @default fn main() -> i32 { |
|
| 28 | + | let mut fields: [Field; 4] = undefined; |
|
| 29 | + | fields[0] = Field { name: nil, value: 10 }; |
|
| 30 | + | fields[1] = Field { name: "foo", value: 20 }; |
|
| 31 | + | fields[2] = Field { name: nil, value: 30 }; |
|
| 32 | + | fields[3] = Field { name: "bar", value: 40 }; |
|
| 33 | + | ||
| 34 | + | let v1 = findField(&fields[..], "foo") else { |
|
| 35 | + | return 1; |
|
| 36 | + | }; |
|
| 37 | + | if v1 != 20 { |
|
| 38 | + | return 2; |
|
| 39 | + | } |
|
| 40 | + | ||
| 41 | + | let v2 = findField(&fields[..], "bar") else { |
|
| 42 | + | return 3; |
|
| 43 | + | }; |
|
| 44 | + | if v2 != 40 { |
|
| 45 | + | return 4; |
|
| 46 | + | } |
|
| 47 | + | ||
| 48 | + | if let _ = findField(&fields[..], "baz") { |
|
| 49 | + | return 5; |
|
| 50 | + | } |
|
| 51 | + | ||
| 52 | + | return 0; |
|
| 53 | + | } |
lib/std/arch/rv64/tests/frame.large.rad
added
+34 -0
| 1 | + | //! Test large stack frames and addressing near top of frame. |
|
| 2 | + | fn bigFrame1() -> i32 { |
|
| 3 | + | let pad: [u8; 2024] = undefined; |
|
| 4 | + | let n: i32 = 491823; |
|
| 5 | + | ||
| 6 | + | return n + 1; |
|
| 7 | + | } |
|
| 8 | + | ||
| 9 | + | fn bigFrame2() -> i32 { |
|
| 10 | + | let pad: [u8; 2028] = undefined; |
|
| 11 | + | let arr: [i32; 4] = [1, 2, 3, 4]; |
|
| 12 | + | ||
| 13 | + | return arr[0] + arr[1] + arr[3]; |
|
| 14 | + | } |
|
| 15 | + | ||
| 16 | + | fn bigFrame3() -> i32 { |
|
| 17 | + | let mut ary: [u8; 4096] = undefined; |
|
| 18 | + | ary[4091] = 192; |
|
| 19 | + | ||
| 20 | + | return ary[4091] as i32; |
|
| 21 | + | } |
|
| 22 | + | ||
| 23 | + | @default fn main() -> i32 { |
|
| 24 | + | if bigFrame1() != 491824 { |
|
| 25 | + | return 1; |
|
| 26 | + | } |
|
| 27 | + | if bigFrame2() != 7 { |
|
| 28 | + | return 2; |
|
| 29 | + | } |
|
| 30 | + | if bigFrame3() != 192 { |
|
| 31 | + | return 3; |
|
| 32 | + | } |
|
| 33 | + | return 0; |
|
| 34 | + | } |
lib/std/arch/rv64/tests/iflet.shadow.leak.rad
added
+20 -0
| 1 | + | //! returns: 0 |
|
| 2 | + | ||
| 3 | + | fn leak() -> i32 { |
|
| 4 | + | let mut x: i32 = 7; |
|
| 5 | + | let opt: ?i32 = 41; |
|
| 6 | + | ||
| 7 | + | if let x = opt { |
|
| 8 | + | // Inner pattern binding shadows outer `x`. |
|
| 9 | + | if x != 41 { |
|
| 10 | + | return 100; |
|
| 11 | + | } |
|
| 12 | + | } |
|
| 13 | + | ||
| 14 | + | // Must refer to outer x (7), not if-let binding (41). |
|
| 15 | + | return x; |
|
| 16 | + | } |
|
| 17 | + | ||
| 18 | + | @default fn main() -> i32 { |
|
| 19 | + | return leak() - 7; |
|
| 20 | + | } |
lib/std/arch/rv64/tests/integer.bitwise.basic.rad
added
+20 -0
| 1 | + | @default fn main() -> i32 { |
|
| 2 | + | let a: i32 = 12; // 0b1100 |
|
| 3 | + | let b: i32 = 5; // 0b0101 |
|
| 4 | + | ||
| 5 | + | let and_result: i32 = a & b; // 0b00100 |
|
| 6 | + | let or_result: i32 = a | b; // 0b01101 |
|
| 7 | + | let xor_result: i32 = a ^ b; // 0b01001 |
|
| 8 | + | let not_result: i32 = ~a; // 0b00011 |
|
| 9 | + | let lshift_result: i32 = a << 1; // 0b11000 |
|
| 10 | + | let rshift_result: i32 = a >> 2; // 0b00011 |
|
| 11 | + | ||
| 12 | + | if and_result != 4 { return (1) - 42; } |
|
| 13 | + | if or_result != 13 { return (1) - 42; } |
|
| 14 | + | if xor_result != 9 { return (1) - 42; } |
|
| 15 | + | if not_result != -13 { return (1) - 42; } |
|
| 16 | + | if lshift_result != 24 { return (1) - 42; } |
|
| 17 | + | if rshift_result != 3 { return (1) - 42; } |
|
| 18 | + | ||
| 19 | + | return (42) - 42; |
|
| 20 | + | } |
lib/std/arch/rv64/tests/integer.overflow.rad
added
+95 -0
| 1 | + | //! Test integer overflow and underflow wrapping behavior. |
|
| 2 | + | ||
| 3 | + | fn testI32Overflow() -> bool { |
|
| 4 | + | let maxI32: i32 = 2147483647; |
|
| 5 | + | let overflowResult: i32 = maxI32 + 1; |
|
| 6 | + | return overflowResult == -2147483648; |
|
| 7 | + | } |
|
| 8 | + | ||
| 9 | + | fn testI32Underflow() -> bool { |
|
| 10 | + | let minI32: i32 = -2147483648; |
|
| 11 | + | let underflowResult: i32 = minI32 - 1; |
|
| 12 | + | return underflowResult == 2147483647; |
|
| 13 | + | } |
|
| 14 | + | ||
| 15 | + | fn testU32Overflow() -> bool { |
|
| 16 | + | let maxU32: u32 = 4294967295; |
|
| 17 | + | let overflowResult: u32 = maxU32 + 1; |
|
| 18 | + | return overflowResult == 0; |
|
| 19 | + | } |
|
| 20 | + | ||
| 21 | + | fn testU32Underflow() -> bool { |
|
| 22 | + | let minU32: u32 = 0; |
|
| 23 | + | let underflowResult: u32 = minU32 - 1; |
|
| 24 | + | return underflowResult == 4294967295; |
|
| 25 | + | } |
|
| 26 | + | ||
| 27 | + | fn testI8Overflow() -> bool { |
|
| 28 | + | let maxI8: i8 = 127; |
|
| 29 | + | let overflowResult: i8 = maxI8 + 1; |
|
| 30 | + | return overflowResult == -128; |
|
| 31 | + | } |
|
| 32 | + | ||
| 33 | + | fn testI8Underflow() -> bool { |
|
| 34 | + | let minI8: i8 = -128; |
|
| 35 | + | let underflowResult: i8 = minI8 - 1; |
|
| 36 | + | return underflowResult == 127; |
|
| 37 | + | } |
|
| 38 | + | ||
| 39 | + | fn testU8Overflow() -> bool { |
|
| 40 | + | let maxU8: u8 = 255; |
|
| 41 | + | let overflowResult: u8 = maxU8 + 1 as u8; |
|
| 42 | + | return overflowResult == 0; |
|
| 43 | + | } |
|
| 44 | + | ||
| 45 | + | fn testU8Underflow() -> bool { |
|
| 46 | + | let minU8: u8 = 0; |
|
| 47 | + | let underflowResult: u8 = minU8 - 1 as u8; |
|
| 48 | + | return underflowResult == 255; |
|
| 49 | + | } |
|
| 50 | + | ||
| 51 | + | fn testI8DivOverflow() -> bool { |
|
| 52 | + | let minI8: i8 = -128; |
|
| 53 | + | let overflowResult: i8 = minI8 / -1 as i8; |
|
| 54 | + | return overflowResult == -128; |
|
| 55 | + | } |
|
| 56 | + | ||
| 57 | + | fn testI16DivOverflow() -> bool { |
|
| 58 | + | let minI16: i16 = -32768; |
|
| 59 | + | let overflowResult: i16 = minI16 / -1 as i16; |
|
| 60 | + | return overflowResult == -32768; |
|
| 61 | + | } |
|
| 62 | + | ||
| 63 | + | @default fn main() -> i32 { |
|
| 64 | + | if not testI32Overflow() { |
|
| 65 | + | return 1; |
|
| 66 | + | } |
|
| 67 | + | if not testI32Underflow() { |
|
| 68 | + | return 2; |
|
| 69 | + | } |
|
| 70 | + | if not testU32Overflow() { |
|
| 71 | + | return 3; |
|
| 72 | + | } |
|
| 73 | + | if not testU32Underflow() { |
|
| 74 | + | return 4; |
|
| 75 | + | } |
|
| 76 | + | if not testI8Overflow() { |
|
| 77 | + | return 5; |
|
| 78 | + | } |
|
| 79 | + | if not testI8Underflow() { |
|
| 80 | + | return 6; |
|
| 81 | + | } |
|
| 82 | + | if not testU8Overflow() { |
|
| 83 | + | return 7; |
|
| 84 | + | } |
|
| 85 | + | if not testU8Underflow() { |
|
| 86 | + | return 8; |
|
| 87 | + | } |
|
| 88 | + | if not testI8DivOverflow() { |
|
| 89 | + | return 9; |
|
| 90 | + | } |
|
| 91 | + | if not testI16DivOverflow() { |
|
| 92 | + | return 10; |
|
| 93 | + | } |
|
| 94 | + | return 0; |
|
| 95 | + | } |
lib/std/arch/rv64/tests/large.blit.store.rad
added
+107 -0
| 1 | + | //! Regression test for large struct copies and stores with large offsets. |
|
| 2 | + | //! |
|
| 3 | + | //! Tests two bugs in the RV64 backend: |
|
| 4 | + | //! |
|
| 5 | + | //! 1. Store with large offset (> 2047): `adjustOffset` in `emitStore` uses |
|
| 6 | + | //! SCRATCH1 for the LUI+ADD address computation, clobbering the value |
|
| 7 | + | //! register when it was also materialized into SCRATCH1. |
|
| 8 | + | //! Fixed by folding large offsets into the base address at the isel level |
|
| 9 | + | //! before materializing the value. |
|
| 10 | + | //! |
|
| 11 | + | //! 2. Blit (memcpy) offset overflow: copying structs larger than ~2047 bytes |
|
| 12 | + | //! caused the offset in the load/store loop to exceed the 12-bit immediate. |
|
| 13 | + | //! Fixed by periodically advancing the base registers in the blit loop. |
|
| 14 | + | ||
| 15 | + | record Big { |
|
| 16 | + | a: [u8; 2200], |
|
| 17 | + | tag: i32, |
|
| 18 | + | } |
|
| 19 | + | ||
| 20 | + | /// Store at offset 2200 (> MAX_IMM) into a record field. |
|
| 21 | + | /// Generates `store w32 <imm> %0 2200` in IL, which triggers the |
|
| 22 | + | /// SCRATCH1 aliasing bug when adjustOffset clobbers the value. |
|
| 23 | + | fn storeLargeOffset() -> i32 { |
|
| 24 | + | let mut b: Big = undefined; |
|
| 25 | + | b.tag = 42; |
|
| 26 | + | if b.tag != 42 { |
|
| 27 | + | return 1; |
|
| 28 | + | } |
|
| 29 | + | b.tag = 99; |
|
| 30 | + | if b.tag != 99 { |
|
| 31 | + | return 2; |
|
| 32 | + | } |
|
| 33 | + | return 0; |
|
| 34 | + | } |
|
| 35 | + | ||
| 36 | + | /// Copy a >2047 byte struct (triggers blit offset overflow). |
|
| 37 | + | fn copyBig() -> i32 { |
|
| 38 | + | let mut src: Big = undefined; |
|
| 39 | + | src.a[0] = 10; |
|
| 40 | + | src.a[1000] = 20; |
|
| 41 | + | src.a[2199] = 30; |
|
| 42 | + | src.tag = 77; |
|
| 43 | + | ||
| 44 | + | let mut dst: Big = src; |
|
| 45 | + | ||
| 46 | + | if dst.a[0] != 10 { |
|
| 47 | + | return 1; |
|
| 48 | + | } |
|
| 49 | + | if dst.a[1000] != 20 { |
|
| 50 | + | return 2; |
|
| 51 | + | } |
|
| 52 | + | if dst.a[2199] != 30 { |
|
| 53 | + | return 3; |
|
| 54 | + | } |
|
| 55 | + | if dst.tag != 77 { |
|
| 56 | + | return 4; |
|
| 57 | + | } |
|
| 58 | + | return 0; |
|
| 59 | + | } |
|
| 60 | + | ||
| 61 | + | /// Mutate a copy to ensure the blit produced an independent copy. |
|
| 62 | + | fn copyIndependence() -> i32 { |
|
| 63 | + | let mut a: Big = undefined; |
|
| 64 | + | a.a[0] = 1; |
|
| 65 | + | a.a[2199] = 2; |
|
| 66 | + | a.tag = 100; |
|
| 67 | + | ||
| 68 | + | let mut b: Big = a; |
|
| 69 | + | b.a[0] = 99; |
|
| 70 | + | b.tag = 200; |
|
| 71 | + | ||
| 72 | + | if a.a[0] != 1 { |
|
| 73 | + | return 1; |
|
| 74 | + | } |
|
| 75 | + | if a.tag != 100 { |
|
| 76 | + | return 2; |
|
| 77 | + | } |
|
| 78 | + | if b.a[0] != 99 { |
|
| 79 | + | return 3; |
|
| 80 | + | } |
|
| 81 | + | if b.a[2199] != 2 { |
|
| 82 | + | return 4; |
|
| 83 | + | } |
|
| 84 | + | if b.tag != 200 { |
|
| 85 | + | return 5; |
|
| 86 | + | } |
|
| 87 | + | return 0; |
|
| 88 | + | } |
|
| 89 | + | ||
| 90 | + | @default fn main() -> i32 { |
|
| 91 | + | let r1 = storeLargeOffset(); |
|
| 92 | + | if r1 != 0 { |
|
| 93 | + | return 10 + r1; |
|
| 94 | + | } |
|
| 95 | + | ||
| 96 | + | let r2 = copyBig(); |
|
| 97 | + | if r2 != 0 { |
|
| 98 | + | return 20 + r2; |
|
| 99 | + | } |
|
| 100 | + | ||
| 101 | + | let r3 = copyIndependence(); |
|
| 102 | + | if r3 != 0 { |
|
| 103 | + | return 30 + r3; |
|
| 104 | + | } |
|
| 105 | + | ||
| 106 | + | return 0; |
|
| 107 | + | } |
lib/std/arch/rv64/tests/let.guard.rad
added
+94 -0
| 1 | + | //! returns: 1 |
|
| 2 | + | ||
| 3 | + | union Response { |
|
| 4 | + | success(i32), |
|
| 5 | + | failure, |
|
| 6 | + | other { message: *[u8] }, |
|
| 7 | + | } |
|
| 8 | + | ||
| 9 | + | fn optionGuardSuccess() -> bool { |
|
| 10 | + | let opt: ?i32 = 21; |
|
| 11 | + | let value = opt else { |
|
| 12 | + | return false; |
|
| 13 | + | }; |
|
| 14 | + | return value == 21; |
|
| 15 | + | } |
|
| 16 | + | ||
| 17 | + | fn optionGuardElseBlk() -> bool { |
|
| 18 | + | let opt: ?i32 = nil; |
|
| 19 | + | let value = opt else { |
|
| 20 | + | return true; |
|
| 21 | + | }; |
|
| 22 | + | return false; |
|
| 23 | + | } |
|
| 24 | + | ||
| 25 | + | fn optionGuardElseStmt() -> bool { |
|
| 26 | + | let opt: ?i32 = nil; |
|
| 27 | + | let value = opt else return true; |
|
| 28 | + | ||
| 29 | + | return false; |
|
| 30 | + | } |
|
| 31 | + | ||
| 32 | + | fn caseGuardSuccess() -> bool { |
|
| 33 | + | let resp: Response = Response::success(9); |
|
| 34 | + | let case Response::success(inner) = resp else { |
|
| 35 | + | return false; |
|
| 36 | + | }; |
|
| 37 | + | return inner == 9; |
|
| 38 | + | } |
|
| 39 | + | ||
| 40 | + | fn caseGuardElseBlk() -> bool { |
|
| 41 | + | let resp: Response = Response::failure; |
|
| 42 | + | let case Response::success(inner) = resp else { |
|
| 43 | + | return true; |
|
| 44 | + | }; |
|
| 45 | + | return false; |
|
| 46 | + | } |
|
| 47 | + | ||
| 48 | + | fn caseGuardElseStmt() -> bool { |
|
| 49 | + | let resp: Response = Response::failure; |
|
| 50 | + | let case Response::success(inner) = resp else return true; |
|
| 51 | + | ||
| 52 | + | return false; |
|
| 53 | + | } |
|
| 54 | + | ||
| 55 | + | fn chainedGuards() -> bool { |
|
| 56 | + | let first: ?i32 = 1; |
|
| 57 | + | let second: ?i32 = 2; |
|
| 58 | + | ||
| 59 | + | let a = first else { |
|
| 60 | + | return false; |
|
| 61 | + | }; |
|
| 62 | + | let b = second else return false; |
|
| 63 | + | ||
| 64 | + | return a == 1 and b == 2; |
|
| 65 | + | } |
|
| 66 | + | ||
| 67 | + | fn caseGuardNoPayload() -> bool { |
|
| 68 | + | let resp: Response = Response::failure; |
|
| 69 | + | let case Response::failure = resp else { |
|
| 70 | + | return false; |
|
| 71 | + | }; |
|
| 72 | + | return true; |
|
| 73 | + | } |
|
| 74 | + | ||
| 75 | + | fn caseGuardStruct() -> bool { |
|
| 76 | + | let resp: Response = Response::other { message: "ouch" }; |
|
| 77 | + | let case Response::other { message } = resp else { |
|
| 78 | + | return false; |
|
| 79 | + | }; |
|
| 80 | + | return message.len == 4; |
|
| 81 | + | } |
|
| 82 | + | ||
| 83 | + | @default fn main() -> bool { |
|
| 84 | + | return |
|
| 85 | + | optionGuardSuccess() and |
|
| 86 | + | optionGuardElseBlk() and |
|
| 87 | + | optionGuardElseStmt() and |
|
| 88 | + | caseGuardSuccess() and |
|
| 89 | + | caseGuardElseBlk() and |
|
| 90 | + | caseGuardElseStmt() and |
|
| 91 | + | chainedGuards() and |
|
| 92 | + | caseGuardNoPayload() and |
|
| 93 | + | caseGuardStruct(); |
|
| 94 | + | } |
lib/std/arch/rv64/tests/loc.addr.offset.bug.rad
added
+24 -0
| 1 | + | //! Test that LOC_ADDR values with non-zero offsets work correctly |
|
| 2 | + | //! for pass-by-ref types (structs). |
|
| 3 | + | ||
| 4 | + | record Inner { |
|
| 5 | + | value: i32, |
|
| 6 | + | } |
|
| 7 | + | ||
| 8 | + | record Outer { |
|
| 9 | + | pad: i32, // offset 0, size 4 |
|
| 10 | + | inner: Inner, // offset 4, size 4 |
|
| 11 | + | } |
|
| 12 | + | ||
| 13 | + | static outer: Outer = undefined; |
|
| 14 | + | ||
| 15 | + | @default fn main() -> i32 { |
|
| 16 | + | outer.inner.value = 42; |
|
| 17 | + | ||
| 18 | + | let ptr: *Inner = &outer.inner; |
|
| 19 | + | ||
| 20 | + | if (*ptr).value != 42 { |
|
| 21 | + | return 1; |
|
| 22 | + | } |
|
| 23 | + | return 0; |
|
| 24 | + | } |
lib/std/arch/rv64/tests/loc.addr.opt.to.opt.rad
added
+24 -0
| 1 | + | //! Test assigning one optional to another when destination is LOC_ADDR |
|
| 2 | + | //! with offset. |
|
| 3 | + | ||
| 4 | + | record Container { |
|
| 5 | + | pad: i32, // offset 0 |
|
| 6 | + | opt: ?i32, // offset 4 |
|
| 7 | + | } |
|
| 8 | + | ||
| 9 | + | static container: Container = undefined; |
|
| 10 | + | ||
| 11 | + | @default fn main() -> i32 { |
|
| 12 | + | let source_opt: ?i32 = 42; |
|
| 13 | + | ||
| 14 | + | container.opt = source_opt; |
|
| 15 | + | ||
| 16 | + | if let val = container.opt { |
|
| 17 | + | if val != 42 { |
|
| 18 | + | return 2; |
|
| 19 | + | } |
|
| 20 | + | return 0; |
|
| 21 | + | } else { |
|
| 22 | + | return 1; |
|
| 23 | + | } |
|
| 24 | + | } |
lib/std/arch/rv64/tests/loc.addr.optional.assign.rad
added
+21 -0
| 1 | + | //! Test assigning to an optional field stored at LOC_ADDR with offset. |
|
| 2 | + | ||
| 3 | + | record Container { |
|
| 4 | + | pad: i32, // offset 0, size 4 |
|
| 5 | + | opt: ?i32, // offset 4, size 8 (tag + value) |
|
| 6 | + | } |
|
| 7 | + | ||
| 8 | + | static container: Container = undefined; |
|
| 9 | + | ||
| 10 | + | @default fn main() -> i32 { |
|
| 11 | + | container.opt = 42; |
|
| 12 | + | ||
| 13 | + | if let val = container.opt { |
|
| 14 | + | if val != 42 { |
|
| 15 | + | return 2; |
|
| 16 | + | } |
|
| 17 | + | return 0; |
|
| 18 | + | } else { |
|
| 19 | + | return 1; |
|
| 20 | + | } |
|
| 21 | + | } |
lib/std/arch/rv64/tests/loc.addr.record.assign.rad
added
+25 -0
| 1 | + | //! Test assigning to a record field that is itself a record |
|
| 2 | + | //! when the parent is stored at LOC_ADDR (static). |
|
| 3 | + | ||
| 4 | + | record Inner { |
|
| 5 | + | value: i32, |
|
| 6 | + | } |
|
| 7 | + | ||
| 8 | + | record Outer { |
|
| 9 | + | pad: i32, // offset 0, size 4 |
|
| 10 | + | inner: Inner, // offset 4, size 4 |
|
| 11 | + | } |
|
| 12 | + | ||
| 13 | + | static outer: Outer = undefined; |
|
| 14 | + | static source: Inner = undefined; |
|
| 15 | + | ||
| 16 | + | @default fn main() -> i32 { |
|
| 17 | + | source.value = 42; |
|
| 18 | + | ||
| 19 | + | outer.inner = source; |
|
| 20 | + | ||
| 21 | + | if outer.inner.value != 42 { |
|
| 22 | + | return 1; |
|
| 23 | + | } |
|
| 24 | + | return 0; |
|
| 25 | + | } |
lib/std/arch/rv64/tests/loop.complex.flow.rad
added
+49 -0
| 1 | + | //! Test loop with complex control flow inside the body. |
|
| 2 | + | //! This mirrors patterns in the lowerer where loops contain |
|
| 3 | + | //! match statements, function calls, error handling, etc. |
|
| 4 | + | ||
| 5 | + | union Result { |
|
| 6 | + | Ok(i32), |
|
| 7 | + | Err(i32), |
|
| 8 | + | } |
|
| 9 | + | ||
| 10 | + | fn maybeAdd(x: i32, y: i32) -> Result { |
|
| 11 | + | if y > 0 { |
|
| 12 | + | return Result::Ok(x + y); |
|
| 13 | + | } else { |
|
| 14 | + | return Result::Err(0 - y); |
|
| 15 | + | } |
|
| 16 | + | } |
|
| 17 | + | ||
| 18 | + | @default fn main() -> i32 { |
|
| 19 | + | let mut sum: i32 = 0; |
|
| 20 | + | let mut errCount: i32 = 0; |
|
| 21 | + | let mut i: i32 = 0; |
|
| 22 | + | ||
| 23 | + | while i < 6 { |
|
| 24 | + | let val = i - 2; // -2, -1, 0, 1, 2, 3 |
|
| 25 | + | let result = maybeAdd(sum, val); |
|
| 26 | + | ||
| 27 | + | match result { |
|
| 28 | + | case Result::Ok(v) => { |
|
| 29 | + | sum = v; |
|
| 30 | + | }, |
|
| 31 | + | case Result::Err(e) => { |
|
| 32 | + | errCount = errCount + 1; |
|
| 33 | + | }, |
|
| 34 | + | } |
|
| 35 | + | i = i + 1; |
|
| 36 | + | } |
|
| 37 | + | ||
| 38 | + | // val sequence: -2, -1, 0, 1, 2, 3 |
|
| 39 | + | // Err for val <= 0: errCount = 3 (for -2, -1, 0) |
|
| 40 | + | // Ok for val > 0: sum = 0 + 1 + 2 + 3 = 6 |
|
| 41 | + | if sum != 6 { |
|
| 42 | + | return 1; |
|
| 43 | + | } |
|
| 44 | + | if errCount != 3 { |
|
| 45 | + | return 2; |
|
| 46 | + | } |
|
| 47 | + | ||
| 48 | + | return 0; |
|
| 49 | + | } |
lib/std/arch/rv64/tests/loop.sealblock.rad
added
+43 -0
| 1 | + | //! Test that loop-carried mutable variables work correctly. |
|
| 2 | + | //! This is the fundamental pattern used by sealBlock in the lowerer. |
|
| 3 | + | //! A mutable variable defined before a loop, modified inside the loop body, |
|
| 4 | + | //! must have its updated value propagated to the next iteration. |
|
| 5 | + | ||
| 6 | + | @default fn main() -> i32 { |
|
| 7 | + | // Simple loop with mutable variable. |
|
| 8 | + | let mut sum: i32 = 0; |
|
| 9 | + | let mut i: i32 = 0; |
|
| 10 | + | while i < 5 { |
|
| 11 | + | sum = sum + i; |
|
| 12 | + | i = i + 1; |
|
| 13 | + | } |
|
| 14 | + | // sum = 0 + 1 + 2 + 3 + 4 = 10 |
|
| 15 | + | if sum != 10 { |
|
| 16 | + | return 1; |
|
| 17 | + | } |
|
| 18 | + | ||
| 19 | + | // For loop with mutable variable. |
|
| 20 | + | let mut count: i32 = 0; |
|
| 21 | + | for j in 0..4 { |
|
| 22 | + | count = count + 1; |
|
| 23 | + | } |
|
| 24 | + | if count != 4 { |
|
| 25 | + | return 2; |
|
| 26 | + | } |
|
| 27 | + | ||
| 28 | + | // Nested pattern: mutable var with function call in loop. |
|
| 29 | + | let mut total: i32 = 0; |
|
| 30 | + | for k in 0..3 { |
|
| 31 | + | total = total + addOne(k); |
|
| 32 | + | } |
|
| 33 | + | // total = 1 + 2 + 3 = 6 |
|
| 34 | + | if total != 6 { |
|
| 35 | + | return 3; |
|
| 36 | + | } |
|
| 37 | + | ||
| 38 | + | return 0; |
|
| 39 | + | } |
|
| 40 | + | ||
| 41 | + | fn addOne(x: i32) -> i32 { |
|
| 42 | + | return x + 1; |
|
| 43 | + | } |
lib/std/arch/rv64/tests/match.multi.seal.rad
added
+41 -0
| 1 | + | //! Regression test: intermediate arm blocks in multi-pattern matches must be |
|
| 2 | + | //! sealed so that SSA variables flow correctly through single-predecessor |
|
| 3 | + | //! optimization. Without sealing, block parameters are created but never |
|
| 4 | + | //! resolved, causing values to be read from wrong registers. |
|
| 5 | + | ||
| 6 | + | union Kind { A, B, C, D, E } |
|
| 7 | + | ||
| 8 | + | fn classify(k: Kind, y: i32) -> i32 { |
|
| 9 | + | match k { |
|
| 10 | + | case Kind::A, Kind::B, Kind::C => { |
|
| 11 | + | return y + 10; |
|
| 12 | + | }, |
|
| 13 | + | case Kind::D => { |
|
| 14 | + | return y + 20; |
|
| 15 | + | }, |
|
| 16 | + | else => { |
|
| 17 | + | // y must survive through all intermediate arm blocks. |
|
| 18 | + | return y + 30; |
|
| 19 | + | } |
|
| 20 | + | } |
|
| 21 | + | } |
|
| 22 | + | ||
| 23 | + | @default fn main() -> i32 { |
|
| 24 | + | // Multi-pattern first variant |
|
| 25 | + | if classify(Kind::A, 5) != 15 { |
|
| 26 | + | return 1; |
|
| 27 | + | } |
|
| 28 | + | // Multi-pattern last variant |
|
| 29 | + | if classify(Kind::C, 5) != 15 { |
|
| 30 | + | return 2; |
|
| 31 | + | } |
|
| 32 | + | // Single-pattern variant |
|
| 33 | + | if classify(Kind::D, 5) != 25 { |
|
| 34 | + | return 3; |
|
| 35 | + | } |
|
| 36 | + | // Else branch - the critical path |
|
| 37 | + | if classify(Kind::E, 5) != 35 { |
|
| 38 | + | return 4; |
|
| 39 | + | } |
|
| 40 | + | return 0; |
|
| 41 | + | } |
lib/std/arch/rv64/tests/match.multi.survive.rad
added
+67 -0
| 1 | + | //! Regression test: values must survive across multi-pattern match arms. |
|
| 2 | + | //! |
|
| 3 | + | //! When a match has multi-pattern cases (e.g. `case A, B, C`), the isel |
|
| 4 | + | //! emits block-argument moves for the then-target before the conditional |
|
| 5 | + | //! branch. Without `InvertedCond`, these moves clobber registers needed by |
|
| 6 | + | //! the fall-through (else) path. |
|
| 7 | + | //! |
|
| 8 | + | //! This test catches the bug by requiring a value (`state`) to survive |
|
| 9 | + | //! through multiple match arms and into the `else` branch. |
|
| 10 | + | ||
| 11 | + | union Kind { |
|
| 12 | + | Alpha, |
|
| 13 | + | Beta, |
|
| 14 | + | Gamma, |
|
| 15 | + | Delta, |
|
| 16 | + | Epsilon, |
|
| 17 | + | } |
|
| 18 | + | ||
| 19 | + | record State { |
|
| 20 | + | value: i32, |
|
| 21 | + | kind: Kind, |
|
| 22 | + | } |
|
| 23 | + | ||
| 24 | + | fn classify(s: State) -> i32 { |
|
| 25 | + | match s.kind { |
|
| 26 | + | case Kind::Alpha, |
|
| 27 | + | Kind::Beta, |
|
| 28 | + | Kind::Gamma => |
|
| 29 | + | { |
|
| 30 | + | return s.value + 10; |
|
| 31 | + | }, |
|
| 32 | + | case Kind::Delta => |
|
| 33 | + | { |
|
| 34 | + | return s.value + 20; |
|
| 35 | + | }, |
|
| 36 | + | else => { |
|
| 37 | + | // `s.value` must still be accessible here. |
|
| 38 | + | // Before the fix, the register holding `s` was clobbered |
|
| 39 | + | // by block-argument moves for the first case. |
|
| 40 | + | return s.value + 30; |
|
| 41 | + | } |
|
| 42 | + | } |
|
| 43 | + | } |
|
| 44 | + | ||
| 45 | + | @default fn main() -> i32 { |
|
| 46 | + | // Test multi-pattern match (Alpha) |
|
| 47 | + | let s1 = State { value: 5, kind: Kind::Alpha }; |
|
| 48 | + | if classify(s1) != 15 { |
|
| 49 | + | return 1; |
|
| 50 | + | } |
|
| 51 | + | // Test multi-pattern match (Gamma) |
|
| 52 | + | let s2 = State { value: 5, kind: Kind::Gamma }; |
|
| 53 | + | if classify(s2) != 15 { |
|
| 54 | + | return 2; |
|
| 55 | + | } |
|
| 56 | + | // Test single-pattern match (Delta) |
|
| 57 | + | let s3 = State { value: 5, kind: Kind::Delta }; |
|
| 58 | + | if classify(s3) != 25 { |
|
| 59 | + | return 3; |
|
| 60 | + | } |
|
| 61 | + | // Test else branch (Epsilon) - the critical case |
|
| 62 | + | let s4 = State { value: 5, kind: Kind::Epsilon }; |
|
| 63 | + | if classify(s4) != 35 { |
|
| 64 | + | return 4; |
|
| 65 | + | } |
|
| 66 | + | return 0; |
|
| 67 | + | } |
lib/std/arch/rv64/tests/match.mutref.push.rad
added
+56 -0
| 1 | + | //! Test pushing items through a mutable reference obtained via match. |
|
| 2 | + | ||
| 3 | + | record U32List { |
|
| 4 | + | data: *mut [u32], |
|
| 5 | + | len: u32, |
|
| 6 | + | } |
|
| 7 | + | ||
| 8 | + | union Sealed { |
|
| 9 | + | No { items: U32List }, |
|
| 10 | + | Yes, |
|
| 11 | + | } |
|
| 12 | + | ||
| 13 | + | fn pushItem(list: *mut U32List, value: u32) { |
|
| 14 | + | list.data[list.len] = value; |
|
| 15 | + | list.len = list.len + 1; |
|
| 16 | + | } |
|
| 17 | + | ||
| 18 | + | fn addToUnsealedBlock(state: *mut Sealed, value: u32) -> bool { |
|
| 19 | + | match state { |
|
| 20 | + | case Sealed::No { items } => { |
|
| 21 | + | pushItem(items, value); |
|
| 22 | + | return true; |
|
| 23 | + | }, |
|
| 24 | + | case Sealed::Yes => { |
|
| 25 | + | return false; |
|
| 26 | + | }, |
|
| 27 | + | } |
|
| 28 | + | } |
|
| 29 | + | ||
| 30 | + | @default fn main() -> i32 { |
|
| 31 | + | let mut buf: [u32; 8] = undefined; |
|
| 32 | + | buf[0] = 0; |
|
| 33 | + | let mut state = Sealed::No { items: U32List { data: &mut buf[0..8], len: 0 } }; |
|
| 34 | + | ||
| 35 | + | if not addToUnsealedBlock(&mut state, 42) { |
|
| 36 | + | return 1; |
|
| 37 | + | } |
|
| 38 | + | if not addToUnsealedBlock(&mut state, 99) { |
|
| 39 | + | return 2; |
|
| 40 | + | } |
|
| 41 | + | ||
| 42 | + | // Check that the state is still No with len=2. |
|
| 43 | + | let case Sealed::No { items } = state else { |
|
| 44 | + | return 3; |
|
| 45 | + | }; |
|
| 46 | + | if items.len != 2 { |
|
| 47 | + | return 4; |
|
| 48 | + | } |
|
| 49 | + | if items.data[0] != 42 { |
|
| 50 | + | return 5; |
|
| 51 | + | } |
|
| 52 | + | if items.data[1] != 99 { |
|
| 53 | + | return 6; |
|
| 54 | + | } |
|
| 55 | + | return 0; |
|
| 56 | + | } |
lib/std/arch/rv64/tests/match.mutref.union.rad
added
+38 -0
| 1 | + | //! Test matching on a mutable reference to a union field and mutating |
|
| 2 | + | //! the payload in place. |
|
| 3 | + | ||
| 4 | + | union State { |
|
| 5 | + | A { count: u32 }, |
|
| 6 | + | B, |
|
| 7 | + | } |
|
| 8 | + | ||
| 9 | + | record Data { |
|
| 10 | + | state: State, |
|
| 11 | + | value: u32, |
|
| 12 | + | } |
|
| 13 | + | ||
| 14 | + | fn process(d: *mut Data) -> u32 { |
|
| 15 | + | match &mut d.state { |
|
| 16 | + | case State::A { count } => { |
|
| 17 | + | let c = *count; |
|
| 18 | + | *count = c + 1; |
|
| 19 | + | return c; |
|
| 20 | + | }, |
|
| 21 | + | case State::B => { |
|
| 22 | + | return 0; |
|
| 23 | + | }, |
|
| 24 | + | } |
|
| 25 | + | } |
|
| 26 | + | ||
| 27 | + | @default fn main() -> i32 { |
|
| 28 | + | let mut d = Data { state: State::A { count: 10 }, value: 42 }; |
|
| 29 | + | let r1 = process(&mut d); |
|
| 30 | + | if r1 != 10 { |
|
| 31 | + | return 1; |
|
| 32 | + | } |
|
| 33 | + | let r2 = process(&mut d); |
|
| 34 | + | if r2 != 11 { |
|
| 35 | + | return 2; |
|
| 36 | + | } |
|
| 37 | + | return 0; |
|
| 38 | + | } |
lib/std/arch/rv64/tests/match.value.copy.rad
added
+103 -0
| 1 | + | //! Test that pattern match bindings on aggregates are independent copies, |
|
| 2 | + | //! not aliases into the original memory. Mutating the source after the |
|
| 3 | + | //! match must not affect the bound variables. |
|
| 4 | + | //! |
|
| 5 | + | //! The bug: when pattern-matching a union by value, the lowerer returns a |
|
| 6 | + | //! pointer into the original union's payload instead of copying it. If the |
|
| 7 | + | //! source is overwritten, the binding reads garbage. |
|
| 8 | + | ||
| 9 | + | record Pair { |
|
| 10 | + | a: u32, |
|
| 11 | + | b: u32, |
|
| 12 | + | } |
|
| 13 | + | ||
| 14 | + | union Data { |
|
| 15 | + | Some { pair: Pair }, |
|
| 16 | + | None, |
|
| 17 | + | } |
|
| 18 | + | ||
| 19 | + | record Box { |
|
| 20 | + | data: Data, |
|
| 21 | + | } |
|
| 22 | + | ||
| 23 | + | /// Test let-case on an aggregate union payload. |
|
| 24 | + | /// The Pair inside the union is an aggregate - its binding will be a pointer |
|
| 25 | + | /// into the union payload unless the lowerer copies it. |
|
| 26 | + | fn testLetCase() -> i32 { |
|
| 27 | + | let mut b = Box { |
|
| 28 | + | data: Data::Some { pair: Pair { a: 10, b: 20 } }, |
|
| 29 | + | }; |
|
| 30 | + | ||
| 31 | + | let case Data::Some { pair } = b.data else { |
|
| 32 | + | return 1; |
|
| 33 | + | }; |
|
| 34 | + | // Overwrite the source - binding should be unaffected. |
|
| 35 | + | b.data = Data::None; |
|
| 36 | + | ||
| 37 | + | if pair.a != 10 { |
|
| 38 | + | return 2; |
|
| 39 | + | } |
|
| 40 | + | if pair.b != 20 { |
|
| 41 | + | return 3; |
|
| 42 | + | } |
|
| 43 | + | return 0; |
|
| 44 | + | } |
|
| 45 | + | ||
| 46 | + | /// Test if-let-case with aggregate payload. |
|
| 47 | + | fn testIfLetCase() -> i32 { |
|
| 48 | + | let mut b = Box { |
|
| 49 | + | data: Data::Some { pair: Pair { a: 30, b: 40 } }, |
|
| 50 | + | }; |
|
| 51 | + | ||
| 52 | + | if let case Data::Some { pair } = b.data { |
|
| 53 | + | b.data = Data::None; |
|
| 54 | + | if pair.a != 30 { |
|
| 55 | + | return 5; |
|
| 56 | + | } |
|
| 57 | + | if pair.b != 40 { |
|
| 58 | + | return 6; |
|
| 59 | + | } |
|
| 60 | + | } else { |
|
| 61 | + | return 4; |
|
| 62 | + | } |
|
| 63 | + | return 0; |
|
| 64 | + | } |
|
| 65 | + | ||
| 66 | + | /// Test match with aggregate payload. |
|
| 67 | + | fn testMatch() -> i32 { |
|
| 68 | + | let mut b = Box { |
|
| 69 | + | data: Data::Some { pair: Pair { a: 50, b: 60 } }, |
|
| 70 | + | }; |
|
| 71 | + | ||
| 72 | + | match b.data { |
|
| 73 | + | case Data::Some { pair } => { |
|
| 74 | + | b.data = Data::None; |
|
| 75 | + | if pair.a != 50 { |
|
| 76 | + | return 8; |
|
| 77 | + | } |
|
| 78 | + | if pair.b != 60 { |
|
| 79 | + | return 9; |
|
| 80 | + | } |
|
| 81 | + | } |
|
| 82 | + | case Data::None => { |
|
| 83 | + | return 7; |
|
| 84 | + | } |
|
| 85 | + | } |
|
| 86 | + | return 0; |
|
| 87 | + | } |
|
| 88 | + | ||
| 89 | + | @default fn main() -> i32 { |
|
| 90 | + | let r1 = testLetCase(); |
|
| 91 | + | if r1 != 0 { |
|
| 92 | + | return r1; |
|
| 93 | + | } |
|
| 94 | + | let r2 = testIfLetCase(); |
|
| 95 | + | if r2 != 0 { |
|
| 96 | + | return r2; |
|
| 97 | + | } |
|
| 98 | + | let r3 = testMatch(); |
|
| 99 | + | if r3 != 0 { |
|
| 100 | + | return r3; |
|
| 101 | + | } |
|
| 102 | + | return 0; |
|
| 103 | + | } |
lib/std/arch/rv64/tests/match.void.then.or.rad
added
+82 -0
| 1 | + | //! Regression test: match on void union followed by 'or' equality check |
|
| 2 | + | //! in the else branch. |
|
| 3 | + | ||
| 4 | + | union Op { |
|
| 5 | + | Add, |
|
| 6 | + | Sub, |
|
| 7 | + | Mul, |
|
| 8 | + | Div, |
|
| 9 | + | And, |
|
| 10 | + | Or, |
|
| 11 | + | Xor, |
|
| 12 | + | Eq, |
|
| 13 | + | Ne, |
|
| 14 | + | } |
|
| 15 | + | ||
| 16 | + | record Expr { |
|
| 17 | + | op: Op, |
|
| 18 | + | left: i32, |
|
| 19 | + | right: i32, |
|
| 20 | + | } |
|
| 21 | + | ||
| 22 | + | fn eval(e: Expr) -> i32 { |
|
| 23 | + | match e.op { |
|
| 24 | + | case Op::And, |
|
| 25 | + | Op::Or, |
|
| 26 | + | Op::Xor => |
|
| 27 | + | { |
|
| 28 | + | return -1; |
|
| 29 | + | }, |
|
| 30 | + | case Op::Eq, |
|
| 31 | + | Op::Ne => |
|
| 32 | + | { |
|
| 33 | + | return -2; |
|
| 34 | + | }, |
|
| 35 | + | else => { |
|
| 36 | + | // Pointer arithmetic check: uses 'or' with void union equality. |
|
| 37 | + | if e.op == Op::Add or e.op == Op::Sub { |
|
| 38 | + | return e.left + e.right; |
|
| 39 | + | } |
|
| 40 | + | return e.left * e.right; |
|
| 41 | + | } |
|
| 42 | + | } |
|
| 43 | + | } |
|
| 44 | + | ||
| 45 | + | @default fn main() -> i32 { |
|
| 46 | + | // Test Add (in or-branch) |
|
| 47 | + | let e1 = Expr { op: Op::Add, left: 3, right: 4 }; |
|
| 48 | + | if eval(e1) != 7 { |
|
| 49 | + | return 1; |
|
| 50 | + | } |
|
| 51 | + | // Test Sub (in or-branch) |
|
| 52 | + | let e2 = Expr { op: Op::Sub, left: 10, right: 3 }; |
|
| 53 | + | if eval(e2) != 13 { |
|
| 54 | + | return 2; |
|
| 55 | + | } |
|
| 56 | + | // Test Mul (else of or-branch) |
|
| 57 | + | let e3 = Expr { op: Op::Mul, left: 3, right: 4 }; |
|
| 58 | + | if eval(e3) != 12 { |
|
| 59 | + | return 3; |
|
| 60 | + | } |
|
| 61 | + | // Test And (first multi-pattern case) |
|
| 62 | + | let e4 = Expr { op: Op::And, left: 0, right: 0 }; |
|
| 63 | + | if eval(e4) != -1 { |
|
| 64 | + | return 4; |
|
| 65 | + | } |
|
| 66 | + | // Test Xor (last in multi-pattern case) |
|
| 67 | + | let e5 = Expr { op: Op::Xor, left: 0, right: 0 }; |
|
| 68 | + | if eval(e5) != -1 { |
|
| 69 | + | return 5; |
|
| 70 | + | } |
|
| 71 | + | // Test Eq (second multi-pattern case) |
|
| 72 | + | let e6 = Expr { op: Op::Eq, left: 0, right: 0 }; |
|
| 73 | + | if eval(e6) != -2 { |
|
| 74 | + | return 6; |
|
| 75 | + | } |
|
| 76 | + | // Test Div (else of or, uses multiply path) |
|
| 77 | + | let e7 = Expr { op: Op::Div, left: 5, right: 6 }; |
|
| 78 | + | if eval(e7) != 30 { |
|
| 79 | + | return 7; |
|
| 80 | + | } |
|
| 81 | + | return 0; |
|
| 82 | + | } |
lib/std/arch/rv64/tests/memzero.result.bug.rad
added
+29 -0
| 1 | + | //! Test for result memzero overrun: success payload is tiny, error payload |
|
| 2 | + | //! is large, and a guard word sits immediately after the return slot. |
|
| 3 | + | ||
| 4 | + | union Err { |
|
| 5 | + | Big([u32; 3]), // 12-byte error payload, forces a large result container |
|
| 6 | + | } |
|
| 7 | + | ||
| 8 | + | fn fallible(ok: bool) throws (Err) { |
|
| 9 | + | if ok { |
|
| 10 | + | return; // success path (small payload) |
|
| 11 | + | } |
|
| 12 | + | throw Err::Big([1, 2, 3]); // large error payload |
|
| 13 | + | } |
|
| 14 | + | ||
| 15 | + | @default fn main() -> u32 { |
|
| 16 | + | // Place the return slot and a guard back-to-back on the stack. |
|
| 17 | + | let mut guard: u32 = 0; |
|
| 18 | + | ||
| 19 | + | // Success return should write only the small payload. |
|
| 20 | + | try fallible(false) catch { |
|
| 21 | + | guard = 0xDEAD; |
|
| 22 | + | }; |
|
| 23 | + | guard = 0xDEADBEEF; |
|
| 24 | + | ||
| 25 | + | if guard != 0xDEADBEEF { |
|
| 26 | + | return 1; // Guard was clobbered by an overzealous memzero. |
|
| 27 | + | } |
|
| 28 | + | return 0; |
|
| 29 | + | } |
lib/std/arch/rv64/tests/memzero.union.bug.rad
added
+25 -0
| 1 | + | //! Test enum with a large variant to force a payload capacity bigger than |
|
| 2 | + | //! the small variant we actually store. |
|
| 3 | + | union Payload { |
|
| 4 | + | Big([u32; 3]), // 12-byte payload |
|
| 5 | + | Small(u8), // 1-byte payload |
|
| 6 | + | } |
|
| 7 | + | ||
| 8 | + | record Frame { |
|
| 9 | + | guard1: u16, |
|
| 10 | + | val: Payload, |
|
| 11 | + | guard2: u32, |
|
| 12 | + | } |
|
| 13 | + | ||
| 14 | + | @default fn main() -> i32 { |
|
| 15 | + | let mut f: Frame = Frame { guard1: 0xDEAD, val: undefined, guard2: 0xDEADBEEF }; |
|
| 16 | + | ||
| 17 | + | f.val = Payload::Small(7); |
|
| 18 | + | f.val = Payload::Big([1, 2, 3]); |
|
| 19 | + | ||
| 20 | + | if f.guard1 == 0xDEAD and f.guard2 == 0xDEADBEEF { |
|
| 21 | + | return 0; |
|
| 22 | + | } else { |
|
| 23 | + | return 1; |
|
| 24 | + | }; |
|
| 25 | + | } |
lib/std/arch/rv64/tests/mutref.loop.bug.rad
added
+72 -0
| 1 | + | //! returns: 0 |
|
| 2 | + | //! Regression test: address-taken variable inside a loop. |
|
| 3 | + | //! |
|
| 4 | + | //! When `&mut var` appears inside a loop body, the variable must be |
|
| 5 | + | //! memory-backed from the start. Otherwise the SSA phi at the loop header |
|
| 6 | + | //! merges an integer value (from before the first iteration) with a pointer |
|
| 7 | + | //! value (the stack slot created by `&mut`), producing invalid IL. |
|
| 8 | + | //! |
|
| 9 | + | //! The critical case is when the loop executes zero iterations: the merge |
|
| 10 | + | //! block tries to `load` through the initial integer value (not a valid |
|
| 11 | + | //! pointer), crashing the program. |
|
| 12 | + | ||
| 13 | + | fn set(ptr: *mut u32, val: u32) { |
|
| 14 | + | *ptr = val; |
|
| 15 | + | } |
|
| 16 | + | ||
| 17 | + | /// Zero-iteration loop with &mut inside the body. |
|
| 18 | + | /// Without the fix, `val` starts as integer 42 in SSA, but the post-loop |
|
| 19 | + | /// merge tries to `load` through it as if it were a pointer. |
|
| 20 | + | fn testZeroIter(n: u32) -> u32 { |
|
| 21 | + | let mut val: u32 = 42; |
|
| 22 | + | let mut i: u32 = 0; |
|
| 23 | + | while i < n { |
|
| 24 | + | set(&mut val, val + 1); |
|
| 25 | + | i = i + 1; |
|
| 26 | + | } |
|
| 27 | + | return val; |
|
| 28 | + | } |
|
| 29 | + | ||
| 30 | + | /// Multiple iterations: accumulate via &mut pointer in a loop. |
|
| 31 | + | fn testMultiIter() -> u32 { |
|
| 32 | + | let mut acc: u32 = 0; |
|
| 33 | + | let mut i: u32 = 0; |
|
| 34 | + | while i < 5 { |
|
| 35 | + | set(&mut acc, acc + i); |
|
| 36 | + | i = i + 1; |
|
| 37 | + | } |
|
| 38 | + | return acc; |
|
| 39 | + | } |
|
| 40 | + | ||
| 41 | + | /// Multiple address-taken variables in the same loop. |
|
| 42 | + | fn testMultipleVars() -> u32 { |
|
| 43 | + | let mut a: u32 = 0; |
|
| 44 | + | let mut b: u32 = 100; |
|
| 45 | + | let mut i: u32 = 0; |
|
| 46 | + | while i < 3 { |
|
| 47 | + | set(&mut a, a + 1); |
|
| 48 | + | set(&mut b, b - 1); |
|
| 49 | + | i = i + 1; |
|
| 50 | + | } |
|
| 51 | + | return a + b; |
|
| 52 | + | } |
|
| 53 | + | ||
| 54 | + | @default fn main() -> i32 { |
|
| 55 | + | // Zero iterations: the critical regression case. |
|
| 56 | + | if testZeroIter(0) != 42 { |
|
| 57 | + | return 1; |
|
| 58 | + | } |
|
| 59 | + | // Non-zero iterations still work. |
|
| 60 | + | if testZeroIter(3) != 45 { |
|
| 61 | + | return 2; |
|
| 62 | + | } |
|
| 63 | + | // Accumulation: 0+0+1+2+3+4 = 10 |
|
| 64 | + | if testMultiIter() != 10 { |
|
| 65 | + | return 3; |
|
| 66 | + | } |
|
| 67 | + | // Multiple vars: 3 + 97 = 100 |
|
| 68 | + | if testMultipleVars() != 100 { |
|
| 69 | + | return 4; |
|
| 70 | + | } |
|
| 71 | + | return 0; |
|
| 72 | + | } |
lib/std/arch/rv64/tests/opt.assignment.bug.rad
added
+68 -0
| 1 | + | //! returns: 1 |
|
| 2 | + | //! Test optional assignment from function return values. |
|
| 3 | + | ||
| 4 | + | /// Return an optional value based on a flag. |
|
| 5 | + | fn returnOptional(shouldReturn: bool) -> ?i32 { |
|
| 6 | + | if shouldReturn { |
|
| 7 | + | return 99; |
|
| 8 | + | } |
|
| 9 | + | return nil; |
|
| 10 | + | } |
|
| 11 | + | ||
| 12 | + | /// Test assigning via if-let pattern. |
|
| 13 | + | fn testIfLetPattern() -> bool { |
|
| 14 | + | let mut result: ?i32 = nil; |
|
| 15 | + | ||
| 16 | + | if let val = returnOptional(true) { |
|
| 17 | + | result = val; |
|
| 18 | + | } |
|
| 19 | + | if let r = result { |
|
| 20 | + | return r == 99; |
|
| 21 | + | } |
|
| 22 | + | return false; |
|
| 23 | + | } |
|
| 24 | + | ||
| 25 | + | /// Test direct assignment from function. |
|
| 26 | + | fn testDirectAssignment() -> bool { |
|
| 27 | + | let mut result: ?i32 = nil; |
|
| 28 | + | ||
| 29 | + | result = returnOptional(true); |
|
| 30 | + | ||
| 31 | + | if let r = result { |
|
| 32 | + | return r == 99; |
|
| 33 | + | } |
|
| 34 | + | return false; |
|
| 35 | + | } |
|
| 36 | + | ||
| 37 | + | /// Test if-let pattern with nil return. |
|
| 38 | + | fn testIfLetPatternNil() -> bool { |
|
| 39 | + | let mut result: ?i32 = nil; |
|
| 40 | + | ||
| 41 | + | if let val = returnOptional(false) { |
|
| 42 | + | result = val; |
|
| 43 | + | } |
|
| 44 | + | if let r = result { |
|
| 45 | + | return false; |
|
| 46 | + | } |
|
| 47 | + | return true; |
|
| 48 | + | } |
|
| 49 | + | ||
| 50 | + | /// Test direct assignment with nil return. |
|
| 51 | + | fn testDirectAssignmentNil() -> bool { |
|
| 52 | + | let mut result: ?i32 = nil; |
|
| 53 | + | ||
| 54 | + | result = returnOptional(false); |
|
| 55 | + | ||
| 56 | + | if let r = result { |
|
| 57 | + | return false; |
|
| 58 | + | } |
|
| 59 | + | return true; |
|
| 60 | + | } |
|
| 61 | + | ||
| 62 | + | @default fn main() -> bool { |
|
| 63 | + | return |
|
| 64 | + | testIfLetPattern() and |
|
| 65 | + | testDirectAssignment() and |
|
| 66 | + | testIfLetPatternNil() and |
|
| 67 | + | testDirectAssignmentNil(); |
|
| 68 | + | } |
lib/std/arch/rv64/tests/opt.bug.test.rad
added
+73 -0
| 1 | + | //! returns: 1 |
|
| 2 | + | //! Test optional if-let patterns with helper functions. |
|
| 3 | + | ||
| 4 | + | fn inner(val: i32) -> i32 { |
|
| 5 | + | return val + 1; |
|
| 6 | + | } |
|
| 7 | + | ||
| 8 | + | fn helperWithOptional(opt: ?i32) -> ?i32 { |
|
| 9 | + | if let val = opt { |
|
| 10 | + | return inner(val); |
|
| 11 | + | } |
|
| 12 | + | return nil; |
|
| 13 | + | } |
|
| 14 | + | ||
| 15 | + | /// Test: Manual if-let pattern |
|
| 16 | + | fn testManualPattern() -> bool { |
|
| 17 | + | let mut result: ?i32 = nil; |
|
| 18 | + | let opt: ?i32 = 42; |
|
| 19 | + | ||
| 20 | + | if let val = opt { |
|
| 21 | + | result = inner(val); |
|
| 22 | + | } |
|
| 23 | + | ||
| 24 | + | if let r = result { |
|
| 25 | + | return r == 43; |
|
| 26 | + | } |
|
| 27 | + | return false; |
|
| 28 | + | } |
|
| 29 | + | ||
| 30 | + | /// Test: Using helper function with optional |
|
| 31 | + | fn testHelperPattern() -> bool { |
|
| 32 | + | let opt: ?i32 = 42; |
|
| 33 | + | let result = helperWithOptional(opt); |
|
| 34 | + | ||
| 35 | + | if let r = result { |
|
| 36 | + | return r == 43; |
|
| 37 | + | } |
|
| 38 | + | return false; |
|
| 39 | + | } |
|
| 40 | + | ||
| 41 | + | /// Test: Manual pattern with nil |
|
| 42 | + | fn testManualPatternNil() -> bool { |
|
| 43 | + | let mut result: ?i32 = nil; |
|
| 44 | + | let opt: ?i32 = nil; |
|
| 45 | + | ||
| 46 | + | if let val = opt { |
|
| 47 | + | result = inner(val); |
|
| 48 | + | } |
|
| 49 | + | ||
| 50 | + | if let r = result { |
|
| 51 | + | return false; // Should be nil |
|
| 52 | + | } |
|
| 53 | + | return true; |
|
| 54 | + | } |
|
| 55 | + | ||
| 56 | + | /// Test: Helper pattern with nil |
|
| 57 | + | fn testHelperPatternNil() -> bool { |
|
| 58 | + | let opt: ?i32 = nil; |
|
| 59 | + | let result = helperWithOptional(opt); |
|
| 60 | + | ||
| 61 | + | if let r = result { |
|
| 62 | + | return false; // Should be nil |
|
| 63 | + | } |
|
| 64 | + | return true; |
|
| 65 | + | } |
|
| 66 | + | ||
| 67 | + | @default fn main() -> bool { |
|
| 68 | + | return |
|
| 69 | + | testManualPattern() and |
|
| 70 | + | testHelperPattern() and |
|
| 71 | + | testManualPatternNil() and |
|
| 72 | + | testHelperPatternNil(); |
|
| 73 | + | } |
lib/std/arch/rv64/tests/opt.if.let.complex.rad
added
+307 -0
| 1 | + | //! returns: 0 |
|
| 2 | + | //! Test optional types with complex if-let patterns. |
|
| 3 | + | ||
| 4 | + | /// Test optional types with different base types |
|
| 5 | + | fn testOptionalTypes() -> bool { |
|
| 6 | + | let optBool: ?bool = true; |
|
| 7 | + | let optFloat: ?i32 = 42; |
|
| 8 | + | let optNilBool: ?bool = nil; |
|
| 9 | + | let optNilFloat: ?i32 = nil; |
|
| 10 | + | ||
| 11 | + | if let b = optBool; b { |
|
| 12 | + | if let f = optFloat; f > 40 { |
|
| 13 | + | if let nb = optNilBool { |
|
| 14 | + | return false; // Should not reach here |
|
| 15 | + | } else { |
|
| 16 | + | if let nf = optNilFloat { |
|
| 17 | + | return false; // Should not reach here |
|
| 18 | + | } else { |
|
| 19 | + | return true; // All conditions met |
|
| 20 | + | } |
|
| 21 | + | } |
|
| 22 | + | } else { |
|
| 23 | + | return false; |
|
| 24 | + | } |
|
| 25 | + | } else { |
|
| 26 | + | return false; |
|
| 27 | + | } |
|
| 28 | + | return false; |
|
| 29 | + | } |
|
| 30 | + | ||
| 31 | + | /// Test complex type coercion chains |
|
| 32 | + | fn testComplexCoercion() -> bool { |
|
| 33 | + | let val: i32 = 42; |
|
| 34 | + | let opt1: ?i32 = val; // i32 -> ?i32 |
|
| 35 | + | let opt2: ?i32 = 42; // Direct literal -> ?i32 |
|
| 36 | + | ||
| 37 | + | // Test that both produce same result |
|
| 38 | + | if let x = opt1 { |
|
| 39 | + | if let y = opt2 { |
|
| 40 | + | return x == y; |
|
| 41 | + | } else { |
|
| 42 | + | return false; |
|
| 43 | + | } |
|
| 44 | + | } else { |
|
| 45 | + | return false; |
|
| 46 | + | } |
|
| 47 | + | return false; |
|
| 48 | + | } |
|
| 49 | + | ||
| 50 | + | /// Test variable shadowing in different scopes |
|
| 51 | + | fn testVariableShadowing() -> bool { |
|
| 52 | + | let x: i32 = 10; |
|
| 53 | + | let opt: ?i32 = 20; |
|
| 54 | + | ||
| 55 | + | if let x = opt; x > 15 { |
|
| 56 | + | // x is now 20, shadowing outer x (10) |
|
| 57 | + | if x == 20 { |
|
| 58 | + | let x: i32 = 30; // Another shadow |
|
| 59 | + | if x == 30 { |
|
| 60 | + | return true; |
|
| 61 | + | } else { |
|
| 62 | + | return false; |
|
| 63 | + | } |
|
| 64 | + | } else { |
|
| 65 | + | return false; |
|
| 66 | + | } |
|
| 67 | + | } else { |
|
| 68 | + | return false; |
|
| 69 | + | } |
|
| 70 | + | return false; |
|
| 71 | + | } |
|
| 72 | + | ||
| 73 | + | /// Test zero and boundary values |
|
| 74 | + | fn testBoundaryValues() -> bool { |
|
| 75 | + | let zero: ?i32 = 0; |
|
| 76 | + | let negative: ?i32 = -1; |
|
| 77 | + | let minVal: ?i32 = -2147483648; // i32 min |
|
| 78 | + | let maxVal: ?i32 = 2147483647; // i32 max |
|
| 79 | + | ||
| 80 | + | if let z = zero; z == 0 { |
|
| 81 | + | if let n = negative; n < 0 { |
|
| 82 | + | if let min = minVal; min < 0 { |
|
| 83 | + | if let max = maxVal; max > 0 { |
|
| 84 | + | return true; |
|
| 85 | + | } else { |
|
| 86 | + | return false; |
|
| 87 | + | } |
|
| 88 | + | } else { |
|
| 89 | + | return false; |
|
| 90 | + | } |
|
| 91 | + | } else { |
|
| 92 | + | return false; |
|
| 93 | + | } |
|
| 94 | + | } else { |
|
| 95 | + | return false; |
|
| 96 | + | } |
|
| 97 | + | return false; |
|
| 98 | + | } |
|
| 99 | + | ||
| 100 | + | /// Test optional with zero guard condition |
|
| 101 | + | fn testZeroGuardCondition() -> bool { |
|
| 102 | + | let opt: ?i32 = 0; |
|
| 103 | + | ||
| 104 | + | if let x = opt; x >= 0 { |
|
| 105 | + | return x == 0; |
|
| 106 | + | } else { |
|
| 107 | + | return false; |
|
| 108 | + | } |
|
| 109 | + | return false; |
|
| 110 | + | } |
|
| 111 | + | ||
| 112 | + | /// Test multiple nil assignments |
|
| 113 | + | fn testMultipleNilAssignments() -> bool { |
|
| 114 | + | let mut opt: ?i32 = 42; |
|
| 115 | + | ||
| 116 | + | if let x = opt { |
|
| 117 | + | if x == 42 { |
|
| 118 | + | opt = nil; |
|
| 119 | + | if let y = opt { |
|
| 120 | + | return false; // Should not reach here after nil assignment |
|
| 121 | + | } else { |
|
| 122 | + | opt = 100; |
|
| 123 | + | if let z = opt { |
|
| 124 | + | return z == 100; |
|
| 125 | + | } else { |
|
| 126 | + | return false; |
|
| 127 | + | } |
|
| 128 | + | } |
|
| 129 | + | } else { |
|
| 130 | + | return false; |
|
| 131 | + | } |
|
| 132 | + | } else { |
|
| 133 | + | return false; |
|
| 134 | + | } |
|
| 135 | + | return false; |
|
| 136 | + | } |
|
| 137 | + | ||
| 138 | + | /// Test nested guard conditions with same variable |
|
| 139 | + | fn testNestedSameVariableGuards() -> bool { |
|
| 140 | + | let opt1: ?i32 = 25; |
|
| 141 | + | let opt2: ?i32 = 25; |
|
| 142 | + | ||
| 143 | + | if let x = opt1; x > 20 { |
|
| 144 | + | if let x = opt2; x < 30 { |
|
| 145 | + | // Inner x shadows outer x, both are 25 |
|
| 146 | + | return x == 25; |
|
| 147 | + | } else { |
|
| 148 | + | return false; |
|
| 149 | + | } |
|
| 150 | + | } else { |
|
| 151 | + | return false; |
|
| 152 | + | } |
|
| 153 | + | return false; |
|
| 154 | + | } |
|
| 155 | + | ||
| 156 | + | /// Test complex boolean expressions in guards |
|
| 157 | + | fn testComplexGuardExpressions() -> bool { |
|
| 158 | + | let opt1: ?i32 = 15; |
|
| 159 | + | let opt2: ?i32 = 25; |
|
| 160 | + | ||
| 161 | + | if let x = opt1; x > 10 and x < 20 { |
|
| 162 | + | if let y = opt2; y > 20 or y == 25 { |
|
| 163 | + | return (x + y) == 40; |
|
| 164 | + | } else { |
|
| 165 | + | return false; |
|
| 166 | + | } |
|
| 167 | + | } else { |
|
| 168 | + | return false; |
|
| 169 | + | } |
|
| 170 | + | return false; |
|
| 171 | + | } |
|
| 172 | + | ||
| 173 | + | /// Test guard with arithmetic expressions |
|
| 174 | + | fn testArithmeticInGuards() -> bool { |
|
| 175 | + | let opt: ?i32 = 10; |
|
| 176 | + | ||
| 177 | + | if let x = opt; (x + 5) > 12 and (x * 2) < 25 { |
|
| 178 | + | return x == 10; |
|
| 179 | + | } else { |
|
| 180 | + | return false; |
|
| 181 | + | } |
|
| 182 | + | return false; |
|
| 183 | + | } |
|
| 184 | + | ||
| 185 | + | /// Test early returns from guard failure |
|
| 186 | + | fn testEarlyReturnGuardFailure() -> bool { |
|
| 187 | + | let opt: ?i32 = 5; |
|
| 188 | + | ||
| 189 | + | if let x = opt; x > 10 { |
|
| 190 | + | return false; // Should not reach here |
|
| 191 | + | } else { |
|
| 192 | + | return true; // Should reach here due to guard failure |
|
| 193 | + | } |
|
| 194 | + | return false; |
|
| 195 | + | } |
|
| 196 | + | ||
| 197 | + | /// Test sequential nil checks |
|
| 198 | + | fn testSequentialNilChecks() -> bool { |
|
| 199 | + | let opt1: ?i32 = nil; |
|
| 200 | + | let opt2: ?i32 = nil; |
|
| 201 | + | let opt3: ?i32 = 42; |
|
| 202 | + | ||
| 203 | + | if let x = opt1 { |
|
| 204 | + | return false; |
|
| 205 | + | } |
|
| 206 | + | ||
| 207 | + | if let y = opt2 { |
|
| 208 | + | return false; |
|
| 209 | + | } |
|
| 210 | + | ||
| 211 | + | if let z = opt3 { |
|
| 212 | + | return z == 42; |
|
| 213 | + | } |
|
| 214 | + | ||
| 215 | + | return false; |
|
| 216 | + | } |
|
| 217 | + | ||
| 218 | + | /// Test mixed regular if and if let |
|
| 219 | + | fn testMixedIfTypes() -> bool { |
|
| 220 | + | let condition: bool = true; |
|
| 221 | + | let opt: ?i32 = 30; |
|
| 222 | + | ||
| 223 | + | if condition { |
|
| 224 | + | if let x = opt; x == 30 { |
|
| 225 | + | if not condition { |
|
| 226 | + | return false; |
|
| 227 | + | } else { |
|
| 228 | + | return true; |
|
| 229 | + | } |
|
| 230 | + | } else { |
|
| 231 | + | return false; |
|
| 232 | + | } |
|
| 233 | + | } else { |
|
| 234 | + | return false; |
|
| 235 | + | } |
|
| 236 | + | return false; |
|
| 237 | + | } |
|
| 238 | + | ||
| 239 | + | /// Test optional assignment in conditional chains |
|
| 240 | + | fn testOptionalAssignmentChains() -> bool { |
|
| 241 | + | let mut opt: ?i32 = nil; |
|
| 242 | + | ||
| 243 | + | if let x = opt { |
|
| 244 | + | return false; |
|
| 245 | + | } |
|
| 246 | + | ||
| 247 | + | opt = 10; |
|
| 248 | + | if let x = opt; x == 10 { |
|
| 249 | + | opt = 20; |
|
| 250 | + | if let y = opt; y == 20 { |
|
| 251 | + | opt = nil; |
|
| 252 | + | if let z = opt { |
|
| 253 | + | return false; |
|
| 254 | + | } else { |
|
| 255 | + | return true; |
|
| 256 | + | } |
|
| 257 | + | } else { |
|
| 258 | + | return false; |
|
| 259 | + | } |
|
| 260 | + | } else { |
|
| 261 | + | return false; |
|
| 262 | + | } |
|
| 263 | + | return false; |
|
| 264 | + | } |
|
| 265 | + | ||
| 266 | + | @default fn main() -> i32 { |
|
| 267 | + | if not testOptionalTypes() { |
|
| 268 | + | return 1; |
|
| 269 | + | } |
|
| 270 | + | if not testComplexCoercion() { |
|
| 271 | + | return 2; |
|
| 272 | + | } |
|
| 273 | + | if not testVariableShadowing() { |
|
| 274 | + | return 3; |
|
| 275 | + | } |
|
| 276 | + | if not testBoundaryValues() { |
|
| 277 | + | return 4; |
|
| 278 | + | } |
|
| 279 | + | if not testZeroGuardCondition() { |
|
| 280 | + | return 5; |
|
| 281 | + | } |
|
| 282 | + | if not testMultipleNilAssignments() { |
|
| 283 | + | return 6; |
|
| 284 | + | } |
|
| 285 | + | if not testNestedSameVariableGuards() { |
|
| 286 | + | return 7; |
|
| 287 | + | } |
|
| 288 | + | if not testComplexGuardExpressions() { |
|
| 289 | + | return 8; |
|
| 290 | + | } |
|
| 291 | + | if not testArithmeticInGuards() { |
|
| 292 | + | return 9; |
|
| 293 | + | } |
|
| 294 | + | if not testEarlyReturnGuardFailure() { |
|
| 295 | + | return 10; |
|
| 296 | + | } |
|
| 297 | + | if not testSequentialNilChecks() { |
|
| 298 | + | return 11; |
|
| 299 | + | } |
|
| 300 | + | if not testMixedIfTypes() { |
|
| 301 | + | return 12; |
|
| 302 | + | } |
|
| 303 | + | if not testOptionalAssignmentChains() { |
|
| 304 | + | return 13; |
|
| 305 | + | } |
|
| 306 | + | return 0; |
|
| 307 | + | } |
lib/std/arch/rv64/tests/opt.if.let.guard.rad
added
+42 -0
| 1 | + | //! returns: 1 |
|
| 2 | + | //! Test if-let with guard conditions. |
|
| 3 | + | ||
| 4 | + | /// Test if-let with a guard condition that succeeds. |
|
| 5 | + | fn testGuard() -> bool { |
|
| 6 | + | let opt: ?i32 = 42; |
|
| 7 | + | if let x = opt; x > 40 { |
|
| 8 | + | return x == 42; |
|
| 9 | + | } else { |
|
| 10 | + | return false; |
|
| 11 | + | } |
|
| 12 | + | return false; |
|
| 13 | + | } |
|
| 14 | + | ||
| 15 | + | /// Test if-let with a guard that fails. |
|
| 16 | + | fn testGuardFails() -> bool { |
|
| 17 | + | let opt: ?i32 = 30; |
|
| 18 | + | if let x = opt; x > 40 { |
|
| 19 | + | return false; |
|
| 20 | + | } else { |
|
| 21 | + | return true; |
|
| 22 | + | } |
|
| 23 | + | return false; |
|
| 24 | + | } |
|
| 25 | + | ||
| 26 | + | /// Test if-let with nil and a guard. |
|
| 27 | + | fn testNilWithGuard() -> bool { |
|
| 28 | + | let opt: ?i32 = nil; |
|
| 29 | + | if let x = opt; x > 0 { |
|
| 30 | + | return false; |
|
| 31 | + | } else { |
|
| 32 | + | return true; |
|
| 33 | + | } |
|
| 34 | + | return false; |
|
| 35 | + | } |
|
| 36 | + | ||
| 37 | + | @default fn main() -> bool { |
|
| 38 | + | return |
|
| 39 | + | testGuard() and |
|
| 40 | + | testGuardFails() and |
|
| 41 | + | testNilWithGuard(); |
|
| 42 | + | } |
lib/std/arch/rv64/tests/opt.if.let.rad
added
+50 -0
| 1 | + | //! returns: 1 |
|
| 2 | + | //! Test if-let with optionals. |
|
| 3 | + | ||
| 4 | + | /// Test if-let with an optional containing a value. |
|
| 5 | + | fn testSomeValue() -> bool { |
|
| 6 | + | let opt: ?i32 = 42; |
|
| 7 | + | if let x = opt { |
|
| 8 | + | return x == 42; |
|
| 9 | + | } else { |
|
| 10 | + | return false; |
|
| 11 | + | } |
|
| 12 | + | return false; |
|
| 13 | + | } |
|
| 14 | + | ||
| 15 | + | /// Test if-let with a nil optional. |
|
| 16 | + | fn testNilValue() -> bool { |
|
| 17 | + | let opt: ?i32 = nil; |
|
| 18 | + | if let x = opt { |
|
| 19 | + | return false; |
|
| 20 | + | } else { |
|
| 21 | + | return true; |
|
| 22 | + | } |
|
| 23 | + | return false; |
|
| 24 | + | } |
|
| 25 | + | ||
| 26 | + | /// Test if-let without else branch, with a some value. |
|
| 27 | + | fn testNoElseSome() -> bool { |
|
| 28 | + | let opt: ?i32 = 0; |
|
| 29 | + | if let x = opt { |
|
| 30 | + | return x == 0; |
|
| 31 | + | } |
|
| 32 | + | return false; |
|
| 33 | + | } |
|
| 34 | + | ||
| 35 | + | /// Test if-let without else branch, with nil. |
|
| 36 | + | fn testNoElseNil() -> bool { |
|
| 37 | + | let opt: ?i32 = nil; |
|
| 38 | + | if let x = opt { |
|
| 39 | + | return false; |
|
| 40 | + | } |
|
| 41 | + | return true; |
|
| 42 | + | } |
|
| 43 | + | ||
| 44 | + | @default fn main() -> bool { |
|
| 45 | + | return |
|
| 46 | + | testSomeValue() and |
|
| 47 | + | testNilValue() and |
|
| 48 | + | testNoElseSome() and |
|
| 49 | + | testNoElseNil(); |
|
| 50 | + | } |
lib/std/arch/rv64/tests/opt.record.eq.rad
added
+45 -0
| 1 | + | //! returns: 1 |
|
| 2 | + | //! Test equality comparison of optional records. |
|
| 3 | + | ||
| 4 | + | record Inner { |
|
| 5 | + | start: i32, |
|
| 6 | + | end: i32, |
|
| 7 | + | } |
|
| 8 | + | ||
| 9 | + | record Outer { |
|
| 10 | + | item: ?Inner, |
|
| 11 | + | } |
|
| 12 | + | ||
| 13 | + | @default fn main() -> bool { |
|
| 14 | + | let outer_a = Outer { item: Inner { start: 3, end: 9 } }; |
|
| 15 | + | let outer_b = Outer { item: Inner { start: 3, end: 9 } }; |
|
| 16 | + | ||
| 17 | + | if outer_a != outer_b { |
|
| 18 | + | return false; |
|
| 19 | + | } |
|
| 20 | + | ||
| 21 | + | let outer_c = Outer { item: Inner { start: 4, end: 6 } }; |
|
| 22 | + | if outer_a == outer_c { |
|
| 23 | + | return false; |
|
| 24 | + | } |
|
| 25 | + | ||
| 26 | + | let mut opt_a: ?Outer = nil; |
|
| 27 | + | let mut opt_b: ?Outer = nil; |
|
| 28 | + | let mut opt_diff: ?Outer = nil; |
|
| 29 | + | let mut opt_nil: ?Outer = nil; |
|
| 30 | + | ||
| 31 | + | opt_a = outer_a; |
|
| 32 | + | opt_b = outer_b; |
|
| 33 | + | opt_diff = outer_c; |
|
| 34 | + | ||
| 35 | + | if opt_a != opt_b { |
|
| 36 | + | return false; |
|
| 37 | + | } |
|
| 38 | + | if opt_a == opt_diff { |
|
| 39 | + | return false; |
|
| 40 | + | } |
|
| 41 | + | if opt_a == opt_nil { |
|
| 42 | + | return false; |
|
| 43 | + | } |
|
| 44 | + | return true; |
|
| 45 | + | } |
lib/std/arch/rv64/tests/opt.record.rad
added
+42 -0
| 1 | + | //! returns: 28 |
|
| 2 | + | //! Test optional records with if-let. |
|
| 3 | + | ||
| 4 | + | record Vector { |
|
| 5 | + | x: i32, |
|
| 6 | + | y: i32, |
|
| 7 | + | z: i32, |
|
| 8 | + | } |
|
| 9 | + | ||
| 10 | + | record Wrapper { |
|
| 11 | + | opt: ?i32, |
|
| 12 | + | } |
|
| 13 | + | ||
| 14 | + | fn take_optional(x: ?i32) -> i32 { |
|
| 15 | + | if let value = x { |
|
| 16 | + | return value; |
|
| 17 | + | } |
|
| 18 | + | return 0; |
|
| 19 | + | } |
|
| 20 | + | ||
| 21 | + | @default fn main() -> i32 { |
|
| 22 | + | let mut v: ?Vector = nil; |
|
| 23 | + | v = Vector { x: 3, y: 4, z: 5 }; |
|
| 24 | + | ||
| 25 | + | let mut total: i32 = 0; |
|
| 26 | + | ||
| 27 | + | if let w = v { |
|
| 28 | + | total = w.x + w.y + w.z; |
|
| 29 | + | } else { |
|
| 30 | + | return 0; |
|
| 31 | + | } |
|
| 32 | + | ||
| 33 | + | let wrapper = Wrapper { opt: 7 }; |
|
| 34 | + | if let value = wrapper.opt { |
|
| 35 | + | total = total + value; |
|
| 36 | + | } else { |
|
| 37 | + | return 0; |
|
| 38 | + | } |
|
| 39 | + | ||
| 40 | + | let call = take_optional(9); |
|
| 41 | + | return total + call; |
|
| 42 | + | } |
lib/std/arch/rv64/tests/opt.return.array.rad
added
+15 -0
| 1 | + | //! returns: 99 |
|
| 2 | + | //! Test returning optional arrays from functions. |
|
| 3 | + | ||
| 4 | + | fn ary() -> ?[i32; 3] { |
|
| 5 | + | return [1, 2, 3]; |
|
| 6 | + | } |
|
| 7 | + | ||
| 8 | + | @default fn main() -> u32 { |
|
| 9 | + | if let s = ary() { |
|
| 10 | + | if s.len == 3 and s[0] == 1 and s[1] == 2 and s[2] == 3 { |
|
| 11 | + | return 99; |
|
| 12 | + | } |
|
| 13 | + | } |
|
| 14 | + | return 1; |
|
| 15 | + | } |
lib/std/arch/rv64/tests/opt.return.nested.rad
added
+40 -0
| 1 | + | //! returns: 99 |
|
| 2 | + | ||
| 3 | + | record Pair { |
|
| 4 | + | x: u32, |
|
| 5 | + | y: u32, |
|
| 6 | + | } |
|
| 7 | + | ||
| 8 | + | fn pick(flag: bool) -> ?Pair { |
|
| 9 | + | if flag { |
|
| 10 | + | return Pair { x: 10, y: 89 }; |
|
| 11 | + | } |
|
| 12 | + | return nil; |
|
| 13 | + | } |
|
| 14 | + | ||
| 15 | + | fn nest(outer: bool, inner: bool) -> ??Pair { |
|
| 16 | + | if outer { |
|
| 17 | + | let value = pick(inner); |
|
| 18 | + | return value; |
|
| 19 | + | } |
|
| 20 | + | return nil; |
|
| 21 | + | } |
|
| 22 | + | ||
| 23 | + | @default fn main() -> u32 { |
|
| 24 | + | if let outer = nest(true, true) { |
|
| 25 | + | if let pair = outer; pair.x == 10 and pair.y == 89 { |
|
| 26 | + | if let outer_some_nil = nest(true, false) { |
|
| 27 | + | if let inner_nil = outer_some_nil { |
|
| 28 | + | return 11; |
|
| 29 | + | } |
|
| 30 | + | } else { |
|
| 31 | + | return 12; |
|
| 32 | + | } |
|
| 33 | + | if let unreachable = nest(false, true) { |
|
| 34 | + | return 13; |
|
| 35 | + | } |
|
| 36 | + | return 99; |
|
| 37 | + | } |
|
| 38 | + | } |
|
| 39 | + | return 1; |
|
| 40 | + | } |
lib/std/arch/rv64/tests/opt.return.record.rad
added
+21 -0
| 1 | + | //! returns: 99 |
|
| 2 | + | //! Test returning optional records from functions. |
|
| 3 | + | ||
| 4 | + | record Vector { |
|
| 5 | + | x: i32, |
|
| 6 | + | y: i32, |
|
| 7 | + | z: i32 |
|
| 8 | + | } |
|
| 9 | + | ||
| 10 | + | fn strt() -> ?Vector { |
|
| 11 | + | return Vector { x: 1, y: 2, z: 3 }; |
|
| 12 | + | } |
|
| 13 | + | ||
| 14 | + | @default fn main() -> u32 { |
|
| 15 | + | if let s = strt() { |
|
| 16 | + | if s.x == 1 and s.y == 2 and s.z == 3 { |
|
| 17 | + | return 99; |
|
| 18 | + | } |
|
| 19 | + | } |
|
| 20 | + | return 1; |
|
| 21 | + | } |
lib/std/arch/rv64/tests/opt.type.rad
added
+11 -0
| 1 | + | @default fn main() -> i32 { |
|
| 2 | + | let optSome: ?i32 = 42; |
|
| 3 | + | let optNone: ?i32 = nil; |
|
| 4 | + | ||
| 5 | + | let mut optMaybe: ?i32 = nil; |
|
| 6 | + | optMaybe = optSome; |
|
| 7 | + | optMaybe = 42; |
|
| 8 | + | optMaybe = nil; |
|
| 9 | + | ||
| 10 | + | return 0; |
|
| 11 | + | } |
lib/std/arch/rv64/tests/opt.while.let.complex.rad
added
+26 -0
| 1 | + | //! returns: 0 |
|
| 2 | + | //! Test while-let with guard condition. |
|
| 3 | + | ||
| 4 | + | fn decr(n: u32) -> ?u32 { |
|
| 5 | + | if n > 0 { |
|
| 6 | + | return n - 1; |
|
| 7 | + | } else { |
|
| 8 | + | return nil; |
|
| 9 | + | } |
|
| 10 | + | } |
|
| 11 | + | ||
| 12 | + | @default fn main() -> i32 { |
|
| 13 | + | let mut cur: u32 = 7; |
|
| 14 | + | let mut sum: u32 = 0; |
|
| 15 | + | ||
| 16 | + | while let x = decr(cur); x > 1 { |
|
| 17 | + | sum = sum + x; |
|
| 18 | + | cur = x; |
|
| 19 | + | } |
|
| 20 | + | ||
| 21 | + | // sum should be 6 + 5 + 4 + 3 + 2 = 20 |
|
| 22 | + | if sum != 20 { |
|
| 23 | + | return 1; |
|
| 24 | + | } |
|
| 25 | + | return 0; |
|
| 26 | + | } |
lib/std/arch/rv64/tests/placeholder.basic.rad
added
+10 -0
| 1 | + | ||
| 2 | + | @default fn main() -> i32 { |
|
| 3 | + | let _ : i32 = 42; |
|
| 4 | + | let x: ?i32 = 10; |
|
| 5 | + | ||
| 6 | + | if let _ = x { |
|
| 7 | + | return 0; |
|
| 8 | + | } |
|
| 9 | + | return 1; |
|
| 10 | + | } |
lib/std/arch/rv64/tests/placeholder.comprehensive.rad
added
+30 -0
| 1 | + | ||
| 2 | + | @default fn main() -> i32 { |
|
| 3 | + | // Test 1: let with placeholder |
|
| 4 | + | let _ : i32 = 100; |
|
| 5 | + | ||
| 6 | + | // Test 2: if let with placeholder |
|
| 7 | + | let x: ?i32 = 42; |
|
| 8 | + | if let _ = x { |
|
| 9 | + | // Should execute this branch |
|
| 10 | + | } else { |
|
| 11 | + | return 1; |
|
| 12 | + | } |
|
| 13 | + | ||
| 14 | + | // Test 3: if let with nil and placeholder |
|
| 15 | + | let y: ?i32 = nil; |
|
| 16 | + | if let _ = y { |
|
| 17 | + | return 2; |
|
| 18 | + | } |
|
| 19 | + | ||
| 20 | + | // Test 4: for loop with placeholder for value |
|
| 21 | + | let arr: [i32; 3] = [1, 2, 3]; |
|
| 22 | + | let mut count: i32 = 0; |
|
| 23 | + | for _ in &arr[..] { |
|
| 24 | + | count = count + 1; |
|
| 25 | + | } |
|
| 26 | + | if count != 3 { |
|
| 27 | + | return 3; |
|
| 28 | + | } |
|
| 29 | + | return 0; |
|
| 30 | + | } |
lib/std/arch/rv64/tests/pointer.copy.edge.case.rad
added
+77 -0
| 1 | + | //! returns: 0 |
|
| 2 | + | ||
| 3 | + | union NodeKind { |
|
| 4 | + | Placeholder, |
|
| 5 | + | Bool(bool), |
|
| 6 | + | } |
|
| 7 | + | ||
| 8 | + | record Span { |
|
| 9 | + | length: u32, |
|
| 10 | + | } |
|
| 11 | + | ||
| 12 | + | record Node { |
|
| 13 | + | span: Span, |
|
| 14 | + | kind: NodeKind, |
|
| 15 | + | } |
|
| 16 | + | ||
| 17 | + | record Parser { |
|
| 18 | + | nodes: [Node; 1], |
|
| 19 | + | count: u32, |
|
| 20 | + | } |
|
| 21 | + | ||
| 22 | + | fn makeNode(p: *mut Parser, kind: NodeKind) -> *mut Node { |
|
| 23 | + | let idx: u32 = p.count; |
|
| 24 | + | p.nodes[idx] = Node { |
|
| 25 | + | span: Span { length: 0 }, |
|
| 26 | + | kind, |
|
| 27 | + | }; |
|
| 28 | + | p.count = idx + 1; |
|
| 29 | + | return &mut p.nodes[idx]; |
|
| 30 | + | } |
|
| 31 | + | ||
| 32 | + | fn setLen(n: *mut Node, len: u32) { |
|
| 33 | + | n.span.length = len; |
|
| 34 | + | } |
|
| 35 | + | ||
| 36 | + | @default fn main() -> i32 { |
|
| 37 | + | let mut parser: Parser = Parser { |
|
| 38 | + | nodes: [Node { |
|
| 39 | + | span: Span { length: 0 }, |
|
| 40 | + | kind: NodeKind::Placeholder, |
|
| 41 | + | }; 1], |
|
| 42 | + | count: 0, |
|
| 43 | + | }; |
|
| 44 | + | ||
| 45 | + | parser.count = 0; |
|
| 46 | + | parser.nodes[0] = Node { |
|
| 47 | + | span: Span { length: 0 }, |
|
| 48 | + | kind: NodeKind::Placeholder, |
|
| 49 | + | }; |
|
| 50 | + | ||
| 51 | + | let node: *mut Node = makeNode(&mut parser, NodeKind::Bool(true)); |
|
| 52 | + | setLen(node, 4); |
|
| 53 | + | ||
| 54 | + | match parser.nodes[0].kind { |
|
| 55 | + | case NodeKind::Bool(value) => { |
|
| 56 | + | if not value { |
|
| 57 | + | return 1; |
|
| 58 | + | } |
|
| 59 | + | } |
|
| 60 | + | case NodeKind::Placeholder => { |
|
| 61 | + | return 2; |
|
| 62 | + | } |
|
| 63 | + | } |
|
| 64 | + | ||
| 65 | + | match node.kind { |
|
| 66 | + | case NodeKind::Bool(value) => { |
|
| 67 | + | if value { |
|
| 68 | + | return 0; |
|
| 69 | + | } |
|
| 70 | + | return 4; |
|
| 71 | + | } |
|
| 72 | + | case NodeKind::Placeholder => { |
|
| 73 | + | return 5; |
|
| 74 | + | } |
|
| 75 | + | } |
|
| 76 | + | return 1; |
|
| 77 | + | } |
lib/std/arch/rv64/tests/pointer.slice.index.rad
added
+13 -0
| 1 | + | ||
| 2 | + | record Holder { |
|
| 3 | + | ptr: *[i32], |
|
| 4 | + | zero: u32, |
|
| 5 | + | } |
|
| 6 | + | ||
| 7 | + | // Test that pointer-to-slice indexing correctly dereferences the slice header. |
|
| 8 | + | @default fn main() -> i32 { |
|
| 9 | + | let arr: [i32; 2] = [5, 7]; |
|
| 10 | + | let h = Holder { ptr: &arr[..], zero: 0 }; |
|
| 11 | + | ||
| 12 | + | return (h.ptr[1]) - 7; |
|
| 13 | + | } |
lib/std/arch/rv64/tests/pointer.slice.store.rad
added
+48 -0
| 1 | + | //! Test storing through a pointer to a slice in static storage. |
|
| 2 | + | ||
| 3 | + | record Entry { |
|
| 4 | + | a: u32, |
|
| 5 | + | b: u32, |
|
| 6 | + | c: u32, |
|
| 7 | + | d: u32, |
|
| 8 | + | e: u32, |
|
| 9 | + | } |
|
| 10 | + | ||
| 11 | + | record Table { |
|
| 12 | + | entries: *mut [Entry], |
|
| 13 | + | len: u32, |
|
| 14 | + | } |
|
| 15 | + | ||
| 16 | + | record PtrBox { |
|
| 17 | + | ptr: *mut *mut [Entry], |
|
| 18 | + | } |
|
| 19 | + | ||
| 20 | + | static STORAGE: [Entry; 2] = undefined; |
|
| 21 | + | static TABLE: Table = undefined; |
|
| 22 | + | static HOLDER: PtrBox = undefined; |
|
| 23 | + | ||
| 24 | + | @default fn main() -> i32 { |
|
| 25 | + | TABLE.entries = &mut STORAGE[..]; |
|
| 26 | + | TABLE.len = 0; |
|
| 27 | + | ||
| 28 | + | HOLDER.ptr = &mut TABLE.entries; |
|
| 29 | + | ||
| 30 | + | HOLDER.ptr[0] = Entry { a: 1, b: 2, c: 3, d: 4, e: 5 }; |
|
| 31 | + | ||
| 32 | + | if TABLE.entries.len != 2 or TABLE.entries.ptr != &STORAGE[0] { |
|
| 33 | + | return 1; |
|
| 34 | + | } |
|
| 35 | + | if STORAGE[0].a != 1 or STORAGE[0].e != 5 { |
|
| 36 | + | return 2; |
|
| 37 | + | } |
|
| 38 | + | ||
| 39 | + | HOLDER.ptr[1] = Entry { a: 10, b: 20, c: 30, d: 40, e: 50 }; |
|
| 40 | + | ||
| 41 | + | if TABLE.entries.len != 2 or TABLE.entries.ptr != &STORAGE[0] { |
|
| 42 | + | return 3; |
|
| 43 | + | } |
|
| 44 | + | if STORAGE[1].c != 30 or STORAGE[1].d != 40 { |
|
| 45 | + | return 4; |
|
| 46 | + | } |
|
| 47 | + | return 0; |
|
| 48 | + | } |
lib/std/arch/rv64/tests/prog.ackermann.rad
added
+264 -0
| 1 | + | //! Ackermann function. |
|
| 2 | + | //! The Ackermann function is a classic benchmark for deep recursion and |
|
| 3 | + | //! stack usage. It grows extremely fast. We compute small values and |
|
| 4 | + | //! verify against known results. Also implements a memoized iterative |
|
| 5 | + | //! version and cross-checks the two. |
|
| 6 | + | ||
| 7 | + | /// Maximum memo table dimensions. |
|
| 8 | + | /// We memoize for m <= 3 and n <= MAX_N. |
|
| 9 | + | const MAX_M: u32 = 4; |
|
| 10 | + | const MAX_N: u32 = 128; |
|
| 11 | + | ||
| 12 | + | /// Classic recursive Ackermann function. |
|
| 13 | + | /// Only call with small arguments to avoid stack overflow. |
|
| 14 | + | fn ack(m: u32, n: u32) -> i32 { |
|
| 15 | + | if m == 0 { |
|
| 16 | + | return n as i32 + 1; |
|
| 17 | + | } |
|
| 18 | + | if n == 0 { |
|
| 19 | + | return ack(m - 1, 1); |
|
| 20 | + | } |
|
| 21 | + | return ack(m - 1, ack(m, n - 1) as u32); |
|
| 22 | + | } |
|
| 23 | + | ||
| 24 | + | /// Memoized Ackermann using the closed-form for m <= 3, recursive for m > 3. |
|
| 25 | + | /// For m=0: A(0,n) = n+1 |
|
| 26 | + | /// For m=1: A(1,n) = n+2 |
|
| 27 | + | /// For m=2: A(2,n) = 2n+3 |
|
| 28 | + | /// For m=3: A(3,n) = 2^(n+3) - 3 |
|
| 29 | + | fn ackMemo(m: u32, n: u32) -> i32 { |
|
| 30 | + | if m == 0 { |
|
| 31 | + | return n as i32 + 1; |
|
| 32 | + | } |
|
| 33 | + | if m == 1 { |
|
| 34 | + | return n as i32 + 2; |
|
| 35 | + | } |
|
| 36 | + | if m == 2 { |
|
| 37 | + | return (2 * n + 3) as i32; |
|
| 38 | + | } |
|
| 39 | + | if m == 3 { |
|
| 40 | + | // 2^(n+3) - 3 |
|
| 41 | + | let mut power: i32 = 1; |
|
| 42 | + | let mut i: u32 = 0; |
|
| 43 | + | while i < n + 3 { |
|
| 44 | + | power = power * 2; |
|
| 45 | + | i = i + 1; |
|
| 46 | + | } |
|
| 47 | + | return power - 3; |
|
| 48 | + | } |
|
| 49 | + | // For m >= 4, fall back to recursion (only safe for very small n). |
|
| 50 | + | if n == 0 { |
|
| 51 | + | return ackMemo(m - 1, 1); |
|
| 52 | + | } |
|
| 53 | + | return ackMemo(m - 1, ackMemo(m, n - 1) as u32); |
|
| 54 | + | } |
|
| 55 | + | ||
| 56 | + | /// Power of 2 helper. |
|
| 57 | + | fn pow2(exp: u32) -> i32 { |
|
| 58 | + | let mut result: i32 = 1; |
|
| 59 | + | let mut i: u32 = 0; |
|
| 60 | + | while i < exp { |
|
| 61 | + | result = result * 2; |
|
| 62 | + | i = i + 1; |
|
| 63 | + | } |
|
| 64 | + | return result; |
|
| 65 | + | } |
|
| 66 | + | ||
| 67 | + | /// Test known small values using the recursive implementation. |
|
| 68 | + | fn testSmallRecursive() -> i32 { |
|
| 69 | + | // A(0, 0) = 1 |
|
| 70 | + | if ack(0, 0) != 1 { |
|
| 71 | + | return 1; |
|
| 72 | + | } |
|
| 73 | + | // A(0, 5) = 6 |
|
| 74 | + | if ack(0, 5) != 6 { |
|
| 75 | + | return 2; |
|
| 76 | + | } |
|
| 77 | + | // A(1, 0) = 2 |
|
| 78 | + | if ack(1, 0) != 2 { |
|
| 79 | + | return 3; |
|
| 80 | + | } |
|
| 81 | + | // A(1, 5) = 7 |
|
| 82 | + | if ack(1, 5) != 7 { |
|
| 83 | + | return 4; |
|
| 84 | + | } |
|
| 85 | + | // A(2, 0) = 3 |
|
| 86 | + | if ack(2, 0) != 3 { |
|
| 87 | + | return 5; |
|
| 88 | + | } |
|
| 89 | + | // A(2, 3) = 9 |
|
| 90 | + | if ack(2, 3) != 9 { |
|
| 91 | + | return 6; |
|
| 92 | + | } |
|
| 93 | + | // A(2, 4) = 11 |
|
| 94 | + | if ack(2, 4) != 11 { |
|
| 95 | + | return 7; |
|
| 96 | + | } |
|
| 97 | + | // A(3, 0) = 5 |
|
| 98 | + | if ack(3, 0) != 5 { |
|
| 99 | + | return 8; |
|
| 100 | + | } |
|
| 101 | + | // A(3, 1) = 13 |
|
| 102 | + | if ack(3, 1) != 13 { |
|
| 103 | + | return 9; |
|
| 104 | + | } |
|
| 105 | + | // A(3, 2) = 29 |
|
| 106 | + | if ack(3, 2) != 29 { |
|
| 107 | + | return 10; |
|
| 108 | + | } |
|
| 109 | + | // A(3, 3) = 61 |
|
| 110 | + | if ack(3, 3) != 61 { |
|
| 111 | + | return 11; |
|
| 112 | + | } |
|
| 113 | + | // A(3, 4) = 125 |
|
| 114 | + | if ack(3, 4) != 125 { |
|
| 115 | + | return 12; |
|
| 116 | + | } |
|
| 117 | + | return 0; |
|
| 118 | + | } |
|
| 119 | + | ||
| 120 | + | /// Test the memoized version against known values. |
|
| 121 | + | fn testMemoized() -> i32 { |
|
| 122 | + | // Verify m=0 row. |
|
| 123 | + | let mut n: u32 = 0; |
|
| 124 | + | while n < 50 { |
|
| 125 | + | let expected: i32 = n as i32 + 1; |
|
| 126 | + | if ackMemo(0, n) != expected { |
|
| 127 | + | return 1; |
|
| 128 | + | } |
|
| 129 | + | n = n + 1; |
|
| 130 | + | } |
|
| 131 | + | ||
| 132 | + | // Verify m=1 row. |
|
| 133 | + | n = 0; |
|
| 134 | + | while n < 50 { |
|
| 135 | + | let expected: i32 = n as i32 + 2; |
|
| 136 | + | if ackMemo(1, n) != expected { |
|
| 137 | + | return 2; |
|
| 138 | + | } |
|
| 139 | + | n = n + 1; |
|
| 140 | + | } |
|
| 141 | + | ||
| 142 | + | // Verify m=2 row. |
|
| 143 | + | n = 0; |
|
| 144 | + | while n < 50 { |
|
| 145 | + | let expected: i32 = (2 * n + 3) as i32; |
|
| 146 | + | if ackMemo(2, n) != expected { |
|
| 147 | + | return 3; |
|
| 148 | + | } |
|
| 149 | + | n = n + 1; |
|
| 150 | + | } |
|
| 151 | + | ||
| 152 | + | // Verify m=3 row for small n. |
|
| 153 | + | // A(3,0)=5, A(3,1)=13, A(3,2)=29, A(3,3)=61, A(3,4)=125 |
|
| 154 | + | if ackMemo(3, 0) != 5 { |
|
| 155 | + | return 4; |
|
| 156 | + | } |
|
| 157 | + | if ackMemo(3, 1) != 13 { |
|
| 158 | + | return 5; |
|
| 159 | + | } |
|
| 160 | + | if ackMemo(3, 2) != 29 { |
|
| 161 | + | return 6; |
|
| 162 | + | } |
|
| 163 | + | if ackMemo(3, 3) != 61 { |
|
| 164 | + | return 7; |
|
| 165 | + | } |
|
| 166 | + | if ackMemo(3, 4) != 125 { |
|
| 167 | + | return 8; |
|
| 168 | + | } |
|
| 169 | + | // A(3, 5) = 2^8 - 3 = 253 |
|
| 170 | + | if ackMemo(3, 5) != 253 { |
|
| 171 | + | return 9; |
|
| 172 | + | } |
|
| 173 | + | // A(3, 10) = 2^13 - 3 = 8189 |
|
| 174 | + | if ackMemo(3, 10) != 8189 { |
|
| 175 | + | return 10; |
|
| 176 | + | } |
|
| 177 | + | ||
| 178 | + | return 0; |
|
| 179 | + | } |
|
| 180 | + | ||
| 181 | + | /// Cross-check recursive and memoized for small values. |
|
| 182 | + | fn testCrossCheck() -> i32 { |
|
| 183 | + | let mut m: u32 = 0; |
|
| 184 | + | while m <= 3 { |
|
| 185 | + | let mut n: u32 = 0; |
|
| 186 | + | // Limit n to keep recursion tractable. |
|
| 187 | + | let mut limit: u32 = 4; |
|
| 188 | + | if m <= 2 { |
|
| 189 | + | limit = 8; |
|
| 190 | + | } |
|
| 191 | + | while n <= limit { |
|
| 192 | + | let r: i32 = ack(m, n); |
|
| 193 | + | let memoR: i32 = ackMemo(m, n); |
|
| 194 | + | if r != memoR { |
|
| 195 | + | return (m * 20 + n) as i32 + 1; |
|
| 196 | + | } |
|
| 197 | + | n = n + 1; |
|
| 198 | + | } |
|
| 199 | + | m = m + 1; |
|
| 200 | + | } |
|
| 201 | + | return 0; |
|
| 202 | + | } |
|
| 203 | + | ||
| 204 | + | /// Test the Ackermann inverse property: A(m, n) > n for all m, n. |
|
| 205 | + | fn testMonotonicity() -> i32 { |
|
| 206 | + | let mut m: u32 = 0; |
|
| 207 | + | while m <= 3 { |
|
| 208 | + | let mut n: u32 = 0; |
|
| 209 | + | while n < 20 { |
|
| 210 | + | let val: i32 = ackMemo(m, n); |
|
| 211 | + | if val <= n as i32 { |
|
| 212 | + | return 1; |
|
| 213 | + | } |
|
| 214 | + | // A(m, n) < A(m, n+1) (strictly increasing in n). |
|
| 215 | + | let valNext: i32 = ackMemo(m, n + 1); |
|
| 216 | + | if valNext <= val { |
|
| 217 | + | return 2; |
|
| 218 | + | } |
|
| 219 | + | n = n + 1; |
|
| 220 | + | } |
|
| 221 | + | m = m + 1; |
|
| 222 | + | } |
|
| 223 | + | ||
| 224 | + | // A(m, n) < A(m+1, n) (strictly increasing in m). |
|
| 225 | + | let mut m2: u32 = 0; |
|
| 226 | + | while m2 < 3 { |
|
| 227 | + | let mut n2: u32 = 0; |
|
| 228 | + | while n2 < 10 { |
|
| 229 | + | let lower: i32 = ackMemo(m2, n2); |
|
| 230 | + | let upper: i32 = ackMemo(m2 + 1, n2); |
|
| 231 | + | if upper <= lower { |
|
| 232 | + | return 3; |
|
| 233 | + | } |
|
| 234 | + | n2 = n2 + 1; |
|
| 235 | + | } |
|
| 236 | + | m2 = m2 + 1; |
|
| 237 | + | } |
|
| 238 | + | ||
| 239 | + | return 0; |
|
| 240 | + | } |
|
| 241 | + | ||
| 242 | + | @default fn main() -> i32 { |
|
| 243 | + | let r1: i32 = testSmallRecursive(); |
|
| 244 | + | if r1 != 0 { |
|
| 245 | + | return 10 + r1; |
|
| 246 | + | } |
|
| 247 | + | ||
| 248 | + | let r2: i32 = testMemoized(); |
|
| 249 | + | if r2 != 0 { |
|
| 250 | + | return 30 + r2; |
|
| 251 | + | } |
|
| 252 | + | ||
| 253 | + | let r3: i32 = testCrossCheck(); |
|
| 254 | + | if r3 != 0 { |
|
| 255 | + | return 50 + r3; |
|
| 256 | + | } |
|
| 257 | + | ||
| 258 | + | let r4: i32 = testMonotonicity(); |
|
| 259 | + | if r4 != 0 { |
|
| 260 | + | return 70 + r4; |
|
| 261 | + | } |
|
| 262 | + | ||
| 263 | + | return 0; |
|
| 264 | + | } |
lib/std/arch/rv64/tests/prog.bignum.rad
added
+495 -0
| 1 | + | //! Big number arithmetic. |
|
| 2 | + | //! Implement multi-word unsigned integer arithmetic using arrays of u32 limbs |
|
| 3 | + | //! (little-endian). Operations: add, subtract, multiply, compare, shift. |
|
| 4 | + | //! Verify using known large-number identities and cross-checks. |
|
| 5 | + | ||
| 6 | + | /// Number of limbs per big number (128 bits = 4 x 32-bit words). |
|
| 7 | + | const LIMBS: u32 = 4; |
|
| 8 | + | ||
| 9 | + | /// Set a big number to a u32 value. |
|
| 10 | + | fn bnFromU32(dst: *mut [u32], val: u32) { |
|
| 11 | + | dst[0] = val; |
|
| 12 | + | let mut i: u32 = 1; |
|
| 13 | + | while i < LIMBS { |
|
| 14 | + | dst[i] = 0; |
|
| 15 | + | i = i + 1; |
|
| 16 | + | } |
|
| 17 | + | } |
|
| 18 | + | ||
| 19 | + | /// Set a big number to zero. |
|
| 20 | + | fn bnZero(dst: *mut [u32]) { |
|
| 21 | + | let mut i: u32 = 0; |
|
| 22 | + | while i < LIMBS { |
|
| 23 | + | dst[i] = 0; |
|
| 24 | + | i = i + 1; |
|
| 25 | + | } |
|
| 26 | + | } |
|
| 27 | + | ||
| 28 | + | /// Copy src to dst. |
|
| 29 | + | fn bnCopy(dst: *mut [u32], src: *[u32]) { |
|
| 30 | + | let mut i: u32 = 0; |
|
| 31 | + | while i < LIMBS { |
|
| 32 | + | dst[i] = src[i]; |
|
| 33 | + | i = i + 1; |
|
| 34 | + | } |
|
| 35 | + | } |
|
| 36 | + | ||
| 37 | + | /// Compare two big numbers. Returns 0 if equal, 1 if a > b, -1 if a < b. |
|
| 38 | + | fn bnCmp(a: *[u32], b: *[u32]) -> i32 { |
|
| 39 | + | let mut i: i32 = LIMBS as i32 - 1; |
|
| 40 | + | while i >= 0 { |
|
| 41 | + | if a[i as u32] > b[i as u32] { |
|
| 42 | + | return 1; |
|
| 43 | + | } |
|
| 44 | + | if a[i as u32] < b[i as u32] { |
|
| 45 | + | return -1; |
|
| 46 | + | } |
|
| 47 | + | if i == 0 { |
|
| 48 | + | break; |
|
| 49 | + | } |
|
| 50 | + | i = i - 1; |
|
| 51 | + | } |
|
| 52 | + | return 0; |
|
| 53 | + | } |
|
| 54 | + | ||
| 55 | + | /// Add two big numbers: dst = a + b. Returns carry (0 or 1). |
|
| 56 | + | fn bnAdd(dst: *mut [u32], a: *[u32], b: *[u32]) -> u32 { |
|
| 57 | + | let mut carry: u32 = 0; |
|
| 58 | + | let mut i: u32 = 0; |
|
| 59 | + | while i < LIMBS { |
|
| 60 | + | let sumLo: u32 = a[i] + b[i]; |
|
| 61 | + | let mut carry1: u32 = 0; |
|
| 62 | + | if sumLo < a[i] { |
|
| 63 | + | carry1 = 1; |
|
| 64 | + | } |
|
| 65 | + | let sum: u32 = sumLo + carry; |
|
| 66 | + | let mut carry2: u32 = 0; |
|
| 67 | + | if sum < sumLo { |
|
| 68 | + | carry2 = 1; |
|
| 69 | + | } |
|
| 70 | + | dst[i] = sum; |
|
| 71 | + | carry = carry1 + carry2; |
|
| 72 | + | i = i + 1; |
|
| 73 | + | } |
|
| 74 | + | return carry; |
|
| 75 | + | } |
|
| 76 | + | ||
| 77 | + | /// Subtract two big numbers: dst = a - b. Returns borrow (0 or 1). |
|
| 78 | + | fn bnSub(dst: *mut [u32], a: *[u32], b: *[u32]) -> u32 { |
|
| 79 | + | let mut borrow: u32 = 0; |
|
| 80 | + | let mut i: u32 = 0; |
|
| 81 | + | while i < LIMBS { |
|
| 82 | + | let diff: u32 = a[i] - b[i]; |
|
| 83 | + | let mut borrow1: u32 = 0; |
|
| 84 | + | if diff > a[i] { |
|
| 85 | + | borrow1 = 1; |
|
| 86 | + | } |
|
| 87 | + | let result: u32 = diff - borrow; |
|
| 88 | + | let mut borrow2: u32 = 0; |
|
| 89 | + | if result > diff { |
|
| 90 | + | borrow2 = 1; |
|
| 91 | + | } |
|
| 92 | + | dst[i] = result; |
|
| 93 | + | borrow = borrow1 + borrow2; |
|
| 94 | + | i = i + 1; |
|
| 95 | + | } |
|
| 96 | + | return borrow; |
|
| 97 | + | } |
|
| 98 | + | ||
| 99 | + | /// Multiply two LIMBS-word numbers, producing a 2*LIMBS-word result in wide. |
|
| 100 | + | fn bnMul(wide: *mut [u32], a: *[u32], b: *[u32]) { |
|
| 101 | + | let mut i: u32 = 0; |
|
| 102 | + | while i < LIMBS * 2 { |
|
| 103 | + | wide[i] = 0; |
|
| 104 | + | i = i + 1; |
|
| 105 | + | } |
|
| 106 | + | ||
| 107 | + | i = 0; |
|
| 108 | + | while i < LIMBS { |
|
| 109 | + | let mut carry: u32 = 0; |
|
| 110 | + | let mut j: u32 = 0; |
|
| 111 | + | while j < LIMBS { |
|
| 112 | + | let al: u32 = a[i] & 0xFFFF; |
|
| 113 | + | let ah: u32 = a[i] >> 16; |
|
| 114 | + | let bl: u32 = b[j] & 0xFFFF; |
|
| 115 | + | let bh: u32 = b[j] >> 16; |
|
| 116 | + | ||
| 117 | + | let ll: u32 = al * bl; |
|
| 118 | + | let lh: u32 = al * bh; |
|
| 119 | + | let hl: u32 = ah * bl; |
|
| 120 | + | let hh: u32 = ah * bh; |
|
| 121 | + | ||
| 122 | + | let mid: u32 = lh + hl; |
|
| 123 | + | let mut midCarry: u32 = 0; |
|
| 124 | + | if mid < lh { |
|
| 125 | + | midCarry = 1; |
|
| 126 | + | } |
|
| 127 | + | ||
| 128 | + | let lo: u32 = ll + (mid << 16); |
|
| 129 | + | let mut loCarry: u32 = 0; |
|
| 130 | + | if lo < ll { |
|
| 131 | + | loCarry = 1; |
|
| 132 | + | } |
|
| 133 | + | let hi: u32 = hh + (mid >> 16) + (midCarry << 16) + loCarry; |
|
| 134 | + | ||
| 135 | + | let sum1: u32 = wide[i + j] + lo; |
|
| 136 | + | let mut c1: u32 = 0; |
|
| 137 | + | if sum1 < wide[i + j] { |
|
| 138 | + | c1 = 1; |
|
| 139 | + | } |
|
| 140 | + | let sum2: u32 = sum1 + carry; |
|
| 141 | + | let mut c2: u32 = 0; |
|
| 142 | + | if sum2 < sum1 { |
|
| 143 | + | c2 = 1; |
|
| 144 | + | } |
|
| 145 | + | wide[i + j] = sum2; |
|
| 146 | + | carry = hi + c1 + c2; |
|
| 147 | + | ||
| 148 | + | j = j + 1; |
|
| 149 | + | } |
|
| 150 | + | wide[i + LIMBS] = wide[i + LIMBS] + carry; |
|
| 151 | + | i = i + 1; |
|
| 152 | + | } |
|
| 153 | + | } |
|
| 154 | + | ||
| 155 | + | /// Left shift a big number by 1 bit. |
|
| 156 | + | fn bnShl1(dst: *mut [u32], src: *[u32]) { |
|
| 157 | + | let mut carry: u32 = 0; |
|
| 158 | + | let mut i: u32 = 0; |
|
| 159 | + | while i < LIMBS { |
|
| 160 | + | let newCarry: u32 = src[i] >> 31; |
|
| 161 | + | dst[i] = (src[i] << 1) | carry; |
|
| 162 | + | carry = newCarry; |
|
| 163 | + | i = i + 1; |
|
| 164 | + | } |
|
| 165 | + | } |
|
| 166 | + | ||
| 167 | + | /// Right shift a big number by 1 bit. |
|
| 168 | + | fn bnShr1(dst: *mut [u32], src: *[u32]) { |
|
| 169 | + | let mut carry: u32 = 0; |
|
| 170 | + | let mut i: u32 = LIMBS; |
|
| 171 | + | while i > 0 { |
|
| 172 | + | i = i - 1; |
|
| 173 | + | let newCarry: u32 = src[i] & 1; |
|
| 174 | + | dst[i] = (src[i] >> 1) | (carry << 31); |
|
| 175 | + | carry = newCarry; |
|
| 176 | + | } |
|
| 177 | + | } |
|
| 178 | + | ||
| 179 | + | /// Test basic addition. |
|
| 180 | + | fn testAdd() -> i32 { |
|
| 181 | + | let mut a: [u32; 4] = [0; 4]; |
|
| 182 | + | let mut b: [u32; 4] = [0; 4]; |
|
| 183 | + | let mut c: [u32; 4] = [0; 4]; |
|
| 184 | + | let mut d: [u32; 4] = [0; 4]; |
|
| 185 | + | ||
| 186 | + | bnFromU32(&mut a[..], 0xFFFFFFFF); |
|
| 187 | + | bnFromU32(&mut b[..], 1); |
|
| 188 | + | let carry: u32 = bnAdd(&mut c[..], &a[..], &b[..]); |
|
| 189 | + | ||
| 190 | + | if carry != 0 { |
|
| 191 | + | return 1; |
|
| 192 | + | } |
|
| 193 | + | if c[0] != 0 { |
|
| 194 | + | return 2; |
|
| 195 | + | } |
|
| 196 | + | if c[1] != 1 { |
|
| 197 | + | return 3; |
|
| 198 | + | } |
|
| 199 | + | ||
| 200 | + | bnFromU32(&mut b[..], 0xFFFFFFFF); |
|
| 201 | + | let carry2: u32 = bnAdd(&mut d[..], &c[..], &b[..]); |
|
| 202 | + | if carry2 != 0 { |
|
| 203 | + | return 4; |
|
| 204 | + | } |
|
| 205 | + | if d[0] != 0xFFFFFFFF { |
|
| 206 | + | return 5; |
|
| 207 | + | } |
|
| 208 | + | if d[1] != 1 { |
|
| 209 | + | return 6; |
|
| 210 | + | } |
|
| 211 | + | ||
| 212 | + | return 0; |
|
| 213 | + | } |
|
| 214 | + | ||
| 215 | + | /// Test subtraction. |
|
| 216 | + | fn testSub() -> i32 { |
|
| 217 | + | let mut a: [u32; 4] = [0; 4]; |
|
| 218 | + | let mut b: [u32; 4] = [0; 4]; |
|
| 219 | + | let mut c: [u32; 4] = [0; 4]; |
|
| 220 | + | ||
| 221 | + | bnZero(&mut a[..]); |
|
| 222 | + | a[1] = 1; |
|
| 223 | + | bnFromU32(&mut b[..], 1); |
|
| 224 | + | let borrow: u32 = bnSub(&mut c[..], &a[..], &b[..]); |
|
| 225 | + | ||
| 226 | + | if borrow != 0 { |
|
| 227 | + | return 1; |
|
| 228 | + | } |
|
| 229 | + | if c[0] != 0xFFFFFFFF { |
|
| 230 | + | return 2; |
|
| 231 | + | } |
|
| 232 | + | if c[1] != 0 { |
|
| 233 | + | return 3; |
|
| 234 | + | } |
|
| 235 | + | ||
| 236 | + | return 0; |
|
| 237 | + | } |
|
| 238 | + | ||
| 239 | + | /// Test multiplication. |
|
| 240 | + | fn testMul() -> i32 { |
|
| 241 | + | let mut a: [u32; 4] = [0; 4]; |
|
| 242 | + | let mut b: [u32; 4] = [0; 4]; |
|
| 243 | + | let mut wide: [u32; 8] = [0; 8]; |
|
| 244 | + | ||
| 245 | + | bnFromU32(&mut a[..], 12345); |
|
| 246 | + | bnFromU32(&mut b[..], 67890); |
|
| 247 | + | bnMul(&mut wide[..], &a[..], &b[..]); |
|
| 248 | + | ||
| 249 | + | if wide[0] != 838102050 { |
|
| 250 | + | return 1; |
|
| 251 | + | } |
|
| 252 | + | if wide[1] != 0 { |
|
| 253 | + | return 2; |
|
| 254 | + | } |
|
| 255 | + | ||
| 256 | + | bnFromU32(&mut a[..], 0xFFFFFFFF); |
|
| 257 | + | bnFromU32(&mut b[..], 0xFFFFFFFF); |
|
| 258 | + | bnMul(&mut wide[..], &a[..], &b[..]); |
|
| 259 | + | ||
| 260 | + | if wide[0] != 0x00000001 { |
|
| 261 | + | return 3; |
|
| 262 | + | } |
|
| 263 | + | if wide[1] != 0xFFFFFFFE { |
|
| 264 | + | return 4; |
|
| 265 | + | } |
|
| 266 | + | ||
| 267 | + | return 0; |
|
| 268 | + | } |
|
| 269 | + | ||
| 270 | + | /// Test the identity a + b - b = a. |
|
| 271 | + | fn testAddSubIdentity() -> i32 { |
|
| 272 | + | let mut a: [u32; 4] = [0; 4]; |
|
| 273 | + | let mut b: [u32; 4] = [0; 4]; |
|
| 274 | + | let mut c: [u32; 4] = [0; 4]; |
|
| 275 | + | let mut d: [u32; 4] = [0; 4]; |
|
| 276 | + | ||
| 277 | + | a[0] = 0x12345678; |
|
| 278 | + | a[1] = 0xABCDEF01; |
|
| 279 | + | a[2] = 0x9876FEDC; |
|
| 280 | + | a[3] = 0x01020304; |
|
| 281 | + | ||
| 282 | + | b[0] = 0xFEDCBA98; |
|
| 283 | + | b[1] = 0x76543210; |
|
| 284 | + | b[2] = 0x11223344; |
|
| 285 | + | b[3] = 0x00AABB00; |
|
| 286 | + | ||
| 287 | + | bnAdd(&mut c[..], &a[..], &b[..]); |
|
| 288 | + | bnSub(&mut d[..], &c[..], &b[..]); |
|
| 289 | + | ||
| 290 | + | if bnCmp(&d[..], &a[..]) != 0 { |
|
| 291 | + | return 1; |
|
| 292 | + | } |
|
| 293 | + | ||
| 294 | + | bnSub(&mut d[..], &c[..], &a[..]); |
|
| 295 | + | if bnCmp(&d[..], &b[..]) != 0 { |
|
| 296 | + | return 2; |
|
| 297 | + | } |
|
| 298 | + | ||
| 299 | + | return 0; |
|
| 300 | + | } |
|
| 301 | + | ||
| 302 | + | /// Test shift operations. |
|
| 303 | + | fn testShift() -> i32 { |
|
| 304 | + | let mut a: [u32; 4] = [0; 4]; |
|
| 305 | + | let mut b: [u32; 4] = [0; 4]; |
|
| 306 | + | let mut c: [u32; 4] = [0; 4]; |
|
| 307 | + | ||
| 308 | + | bnFromU32(&mut a[..], 1); |
|
| 309 | + | bnShl1(&mut b[..], &a[..]); |
|
| 310 | + | if b[0] != 2 { |
|
| 311 | + | return 1; |
|
| 312 | + | } |
|
| 313 | + | ||
| 314 | + | bnFromU32(&mut a[..], 0x80000000); |
|
| 315 | + | bnShl1(&mut b[..], &a[..]); |
|
| 316 | + | if b[0] != 0 { |
|
| 317 | + | return 2; |
|
| 318 | + | } |
|
| 319 | + | if b[1] != 1 { |
|
| 320 | + | return 3; |
|
| 321 | + | } |
|
| 322 | + | ||
| 323 | + | bnShr1(&mut c[..], &b[..]); |
|
| 324 | + | if c[0] != 0x80000000 { |
|
| 325 | + | return 4; |
|
| 326 | + | } |
|
| 327 | + | if c[1] != 0 { |
|
| 328 | + | return 5; |
|
| 329 | + | } |
|
| 330 | + | ||
| 331 | + | return 0; |
|
| 332 | + | } |
|
| 333 | + | ||
| 334 | + | /// Test large multiply. |
|
| 335 | + | fn testLargeMultiply() -> i32 { |
|
| 336 | + | let mut a: [u32; 4] = [0; 4]; |
|
| 337 | + | let mut b: [u32; 4] = [0; 4]; |
|
| 338 | + | let mut wide: [u32; 8] = [0; 8]; |
|
| 339 | + | ||
| 340 | + | a[0] = 0xFFFFFFFF; |
|
| 341 | + | a[1] = 0xFFFFFFFF; |
|
| 342 | + | a[2] = 0; |
|
| 343 | + | a[3] = 0; |
|
| 344 | + | ||
| 345 | + | bnFromU32(&mut b[..], 1); |
|
| 346 | + | bnMul(&mut wide[..], &a[..], &b[..]); |
|
| 347 | + | if wide[0] != 0xFFFFFFFF { |
|
| 348 | + | return 1; |
|
| 349 | + | } |
|
| 350 | + | if wide[1] != 0xFFFFFFFF { |
|
| 351 | + | return 2; |
|
| 352 | + | } |
|
| 353 | + | if wide[2] != 0 { |
|
| 354 | + | return 3; |
|
| 355 | + | } |
|
| 356 | + | ||
| 357 | + | bnZero(&mut a[..]); |
|
| 358 | + | a[1] = 1; |
|
| 359 | + | bnZero(&mut b[..]); |
|
| 360 | + | b[1] = 1; |
|
| 361 | + | bnMul(&mut wide[..], &a[..], &b[..]); |
|
| 362 | + | if wide[0] != 0 { |
|
| 363 | + | return 4; |
|
| 364 | + | } |
|
| 365 | + | if wide[1] != 0 { |
|
| 366 | + | return 5; |
|
| 367 | + | } |
|
| 368 | + | if wide[2] != 1 { |
|
| 369 | + | return 6; |
|
| 370 | + | } |
|
| 371 | + | if wide[3] != 0 { |
|
| 372 | + | return 7; |
|
| 373 | + | } |
|
| 374 | + | ||
| 375 | + | return 0; |
|
| 376 | + | } |
|
| 377 | + | ||
| 378 | + | /// Test Fibonacci sequence with big numbers. |
|
| 379 | + | fn testFibonacci() -> i32 { |
|
| 380 | + | let mut fibA: [u32; 4] = [0; 4]; |
|
| 381 | + | let mut fibB: [u32; 4] = [0; 4]; |
|
| 382 | + | let mut fibC: [u32; 4] = [0; 4]; |
|
| 383 | + | ||
| 384 | + | fibA[0] = 0; |
|
| 385 | + | fibB[0] = 1; |
|
| 386 | + | ||
| 387 | + | let mut i: u32 = 2; |
|
| 388 | + | while i <= 48 { |
|
| 389 | + | bnAdd(&mut fibC[..], &fibA[..], &fibB[..]); |
|
| 390 | + | bnCopy(&mut fibA[..], &fibB[..]); |
|
| 391 | + | bnCopy(&mut fibB[..], &fibC[..]); |
|
| 392 | + | i = i + 1; |
|
| 393 | + | } |
|
| 394 | + | ||
| 395 | + | if fibB[0] != 0x1E8D0A40 { |
|
| 396 | + | return 1; |
|
| 397 | + | } |
|
| 398 | + | if fibB[1] != 0x01 { |
|
| 399 | + | return 2; |
|
| 400 | + | } |
|
| 401 | + | ||
| 402 | + | let mut fa: [u32; 4] = [0; 4]; |
|
| 403 | + | let mut fb: [u32; 4] = [0; 4]; |
|
| 404 | + | let mut fc: [u32; 4] = [0; 4]; |
|
| 405 | + | fa[0] = 0; |
|
| 406 | + | fb[0] = 1; |
|
| 407 | + | i = 2; |
|
| 408 | + | while i <= 47 { |
|
| 409 | + | bnAdd(&mut fc[..], &fa[..], &fb[..]); |
|
| 410 | + | bnCopy(&mut fa[..], &fb[..]); |
|
| 411 | + | bnCopy(&mut fb[..], &fc[..]); |
|
| 412 | + | i = i + 1; |
|
| 413 | + | } |
|
| 414 | + | bnAdd(&mut fc[..], &fa[..], &fb[..]); |
|
| 415 | + | if bnCmp(&fc[..], &fibB[..]) != 0 { |
|
| 416 | + | return 3; |
|
| 417 | + | } |
|
| 418 | + | ||
| 419 | + | return 0; |
|
| 420 | + | } |
|
| 421 | + | ||
| 422 | + | /// Test compare function. |
|
| 423 | + | fn testCompare() -> i32 { |
|
| 424 | + | let mut a: [u32; 4] = [0; 4]; |
|
| 425 | + | let mut b: [u32; 4] = [0; 4]; |
|
| 426 | + | ||
| 427 | + | bnFromU32(&mut a[..], 100); |
|
| 428 | + | bnFromU32(&mut b[..], 200); |
|
| 429 | + | ||
| 430 | + | if bnCmp(&a[..], &b[..]) != -1 { |
|
| 431 | + | return 1; |
|
| 432 | + | } |
|
| 433 | + | if bnCmp(&b[..], &a[..]) != 1 { |
|
| 434 | + | return 2; |
|
| 435 | + | } |
|
| 436 | + | if bnCmp(&a[..], &a[..]) != 0 { |
|
| 437 | + | return 3; |
|
| 438 | + | } |
|
| 439 | + | ||
| 440 | + | bnZero(&mut a[..]); |
|
| 441 | + | bnZero(&mut b[..]); |
|
| 442 | + | a[3] = 1; |
|
| 443 | + | b[0] = 0xFFFFFFFF; |
|
| 444 | + | b[1] = 0xFFFFFFFF; |
|
| 445 | + | b[2] = 0xFFFFFFFF; |
|
| 446 | + | if bnCmp(&a[..], &b[..]) != 1 { |
|
| 447 | + | return 4; |
|
| 448 | + | } |
|
| 449 | + | ||
| 450 | + | return 0; |
|
| 451 | + | } |
|
| 452 | + | ||
| 453 | + | @default fn main() -> i32 { |
|
| 454 | + | let r1: i32 = testAdd(); |
|
| 455 | + | if r1 != 0 { |
|
| 456 | + | return 10 + r1; |
|
| 457 | + | } |
|
| 458 | + | ||
| 459 | + | let r2: i32 = testSub(); |
|
| 460 | + | if r2 != 0 { |
|
| 461 | + | return 20 + r2; |
|
| 462 | + | } |
|
| 463 | + | ||
| 464 | + | let r3: i32 = testMul(); |
|
| 465 | + | if r3 != 0 { |
|
| 466 | + | return 30 + r3; |
|
| 467 | + | } |
|
| 468 | + | ||
| 469 | + | let r4: i32 = testAddSubIdentity(); |
|
| 470 | + | if r4 != 0 { |
|
| 471 | + | return 40 + r4; |
|
| 472 | + | } |
|
| 473 | + | ||
| 474 | + | let r5: i32 = testShift(); |
|
| 475 | + | if r5 != 0 { |
|
| 476 | + | return 50 + r5; |
|
| 477 | + | } |
|
| 478 | + | ||
| 479 | + | let r6: i32 = testLargeMultiply(); |
|
| 480 | + | if r6 != 0 { |
|
| 481 | + | return 60 + r6; |
|
| 482 | + | } |
|
| 483 | + | ||
| 484 | + | let r7: i32 = testFibonacci(); |
|
| 485 | + | if r7 != 0 { |
|
| 486 | + | return 70 + r7; |
|
| 487 | + | } |
|
| 488 | + | ||
| 489 | + | let r8: i32 = testCompare(); |
|
| 490 | + | if r8 != 0 { |
|
| 491 | + | return 80 + r8; |
|
| 492 | + | } |
|
| 493 | + | ||
| 494 | + | return 0; |
|
| 495 | + | } |
lib/std/arch/rv64/tests/prog.binsearch.rad
added
+128 -0
| 1 | + | //! Binary search. |
|
| 2 | + | //! Search a pre-sorted array for present and absent values. |
|
| 3 | + | ||
| 4 | + | /// Binary search for `target` in `data`. Returns the index if found, or nil. |
|
| 5 | + | fn binarySearch(data: *[i32], target: i32) -> ?u32 { |
|
| 6 | + | let mut lo: i32 = 0; |
|
| 7 | + | let mut hi: i32 = data.len as i32 - 1; |
|
| 8 | + | ||
| 9 | + | while lo <= hi { |
|
| 10 | + | let mid: i32 = lo + (hi - lo) / 2; |
|
| 11 | + | let val: i32 = data[mid as u32]; |
|
| 12 | + | ||
| 13 | + | if val == target { |
|
| 14 | + | return mid as u32; |
|
| 15 | + | } else if val < target { |
|
| 16 | + | lo = mid + 1; |
|
| 17 | + | } else { |
|
| 18 | + | hi = mid - 1; |
|
| 19 | + | } |
|
| 20 | + | } |
|
| 21 | + | return nil; |
|
| 22 | + | } |
|
| 23 | + | ||
| 24 | + | /// Test searching for elements that exist. |
|
| 25 | + | fn testPresent(data: *[i32]) -> i32 { |
|
| 26 | + | // First element. |
|
| 27 | + | let idx0 = binarySearch(data, 3) else { |
|
| 28 | + | return 1; |
|
| 29 | + | }; |
|
| 30 | + | if idx0 != 0 { |
|
| 31 | + | return 1; |
|
| 32 | + | } |
|
| 33 | + | ||
| 34 | + | // Last element. |
|
| 35 | + | let idx19 = binarySearch(data, 120) else { |
|
| 36 | + | return 2; |
|
| 37 | + | }; |
|
| 38 | + | if idx19 != 19 { |
|
| 39 | + | return 2; |
|
| 40 | + | } |
|
| 41 | + | ||
| 42 | + | // Middle element. |
|
| 43 | + | let idx9 = binarySearch(data, 58) else { |
|
| 44 | + | return 3; |
|
| 45 | + | }; |
|
| 46 | + | if idx9 != 9 { |
|
| 47 | + | return 3; |
|
| 48 | + | } |
|
| 49 | + | ||
| 50 | + | // Another element. |
|
| 51 | + | if let idx = binarySearch(data, 22) { |
|
| 52 | + | if idx != 4 { |
|
| 53 | + | return 4; |
|
| 54 | + | } |
|
| 55 | + | } else { |
|
| 56 | + | return 4; |
|
| 57 | + | } |
|
| 58 | + | ||
| 59 | + | // Element near end. |
|
| 60 | + | let idx16 = binarySearch(data, 100) else { |
|
| 61 | + | return 5; |
|
| 62 | + | }; |
|
| 63 | + | if idx16 != 16 { |
|
| 64 | + | return 5; |
|
| 65 | + | } |
|
| 66 | + | return 0; |
|
| 67 | + | } |
|
| 68 | + | ||
| 69 | + | /// Test searching for elements that do not exist. |
|
| 70 | + | fn testAbsent(data: *[i32]) -> i32 { |
|
| 71 | + | // Below range. |
|
| 72 | + | if binarySearch(data, 1) != nil { |
|
| 73 | + | return 1; |
|
| 74 | + | } |
|
| 75 | + | // Above range. |
|
| 76 | + | if binarySearch(data, 200) != nil { |
|
| 77 | + | return 2; |
|
| 78 | + | } |
|
| 79 | + | // Between existing elements. |
|
| 80 | + | if binarySearch(data, 40) != nil { |
|
| 81 | + | return 3; |
|
| 82 | + | } |
|
| 83 | + | // Just above an existing element. |
|
| 84 | + | if binarySearch(data, 8) != nil { |
|
| 85 | + | return 4; |
|
| 86 | + | } |
|
| 87 | + | // Just below an existing element. |
|
| 88 | + | if binarySearch(data, 99) != nil { |
|
| 89 | + | return 5; |
|
| 90 | + | } |
|
| 91 | + | return 0; |
|
| 92 | + | } |
|
| 93 | + | ||
| 94 | + | /// Test searching for every element in the array. |
|
| 95 | + | fn testAllPresent(data: *[i32]) -> i32 { |
|
| 96 | + | for value, index in data { |
|
| 97 | + | let found = binarySearch(data, value) else { |
|
| 98 | + | return index as i32 + 1; |
|
| 99 | + | }; |
|
| 100 | + | if found != index { |
|
| 101 | + | return index as i32 + 1; |
|
| 102 | + | } |
|
| 103 | + | } |
|
| 104 | + | return 0; |
|
| 105 | + | } |
|
| 106 | + | ||
| 107 | + | @default fn main() -> i32 { |
|
| 108 | + | let sorted: [i32; 20] = [ |
|
| 109 | + | 3, 7, 11, 15, 22, 30, 35, 41, 50, 58, |
|
| 110 | + | 63, 70, 77, 84, 90, 95, 100, 108, 115, 120 |
|
| 111 | + | ]; |
|
| 112 | + | ||
| 113 | + | let r1 = testPresent(&sorted[..]); |
|
| 114 | + | if r1 != 0 { |
|
| 115 | + | return 10 + r1; |
|
| 116 | + | } |
|
| 117 | + | ||
| 118 | + | let r2 = testAbsent(&sorted[..]); |
|
| 119 | + | if r2 != 0 { |
|
| 120 | + | return 20 + r2; |
|
| 121 | + | } |
|
| 122 | + | ||
| 123 | + | let r3 = testAllPresent(&sorted[..]); |
|
| 124 | + | if r3 != 0 { |
|
| 125 | + | return 30 + r3; |
|
| 126 | + | } |
|
| 127 | + | return 0; |
|
| 128 | + | } |
lib/std/arch/rv64/tests/prog.bubblesort.rad
added
+102 -0
| 1 | + | //! Bubble sort. |
|
| 2 | + | //! Sort an array of integers and verify the result. |
|
| 3 | + | ||
| 4 | + | /// Bubble sort the array in ascending order. |
|
| 5 | + | fn bubbleSort(data: *mut [i32]) { |
|
| 6 | + | let mut n: u32 = data.len; |
|
| 7 | + | while n > 1 { |
|
| 8 | + | let mut swapped: bool = false; |
|
| 9 | + | let mut i: u32 = 0; |
|
| 10 | + | while i < n - 1 { |
|
| 11 | + | if data[i] > data[i + 1] { |
|
| 12 | + | let tmp: i32 = data[i]; |
|
| 13 | + | data[i] = data[i + 1]; |
|
| 14 | + | data[i + 1] = tmp; |
|
| 15 | + | swapped = true; |
|
| 16 | + | } |
|
| 17 | + | i = i + 1; |
|
| 18 | + | } |
|
| 19 | + | if not swapped { |
|
| 20 | + | // Already sorted, early exit. |
|
| 21 | + | return; |
|
| 22 | + | } |
|
| 23 | + | n = n - 1; |
|
| 24 | + | } |
|
| 25 | + | } |
|
| 26 | + | ||
| 27 | + | /// Verify the array is sorted in ascending order. |
|
| 28 | + | fn isSorted(data: *[i32]) -> bool { |
|
| 29 | + | let mut prev: ?i32 = nil; |
|
| 30 | + | for val in data { |
|
| 31 | + | if let p = prev { |
|
| 32 | + | if p > val { |
|
| 33 | + | return false; |
|
| 34 | + | } |
|
| 35 | + | } |
|
| 36 | + | prev = val; |
|
| 37 | + | } |
|
| 38 | + | return true; |
|
| 39 | + | } |
|
| 40 | + | ||
| 41 | + | /// Compute the sum of all elements. |
|
| 42 | + | fn sum(data: *[i32]) -> i32 { |
|
| 43 | + | let mut total: i32 = 0; |
|
| 44 | + | for val in data { |
|
| 45 | + | total = total + val; |
|
| 46 | + | } |
|
| 47 | + | return total; |
|
| 48 | + | } |
|
| 49 | + | ||
| 50 | + | /// Verify specific positions in the sorted output. |
|
| 51 | + | fn verifyPositions(data: *[i32]) -> i32 { |
|
| 52 | + | // Sorted: 3 5 7 12 17 19 28 31 42 50 55 66 71 80 88 93 |
|
| 53 | + | if data[0] != 3 { |
|
| 54 | + | return 1; |
|
| 55 | + | } |
|
| 56 | + | if data[1] != 5 { |
|
| 57 | + | return 2; |
|
| 58 | + | } |
|
| 59 | + | if data[2] != 7 { |
|
| 60 | + | return 3; |
|
| 61 | + | } |
|
| 62 | + | if data[7] != 31 { |
|
| 63 | + | return 4; |
|
| 64 | + | } |
|
| 65 | + | if data[14] != 88 { |
|
| 66 | + | return 5; |
|
| 67 | + | } |
|
| 68 | + | if data[15] != 93 { |
|
| 69 | + | return 6; |
|
| 70 | + | } |
|
| 71 | + | return 0; |
|
| 72 | + | } |
|
| 73 | + | ||
| 74 | + | @default fn main() -> i32 { |
|
| 75 | + | let mut data: [i32; 16] = [ |
|
| 76 | + | 42, 17, 93, 5, 28, 71, 3, 66, |
|
| 77 | + | 50, 12, 88, 31, 7, 55, 19, 80 |
|
| 78 | + | ]; |
|
| 79 | + | ||
| 80 | + | // Compute sum before sorting. |
|
| 81 | + | let sumBefore = sum(&data[..]); |
|
| 82 | + | ||
| 83 | + | bubbleSort(&mut data[..]); |
|
| 84 | + | ||
| 85 | + | // Check the array is sorted. |
|
| 86 | + | if not isSorted(&data[..]) { |
|
| 87 | + | return 1; |
|
| 88 | + | } |
|
| 89 | + | ||
| 90 | + | // Check sum is preserved (no data corruption). |
|
| 91 | + | let sumAfter = sum(&data[..]); |
|
| 92 | + | if sumBefore != sumAfter { |
|
| 93 | + | return 2; |
|
| 94 | + | } |
|
| 95 | + | ||
| 96 | + | // Check specific element positions. |
|
| 97 | + | let r = verifyPositions(&data[..]); |
|
| 98 | + | if r != 0 { |
|
| 99 | + | return 10 + r; |
|
| 100 | + | } |
|
| 101 | + | return 0; |
|
| 102 | + | } |
lib/std/arch/rv64/tests/prog.cordic.rad
added
+290 -0
| 1 | + | //! CORDIC fixed-point trigonometry. |
|
| 2 | + | //! Implement sine and cosine using the CORDIC algorithm with 16-bit |
|
| 3 | + | //! fixed-point arithmetic (Q16.16 format). This is a classic embedded |
|
| 4 | + | //! systems / DSP benchmark that stress-tests shift, multiply, and |
|
| 5 | + | //! table-driven computation without floating point. |
|
| 6 | + | ||
| 7 | + | /// Fixed-point scale factor: 1.0 = 65536 (Q16.16). |
|
| 8 | + | const SCALE: i32 = 65536; |
|
| 9 | + | ||
| 10 | + | /// Number of CORDIC iterations (more = more precision). |
|
| 11 | + | const ITERATIONS: u32 = 16; |
|
| 12 | + | ||
| 13 | + | /// CORDIC gain factor in Q16.16. After 16 iterations, the gain is |
|
| 14 | + | /// approximately 1/K = 1/1.6468 = 0.60725 * 65536 = 39797. |
|
| 15 | + | const CORDIC_GAIN: i32 = 39797; |
|
| 16 | + | ||
| 17 | + | /// Pi in Q16.16: 3.14159 * 65536 = 205887 |
|
| 18 | + | const PI: i32 = 205887; |
|
| 19 | + | ||
| 20 | + | /// Pi/2 in Q16.16. |
|
| 21 | + | const HALF_PI: i32 = 102944; |
|
| 22 | + | ||
| 23 | + | /// Result record for cos and sin. |
|
| 24 | + | record CosSin { |
|
| 25 | + | cos: i32, |
|
| 26 | + | sin: i32, |
|
| 27 | + | } |
|
| 28 | + | ||
| 29 | + | /// CORDIC rotation mode: compute cos(angle) and sin(angle). |
|
| 30 | + | /// Input angle in Q16.16 radians, must be in [-pi/2, pi/2]. |
|
| 31 | + | fn cordicRotate(angle: i32, atanTable: *[i32]) -> CosSin { |
|
| 32 | + | let mut x: i32 = CORDIC_GAIN; |
|
| 33 | + | let mut y: i32 = 0; |
|
| 34 | + | let mut z: i32 = angle; |
|
| 35 | + | ||
| 36 | + | let mut i: u32 = 0; |
|
| 37 | + | while i < ITERATIONS { |
|
| 38 | + | let dx: i32 = x >> i as i32; |
|
| 39 | + | let dy: i32 = y >> i as i32; |
|
| 40 | + | ||
| 41 | + | if z >= 0 { |
|
| 42 | + | x = x - dy; |
|
| 43 | + | y = y + dx; |
|
| 44 | + | z = z - atanTable[i]; |
|
| 45 | + | } else { |
|
| 46 | + | x = x + dy; |
|
| 47 | + | y = y - dx; |
|
| 48 | + | z = z + atanTable[i]; |
|
| 49 | + | } |
|
| 50 | + | i = i + 1; |
|
| 51 | + | } |
|
| 52 | + | ||
| 53 | + | return CosSin { cos: x, sin: y }; |
|
| 54 | + | } |
|
| 55 | + | ||
| 56 | + | /// Compute cos and sin for any angle by reducing to [-pi/2, pi/2]. |
|
| 57 | + | fn cosSin(angle: i32, atanTable: *[i32]) -> CosSin { |
|
| 58 | + | let mut a: i32 = angle; |
|
| 59 | + | ||
| 60 | + | // Reduce to [-pi, pi]. |
|
| 61 | + | while a > PI { |
|
| 62 | + | a = a - 2 * PI; |
|
| 63 | + | } |
|
| 64 | + | while a < 0 - PI { |
|
| 65 | + | a = a + 2 * PI; |
|
| 66 | + | } |
|
| 67 | + | ||
| 68 | + | // If in [pi/2, pi], use cos(a) = -cos(pi-a), sin(a) = sin(pi-a). |
|
| 69 | + | if a > HALF_PI { |
|
| 70 | + | let r: CosSin = cordicRotate(PI - a, atanTable); |
|
| 71 | + | return CosSin { cos: 0 - r.cos, sin: r.sin }; |
|
| 72 | + | } else if a < 0 - HALF_PI { |
|
| 73 | + | // If in [-pi, -pi/2], use cos(a) = -cos(-pi-a), sin(a) = sin(-pi-a) |
|
| 74 | + | let r: CosSin = cordicRotate(0 - PI - a, atanTable); |
|
| 75 | + | return CosSin { cos: 0 - r.cos, sin: r.sin }; |
|
| 76 | + | } else { |
|
| 77 | + | return cordicRotate(a, atanTable); |
|
| 78 | + | } |
|
| 79 | + | } |
|
| 80 | + | ||
| 81 | + | /// Absolute value. |
|
| 82 | + | fn abs(x: i32) -> i32 { |
|
| 83 | + | if x < 0 { |
|
| 84 | + | return 0 - x; |
|
| 85 | + | } |
|
| 86 | + | return x; |
|
| 87 | + | } |
|
| 88 | + | ||
| 89 | + | /// Fixed-point multiply: (a * b) >> 16, using half-word decomposition |
|
| 90 | + | /// to avoid 32-bit overflow. |
|
| 91 | + | fn fpmul(a: i32, b: i32) -> i32 { |
|
| 92 | + | // Determine sign and work with magnitudes. |
|
| 93 | + | let neg: bool = (a < 0) != (b < 0); |
|
| 94 | + | let mut ua: u32 = a as u32; |
|
| 95 | + | if a < 0 { |
|
| 96 | + | ua = (0 - a) as u32; |
|
| 97 | + | } |
|
| 98 | + | let mut ub: u32 = b as u32; |
|
| 99 | + | if b < 0 { |
|
| 100 | + | ub = (0 - b) as u32; |
|
| 101 | + | } |
|
| 102 | + | ||
| 103 | + | // Split into 16-bit halves: ua = ah:al, ub = bh:bl. |
|
| 104 | + | let al: u32 = ua & 0xFFFF; |
|
| 105 | + | let ah: u32 = ua >> 16; |
|
| 106 | + | let bl: u32 = ub & 0xFFFF; |
|
| 107 | + | let bh: u32 = ub >> 16; |
|
| 108 | + | ||
| 109 | + | // Product = ah*bh*2^32 + (ah*bl + al*bh)*2^16 + al*bl |
|
| 110 | + | // We need the result >> 16, so: |
|
| 111 | + | // (al*bl) >> 16 + (ah*bl + al*bh) + ah*bh*2^16 |
|
| 112 | + | let ll: u32 = al * bl; |
|
| 113 | + | let mid: u32 = ah * bl + al * bh; |
|
| 114 | + | let hh: u32 = ah * bh; |
|
| 115 | + | ||
| 116 | + | let result: u32 = (ll >> 16) + mid + (hh << 16); |
|
| 117 | + | if neg { |
|
| 118 | + | return 0 - result as i32; |
|
| 119 | + | } |
|
| 120 | + | return result as i32; |
|
| 121 | + | } |
|
| 122 | + | ||
| 123 | + | /// Test cos(0) = 1, sin(0) = 0. |
|
| 124 | + | fn testZero(atanTable: *[i32]) -> i32 { |
|
| 125 | + | let r: CosSin = cosSin(0, atanTable); |
|
| 126 | + | // cos(0) should be close to 65536 (1.0 in Q16.16). |
|
| 127 | + | let cosErr: i32 = abs(r.cos - SCALE); |
|
| 128 | + | let sinErr: i32 = abs(r.sin); |
|
| 129 | + | ||
| 130 | + | // Allow error of ~1% = 655. |
|
| 131 | + | if cosErr > 655 { |
|
| 132 | + | return 1; |
|
| 133 | + | } |
|
| 134 | + | if sinErr > 655 { |
|
| 135 | + | return 2; |
|
| 136 | + | } |
|
| 137 | + | return 0; |
|
| 138 | + | } |
|
| 139 | + | ||
| 140 | + | /// Test cos(pi/2) = 0, sin(pi/2) = 1. |
|
| 141 | + | fn testHalfPi(atanTable: *[i32]) -> i32 { |
|
| 142 | + | let r: CosSin = cosSin(HALF_PI, atanTable); |
|
| 143 | + | let cosErr: i32 = abs(r.cos); |
|
| 144 | + | let sinErr: i32 = abs(r.sin - SCALE); |
|
| 145 | + | ||
| 146 | + | if cosErr > 655 { |
|
| 147 | + | return 1; |
|
| 148 | + | } |
|
| 149 | + | if sinErr > 655 { |
|
| 150 | + | return 2; |
|
| 151 | + | } |
|
| 152 | + | return 0; |
|
| 153 | + | } |
|
| 154 | + | ||
| 155 | + | /// Test cos(pi) = -1, sin(pi) = 0. |
|
| 156 | + | fn testPi(atanTable: *[i32]) -> i32 { |
|
| 157 | + | let r: CosSin = cosSin(PI, atanTable); |
|
| 158 | + | let cosErr: i32 = abs(r.cos + SCALE); |
|
| 159 | + | let sinErr: i32 = abs(r.sin); |
|
| 160 | + | ||
| 161 | + | if cosErr > 655 { |
|
| 162 | + | return 1; |
|
| 163 | + | } |
|
| 164 | + | if sinErr > 655 { |
|
| 165 | + | return 2; |
|
| 166 | + | } |
|
| 167 | + | return 0; |
|
| 168 | + | } |
|
| 169 | + | ||
| 170 | + | /// Test Pythagorean identity: sin^2 + cos^2 = 1 for several angles. |
|
| 171 | + | fn testPythagorean(atanTable: *[i32]) -> i32 { |
|
| 172 | + | // Test at 16 evenly spaced angles from 0 to 2*pi. |
|
| 173 | + | let step: i32 = PI / 8; |
|
| 174 | + | let mut i: i32 = 0 - PI; |
|
| 175 | + | ||
| 176 | + | while i <= PI { |
|
| 177 | + | let r: CosSin = cosSin(i, atanTable); |
|
| 178 | + | // sin^2 + cos^2 in Q16.16. |
|
| 179 | + | let sin2: i32 = fpmul(r.sin, r.sin); |
|
| 180 | + | let cos2: i32 = fpmul(r.cos, r.cos); |
|
| 181 | + | let sum: i32 = sin2 + cos2; |
|
| 182 | + | let err: i32 = abs(sum - SCALE); |
|
| 183 | + | ||
| 184 | + | // Allow ~2% error due to fixed-point precision. |
|
| 185 | + | if err > 1311 { |
|
| 186 | + | return 1; |
|
| 187 | + | } |
|
| 188 | + | i = i + step; |
|
| 189 | + | } |
|
| 190 | + | return 0; |
|
| 191 | + | } |
|
| 192 | + | ||
| 193 | + | /// Test symmetry: sin(-x) = -sin(x), cos(-x) = cos(x). |
|
| 194 | + | fn testSymmetry(atanTable: *[i32]) -> i32 { |
|
| 195 | + | let angles: [i32; 5] = [16384, 32768, 51472, 65536, 81920]; |
|
| 196 | + | ||
| 197 | + | let mut i: u32 = 0; |
|
| 198 | + | while i < 5 { |
|
| 199 | + | let a: i32 = angles[i]; |
|
| 200 | + | ||
| 201 | + | let rPos: CosSin = cosSin(a, atanTable); |
|
| 202 | + | let rNeg: CosSin = cosSin(0 - a, atanTable); |
|
| 203 | + | ||
| 204 | + | // cos(-x) should equal cos(x). |
|
| 205 | + | if abs(rPos.cos - rNeg.cos) > 655 { |
|
| 206 | + | return 1; |
|
| 207 | + | } |
|
| 208 | + | // sin(-x) should equal -sin(x). |
|
| 209 | + | if abs(rPos.sin + rNeg.sin) > 655 { |
|
| 210 | + | return 2; |
|
| 211 | + | } |
|
| 212 | + | i = i + 1; |
|
| 213 | + | } |
|
| 214 | + | return 0; |
|
| 215 | + | } |
|
| 216 | + | ||
| 217 | + | /// Test specific known value: cos(pi/3) = 0.5, sin(pi/3) = 0.866. |
|
| 218 | + | fn testPiThird(atanTable: *[i32]) -> i32 { |
|
| 219 | + | // pi/3 in Q16.16 = 68629. |
|
| 220 | + | let piThird: i32 = 68629; |
|
| 221 | + | let r: CosSin = cosSin(piThird, atanTable); |
|
| 222 | + | ||
| 223 | + | // cos(pi/3) = 0.5 = 32768 in Q16.16. |
|
| 224 | + | let cosErr: i32 = abs(r.cos - 32768); |
|
| 225 | + | // sin(pi/3) = 0.866 = 56756 in Q16.16. |
|
| 226 | + | let sinErr: i32 = abs(r.sin - 56756); |
|
| 227 | + | ||
| 228 | + | // Allow ~2% error. |
|
| 229 | + | if cosErr > 1311 { |
|
| 230 | + | return 1; |
|
| 231 | + | } |
|
| 232 | + | if sinErr > 1311 { |
|
| 233 | + | return 2; |
|
| 234 | + | } |
|
| 235 | + | return 0; |
|
| 236 | + | } |
|
| 237 | + | ||
| 238 | + | @default fn main() -> i32 { |
|
| 239 | + | /// CORDIC arctangent table in Q16.16 fixed-point. |
|
| 240 | + | let atanTable: [i32; 16] = [ |
|
| 241 | + | 51472, // atan(2^0) |
|
| 242 | + | 30386, // atan(2^-1) |
|
| 243 | + | 16055, // atan(2^-2) |
|
| 244 | + | 8150, // atan(2^-3) |
|
| 245 | + | 4091, // atan(2^-4) |
|
| 246 | + | 2047, // atan(2^-5) |
|
| 247 | + | 1024, // atan(2^-6) |
|
| 248 | + | 512, // atan(2^-7) |
|
| 249 | + | 256, // atan(2^-8) |
|
| 250 | + | 128, // atan(2^-9) |
|
| 251 | + | 64, // atan(2^-10) |
|
| 252 | + | 32, // atan(2^-11) |
|
| 253 | + | 16, // atan(2^-12) |
|
| 254 | + | 8, // atan(2^-13) |
|
| 255 | + | 4, // atan(2^-14) |
|
| 256 | + | 2 // atan(2^-15) |
|
| 257 | + | ]; |
|
| 258 | + | ||
| 259 | + | let r1: i32 = testZero(&atanTable[..]); |
|
| 260 | + | if r1 != 0 { |
|
| 261 | + | return 10 + r1; |
|
| 262 | + | } |
|
| 263 | + | ||
| 264 | + | let r2: i32 = testHalfPi(&atanTable[..]); |
|
| 265 | + | if r2 != 0 { |
|
| 266 | + | return 20 + r2; |
|
| 267 | + | } |
|
| 268 | + | ||
| 269 | + | let r3: i32 = testPi(&atanTable[..]); |
|
| 270 | + | if r3 != 0 { |
|
| 271 | + | return 30 + r3; |
|
| 272 | + | } |
|
| 273 | + | ||
| 274 | + | let r4: i32 = testPythagorean(&atanTable[..]); |
|
| 275 | + | if r4 != 0 { |
|
| 276 | + | return 40 + r4; |
|
| 277 | + | } |
|
| 278 | + | ||
| 279 | + | let r5: i32 = testSymmetry(&atanTable[..]); |
|
| 280 | + | if r5 != 0 { |
|
| 281 | + | return 50 + r5; |
|
| 282 | + | } |
|
| 283 | + | ||
| 284 | + | let r6: i32 = testPiThird(&atanTable[..]); |
|
| 285 | + | if r6 != 0 { |
|
| 286 | + | return 60 + r6; |
|
| 287 | + | } |
|
| 288 | + | ||
| 289 | + | return 0; |
|
| 290 | + | } |
lib/std/arch/rv64/tests/prog.crc32.rad
added
+122 -0
| 1 | + | //! CRC-32. |
|
| 2 | + | //! Compute CRC-32 of a byte buffer using a 256-entry lookup table. |
|
| 3 | + | //! The table is built at startup. Verify against known checksums. |
|
| 4 | + | ||
| 5 | + | /// Build the CRC-32 lookup table using the standard polynomial 0xEDB88320. |
|
| 6 | + | fn buildTable(table: *mut [u32]) { |
|
| 7 | + | let mut i: u32 = 0; |
|
| 8 | + | while i < 256 { |
|
| 9 | + | let mut crc: u32 = i; |
|
| 10 | + | let mut j: u32 = 0; |
|
| 11 | + | while j < 8 { |
|
| 12 | + | if crc & 1 == 1 { |
|
| 13 | + | crc = (crc >> 1) ^ 0xEDB88320; |
|
| 14 | + | } else { |
|
| 15 | + | crc = crc >> 1; |
|
| 16 | + | } |
|
| 17 | + | j = j + 1; |
|
| 18 | + | } |
|
| 19 | + | table[i] = crc; |
|
| 20 | + | i = i + 1; |
|
| 21 | + | } |
|
| 22 | + | } |
|
| 23 | + | ||
| 24 | + | /// Compute CRC-32 of a byte slice. |
|
| 25 | + | fn crc32(table: *[u32], data: *[u8]) -> u32 { |
|
| 26 | + | let mut crc: u32 = 0xFFFFFFFF; |
|
| 27 | + | let mut i: u32 = 0; |
|
| 28 | + | while i < data.len { |
|
| 29 | + | let byte: u8 = data[i]; |
|
| 30 | + | let index: u32 = (crc ^ byte as u32) & 0xFF; |
|
| 31 | + | crc = (crc >> 8) ^ table[index]; |
|
| 32 | + | i = i + 1; |
|
| 33 | + | } |
|
| 34 | + | return crc ^ 0xFFFFFFFF; |
|
| 35 | + | } |
|
| 36 | + | ||
| 37 | + | /// Test the lookup table has been built correctly. |
|
| 38 | + | fn testTable(table: *[u32]) -> i32 { |
|
| 39 | + | // TABLE[0] should be 0 (zero input, all shifts produce zero). |
|
| 40 | + | if table[0] != 0 { |
|
| 41 | + | return 1; |
|
| 42 | + | } |
|
| 43 | + | // Known value: TABLE[1] = 0x77073096 |
|
| 44 | + | if table[1] != 0x77073096 { |
|
| 45 | + | return 2; |
|
| 46 | + | } |
|
| 47 | + | // TABLE[255] is a known value: 0x2D02EF8D |
|
| 48 | + | if table[255] != 0x2D02EF8D { |
|
| 49 | + | return 3; |
|
| 50 | + | } |
|
| 51 | + | return 0; |
|
| 52 | + | } |
|
| 53 | + | ||
| 54 | + | /// Test CRC-32 of known strings. |
|
| 55 | + | fn testKnownCRC(table: *[u32]) -> i32 { |
|
| 56 | + | // CRC-32 of empty data should be 0x00000000. |
|
| 57 | + | if crc32(table, &[]) != 0x00000000 { |
|
| 58 | + | return 1; |
|
| 59 | + | } |
|
| 60 | + | ||
| 61 | + | // CRC-32 of "123456789" = 0xCBF43926 (the standard check value). |
|
| 62 | + | let check: [u8; 9] = [0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39]; |
|
| 63 | + | if crc32(table, &check[..]) != 0xCBF43926 { |
|
| 64 | + | return 2; |
|
| 65 | + | } |
|
| 66 | + | ||
| 67 | + | // CRC-32 of a single byte 'A' (0x41) = 0xD3D99E8B. |
|
| 68 | + | let single: [u8; 1] = [0x41]; |
|
| 69 | + | if crc32(table, &single[..]) != 0xD3D99E8B { |
|
| 70 | + | return 3; |
|
| 71 | + | } |
|
| 72 | + | return 0; |
|
| 73 | + | } |
|
| 74 | + | ||
| 75 | + | /// Test CRC-32 of incrementally built data. |
|
| 76 | + | fn testIncremental(table: *[u32]) -> i32 { |
|
| 77 | + | // Build a 32-byte buffer with values 0..31. |
|
| 78 | + | let mut buf: [u8; 32] = [0; 32]; |
|
| 79 | + | let mut i: u32 = 0; |
|
| 80 | + | while i < 32 { |
|
| 81 | + | buf[i] = i as u8; |
|
| 82 | + | i = i + 1; |
|
| 83 | + | } |
|
| 84 | + | let crcFull = crc32(table, &buf[..]); |
|
| 85 | + | ||
| 86 | + | // CRC should be non-zero and deterministic. |
|
| 87 | + | if crcFull == 0 { |
|
| 88 | + | return 1; |
|
| 89 | + | } |
|
| 90 | + | ||
| 91 | + | // Recompute and verify it's the same. |
|
| 92 | + | if crc32(table, &buf[..]) != crcFull { |
|
| 93 | + | return 2; |
|
| 94 | + | } |
|
| 95 | + | ||
| 96 | + | // Known value for bytes 0..31: 0x91267E8A |
|
| 97 | + | if crcFull != 0x91267E8A { |
|
| 98 | + | return 3; |
|
| 99 | + | } |
|
| 100 | + | return 0; |
|
| 101 | + | } |
|
| 102 | + | ||
| 103 | + | @default fn main() -> i32 { |
|
| 104 | + | let mut table: [u32; 256] = [0; 256]; |
|
| 105 | + | buildTable(&mut table[..]); |
|
| 106 | + | ||
| 107 | + | let r1 = testTable(&table[..]); |
|
| 108 | + | if r1 != 0 { |
|
| 109 | + | return 10 + r1; |
|
| 110 | + | } |
|
| 111 | + | ||
| 112 | + | let r2 = testKnownCRC(&table[..]); |
|
| 113 | + | if r2 != 0 { |
|
| 114 | + | return 20 + r2; |
|
| 115 | + | } |
|
| 116 | + | ||
| 117 | + | let r3 = testIncremental(&table[..]); |
|
| 118 | + | if r3 != 0 { |
|
| 119 | + | return 30 + r3; |
|
| 120 | + | } |
|
| 121 | + | return 0; |
|
| 122 | + | } |
lib/std/arch/rv64/tests/prog.dijkstra.rad
added
+395 -0
| 1 | + | //! Dijkstra's shortest path algorithm. |
|
| 2 | + | //! Find shortest paths in a weighted directed graph using an adjacency matrix |
|
| 3 | + | //! and a min-heap priority queue. Reconstruct paths and verify distances. |
|
| 4 | + | ||
| 5 | + | ||
| 6 | + | const MAX_NODES: u32 = 16; |
|
| 7 | + | const INF: u32 = 0xFFFFFFFF; |
|
| 8 | + | ||
| 9 | + | record HeapEntry { |
|
| 10 | + | dist: u32, |
|
| 11 | + | node: u32, |
|
| 12 | + | } |
|
| 13 | + | ||
| 14 | + | record Graph { |
|
| 15 | + | adj: *mut [[u32; 16]], |
|
| 16 | + | dist: *mut [u32], |
|
| 17 | + | prev: *mut [i32], |
|
| 18 | + | visited: *mut [bool], |
|
| 19 | + | numNodes: u32, |
|
| 20 | + | heap: *mut [HeapEntry], |
|
| 21 | + | heapSize: u32, |
|
| 22 | + | } |
|
| 23 | + | ||
| 24 | + | fn heapSwap(g: *mut Graph, i: u32, j: u32) { |
|
| 25 | + | let tmp: HeapEntry = g.heap[i]; |
|
| 26 | + | g.heap[i] = g.heap[j]; |
|
| 27 | + | g.heap[j] = tmp; |
|
| 28 | + | } |
|
| 29 | + | ||
| 30 | + | fn siftUp(g: *mut Graph, pos: u32) { |
|
| 31 | + | let mut i: u32 = pos; |
|
| 32 | + | while i > 0 { |
|
| 33 | + | let parent: u32 = (i - 1) / 2; |
|
| 34 | + | if g.heap[i].dist < g.heap[parent].dist { |
|
| 35 | + | heapSwap(g, i, parent); |
|
| 36 | + | i = parent; |
|
| 37 | + | } else { |
|
| 38 | + | return; |
|
| 39 | + | } |
|
| 40 | + | } |
|
| 41 | + | } |
|
| 42 | + | ||
| 43 | + | fn siftDown(g: *mut Graph, pos: u32) { |
|
| 44 | + | let mut i: u32 = pos; |
|
| 45 | + | while true { |
|
| 46 | + | let left: u32 = 2 * i + 1; |
|
| 47 | + | let right: u32 = 2 * i + 2; |
|
| 48 | + | let mut smallest: u32 = i; |
|
| 49 | + | ||
| 50 | + | if left < g.heapSize and g.heap[left].dist < g.heap[smallest].dist { |
|
| 51 | + | smallest = left; |
|
| 52 | + | } |
|
| 53 | + | if right < g.heapSize and g.heap[right].dist < g.heap[smallest].dist { |
|
| 54 | + | smallest = right; |
|
| 55 | + | } |
|
| 56 | + | if smallest == i { |
|
| 57 | + | return; |
|
| 58 | + | } |
|
| 59 | + | heapSwap(g, i, smallest); |
|
| 60 | + | i = smallest; |
|
| 61 | + | } |
|
| 62 | + | } |
|
| 63 | + | ||
| 64 | + | fn heapPush(g: *mut Graph, dist: u32, node: u32) { |
|
| 65 | + | g.heap[g.heapSize] = HeapEntry { dist, node }; |
|
| 66 | + | g.heapSize = g.heapSize + 1; |
|
| 67 | + | siftUp(g, g.heapSize - 1); |
|
| 68 | + | } |
|
| 69 | + | ||
| 70 | + | /// Pop from the heap. Returns nil if the heap is empty. |
|
| 71 | + | fn heapPop(g: *mut Graph) -> ?HeapEntry { |
|
| 72 | + | if g.heapSize == 0 { |
|
| 73 | + | return nil; |
|
| 74 | + | } |
|
| 75 | + | let result: HeapEntry = g.heap[0]; |
|
| 76 | + | g.heapSize = g.heapSize - 1; |
|
| 77 | + | g.heap[0] = g.heap[g.heapSize]; |
|
| 78 | + | if g.heapSize > 0 { |
|
| 79 | + | siftDown(g, 0); |
|
| 80 | + | } |
|
| 81 | + | return result; |
|
| 82 | + | } |
|
| 83 | + | ||
| 84 | + | fn addEdge(g: *mut Graph, from: u32, to: u32, weight: u32) { |
|
| 85 | + | g.adj[from][to] = weight; |
|
| 86 | + | } |
|
| 87 | + | ||
| 88 | + | fn addBidiEdge(g: *mut Graph, from: u32, to: u32, weight: u32) { |
|
| 89 | + | g.adj[from][to] = weight; |
|
| 90 | + | g.adj[to][from] = weight; |
|
| 91 | + | } |
|
| 92 | + | ||
| 93 | + | fn resetGraph(g: *mut Graph, n: u32) { |
|
| 94 | + | g.numNodes = n; |
|
| 95 | + | let mut i: u32 = 0; |
|
| 96 | + | while i < MAX_NODES { |
|
| 97 | + | let mut j: u32 = 0; |
|
| 98 | + | while j < MAX_NODES { |
|
| 99 | + | g.adj[i][j] = INF; |
|
| 100 | + | j = j + 1; |
|
| 101 | + | } |
|
| 102 | + | g.dist[i] = INF; |
|
| 103 | + | g.prev[i] = -1; |
|
| 104 | + | g.visited[i] = false; |
|
| 105 | + | i = i + 1; |
|
| 106 | + | } |
|
| 107 | + | g.heapSize = 0; |
|
| 108 | + | } |
|
| 109 | + | ||
| 110 | + | /// Get the predecessor as an optional; -1 means no predecessor. |
|
| 111 | + | fn getPrev(g: *Graph, node: u32) -> ?u32 { |
|
| 112 | + | let p = g.prev[node]; |
|
| 113 | + | if p < 0 { |
|
| 114 | + | return nil; |
|
| 115 | + | } |
|
| 116 | + | return p as u32; |
|
| 117 | + | } |
|
| 118 | + | ||
| 119 | + | fn dijkstra(g: *mut Graph, source: u32) { |
|
| 120 | + | g.dist[source] = 0; |
|
| 121 | + | heapPush(g, 0, source); |
|
| 122 | + | ||
| 123 | + | while let entry = heapPop(g) { |
|
| 124 | + | let u: u32 = entry.node; |
|
| 125 | + | ||
| 126 | + | if g.visited[u] { |
|
| 127 | + | continue; |
|
| 128 | + | } |
|
| 129 | + | g.visited[u] = true; |
|
| 130 | + | ||
| 131 | + | let mut v: u32 = 0; |
|
| 132 | + | while v < g.numNodes { |
|
| 133 | + | if g.adj[u][v] != INF and not g.visited[v] { |
|
| 134 | + | let newDist: u32 = g.dist[u] + g.adj[u][v]; |
|
| 135 | + | if newDist < g.dist[v] { |
|
| 136 | + | g.dist[v] = newDist; |
|
| 137 | + | g.prev[v] = u as i32; |
|
| 138 | + | heapPush(g, newDist, v); |
|
| 139 | + | } |
|
| 140 | + | } |
|
| 141 | + | v = v + 1; |
|
| 142 | + | } |
|
| 143 | + | } |
|
| 144 | + | } |
|
| 145 | + | ||
| 146 | + | /// Reconstruct the shortest path from source to target. |
|
| 147 | + | fn reconstructPath(g: *Graph, target: u32, path: *mut [u32]) -> u32 { |
|
| 148 | + | let mut len: u32 = 0; |
|
| 149 | + | ||
| 150 | + | path[len] = target; |
|
| 151 | + | len = len + 1; |
|
| 152 | + | ||
| 153 | + | let mut cur: ?u32 = getPrev(g, target); |
|
| 154 | + | while let node = cur { |
|
| 155 | + | path[len] = node; |
|
| 156 | + | len = len + 1; |
|
| 157 | + | cur = getPrev(g, node); |
|
| 158 | + | } |
|
| 159 | + | ||
| 160 | + | // Reverse the path. |
|
| 161 | + | let mut a: u32 = 0; |
|
| 162 | + | let mut b: u32 = len - 1; |
|
| 163 | + | while a < b { |
|
| 164 | + | let tmp: u32 = path[a]; |
|
| 165 | + | path[a] = path[b]; |
|
| 166 | + | path[b] = tmp; |
|
| 167 | + | a = a + 1; |
|
| 168 | + | b = b - 1; |
|
| 169 | + | } |
|
| 170 | + | return len; |
|
| 171 | + | } |
|
| 172 | + | ||
| 173 | + | fn testLinear(g: *mut Graph) -> i32 { |
|
| 174 | + | resetGraph(g, 5); |
|
| 175 | + | addEdge(g, 0, 1, 10); |
|
| 176 | + | addEdge(g, 1, 2, 10); |
|
| 177 | + | addEdge(g, 2, 3, 10); |
|
| 178 | + | addEdge(g, 3, 4, 10); |
|
| 179 | + | ||
| 180 | + | dijkstra(g, 0); |
|
| 181 | + | ||
| 182 | + | ||
| 183 | + | let expected: [u32; 5] = [0, 10, 20, 30, 40]; |
|
| 184 | + | for exp, i in expected { |
|
| 185 | + | if g.dist[i] != exp { |
|
| 186 | + | return i as i32 + 1; |
|
| 187 | + | } |
|
| 188 | + | } |
|
| 189 | + | ||
| 190 | + | let mut path: [u32; 16] = [0; 16]; |
|
| 191 | + | let len: u32 = reconstructPath(g, 4, &mut path[..]); |
|
| 192 | + | if len != 5 { |
|
| 193 | + | return 6; |
|
| 194 | + | } |
|
| 195 | + | if path[0] != 0 { |
|
| 196 | + | return 7; |
|
| 197 | + | } |
|
| 198 | + | if path[4] != 4 { |
|
| 199 | + | return 8; |
|
| 200 | + | } |
|
| 201 | + | ||
| 202 | + | return 0; |
|
| 203 | + | } |
|
| 204 | + | ||
| 205 | + | fn testShortcut(g: *mut Graph) -> i32 { |
|
| 206 | + | resetGraph(g, 4); |
|
| 207 | + | addEdge(g, 0, 1, 10); |
|
| 208 | + | addEdge(g, 1, 2, 10); |
|
| 209 | + | addEdge(g, 0, 3, 5); |
|
| 210 | + | addEdge(g, 3, 2, 3); |
|
| 211 | + | ||
| 212 | + | dijkstra(g, 0); |
|
| 213 | + | ||
| 214 | + | if g.dist[2] != 8 { |
|
| 215 | + | return 1; |
|
| 216 | + | } |
|
| 217 | + | ||
| 218 | + | let prev2 = getPrev(g, 2) else { |
|
| 219 | + | return 2; |
|
| 220 | + | }; |
|
| 221 | + | if prev2 != 3 { |
|
| 222 | + | return 2; |
|
| 223 | + | } |
|
| 224 | + | ||
| 225 | + | return 0; |
|
| 226 | + | } |
|
| 227 | + | ||
| 228 | + | fn testBidirectional(g: *mut Graph) -> i32 { |
|
| 229 | + | resetGraph(g, 5); |
|
| 230 | + | addBidiEdge(g, 0, 1, 1); |
|
| 231 | + | addBidiEdge(g, 1, 2, 2); |
|
| 232 | + | addBidiEdge(g, 2, 3, 3); |
|
| 233 | + | addBidiEdge(g, 3, 0, 4); |
|
| 234 | + | addBidiEdge(g, 0, 4, 7); |
|
| 235 | + | addBidiEdge(g, 1, 4, 5); |
|
| 236 | + | addBidiEdge(g, 2, 4, 1); |
|
| 237 | + | ||
| 238 | + | dijkstra(g, 0); |
|
| 239 | + | ||
| 240 | + | let expected: [u32; 5] = [0, 1, 3, 4, 4]; |
|
| 241 | + | for exp, i in expected { |
|
| 242 | + | if g.dist[i] != exp { |
|
| 243 | + | return i as i32 + 1; |
|
| 244 | + | } |
|
| 245 | + | } |
|
| 246 | + | ||
| 247 | + | return 0; |
|
| 248 | + | } |
|
| 249 | + | ||
| 250 | + | fn testComplete(g: *mut Graph) -> i32 { |
|
| 251 | + | resetGraph(g, 8); |
|
| 252 | + | ||
| 253 | + | let mut i: u32 = 0; |
|
| 254 | + | while i < 8 { |
|
| 255 | + | let mut j: u32 = 0; |
|
| 256 | + | while j < 8 { |
|
| 257 | + | if i != j { |
|
| 258 | + | let mut diff: u32 = j - i; |
|
| 259 | + | if i > j { |
|
| 260 | + | diff = i - j; |
|
| 261 | + | } |
|
| 262 | + | addEdge(g, i, j, diff * 3 + 1); |
|
| 263 | + | } |
|
| 264 | + | j = j + 1; |
|
| 265 | + | } |
|
| 266 | + | i = i + 1; |
|
| 267 | + | } |
|
| 268 | + | ||
| 269 | + | dijkstra(g, 0); |
|
| 270 | + | ||
| 271 | + | let mut k: u32 = 1; |
|
| 272 | + | while k < 8 { |
|
| 273 | + | let expected: u32 = k * 3 + 1; |
|
| 274 | + | if g.dist[k] != expected { |
|
| 275 | + | return k as i32; |
|
| 276 | + | } |
|
| 277 | + | k = k + 1; |
|
| 278 | + | } |
|
| 279 | + | ||
| 280 | + | return 0; |
|
| 281 | + | } |
|
| 282 | + | ||
| 283 | + | fn testDisconnected(g: *mut Graph) -> i32 { |
|
| 284 | + | resetGraph(g, 6); |
|
| 285 | + | addEdge(g, 0, 1, 5); |
|
| 286 | + | addEdge(g, 1, 2, 3); |
|
| 287 | + | addEdge(g, 3, 4, 2); |
|
| 288 | + | addEdge(g, 4, 5, 1); |
|
| 289 | + | ||
| 290 | + | dijkstra(g, 0); |
|
| 291 | + | ||
| 292 | + | if g.dist[0] != 0 { |
|
| 293 | + | return 1; |
|
| 294 | + | } |
|
| 295 | + | if g.dist[1] != 5 { |
|
| 296 | + | return 2; |
|
| 297 | + | } |
|
| 298 | + | if g.dist[2] != 8 { |
|
| 299 | + | return 3; |
|
| 300 | + | } |
|
| 301 | + | // Unreachable nodes stay at INF. |
|
| 302 | + | if g.dist[3] != INF { |
|
| 303 | + | return 4; |
|
| 304 | + | } |
|
| 305 | + | if g.dist[4] != INF { |
|
| 306 | + | return 5; |
|
| 307 | + | } |
|
| 308 | + | if g.dist[5] != INF { |
|
| 309 | + | return 6; |
|
| 310 | + | } |
|
| 311 | + | ||
| 312 | + | // Predecessors of unreachable nodes should be nil. |
|
| 313 | + | if getPrev(g, 3) != nil { |
|
| 314 | + | return 7; |
|
| 315 | + | } |
|
| 316 | + | ||
| 317 | + | return 0; |
|
| 318 | + | } |
|
| 319 | + | ||
| 320 | + | fn testDiamond(g: *mut Graph) -> i32 { |
|
| 321 | + | resetGraph(g, 5); |
|
| 322 | + | addEdge(g, 0, 1, 5); |
|
| 323 | + | addEdge(g, 0, 2, 5); |
|
| 324 | + | addEdge(g, 1, 3, 5); |
|
| 325 | + | addEdge(g, 2, 3, 5); |
|
| 326 | + | addEdge(g, 3, 4, 2); |
|
| 327 | + | ||
| 328 | + | dijkstra(g, 0); |
|
| 329 | + | ||
| 330 | + | if g.dist[3] != 10 { |
|
| 331 | + | return 1; |
|
| 332 | + | } |
|
| 333 | + | if g.dist[4] != 12 { |
|
| 334 | + | return 2; |
|
| 335 | + | } |
|
| 336 | + | // Predecessor should be 1 or 2. |
|
| 337 | + | let prev3 = getPrev(g, 3) else { |
|
| 338 | + | return 3; |
|
| 339 | + | }; |
|
| 340 | + | if prev3 != 1 and prev3 != 2 { |
|
| 341 | + | return 3; |
|
| 342 | + | } |
|
| 343 | + | ||
| 344 | + | return 0; |
|
| 345 | + | } |
|
| 346 | + | ||
| 347 | + | @default fn main() -> i32 { |
|
| 348 | + | let mut adj: [[u32; 16]; 16] = [[0xFFFFFFFF; 16]; 16]; |
|
| 349 | + | let mut dist: [u32; 16] = [0xFFFFFFFF; 16]; |
|
| 350 | + | let mut prev: [i32; 16] = [-1; 16]; |
|
| 351 | + | let mut visited: [bool; 16] = [false; 16]; |
|
| 352 | + | let mut heap: [HeapEntry; 256] = [HeapEntry { dist: 0, node: 0 }; 256]; |
|
| 353 | + | ||
| 354 | + | let mut g: Graph = Graph { |
|
| 355 | + | adj: &mut adj[..], |
|
| 356 | + | dist: &mut dist[..], |
|
| 357 | + | prev: &mut prev[..], |
|
| 358 | + | visited: &mut visited[..], |
|
| 359 | + | numNodes: 0, |
|
| 360 | + | heap: &mut heap[..], |
|
| 361 | + | heapSize: 0, |
|
| 362 | + | }; |
|
| 363 | + | ||
| 364 | + | let r1: i32 = testLinear(&mut g); |
|
| 365 | + | if r1 != 0 { |
|
| 366 | + | return 10 + r1; |
|
| 367 | + | } |
|
| 368 | + | ||
| 369 | + | let r2: i32 = testShortcut(&mut g); |
|
| 370 | + | if r2 != 0 { |
|
| 371 | + | return 20 + r2; |
|
| 372 | + | } |
|
| 373 | + | ||
| 374 | + | let r3: i32 = testBidirectional(&mut g); |
|
| 375 | + | if r3 != 0 { |
|
| 376 | + | return 30 + r3; |
|
| 377 | + | } |
|
| 378 | + | ||
| 379 | + | let r4: i32 = testComplete(&mut g); |
|
| 380 | + | if r4 != 0 { |
|
| 381 | + | return 40 + r4; |
|
| 382 | + | } |
|
| 383 | + | ||
| 384 | + | let r5: i32 = testDisconnected(&mut g); |
|
| 385 | + | if r5 != 0 { |
|
| 386 | + | return 50 + r5; |
|
| 387 | + | } |
|
| 388 | + | ||
| 389 | + | let r6: i32 = testDiamond(&mut g); |
|
| 390 | + | if r6 != 0 { |
|
| 391 | + | return 60 + r6; |
|
| 392 | + | } |
|
| 393 | + | ||
| 394 | + | return 0; |
|
| 395 | + | } |
lib/std/arch/rv64/tests/prog.eval.rad
added
+256 -0
| 1 | + | //! Expression evaluator. |
|
| 2 | + | //! Evaluate arithmetic expressions encoded as a tree in an array |
|
| 3 | + | //! of tagged union nodes. Build expression trees, evaluate them recursively, |
|
| 4 | + | //! and verify results. |
|
| 5 | + | ||
| 6 | + | /// An expression node. Leaf nodes hold a number. Interior nodes hold an |
|
| 7 | + | /// operator and indices (into the nodes array) of their left and right children. |
|
| 8 | + | union Expr { |
|
| 9 | + | Num(i32), |
|
| 10 | + | Add(BinOp), |
|
| 11 | + | Sub(BinOp), |
|
| 12 | + | Mul(BinOp), |
|
| 13 | + | Div(BinOp), |
|
| 14 | + | Neg(u32), |
|
| 15 | + | } |
|
| 16 | + | ||
| 17 | + | /// Binary operator: indices of left and right child nodes. |
|
| 18 | + | record BinOp { |
|
| 19 | + | left: u32, |
|
| 20 | + | right: u32, |
|
| 21 | + | } |
|
| 22 | + | ||
| 23 | + | /// A pool of expression nodes with a count of allocated nodes. |
|
| 24 | + | record Pool { |
|
| 25 | + | nodes: [Expr; 64], |
|
| 26 | + | count: u32, |
|
| 27 | + | } |
|
| 28 | + | ||
| 29 | + | /// Evaluation error. |
|
| 30 | + | union EvalError { |
|
| 31 | + | DivByZero |
|
| 32 | + | } |
|
| 33 | + | ||
| 34 | + | /// Allocate a new node, returning its index. |
|
| 35 | + | fn newNode(pool: *mut Pool, expr: Expr) -> u32 { |
|
| 36 | + | let idx = pool.count; |
|
| 37 | + | pool.nodes[idx] = expr; |
|
| 38 | + | pool.count = pool.count + 1; |
|
| 39 | + | return idx; |
|
| 40 | + | } |
|
| 41 | + | ||
| 42 | + | /// Convenience constructors. |
|
| 43 | + | fn num(pool: *mut Pool, n: i32) -> u32 { |
|
| 44 | + | return newNode(pool, Expr::Num(n)); |
|
| 45 | + | } |
|
| 46 | + | ||
| 47 | + | fn add(pool: *mut Pool, left: u32, right: u32) -> u32 { |
|
| 48 | + | return newNode(pool, Expr::Add(BinOp { left, right })); |
|
| 49 | + | } |
|
| 50 | + | ||
| 51 | + | fn sub(pool: *mut Pool, left: u32, right: u32) -> u32 { |
|
| 52 | + | return newNode(pool, Expr::Sub(BinOp { left, right })); |
|
| 53 | + | } |
|
| 54 | + | ||
| 55 | + | fn mul(pool: *mut Pool, left: u32, right: u32) -> u32 { |
|
| 56 | + | return newNode(pool, Expr::Mul(BinOp { left, right })); |
|
| 57 | + | } |
|
| 58 | + | ||
| 59 | + | fn div(pool: *mut Pool, left: u32, right: u32) -> u32 { |
|
| 60 | + | return newNode(pool, Expr::Div(BinOp { left, right })); |
|
| 61 | + | } |
|
| 62 | + | ||
| 63 | + | fn neg(pool: *mut Pool, child: u32) -> u32 { |
|
| 64 | + | return newNode(pool, Expr::Neg(child)); |
|
| 65 | + | } |
|
| 66 | + | ||
| 67 | + | /// Recursively evaluate the expression tree rooted at nodes[idx]. |
|
| 68 | + | fn eval(nodes: *[Expr], idx: u32) -> i32 throws (EvalError) { |
|
| 69 | + | let node = nodes[idx]; |
|
| 70 | + | match node { |
|
| 71 | + | case Expr::Num(n) => { |
|
| 72 | + | return n; |
|
| 73 | + | } |
|
| 74 | + | case Expr::Add(op) => { |
|
| 75 | + | return try eval(nodes, op.left) + try eval(nodes, op.right); |
|
| 76 | + | } |
|
| 77 | + | case Expr::Sub(op) => { |
|
| 78 | + | return try eval(nodes, op.left) - try eval(nodes, op.right); |
|
| 79 | + | } |
|
| 80 | + | case Expr::Mul(op) => { |
|
| 81 | + | return try eval(nodes, op.left) * try eval(nodes, op.right); |
|
| 82 | + | } |
|
| 83 | + | case Expr::Div(op) => { |
|
| 84 | + | let l = try eval(nodes, op.left); |
|
| 85 | + | let r = try eval(nodes, op.right); |
|
| 86 | + | if r == 0 { |
|
| 87 | + | throw EvalError::DivByZero; |
|
| 88 | + | } |
|
| 89 | + | return l / r; |
|
| 90 | + | } |
|
| 91 | + | case Expr::Neg(child) => { |
|
| 92 | + | return 0 - try eval(nodes, child); |
|
| 93 | + | } |
|
| 94 | + | } |
|
| 95 | + | } |
|
| 96 | + | ||
| 97 | + | /// Count the total number of nodes in the tree rooted at idx. |
|
| 98 | + | fn countNodes(nodes: *[Expr], idx: u32) -> u32 { |
|
| 99 | + | let node = nodes[idx]; |
|
| 100 | + | match node { |
|
| 101 | + | case Expr::Num(_) => { |
|
| 102 | + | return 1; |
|
| 103 | + | } |
|
| 104 | + | case Expr::Add(op) => { |
|
| 105 | + | return 1 + countNodes(nodes, op.left) + countNodes(nodes, op.right); |
|
| 106 | + | } |
|
| 107 | + | case Expr::Sub(op) => { |
|
| 108 | + | return 1 + countNodes(nodes, op.left) + countNodes(nodes, op.right); |
|
| 109 | + | } |
|
| 110 | + | case Expr::Mul(op) => { |
|
| 111 | + | return 1 + countNodes(nodes, op.left) + countNodes(nodes, op.right); |
|
| 112 | + | } |
|
| 113 | + | case Expr::Div(op) => { |
|
| 114 | + | return 1 + countNodes(nodes, op.left) + countNodes(nodes, op.right); |
|
| 115 | + | } |
|
| 116 | + | case Expr::Neg(child) => { |
|
| 117 | + | return 1 + countNodes(nodes, child); |
|
| 118 | + | } |
|
| 119 | + | } |
|
| 120 | + | } |
|
| 121 | + | ||
| 122 | + | /// Reset the node pool. |
|
| 123 | + | fn reset(pool: *mut Pool) { |
|
| 124 | + | pool.count = 0; |
|
| 125 | + | } |
|
| 126 | + | ||
| 127 | + | /// Test 1: Simple addition: 3 + 4 = 7 |
|
| 128 | + | fn testSimpleAdd(pool: *mut Pool) -> i32 { |
|
| 129 | + | reset(pool); |
|
| 130 | + | let root = add(pool, num(pool, 3), num(pool, 4)); |
|
| 131 | + | if try! eval(&pool.nodes[..], root) != 7 { |
|
| 132 | + | return 1; |
|
| 133 | + | } |
|
| 134 | + | return 0; |
|
| 135 | + | } |
|
| 136 | + | ||
| 137 | + | /// Test 2: Nested expression: (2 + 3) * (4 - 1) = 5 * 3 = 15 |
|
| 138 | + | fn testNested(pool: *mut Pool) -> i32 { |
|
| 139 | + | reset(pool); |
|
| 140 | + | let left = add(pool, num(pool, 2), num(pool, 3)); |
|
| 141 | + | let right = sub(pool, num(pool, 4), num(pool, 1)); |
|
| 142 | + | let root = mul(pool, left, right); |
|
| 143 | + | if try! eval(&pool.nodes[..], root) != 15 { |
|
| 144 | + | return 1; |
|
| 145 | + | } |
|
| 146 | + | if countNodes(&pool.nodes[..], root) != 7 { |
|
| 147 | + | return 2; |
|
| 148 | + | } |
|
| 149 | + | return 0; |
|
| 150 | + | } |
|
| 151 | + | ||
| 152 | + | /// Test 3: Complex expression: ((10 + 5) * 2 - 6) / 4 = (30 - 6) / 4 = 24 / 4 = 6 |
|
| 153 | + | fn testComplex(pool: *mut Pool) -> i32 { |
|
| 154 | + | reset(pool); |
|
| 155 | + | let a = add(pool, num(pool, 10), num(pool, 5)); |
|
| 156 | + | let b = mul(pool, a, num(pool, 2)); |
|
| 157 | + | let c = sub(pool, b, num(pool, 6)); |
|
| 158 | + | let root = div(pool, c, num(pool, 4)); |
|
| 159 | + | if try! eval(&pool.nodes[..], root) != 6 { |
|
| 160 | + | return 1; |
|
| 161 | + | } |
|
| 162 | + | return 0; |
|
| 163 | + | } |
|
| 164 | + | ||
| 165 | + | /// Test 4: Negation: -(3 + 4) = -7 |
|
| 166 | + | fn testNeg(pool: *mut Pool) -> i32 { |
|
| 167 | + | reset(pool); |
|
| 168 | + | let root = neg(pool, add(pool, num(pool, 3), num(pool, 4))); |
|
| 169 | + | if try! eval(&pool.nodes[..], root) != -7 { |
|
| 170 | + | return 1; |
|
| 171 | + | } |
|
| 172 | + | return 0; |
|
| 173 | + | } |
|
| 174 | + | ||
| 175 | + | /// Test 5: Deep tree: 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 = 36 |
|
| 176 | + | fn testDeep(pool: *mut Pool) -> i32 { |
|
| 177 | + | reset(pool); |
|
| 178 | + | let mut root = num(pool, 1); |
|
| 179 | + | let values: [i32; 7] = [2, 3, 4, 5, 6, 7, 8]; |
|
| 180 | + | for v in values { |
|
| 181 | + | root = add(pool, root, num(pool, v)); |
|
| 182 | + | } |
|
| 183 | + | if try! eval(&pool.nodes[..], root) != 36 { |
|
| 184 | + | return 1; |
|
| 185 | + | } |
|
| 186 | + | if countNodes(&pool.nodes[..], root) != 15 { |
|
| 187 | + | return 2; |
|
| 188 | + | } |
|
| 189 | + | return 0; |
|
| 190 | + | } |
|
| 191 | + | ||
| 192 | + | /// Test 6: Mixed operations: (100 - 3 * (2 + 8)) / 7 = (100 - 30) / 7 = 70 / 7 = 10 |
|
| 193 | + | fn testMixed(pool: *mut Pool) -> i32 { |
|
| 194 | + | reset(pool); |
|
| 195 | + | let inner = add(pool, num(pool, 2), num(pool, 8)); |
|
| 196 | + | let product = mul(pool, num(pool, 3), inner); |
|
| 197 | + | let diff = sub(pool, num(pool, 100), product); |
|
| 198 | + | let root = div(pool, diff, num(pool, 7)); |
|
| 199 | + | if try! eval(&pool.nodes[..], root) != 10 { |
|
| 200 | + | return 1; |
|
| 201 | + | } |
|
| 202 | + | return 0; |
|
| 203 | + | } |
|
| 204 | + | ||
| 205 | + | /// Test 7: Single number. |
|
| 206 | + | fn testSingleNum(pool: *mut Pool) -> i32 { |
|
| 207 | + | reset(pool); |
|
| 208 | + | let root = num(pool, 42); |
|
| 209 | + | if try! eval(&pool.nodes[..], root) != 42 { |
|
| 210 | + | return 1; |
|
| 211 | + | } |
|
| 212 | + | if countNodes(&pool.nodes[..], root) != 1 { |
|
| 213 | + | return 2; |
|
| 214 | + | } |
|
| 215 | + | return 0; |
|
| 216 | + | } |
|
| 217 | + | ||
| 218 | + | @default fn main() -> i32 { |
|
| 219 | + | let mut pool: Pool = Pool { nodes: [Expr::Num(0); 64], count: 0 }; |
|
| 220 | + | ||
| 221 | + | let r1 = testSimpleAdd(&mut pool); |
|
| 222 | + | if r1 != 0 { |
|
| 223 | + | return 10 + r1; |
|
| 224 | + | } |
|
| 225 | + | ||
| 226 | + | let r2 = testNested(&mut pool); |
|
| 227 | + | if r2 != 0 { |
|
| 228 | + | return 20 + r2; |
|
| 229 | + | } |
|
| 230 | + | ||
| 231 | + | let r3 = testComplex(&mut pool); |
|
| 232 | + | if r3 != 0 { |
|
| 233 | + | return 30 + r3; |
|
| 234 | + | } |
|
| 235 | + | ||
| 236 | + | let r4 = testNeg(&mut pool); |
|
| 237 | + | if r4 != 0 { |
|
| 238 | + | return 40 + r4; |
|
| 239 | + | } |
|
| 240 | + | ||
| 241 | + | let r5 = testDeep(&mut pool); |
|
| 242 | + | if r5 != 0 { |
|
| 243 | + | return 50 + r5; |
|
| 244 | + | } |
|
| 245 | + | ||
| 246 | + | let r6 = testMixed(&mut pool); |
|
| 247 | + | if r6 != 0 { |
|
| 248 | + | return 60 + r6; |
|
| 249 | + | } |
|
| 250 | + | ||
| 251 | + | let r7 = testSingleNum(&mut pool); |
|
| 252 | + | if r7 != 0 { |
|
| 253 | + | return 70 + r7; |
|
| 254 | + | } |
|
| 255 | + | return 0; |
|
| 256 | + | } |
lib/std/arch/rv64/tests/prog.hanoi.rad
added
+190 -0
| 1 | + | //! Tower of Hanoi. |
|
| 2 | + | //! Solve Tower of Hanoi for N disks, recording moves into an array. |
|
| 3 | + | //! Verify the move count and specific moves are correct. |
|
| 4 | + | ||
| 5 | + | const NUM_DISKS: u32 = 6; |
|
| 6 | + | /// 2^6 - 1 = 63 moves. |
|
| 7 | + | const MAX_MOVES: u32 = 63; |
|
| 8 | + | ||
| 9 | + | /// A single move: move a disk from one peg to another. |
|
| 10 | + | record Move { |
|
| 11 | + | disk: u32, |
|
| 12 | + | from: u32, |
|
| 13 | + | to: u32, |
|
| 14 | + | } |
|
| 15 | + | ||
| 16 | + | /// Log of all moves performed. |
|
| 17 | + | record MoveLog { |
|
| 18 | + | moves: [Move; 63], |
|
| 19 | + | count: u32, |
|
| 20 | + | } |
|
| 21 | + | ||
| 22 | + | /// Record a move. |
|
| 23 | + | fn recordMove(ml: *mut MoveLog, disk: u32, from: u32, to: u32) { |
|
| 24 | + | if ml.count < MAX_MOVES { |
|
| 25 | + | ml.moves[ml.count] = Move { disk, from, to }; |
|
| 26 | + | ml.count = ml.count + 1; |
|
| 27 | + | } |
|
| 28 | + | } |
|
| 29 | + | ||
| 30 | + | /// Solve Tower of Hanoi recursively. |
|
| 31 | + | /// Move `n` disks from peg `from` to peg `to` using `aux` as auxiliary. |
|
| 32 | + | fn hanoi(ml: *mut MoveLog, n: u32, from: u32, to: u32, aux: u32) { |
|
| 33 | + | if n == 0 { |
|
| 34 | + | return; |
|
| 35 | + | } |
|
| 36 | + | hanoi(ml, n - 1, from, aux, to); |
|
| 37 | + | recordMove(ml, n, from, to); |
|
| 38 | + | hanoi(ml, n - 1, aux, to, from); |
|
| 39 | + | } |
|
| 40 | + | ||
| 41 | + | /// Verify total move count. |
|
| 42 | + | fn testMoveCount(ml: *MoveLog) -> i32 { |
|
| 43 | + | // For N disks, there are 2^N - 1 moves. |
|
| 44 | + | if ml.count != MAX_MOVES { |
|
| 45 | + | return 1; |
|
| 46 | + | } |
|
| 47 | + | return 0; |
|
| 48 | + | } |
|
| 49 | + | ||
| 50 | + | /// Verify specific moves. |
|
| 51 | + | fn testSpecificMoves(ml: *MoveLog) -> i32 { |
|
| 52 | + | // First move: smallest disk (1) from peg 0 to peg 2. |
|
| 53 | + | if ml.moves[0].disk != 1 { |
|
| 54 | + | return 1; |
|
| 55 | + | } |
|
| 56 | + | if ml.moves[0].from != 0 { |
|
| 57 | + | return 2; |
|
| 58 | + | } |
|
| 59 | + | if ml.moves[0].to != 2 { |
|
| 60 | + | return 3; |
|
| 61 | + | } |
|
| 62 | + | ||
| 63 | + | // Middle move (index 31): disk 6 from peg 0 to peg 1. |
|
| 64 | + | if ml.moves[31].disk != 6 { |
|
| 65 | + | return 4; |
|
| 66 | + | } |
|
| 67 | + | if ml.moves[31].from != 0 { |
|
| 68 | + | return 5; |
|
| 69 | + | } |
|
| 70 | + | if ml.moves[31].to != 1 { |
|
| 71 | + | return 6; |
|
| 72 | + | } |
|
| 73 | + | ||
| 74 | + | // Last move: smallest disk (1) from peg 2 to peg 1. |
|
| 75 | + | if ml.moves[62].disk != 1 { |
|
| 76 | + | return 7; |
|
| 77 | + | } |
|
| 78 | + | if ml.moves[62].from != 2 { |
|
| 79 | + | return 8; |
|
| 80 | + | } |
|
| 81 | + | if ml.moves[62].to != 1 { |
|
| 82 | + | return 9; |
|
| 83 | + | } |
|
| 84 | + | return 0; |
|
| 85 | + | } |
|
| 86 | + | ||
| 87 | + | /// Peg state: array of 3 pegs, each holding up to NUM_DISKS disks. |
|
| 88 | + | /// top[p] = number of disks currently on peg p. |
|
| 89 | + | record Pegs { |
|
| 90 | + | stacks: [[u32; 6]; 3], |
|
| 91 | + | top: [u32; 3], |
|
| 92 | + | } |
|
| 93 | + | ||
| 94 | + | fn pegPush(pegs: *mut Pegs, peg: u32, disk: u32) -> bool { |
|
| 95 | + | let t = pegs.top[peg]; |
|
| 96 | + | if t > 0 { |
|
| 97 | + | // Check that the top disk is larger than the one being placed. |
|
| 98 | + | if pegs.stacks[peg][t - 1] < disk { |
|
| 99 | + | return false; |
|
| 100 | + | } |
|
| 101 | + | } |
|
| 102 | + | pegs.stacks[peg][t] = disk; |
|
| 103 | + | pegs.top[peg] = t + 1; |
|
| 104 | + | return true; |
|
| 105 | + | } |
|
| 106 | + | ||
| 107 | + | fn pegPop(pegs: *mut Pegs, peg: u32) -> u32 { |
|
| 108 | + | let t = pegs.top[peg]; |
|
| 109 | + | if t == 0 { |
|
| 110 | + | // Should not happen in a valid solution. |
|
| 111 | + | return 0; |
|
| 112 | + | } |
|
| 113 | + | pegs.top[peg] = t - 1; |
|
| 114 | + | return pegs.stacks[peg][t - 1]; |
|
| 115 | + | } |
|
| 116 | + | ||
| 117 | + | /// Simulate the peg state to verify correctness. |
|
| 118 | + | /// Replay all moves and check: |
|
| 119 | + | /// 1. No larger disk is placed on a smaller disk. |
|
| 120 | + | /// 2. All disks end up on peg 1. |
|
| 121 | + | fn testSimulate(ml: *MoveLog) -> i32 { |
|
| 122 | + | let mut pegs = Pegs { |
|
| 123 | + | stacks: [[0; 6]; 3], |
|
| 124 | + | top: [0, 0, 0], |
|
| 125 | + | }; |
|
| 126 | + | ||
| 127 | + | // Initialize: all disks on peg 0, largest at bottom. |
|
| 128 | + | let mut d: u32 = NUM_DISKS; |
|
| 129 | + | while d >= 1 { |
|
| 130 | + | if not pegPush(&mut pegs, 0, d) { |
|
| 131 | + | return 1; |
|
| 132 | + | } |
|
| 133 | + | if d == 0 { |
|
| 134 | + | break; |
|
| 135 | + | } |
|
| 136 | + | d = d - 1; |
|
| 137 | + | } |
|
| 138 | + | ||
| 139 | + | // Replay all moves. |
|
| 140 | + | let mut i: u32 = 0; |
|
| 141 | + | while i < ml.count { |
|
| 142 | + | let m = ml.moves[i]; |
|
| 143 | + | let disk = pegPop(&mut pegs, m.from); |
|
| 144 | + | if disk != m.disk { |
|
| 145 | + | return 2; |
|
| 146 | + | } |
|
| 147 | + | if not pegPush(&mut pegs, m.to, disk) { |
|
| 148 | + | return 3; |
|
| 149 | + | } |
|
| 150 | + | i = i + 1; |
|
| 151 | + | } |
|
| 152 | + | ||
| 153 | + | // All disks should be on peg 1. |
|
| 154 | + | if pegs.top[0] != 0 { |
|
| 155 | + | return 4; |
|
| 156 | + | } |
|
| 157 | + | if pegs.top[1] != NUM_DISKS { |
|
| 158 | + | return 5; |
|
| 159 | + | } |
|
| 160 | + | if pegs.top[2] != 0 { |
|
| 161 | + | return 6; |
|
| 162 | + | } |
|
| 163 | + | return 0; |
|
| 164 | + | } |
|
| 165 | + | ||
| 166 | + | @default fn main() -> i32 { |
|
| 167 | + | let mut ml = MoveLog { |
|
| 168 | + | moves: [Move { disk: 0, from: 0, to: 0 }; 63], |
|
| 169 | + | count: 0, |
|
| 170 | + | }; |
|
| 171 | + | ||
| 172 | + | // Solve: move all disks from peg 0 to peg 1 using peg 2. |
|
| 173 | + | hanoi(&mut ml, NUM_DISKS, 0, 1, 2); |
|
| 174 | + | ||
| 175 | + | let r1 = testMoveCount(&ml); |
|
| 176 | + | if r1 != 0 { |
|
| 177 | + | return 10 + r1; |
|
| 178 | + | } |
|
| 179 | + | ||
| 180 | + | let r2 = testSpecificMoves(&ml); |
|
| 181 | + | if r2 != 0 { |
|
| 182 | + | return 20 + r2; |
|
| 183 | + | } |
|
| 184 | + | ||
| 185 | + | let r3 = testSimulate(&ml); |
|
| 186 | + | if r3 != 0 { |
|
| 187 | + | return 30 + r3; |
|
| 188 | + | } |
|
| 189 | + | return 0; |
|
| 190 | + | } |
lib/std/arch/rv64/tests/prog.huffman.rad
added
+419 -0
| 1 | + | //! Huffman encoding. |
|
| 2 | + | //! Build a Huffman tree from character frequencies, generate prefix codes, |
|
| 3 | + | //! encode a message, decode it, and verify round-trip correctness. |
|
| 4 | + | ||
| 5 | + | ||
| 6 | + | const MAX_SYMBOLS: u32 = 32; |
|
| 7 | + | const MAX_NODES: u32 = 63; |
|
| 8 | + | const MAX_BITS: u32 = 512; |
|
| 9 | + | ||
| 10 | + | /// A node in the Huffman tree. |
|
| 11 | + | union HNodeKind { |
|
| 12 | + | /// Leaf node with a symbol index. |
|
| 13 | + | Leaf(u32), |
|
| 14 | + | /// Interior node (no symbol). |
|
| 15 | + | Interior, |
|
| 16 | + | } |
|
| 17 | + | ||
| 18 | + | record HNode { |
|
| 19 | + | freq: u32, |
|
| 20 | + | kind: HNodeKind, |
|
| 21 | + | left: u32, |
|
| 22 | + | right: u32, |
|
| 23 | + | } |
|
| 24 | + | ||
| 25 | + | const NIL: u32 = 0xFFFFFFFF; |
|
| 26 | + | ||
| 27 | + | record HuffState { |
|
| 28 | + | nodes: *mut [HNode], |
|
| 29 | + | nodeCount: u32, |
|
| 30 | + | heap: *mut [u32], |
|
| 31 | + | heapSize: u32, |
|
| 32 | + | codeBits: *mut [u32], |
|
| 33 | + | codeLen: *mut [u32], |
|
| 34 | + | bitstream: *mut [u8], |
|
| 35 | + | bitCount: u32, |
|
| 36 | + | } |
|
| 37 | + | ||
| 38 | + | fn newLeaf(s: *mut HuffState, freq: u32, symbol: u32) -> u32 { |
|
| 39 | + | let idx: u32 = s.nodeCount; |
|
| 40 | + | s.nodes[idx] = HNode { freq, kind: HNodeKind::Leaf(symbol), left: NIL, right: NIL }; |
|
| 41 | + | s.nodeCount = s.nodeCount + 1; |
|
| 42 | + | return idx; |
|
| 43 | + | } |
|
| 44 | + | ||
| 45 | + | fn newInterior(s: *mut HuffState, freq: u32, left: u32, right: u32) -> u32 { |
|
| 46 | + | let idx: u32 = s.nodeCount; |
|
| 47 | + | s.nodes[idx] = HNode { freq, kind: HNodeKind::Interior, left, right }; |
|
| 48 | + | s.nodeCount = s.nodeCount + 1; |
|
| 49 | + | return idx; |
|
| 50 | + | } |
|
| 51 | + | ||
| 52 | + | /// Get the symbol from a node, or nil if it's an interior node. |
|
| 53 | + | fn nodeSymbol(node: *HNode) -> ?u32 { |
|
| 54 | + | match node.kind { |
|
| 55 | + | case HNodeKind::Leaf(sym) => { |
|
| 56 | + | return sym; |
|
| 57 | + | } |
|
| 58 | + | case HNodeKind::Interior => { |
|
| 59 | + | return nil; |
|
| 60 | + | } |
|
| 61 | + | } |
|
| 62 | + | } |
|
| 63 | + | ||
| 64 | + | fn heapSwap(s: *mut HuffState, i: u32, j: u32) { |
|
| 65 | + | let tmp: u32 = s.heap[i]; |
|
| 66 | + | s.heap[i] = s.heap[j]; |
|
| 67 | + | s.heap[j] = tmp; |
|
| 68 | + | } |
|
| 69 | + | ||
| 70 | + | fn heapFreq(s: *HuffState, i: u32) -> u32 { |
|
| 71 | + | return s.nodes[s.heap[i]].freq; |
|
| 72 | + | } |
|
| 73 | + | ||
| 74 | + | fn siftUp(s: *mut HuffState, pos: u32) { |
|
| 75 | + | let mut i: u32 = pos; |
|
| 76 | + | while i > 0 { |
|
| 77 | + | let parent: u32 = (i - 1) / 2; |
|
| 78 | + | if heapFreq(s, i) < heapFreq(s, parent) { |
|
| 79 | + | heapSwap(s, i, parent); |
|
| 80 | + | i = parent; |
|
| 81 | + | } else { |
|
| 82 | + | return; |
|
| 83 | + | } |
|
| 84 | + | } |
|
| 85 | + | } |
|
| 86 | + | ||
| 87 | + | fn siftDown(s: *mut HuffState, pos: u32) { |
|
| 88 | + | let mut i: u32 = pos; |
|
| 89 | + | while true { |
|
| 90 | + | let left: u32 = 2 * i + 1; |
|
| 91 | + | let right: u32 = 2 * i + 2; |
|
| 92 | + | let mut smallest: u32 = i; |
|
| 93 | + | ||
| 94 | + | if left < s.heapSize and heapFreq(s, left) < heapFreq(s, smallest) { |
|
| 95 | + | smallest = left; |
|
| 96 | + | } |
|
| 97 | + | if right < s.heapSize and heapFreq(s, right) < heapFreq(s, smallest) { |
|
| 98 | + | smallest = right; |
|
| 99 | + | } |
|
| 100 | + | if smallest == i { |
|
| 101 | + | return; |
|
| 102 | + | } |
|
| 103 | + | heapSwap(s, i, smallest); |
|
| 104 | + | i = smallest; |
|
| 105 | + | } |
|
| 106 | + | } |
|
| 107 | + | ||
| 108 | + | fn heapPush(s: *mut HuffState, nodeIdx: u32) { |
|
| 109 | + | s.heap[s.heapSize] = nodeIdx; |
|
| 110 | + | s.heapSize = s.heapSize + 1; |
|
| 111 | + | siftUp(s, s.heapSize - 1); |
|
| 112 | + | } |
|
| 113 | + | ||
| 114 | + | fn heapPop(s: *mut HuffState) -> u32 { |
|
| 115 | + | let result: u32 = s.heap[0]; |
|
| 116 | + | s.heapSize = s.heapSize - 1; |
|
| 117 | + | s.heap[0] = s.heap[s.heapSize]; |
|
| 118 | + | if s.heapSize > 0 { |
|
| 119 | + | siftDown(s, 0); |
|
| 120 | + | } |
|
| 121 | + | return result; |
|
| 122 | + | } |
|
| 123 | + | ||
| 124 | + | fn buildTree(s: *mut HuffState, freqs: *[u32]) -> u32 { |
|
| 125 | + | s.nodeCount = 0; |
|
| 126 | + | s.heapSize = 0; |
|
| 127 | + | ||
| 128 | + | ||
| 129 | + | for freq, sym in freqs { |
|
| 130 | + | if freq > 0 { |
|
| 131 | + | let idx: u32 = newLeaf(s, freq, sym); |
|
| 132 | + | heapPush(s, idx); |
|
| 133 | + | } |
|
| 134 | + | } |
|
| 135 | + | ||
| 136 | + | while s.heapSize > 1 { |
|
| 137 | + | let left: u32 = heapPop(s); |
|
| 138 | + | let right: u32 = heapPop(s); |
|
| 139 | + | let combinedFreq: u32 = s.nodes[left].freq + s.nodes[right].freq; |
|
| 140 | + | let parent: u32 = newInterior(s, combinedFreq, left, right); |
|
| 141 | + | heapPush(s, parent); |
|
| 142 | + | } |
|
| 143 | + | ||
| 144 | + | return heapPop(s); |
|
| 145 | + | } |
|
| 146 | + | ||
| 147 | + | fn generateCodes(s: *mut HuffState, nodeIdx: u32, code: u32, depth: u32) { |
|
| 148 | + | match s.nodes[nodeIdx].kind { |
|
| 149 | + | case HNodeKind::Leaf(sym) => { |
|
| 150 | + | s.codeBits[sym] = code; |
|
| 151 | + | s.codeLen[sym] = depth; |
|
| 152 | + | } |
|
| 153 | + | case HNodeKind::Interior => { |
|
| 154 | + | if s.nodes[nodeIdx].left != NIL { |
|
| 155 | + | generateCodes(s, s.nodes[nodeIdx].left, code << 1, depth + 1); |
|
| 156 | + | } |
|
| 157 | + | if s.nodes[nodeIdx].right != NIL { |
|
| 158 | + | generateCodes(s, s.nodes[nodeIdx].right, (code << 1) | 1, depth + 1); |
|
| 159 | + | } |
|
| 160 | + | } |
|
| 161 | + | } |
|
| 162 | + | } |
|
| 163 | + | ||
| 164 | + | fn writeBit(s: *mut HuffState, bit: u32) { |
|
| 165 | + | let byteIdx: u32 = s.bitCount / 8; |
|
| 166 | + | let bitIdx: u32 = 7 - (s.bitCount % 8); |
|
| 167 | + | if bit == 1 { |
|
| 168 | + | s.bitstream[byteIdx] = s.bitstream[byteIdx] | (1 as u8 << bitIdx as u8); |
|
| 169 | + | } |
|
| 170 | + | s.bitCount = s.bitCount + 1; |
|
| 171 | + | } |
|
| 172 | + | ||
| 173 | + | fn readBit(s: *HuffState, pos: u32) -> u32 { |
|
| 174 | + | let byteIdx: u32 = pos / 8; |
|
| 175 | + | let bitIdx: u32 = 7 - (pos % 8); |
|
| 176 | + | return (s.bitstream[byteIdx] >> bitIdx as u8) as u32 & 1; |
|
| 177 | + | } |
|
| 178 | + | ||
| 179 | + | fn encode(s: *mut HuffState, msg: *[u32]) { |
|
| 180 | + | s.bitCount = 0; |
|
| 181 | + | let mut i: u32 = 0; |
|
| 182 | + | while i < MAX_BITS { |
|
| 183 | + | s.bitstream[i] = 0; |
|
| 184 | + | i = i + 1; |
|
| 185 | + | } |
|
| 186 | + | ||
| 187 | + | ||
| 188 | + | for sym in msg { |
|
| 189 | + | let bits: u32 = s.codeBits[sym]; |
|
| 190 | + | let len: u32 = s.codeLen[sym]; |
|
| 191 | + | let mut b: u32 = 0; |
|
| 192 | + | while b < len { |
|
| 193 | + | let bit: u32 = (bits >> (len - 1 - b)) & 1; |
|
| 194 | + | writeBit(s, bit); |
|
| 195 | + | b = b + 1; |
|
| 196 | + | } |
|
| 197 | + | } |
|
| 198 | + | } |
|
| 199 | + | ||
| 200 | + | fn decode(s: *HuffState, root: u32, numSymbols: u32, out: *mut [u32]) -> u32 { |
|
| 201 | + | let mut bitPos: u32 = 0; |
|
| 202 | + | let mut decoded: u32 = 0; |
|
| 203 | + | ||
| 204 | + | while decoded < numSymbols { |
|
| 205 | + | let mut cur: u32 = root; |
|
| 206 | + | // Walk the tree until we find a leaf. |
|
| 207 | + | while nodeSymbol(&s.nodes[cur]) == nil { |
|
| 208 | + | let bit: u32 = readBit(s, bitPos); |
|
| 209 | + | bitPos = bitPos + 1; |
|
| 210 | + | if bit == 0 { |
|
| 211 | + | cur = s.nodes[cur].left; |
|
| 212 | + | } else { |
|
| 213 | + | cur = s.nodes[cur].right; |
|
| 214 | + | } |
|
| 215 | + | } |
|
| 216 | + | let sym = nodeSymbol(&s.nodes[cur]) else { |
|
| 217 | + | return decoded; |
|
| 218 | + | }; |
|
| 219 | + | out[decoded] = sym; |
|
| 220 | + | decoded = decoded + 1; |
|
| 221 | + | } |
|
| 222 | + | return decoded; |
|
| 223 | + | } |
|
| 224 | + | ||
| 225 | + | fn resetCodes(s: *mut HuffState) { |
|
| 226 | + | let mut i: u32 = 0; |
|
| 227 | + | while i < MAX_SYMBOLS { |
|
| 228 | + | s.codeBits[i] = 0; |
|
| 229 | + | s.codeLen[i] = 0; |
|
| 230 | + | i = i + 1; |
|
| 231 | + | } |
|
| 232 | + | } |
|
| 233 | + | ||
| 234 | + | fn testBasic(s: *mut HuffState) -> i32 { |
|
| 235 | + | let freqs: [u32; 5] = [5, 9, 12, 13, 16]; |
|
| 236 | + | let root: u32 = buildTree(s, &freqs[..]); |
|
| 237 | + | ||
| 238 | + | if s.nodes[root].freq != 55 { |
|
| 239 | + | return 1; |
|
| 240 | + | } |
|
| 241 | + | ||
| 242 | + | resetCodes(s); |
|
| 243 | + | generateCodes(s, root, 0, 0); |
|
| 244 | + | ||
| 245 | + | // All symbols should have non-zero code lengths. |
|
| 246 | + | let mut i: u32 = 0; |
|
| 247 | + | while i < 5 { |
|
| 248 | + | if s.codeLen[i] == 0 { |
|
| 249 | + | return 2; |
|
| 250 | + | } |
|
| 251 | + | i = i + 1; |
|
| 252 | + | } |
|
| 253 | + | ||
| 254 | + | // No two symbols at the same depth should have the same code. |
|
| 255 | + | let mut a: u32 = 0; |
|
| 256 | + | while a < 5 { |
|
| 257 | + | let mut b: u32 = a + 1; |
|
| 258 | + | while b < 5 { |
|
| 259 | + | if s.codeLen[a] == s.codeLen[b] { |
|
| 260 | + | if s.codeBits[a] == s.codeBits[b] { |
|
| 261 | + | return 3; |
|
| 262 | + | } |
|
| 263 | + | } |
|
| 264 | + | b = b + 1; |
|
| 265 | + | } |
|
| 266 | + | a = a + 1; |
|
| 267 | + | } |
|
| 268 | + | ||
| 269 | + | return 0; |
|
| 270 | + | } |
|
| 271 | + | ||
| 272 | + | fn testRoundTrip(s: *mut HuffState) -> i32 { |
|
| 273 | + | let freqs: [u32; 5] = [5, 9, 12, 13, 16]; |
|
| 274 | + | let root: u32 = buildTree(s, &freqs[..]); |
|
| 275 | + | ||
| 276 | + | resetCodes(s); |
|
| 277 | + | generateCodes(s, root, 0, 0); |
|
| 278 | + | ||
| 279 | + | let msg: [u32; 16] = [4, 3, 2, 1, 0, 0, 1, 2, 3, 4, 2, 2, 3, 3, 4, 4]; |
|
| 280 | + | encode(s, &msg[..]); |
|
| 281 | + | ||
| 282 | + | let mut decoded: [u32; 16] = [0; 16]; |
|
| 283 | + | let count: u32 = decode(s, root, 16, &mut decoded[..]); |
|
| 284 | + | ||
| 285 | + | if count != 16 { |
|
| 286 | + | return 1; |
|
| 287 | + | } |
|
| 288 | + | ||
| 289 | + | // Verify round-trip. |
|
| 290 | + | for expected, i in msg { |
|
| 291 | + | if decoded[i] != expected { |
|
| 292 | + | return i as i32 + 2; |
|
| 293 | + | } |
|
| 294 | + | } |
|
| 295 | + | ||
| 296 | + | return 0; |
|
| 297 | + | } |
|
| 298 | + | ||
| 299 | + | fn testSkewed(s: *mut HuffState) -> i32 { |
|
| 300 | + | let freqs: [u32; 6] = [100, 1, 1, 1, 1, 1]; |
|
| 301 | + | let root: u32 = buildTree(s, &freqs[..]); |
|
| 302 | + | ||
| 303 | + | resetCodes(s); |
|
| 304 | + | generateCodes(s, root, 0, 0); |
|
| 305 | + | ||
| 306 | + | // The most frequent symbol should have the shortest code. |
|
| 307 | + | let mut shortestLen: u32 = 100; |
|
| 308 | + | let mut shortestSym: u32 = 0; |
|
| 309 | + | let mut i: u32 = 0; |
|
| 310 | + | while i < 6 { |
|
| 311 | + | if s.codeLen[i] > 0 and s.codeLen[i] < shortestLen { |
|
| 312 | + | shortestLen = s.codeLen[i]; |
|
| 313 | + | shortestSym = i; |
|
| 314 | + | } |
|
| 315 | + | i = i + 1; |
|
| 316 | + | } |
|
| 317 | + | if shortestSym != 0 { |
|
| 318 | + | return 1; |
|
| 319 | + | } |
|
| 320 | + | ||
| 321 | + | let msg: [u32; 12] = [0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 2]; |
|
| 322 | + | encode(s, &msg[..]); |
|
| 323 | + | ||
| 324 | + | let mut decoded: [u32; 12] = [0; 12]; |
|
| 325 | + | let count: u32 = decode(s, root, 12, &mut decoded[..]); |
|
| 326 | + | if count != 12 { |
|
| 327 | + | return 2; |
|
| 328 | + | } |
|
| 329 | + | ||
| 330 | + | for expected, i in msg { |
|
| 331 | + | if decoded[i] != expected { |
|
| 332 | + | return 3; |
|
| 333 | + | } |
|
| 334 | + | } |
|
| 335 | + | ||
| 336 | + | return 0; |
|
| 337 | + | } |
|
| 338 | + | ||
| 339 | + | fn testUniform(s: *mut HuffState) -> i32 { |
|
| 340 | + | let freqs: [u32; 8] = [10, 10, 10, 10, 10, 10, 10, 10]; |
|
| 341 | + | let root: u32 = buildTree(s, &freqs[..]); |
|
| 342 | + | ||
| 343 | + | if s.nodes[root].freq != 80 { |
|
| 344 | + | return 1; |
|
| 345 | + | } |
|
| 346 | + | ||
| 347 | + | resetCodes(s); |
|
| 348 | + | generateCodes(s, root, 0, 0); |
|
| 349 | + | ||
| 350 | + | // All 8 symbols should have code length 3 (8 = 2^3). |
|
| 351 | + | let mut i: u32 = 0; |
|
| 352 | + | while i < 8 { |
|
| 353 | + | if s.codeLen[i] != 3 { |
|
| 354 | + | return 2; |
|
| 355 | + | } |
|
| 356 | + | i = i + 1; |
|
| 357 | + | } |
|
| 358 | + | ||
| 359 | + | let msg: [u32; 8] = [0, 1, 2, 3, 4, 5, 6, 7]; |
|
| 360 | + | encode(s, &msg[..]); |
|
| 361 | + | ||
| 362 | + | if s.bitCount != 24 { |
|
| 363 | + | return 3; |
|
| 364 | + | } |
|
| 365 | + | ||
| 366 | + | let mut decoded: [u32; 8] = [0; 8]; |
|
| 367 | + | let count: u32 = decode(s, root, 8, &mut decoded[..]); |
|
| 368 | + | if count != 8 { |
|
| 369 | + | return 4; |
|
| 370 | + | } |
|
| 371 | + | for expected, i in msg { |
|
| 372 | + | if decoded[i] != expected { |
|
| 373 | + | return 5; |
|
| 374 | + | } |
|
| 375 | + | } |
|
| 376 | + | ||
| 377 | + | return 0; |
|
| 378 | + | } |
|
| 379 | + | ||
| 380 | + | @default fn main() -> i32 { |
|
| 381 | + | let mut nodes: [HNode; 63] = [HNode { freq: 0, kind: HNodeKind::Interior, left: 0xFFFFFFFF, right: 0xFFFFFFFF }; 63]; |
|
| 382 | + | let mut heap: [u32; 63] = [0; 63]; |
|
| 383 | + | let mut codeBits: [u32; 32] = [0; 32]; |
|
| 384 | + | let mut codeLen: [u32; 32] = [0; 32]; |
|
| 385 | + | let mut bitstream: [u8; 512] = [0; 512]; |
|
| 386 | + | ||
| 387 | + | let mut s: HuffState = HuffState { |
|
| 388 | + | nodes: &mut nodes[..], |
|
| 389 | + | nodeCount: 0, |
|
| 390 | + | heap: &mut heap[..], |
|
| 391 | + | heapSize: 0, |
|
| 392 | + | codeBits: &mut codeBits[..], |
|
| 393 | + | codeLen: &mut codeLen[..], |
|
| 394 | + | bitstream: &mut bitstream[..], |
|
| 395 | + | bitCount: 0, |
|
| 396 | + | }; |
|
| 397 | + | ||
| 398 | + | let r1: i32 = testBasic(&mut s); |
|
| 399 | + | if r1 != 0 { |
|
| 400 | + | return 10 + r1; |
|
| 401 | + | } |
|
| 402 | + | ||
| 403 | + | let r2: i32 = testRoundTrip(&mut s); |
|
| 404 | + | if r2 != 0 { |
|
| 405 | + | return 20 + r2; |
|
| 406 | + | } |
|
| 407 | + | ||
| 408 | + | let r3: i32 = testSkewed(&mut s); |
|
| 409 | + | if r3 != 0 { |
|
| 410 | + | return 30 + r3; |
|
| 411 | + | } |
|
| 412 | + | ||
| 413 | + | let r4: i32 = testUniform(&mut s); |
|
| 414 | + | if r4 != 0 { |
|
| 415 | + | return 40 + r4; |
|
| 416 | + | } |
|
| 417 | + | ||
| 418 | + | return 0; |
|
| 419 | + | } |
lib/std/arch/rv64/tests/prog.hybridsort.rad
added
+139 -0
| 1 | + | //! Insertion sort and selection sort. |
|
| 2 | + | //! Sort two copies of the same data with each algorithm, then verify |
|
| 3 | + | //! both produce identical sorted output. |
|
| 4 | + | ||
| 5 | + | /// Copy `src` into `dst`. |
|
| 6 | + | fn copy(dst: *mut [i32], src: *[i32]) { |
|
| 7 | + | for val, i in src { |
|
| 8 | + | dst[i] = val; |
|
| 9 | + | } |
|
| 10 | + | } |
|
| 11 | + | ||
| 12 | + | /// Insertion sort on the given array. |
|
| 13 | + | fn insertionSort(data: *mut [i32]) { |
|
| 14 | + | let mut i: u32 = 1; |
|
| 15 | + | while i < data.len { |
|
| 16 | + | let key = data[i]; |
|
| 17 | + | let mut j: i32 = i as i32 - 1; |
|
| 18 | + | while j >= 0 and data[j as u32] > key { |
|
| 19 | + | data[(j + 1) as u32] = data[j as u32]; |
|
| 20 | + | j = j - 1; |
|
| 21 | + | } |
|
| 22 | + | data[(j + 1) as u32] = key; |
|
| 23 | + | i = i + 1; |
|
| 24 | + | } |
|
| 25 | + | } |
|
| 26 | + | ||
| 27 | + | /// Selection sort on the given array. |
|
| 28 | + | fn selectionSort(data: *mut [i32]) { |
|
| 29 | + | let mut i: u32 = 0; |
|
| 30 | + | while i < data.len - 1 { |
|
| 31 | + | let mut minIdx: u32 = i; |
|
| 32 | + | let mut j: u32 = i + 1; |
|
| 33 | + | while j < data.len { |
|
| 34 | + | if data[j] < data[minIdx] { |
|
| 35 | + | minIdx = j; |
|
| 36 | + | } |
|
| 37 | + | j = j + 1; |
|
| 38 | + | } |
|
| 39 | + | if minIdx != i { |
|
| 40 | + | let tmp = data[i]; |
|
| 41 | + | data[i] = data[minIdx]; |
|
| 42 | + | data[minIdx] = tmp; |
|
| 43 | + | } |
|
| 44 | + | i = i + 1; |
|
| 45 | + | } |
|
| 46 | + | } |
|
| 47 | + | ||
| 48 | + | /// Check that an array is sorted in ascending order. |
|
| 49 | + | fn isSorted(data: *[i32]) -> bool { |
|
| 50 | + | let mut prev: ?i32 = nil; |
|
| 51 | + | for val in data { |
|
| 52 | + | if let p = prev { |
|
| 53 | + | if p > val { |
|
| 54 | + | return false; |
|
| 55 | + | } |
|
| 56 | + | } |
|
| 57 | + | prev = val; |
|
| 58 | + | } |
|
| 59 | + | return true; |
|
| 60 | + | } |
|
| 61 | + | ||
| 62 | + | /// Compute sum of all elements. |
|
| 63 | + | fn sum(data: *[i32]) -> i32 { |
|
| 64 | + | let mut total: i32 = 0; |
|
| 65 | + | for val in data { |
|
| 66 | + | total = total + val; |
|
| 67 | + | } |
|
| 68 | + | return total; |
|
| 69 | + | } |
|
| 70 | + | ||
| 71 | + | /// Compare two arrays element by element. |
|
| 72 | + | fn arraysEqual(a: *[i32], b: *[i32]) -> i32 { |
|
| 73 | + | for val, i in a { |
|
| 74 | + | if val != b[i] { |
|
| 75 | + | return i as i32 + 1; |
|
| 76 | + | } |
|
| 77 | + | } |
|
| 78 | + | return 0; |
|
| 79 | + | } |
|
| 80 | + | ||
| 81 | + | /// Verify specific positions in the sorted output. |
|
| 82 | + | fn verifyPositions(data: *[i32]) -> i32 { |
|
| 83 | + | // Sorted: 2 3 6 8 10 14 19 25 27 33 39 41 45 48 53 56 62 67 72 74 81 88 91 97 |
|
| 84 | + | let expected: [i32; 6] = [2, 3, 6, 41, 91, 97]; |
|
| 85 | + | let indices: [u32; 6] = [0, 1, 2, 11, 22, 23]; |
|
| 86 | + | ||
| 87 | + | for exp, i in expected { |
|
| 88 | + | if data[indices[i]] != exp { |
|
| 89 | + | return i as i32 + 1; |
|
| 90 | + | } |
|
| 91 | + | } |
|
| 92 | + | return 0; |
|
| 93 | + | } |
|
| 94 | + | ||
| 95 | + | @default fn main() -> i32 { |
|
| 96 | + | let orig: [i32; 24] = [ |
|
| 97 | + | 88, 14, 53, 27, 91, 6, 72, 39, |
|
| 98 | + | 45, 2, 67, 33, 81, 19, 56, 10, |
|
| 99 | + | 74, 48, 3, 62, 25, 97, 8, 41 |
|
| 100 | + | ]; |
|
| 101 | + | let origSum = sum(&orig[..]); |
|
| 102 | + | ||
| 103 | + | // Sort with insertion sort. |
|
| 104 | + | let mut a: [i32; 24] = [0; 24]; |
|
| 105 | + | copy(&mut a[..], &orig[..]); |
|
| 106 | + | insertionSort(&mut a[..]); |
|
| 107 | + | ||
| 108 | + | if not isSorted(&a[..]) { |
|
| 109 | + | return 1; |
|
| 110 | + | } |
|
| 111 | + | if sum(&a[..]) != origSum { |
|
| 112 | + | return 2; |
|
| 113 | + | } |
|
| 114 | + | ||
| 115 | + | // Sort with selection sort. |
|
| 116 | + | let mut b: [i32; 24] = [0; 24]; |
|
| 117 | + | copy(&mut b[..], &orig[..]); |
|
| 118 | + | selectionSort(&mut b[..]); |
|
| 119 | + | ||
| 120 | + | if not isSorted(&b[..]) { |
|
| 121 | + | return 3; |
|
| 122 | + | } |
|
| 123 | + | if sum(&b[..]) != origSum { |
|
| 124 | + | return 4; |
|
| 125 | + | } |
|
| 126 | + | ||
| 127 | + | // Both algorithms must produce the same result. |
|
| 128 | + | let r = arraysEqual(&a[..], &b[..]); |
|
| 129 | + | if r != 0 { |
|
| 130 | + | return 10 + r; |
|
| 131 | + | } |
|
| 132 | + | ||
| 133 | + | // Verify specific sorted positions. |
|
| 134 | + | let r2 = verifyPositions(&a[..]); |
|
| 135 | + | if r2 != 0 { |
|
| 136 | + | return 40 + r2; |
|
| 137 | + | } |
|
| 138 | + | return 0; |
|
| 139 | + | } |
lib/std/arch/rv64/tests/prog.linkedlist.rad
added
+327 -0
| 1 | + | //! Linked list via array pool. |
|
| 2 | + | //! Implement a singly-linked list using an array of node records |
|
| 3 | + | //! (pool allocator pattern). Operations: push, pop, find, reverse, length. |
|
| 4 | + | ||
| 5 | + | ||
| 6 | + | /// A node in the linked list. |
|
| 7 | + | record Node { |
|
| 8 | + | value: i32, |
|
| 9 | + | next: ?u32, |
|
| 10 | + | } |
|
| 11 | + | ||
| 12 | + | /// A linked list backed by a pool of pre-allocated nodes. |
|
| 13 | + | record List { |
|
| 14 | + | pool: [Node; 64], |
|
| 15 | + | free: u32, |
|
| 16 | + | head: ?u32, |
|
| 17 | + | } |
|
| 18 | + | ||
| 19 | + | /// Allocate a node from the pool. Returns its index. |
|
| 20 | + | fn alloc(list: *mut List, value: i32) -> u32 { |
|
| 21 | + | let idx = list.free; |
|
| 22 | + | list.free = list.free + 1; |
|
| 23 | + | list.pool[idx] = Node { value, next: nil }; |
|
| 24 | + | return idx; |
|
| 25 | + | } |
|
| 26 | + | ||
| 27 | + | /// Push a value onto the front of the list. |
|
| 28 | + | fn push(list: *mut List, value: i32) { |
|
| 29 | + | let idx = alloc(list, value); |
|
| 30 | + | list.pool[idx].next = list.head; |
|
| 31 | + | list.head = idx; |
|
| 32 | + | } |
|
| 33 | + | ||
| 34 | + | /// Pop a value from the front of the list. Returns nil if empty. |
|
| 35 | + | fn pop(list: *mut List) -> ?i32 { |
|
| 36 | + | let idx = list.head else { |
|
| 37 | + | return nil; |
|
| 38 | + | }; |
|
| 39 | + | let value = list.pool[idx].value; |
|
| 40 | + | list.head = list.pool[idx].next; |
|
| 41 | + | return value; |
|
| 42 | + | } |
|
| 43 | + | ||
| 44 | + | /// Get the length of the list. |
|
| 45 | + | fn length(list: *List) -> u32 { |
|
| 46 | + | let mut count: u32 = 0; |
|
| 47 | + | let mut cur = list.head; |
|
| 48 | + | while let idx = cur { |
|
| 49 | + | count = count + 1; |
|
| 50 | + | cur = list.pool[idx].next; |
|
| 51 | + | } |
|
| 52 | + | return count; |
|
| 53 | + | } |
|
| 54 | + | ||
| 55 | + | /// Find a value in the list. Returns the node index if found, or nil. |
|
| 56 | + | fn find(list: *List, value: i32) -> ?u32 { |
|
| 57 | + | let mut cur = list.head; |
|
| 58 | + | while let idx = cur { |
|
| 59 | + | if list.pool[idx].value == value { |
|
| 60 | + | return idx; |
|
| 61 | + | } |
|
| 62 | + | cur = list.pool[idx].next; |
|
| 63 | + | } |
|
| 64 | + | return nil; |
|
| 65 | + | } |
|
| 66 | + | ||
| 67 | + | /// Get the value at a given index (0-based from head). |
|
| 68 | + | fn valueAt(list: *List, index: u32) -> ?i32 { |
|
| 69 | + | let mut cur = list.head; |
|
| 70 | + | let mut i: u32 = 0; |
|
| 71 | + | while let idx = cur { |
|
| 72 | + | if i == index { |
|
| 73 | + | return list.pool[idx].value; |
|
| 74 | + | } |
|
| 75 | + | cur = list.pool[idx].next; |
|
| 76 | + | i = i + 1; |
|
| 77 | + | } |
|
| 78 | + | return nil; |
|
| 79 | + | } |
|
| 80 | + | ||
| 81 | + | /// Reverse the linked list in-place. |
|
| 82 | + | fn reverse(list: *mut List) { |
|
| 83 | + | let mut prev: ?u32 = nil; |
|
| 84 | + | let mut cur = list.head; |
|
| 85 | + | while let idx = cur { |
|
| 86 | + | let next = list.pool[idx].next; |
|
| 87 | + | list.pool[idx].next = prev; |
|
| 88 | + | prev = idx; |
|
| 89 | + | cur = next; |
|
| 90 | + | } |
|
| 91 | + | list.head = prev; |
|
| 92 | + | } |
|
| 93 | + | ||
| 94 | + | /// Compute the sum of all values in the list. |
|
| 95 | + | fn sum(list: *List) -> i32 { |
|
| 96 | + | let mut total: i32 = 0; |
|
| 97 | + | let mut cur = list.head; |
|
| 98 | + | while let idx = cur { |
|
| 99 | + | total = total + list.pool[idx].value; |
|
| 100 | + | cur = list.pool[idx].next; |
|
| 101 | + | } |
|
| 102 | + | return total; |
|
| 103 | + | } |
|
| 104 | + | ||
| 105 | + | /// Reset the list to empty. |
|
| 106 | + | fn reset(list: *mut List) { |
|
| 107 | + | list.head = nil; |
|
| 108 | + | list.free = 0; |
|
| 109 | + | } |
|
| 110 | + | ||
| 111 | + | /// Test basic push and length. |
|
| 112 | + | fn testPushLength(list: *mut List) -> i32 { |
|
| 113 | + | push(list, 10); |
|
| 114 | + | push(list, 20); |
|
| 115 | + | push(list, 30); |
|
| 116 | + | ||
| 117 | + | if length(list) != 3 { |
|
| 118 | + | return 1; |
|
| 119 | + | } |
|
| 120 | + | // Head should be most recently pushed. |
|
| 121 | + | let v0 = valueAt(list, 0) else { |
|
| 122 | + | return 2; |
|
| 123 | + | }; |
|
| 124 | + | if v0 != 30 { |
|
| 125 | + | return 2; |
|
| 126 | + | } |
|
| 127 | + | let v1 = valueAt(list, 1) else { |
|
| 128 | + | return 3; |
|
| 129 | + | }; |
|
| 130 | + | if v1 != 20 { |
|
| 131 | + | return 3; |
|
| 132 | + | } |
|
| 133 | + | let v2 = valueAt(list, 2) else { |
|
| 134 | + | return 4; |
|
| 135 | + | }; |
|
| 136 | + | if v2 != 10 { |
|
| 137 | + | return 4; |
|
| 138 | + | } |
|
| 139 | + | return 0; |
|
| 140 | + | } |
|
| 141 | + | ||
| 142 | + | /// Test pop. |
|
| 143 | + | fn testPop(list: *mut List) -> i32 { |
|
| 144 | + | let v = pop(list) else { |
|
| 145 | + | return 1; |
|
| 146 | + | }; |
|
| 147 | + | if v != 30 { |
|
| 148 | + | return 1; |
|
| 149 | + | } |
|
| 150 | + | if length(list) != 2 { |
|
| 151 | + | return 2; |
|
| 152 | + | } |
|
| 153 | + | let v0 = valueAt(list, 0) else { |
|
| 154 | + | return 3; |
|
| 155 | + | }; |
|
| 156 | + | if v0 != 20 { |
|
| 157 | + | return 3; |
|
| 158 | + | } |
|
| 159 | + | return 0; |
|
| 160 | + | } |
|
| 161 | + | ||
| 162 | + | /// Test find. |
|
| 163 | + | fn testFind(list: *mut List) -> i32 { |
|
| 164 | + | // 20 should be found. |
|
| 165 | + | if find(list, 20) == nil { |
|
| 166 | + | return 1; |
|
| 167 | + | } |
|
| 168 | + | // 10 should be found. |
|
| 169 | + | if find(list, 10) == nil { |
|
| 170 | + | return 2; |
|
| 171 | + | } |
|
| 172 | + | // 30 was popped, should not be found. |
|
| 173 | + | if find(list, 30) != nil { |
|
| 174 | + | return 3; |
|
| 175 | + | } |
|
| 176 | + | // 99 never inserted. |
|
| 177 | + | if find(list, 99) != nil { |
|
| 178 | + | return 4; |
|
| 179 | + | } |
|
| 180 | + | return 0; |
|
| 181 | + | } |
|
| 182 | + | ||
| 183 | + | /// Test reverse. |
|
| 184 | + | fn testReverse(list: *mut List) -> i32 { |
|
| 185 | + | // Current list: 20 -> 10 |
|
| 186 | + | // Add more elements. |
|
| 187 | + | push(list, 40); |
|
| 188 | + | push(list, 50); |
|
| 189 | + | // List: 50 -> 40 -> 20 -> 10 |
|
| 190 | + | ||
| 191 | + | let sumBefore = sum(list); |
|
| 192 | + | ||
| 193 | + | reverse(list); |
|
| 194 | + | // List should be: 10 -> 20 -> 40 -> 50 |
|
| 195 | + | ||
| 196 | + | if length(list) != 4 { |
|
| 197 | + | return 1; |
|
| 198 | + | } |
|
| 199 | + | ||
| 200 | + | let v0 = valueAt(list, 0) else { |
|
| 201 | + | return 2; |
|
| 202 | + | }; |
|
| 203 | + | if v0 != 10 { |
|
| 204 | + | return 2; |
|
| 205 | + | } |
|
| 206 | + | let v1 = valueAt(list, 1) else { |
|
| 207 | + | return 3; |
|
| 208 | + | }; |
|
| 209 | + | if v1 != 20 { |
|
| 210 | + | return 3; |
|
| 211 | + | } |
|
| 212 | + | let v2 = valueAt(list, 2) else { |
|
| 213 | + | return 4; |
|
| 214 | + | }; |
|
| 215 | + | if v2 != 40 { |
|
| 216 | + | return 4; |
|
| 217 | + | } |
|
| 218 | + | let v3 = valueAt(list, 3) else { |
|
| 219 | + | return 5; |
|
| 220 | + | }; |
|
| 221 | + | if v3 != 50 { |
|
| 222 | + | return 5; |
|
| 223 | + | } |
|
| 224 | + | ||
| 225 | + | if sum(list) != sumBefore { |
|
| 226 | + | return 6; |
|
| 227 | + | } |
|
| 228 | + | return 0; |
|
| 229 | + | } |
|
| 230 | + | ||
| 231 | + | /// Test building a larger list. |
|
| 232 | + | fn testLargerList(list: *mut List) -> i32 { |
|
| 233 | + | reset(list); |
|
| 234 | + | ||
| 235 | + | // Push 32 elements. |
|
| 236 | + | let mut i: u32 = 0; |
|
| 237 | + | while i < 32 { |
|
| 238 | + | push(list, i as i32 * 3); |
|
| 239 | + | i = i + 1; |
|
| 240 | + | } |
|
| 241 | + | ||
| 242 | + | if length(list) != 32 { |
|
| 243 | + | return 1; |
|
| 244 | + | } |
|
| 245 | + | ||
| 246 | + | // Sum should be 3 * (0 + 1 + ... + 31) = 3 * 496 = 1488 |
|
| 247 | + | if sum(list) != 1488 { |
|
| 248 | + | return 2; |
|
| 249 | + | } |
|
| 250 | + | ||
| 251 | + | // Reverse and verify sum is preserved. |
|
| 252 | + | reverse(list); |
|
| 253 | + | if sum(list) != 1488 { |
|
| 254 | + | return 3; |
|
| 255 | + | } |
|
| 256 | + | ||
| 257 | + | // After reverse, head should be 0 (first pushed), tail should be 93 (last pushed). |
|
| 258 | + | let head = valueAt(list, 0) else { |
|
| 259 | + | return 4; |
|
| 260 | + | }; |
|
| 261 | + | if head != 0 { |
|
| 262 | + | return 4; |
|
| 263 | + | } |
|
| 264 | + | let tail = valueAt(list, 31) else { |
|
| 265 | + | return 5; |
|
| 266 | + | }; |
|
| 267 | + | if tail != 93 { |
|
| 268 | + | return 5; |
|
| 269 | + | } |
|
| 270 | + | ||
| 271 | + | // Pop all and verify decreasing original order. |
|
| 272 | + | let mut j: u32 = 0; |
|
| 273 | + | while j < 32 { |
|
| 274 | + | let v = pop(list) else { |
|
| 275 | + | return 6; |
|
| 276 | + | }; |
|
| 277 | + | let expected = j as i32 * 3; |
|
| 278 | + | if v != expected { |
|
| 279 | + | return 6; |
|
| 280 | + | } |
|
| 281 | + | j = j + 1; |
|
| 282 | + | } |
|
| 283 | + | ||
| 284 | + | if length(list) != 0 { |
|
| 285 | + | return 7; |
|
| 286 | + | } |
|
| 287 | + | ||
| 288 | + | // Pop from empty list should return nil. |
|
| 289 | + | if pop(list) != nil { |
|
| 290 | + | return 8; |
|
| 291 | + | } |
|
| 292 | + | return 0; |
|
| 293 | + | } |
|
| 294 | + | ||
| 295 | + | @default fn main() -> i32 { |
|
| 296 | + | let mut list: List = List { |
|
| 297 | + | pool: [Node { value: 0, next: nil }; 64], |
|
| 298 | + | free: 0, |
|
| 299 | + | head: nil, |
|
| 300 | + | }; |
|
| 301 | + | ||
| 302 | + | let r1 = testPushLength(&mut list); |
|
| 303 | + | if r1 != 0 { |
|
| 304 | + | return 10 + r1; |
|
| 305 | + | } |
|
| 306 | + | ||
| 307 | + | let r2 = testPop(&mut list); |
|
| 308 | + | if r2 != 0 { |
|
| 309 | + | return 20 + r2; |
|
| 310 | + | } |
|
| 311 | + | ||
| 312 | + | let r3 = testFind(&mut list); |
|
| 313 | + | if r3 != 0 { |
|
| 314 | + | return 30 + r3; |
|
| 315 | + | } |
|
| 316 | + | ||
| 317 | + | let r4 = testReverse(&mut list); |
|
| 318 | + | if r4 != 0 { |
|
| 319 | + | return 40 + r4; |
|
| 320 | + | } |
|
| 321 | + | ||
| 322 | + | let r5 = testLargerList(&mut list); |
|
| 323 | + | if r5 != 0 { |
|
| 324 | + | return 50 + r5; |
|
| 325 | + | } |
|
| 326 | + | return 0; |
|
| 327 | + | } |
lib/std/arch/rv64/tests/prog.lzw.rad
added
+307 -0
| 1 | + | //! LZW compression and decompression. |
|
| 2 | + | //! Implement the Lempel-Ziv-Welch algorithm with a fixed-size dictionary. |
|
| 3 | + | //! Encode a byte buffer, decode it, and verify perfect round-trip. |
|
| 4 | + | ||
| 5 | + | const MAX_DICT: u32 = 512; |
|
| 6 | + | const INIT_DICT: u32 = 258; |
|
| 7 | + | const CLEAR_CODE: u32 = 256; |
|
| 8 | + | const EOI_CODE: u32 = 257; |
|
| 9 | + | ||
| 10 | + | record DictEntry { |
|
| 11 | + | prefix: u32, |
|
| 12 | + | suffix: u8, |
|
| 13 | + | } |
|
| 14 | + | ||
| 15 | + | record LzwState { |
|
| 16 | + | encDict: *mut [DictEntry], |
|
| 17 | + | encDictSize: u32, |
|
| 18 | + | decDict: *mut [DictEntry], |
|
| 19 | + | decDictSize: u32, |
|
| 20 | + | encoded: *mut [u32], |
|
| 21 | + | encLen: u32, |
|
| 22 | + | decoded: *mut [u8], |
|
| 23 | + | decLen: u32, |
|
| 24 | + | temp: *mut [u8], |
|
| 25 | + | } |
|
| 26 | + | ||
| 27 | + | fn initEncDict(s: *mut LzwState) { |
|
| 28 | + | s.encDictSize = INIT_DICT; |
|
| 29 | + | let mut i: u32 = 0; |
|
| 30 | + | while i < 256 { |
|
| 31 | + | s.encDict[i] = DictEntry { prefix: 0xFFFF, suffix: i as u8 }; |
|
| 32 | + | i = i + 1; |
|
| 33 | + | } |
|
| 34 | + | } |
|
| 35 | + | ||
| 36 | + | fn initDecDict(s: *mut LzwState) { |
|
| 37 | + | s.decDictSize = INIT_DICT; |
|
| 38 | + | let mut i: u32 = 0; |
|
| 39 | + | while i < 256 { |
|
| 40 | + | s.decDict[i] = DictEntry { prefix: 0xFFFF, suffix: i as u8 }; |
|
| 41 | + | i = i + 1; |
|
| 42 | + | } |
|
| 43 | + | } |
|
| 44 | + | ||
| 45 | + | fn dictLookup(s: *LzwState, prefix: u32, suffix: u8) -> u32 { |
|
| 46 | + | let mut i: u32 = 0; |
|
| 47 | + | while i < s.encDictSize { |
|
| 48 | + | if s.encDict[i].prefix == prefix and s.encDict[i].suffix == suffix { |
|
| 49 | + | return i; |
|
| 50 | + | } |
|
| 51 | + | i = i + 1; |
|
| 52 | + | } |
|
| 53 | + | return 0xFFFFFFFF; |
|
| 54 | + | } |
|
| 55 | + | ||
| 56 | + | fn dictAdd(s: *mut LzwState, prefix: u32, suffix: u8) { |
|
| 57 | + | if s.encDictSize < MAX_DICT { |
|
| 58 | + | s.encDict[s.encDictSize] = DictEntry { prefix, suffix }; |
|
| 59 | + | s.encDictSize = s.encDictSize + 1; |
|
| 60 | + | } |
|
| 61 | + | } |
|
| 62 | + | ||
| 63 | + | fn emitCode(s: *mut LzwState, code: u32) { |
|
| 64 | + | s.encoded[s.encLen] = code; |
|
| 65 | + | s.encLen = s.encLen + 1; |
|
| 66 | + | } |
|
| 67 | + | ||
| 68 | + | fn encode(s: *mut LzwState, data: *[u8]) { |
|
| 69 | + | initEncDict(s); |
|
| 70 | + | s.encLen = 0; |
|
| 71 | + | ||
| 72 | + | if data.len == 0 { |
|
| 73 | + | emitCode(s, EOI_CODE); |
|
| 74 | + | return; |
|
| 75 | + | } |
|
| 76 | + | ||
| 77 | + | let mut w: u32 = data[0] as u32; |
|
| 78 | + | let mut i: u32 = 1; |
|
| 79 | + | ||
| 80 | + | while i < data.len { |
|
| 81 | + | let c: u8 = data[i]; |
|
| 82 | + | let wc: u32 = dictLookup(s, w, c); |
|
| 83 | + | if wc != 0xFFFFFFFF { |
|
| 84 | + | w = wc; |
|
| 85 | + | } else { |
|
| 86 | + | emitCode(s, w); |
|
| 87 | + | dictAdd(s, w, c); |
|
| 88 | + | w = c as u32; |
|
| 89 | + | } |
|
| 90 | + | i = i + 1; |
|
| 91 | + | } |
|
| 92 | + | emitCode(s, w); |
|
| 93 | + | emitCode(s, EOI_CODE); |
|
| 94 | + | } |
|
| 95 | + | ||
| 96 | + | fn decodeString(s: *mut LzwState, code: u32) -> u32 { |
|
| 97 | + | let mut len: u32 = 0; |
|
| 98 | + | let mut c: u32 = code; |
|
| 99 | + | while c != 0xFFFF and c < s.decDictSize { |
|
| 100 | + | s.temp[len] = s.decDict[c].suffix; |
|
| 101 | + | len = len + 1; |
|
| 102 | + | c = s.decDict[c].prefix; |
|
| 103 | + | } |
|
| 104 | + | let mut a: u32 = 0; |
|
| 105 | + | let mut b: u32 = len - 1; |
|
| 106 | + | while a < b { |
|
| 107 | + | let tmp: u8 = s.temp[a]; |
|
| 108 | + | s.temp[a] = s.temp[b]; |
|
| 109 | + | s.temp[b] = tmp; |
|
| 110 | + | a = a + 1; |
|
| 111 | + | b = b - 1; |
|
| 112 | + | } |
|
| 113 | + | return len; |
|
| 114 | + | } |
|
| 115 | + | ||
| 116 | + | fn firstByte(s: *LzwState, code: u32) -> u8 { |
|
| 117 | + | let mut c: u32 = code; |
|
| 118 | + | while s.decDict[c].prefix != 0xFFFF and s.decDict[c].prefix < s.decDictSize { |
|
| 119 | + | c = s.decDict[c].prefix; |
|
| 120 | + | } |
|
| 121 | + | return s.decDict[c].suffix; |
|
| 122 | + | } |
|
| 123 | + | ||
| 124 | + | fn decDictAdd(s: *mut LzwState, prefix: u32, suffix: u8) { |
|
| 125 | + | if s.decDictSize < MAX_DICT { |
|
| 126 | + | s.decDict[s.decDictSize] = DictEntry { prefix, suffix }; |
|
| 127 | + | s.decDictSize = s.decDictSize + 1; |
|
| 128 | + | } |
|
| 129 | + | } |
|
| 130 | + | ||
| 131 | + | fn outputByte(s: *mut LzwState, b: u8) { |
|
| 132 | + | s.decoded[s.decLen] = b; |
|
| 133 | + | s.decLen = s.decLen + 1; |
|
| 134 | + | } |
|
| 135 | + | ||
| 136 | + | fn decode(s: *mut LzwState) { |
|
| 137 | + | initDecDict(s); |
|
| 138 | + | s.decLen = 0; |
|
| 139 | + | ||
| 140 | + | if s.encLen == 0 { |
|
| 141 | + | return; |
|
| 142 | + | } |
|
| 143 | + | ||
| 144 | + | let mut pos: u32 = 0; |
|
| 145 | + | let mut code: u32 = s.encoded[pos]; |
|
| 146 | + | pos = pos + 1; |
|
| 147 | + | ||
| 148 | + | if code == EOI_CODE { |
|
| 149 | + | return; |
|
| 150 | + | } |
|
| 151 | + | ||
| 152 | + | outputByte(s, code as u8); |
|
| 153 | + | let mut prevCode: u32 = code; |
|
| 154 | + | ||
| 155 | + | while pos < s.encLen { |
|
| 156 | + | code = s.encoded[pos]; |
|
| 157 | + | pos = pos + 1; |
|
| 158 | + | ||
| 159 | + | if code == EOI_CODE { |
|
| 160 | + | return; |
|
| 161 | + | } |
|
| 162 | + | ||
| 163 | + | if code < s.decDictSize { |
|
| 164 | + | let len: u32 = decodeString(s, code); |
|
| 165 | + | let mut i: u32 = 0; |
|
| 166 | + | while i < len { |
|
| 167 | + | outputByte(s, s.temp[i]); |
|
| 168 | + | i = i + 1; |
|
| 169 | + | } |
|
| 170 | + | decDictAdd(s, prevCode, s.temp[0]); |
|
| 171 | + | } else { |
|
| 172 | + | let fb: u8 = firstByte(s, prevCode); |
|
| 173 | + | let len: u32 = decodeString(s, prevCode); |
|
| 174 | + | let mut i: u32 = 0; |
|
| 175 | + | while i < len { |
|
| 176 | + | outputByte(s, s.temp[i]); |
|
| 177 | + | i = i + 1; |
|
| 178 | + | } |
|
| 179 | + | outputByte(s, fb); |
|
| 180 | + | decDictAdd(s, prevCode, fb); |
|
| 181 | + | } |
|
| 182 | + | prevCode = code; |
|
| 183 | + | } |
|
| 184 | + | } |
|
| 185 | + | ||
| 186 | + | fn testSimple(s: *mut LzwState) -> i32 { |
|
| 187 | + | let data: *[u8] = "ABABABABAB"; |
|
| 188 | + | encode(s, data); |
|
| 189 | + | ||
| 190 | + | if s.encoded[s.encLen - 1] != EOI_CODE { return 1; } |
|
| 191 | + | ||
| 192 | + | decode(s); |
|
| 193 | + | if s.decLen != 10 { return 2; } |
|
| 194 | + | let mut i: u32 = 0; |
|
| 195 | + | while i < 10 { |
|
| 196 | + | if s.decoded[i] != data[i] { return 3; } |
|
| 197 | + | i = i + 1; |
|
| 198 | + | } |
|
| 199 | + | return 0; |
|
| 200 | + | } |
|
| 201 | + | ||
| 202 | + | fn testDistinct(s: *mut LzwState) -> i32 { |
|
| 203 | + | let data: [u8; 16] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; |
|
| 204 | + | encode(s, &data[..]); |
|
| 205 | + | ||
| 206 | + | decode(s); |
|
| 207 | + | if s.decLen != 16 { return 1; } |
|
| 208 | + | let mut i: u32 = 0; |
|
| 209 | + | while i < 16 { |
|
| 210 | + | if s.decoded[i] != data[i] { return 2; } |
|
| 211 | + | i = i + 1; |
|
| 212 | + | } |
|
| 213 | + | return 0; |
|
| 214 | + | } |
|
| 215 | + | ||
| 216 | + | fn testRepetitive(s: *mut LzwState) -> i32 { |
|
| 217 | + | let mut data: [u8; 64] = [65; 64]; |
|
| 218 | + | encode(s, &data[..]); |
|
| 219 | + | ||
| 220 | + | if s.encLen >= 32 { return 1; } |
|
| 221 | + | ||
| 222 | + | decode(s); |
|
| 223 | + | if s.decLen != 64 { return 2; } |
|
| 224 | + | let mut i: u32 = 0; |
|
| 225 | + | while i < 64 { |
|
| 226 | + | if s.decoded[i] != 65 { return 3; } |
|
| 227 | + | i = i + 1; |
|
| 228 | + | } |
|
| 229 | + | return 0; |
|
| 230 | + | } |
|
| 231 | + | ||
| 232 | + | fn testMixed(s: *mut LzwState) -> i32 { |
|
| 233 | + | let data: *[u8] = "TOBEORNOTTOBEORTOBEORNOT"; |
|
| 234 | + | encode(s, data); |
|
| 235 | + | ||
| 236 | + | decode(s); |
|
| 237 | + | if s.decLen != 24 { return 1; } |
|
| 238 | + | let mut i: u32 = 0; |
|
| 239 | + | while i < 24 { |
|
| 240 | + | if s.decoded[i] != data[i] { return 2; } |
|
| 241 | + | i = i + 1; |
|
| 242 | + | } |
|
| 243 | + | ||
| 244 | + | if s.encLen - 1 >= 24 { return 3; } |
|
| 245 | + | ||
| 246 | + | return 0; |
|
| 247 | + | } |
|
| 248 | + | ||
| 249 | + | fn testEmpty(s: *mut LzwState) -> i32 { |
|
| 250 | + | encode(s, &[]); |
|
| 251 | + | if s.encLen != 1 { return 1; } |
|
| 252 | + | if s.encoded[0] != EOI_CODE { return 2; } |
|
| 253 | + | ||
| 254 | + | decode(s); |
|
| 255 | + | if s.decLen != 0 { return 3; } |
|
| 256 | + | return 0; |
|
| 257 | + | } |
|
| 258 | + | ||
| 259 | + | fn testSingle(s: *mut LzwState) -> i32 { |
|
| 260 | + | let data: *[u8] = "*"; |
|
| 261 | + | encode(s, data); |
|
| 262 | + | ||
| 263 | + | decode(s); |
|
| 264 | + | if s.decLen != 1 { return 1; } |
|
| 265 | + | if s.decoded[0] != 42 { return 2; } |
|
| 266 | + | return 0; |
|
| 267 | + | } |
|
| 268 | + | ||
| 269 | + | @default fn main() -> i32 { |
|
| 270 | + | let mut encDict: [DictEntry; 512] = [DictEntry { prefix: 0xFFFF, suffix: 0 }; 512]; |
|
| 271 | + | let mut decDict: [DictEntry; 512] = [DictEntry { prefix: 0xFFFF, suffix: 0 }; 512]; |
|
| 272 | + | let mut encoded: [u32; 512] = [0; 512]; |
|
| 273 | + | let mut decoded: [u8; 256] = [0; 256]; |
|
| 274 | + | let mut temp: [u8; 256] = [0; 256]; |
|
| 275 | + | ||
| 276 | + | let mut s: LzwState = LzwState { |
|
| 277 | + | encDict: &mut encDict[..], |
|
| 278 | + | encDictSize: 0, |
|
| 279 | + | decDict: &mut decDict[..], |
|
| 280 | + | decDictSize: 0, |
|
| 281 | + | encoded: &mut encoded[..], |
|
| 282 | + | encLen: 0, |
|
| 283 | + | decoded: &mut decoded[..], |
|
| 284 | + | decLen: 0, |
|
| 285 | + | temp: &mut temp[..], |
|
| 286 | + | }; |
|
| 287 | + | ||
| 288 | + | let r1: i32 = testSimple(&mut s); |
|
| 289 | + | if r1 != 0 { return 10 + r1; } |
|
| 290 | + | ||
| 291 | + | let r2: i32 = testDistinct(&mut s); |
|
| 292 | + | if r2 != 0 { return 20 + r2; } |
|
| 293 | + | ||
| 294 | + | let r3: i32 = testRepetitive(&mut s); |
|
| 295 | + | if r3 != 0 { return 30 + r3; } |
|
| 296 | + | ||
| 297 | + | let r4: i32 = testMixed(&mut s); |
|
| 298 | + | if r4 != 0 { return 40 + r4; } |
|
| 299 | + | ||
| 300 | + | let r5: i32 = testEmpty(&mut s); |
|
| 301 | + | if r5 != 0 { return 50 + r5; } |
|
| 302 | + | ||
| 303 | + | let r6: i32 = testSingle(&mut s); |
|
| 304 | + | if r6 != 0 { return 60 + r6; } |
|
| 305 | + | ||
| 306 | + | return 0; |
|
| 307 | + | } |
lib/std/arch/rv64/tests/prog.matmul.rad
added
+127 -0
| 1 | + | //! Matrix multiplication. |
|
| 2 | + | //! Multiply two 4x4 integer matrices and verify the result against |
|
| 3 | + | //! a known expected output. Classic benchmark kernel. |
|
| 4 | + | ||
| 5 | + | const N: u32 = 4; |
|
| 6 | + | ||
| 7 | + | /// A 4x4 integer matrix (row-major). |
|
| 8 | + | record Mat4 { |
|
| 9 | + | rows: [[i32; 4]; 4], |
|
| 10 | + | } |
|
| 11 | + | ||
| 12 | + | /// Perform matrix multiplication: c = a * b. |
|
| 13 | + | fn matmul(c: *mut Mat4, a: *Mat4, b: *Mat4) { |
|
| 14 | + | let mut i: u32 = 0; |
|
| 15 | + | while i < N { |
|
| 16 | + | let mut j: u32 = 0; |
|
| 17 | + | while j < N { |
|
| 18 | + | let mut sum: i32 = 0; |
|
| 19 | + | let mut k: u32 = 0; |
|
| 20 | + | while k < N { |
|
| 21 | + | sum = sum + a.rows[i][k] * b.rows[k][j]; |
|
| 22 | + | k = k + 1; |
|
| 23 | + | } |
|
| 24 | + | c.rows[i][j] = sum; |
|
| 25 | + | j = j + 1; |
|
| 26 | + | } |
|
| 27 | + | i = i + 1; |
|
| 28 | + | } |
|
| 29 | + | } |
|
| 30 | + | ||
| 31 | + | /// Verify c matches expected. |
|
| 32 | + | fn verifyResult(c: *Mat4, expected: *Mat4) -> i32 { |
|
| 33 | + | let mut i: u32 = 0; |
|
| 34 | + | while i < N { |
|
| 35 | + | let mut j: u32 = 0; |
|
| 36 | + | while j < N { |
|
| 37 | + | if c.rows[i][j] != expected.rows[i][j] { |
|
| 38 | + | return (i * N + j) as i32 + 1; |
|
| 39 | + | } |
|
| 40 | + | j = j + 1; |
|
| 41 | + | } |
|
| 42 | + | i = i + 1; |
|
| 43 | + | } |
|
| 44 | + | return 0; |
|
| 45 | + | } |
|
| 46 | + | ||
| 47 | + | /// Compute the trace (sum of diagonal) of a matrix. |
|
| 48 | + | fn trace(m: *Mat4) -> i32 { |
|
| 49 | + | let mut sum: i32 = 0; |
|
| 50 | + | let mut i: u32 = 0; |
|
| 51 | + | while i < N { |
|
| 52 | + | sum = sum + m.rows[i][i]; |
|
| 53 | + | i = i + 1; |
|
| 54 | + | } |
|
| 55 | + | return sum; |
|
| 56 | + | } |
|
| 57 | + | ||
| 58 | + | /// Zero out a matrix. |
|
| 59 | + | fn zero(m: *mut Mat4) { |
|
| 60 | + | let mut i: u32 = 0; |
|
| 61 | + | while i < N { |
|
| 62 | + | let mut j: u32 = 0; |
|
| 63 | + | while j < N { |
|
| 64 | + | m.rows[i][j] = 0; |
|
| 65 | + | j = j + 1; |
|
| 66 | + | } |
|
| 67 | + | i = i + 1; |
|
| 68 | + | } |
|
| 69 | + | } |
|
| 70 | + | ||
| 71 | + | /// Multiply matrices multiple times to test repeated computation. |
|
| 72 | + | fn testRepeatedMultiply(c: *mut Mat4, a: *Mat4, b: *Mat4) -> i32 { |
|
| 73 | + | // First pass already done, trace = 13+43+43+85 = 184. |
|
| 74 | + | if trace(c) != 184 { |
|
| 75 | + | return 1; |
|
| 76 | + | } |
|
| 77 | + | ||
| 78 | + | // Zero out c and re-multiply to ensure idempotent. |
|
| 79 | + | zero(c); |
|
| 80 | + | matmul(c, a, b); |
|
| 81 | + | if trace(c) != 184 { |
|
| 82 | + | return 2; |
|
| 83 | + | } |
|
| 84 | + | return 0; |
|
| 85 | + | } |
|
| 86 | + | ||
| 87 | + | @default fn main() -> i32 { |
|
| 88 | + | let a = Mat4 { rows: [ |
|
| 89 | + | [ 1, 2, 3, 4], |
|
| 90 | + | [ 5, 6, 7, 8], |
|
| 91 | + | [ 9, 10, 11, 12], |
|
| 92 | + | [13, 14, 15, 16] |
|
| 93 | + | ] }; |
|
| 94 | + | ||
| 95 | + | let b = Mat4 { rows: [ |
|
| 96 | + | [ 2, 0, 1, 3], |
|
| 97 | + | [ 1, 2, 0, 1], |
|
| 98 | + | [ 3, 1, 2, 0], |
|
| 99 | + | [ 0, 3, 1, 2] |
|
| 100 | + | ] }; |
|
| 101 | + | ||
| 102 | + | // Expected result of a * b: |
|
| 103 | + | // [0] = [1*2+2*1+3*3+4*0, 1*0+2*2+3*1+4*3, 1*1+2*0+3*2+4*1, 1*3+2*1+3*0+4*2] = [13, 19, 11, 13] |
|
| 104 | + | // [1] = [5*2+6*1+7*3+8*0, 5*0+6*2+7*1+8*3, 5*1+6*0+7*2+8*1, 5*3+6*1+7*0+8*2] = [37, 43, 27, 37] |
|
| 105 | + | // [2] = [9*2+10*1+11*3+12*0, ...] = [61, 67, 43, 61] |
|
| 106 | + | // [3] = [13*2+14*1+15*3+16*0, ...] = [85, 91, 59, 85] |
|
| 107 | + | let expected = Mat4 { rows: [ |
|
| 108 | + | [13, 19, 11, 13], |
|
| 109 | + | [37, 43, 27, 37], |
|
| 110 | + | [61, 67, 43, 61], |
|
| 111 | + | [85, 91, 59, 85] |
|
| 112 | + | ] }; |
|
| 113 | + | ||
| 114 | + | let mut c = Mat4 { rows: [[0; 4]; 4] }; |
|
| 115 | + | matmul(&mut c, &a, &b); |
|
| 116 | + | ||
| 117 | + | let r1 = verifyResult(&c, &expected); |
|
| 118 | + | if r1 != 0 { |
|
| 119 | + | return 10 + r1; |
|
| 120 | + | } |
|
| 121 | + | ||
| 122 | + | let r2 = testRepeatedMultiply(&mut c, &a, &b); |
|
| 123 | + | if r2 != 0 { |
|
| 124 | + | return 30 + r2; |
|
| 125 | + | } |
|
| 126 | + | return 0; |
|
| 127 | + | } |
lib/std/arch/rv64/tests/prog.mersenne.rad
added
+269 -0
| 1 | + | //! Mersenne Twister PRNG with statistical testing. |
|
| 2 | + | ||
| 3 | + | const N: u32 = 624; |
|
| 4 | + | const M: u32 = 397; |
|
| 5 | + | const MATRIX_A: u32 = 0x9908B0DF; |
|
| 6 | + | const UPPER_MASK: u32 = 0x80000000; |
|
| 7 | + | const LOWER_MASK: u32 = 0x7FFFFFFF; |
|
| 8 | + | ||
| 9 | + | record MtState { |
|
| 10 | + | mt: *mut [u32], |
|
| 11 | + | mti: u32, |
|
| 12 | + | } |
|
| 13 | + | ||
| 14 | + | fn mtInit(s: *mut MtState, seed: u32) { |
|
| 15 | + | s.mt[0] = seed; |
|
| 16 | + | let mut i: u32 = 1; |
|
| 17 | + | while i < N { |
|
| 18 | + | let prev: u32 = s.mt[i - 1]; |
|
| 19 | + | let xored: u32 = prev ^ (prev >> 30); |
|
| 20 | + | ||
| 21 | + | let lo: u32 = xored & 0xFFFF; |
|
| 22 | + | let hi: u32 = xored >> 16; |
|
| 23 | + | let c: u32 = 1812433253; |
|
| 24 | + | let clo: u32 = c & 0xFFFF; |
|
| 25 | + | let chi: u32 = c >> 16; |
|
| 26 | + | ||
| 27 | + | let ll: u32 = clo * lo; |
|
| 28 | + | let lh: u32 = clo * hi; |
|
| 29 | + | let hl: u32 = chi * lo; |
|
| 30 | + | ||
| 31 | + | let result: u32 = ll + ((lh + hl) << 16) + i; |
|
| 32 | + | s.mt[i] = result; |
|
| 33 | + | i = i + 1; |
|
| 34 | + | } |
|
| 35 | + | s.mti = N; |
|
| 36 | + | } |
|
| 37 | + | ||
| 38 | + | fn generateNumbers(s: *mut MtState) { |
|
| 39 | + | let mut i: u32 = 0; |
|
| 40 | + | ||
| 41 | + | while i < N - M { |
|
| 42 | + | let y: u32 = (s.mt[i] & UPPER_MASK) | (s.mt[i + 1] & LOWER_MASK); |
|
| 43 | + | let mut mag: u32 = 0; |
|
| 44 | + | if y & 1 == 1 { |
|
| 45 | + | mag = MATRIX_A; |
|
| 46 | + | } |
|
| 47 | + | s.mt[i] = s.mt[i + M] ^ (y >> 1) ^ mag; |
|
| 48 | + | i = i + 1; |
|
| 49 | + | } |
|
| 50 | + | ||
| 51 | + | while i < N - 1 { |
|
| 52 | + | let y: u32 = (s.mt[i] & UPPER_MASK) | (s.mt[i + 1] & LOWER_MASK); |
|
| 53 | + | let mut mag: u32 = 0; |
|
| 54 | + | if y & 1 == 1 { |
|
| 55 | + | mag = MATRIX_A; |
|
| 56 | + | } |
|
| 57 | + | s.mt[i] = s.mt[i + M - N] ^ (y >> 1) ^ mag; |
|
| 58 | + | i = i + 1; |
|
| 59 | + | } |
|
| 60 | + | ||
| 61 | + | let y: u32 = (s.mt[N - 1] & UPPER_MASK) | (s.mt[0] & LOWER_MASK); |
|
| 62 | + | let mut mag: u32 = 0; |
|
| 63 | + | if y & 1 == 1 { |
|
| 64 | + | mag = MATRIX_A; |
|
| 65 | + | } |
|
| 66 | + | s.mt[N - 1] = s.mt[M - 1] ^ (y >> 1) ^ mag; |
|
| 67 | + | ||
| 68 | + | s.mti = 0; |
|
| 69 | + | } |
|
| 70 | + | ||
| 71 | + | fn mtNext(s: *mut MtState) -> u32 { |
|
| 72 | + | if s.mti >= N { |
|
| 73 | + | generateNumbers(s); |
|
| 74 | + | } |
|
| 75 | + | ||
| 76 | + | let mut y: u32 = s.mt[s.mti]; |
|
| 77 | + | s.mti = s.mti + 1; |
|
| 78 | + | ||
| 79 | + | y = y ^ (y >> 11); |
|
| 80 | + | y = y ^ ((y << 7) & 0x9D2C5680); |
|
| 81 | + | y = y ^ ((y << 15) & 0xEFC60000); |
|
| 82 | + | y = y ^ (y >> 18); |
|
| 83 | + | ||
| 84 | + | return y; |
|
| 85 | + | } |
|
| 86 | + | ||
| 87 | + | fn testKnownSequence(s: *mut MtState) -> i32 { |
|
| 88 | + | mtInit(s, 1); |
|
| 89 | + | ||
| 90 | + | let v0: u32 = mtNext(s); |
|
| 91 | + | if v0 != 1791095845 { return 1; } |
|
| 92 | + | ||
| 93 | + | let v1: u32 = mtNext(s); |
|
| 94 | + | if v1 != 4282876139 { return 2; } |
|
| 95 | + | ||
| 96 | + | let v2: u32 = mtNext(s); |
|
| 97 | + | if v2 != 3093770124 { return 3; } |
|
| 98 | + | ||
| 99 | + | let v3: u32 = mtNext(s); |
|
| 100 | + | if v3 != 4005303368 { return 4; } |
|
| 101 | + | ||
| 102 | + | let v4: u32 = mtNext(s); |
|
| 103 | + | if v4 != 491263 { return 5; } |
|
| 104 | + | ||
| 105 | + | return 0; |
|
| 106 | + | } |
|
| 107 | + | ||
| 108 | + | fn testDeterminism(s: *mut MtState) -> i32 { |
|
| 109 | + | mtInit(s, 42); |
|
| 110 | + | ||
| 111 | + | let mut first: [u32; 10] = [0; 10]; |
|
| 112 | + | let mut i: u32 = 0; |
|
| 113 | + | while i < 10 { |
|
| 114 | + | first[i] = mtNext(s); |
|
| 115 | + | i = i + 1; |
|
| 116 | + | } |
|
| 117 | + | ||
| 118 | + | mtInit(s, 42); |
|
| 119 | + | ||
| 120 | + | i = 0; |
|
| 121 | + | while i < 10 { |
|
| 122 | + | let v: u32 = mtNext(s); |
|
| 123 | + | if v != first[i] { return i as i32 + 1; } |
|
| 124 | + | i = i + 1; |
|
| 125 | + | } |
|
| 126 | + | ||
| 127 | + | return 0; |
|
| 128 | + | } |
|
| 129 | + | ||
| 130 | + | fn testDifferentSeeds(s: *mut MtState) -> i32 { |
|
| 131 | + | mtInit(s, 1); |
|
| 132 | + | let a: u32 = mtNext(s); |
|
| 133 | + | ||
| 134 | + | mtInit(s, 2); |
|
| 135 | + | let b: u32 = mtNext(s); |
|
| 136 | + | ||
| 137 | + | mtInit(s, 3); |
|
| 138 | + | let c: u32 = mtNext(s); |
|
| 139 | + | ||
| 140 | + | if a == b { return 1; } |
|
| 141 | + | if b == c { return 2; } |
|
| 142 | + | if a == c { return 3; } |
|
| 143 | + | ||
| 144 | + | return 0; |
|
| 145 | + | } |
|
| 146 | + | ||
| 147 | + | fn testChiSquared(s: *mut MtState) -> i32 { |
|
| 148 | + | mtInit(s, 12345); |
|
| 149 | + | ||
| 150 | + | const NUM_BINS: u32 = 16; |
|
| 151 | + | const NUM_SAMPLES: u32 = 1600; |
|
| 152 | + | const EXPECTED: u32 = 100; |
|
| 153 | + | ||
| 154 | + | let mut bins: [u32; 16] = [0; 16]; |
|
| 155 | + | ||
| 156 | + | let mut i: u32 = 0; |
|
| 157 | + | while i < NUM_SAMPLES { |
|
| 158 | + | let val: u32 = mtNext(s); |
|
| 159 | + | let bin: u32 = val >> 28; |
|
| 160 | + | bins[bin] = bins[bin] + 1; |
|
| 161 | + | i = i + 1; |
|
| 162 | + | } |
|
| 163 | + | ||
| 164 | + | let mut chi2Scaled: u32 = 0; |
|
| 165 | + | let mut b: u32 = 0; |
|
| 166 | + | while b < NUM_BINS { |
|
| 167 | + | let obs: i32 = bins[b] as i32; |
|
| 168 | + | let exp: i32 = EXPECTED as i32; |
|
| 169 | + | let diff: i32 = obs - exp; |
|
| 170 | + | chi2Scaled = chi2Scaled + (diff * diff) as u32; |
|
| 171 | + | b = b + 1; |
|
| 172 | + | } |
|
| 173 | + | ||
| 174 | + | if chi2Scaled > 5000 { return 1; } |
|
| 175 | + | ||
| 176 | + | b = 0; |
|
| 177 | + | while b < NUM_BINS { |
|
| 178 | + | if bins[b] == 0 { return 2; } |
|
| 179 | + | b = b + 1; |
|
| 180 | + | } |
|
| 181 | + | ||
| 182 | + | b = 0; |
|
| 183 | + | while b < NUM_BINS { |
|
| 184 | + | if bins[b] > NUM_SAMPLES / 2 { return 3; } |
|
| 185 | + | b = b + 1; |
|
| 186 | + | } |
|
| 187 | + | ||
| 188 | + | return 0; |
|
| 189 | + | } |
|
| 190 | + | ||
| 191 | + | fn testRegeneration(s: *mut MtState) -> i32 { |
|
| 192 | + | mtInit(s, 7); |
|
| 193 | + | ||
| 194 | + | let mut last: u32 = 0; |
|
| 195 | + | let mut i: u32 = 0; |
|
| 196 | + | while i < 700 { |
|
| 197 | + | last = mtNext(s); |
|
| 198 | + | i = i + 1; |
|
| 199 | + | } |
|
| 200 | + | ||
| 201 | + | mtInit(s, 7); |
|
| 202 | + | let mut last2: u32 = 0; |
|
| 203 | + | i = 0; |
|
| 204 | + | while i < 700 { |
|
| 205 | + | last2 = mtNext(s); |
|
| 206 | + | i = i + 1; |
|
| 207 | + | } |
|
| 208 | + | ||
| 209 | + | if last != last2 { return 1; } |
|
| 210 | + | ||
| 211 | + | let mut more: u32 = 0; |
|
| 212 | + | i = 0; |
|
| 213 | + | while i < 700 { |
|
| 214 | + | more = mtNext(s); |
|
| 215 | + | i = i + 1; |
|
| 216 | + | } |
|
| 217 | + | if more == 0 { return 2; } |
|
| 218 | + | ||
| 219 | + | return 0; |
|
| 220 | + | } |
|
| 221 | + | ||
| 222 | + | fn testBitCoverage(s: *mut MtState) -> i32 { |
|
| 223 | + | mtInit(s, 999); |
|
| 224 | + | ||
| 225 | + | let mut orAll: u32 = 0; |
|
| 226 | + | let mut andAll: u32 = 0xFFFFFFFF; |
|
| 227 | + | ||
| 228 | + | let mut i: u32 = 0; |
|
| 229 | + | while i < 200 { |
|
| 230 | + | let v: u32 = mtNext(s); |
|
| 231 | + | orAll = orAll | v; |
|
| 232 | + | andAll = andAll & v; |
|
| 233 | + | i = i + 1; |
|
| 234 | + | } |
|
| 235 | + | ||
| 236 | + | if orAll != 0xFFFFFFFF { return 1; } |
|
| 237 | + | if andAll == 0xFFFFFFFF { return 2; } |
|
| 238 | + | if andAll != 0 { return 3; } |
|
| 239 | + | ||
| 240 | + | return 0; |
|
| 241 | + | } |
|
| 242 | + | ||
| 243 | + | @default fn main() -> i32 { |
|
| 244 | + | let mut mt: [u32; 624] = [0; 624]; |
|
| 245 | + | let mut s: MtState = MtState { |
|
| 246 | + | mt: &mut mt[..], |
|
| 247 | + | mti: 625, |
|
| 248 | + | }; |
|
| 249 | + | ||
| 250 | + | let r1: i32 = testKnownSequence(&mut s); |
|
| 251 | + | if r1 != 0 { return 10 + r1; } |
|
| 252 | + | ||
| 253 | + | let r2: i32 = testDeterminism(&mut s); |
|
| 254 | + | if r2 != 0 { return 20 + r2; } |
|
| 255 | + | ||
| 256 | + | let r3: i32 = testDifferentSeeds(&mut s); |
|
| 257 | + | if r3 != 0 { return 30 + r3; } |
|
| 258 | + | ||
| 259 | + | let r4: i32 = testChiSquared(&mut s); |
|
| 260 | + | if r4 != 0 { return 40 + r4; } |
|
| 261 | + | ||
| 262 | + | let r5: i32 = testRegeneration(&mut s); |
|
| 263 | + | if r5 != 0 { return 50 + r5; } |
|
| 264 | + | ||
| 265 | + | let r6: i32 = testBitCoverage(&mut s); |
|
| 266 | + | if r6 != 0 { return 60 + r6; } |
|
| 267 | + | ||
| 268 | + | return 0; |
|
| 269 | + | } |
lib/std/arch/rv64/tests/prog.nqueens.rad
added
+158 -0
| 1 | + | //! N-Queens solver. |
|
| 2 | + | //! Solve the N-Queens problem using backtracking. Count all valid placements |
|
| 3 | + | //! for boards of size 1 through 8 and verify against known solution counts. |
|
| 4 | + | ||
| 5 | + | const MAX_N: u32 = 8; |
|
| 6 | + | ||
| 7 | + | record Board { |
|
| 8 | + | queens: *mut [i32], |
|
| 9 | + | solutionCount: u32, |
|
| 10 | + | boardSize: u32, |
|
| 11 | + | } |
|
| 12 | + | ||
| 13 | + | fn isSafe(b: *Board, row: u32, col: u32) -> bool { |
|
| 14 | + | let mut i: u32 = 0; |
|
| 15 | + | while i < row { |
|
| 16 | + | let qcol: i32 = b.queens[i]; |
|
| 17 | + | if qcol == col as i32 { |
|
| 18 | + | return false; |
|
| 19 | + | } |
|
| 20 | + | let rowDiff: i32 = row as i32 - i as i32; |
|
| 21 | + | let colDiff: i32 = col as i32 - qcol; |
|
| 22 | + | if colDiff == rowDiff or colDiff == 0 - rowDiff { |
|
| 23 | + | return false; |
|
| 24 | + | } |
|
| 25 | + | i = i + 1; |
|
| 26 | + | } |
|
| 27 | + | return true; |
|
| 28 | + | } |
|
| 29 | + | ||
| 30 | + | fn solve(b: *mut Board, row: u32) { |
|
| 31 | + | if row == b.boardSize { |
|
| 32 | + | b.solutionCount = b.solutionCount + 1; |
|
| 33 | + | return; |
|
| 34 | + | } |
|
| 35 | + | let mut col: u32 = 0; |
|
| 36 | + | while col < b.boardSize { |
|
| 37 | + | if isSafe(b, row, col) { |
|
| 38 | + | b.queens[row] = col as i32; |
|
| 39 | + | solve(b, row + 1); |
|
| 40 | + | b.queens[row] = -1; |
|
| 41 | + | } |
|
| 42 | + | col = col + 1; |
|
| 43 | + | } |
|
| 44 | + | } |
|
| 45 | + | ||
| 46 | + | fn resetBoard(b: *mut Board) { |
|
| 47 | + | let mut i: u32 = 0; |
|
| 48 | + | while i < MAX_N { |
|
| 49 | + | b.queens[i] = -1; |
|
| 50 | + | i = i + 1; |
|
| 51 | + | } |
|
| 52 | + | b.solutionCount = 0; |
|
| 53 | + | } |
|
| 54 | + | ||
| 55 | + | fn solveNQueens(b: *mut Board, n: u32) -> u32 { |
|
| 56 | + | resetBoard(b); |
|
| 57 | + | b.boardSize = n; |
|
| 58 | + | solve(b, 0); |
|
| 59 | + | return b.solutionCount; |
|
| 60 | + | } |
|
| 61 | + | ||
| 62 | + | fn testAllSizes(b: *mut Board) -> i32 { |
|
| 63 | + | let expected: [u32; 9] = [0, 1, 0, 0, 2, 10, 4, 40, 92]; |
|
| 64 | + | let mut n: u32 = 1; |
|
| 65 | + | while n <= 8 { |
|
| 66 | + | let count: u32 = solveNQueens(b, n); |
|
| 67 | + | if count != expected[n] { |
|
| 68 | + | return n as i32; |
|
| 69 | + | } |
|
| 70 | + | n = n + 1; |
|
| 71 | + | } |
|
| 72 | + | return 0; |
|
| 73 | + | } |
|
| 74 | + | ||
| 75 | + | fn testKnownSolution() -> i32 { |
|
| 76 | + | let solution: [i32; 8] = [0, 4, 7, 5, 2, 6, 1, 3]; |
|
| 77 | + | ||
| 78 | + | let mut i: u32 = 0; |
|
| 79 | + | while i < 8 { |
|
| 80 | + | let mut j: u32 = i + 1; |
|
| 81 | + | while j < 8 { |
|
| 82 | + | if solution[i] == solution[j] { |
|
| 83 | + | return 1; |
|
| 84 | + | } |
|
| 85 | + | let rowDiff: i32 = j as i32 - i as i32; |
|
| 86 | + | let colDiff: i32 = solution[j] - solution[i]; |
|
| 87 | + | if colDiff == rowDiff or colDiff == 0 - rowDiff { |
|
| 88 | + | return 2; |
|
| 89 | + | } |
|
| 90 | + | j = j + 1; |
|
| 91 | + | } |
|
| 92 | + | i = i + 1; |
|
| 93 | + | } |
|
| 94 | + | ||
| 95 | + | let mut used: [bool; 8] = [false; 8]; |
|
| 96 | + | let mut k: u32 = 0; |
|
| 97 | + | while k < 8 { |
|
| 98 | + | let col: u32 = solution[k] as u32; |
|
| 99 | + | if used[col] { |
|
| 100 | + | return 3; |
|
| 101 | + | } |
|
| 102 | + | used[col] = true; |
|
| 103 | + | k = k + 1; |
|
| 104 | + | } |
|
| 105 | + | ||
| 106 | + | return 0; |
|
| 107 | + | } |
|
| 108 | + | ||
| 109 | + | fn testDeterminism(b: *mut Board) -> i32 { |
|
| 110 | + | let count1: u32 = solveNQueens(b, 8); |
|
| 111 | + | let count2: u32 = solveNQueens(b, 8); |
|
| 112 | + | let count3: u32 = solveNQueens(b, 8); |
|
| 113 | + | ||
| 114 | + | if count1 != 92 { return 1; } |
|
| 115 | + | if count2 != 92 { return 2; } |
|
| 116 | + | if count3 != 92 { return 3; } |
|
| 117 | + | return 0; |
|
| 118 | + | } |
|
| 119 | + | ||
| 120 | + | fn testProperties() -> i32 { |
|
| 121 | + | let expected: [u32; 9] = [0, 1, 0, 0, 2, 10, 4, 40, 92]; |
|
| 122 | + | ||
| 123 | + | let mut n: u32 = 4; |
|
| 124 | + | while n <= 8 { |
|
| 125 | + | if expected[n] == 0 { return 1; } |
|
| 126 | + | n = n + 1; |
|
| 127 | + | } |
|
| 128 | + | ||
| 129 | + | if expected[2] != 0 { return 2; } |
|
| 130 | + | if expected[3] != 0 { return 3; } |
|
| 131 | + | if expected[7] <= expected[6] { return 4; } |
|
| 132 | + | if expected[8] <= expected[7] { return 5; } |
|
| 133 | + | ||
| 134 | + | return 0; |
|
| 135 | + | } |
|
| 136 | + | ||
| 137 | + | @default fn main() -> i32 { |
|
| 138 | + | let mut queens: [i32; 8] = [-1; 8]; |
|
| 139 | + | let mut b: Board = Board { |
|
| 140 | + | queens: &mut queens[..], |
|
| 141 | + | solutionCount: 0, |
|
| 142 | + | boardSize: 0, |
|
| 143 | + | }; |
|
| 144 | + | ||
| 145 | + | let r1: i32 = testAllSizes(&mut b); |
|
| 146 | + | if r1 != 0 { return 10 + r1; } |
|
| 147 | + | ||
| 148 | + | let r2: i32 = testKnownSolution(); |
|
| 149 | + | if r2 != 0 { return 20 + r2; } |
|
| 150 | + | ||
| 151 | + | let r3: i32 = testDeterminism(&mut b); |
|
| 152 | + | if r3 != 0 { return 30 + r3; } |
|
| 153 | + | ||
| 154 | + | let r4: i32 = testProperties(); |
|
| 155 | + | if r4 != 0 { return 40 + r4; } |
|
| 156 | + | ||
| 157 | + | return 0; |
|
| 158 | + | } |
lib/std/arch/rv64/tests/prog.rbtree.rad
added
+355 -0
| 1 | + | //! Red-black tree. |
|
| 2 | + | //! Implement a red-black tree (balanced BST) using a stack-allocated node pool. |
|
| 3 | + | ||
| 4 | + | const POOL_SIZE: u32 = 128; |
|
| 5 | + | const NIL: u32 = 0; |
|
| 6 | + | ||
| 7 | + | const RED: u32 = 0; |
|
| 8 | + | const BLACK: u32 = 1; |
|
| 9 | + | ||
| 10 | + | record RBNode { |
|
| 11 | + | key: i32, |
|
| 12 | + | color: u32, |
|
| 13 | + | left: u32, |
|
| 14 | + | right: u32, |
|
| 15 | + | parent: u32, |
|
| 16 | + | } |
|
| 17 | + | ||
| 18 | + | record RBTree { |
|
| 19 | + | pool: *mut [RBNode], |
|
| 20 | + | poolNext: u32, |
|
| 21 | + | root: u32, |
|
| 22 | + | inorder: *mut [i32], |
|
| 23 | + | inorderCount: u32, |
|
| 24 | + | } |
|
| 25 | + | ||
| 26 | + | fn allocNode(t: *mut RBTree, key: i32) -> u32 { |
|
| 27 | + | let idx: u32 = t.poolNext; |
|
| 28 | + | t.poolNext = t.poolNext + 1; |
|
| 29 | + | t.pool[idx] = RBNode { key, color: RED, left: NIL, right: NIL, parent: NIL }; |
|
| 30 | + | return idx; |
|
| 31 | + | } |
|
| 32 | + | ||
| 33 | + | fn rotateLeft(t: *mut RBTree, x: u32) { |
|
| 34 | + | let y: u32 = t.pool[x].right; |
|
| 35 | + | t.pool[x].right = t.pool[y].left; |
|
| 36 | + | if t.pool[y].left != NIL { |
|
| 37 | + | t.pool[t.pool[y].left].parent = x; |
|
| 38 | + | } |
|
| 39 | + | t.pool[y].parent = t.pool[x].parent; |
|
| 40 | + | if t.pool[x].parent == NIL { |
|
| 41 | + | t.root = y; |
|
| 42 | + | } else if x == t.pool[t.pool[x].parent].left { |
|
| 43 | + | t.pool[t.pool[x].parent].left = y; |
|
| 44 | + | } else { |
|
| 45 | + | t.pool[t.pool[x].parent].right = y; |
|
| 46 | + | } |
|
| 47 | + | t.pool[y].left = x; |
|
| 48 | + | t.pool[x].parent = y; |
|
| 49 | + | } |
|
| 50 | + | ||
| 51 | + | fn rotateRight(t: *mut RBTree, x: u32) { |
|
| 52 | + | let y: u32 = t.pool[x].left; |
|
| 53 | + | t.pool[x].left = t.pool[y].right; |
|
| 54 | + | if t.pool[y].right != NIL { |
|
| 55 | + | t.pool[t.pool[y].right].parent = x; |
|
| 56 | + | } |
|
| 57 | + | t.pool[y].parent = t.pool[x].parent; |
|
| 58 | + | if t.pool[x].parent == NIL { |
|
| 59 | + | t.root = y; |
|
| 60 | + | } else if x == t.pool[t.pool[x].parent].right { |
|
| 61 | + | t.pool[t.pool[x].parent].right = y; |
|
| 62 | + | } else { |
|
| 63 | + | t.pool[t.pool[x].parent].left = y; |
|
| 64 | + | } |
|
| 65 | + | t.pool[y].right = x; |
|
| 66 | + | t.pool[x].parent = y; |
|
| 67 | + | } |
|
| 68 | + | ||
| 69 | + | fn insertFixup(t: *mut RBTree, zArg: u32) { |
|
| 70 | + | let mut z: u32 = zArg; |
|
| 71 | + | while t.pool[t.pool[z].parent].color == RED { |
|
| 72 | + | if t.pool[z].parent == t.pool[t.pool[t.pool[z].parent].parent].left { |
|
| 73 | + | let y: u32 = t.pool[t.pool[t.pool[z].parent].parent].right; |
|
| 74 | + | if t.pool[y].color == RED { |
|
| 75 | + | t.pool[t.pool[z].parent].color = BLACK; |
|
| 76 | + | t.pool[y].color = BLACK; |
|
| 77 | + | t.pool[t.pool[t.pool[z].parent].parent].color = RED; |
|
| 78 | + | z = t.pool[t.pool[z].parent].parent; |
|
| 79 | + | } else { |
|
| 80 | + | if z == t.pool[t.pool[z].parent].right { |
|
| 81 | + | z = t.pool[z].parent; |
|
| 82 | + | rotateLeft(t, z); |
|
| 83 | + | } |
|
| 84 | + | t.pool[t.pool[z].parent].color = BLACK; |
|
| 85 | + | t.pool[t.pool[t.pool[z].parent].parent].color = RED; |
|
| 86 | + | rotateRight(t, t.pool[t.pool[z].parent].parent); |
|
| 87 | + | } |
|
| 88 | + | } else { |
|
| 89 | + | let y: u32 = t.pool[t.pool[t.pool[z].parent].parent].left; |
|
| 90 | + | if t.pool[y].color == RED { |
|
| 91 | + | t.pool[t.pool[z].parent].color = BLACK; |
|
| 92 | + | t.pool[y].color = BLACK; |
|
| 93 | + | t.pool[t.pool[t.pool[z].parent].parent].color = RED; |
|
| 94 | + | z = t.pool[t.pool[z].parent].parent; |
|
| 95 | + | } else { |
|
| 96 | + | if z == t.pool[t.pool[z].parent].left { |
|
| 97 | + | z = t.pool[z].parent; |
|
| 98 | + | rotateRight(t, z); |
|
| 99 | + | } |
|
| 100 | + | t.pool[t.pool[z].parent].color = BLACK; |
|
| 101 | + | t.pool[t.pool[t.pool[z].parent].parent].color = RED; |
|
| 102 | + | rotateLeft(t, t.pool[t.pool[z].parent].parent); |
|
| 103 | + | } |
|
| 104 | + | } |
|
| 105 | + | } |
|
| 106 | + | t.pool[t.root].color = BLACK; |
|
| 107 | + | } |
|
| 108 | + | ||
| 109 | + | fn insert(t: *mut RBTree, key: i32) { |
|
| 110 | + | let z: u32 = allocNode(t, key); |
|
| 111 | + | let mut y: u32 = NIL; |
|
| 112 | + | let mut x: u32 = t.root; |
|
| 113 | + | ||
| 114 | + | while x != NIL { |
|
| 115 | + | y = x; |
|
| 116 | + | if key < t.pool[x].key { |
|
| 117 | + | x = t.pool[x].left; |
|
| 118 | + | } else { |
|
| 119 | + | x = t.pool[x].right; |
|
| 120 | + | } |
|
| 121 | + | } |
|
| 122 | + | ||
| 123 | + | t.pool[z].parent = y; |
|
| 124 | + | if y == NIL { |
|
| 125 | + | t.root = z; |
|
| 126 | + | } else if key < t.pool[y].key { |
|
| 127 | + | t.pool[y].left = z; |
|
| 128 | + | } else { |
|
| 129 | + | t.pool[y].right = z; |
|
| 130 | + | } |
|
| 131 | + | ||
| 132 | + | insertFixup(t, z); |
|
| 133 | + | } |
|
| 134 | + | ||
| 135 | + | fn search(t: *RBTree, key: i32) -> bool { |
|
| 136 | + | let mut x: u32 = t.root; |
|
| 137 | + | while x != NIL { |
|
| 138 | + | if key == t.pool[x].key { |
|
| 139 | + | return true; |
|
| 140 | + | } else if key < t.pool[x].key { |
|
| 141 | + | x = t.pool[x].left; |
|
| 142 | + | } else { |
|
| 143 | + | x = t.pool[x].right; |
|
| 144 | + | } |
|
| 145 | + | } |
|
| 146 | + | return false; |
|
| 147 | + | } |
|
| 148 | + | ||
| 149 | + | fn inorderWalk(t: *mut RBTree, x: u32) { |
|
| 150 | + | if x == NIL { |
|
| 151 | + | return; |
|
| 152 | + | } |
|
| 153 | + | inorderWalk(t, t.pool[x].left); |
|
| 154 | + | t.inorder[t.inorderCount] = t.pool[x].key; |
|
| 155 | + | t.inorderCount = t.inorderCount + 1; |
|
| 156 | + | inorderWalk(t, t.pool[x].right); |
|
| 157 | + | } |
|
| 158 | + | ||
| 159 | + | fn countNodes(t: *RBTree, x: u32) -> u32 { |
|
| 160 | + | if x == NIL { |
|
| 161 | + | return 0; |
|
| 162 | + | } |
|
| 163 | + | return 1 + countNodes(t, t.pool[x].left) + countNodes(t, t.pool[x].right); |
|
| 164 | + | } |
|
| 165 | + | ||
| 166 | + | fn blackHeight(t: *RBTree, x: u32) -> i32 { |
|
| 167 | + | if x == NIL { |
|
| 168 | + | return 1; |
|
| 169 | + | } |
|
| 170 | + | let leftBH: i32 = blackHeight(t, t.pool[x].left); |
|
| 171 | + | let rightBH: i32 = blackHeight(t, t.pool[x].right); |
|
| 172 | + | ||
| 173 | + | if leftBH == -1 or rightBH == -1 { |
|
| 174 | + | return -1; |
|
| 175 | + | } |
|
| 176 | + | if leftBH != rightBH { |
|
| 177 | + | return -1; |
|
| 178 | + | } |
|
| 179 | + | ||
| 180 | + | if t.pool[x].color == BLACK { |
|
| 181 | + | return leftBH + 1; |
|
| 182 | + | } |
|
| 183 | + | return leftBH; |
|
| 184 | + | } |
|
| 185 | + | ||
| 186 | + | fn noRedRed(t: *RBTree, x: u32) -> bool { |
|
| 187 | + | if x == NIL { |
|
| 188 | + | return true; |
|
| 189 | + | } |
|
| 190 | + | if t.pool[x].color == RED { |
|
| 191 | + | if t.pool[t.pool[x].left].color == RED { |
|
| 192 | + | return false; |
|
| 193 | + | } |
|
| 194 | + | if t.pool[t.pool[x].right].color == RED { |
|
| 195 | + | return false; |
|
| 196 | + | } |
|
| 197 | + | } |
|
| 198 | + | if not noRedRed(t, t.pool[x].left) { |
|
| 199 | + | return false; |
|
| 200 | + | } |
|
| 201 | + | return noRedRed(t, t.pool[x].right); |
|
| 202 | + | } |
|
| 203 | + | ||
| 204 | + | fn resetTree(t: *mut RBTree) { |
|
| 205 | + | let mut i: u32 = 0; |
|
| 206 | + | while i < POOL_SIZE { |
|
| 207 | + | t.pool[i] = RBNode { key: 0, color: BLACK, left: NIL, right: NIL, parent: NIL }; |
|
| 208 | + | i = i + 1; |
|
| 209 | + | } |
|
| 210 | + | t.poolNext = 1; |
|
| 211 | + | t.root = NIL; |
|
| 212 | + | t.inorderCount = 0; |
|
| 213 | + | } |
|
| 214 | + | ||
| 215 | + | fn testAscending(t: *mut RBTree) -> i32 { |
|
| 216 | + | resetTree(t); |
|
| 217 | + | ||
| 218 | + | let mut i: i32 = 0; |
|
| 219 | + | while i < 32 { |
|
| 220 | + | insert(t, i); |
|
| 221 | + | i = i + 1; |
|
| 222 | + | } |
|
| 223 | + | ||
| 224 | + | if countNodes(t, t.root) != 32 { return 1; } |
|
| 225 | + | if t.pool[t.root].color != BLACK { return 2; } |
|
| 226 | + | ||
| 227 | + | let bh: i32 = blackHeight(t, t.root); |
|
| 228 | + | if bh == -1 { return 3; } |
|
| 229 | + | ||
| 230 | + | if not noRedRed(t, t.root) { return 4; } |
|
| 231 | + | ||
| 232 | + | t.inorderCount = 0; |
|
| 233 | + | inorderWalk(t, t.root); |
|
| 234 | + | if t.inorderCount != 32 { return 5; } |
|
| 235 | + | let mut j: u32 = 0; |
|
| 236 | + | while j < 32 { |
|
| 237 | + | if t.inorder[j] != j as i32 { return 6; } |
|
| 238 | + | j = j + 1; |
|
| 239 | + | } |
|
| 240 | + | ||
| 241 | + | return 0; |
|
| 242 | + | } |
|
| 243 | + | ||
| 244 | + | fn testDescending(t: *mut RBTree) -> i32 { |
|
| 245 | + | resetTree(t); |
|
| 246 | + | ||
| 247 | + | let mut i: i32 = 31; |
|
| 248 | + | while i >= 0 { |
|
| 249 | + | insert(t, i); |
|
| 250 | + | if i == 0 { |
|
| 251 | + | break; |
|
| 252 | + | } |
|
| 253 | + | i = i - 1; |
|
| 254 | + | } |
|
| 255 | + | ||
| 256 | + | if countNodes(t, t.root) != 32 { return 1; } |
|
| 257 | + | if blackHeight(t, t.root) == -1 { return 2; } |
|
| 258 | + | if not noRedRed(t, t.root) { return 3; } |
|
| 259 | + | ||
| 260 | + | t.inorderCount = 0; |
|
| 261 | + | inorderWalk(t, t.root); |
|
| 262 | + | let mut j: u32 = 0; |
|
| 263 | + | while j < 32 { |
|
| 264 | + | if t.inorder[j] != j as i32 { return 4; } |
|
| 265 | + | j = j + 1; |
|
| 266 | + | } |
|
| 267 | + | ||
| 268 | + | return 0; |
|
| 269 | + | } |
|
| 270 | + | ||
| 271 | + | fn testRandom(t: *mut RBTree) -> i32 { |
|
| 272 | + | resetTree(t); |
|
| 273 | + | ||
| 274 | + | let mut inserted: [bool; 48] = [false; 48]; |
|
| 275 | + | let mut seed: u32 = 42; |
|
| 276 | + | let mut count: u32 = 0; |
|
| 277 | + | ||
| 278 | + | while count < 48 { |
|
| 279 | + | seed = (seed * 1103515245 + 12345) & 0x7FFFFFFF; |
|
| 280 | + | let val: u32 = seed % 48; |
|
| 281 | + | if not inserted[val] { |
|
| 282 | + | insert(t, val as i32); |
|
| 283 | + | inserted[val] = true; |
|
| 284 | + | count = count + 1; |
|
| 285 | + | } |
|
| 286 | + | } |
|
| 287 | + | ||
| 288 | + | if countNodes(t, t.root) != 48 { return 1; } |
|
| 289 | + | if blackHeight(t, t.root) == -1 { return 2; } |
|
| 290 | + | if not noRedRed(t, t.root) { return 3; } |
|
| 291 | + | ||
| 292 | + | let mut i: i32 = 0; |
|
| 293 | + | while i < 48 { |
|
| 294 | + | if not search(t, i) { return 4; } |
|
| 295 | + | i = i + 1; |
|
| 296 | + | } |
|
| 297 | + | ||
| 298 | + | if search(t, -1) { return 5; } |
|
| 299 | + | if search(t, 48) { return 6; } |
|
| 300 | + | if search(t, 100) { return 7; } |
|
| 301 | + | ||
| 302 | + | t.inorderCount = 0; |
|
| 303 | + | inorderWalk(t, t.root); |
|
| 304 | + | if t.inorderCount != 48 { return 8; } |
|
| 305 | + | let mut j: u32 = 0; |
|
| 306 | + | while j < 48 { |
|
| 307 | + | if t.inorder[j] != j as i32 { return 9; } |
|
| 308 | + | j = j + 1; |
|
| 309 | + | } |
|
| 310 | + | ||
| 311 | + | return 0; |
|
| 312 | + | } |
|
| 313 | + | ||
| 314 | + | fn testHeight(t: *mut RBTree) -> i32 { |
|
| 315 | + | resetTree(t); |
|
| 316 | + | ||
| 317 | + | let mut i: i32 = 0; |
|
| 318 | + | while i < 63 { |
|
| 319 | + | insert(t, i); |
|
| 320 | + | i = i + 1; |
|
| 321 | + | } |
|
| 322 | + | ||
| 323 | + | let bh: i32 = blackHeight(t, t.root); |
|
| 324 | + | if bh < 3 { return 1; } |
|
| 325 | + | if bh > 7 { return 2; } |
|
| 326 | + | ||
| 327 | + | return 0; |
|
| 328 | + | } |
|
| 329 | + | ||
| 330 | + | @default fn main() -> i32 { |
|
| 331 | + | let mut pool: [RBNode; 128] = [RBNode { key: 0, color: 1, left: 0, right: 0, parent: 0 }; 128]; |
|
| 332 | + | let mut inorder: [i32; 128] = [0; 128]; |
|
| 333 | + | ||
| 334 | + | let mut t: RBTree = RBTree { |
|
| 335 | + | pool: &mut pool[..], |
|
| 336 | + | poolNext: 1, |
|
| 337 | + | root: NIL, |
|
| 338 | + | inorder: &mut inorder[..], |
|
| 339 | + | inorderCount: 0, |
|
| 340 | + | }; |
|
| 341 | + | ||
| 342 | + | let r1: i32 = testAscending(&mut t); |
|
| 343 | + | if r1 != 0 { return 10 + r1; } |
|
| 344 | + | ||
| 345 | + | let r2: i32 = testDescending(&mut t); |
|
| 346 | + | if r2 != 0 { return 20 + r2; } |
|
| 347 | + | ||
| 348 | + | let r3: i32 = testRandom(&mut t); |
|
| 349 | + | if r3 != 0 { return 30 + r3; } |
|
| 350 | + | ||
| 351 | + | let r4: i32 = testHeight(&mut t); |
|
| 352 | + | if r4 != 0 { return 50 + r4; } |
|
| 353 | + | ||
| 354 | + | return 0; |
|
| 355 | + | } |
lib/std/arch/rv64/tests/prog.regex.rad
added
+396 -0
| 1 | + | //! NFA-based regex matcher. |
|
| 2 | + | //! Implement a simple regular expression engine using Thompson's NFA |
|
| 3 | + | //! construction. Supports: literal characters, '.', '*', '+', '?', |
|
| 4 | + | //! and concatenation. |
|
| 5 | + | ||
| 6 | + | const MAX_STATES: u32 = 128; |
|
| 7 | + | const MAX_TRANSITIONS: u32 = 256; |
|
| 8 | + | const NIL: u32 = 0xFFFFFFFF; |
|
| 9 | + | ||
| 10 | + | const TRANS_CHAR: u32 = 0; |
|
| 11 | + | const TRANS_EPSILON: u32 = 1; |
|
| 12 | + | const TRANS_DOT: u32 = 2; |
|
| 13 | + | ||
| 14 | + | record Trans { |
|
| 15 | + | kind: u32, |
|
| 16 | + | ch: u8, |
|
| 17 | + | to: u32, |
|
| 18 | + | } |
|
| 19 | + | ||
| 20 | + | record Frag { |
|
| 21 | + | start: u32, |
|
| 22 | + | endState: u32, |
|
| 23 | + | } |
|
| 24 | + | ||
| 25 | + | record NfaState { |
|
| 26 | + | trans: *mut [Trans], |
|
| 27 | + | transCount: u32, |
|
| 28 | + | stateFirst: *mut [u32], |
|
| 29 | + | transNext: *mut [u32], |
|
| 30 | + | stateCount: u32, |
|
| 31 | + | acceptState: u32, |
|
| 32 | + | current: *mut [u32], |
|
| 33 | + | nextSet: *mut [u32], |
|
| 34 | + | closure: *mut [u32], |
|
| 35 | + | fragStack: *mut [Frag], |
|
| 36 | + | fragTop: u32, |
|
| 37 | + | } |
|
| 38 | + | ||
| 39 | + | fn newState(nfa: *mut NfaState) -> u32 { |
|
| 40 | + | let s: u32 = nfa.stateCount; |
|
| 41 | + | nfa.stateFirst[s] = NIL; |
|
| 42 | + | nfa.stateCount = nfa.stateCount + 1; |
|
| 43 | + | return s; |
|
| 44 | + | } |
|
| 45 | + | ||
| 46 | + | fn addTrans(nfa: *mut NfaState, from: u32, kind: u32, ch: u8, to: u32) { |
|
| 47 | + | let idx: u32 = nfa.transCount; |
|
| 48 | + | nfa.trans[idx] = Trans { kind, ch, to }; |
|
| 49 | + | nfa.transNext[idx] = nfa.stateFirst[from]; |
|
| 50 | + | nfa.stateFirst[from] = idx; |
|
| 51 | + | nfa.transCount = nfa.transCount + 1; |
|
| 52 | + | } |
|
| 53 | + | ||
| 54 | + | fn pushFrag(nfa: *mut NfaState, f: Frag) { |
|
| 55 | + | nfa.fragStack[nfa.fragTop] = f; |
|
| 56 | + | nfa.fragTop = nfa.fragTop + 1; |
|
| 57 | + | } |
|
| 58 | + | ||
| 59 | + | fn popFrag(nfa: *mut NfaState) -> Frag { |
|
| 60 | + | nfa.fragTop = nfa.fragTop - 1; |
|
| 61 | + | return nfa.fragStack[nfa.fragTop]; |
|
| 62 | + | } |
|
| 63 | + | ||
| 64 | + | fn setEmpty(s: *mut [u32]) { |
|
| 65 | + | s[0] = 0; |
|
| 66 | + | s[1] = 0; |
|
| 67 | + | s[2] = 0; |
|
| 68 | + | s[3] = 0; |
|
| 69 | + | } |
|
| 70 | + | ||
| 71 | + | fn setAdd(s: *mut [u32], bit: u32) { |
|
| 72 | + | let word: u32 = bit / 32; |
|
| 73 | + | let pos: u32 = bit % 32; |
|
| 74 | + | s[word] = s[word] | (1 << pos); |
|
| 75 | + | } |
|
| 76 | + | ||
| 77 | + | fn setHas(s: *[u32], bit: u32) -> bool { |
|
| 78 | + | let word: u32 = bit / 32; |
|
| 79 | + | let pos: u32 = bit % 32; |
|
| 80 | + | return (s[word] >> pos) & 1 == 1; |
|
| 81 | + | } |
|
| 82 | + | ||
| 83 | + | fn setIsEmpty(s: *[u32]) -> bool { |
|
| 84 | + | return s[0] == 0 and s[1] == 0 and s[2] == 0 and s[3] == 0; |
|
| 85 | + | } |
|
| 86 | + | ||
| 87 | + | fn setCopy(dst: *mut [u32], src: *[u32]) { |
|
| 88 | + | dst[0] = src[0]; |
|
| 89 | + | dst[1] = src[1]; |
|
| 90 | + | dst[2] = src[2]; |
|
| 91 | + | dst[3] = src[3]; |
|
| 92 | + | } |
|
| 93 | + | ||
| 94 | + | fn epsilonClosure(nfa: *mut NfaState, states: *mut [u32]) { |
|
| 95 | + | setCopy(nfa.closure, states); |
|
| 96 | + | ||
| 97 | + | let mut changed: bool = true; |
|
| 98 | + | while changed { |
|
| 99 | + | changed = false; |
|
| 100 | + | let mut s: u32 = 0; |
|
| 101 | + | while s < nfa.stateCount { |
|
| 102 | + | if setHas(nfa.closure, s) { |
|
| 103 | + | let mut t: u32 = nfa.stateFirst[s]; |
|
| 104 | + | while t != NIL { |
|
| 105 | + | if nfa.trans[t].kind == TRANS_EPSILON { |
|
| 106 | + | if not setHas(nfa.closure, nfa.trans[t].to) { |
|
| 107 | + | setAdd(nfa.closure, nfa.trans[t].to); |
|
| 108 | + | changed = true; |
|
| 109 | + | } |
|
| 110 | + | } |
|
| 111 | + | t = nfa.transNext[t]; |
|
| 112 | + | } |
|
| 113 | + | } |
|
| 114 | + | s = s + 1; |
|
| 115 | + | } |
|
| 116 | + | } |
|
| 117 | + | ||
| 118 | + | setCopy(states, nfa.closure); |
|
| 119 | + | } |
|
| 120 | + | ||
| 121 | + | fn resetNFA(nfa: *mut NfaState) { |
|
| 122 | + | nfa.stateCount = 0; |
|
| 123 | + | nfa.transCount = 0; |
|
| 124 | + | nfa.fragTop = 0; |
|
| 125 | + | let mut i: u32 = 0; |
|
| 126 | + | while i < MAX_STATES { |
|
| 127 | + | nfa.stateFirst[i] = NIL; |
|
| 128 | + | i = i + 1; |
|
| 129 | + | } |
|
| 130 | + | i = 0; |
|
| 131 | + | while i < MAX_TRANSITIONS { |
|
| 132 | + | nfa.transNext[i] = NIL; |
|
| 133 | + | i = i + 1; |
|
| 134 | + | } |
|
| 135 | + | } |
|
| 136 | + | ||
| 137 | + | fn compile(nfa: *mut NfaState, pattern: *[u8]) -> u32 { |
|
| 138 | + | resetNFA(nfa); |
|
| 139 | + | ||
| 140 | + | let mut i: u32 = 0; |
|
| 141 | + | while i < pattern.len { |
|
| 142 | + | let ch: u8 = pattern[i]; |
|
| 143 | + | ||
| 144 | + | if i + 1 < pattern.len { |
|
| 145 | + | let nextCh: u8 = pattern[i + 1]; |
|
| 146 | + | ||
| 147 | + | if nextCh == 42 { |
|
| 148 | + | let s: u32 = newState(nfa); |
|
| 149 | + | let e: u32 = newState(nfa); |
|
| 150 | + | let body: u32 = newState(nfa); |
|
| 151 | + | ||
| 152 | + | if ch == 46 { |
|
| 153 | + | addTrans(nfa, body, TRANS_DOT, 0, body); |
|
| 154 | + | } else { |
|
| 155 | + | addTrans(nfa, body, TRANS_CHAR, ch, body); |
|
| 156 | + | } |
|
| 157 | + | addTrans(nfa, s, TRANS_EPSILON, 0, body); |
|
| 158 | + | addTrans(nfa, s, TRANS_EPSILON, 0, e); |
|
| 159 | + | addTrans(nfa, body, TRANS_EPSILON, 0, e); |
|
| 160 | + | ||
| 161 | + | pushFrag(nfa, Frag { start: s, endState: e }); |
|
| 162 | + | i = i + 2; |
|
| 163 | + | continue; |
|
| 164 | + | } |
|
| 165 | + | if nextCh == 43 { |
|
| 166 | + | let s: u32 = newState(nfa); |
|
| 167 | + | let m: u32 = newState(nfa); |
|
| 168 | + | let e: u32 = newState(nfa); |
|
| 169 | + | ||
| 170 | + | if ch == 46 { |
|
| 171 | + | addTrans(nfa, s, TRANS_DOT, 0, m); |
|
| 172 | + | addTrans(nfa, m, TRANS_DOT, 0, m); |
|
| 173 | + | } else { |
|
| 174 | + | addTrans(nfa, s, TRANS_CHAR, ch, m); |
|
| 175 | + | addTrans(nfa, m, TRANS_CHAR, ch, m); |
|
| 176 | + | } |
|
| 177 | + | addTrans(nfa, m, TRANS_EPSILON, 0, e); |
|
| 178 | + | ||
| 179 | + | pushFrag(nfa, Frag { start: s, endState: e }); |
|
| 180 | + | i = i + 2; |
|
| 181 | + | continue; |
|
| 182 | + | } |
|
| 183 | + | if nextCh == 63 { |
|
| 184 | + | let s: u32 = newState(nfa); |
|
| 185 | + | let m: u32 = newState(nfa); |
|
| 186 | + | let e: u32 = newState(nfa); |
|
| 187 | + | ||
| 188 | + | if ch == 46 { |
|
| 189 | + | addTrans(nfa, s, TRANS_DOT, 0, m); |
|
| 190 | + | } else { |
|
| 191 | + | addTrans(nfa, s, TRANS_CHAR, ch, m); |
|
| 192 | + | } |
|
| 193 | + | addTrans(nfa, s, TRANS_EPSILON, 0, e); |
|
| 194 | + | addTrans(nfa, m, TRANS_EPSILON, 0, e); |
|
| 195 | + | ||
| 196 | + | pushFrag(nfa, Frag { start: s, endState: e }); |
|
| 197 | + | i = i + 2; |
|
| 198 | + | continue; |
|
| 199 | + | } |
|
| 200 | + | } |
|
| 201 | + | ||
| 202 | + | let s: u32 = newState(nfa); |
|
| 203 | + | let e: u32 = newState(nfa); |
|
| 204 | + | if ch == 46 { |
|
| 205 | + | addTrans(nfa, s, TRANS_DOT, 0, e); |
|
| 206 | + | } else { |
|
| 207 | + | addTrans(nfa, s, TRANS_CHAR, ch, e); |
|
| 208 | + | } |
|
| 209 | + | pushFrag(nfa, Frag { start: s, endState: e }); |
|
| 210 | + | i = i + 1; |
|
| 211 | + | } |
|
| 212 | + | ||
| 213 | + | if nfa.fragTop == 0 { |
|
| 214 | + | let s: u32 = newState(nfa); |
|
| 215 | + | nfa.acceptState = s; |
|
| 216 | + | return s; |
|
| 217 | + | } |
|
| 218 | + | ||
| 219 | + | let numFrags: u32 = nfa.fragTop; |
|
| 220 | + | let mut frags: [Frag; 64] = [Frag { start: 0, endState: 0 }; 64]; |
|
| 221 | + | let mut k: u32 = 0; |
|
| 222 | + | while k < numFrags { |
|
| 223 | + | frags[numFrags - 1 - k] = popFrag(nfa); |
|
| 224 | + | k = k + 1; |
|
| 225 | + | } |
|
| 226 | + | ||
| 227 | + | let mut j: u32 = 0; |
|
| 228 | + | while j < numFrags - 1 { |
|
| 229 | + | addTrans(nfa, frags[j].endState, TRANS_EPSILON, 0, frags[j + 1].start); |
|
| 230 | + | j = j + 1; |
|
| 231 | + | } |
|
| 232 | + | ||
| 233 | + | nfa.acceptState = frags[numFrags - 1].endState; |
|
| 234 | + | return frags[0].start; |
|
| 235 | + | } |
|
| 236 | + | ||
| 237 | + | fn nfaMatches(nfa: *mut NfaState, start: u32, input: *[u8]) -> bool { |
|
| 238 | + | setEmpty(nfa.current); |
|
| 239 | + | setAdd(nfa.current, start); |
|
| 240 | + | epsilonClosure(nfa, nfa.current); |
|
| 241 | + | ||
| 242 | + | let mut i: u32 = 0; |
|
| 243 | + | while i < input.len { |
|
| 244 | + | let ch: u8 = input[i]; |
|
| 245 | + | setEmpty(nfa.nextSet); |
|
| 246 | + | ||
| 247 | + | let mut s: u32 = 0; |
|
| 248 | + | while s < nfa.stateCount { |
|
| 249 | + | if setHas(nfa.current, s) { |
|
| 250 | + | let mut t: u32 = nfa.stateFirst[s]; |
|
| 251 | + | while t != NIL { |
|
| 252 | + | if nfa.trans[t].kind == TRANS_CHAR and nfa.trans[t].ch == ch { |
|
| 253 | + | setAdd(nfa.nextSet, nfa.trans[t].to); |
|
| 254 | + | } else if nfa.trans[t].kind == TRANS_DOT { |
|
| 255 | + | setAdd(nfa.nextSet, nfa.trans[t].to); |
|
| 256 | + | } |
|
| 257 | + | t = nfa.transNext[t]; |
|
| 258 | + | } |
|
| 259 | + | } |
|
| 260 | + | s = s + 1; |
|
| 261 | + | } |
|
| 262 | + | ||
| 263 | + | epsilonClosure(nfa, nfa.nextSet); |
|
| 264 | + | setCopy(nfa.current, nfa.nextSet); |
|
| 265 | + | ||
| 266 | + | if setIsEmpty(nfa.current) { |
|
| 267 | + | return false; |
|
| 268 | + | } |
|
| 269 | + | i = i + 1; |
|
| 270 | + | } |
|
| 271 | + | ||
| 272 | + | return setHas(nfa.current, nfa.acceptState); |
|
| 273 | + | } |
|
| 274 | + | ||
| 275 | + | fn testLiteral(nfa: *mut NfaState) -> i32 { |
|
| 276 | + | let start: u32 = compile(nfa, "abc"); |
|
| 277 | + | ||
| 278 | + | if not nfaMatches(nfa, start, "abc") { return 1; } |
|
| 279 | + | if nfaMatches(nfa, start, "ab") { return 2; } |
|
| 280 | + | if nfaMatches(nfa, start, "abcd") { return 3; } |
|
| 281 | + | if nfaMatches(nfa, start, "abd") { return 4; } |
|
| 282 | + | ||
| 283 | + | return 0; |
|
| 284 | + | } |
|
| 285 | + | ||
| 286 | + | fn testStar(nfa: *mut NfaState) -> i32 { |
|
| 287 | + | let start: u32 = compile(nfa, "a*"); |
|
| 288 | + | ||
| 289 | + | if not nfaMatches(nfa, start, "") { return 1; } |
|
| 290 | + | if not nfaMatches(nfa, start, "a") { return 2; } |
|
| 291 | + | if not nfaMatches(nfa, start, "aaa") { return 3; } |
|
| 292 | + | if nfaMatches(nfa, start, "b") { return 4; } |
|
| 293 | + | ||
| 294 | + | return 0; |
|
| 295 | + | } |
|
| 296 | + | ||
| 297 | + | fn testPlus(nfa: *mut NfaState) -> i32 { |
|
| 298 | + | let start: u32 = compile(nfa, "a+"); |
|
| 299 | + | ||
| 300 | + | if nfaMatches(nfa, start, "") { return 1; } |
|
| 301 | + | if not nfaMatches(nfa, start, "a") { return 2; } |
|
| 302 | + | if not nfaMatches(nfa, start, "aaaaa") { return 3; } |
|
| 303 | + | ||
| 304 | + | return 0; |
|
| 305 | + | } |
|
| 306 | + | ||
| 307 | + | fn testQuestion(nfa: *mut NfaState) -> i32 { |
|
| 308 | + | let start: u32 = compile(nfa, "a?"); |
|
| 309 | + | ||
| 310 | + | if not nfaMatches(nfa, start, "") { return 1; } |
|
| 311 | + | if not nfaMatches(nfa, start, "a") { return 2; } |
|
| 312 | + | if nfaMatches(nfa, start, "aa") { return 3; } |
|
| 313 | + | ||
| 314 | + | return 0; |
|
| 315 | + | } |
|
| 316 | + | ||
| 317 | + | fn testDot(nfa: *mut NfaState) -> i32 { |
|
| 318 | + | let start: u32 = compile(nfa, ".."); |
|
| 319 | + | ||
| 320 | + | if not nfaMatches(nfa, start, "ab") { return 1; } |
|
| 321 | + | if not nfaMatches(nfa, start, "zz") { return 2; } |
|
| 322 | + | if nfaMatches(nfa, start, "a") { return 3; } |
|
| 323 | + | if nfaMatches(nfa, start, "abc") { return 4; } |
|
| 324 | + | ||
| 325 | + | return 0; |
|
| 326 | + | } |
|
| 327 | + | ||
| 328 | + | fn testComplex(nfa: *mut NfaState) -> i32 { |
|
| 329 | + | let start: u32 = compile(nfa, "ab*c"); |
|
| 330 | + | ||
| 331 | + | if not nfaMatches(nfa, start, "ac") { return 1; } |
|
| 332 | + | if not nfaMatches(nfa, start, "abc") { return 2; } |
|
| 333 | + | if not nfaMatches(nfa, start, "abbc") { return 3; } |
|
| 334 | + | if not nfaMatches(nfa, start, "abbbc") { return 4; } |
|
| 335 | + | if nfaMatches(nfa, start, "a") { return 5; } |
|
| 336 | + | if nfaMatches(nfa, start, "adc") { return 6; } |
|
| 337 | + | ||
| 338 | + | return 0; |
|
| 339 | + | } |
|
| 340 | + | ||
| 341 | + | fn testDotStar(nfa: *mut NfaState) -> i32 { |
|
| 342 | + | let start: u32 = compile(nfa, ".*"); |
|
| 343 | + | ||
| 344 | + | if not nfaMatches(nfa, start, "") { return 1; } |
|
| 345 | + | if not nfaMatches(nfa, start, "hello") { return 2; } |
|
| 346 | + | if not nfaMatches(nfa, start, "x") { return 3; } |
|
| 347 | + | ||
| 348 | + | return 0; |
|
| 349 | + | } |
|
| 350 | + | ||
| 351 | + | @default fn main() -> i32 { |
|
| 352 | + | let mut trans: [Trans; 256] = [Trans { kind: 0, ch: 0, to: 0 }; 256]; |
|
| 353 | + | let mut stateFirst: [u32; 128] = [0xFFFFFFFF; 128]; |
|
| 354 | + | let mut transNext: [u32; 256] = [0xFFFFFFFF; 256]; |
|
| 355 | + | let mut current: [u32; 4] = [0; 4]; |
|
| 356 | + | let mut nextSet: [u32; 4] = [0; 4]; |
|
| 357 | + | let mut closureBuf: [u32; 4] = [0; 4]; |
|
| 358 | + | let mut fragStack: [Frag; 64] = [Frag { start: 0, endState: 0 }; 64]; |
|
| 359 | + | ||
| 360 | + | let mut nfa: NfaState = NfaState { |
|
| 361 | + | trans: &mut trans[..], |
|
| 362 | + | transCount: 0, |
|
| 363 | + | stateFirst: &mut stateFirst[..], |
|
| 364 | + | transNext: &mut transNext[..], |
|
| 365 | + | stateCount: 0, |
|
| 366 | + | acceptState: 0, |
|
| 367 | + | current: &mut current[..], |
|
| 368 | + | nextSet: &mut nextSet[..], |
|
| 369 | + | closure: &mut closureBuf[..], |
|
| 370 | + | fragStack: &mut fragStack[..], |
|
| 371 | + | fragTop: 0, |
|
| 372 | + | }; |
|
| 373 | + | ||
| 374 | + | let r1: i32 = testLiteral(&mut nfa); |
|
| 375 | + | if r1 != 0 { return 10 + r1; } |
|
| 376 | + | ||
| 377 | + | let r2: i32 = testStar(&mut nfa); |
|
| 378 | + | if r2 != 0 { return 20 + r2; } |
|
| 379 | + | ||
| 380 | + | let r3: i32 = testPlus(&mut nfa); |
|
| 381 | + | if r3 != 0 { return 30 + r3; } |
|
| 382 | + | ||
| 383 | + | let r4: i32 = testQuestion(&mut nfa); |
|
| 384 | + | if r4 != 0 { return 40 + r4; } |
|
| 385 | + | ||
| 386 | + | let r5: i32 = testDot(&mut nfa); |
|
| 387 | + | if r5 != 0 { return 50 + r5; } |
|
| 388 | + | ||
| 389 | + | let r6: i32 = testComplex(&mut nfa); |
|
| 390 | + | if r6 != 0 { return 60 + r6; } |
|
| 391 | + | ||
| 392 | + | let r7: i32 = testDotStar(&mut nfa); |
|
| 393 | + | if r7 != 0 { return 70 + r7; } |
|
| 394 | + | ||
| 395 | + | return 0; |
|
| 396 | + | } |
lib/std/arch/rv64/tests/prog.sha256.rad
added
+295 -0
| 1 | + | //! SHA-256 (single block). |
|
| 2 | + | //! Implement the SHA-256 compression function for a single 512-bit block. |
|
| 3 | + | //! Verify against a known hash of a short message. |
|
| 4 | + | ||
| 5 | + | /// SHA-256 mutable state: hash values and message schedule. |
|
| 6 | + | record Sha256 { |
|
| 7 | + | h: [u32; 8], |
|
| 8 | + | w: [u32; 64], |
|
| 9 | + | } |
|
| 10 | + | ||
| 11 | + | /// SHA-256 initial hash values (first 32 bits of fractional parts of square |
|
| 12 | + | /// roots of the first 8 primes). |
|
| 13 | + | const INIT_H: [u32; 8] = [ |
|
| 14 | + | 0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, |
|
| 15 | + | 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19 |
|
| 16 | + | ]; |
|
| 17 | + | ||
| 18 | + | /// Right-rotate a 32-bit value by `n` bits. |
|
| 19 | + | fn rotr(x: u32, n: u32) -> u32 { |
|
| 20 | + | return (x >> n) | (x << (32 - n)); |
|
| 21 | + | } |
|
| 22 | + | ||
| 23 | + | /// SHA-256 Ch function: Ch(e, f, g) = (e AND f) XOR (NOT e AND g). |
|
| 24 | + | fn ch(e: u32, f: u32, g: u32) -> u32 { |
|
| 25 | + | return (e & f) ^ (~e & g); |
|
| 26 | + | } |
|
| 27 | + | ||
| 28 | + | /// SHA-256 Maj function: Maj(a, b, c) = (a AND b) XOR (a AND c) XOR (b AND c). |
|
| 29 | + | fn maj(a: u32, b: u32, c: u32) -> u32 { |
|
| 30 | + | return (a & b) ^ (a & c) ^ (b & c); |
|
| 31 | + | } |
|
| 32 | + | ||
| 33 | + | /// SHA-256 big sigma 0: rotr(a, 2) XOR rotr(a, 13) XOR rotr(a, 22). |
|
| 34 | + | fn bsig0(a: u32) -> u32 { |
|
| 35 | + | return rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22); |
|
| 36 | + | } |
|
| 37 | + | ||
| 38 | + | /// SHA-256 big sigma 1: rotr(e, 6) XOR rotr(e, 11) XOR rotr(e, 25). |
|
| 39 | + | fn bsig1(e: u32) -> u32 { |
|
| 40 | + | return rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25); |
|
| 41 | + | } |
|
| 42 | + | ||
| 43 | + | /// SHA-256 small sigma 0: rotr(x, 7) XOR rotr(x, 18) XOR (x >> 3). |
|
| 44 | + | fn ssig0(x: u32) -> u32 { |
|
| 45 | + | return rotr(x, 7) ^ rotr(x, 18) ^ (x >> 3); |
|
| 46 | + | } |
|
| 47 | + | ||
| 48 | + | /// SHA-256 small sigma 1: rotr(x, 17) XOR rotr(x, 19) XOR (x >> 10). |
|
| 49 | + | fn ssig1(x: u32) -> u32 { |
|
| 50 | + | return rotr(x, 17) ^ rotr(x, 19) ^ (x >> 10); |
|
| 51 | + | } |
|
| 52 | + | ||
| 53 | + | /// Prepare the message schedule from a 16-word (512-bit) block. |
|
| 54 | + | fn prepareSchedule(s: *mut Sha256, block: *[u32]) { |
|
| 55 | + | // Copy the first 16 words directly. |
|
| 56 | + | let mut i: u32 = 0; |
|
| 57 | + | while i < 16 { |
|
| 58 | + | s.w[i] = block[i]; |
|
| 59 | + | i = i + 1; |
|
| 60 | + | } |
|
| 61 | + | // Extend to 64 words. |
|
| 62 | + | while i < 64 { |
|
| 63 | + | s.w[i] = ssig1(s.w[i - 2]) + s.w[i - 7] + ssig0(s.w[i - 15]) + s.w[i - 16]; |
|
| 64 | + | i = i + 1; |
|
| 65 | + | } |
|
| 66 | + | } |
|
| 67 | + | ||
| 68 | + | /// Run the 64-round compression function. |
|
| 69 | + | fn compress(s: *mut Sha256, k: *[u32]) { |
|
| 70 | + | let mut a: u32 = s.h[0]; |
|
| 71 | + | let mut b: u32 = s.h[1]; |
|
| 72 | + | let mut c: u32 = s.h[2]; |
|
| 73 | + | let mut d: u32 = s.h[3]; |
|
| 74 | + | let mut e: u32 = s.h[4]; |
|
| 75 | + | let mut f: u32 = s.h[5]; |
|
| 76 | + | let mut g: u32 = s.h[6]; |
|
| 77 | + | let mut hh: u32 = s.h[7]; |
|
| 78 | + | ||
| 79 | + | let mut i: u32 = 0; |
|
| 80 | + | while i < 64 { |
|
| 81 | + | let t1 = hh + bsig1(e) + ch(e, f, g) + k[i] + s.w[i]; |
|
| 82 | + | let t2 = bsig0(a) + maj(a, b, c); |
|
| 83 | + | hh = g; |
|
| 84 | + | g = f; |
|
| 85 | + | f = e; |
|
| 86 | + | e = d + t1; |
|
| 87 | + | d = c; |
|
| 88 | + | c = b; |
|
| 89 | + | b = a; |
|
| 90 | + | a = t1 + t2; |
|
| 91 | + | i = i + 1; |
|
| 92 | + | } |
|
| 93 | + | ||
| 94 | + | s.h[0] = s.h[0] + a; |
|
| 95 | + | s.h[1] = s.h[1] + b; |
|
| 96 | + | s.h[2] = s.h[2] + c; |
|
| 97 | + | s.h[3] = s.h[3] + d; |
|
| 98 | + | s.h[4] = s.h[4] + e; |
|
| 99 | + | s.h[5] = s.h[5] + f; |
|
| 100 | + | s.h[6] = s.h[6] + g; |
|
| 101 | + | s.h[7] = s.h[7] + hh; |
|
| 102 | + | } |
|
| 103 | + | ||
| 104 | + | /// Reset hash state to initial values. |
|
| 105 | + | fn resetHash(s: *mut Sha256) { |
|
| 106 | + | let mut i: u32 = 0; |
|
| 107 | + | while i < 8 { |
|
| 108 | + | s.h[i] = INIT_H[i]; |
|
| 109 | + | i = i + 1; |
|
| 110 | + | } |
|
| 111 | + | } |
|
| 112 | + | ||
| 113 | + | /// Pad and hash a short message (up to 55 bytes, fits in one 512-bit block). |
|
| 114 | + | fn hashMessage(s: *mut Sha256, k: *[u32], msg: *[u8]) -> i32 { |
|
| 115 | + | // The message must fit in a single block (max 55 bytes for 1-block padding). |
|
| 116 | + | if msg.len > 55 { |
|
| 117 | + | return -1; |
|
| 118 | + | } |
|
| 119 | + | ||
| 120 | + | // Build the 64-byte (512-bit) padded block. |
|
| 121 | + | let mut blockBytes: [u8; 64] = [0; 64]; |
|
| 122 | + | ||
| 123 | + | // Copy message. |
|
| 124 | + | let mut i: u32 = 0; |
|
| 125 | + | while i < msg.len { |
|
| 126 | + | blockBytes[i] = msg[i]; |
|
| 127 | + | i = i + 1; |
|
| 128 | + | } |
|
| 129 | + | ||
| 130 | + | // Append the 1-bit (0x80). |
|
| 131 | + | blockBytes[msg.len] = 0x80; |
|
| 132 | + | ||
| 133 | + | // Append length in bits as big-endian 64-bit integer at the end. |
|
| 134 | + | let bitLen: u32 = msg.len * 8; |
|
| 135 | + | blockBytes[60] = (bitLen >> 24) as u8; |
|
| 136 | + | blockBytes[61] = (bitLen >> 16) as u8; |
|
| 137 | + | blockBytes[62] = (bitLen >> 8) as u8; |
|
| 138 | + | blockBytes[63] = bitLen as u8; |
|
| 139 | + | ||
| 140 | + | // Convert bytes to 16 big-endian 32-bit words. |
|
| 141 | + | let mut block: [u32; 16] = [0; 16]; |
|
| 142 | + | let mut w: u32 = 0; |
|
| 143 | + | while w < 16 { |
|
| 144 | + | let base = w * 4; |
|
| 145 | + | block[w] = (blockBytes[base] as u32 << 24) |
|
| 146 | + | | (blockBytes[base + 1] as u32 << 16) |
|
| 147 | + | | (blockBytes[base + 2] as u32 << 8) |
|
| 148 | + | | (blockBytes[base + 3] as u32); |
|
| 149 | + | w = w + 1; |
|
| 150 | + | } |
|
| 151 | + | ||
| 152 | + | resetHash(s); |
|
| 153 | + | prepareSchedule(s, &block[..]); |
|
| 154 | + | compress(s, k); |
|
| 155 | + | ||
| 156 | + | return 0; |
|
| 157 | + | } |
|
| 158 | + | ||
| 159 | + | /// Test SHA-256 of the empty string "". |
|
| 160 | + | /// Expected: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 |
|
| 161 | + | fn testEmpty(s: *mut Sha256, k: *[u32]) -> i32 { |
|
| 162 | + | if hashMessage(s, k, &[]) != 0 { |
|
| 163 | + | return 1; |
|
| 164 | + | } |
|
| 165 | + | ||
| 166 | + | if s.h[0] != 0xE3B0C442 { |
|
| 167 | + | return 2; |
|
| 168 | + | } |
|
| 169 | + | if s.h[1] != 0x98FC1C14 { |
|
| 170 | + | return 3; |
|
| 171 | + | } |
|
| 172 | + | if s.h[2] != 0x9AFBF4C8 { |
|
| 173 | + | return 4; |
|
| 174 | + | } |
|
| 175 | + | if s.h[3] != 0x996FB924 { |
|
| 176 | + | return 5; |
|
| 177 | + | } |
|
| 178 | + | if s.h[4] != 0x27AE41E4 { |
|
| 179 | + | return 6; |
|
| 180 | + | } |
|
| 181 | + | if s.h[5] != 0x649B934C { |
|
| 182 | + | return 7; |
|
| 183 | + | } |
|
| 184 | + | if s.h[6] != 0xA495991B { |
|
| 185 | + | return 8; |
|
| 186 | + | } |
|
| 187 | + | if s.h[7] != 0x7852B855 { |
|
| 188 | + | return 9; |
|
| 189 | + | } |
|
| 190 | + | return 0; |
|
| 191 | + | } |
|
| 192 | + | ||
| 193 | + | /// Test SHA-256 of "abc". |
|
| 194 | + | /// Expected: ba7816bf 8f01cfea 414140de 5dae2223 b00361a3 96177a9c b410ff61 f20015ad |
|
| 195 | + | fn testAbc(s: *mut Sha256, k: *[u32]) -> i32 { |
|
| 196 | + | let msg: [u8; 3] = [0x61, 0x62, 0x63]; |
|
| 197 | + | if hashMessage(s, k, &msg[..]) != 0 { |
|
| 198 | + | return 1; |
|
| 199 | + | } |
|
| 200 | + | ||
| 201 | + | if s.h[0] != 0xBA7816BF { |
|
| 202 | + | return 2; |
|
| 203 | + | } |
|
| 204 | + | if s.h[1] != 0x8F01CFEA { |
|
| 205 | + | return 3; |
|
| 206 | + | } |
|
| 207 | + | if s.h[2] != 0x414140DE { |
|
| 208 | + | return 4; |
|
| 209 | + | } |
|
| 210 | + | if s.h[3] != 0x5DAE2223 { |
|
| 211 | + | return 5; |
|
| 212 | + | } |
|
| 213 | + | if s.h[4] != 0xB00361A3 { |
|
| 214 | + | return 6; |
|
| 215 | + | } |
|
| 216 | + | if s.h[5] != 0x96177A9C { |
|
| 217 | + | return 7; |
|
| 218 | + | } |
|
| 219 | + | if s.h[6] != 0xB410FF61 { |
|
| 220 | + | return 8; |
|
| 221 | + | } |
|
| 222 | + | if s.h[7] != 0xF20015AD { |
|
| 223 | + | return 9; |
|
| 224 | + | } |
|
| 225 | + | return 0; |
|
| 226 | + | } |
|
| 227 | + | ||
| 228 | + | /// Test the helper functions directly. |
|
| 229 | + | fn testHelpers() -> i32 { |
|
| 230 | + | // rotr(1, 1) should be 0x80000000 |
|
| 231 | + | if rotr(1, 1) != 0x80000000 { |
|
| 232 | + | return 1; |
|
| 233 | + | } |
|
| 234 | + | // rotr(0xFF000000, 8) should be 0x00FF0000 |
|
| 235 | + | if rotr(0xFF000000, 8) != 0x00FF0000 { |
|
| 236 | + | return 2; |
|
| 237 | + | } |
|
| 238 | + | // ch(0xFF, 0x0F, 0xF0): |
|
| 239 | + | // 0xFF & 0x0F = 0x0F |
|
| 240 | + | // ~0xFF = 0xFFFFFF00, & 0xF0 = 0x00 |
|
| 241 | + | // result = 0x0F ^ 0x00 = 0x0F |
|
| 242 | + | if ch(0xFF, 0x0F, 0xF0) != 0x0F { |
|
| 243 | + | return 3; |
|
| 244 | + | } |
|
| 245 | + | // maj(0xFF, 0x0F, 0xF0): |
|
| 246 | + | // 0xFF & 0x0F = 0x0F |
|
| 247 | + | // 0xFF & 0xF0 = 0xF0 |
|
| 248 | + | // 0x0F & 0xF0 = 0x00 |
|
| 249 | + | // result = 0x0F ^ 0xF0 ^ 0x00 = 0xFF |
|
| 250 | + | if maj(0xFF, 0x0F, 0xF0) != 0xFF { |
|
| 251 | + | return 4; |
|
| 252 | + | } |
|
| 253 | + | return 0; |
|
| 254 | + | } |
|
| 255 | + | ||
| 256 | + | @default fn main() -> i32 { |
|
| 257 | + | // SHA-256 round constants (first 32 bits of fractional parts of cube roots |
|
| 258 | + | // of the first 64 primes). |
|
| 259 | + | let k: [u32; 64] = [ |
|
| 260 | + | 0x428A2F98, 0x71374491, 0xB5C0FBCF, 0xE9B5DBA5, |
|
| 261 | + | 0x3956C25B, 0x59F111F1, 0x923F82A4, 0xAB1C5ED5, |
|
| 262 | + | 0xD807AA98, 0x12835B01, 0x243185BE, 0x550C7DC3, |
|
| 263 | + | 0x72BE5D74, 0x80DEB1FE, 0x9BDC06A7, 0xC19BF174, |
|
| 264 | + | 0xE49B69C1, 0xEFBE4786, 0x0FC19DC6, 0x240CA1CC, |
|
| 265 | + | 0x2DE92C6F, 0x4A7484AA, 0x5CB0A9DC, 0x76F988DA, |
|
| 266 | + | 0x983E5152, 0xA831C66D, 0xB00327C8, 0xBF597FC7, |
|
| 267 | + | 0xC6E00BF3, 0xD5A79147, 0x06CA6351, 0x14292967, |
|
| 268 | + | 0x27B70A85, 0x2E1B2138, 0x4D2C6DFC, 0x53380D13, |
|
| 269 | + | 0x650A7354, 0x766A0ABB, 0x81C2C92E, 0x92722C85, |
|
| 270 | + | 0xA2BFE8A1, 0xA81A664B, 0xC24B8B70, 0xC76C51A3, |
|
| 271 | + | 0xD192E819, 0xD6990624, 0xF40E3585, 0x106AA070, |
|
| 272 | + | 0x19A4C116, 0x1E376C08, 0x2748774C, 0x34B0BCB5, |
|
| 273 | + | 0x391C0CB3, 0x4ED8AA4A, 0x5B9CCA4F, 0x682E6FF3, |
|
| 274 | + | 0x748F82EE, 0x78A5636F, 0x84C87814, 0x8CC70208, |
|
| 275 | + | 0x90BEFFFA, 0xA4506CEB, 0xBEF9A3F7, 0xC67178F2 |
|
| 276 | + | ]; |
|
| 277 | + | ||
| 278 | + | let mut s: Sha256 = Sha256 { h: INIT_H, w: [0; 64] }; |
|
| 279 | + | ||
| 280 | + | let r1 = testHelpers(); |
|
| 281 | + | if r1 != 0 { |
|
| 282 | + | return 10 + r1; |
|
| 283 | + | } |
|
| 284 | + | ||
| 285 | + | let r2 = testEmpty(&mut s, &k[..]); |
|
| 286 | + | if r2 != 0 { |
|
| 287 | + | return 20 + r2; |
|
| 288 | + | } |
|
| 289 | + | ||
| 290 | + | let r3 = testAbc(&mut s, &k[..]); |
|
| 291 | + | if r3 != 0 { |
|
| 292 | + | return 30 + r3; |
|
| 293 | + | } |
|
| 294 | + | return 0; |
|
| 295 | + | } |
lib/std/arch/rv64/tests/prog.sieve.rad
added
+138 -0
| 1 | + | //! Sieve of Eratosthenes. |
|
| 2 | + | //! Find all primes up to 256 using a boolean array. |
|
| 3 | + | //! Verify the count of primes matches the known value (54 primes <= 256). |
|
| 4 | + | ||
| 5 | + | /// Mark all multiples of p as composite. |
|
| 6 | + | fn markMultiples(sieve: *mut [bool], p: u32) { |
|
| 7 | + | let mut i: u32 = p * p; |
|
| 8 | + | while i < sieve.len { |
|
| 9 | + | sieve[i] = true; |
|
| 10 | + | i = i + p; |
|
| 11 | + | } |
|
| 12 | + | } |
|
| 13 | + | ||
| 14 | + | /// Run the sieve algorithm. |
|
| 15 | + | fn runSieve(sieve: *mut [bool]) { |
|
| 16 | + | // 0 and 1 are not prime. |
|
| 17 | + | sieve[0] = true; |
|
| 18 | + | sieve[1] = true; |
|
| 19 | + | ||
| 20 | + | let mut p: u32 = 2; |
|
| 21 | + | while p * p < sieve.len { |
|
| 22 | + | if not sieve[p] { |
|
| 23 | + | markMultiples(sieve, p); |
|
| 24 | + | } |
|
| 25 | + | p = p + 1; |
|
| 26 | + | } |
|
| 27 | + | } |
|
| 28 | + | ||
| 29 | + | /// Count the number of primes found. |
|
| 30 | + | fn countPrimes(sieve: *[bool]) -> u32 { |
|
| 31 | + | let mut count: u32 = 0; |
|
| 32 | + | for composite in sieve { |
|
| 33 | + | if not composite { |
|
| 34 | + | count = count + 1; |
|
| 35 | + | } |
|
| 36 | + | } |
|
| 37 | + | return count; |
|
| 38 | + | } |
|
| 39 | + | ||
| 40 | + | /// Collect primes into a slice, return count. |
|
| 41 | + | fn collectPrimes(sieve: *[bool], primes: *mut [u32]) -> u32 { |
|
| 42 | + | let mut count: u32 = 0; |
|
| 43 | + | for composite, idx in sieve { |
|
| 44 | + | if not composite { |
|
| 45 | + | if count < primes.len { |
|
| 46 | + | primes[count] = idx; |
|
| 47 | + | count = count + 1; |
|
| 48 | + | } |
|
| 49 | + | } |
|
| 50 | + | } |
|
| 51 | + | return count; |
|
| 52 | + | } |
|
| 53 | + | ||
| 54 | + | /// Check that specific known primes are marked correctly. |
|
| 55 | + | fn verifyKnownPrimes(sieve: *[bool]) -> i32 { |
|
| 56 | + | // Known small primes. |
|
| 57 | + | let primes: [u32; 6] = [2, 3, 5, 7, 11, 13]; |
|
| 58 | + | for p in primes { |
|
| 59 | + | if sieve[p] { |
|
| 60 | + | return 1; |
|
| 61 | + | } |
|
| 62 | + | } |
|
| 63 | + | // Known small composites. |
|
| 64 | + | let composites: [u32; 5] = [4, 6, 8, 9, 10]; |
|
| 65 | + | for c in composites { |
|
| 66 | + | if not sieve[c] { |
|
| 67 | + | return 7; |
|
| 68 | + | } |
|
| 69 | + | } |
|
| 70 | + | // Larger primes. |
|
| 71 | + | if sieve[97] { |
|
| 72 | + | return 12; |
|
| 73 | + | } |
|
| 74 | + | if sieve[251] { |
|
| 75 | + | return 13; |
|
| 76 | + | } |
|
| 77 | + | // Larger composites. |
|
| 78 | + | if not sieve[100] { |
|
| 79 | + | return 14; |
|
| 80 | + | } |
|
| 81 | + | if not sieve[250] { |
|
| 82 | + | return 15; |
|
| 83 | + | } |
|
| 84 | + | return 0; |
|
| 85 | + | } |
|
| 86 | + | ||
| 87 | + | /// Verify that collected primes are in ascending order and all valid. |
|
| 88 | + | fn verifyCollected(sieve: *[bool]) -> i32 { |
|
| 89 | + | let mut primesBuf: [u32; 64] = [0; 64]; |
|
| 90 | + | let count = collectPrimes(sieve, &mut primesBuf[..]); |
|
| 91 | + | ||
| 92 | + | if count != 54 { |
|
| 93 | + | return 1; |
|
| 94 | + | } |
|
| 95 | + | ||
| 96 | + | // First prime should be 2. |
|
| 97 | + | if primesBuf[0] != 2 { |
|
| 98 | + | return 2; |
|
| 99 | + | } |
|
| 100 | + | // Last prime should be 251. |
|
| 101 | + | if primesBuf[count - 1] != 251 { |
|
| 102 | + | return 3; |
|
| 103 | + | } |
|
| 104 | + | ||
| 105 | + | // Verify ascending order. |
|
| 106 | + | let collected = &primesBuf[0..count]; |
|
| 107 | + | let mut prev: ?u32 = nil; |
|
| 108 | + | for p in collected { |
|
| 109 | + | if let prevVal = prev { |
|
| 110 | + | if prevVal >= p { |
|
| 111 | + | return 4; |
|
| 112 | + | } |
|
| 113 | + | } |
|
| 114 | + | prev = p; |
|
| 115 | + | } |
|
| 116 | + | return 0; |
|
| 117 | + | } |
|
| 118 | + | ||
| 119 | + | @default fn main() -> i32 { |
|
| 120 | + | let mut sieve: [bool; 256] = [false; 256]; |
|
| 121 | + | runSieve(&mut sieve[..]); |
|
| 122 | + | ||
| 123 | + | let r1 = verifyKnownPrimes(&sieve[..]); |
|
| 124 | + | if r1 != 0 { |
|
| 125 | + | return 10 + r1; |
|
| 126 | + | } |
|
| 127 | + | ||
| 128 | + | // There are 54 primes <= 255 (2, 3, 5, ..., 251). |
|
| 129 | + | if countPrimes(&sieve[..]) != 54 { |
|
| 130 | + | return 30; |
|
| 131 | + | } |
|
| 132 | + | ||
| 133 | + | let r3 = verifyCollected(&sieve[..]); |
|
| 134 | + | if r3 != 0 { |
|
| 135 | + | return 40 + r3; |
|
| 136 | + | } |
|
| 137 | + | return 0; |
|
| 138 | + | } |
lib/std/arch/rv64/tests/prog.symtab.rad
added
+481 -0
| 1 | + | //! Symbol table. |
|
| 2 | + | //! Implement a multi-scope symbol table with hash-based lookup, scope |
|
| 3 | + | //! push/pop, and symbol resolution. Exercises: optionals, if-let, |
|
| 4 | + | //! while-let, let-else, for-in with indexing, records with pointer |
|
| 5 | + | //! fields, and complex interactions between data structures. |
|
| 6 | + | ||
| 7 | + | const MAX_SYMBOLS: u32 = 256; |
|
| 8 | + | const MAX_SCOPES: u32 = 16; |
|
| 9 | + | const HASH_SIZE: u32 = 64; |
|
| 10 | + | const NIL: u32 = 0xFFFFFFFF; |
|
| 11 | + | ||
| 12 | + | /// A symbol entry in the table. |
|
| 13 | + | record Symbol { |
|
| 14 | + | /// Name of the symbol (hash for comparison). |
|
| 15 | + | nameHash: u32, |
|
| 16 | + | /// The value associated with this symbol. |
|
| 17 | + | value: i32, |
|
| 18 | + | /// Scope depth at which this symbol was defined. |
|
| 19 | + | depth: u32, |
|
| 20 | + | /// Next symbol in hash chain. |
|
| 21 | + | next: u32, |
|
| 22 | + | /// Previous symbol with the same name (for shadowing). |
|
| 23 | + | shadow: u32, |
|
| 24 | + | } |
|
| 25 | + | ||
| 26 | + | /// A scope boundary marker. |
|
| 27 | + | record ScopeMarker { |
|
| 28 | + | /// Number of symbols when scope was entered. |
|
| 29 | + | symbolCount: u32, |
|
| 30 | + | } |
|
| 31 | + | ||
| 32 | + | /// The symbol table. |
|
| 33 | + | record SymTab { |
|
| 34 | + | symbols: *mut [Symbol], |
|
| 35 | + | symbolCount: u32, |
|
| 36 | + | scopes: *mut [ScopeMarker], |
|
| 37 | + | scopeDepth: u32, |
|
| 38 | + | buckets: *mut [u32], |
|
| 39 | + | } |
|
| 40 | + | ||
| 41 | + | /// Simple string hash function. |
|
| 42 | + | fn hashName(name: *[u8]) -> u32 { |
|
| 43 | + | let mut h: u32 = 5381; |
|
| 44 | + | for ch in name { |
|
| 45 | + | h = ((h << 5) + h) + ch as u32; |
|
| 46 | + | } |
|
| 47 | + | return h; |
|
| 48 | + | } |
|
| 49 | + | ||
| 50 | + | /// Initialize the symbol table. |
|
| 51 | + | fn init(tab: *mut SymTab) { |
|
| 52 | + | tab.symbolCount = 0; |
|
| 53 | + | tab.scopeDepth = 0; |
|
| 54 | + | ||
| 55 | + | for i in 0..HASH_SIZE { |
|
| 56 | + | tab.buckets[i] = NIL; |
|
| 57 | + | } |
|
| 58 | + | } |
|
| 59 | + | ||
| 60 | + | /// Push a new scope. |
|
| 61 | + | fn pushScope(tab: *mut SymTab) { |
|
| 62 | + | tab.scopes[tab.scopeDepth] = ScopeMarker { symbolCount: tab.symbolCount }; |
|
| 63 | + | tab.scopeDepth = tab.scopeDepth + 1; |
|
| 64 | + | } |
|
| 65 | + | ||
| 66 | + | /// Pop the current scope, removing all symbols defined in it. |
|
| 67 | + | fn popScope(tab: *mut SymTab) { |
|
| 68 | + | if tab.scopeDepth == 0 { |
|
| 69 | + | return; |
|
| 70 | + | } |
|
| 71 | + | tab.scopeDepth = tab.scopeDepth - 1; |
|
| 72 | + | let marker = tab.scopes[tab.scopeDepth]; |
|
| 73 | + | ||
| 74 | + | // Remove symbols added in this scope (in reverse order). |
|
| 75 | + | while tab.symbolCount > marker.symbolCount { |
|
| 76 | + | tab.symbolCount = tab.symbolCount - 1; |
|
| 77 | + | let sym = tab.symbols[tab.symbolCount]; |
|
| 78 | + | let bucket = sym.nameHash % HASH_SIZE; |
|
| 79 | + | ||
| 80 | + | // Remove from hash chain. |
|
| 81 | + | tab.buckets[bucket] = sym.next; |
|
| 82 | + | ||
| 83 | + | // Restore shadowed symbol if any. |
|
| 84 | + | if sym.shadow != NIL { |
|
| 85 | + | // The shadowed symbol is still in the symbols array; |
|
| 86 | + | // re-link it into the hash chain. |
|
| 87 | + | let shadowIdx = sym.shadow; |
|
| 88 | + | tab.symbols[shadowIdx].next = tab.buckets[bucket]; |
|
| 89 | + | tab.buckets[bucket] = shadowIdx; |
|
| 90 | + | } |
|
| 91 | + | } |
|
| 92 | + | } |
|
| 93 | + | ||
| 94 | + | /// Define a symbol in the current scope. |
|
| 95 | + | fn define(tab: *mut SymTab, name: *[u8], value: i32) -> u32 { |
|
| 96 | + | let h = hashName(name); |
|
| 97 | + | let bucket = h % HASH_SIZE; |
|
| 98 | + | ||
| 99 | + | // Check for shadowed symbol with same name. |
|
| 100 | + | let mut shadowIdx: u32 = NIL; |
|
| 101 | + | let mut cur = tab.buckets[bucket]; |
|
| 102 | + | while cur != NIL { |
|
| 103 | + | if tab.symbols[cur].nameHash == h { |
|
| 104 | + | // Found existing symbol with same hash - shadow it. |
|
| 105 | + | // Remove it from hash chain first. |
|
| 106 | + | shadowIdx = cur; |
|
| 107 | + | // Remove the shadowed symbol from the bucket chain. |
|
| 108 | + | if tab.buckets[bucket] == cur { |
|
| 109 | + | tab.buckets[bucket] = tab.symbols[cur].next; |
|
| 110 | + | } |
|
| 111 | + | break; |
|
| 112 | + | } |
|
| 113 | + | cur = tab.symbols[cur].next; |
|
| 114 | + | } |
|
| 115 | + | ||
| 116 | + | let idx = tab.symbolCount; |
|
| 117 | + | tab.symbols[idx] = Symbol { |
|
| 118 | + | nameHash: h, |
|
| 119 | + | value, |
|
| 120 | + | depth: tab.scopeDepth, |
|
| 121 | + | next: tab.buckets[bucket], |
|
| 122 | + | shadow: shadowIdx, |
|
| 123 | + | }; |
|
| 124 | + | tab.buckets[bucket] = idx; |
|
| 125 | + | tab.symbolCount = tab.symbolCount + 1; |
|
| 126 | + | return idx; |
|
| 127 | + | } |
|
| 128 | + | ||
| 129 | + | /// Look up a symbol by name. Returns the value if found. |
|
| 130 | + | fn lookup(tab: *SymTab, name: *[u8]) -> ?i32 { |
|
| 131 | + | let h = hashName(name); |
|
| 132 | + | let bucket = h % HASH_SIZE; |
|
| 133 | + | let mut cur = tab.buckets[bucket]; |
|
| 134 | + | ||
| 135 | + | while cur != NIL { |
|
| 136 | + | if tab.symbols[cur].nameHash == h { |
|
| 137 | + | return tab.symbols[cur].value; |
|
| 138 | + | } |
|
| 139 | + | cur = tab.symbols[cur].next; |
|
| 140 | + | } |
|
| 141 | + | return nil; |
|
| 142 | + | } |
|
| 143 | + | ||
| 144 | + | /// Update a symbol's value. Returns true if the symbol was found. |
|
| 145 | + | fn update(tab: *mut SymTab, name: *[u8], newValue: i32) -> bool { |
|
| 146 | + | let h = hashName(name); |
|
| 147 | + | let bucket = h % HASH_SIZE; |
|
| 148 | + | let mut cur = tab.buckets[bucket]; |
|
| 149 | + | ||
| 150 | + | while cur != NIL { |
|
| 151 | + | if tab.symbols[cur].nameHash == h { |
|
| 152 | + | tab.symbols[cur].value = newValue; |
|
| 153 | + | return true; |
|
| 154 | + | } |
|
| 155 | + | cur = tab.symbols[cur].next; |
|
| 156 | + | } |
|
| 157 | + | return false; |
|
| 158 | + | } |
|
| 159 | + | ||
| 160 | + | /// Test basic define and lookup. |
|
| 161 | + | fn testBasic(tab: *mut SymTab) -> i32 { |
|
| 162 | + | init(tab); |
|
| 163 | + | pushScope(tab); |
|
| 164 | + | ||
| 165 | + | define(tab, "x", 10); |
|
| 166 | + | define(tab, "y", 20); |
|
| 167 | + | define(tab, "z", 30); |
|
| 168 | + | ||
| 169 | + | let x = lookup(tab, "x") else { |
|
| 170 | + | return 1; |
|
| 171 | + | }; |
|
| 172 | + | if x != 10 { |
|
| 173 | + | return 2; |
|
| 174 | + | } |
|
| 175 | + | ||
| 176 | + | let y = lookup(tab, "y") else { |
|
| 177 | + | return 3; |
|
| 178 | + | }; |
|
| 179 | + | if y != 20 { |
|
| 180 | + | return 4; |
|
| 181 | + | } |
|
| 182 | + | ||
| 183 | + | let z = lookup(tab, "z") else { |
|
| 184 | + | return 5; |
|
| 185 | + | }; |
|
| 186 | + | if z != 30 { |
|
| 187 | + | return 6; |
|
| 188 | + | } |
|
| 189 | + | ||
| 190 | + | // Lookup nonexistent symbol. |
|
| 191 | + | if let val = lookup(tab, "w") { |
|
| 192 | + | return 7; |
|
| 193 | + | } |
|
| 194 | + | ||
| 195 | + | popScope(tab); |
|
| 196 | + | return 0; |
|
| 197 | + | } |
|
| 198 | + | ||
| 199 | + | /// Test scope shadowing. |
|
| 200 | + | fn testShadowing(tab: *mut SymTab) -> i32 { |
|
| 201 | + | init(tab); |
|
| 202 | + | pushScope(tab); |
|
| 203 | + | define(tab, "x", 1); |
|
| 204 | + | ||
| 205 | + | // Verify outer x. |
|
| 206 | + | let x1 = lookup(tab, "x") else { |
|
| 207 | + | return 1; |
|
| 208 | + | }; |
|
| 209 | + | if x1 != 1 { |
|
| 210 | + | return 2; |
|
| 211 | + | } |
|
| 212 | + | ||
| 213 | + | // Push inner scope, shadow x. |
|
| 214 | + | pushScope(tab); |
|
| 215 | + | define(tab, "x", 2); |
|
| 216 | + | ||
| 217 | + | let x2 = lookup(tab, "x") else { |
|
| 218 | + | return 3; |
|
| 219 | + | }; |
|
| 220 | + | if x2 != 2 { |
|
| 221 | + | return 4; |
|
| 222 | + | } |
|
| 223 | + | ||
| 224 | + | // Pop inner scope, x should revert. |
|
| 225 | + | popScope(tab); |
|
| 226 | + | ||
| 227 | + | let x3 = lookup(tab, "x") else { |
|
| 228 | + | return 5; |
|
| 229 | + | }; |
|
| 230 | + | if x3 != 1 { |
|
| 231 | + | return 6; |
|
| 232 | + | } |
|
| 233 | + | ||
| 234 | + | popScope(tab); |
|
| 235 | + | return 0; |
|
| 236 | + | } |
|
| 237 | + | ||
| 238 | + | /// Test deep nesting with shadowing. |
|
| 239 | + | fn testDeepNesting(tab: *mut SymTab) -> i32 { |
|
| 240 | + | init(tab); |
|
| 241 | + | ||
| 242 | + | // Define x at each of 8 scope levels. |
|
| 243 | + | let mut i: u32 = 0; |
|
| 244 | + | while i < 8 { |
|
| 245 | + | pushScope(tab); |
|
| 246 | + | define(tab, "x", i as i32 * 10); |
|
| 247 | + | i = i + 1; |
|
| 248 | + | } |
|
| 249 | + | ||
| 250 | + | // x should be the innermost value. |
|
| 251 | + | let x = lookup(tab, "x") else { |
|
| 252 | + | return 1; |
|
| 253 | + | }; |
|
| 254 | + | if x != 70 { |
|
| 255 | + | return 2; |
|
| 256 | + | } |
|
| 257 | + | ||
| 258 | + | // Pop scopes one by one and check. |
|
| 259 | + | i = 7; |
|
| 260 | + | while i > 0 { |
|
| 261 | + | popScope(tab); |
|
| 262 | + | let val = lookup(tab, "x") else { |
|
| 263 | + | return 3; |
|
| 264 | + | }; |
|
| 265 | + | let expected = (i - 1) as i32 * 10; |
|
| 266 | + | if val != expected { |
|
| 267 | + | return 4; |
|
| 268 | + | } |
|
| 269 | + | i = i - 1; |
|
| 270 | + | } |
|
| 271 | + | ||
| 272 | + | popScope(tab); |
|
| 273 | + | return 0; |
|
| 274 | + | } |
|
| 275 | + | ||
| 276 | + | /// Test multiple symbols per scope. |
|
| 277 | + | fn testMultipleSymbols(tab: *mut SymTab) -> i32 { |
|
| 278 | + | init(tab); |
|
| 279 | + | pushScope(tab); |
|
| 280 | + | ||
| 281 | + | // Define a bunch of symbols. |
|
| 282 | + | let names: [*[u8]; 8] = ["a", "bb", "ccc", "dddd", "eeeee", "ff", "ggg", "h"]; |
|
| 283 | + | let values: [i32; 8] = [1, 2, 3, 4, 5, 6, 7, 8]; |
|
| 284 | + | ||
| 285 | + | for name, i in names { |
|
| 286 | + | define(tab, name, values[i]); |
|
| 287 | + | } |
|
| 288 | + | ||
| 289 | + | // Verify all of them. |
|
| 290 | + | let mut sum: i32 = 0; |
|
| 291 | + | for name, i in names { |
|
| 292 | + | if let val = lookup(tab, name) { |
|
| 293 | + | sum = sum + val; |
|
| 294 | + | } else { |
|
| 295 | + | return 1; |
|
| 296 | + | } |
|
| 297 | + | } |
|
| 298 | + | ||
| 299 | + | // 1+2+3+4+5+6+7+8 = 36 |
|
| 300 | + | if sum != 36 { |
|
| 301 | + | return 2; |
|
| 302 | + | } |
|
| 303 | + | ||
| 304 | + | popScope(tab); |
|
| 305 | + | return 0; |
|
| 306 | + | } |
|
| 307 | + | ||
| 308 | + | /// Test update functionality. |
|
| 309 | + | fn testUpdate(tab: *mut SymTab) -> i32 { |
|
| 310 | + | init(tab); |
|
| 311 | + | pushScope(tab); |
|
| 312 | + | ||
| 313 | + | define(tab, "counter", 0); |
|
| 314 | + | ||
| 315 | + | // Increment counter 10 times. |
|
| 316 | + | let mut i: u32 = 0; |
|
| 317 | + | while i < 10 { |
|
| 318 | + | let cur = lookup(tab, "counter") else { |
|
| 319 | + | return 1; |
|
| 320 | + | }; |
|
| 321 | + | if not update(tab, "counter", cur + 1) { |
|
| 322 | + | return 2; |
|
| 323 | + | } |
|
| 324 | + | i = i + 1; |
|
| 325 | + | } |
|
| 326 | + | ||
| 327 | + | let final_val = lookup(tab, "counter") else { |
|
| 328 | + | return 3; |
|
| 329 | + | }; |
|
| 330 | + | if final_val != 10 { |
|
| 331 | + | return 4; |
|
| 332 | + | } |
|
| 333 | + | ||
| 334 | + | // Update nonexistent symbol should fail. |
|
| 335 | + | if update(tab, "nonexistent", 99) { |
|
| 336 | + | return 5; |
|
| 337 | + | } |
|
| 338 | + | ||
| 339 | + | popScope(tab); |
|
| 340 | + | return 0; |
|
| 341 | + | } |
|
| 342 | + | ||
| 343 | + | /// Test scope isolation: symbols in popped scopes are gone. |
|
| 344 | + | fn testScopeIsolation(tab: *mut SymTab) -> i32 { |
|
| 345 | + | init(tab); |
|
| 346 | + | ||
| 347 | + | pushScope(tab); |
|
| 348 | + | define(tab, "outer", 1); |
|
| 349 | + | ||
| 350 | + | pushScope(tab); |
|
| 351 | + | define(tab, "inner", 2); |
|
| 352 | + | ||
| 353 | + | // Both visible. |
|
| 354 | + | if let val = lookup(tab, "outer") { |
|
| 355 | + | if val != 1 { |
|
| 356 | + | return 1; |
|
| 357 | + | } |
|
| 358 | + | } else { |
|
| 359 | + | return 2; |
|
| 360 | + | } |
|
| 361 | + | if let val = lookup(tab, "inner") { |
|
| 362 | + | if val != 2 { |
|
| 363 | + | return 3; |
|
| 364 | + | } |
|
| 365 | + | } else { |
|
| 366 | + | return 4; |
|
| 367 | + | } |
|
| 368 | + | ||
| 369 | + | popScope(tab); |
|
| 370 | + | ||
| 371 | + | // outer still visible, inner gone. |
|
| 372 | + | if let val = lookup(tab, "outer") { |
|
| 373 | + | if val != 1 { |
|
| 374 | + | return 5; |
|
| 375 | + | } |
|
| 376 | + | } else { |
|
| 377 | + | return 6; |
|
| 378 | + | } |
|
| 379 | + | if let val = lookup(tab, "inner") { |
|
| 380 | + | return 7; |
|
| 381 | + | } |
|
| 382 | + | ||
| 383 | + | popScope(tab); |
|
| 384 | + | return 0; |
|
| 385 | + | } |
|
| 386 | + | ||
| 387 | + | /// Test interleaved defines and lookups across scopes using while-let. |
|
| 388 | + | fn testInterleaved(tab: *mut SymTab) -> i32 { |
|
| 389 | + | init(tab); |
|
| 390 | + | pushScope(tab); |
|
| 391 | + | ||
| 392 | + | define(tab, "a", 100); |
|
| 393 | + | define(tab, "b", 200); |
|
| 394 | + | ||
| 395 | + | pushScope(tab); |
|
| 396 | + | define(tab, "a", 111); |
|
| 397 | + | define(tab, "c", 300); |
|
| 398 | + | ||
| 399 | + | // Verify values with expected lookup results. |
|
| 400 | + | let queries: [*[u8]; 4] = ["a", "b", "c", "d"]; |
|
| 401 | + | let expected: [?i32; 4] = [111, 200, 300, nil]; |
|
| 402 | + | let mut failures: u32 = 0; |
|
| 403 | + | ||
| 404 | + | for name, i in queries { |
|
| 405 | + | let result = lookup(tab, name); |
|
| 406 | + | if let exp = expected[i] { |
|
| 407 | + | // We expect a value. |
|
| 408 | + | if let r = result { |
|
| 409 | + | if r != exp { |
|
| 410 | + | failures = failures + 1; |
|
| 411 | + | } |
|
| 412 | + | } else { |
|
| 413 | + | failures = failures + 1; |
|
| 414 | + | } |
|
| 415 | + | } else { |
|
| 416 | + | // We expect nil. |
|
| 417 | + | if let _ = result { |
|
| 418 | + | failures = failures + 1; |
|
| 419 | + | } |
|
| 420 | + | } |
|
| 421 | + | } |
|
| 422 | + | ||
| 423 | + | if failures != 0 { |
|
| 424 | + | return failures as i32; |
|
| 425 | + | } |
|
| 426 | + | ||
| 427 | + | popScope(tab); |
|
| 428 | + | popScope(tab); |
|
| 429 | + | return 0; |
|
| 430 | + | } |
|
| 431 | + | ||
| 432 | + | @default fn main() -> i32 { |
|
| 433 | + | let mut symbols: [Symbol; 256] = [Symbol { nameHash: 0, value: 0, depth: 0, next: NIL, shadow: NIL }; 256]; |
|
| 434 | + | let mut scopes: [ScopeMarker; 16] = [ScopeMarker { symbolCount: 0 }; 16]; |
|
| 435 | + | let mut buckets: [u32; 64] = [NIL; 64]; |
|
| 436 | + | ||
| 437 | + | let mut tab = SymTab { |
|
| 438 | + | symbols: &mut symbols[..], |
|
| 439 | + | symbolCount: 0, |
|
| 440 | + | scopes: &mut scopes[..], |
|
| 441 | + | scopeDepth: 0, |
|
| 442 | + | buckets: &mut buckets[..], |
|
| 443 | + | }; |
|
| 444 | + | ||
| 445 | + | let r1 = testBasic(&mut tab); |
|
| 446 | + | if r1 != 0 { |
|
| 447 | + | return 10 + r1; |
|
| 448 | + | } |
|
| 449 | + | ||
| 450 | + | let r2 = testShadowing(&mut tab); |
|
| 451 | + | if r2 != 0 { |
|
| 452 | + | return 20 + r2; |
|
| 453 | + | } |
|
| 454 | + | ||
| 455 | + | let r3 = testDeepNesting(&mut tab); |
|
| 456 | + | if r3 != 0 { |
|
| 457 | + | return 30 + r3; |
|
| 458 | + | } |
|
| 459 | + | ||
| 460 | + | let r4 = testMultipleSymbols(&mut tab); |
|
| 461 | + | if r4 != 0 { |
|
| 462 | + | return 40 + r4; |
|
| 463 | + | } |
|
| 464 | + | ||
| 465 | + | let r5 = testUpdate(&mut tab); |
|
| 466 | + | if r5 != 0 { |
|
| 467 | + | return 50 + r5; |
|
| 468 | + | } |
|
| 469 | + | ||
| 470 | + | let r6 = testScopeIsolation(&mut tab); |
|
| 471 | + | if r6 != 0 { |
|
| 472 | + | return 60 + r6; |
|
| 473 | + | } |
|
| 474 | + | ||
| 475 | + | let r7 = testInterleaved(&mut tab); |
|
| 476 | + | if r7 != 0 { |
|
| 477 | + | return 70 + r7; |
|
| 478 | + | } |
|
| 479 | + | ||
| 480 | + | return 0; |
|
| 481 | + | } |
lib/std/arch/rv64/tests/prog.tokenizer.rad
added
+590 -0
| 1 | + | //! Tokenizer / lexer. |
|
| 2 | + | //! A simplified lexer that tokenizes arithmetic expressions into tokens, |
|
| 3 | + | //! then parses and evaluates them. Exercises: tagged unions with payloads, |
|
| 4 | + | //! match statements, optionals, if-let, while-let, for-in loops, records |
|
| 5 | + | //! with slice fields, and complex control flow. |
|
| 6 | + | ||
| 7 | + | /// Token types produced by the lexer. |
|
| 8 | + | union Token { |
|
| 9 | + | /// Integer literal with its value. |
|
| 10 | + | Number(i32), |
|
| 11 | + | /// + |
|
| 12 | + | Plus, |
|
| 13 | + | /// - |
|
| 14 | + | Minus, |
|
| 15 | + | /// * |
|
| 16 | + | Star, |
|
| 17 | + | /// / |
|
| 18 | + | Slash, |
|
| 19 | + | /// ( |
|
| 20 | + | LParen, |
|
| 21 | + | /// ) |
|
| 22 | + | RParen, |
|
| 23 | + | /// End of input. |
|
| 24 | + | Eof, |
|
| 25 | + | /// Invalid character. |
|
| 26 | + | Invalid(u8), |
|
| 27 | + | } |
|
| 28 | + | ||
| 29 | + | /// A lexer state over a byte slice. |
|
| 30 | + | record Lexer { |
|
| 31 | + | source: *[u8], |
|
| 32 | + | pos: u32, |
|
| 33 | + | } |
|
| 34 | + | ||
| 35 | + | /// A list of tokens with a fixed-size backing store. |
|
| 36 | + | record TokenList { |
|
| 37 | + | tokens: *mut [Token], |
|
| 38 | + | count: u32, |
|
| 39 | + | } |
|
| 40 | + | ||
| 41 | + | /// AST node for parsed expressions. |
|
| 42 | + | union Expr { |
|
| 43 | + | Num(i32), |
|
| 44 | + | BinOp(BinOpData), |
|
| 45 | + | Neg(u32), |
|
| 46 | + | } |
|
| 47 | + | ||
| 48 | + | /// Binary operation data. |
|
| 49 | + | record BinOpData { |
|
| 50 | + | op: u8, |
|
| 51 | + | left: u32, |
|
| 52 | + | right: u32, |
|
| 53 | + | } |
|
| 54 | + | ||
| 55 | + | /// Pool of AST nodes. |
|
| 56 | + | record ExprPool { |
|
| 57 | + | nodes: *mut [Expr], |
|
| 58 | + | count: u32, |
|
| 59 | + | } |
|
| 60 | + | ||
| 61 | + | /// Parser state. |
|
| 62 | + | record Parser { |
|
| 63 | + | tokens: *[Token], |
|
| 64 | + | tokenCount: u32, |
|
| 65 | + | pos: u32, |
|
| 66 | + | pool: *mut ExprPool, |
|
| 67 | + | } |
|
| 68 | + | ||
| 69 | + | /// Check if a byte is a digit. |
|
| 70 | + | fn isDigit(c: u8) -> bool { |
|
| 71 | + | return c >= 48 and c <= 57; |
|
| 72 | + | } |
|
| 73 | + | ||
| 74 | + | /// Check if a byte is whitespace. |
|
| 75 | + | fn isSpace(c: u8) -> bool { |
|
| 76 | + | return c == 32 or c == 9 or c == 10 or c == 13; |
|
| 77 | + | } |
|
| 78 | + | ||
| 79 | + | /// Peek at the current character, returning nil at end. |
|
| 80 | + | fn peek(lex: *Lexer) -> ?u8 { |
|
| 81 | + | if lex.pos < lex.source.len { |
|
| 82 | + | return lex.source[lex.pos]; |
|
| 83 | + | } |
|
| 84 | + | return nil; |
|
| 85 | + | } |
|
| 86 | + | ||
| 87 | + | /// Advance the lexer by one character. |
|
| 88 | + | fn advance(lex: *mut Lexer) { |
|
| 89 | + | if lex.pos < lex.source.len { |
|
| 90 | + | lex.pos = lex.pos + 1; |
|
| 91 | + | } |
|
| 92 | + | } |
|
| 93 | + | ||
| 94 | + | /// Skip whitespace characters. |
|
| 95 | + | fn skipWhitespace(lex: *mut Lexer) { |
|
| 96 | + | while let ch = peek(lex); isSpace(ch) { |
|
| 97 | + | advance(lex); |
|
| 98 | + | } |
|
| 99 | + | } |
|
| 100 | + | ||
| 101 | + | /// Scan a number literal. |
|
| 102 | + | fn scanNumber(lex: *mut Lexer) -> i32 { |
|
| 103 | + | let mut value: i32 = 0; |
|
| 104 | + | while let ch = peek(lex); isDigit(ch) { |
|
| 105 | + | value = value * 10 + (ch - 48) as i32; |
|
| 106 | + | advance(lex); |
|
| 107 | + | } |
|
| 108 | + | return value; |
|
| 109 | + | } |
|
| 110 | + | ||
| 111 | + | /// Get the next token from the lexer. |
|
| 112 | + | fn nextToken(lex: *mut Lexer) -> Token { |
|
| 113 | + | skipWhitespace(lex); |
|
| 114 | + | ||
| 115 | + | if let ch = peek(lex) { |
|
| 116 | + | if isDigit(ch) { |
|
| 117 | + | return Token::Number(scanNumber(lex)); |
|
| 118 | + | } |
|
| 119 | + | ||
| 120 | + | advance(lex); |
|
| 121 | + | match ch { |
|
| 122 | + | case 43 => { return Token::Plus; } |
|
| 123 | + | case 45 => { return Token::Minus; } |
|
| 124 | + | case 42 => { return Token::Star; } |
|
| 125 | + | case 47 => { return Token::Slash; } |
|
| 126 | + | case 40 => { return Token::LParen; } |
|
| 127 | + | case 41 => { return Token::RParen; } |
|
| 128 | + | else => { return Token::Invalid(ch); } |
|
| 129 | + | } |
|
| 130 | + | } |
|
| 131 | + | return Token::Eof; |
|
| 132 | + | } |
|
| 133 | + | ||
| 134 | + | /// Tokenize the entire source into a token list. |
|
| 135 | + | fn tokenize(lex: *mut Lexer, list: *mut TokenList) -> bool { |
|
| 136 | + | let mut done: bool = false; |
|
| 137 | + | while not done { |
|
| 138 | + | let tok = nextToken(lex); |
|
| 139 | + | match tok { |
|
| 140 | + | case Token::Invalid(_) => { |
|
| 141 | + | return false; |
|
| 142 | + | } |
|
| 143 | + | case Token::Eof => { |
|
| 144 | + | list.tokens[list.count] = tok; |
|
| 145 | + | list.count = list.count + 1; |
|
| 146 | + | done = true; |
|
| 147 | + | } |
|
| 148 | + | else => { |
|
| 149 | + | if list.count >= list.tokens.len - 1 { |
|
| 150 | + | return false; |
|
| 151 | + | } |
|
| 152 | + | list.tokens[list.count] = tok; |
|
| 153 | + | list.count = list.count + 1; |
|
| 154 | + | } |
|
| 155 | + | } |
|
| 156 | + | } |
|
| 157 | + | return true; |
|
| 158 | + | } |
|
| 159 | + | ||
| 160 | + | /// Allocate a new expression node. |
|
| 161 | + | fn newExpr(pool: *mut ExprPool, expr: Expr) -> u32 { |
|
| 162 | + | let idx = pool.count; |
|
| 163 | + | pool.nodes[idx] = expr; |
|
| 164 | + | pool.count = pool.count + 1; |
|
| 165 | + | return idx; |
|
| 166 | + | } |
|
| 167 | + | ||
| 168 | + | /// Get the current token in the parser. |
|
| 169 | + | fn currentToken(p: *Parser) -> Token { |
|
| 170 | + | if p.pos < p.tokenCount { |
|
| 171 | + | return p.tokens[p.pos]; |
|
| 172 | + | } |
|
| 173 | + | return Token::Eof; |
|
| 174 | + | } |
|
| 175 | + | ||
| 176 | + | /// Advance the parser to the next token. |
|
| 177 | + | fn advanceParser(p: *mut Parser) { |
|
| 178 | + | if p.pos < p.tokenCount { |
|
| 179 | + | p.pos = p.pos + 1; |
|
| 180 | + | } |
|
| 181 | + | } |
|
| 182 | + | ||
| 183 | + | /// Parse a primary expression (number, parenthesized expression, or unary minus). |
|
| 184 | + | fn parsePrimary(p: *mut Parser) -> ?u32 { |
|
| 185 | + | let tok = currentToken(p); |
|
| 186 | + | ||
| 187 | + | match tok { |
|
| 188 | + | case Token::Number(n) => { |
|
| 189 | + | advanceParser(p); |
|
| 190 | + | return newExpr(p.pool, Expr::Num(n)); |
|
| 191 | + | } |
|
| 192 | + | case Token::LParen => { |
|
| 193 | + | advanceParser(p); |
|
| 194 | + | let inner = parsePrimary(p) else { |
|
| 195 | + | return nil; |
|
| 196 | + | }; |
|
| 197 | + | // Actually parse a full expression inside parens. |
|
| 198 | + | let expr = parseAddSub(p, inner); |
|
| 199 | + | let close = currentToken(p); |
|
| 200 | + | match close { |
|
| 201 | + | case Token::RParen => { |
|
| 202 | + | advanceParser(p); |
|
| 203 | + | return expr; |
|
| 204 | + | } |
|
| 205 | + | else => { |
|
| 206 | + | return nil; |
|
| 207 | + | } |
|
| 208 | + | } |
|
| 209 | + | } |
|
| 210 | + | case Token::Minus => { |
|
| 211 | + | advanceParser(p); |
|
| 212 | + | let operand = parsePrimary(p) else { |
|
| 213 | + | return nil; |
|
| 214 | + | }; |
|
| 215 | + | return newExpr(p.pool, Expr::Neg(operand)); |
|
| 216 | + | } |
|
| 217 | + | else => { |
|
| 218 | + | return nil; |
|
| 219 | + | } |
|
| 220 | + | } |
|
| 221 | + | } |
|
| 222 | + | ||
| 223 | + | /// Parse multiplication and division (higher precedence). |
|
| 224 | + | fn parseMulDiv(p: *mut Parser, left: u32) -> u32 { |
|
| 225 | + | let mut result: u32 = left; |
|
| 226 | + | let mut cont: bool = true; |
|
| 227 | + | ||
| 228 | + | while cont { |
|
| 229 | + | let tok = currentToken(p); |
|
| 230 | + | match tok { |
|
| 231 | + | case Token::Star => { |
|
| 232 | + | advanceParser(p); |
|
| 233 | + | if let right = parsePrimary(p) { |
|
| 234 | + | result = newExpr(p.pool, Expr::BinOp(BinOpData { op: 42, left: result, right })); |
|
| 235 | + | } else { |
|
| 236 | + | cont = false; |
|
| 237 | + | } |
|
| 238 | + | } |
|
| 239 | + | case Token::Slash => { |
|
| 240 | + | advanceParser(p); |
|
| 241 | + | if let right = parsePrimary(p) { |
|
| 242 | + | result = newExpr(p.pool, Expr::BinOp(BinOpData { op: 47, left: result, right })); |
|
| 243 | + | } else { |
|
| 244 | + | cont = false; |
|
| 245 | + | } |
|
| 246 | + | } |
|
| 247 | + | else => { |
|
| 248 | + | cont = false; |
|
| 249 | + | } |
|
| 250 | + | } |
|
| 251 | + | } |
|
| 252 | + | return result; |
|
| 253 | + | } |
|
| 254 | + | ||
| 255 | + | /// Parse addition and subtraction (lower precedence). |
|
| 256 | + | fn parseAddSub(p: *mut Parser, left: u32) -> u32 { |
|
| 257 | + | let mut result: u32 = parseMulDiv(p, left); |
|
| 258 | + | let mut cont: bool = true; |
|
| 259 | + | ||
| 260 | + | while cont { |
|
| 261 | + | let tok = currentToken(p); |
|
| 262 | + | match tok { |
|
| 263 | + | case Token::Plus => { |
|
| 264 | + | advanceParser(p); |
|
| 265 | + | if let right = parsePrimary(p) { |
|
| 266 | + | let rhs = parseMulDiv(p, right); |
|
| 267 | + | result = newExpr(p.pool, Expr::BinOp(BinOpData { op: 43, left: result, right: rhs })); |
|
| 268 | + | } else { |
|
| 269 | + | cont = false; |
|
| 270 | + | } |
|
| 271 | + | } |
|
| 272 | + | case Token::Minus => { |
|
| 273 | + | advanceParser(p); |
|
| 274 | + | if let right = parsePrimary(p) { |
|
| 275 | + | let rhs = parseMulDiv(p, right); |
|
| 276 | + | result = newExpr(p.pool, Expr::BinOp(BinOpData { op: 45, left: result, right: rhs })); |
|
| 277 | + | } else { |
|
| 278 | + | cont = false; |
|
| 279 | + | } |
|
| 280 | + | } |
|
| 281 | + | else => { |
|
| 282 | + | cont = false; |
|
| 283 | + | } |
|
| 284 | + | } |
|
| 285 | + | } |
|
| 286 | + | return result; |
|
| 287 | + | } |
|
| 288 | + | ||
| 289 | + | /// Parse a full expression. |
|
| 290 | + | fn parseExpr(p: *mut Parser) -> ?u32 { |
|
| 291 | + | let left = parsePrimary(p) else { |
|
| 292 | + | return nil; |
|
| 293 | + | }; |
|
| 294 | + | return parseAddSub(p, left); |
|
| 295 | + | } |
|
| 296 | + | ||
| 297 | + | /// Evaluate an expression tree. |
|
| 298 | + | fn eval(nodes: *[Expr], idx: u32) -> i32 { |
|
| 299 | + | let node = nodes[idx]; |
|
| 300 | + | match node { |
|
| 301 | + | case Expr::Num(n) => { |
|
| 302 | + | return n; |
|
| 303 | + | } |
|
| 304 | + | case Expr::BinOp(data) => { |
|
| 305 | + | let l = eval(nodes, data.left); |
|
| 306 | + | let r = eval(nodes, data.right); |
|
| 307 | + | if data.op == 43 { |
|
| 308 | + | return l + r; |
|
| 309 | + | } else if data.op == 45 { |
|
| 310 | + | return l - r; |
|
| 311 | + | } else if data.op == 42 { |
|
| 312 | + | return l * r; |
|
| 313 | + | } else if data.op == 47 { |
|
| 314 | + | if r == 0 { |
|
| 315 | + | return 0; |
|
| 316 | + | } |
|
| 317 | + | return l / r; |
|
| 318 | + | } |
|
| 319 | + | return 0; |
|
| 320 | + | } |
|
| 321 | + | case Expr::Neg(child) => { |
|
| 322 | + | return 0 - eval(nodes, child); |
|
| 323 | + | } |
|
| 324 | + | } |
|
| 325 | + | } |
|
| 326 | + | ||
| 327 | + | /// Count nodes in an expression tree. |
|
| 328 | + | fn countNodes(nodes: *[Expr], idx: u32) -> u32 { |
|
| 329 | + | let node = nodes[idx]; |
|
| 330 | + | match node { |
|
| 331 | + | case Expr::Num(_) => { return 1; } |
|
| 332 | + | case Expr::BinOp(data) => { |
|
| 333 | + | return 1 + countNodes(nodes, data.left) + countNodes(nodes, data.right); |
|
| 334 | + | } |
|
| 335 | + | case Expr::Neg(child) => { |
|
| 336 | + | return 1 + countNodes(nodes, child); |
|
| 337 | + | } |
|
| 338 | + | } |
|
| 339 | + | } |
|
| 340 | + | ||
| 341 | + | /// Helper: tokenize, parse, and evaluate a string expression. |
|
| 342 | + | fn evaluate( |
|
| 343 | + | source: *[u8], |
|
| 344 | + | tokenBuf: *mut [Token], |
|
| 345 | + | exprBuf: *mut [Expr] |
|
| 346 | + | ) -> ?i32 { |
|
| 347 | + | let mut lex = Lexer { source, pos: 0 }; |
|
| 348 | + | let mut list = TokenList { tokens: tokenBuf, count: 0 }; |
|
| 349 | + | ||
| 350 | + | if not tokenize(&mut lex, &mut list) { |
|
| 351 | + | return nil; |
|
| 352 | + | } |
|
| 353 | + | ||
| 354 | + | let mut pool = ExprPool { nodes: exprBuf, count: 0 }; |
|
| 355 | + | let mut parser = Parser { |
|
| 356 | + | tokens: tokenBuf, |
|
| 357 | + | tokenCount: list.count, |
|
| 358 | + | pos: 0, |
|
| 359 | + | pool: &mut pool, |
|
| 360 | + | }; |
|
| 361 | + | ||
| 362 | + | let root = parseExpr(&mut parser) else { |
|
| 363 | + | return nil; |
|
| 364 | + | }; |
|
| 365 | + | return eval(exprBuf, root); |
|
| 366 | + | } |
|
| 367 | + | ||
| 368 | + | /// Test simple number. |
|
| 369 | + | fn testNumber(tokenBuf: *mut [Token], exprBuf: *mut [Expr]) -> i32 { |
|
| 370 | + | let result = evaluate("42", tokenBuf, exprBuf) else { |
|
| 371 | + | return 1; |
|
| 372 | + | }; |
|
| 373 | + | if result != 42 { |
|
| 374 | + | return 2; |
|
| 375 | + | } |
|
| 376 | + | return 0; |
|
| 377 | + | } |
|
| 378 | + | ||
| 379 | + | /// Test addition. |
|
| 380 | + | fn testAdd(tokenBuf: *mut [Token], exprBuf: *mut [Expr]) -> i32 { |
|
| 381 | + | let result = evaluate("3 + 4", tokenBuf, exprBuf) else { |
|
| 382 | + | return 1; |
|
| 383 | + | }; |
|
| 384 | + | if result != 7 { |
|
| 385 | + | return 2; |
|
| 386 | + | } |
|
| 387 | + | return 0; |
|
| 388 | + | } |
|
| 389 | + | ||
| 390 | + | /// Test precedence: multiplication before addition. |
|
| 391 | + | fn testPrecedence(tokenBuf: *mut [Token], exprBuf: *mut [Expr]) -> i32 { |
|
| 392 | + | let result = evaluate("2 + 3 * 4", tokenBuf, exprBuf) else { |
|
| 393 | + | return 1; |
|
| 394 | + | }; |
|
| 395 | + | if result != 14 { |
|
| 396 | + | return 2; |
|
| 397 | + | } |
|
| 398 | + | return 0; |
|
| 399 | + | } |
|
| 400 | + | ||
| 401 | + | /// Test parenthesized expression. |
|
| 402 | + | fn testParens(tokenBuf: *mut [Token], exprBuf: *mut [Expr]) -> i32 { |
|
| 403 | + | let result = evaluate("(2 + 3) * 4", tokenBuf, exprBuf) else { |
|
| 404 | + | return 1; |
|
| 405 | + | }; |
|
| 406 | + | if result != 20 { |
|
| 407 | + | return 2; |
|
| 408 | + | } |
|
| 409 | + | return 0; |
|
| 410 | + | } |
|
| 411 | + | ||
| 412 | + | /// Test negation. |
|
| 413 | + | fn testNeg(tokenBuf: *mut [Token], exprBuf: *mut [Expr]) -> i32 { |
|
| 414 | + | let result = evaluate("-5 + 8", tokenBuf, exprBuf) else { |
|
| 415 | + | return 1; |
|
| 416 | + | }; |
|
| 417 | + | if result != 3 { |
|
| 418 | + | return 2; |
|
| 419 | + | } |
|
| 420 | + | return 0; |
|
| 421 | + | } |
|
| 422 | + | ||
| 423 | + | /// Test complex expression. |
|
| 424 | + | fn testComplex(tokenBuf: *mut [Token], exprBuf: *mut [Expr]) -> i32 { |
|
| 425 | + | // (10 - 3) * (2 + 1) = 7 * 3 = 21 |
|
| 426 | + | let result = evaluate("(10 - 3) * (2 + 1)", tokenBuf, exprBuf) else { |
|
| 427 | + | return 1; |
|
| 428 | + | }; |
|
| 429 | + | if result != 21 { |
|
| 430 | + | return 2; |
|
| 431 | + | } |
|
| 432 | + | return 0; |
|
| 433 | + | } |
|
| 434 | + | ||
| 435 | + | /// Test chained operations. |
|
| 436 | + | fn testChained(tokenBuf: *mut [Token], exprBuf: *mut [Expr]) -> i32 { |
|
| 437 | + | // 100 - 20 - 30 - 10 = 40 |
|
| 438 | + | let result = evaluate("100 - 20 - 30 - 10", tokenBuf, exprBuf) else { |
|
| 439 | + | return 1; |
|
| 440 | + | }; |
|
| 441 | + | if result != 40 { |
|
| 442 | + | return 2; |
|
| 443 | + | } |
|
| 444 | + | return 0; |
|
| 445 | + | } |
|
| 446 | + | ||
| 447 | + | /// Test token counting via for-in. |
|
| 448 | + | fn testTokenCount(tokenBuf: *mut [Token], exprBuf: *mut [Expr]) -> i32 { |
|
| 449 | + | let mut lex = Lexer { source: "1 + 2 * 3", pos: 0 }; |
|
| 450 | + | let mut list = TokenList { tokens: tokenBuf, count: 0 }; |
|
| 451 | + | if not tokenize(&mut lex, &mut list) { |
|
| 452 | + | return 1; |
|
| 453 | + | } |
|
| 454 | + | ||
| 455 | + | // Should be: 1, +, 2, *, 3, Eof = 6 tokens |
|
| 456 | + | if list.count != 6 { |
|
| 457 | + | return 2; |
|
| 458 | + | } |
|
| 459 | + | ||
| 460 | + | // Count number tokens using for-in. |
|
| 461 | + | let mut numCount: u32 = 0; |
|
| 462 | + | let slice = tokenBuf[0..list.count]; |
|
| 463 | + | for tok in slice { |
|
| 464 | + | match tok { |
|
| 465 | + | case Token::Number(_) => { |
|
| 466 | + | numCount = numCount + 1; |
|
| 467 | + | } |
|
| 468 | + | else => {} |
|
| 469 | + | } |
|
| 470 | + | } |
|
| 471 | + | if numCount != 3 { |
|
| 472 | + | return 3; |
|
| 473 | + | } |
|
| 474 | + | ||
| 475 | + | return 0; |
|
| 476 | + | } |
|
| 477 | + | ||
| 478 | + | /// Test division and mixed operations. |
|
| 479 | + | fn testDivision(tokenBuf: *mut [Token], exprBuf: *mut [Expr]) -> i32 { |
|
| 480 | + | // 20 / 4 + 3 = 5 + 3 = 8 |
|
| 481 | + | let result = evaluate("20 / 4 + 3", tokenBuf, exprBuf) else { |
|
| 482 | + | return 1; |
|
| 483 | + | }; |
|
| 484 | + | if result != 8 { |
|
| 485 | + | return 2; |
|
| 486 | + | } |
|
| 487 | + | return 0; |
|
| 488 | + | } |
|
| 489 | + | ||
| 490 | + | /// Test deeply nested parentheses. |
|
| 491 | + | fn testDeepNesting(tokenBuf: *mut [Token], exprBuf: *mut [Expr]) -> i32 { |
|
| 492 | + | // ((((5)))) = 5 |
|
| 493 | + | let result = evaluate("((((5))))", tokenBuf, exprBuf) else { |
|
| 494 | + | return 1; |
|
| 495 | + | }; |
|
| 496 | + | if result != 5 { |
|
| 497 | + | return 2; |
|
| 498 | + | } |
|
| 499 | + | return 0; |
|
| 500 | + | } |
|
| 501 | + | ||
| 502 | + | /// Test node count of complex expression. |
|
| 503 | + | fn testNodeCount(tokenBuf: *mut [Token], exprBuf: *mut [Expr]) -> i32 { |
|
| 504 | + | let mut lex = Lexer { source: "1 + 2 * 3", pos: 0 }; |
|
| 505 | + | let mut list = TokenList { tokens: tokenBuf, count: 0 }; |
|
| 506 | + | if not tokenize(&mut lex, &mut list) { |
|
| 507 | + | return 1; |
|
| 508 | + | } |
|
| 509 | + | ||
| 510 | + | let mut pool = ExprPool { nodes: exprBuf, count: 0 }; |
|
| 511 | + | let mut parser = Parser { |
|
| 512 | + | tokens: tokenBuf, |
|
| 513 | + | tokenCount: list.count, |
|
| 514 | + | pos: 0, |
|
| 515 | + | pool: &mut pool, |
|
| 516 | + | }; |
|
| 517 | + | ||
| 518 | + | let root = parseExpr(&mut parser) else { |
|
| 519 | + | return 2; |
|
| 520 | + | }; |
|
| 521 | + | ||
| 522 | + | // 1 + (2 * 3) = 5 nodes: Num(1), Num(2), Num(3), BinOp(*), BinOp(+) |
|
| 523 | + | let count = countNodes(exprBuf, root); |
|
| 524 | + | if count != 5 { |
|
| 525 | + | return 3; |
|
| 526 | + | } |
|
| 527 | + | return 0; |
|
| 528 | + | } |
|
| 529 | + | ||
| 530 | + | @default fn main() -> i32 { |
|
| 531 | + | let mut tokenBuf: [Token; 128] = [Token::Eof; 128]; |
|
| 532 | + | let mut exprBuf: [Expr; 128] = [Expr::Num(0); 128]; |
|
| 533 | + | ||
| 534 | + | let r1 = testNumber(&mut tokenBuf[..], &mut exprBuf[..]); |
|
| 535 | + | if r1 != 0 { |
|
| 536 | + | return 10 + r1; |
|
| 537 | + | } |
|
| 538 | + | ||
| 539 | + | let r2 = testAdd(&mut tokenBuf[..], &mut exprBuf[..]); |
|
| 540 | + | if r2 != 0 { |
|
| 541 | + | return 20 + r2; |
|
| 542 | + | } |
|
| 543 | + | ||
| 544 | + | let r3 = testPrecedence(&mut tokenBuf[..], &mut exprBuf[..]); |
|
| 545 | + | if r3 != 0 { |
|
| 546 | + | return 30 + r3; |
|
| 547 | + | } |
|
| 548 | + | ||
| 549 | + | let r4 = testParens(&mut tokenBuf[..], &mut exprBuf[..]); |
|
| 550 | + | if r4 != 0 { |
|
| 551 | + | return 40 + r4; |
|
| 552 | + | } |
|
| 553 | + | ||
| 554 | + | let r5 = testNeg(&mut tokenBuf[..], &mut exprBuf[..]); |
|
| 555 | + | if r5 != 0 { |
|
| 556 | + | return 50 + r5; |
|
| 557 | + | } |
|
| 558 | + | ||
| 559 | + | let r6 = testComplex(&mut tokenBuf[..], &mut exprBuf[..]); |
|
| 560 | + | if r6 != 0 { |
|
| 561 | + | return 60 + r6; |
|
| 562 | + | } |
|
| 563 | + | ||
| 564 | + | let r7 = testChained(&mut tokenBuf[..], &mut exprBuf[..]); |
|
| 565 | + | if r7 != 0 { |
|
| 566 | + | return 70 + r7; |
|
| 567 | + | } |
|
| 568 | + | ||
| 569 | + | let r8 = testTokenCount(&mut tokenBuf[..], &mut exprBuf[..]); |
|
| 570 | + | if r8 != 0 { |
|
| 571 | + | return 80 + r8; |
|
| 572 | + | } |
|
| 573 | + | ||
| 574 | + | let r9 = testDivision(&mut tokenBuf[..], &mut exprBuf[..]); |
|
| 575 | + | if r9 != 0 { |
|
| 576 | + | return 90 + r9; |
|
| 577 | + | } |
|
| 578 | + | ||
| 579 | + | let r10 = testDeepNesting(&mut tokenBuf[..], &mut exprBuf[..]); |
|
| 580 | + | if r10 != 0 { |
|
| 581 | + | return 100 + r10; |
|
| 582 | + | } |
|
| 583 | + | ||
| 584 | + | let r11 = testNodeCount(&mut tokenBuf[..], &mut exprBuf[..]); |
|
| 585 | + | if r11 != 0 { |
|
| 586 | + | return 110 + r11; |
|
| 587 | + | } |
|
| 588 | + | ||
| 589 | + | return 0; |
|
| 590 | + | } |
lib/std/arch/rv64/tests/prog.vm.rad
added
+611 -0
| 1 | + | //! Stack-based virtual machine. |
|
| 2 | + | //! Implement a bytecode interpreter for a simple stack machine with |
|
| 3 | + | //! arithmetic, comparisons, jumps, local variables, and function calls. |
|
| 4 | + | //! Exercises: tagged unions, match, throw/try/catch, error handling, |
|
| 5 | + | //! records with slice fields, and complex interacting state. |
|
| 6 | + | ||
| 7 | + | const MAX_STACK: u32 = 64; |
|
| 8 | + | const MAX_CODE: u32 = 256; |
|
| 9 | + | const MAX_LOCALS: u32 = 16; |
|
| 10 | + | const MAX_FRAMES: u32 = 8; |
|
| 11 | + | ||
| 12 | + | /// Bytecode instructions. |
|
| 13 | + | union Op { |
|
| 14 | + | /// Push an immediate value. |
|
| 15 | + | Push(i32), |
|
| 16 | + | /// Pop top two, push sum. |
|
| 17 | + | Add, |
|
| 18 | + | /// Pop top two, push difference (second - first). |
|
| 19 | + | Sub, |
|
| 20 | + | /// Pop top two, push product. |
|
| 21 | + | Mul, |
|
| 22 | + | /// Pop top two, push quotient. |
|
| 23 | + | Div, |
|
| 24 | + | /// Pop top two, push 1 if equal, 0 otherwise. |
|
| 25 | + | Eq, |
|
| 26 | + | /// Pop top two, push 1 if second < first, 0 otherwise. |
|
| 27 | + | Lt, |
|
| 28 | + | /// Pop top two, push 1 if second > first, 0 otherwise. |
|
| 29 | + | Gt, |
|
| 30 | + | /// Negate top of stack. |
|
| 31 | + | Neg, |
|
| 32 | + | /// Duplicate top of stack. |
|
| 33 | + | Dup, |
|
| 34 | + | /// Pop and discard top of stack. |
|
| 35 | + | Pop, |
|
| 36 | + | /// Store top of stack into local variable. |
|
| 37 | + | Store(u32), |
|
| 38 | + | /// Load local variable onto stack. |
|
| 39 | + | Load(u32), |
|
| 40 | + | /// Unconditional jump to instruction index. |
|
| 41 | + | Jump(u32), |
|
| 42 | + | /// Pop; if zero, jump to instruction index. |
|
| 43 | + | JumpIfZero(u32), |
|
| 44 | + | /// Call function at instruction index, saving return address. |
|
| 45 | + | Call(u32), |
|
| 46 | + | /// Return from function call. |
|
| 47 | + | Ret, |
|
| 48 | + | /// Halt execution, top of stack is result. |
|
| 49 | + | Halt, |
|
| 50 | + | } |
|
| 51 | + | ||
| 52 | + | /// A call frame for function calls. |
|
| 53 | + | record Frame { |
|
| 54 | + | returnAddr: u32, |
|
| 55 | + | localBase: u32, |
|
| 56 | + | } |
|
| 57 | + | ||
| 58 | + | /// The VM state. |
|
| 59 | + | record VM { |
|
| 60 | + | code: *[Op], |
|
| 61 | + | codeLen: u32, |
|
| 62 | + | stack: *mut [i32], |
|
| 63 | + | sp: u32, |
|
| 64 | + | locals: *mut [i32], |
|
| 65 | + | frames: *mut [Frame], |
|
| 66 | + | frameCount: u32, |
|
| 67 | + | pc: u32, |
|
| 68 | + | } |
|
| 69 | + | ||
| 70 | + | /// VM error types. |
|
| 71 | + | union VmError { |
|
| 72 | + | /// Stack underflow error. |
|
| 73 | + | StackUnderflow, |
|
| 74 | + | /// Stack overflow error. |
|
| 75 | + | StackOverflow, |
|
| 76 | + | /// Division by zero. |
|
| 77 | + | DivByZero, |
|
| 78 | + | /// Invalid instruction or PC. |
|
| 79 | + | InvalidPC, |
|
| 80 | + | /// Too many nested calls. |
|
| 81 | + | CallOverflow, |
|
| 82 | + | } |
|
| 83 | + | ||
| 84 | + | /// Push a value onto the stack. |
|
| 85 | + | fn push(vm: *mut VM, value: i32) throws (VmError) { |
|
| 86 | + | if vm.sp >= MAX_STACK { |
|
| 87 | + | throw VmError::StackOverflow; |
|
| 88 | + | } |
|
| 89 | + | vm.stack[vm.sp] = value; |
|
| 90 | + | vm.sp = vm.sp + 1; |
|
| 91 | + | } |
|
| 92 | + | ||
| 93 | + | /// Pop a value from the stack. |
|
| 94 | + | fn pop(vm: *mut VM) -> i32 throws (VmError) { |
|
| 95 | + | if vm.sp == 0 { |
|
| 96 | + | throw VmError::StackUnderflow; |
|
| 97 | + | } |
|
| 98 | + | vm.sp = vm.sp - 1; |
|
| 99 | + | return vm.stack[vm.sp]; |
|
| 100 | + | } |
|
| 101 | + | ||
| 102 | + | /// Peek at the top of the stack without removing. |
|
| 103 | + | fn peek(vm: *VM) -> i32 throws (VmError) { |
|
| 104 | + | if vm.sp == 0 { |
|
| 105 | + | throw VmError::StackUnderflow; |
|
| 106 | + | } |
|
| 107 | + | return vm.stack[vm.sp - 1]; |
|
| 108 | + | } |
|
| 109 | + | ||
| 110 | + | /// Execute the bytecode program. |
|
| 111 | + | fn execute(vm: *mut VM) -> i32 throws (VmError) { |
|
| 112 | + | while vm.pc < vm.codeLen { |
|
| 113 | + | let instr = vm.code[vm.pc]; |
|
| 114 | + | vm.pc = vm.pc + 1; |
|
| 115 | + | ||
| 116 | + | match instr { |
|
| 117 | + | case Op::Push(val) => { |
|
| 118 | + | try push(vm, val); |
|
| 119 | + | } |
|
| 120 | + | case Op::Add => { |
|
| 121 | + | let b = try pop(vm); |
|
| 122 | + | let a = try pop(vm); |
|
| 123 | + | try push(vm, a + b); |
|
| 124 | + | } |
|
| 125 | + | case Op::Sub => { |
|
| 126 | + | let b = try pop(vm); |
|
| 127 | + | let a = try pop(vm); |
|
| 128 | + | try push(vm, a - b); |
|
| 129 | + | } |
|
| 130 | + | case Op::Mul => { |
|
| 131 | + | let b = try pop(vm); |
|
| 132 | + | let a = try pop(vm); |
|
| 133 | + | try push(vm, a * b); |
|
| 134 | + | } |
|
| 135 | + | case Op::Div => { |
|
| 136 | + | let b = try pop(vm); |
|
| 137 | + | let a = try pop(vm); |
|
| 138 | + | if b == 0 { |
|
| 139 | + | throw VmError::DivByZero; |
|
| 140 | + | } |
|
| 141 | + | try push(vm, a / b); |
|
| 142 | + | } |
|
| 143 | + | case Op::Eq => { |
|
| 144 | + | let b = try pop(vm); |
|
| 145 | + | let a = try pop(vm); |
|
| 146 | + | let mut result: i32 = 0; |
|
| 147 | + | if a == b { |
|
| 148 | + | result = 1; |
|
| 149 | + | } |
|
| 150 | + | try push(vm, result); |
|
| 151 | + | } |
|
| 152 | + | case Op::Lt => { |
|
| 153 | + | let b = try pop(vm); |
|
| 154 | + | let a = try pop(vm); |
|
| 155 | + | let mut result: i32 = 0; |
|
| 156 | + | if a < b { |
|
| 157 | + | result = 1; |
|
| 158 | + | } |
|
| 159 | + | try push(vm, result); |
|
| 160 | + | } |
|
| 161 | + | case Op::Gt => { |
|
| 162 | + | let b = try pop(vm); |
|
| 163 | + | let a = try pop(vm); |
|
| 164 | + | let mut result: i32 = 0; |
|
| 165 | + | if a > b { |
|
| 166 | + | result = 1; |
|
| 167 | + | } |
|
| 168 | + | try push(vm, result); |
|
| 169 | + | } |
|
| 170 | + | case Op::Neg => { |
|
| 171 | + | let a = try pop(vm); |
|
| 172 | + | try push(vm, 0 - a); |
|
| 173 | + | } |
|
| 174 | + | case Op::Dup => { |
|
| 175 | + | let a = try peek(vm); |
|
| 176 | + | try push(vm, a); |
|
| 177 | + | } |
|
| 178 | + | case Op::Pop => { |
|
| 179 | + | try pop(vm); |
|
| 180 | + | } |
|
| 181 | + | case Op::Store(idx) => { |
|
| 182 | + | let val = try pop(vm); |
|
| 183 | + | let mut base: u32 = 0; |
|
| 184 | + | if vm.frameCount > 0 { |
|
| 185 | + | base = vm.frames[vm.frameCount - 1].localBase; |
|
| 186 | + | } |
|
| 187 | + | vm.locals[base + idx] = val; |
|
| 188 | + | } |
|
| 189 | + | case Op::Load(idx) => { |
|
| 190 | + | let mut base: u32 = 0; |
|
| 191 | + | if vm.frameCount > 0 { |
|
| 192 | + | base = vm.frames[vm.frameCount - 1].localBase; |
|
| 193 | + | } |
|
| 194 | + | let val = vm.locals[base + idx]; |
|
| 195 | + | try push(vm, val); |
|
| 196 | + | } |
|
| 197 | + | case Op::Jump(target) => { |
|
| 198 | + | vm.pc = target; |
|
| 199 | + | } |
|
| 200 | + | case Op::JumpIfZero(target) => { |
|
| 201 | + | let cond = try pop(vm); |
|
| 202 | + | if cond == 0 { |
|
| 203 | + | vm.pc = target; |
|
| 204 | + | } |
|
| 205 | + | } |
|
| 206 | + | case Op::Call(target) => { |
|
| 207 | + | if vm.frameCount >= MAX_FRAMES { |
|
| 208 | + | throw VmError::CallOverflow; |
|
| 209 | + | } |
|
| 210 | + | let mut base: u32 = 0; |
|
| 211 | + | if vm.frameCount > 0 { |
|
| 212 | + | base = vm.frames[vm.frameCount - 1].localBase + MAX_LOCALS; |
|
| 213 | + | } |
|
| 214 | + | vm.frames[vm.frameCount] = Frame { returnAddr: vm.pc, localBase: base }; |
|
| 215 | + | vm.frameCount = vm.frameCount + 1; |
|
| 216 | + | vm.pc = target; |
|
| 217 | + | } |
|
| 218 | + | case Op::Ret => { |
|
| 219 | + | if vm.frameCount == 0 { |
|
| 220 | + | throw VmError::InvalidPC; |
|
| 221 | + | } |
|
| 222 | + | vm.frameCount = vm.frameCount - 1; |
|
| 223 | + | vm.pc = vm.frames[vm.frameCount].returnAddr; |
|
| 224 | + | } |
|
| 225 | + | case Op::Halt => { |
|
| 226 | + | return try pop(vm); |
|
| 227 | + | } |
|
| 228 | + | } |
|
| 229 | + | ||
| 230 | + | if vm.pc > vm.codeLen { |
|
| 231 | + | throw VmError::InvalidPC; |
|
| 232 | + | } |
|
| 233 | + | } |
|
| 234 | + | throw VmError::InvalidPC; |
|
| 235 | + | } |
|
| 236 | + | ||
| 237 | + | /// Helper to initialize VM and run a program. |
|
| 238 | + | fn runProgram( |
|
| 239 | + | code: *[Op], |
|
| 240 | + | codeLen: u32, |
|
| 241 | + | stackBuf: *mut [i32], |
|
| 242 | + | localsBuf: *mut [i32], |
|
| 243 | + | framesBuf: *mut [Frame] |
|
| 244 | + | ) -> i32 throws (VmError) { |
|
| 245 | + | let mut vm = VM { |
|
| 246 | + | code, |
|
| 247 | + | codeLen, |
|
| 248 | + | stack: stackBuf, |
|
| 249 | + | sp: 0, |
|
| 250 | + | locals: localsBuf, |
|
| 251 | + | frames: framesBuf, |
|
| 252 | + | frameCount: 0, |
|
| 253 | + | pc: 0, |
|
| 254 | + | }; |
|
| 255 | + | return try execute(&mut vm); |
|
| 256 | + | } |
|
| 257 | + | ||
| 258 | + | /// Test basic arithmetic: 3 + 4 * 2 = 11 |
|
| 259 | + | fn testArith(stackBuf: *mut [i32], localsBuf: *mut [i32], framesBuf: *mut [Frame]) -> i32 { |
|
| 260 | + | let mut code: [Op; 8] = [Op::Halt; 8]; |
|
| 261 | + | code[0] = Op::Push(3); |
|
| 262 | + | code[1] = Op::Push(4); |
|
| 263 | + | code[2] = Op::Push(2); |
|
| 264 | + | code[3] = Op::Mul; |
|
| 265 | + | code[4] = Op::Add; |
|
| 266 | + | code[5] = Op::Halt; |
|
| 267 | + | ||
| 268 | + | let result: i32 = try! runProgram(&code[..], 6, stackBuf, localsBuf, framesBuf); |
|
| 269 | + | if result != 11 { |
|
| 270 | + | return 1; |
|
| 271 | + | } |
|
| 272 | + | return 0; |
|
| 273 | + | } |
|
| 274 | + | ||
| 275 | + | /// Test local variables: x = 5, y = 7, push x + y. |
|
| 276 | + | fn testLocals(stackBuf: *mut [i32], localsBuf: *mut [i32], framesBuf: *mut [Frame]) -> i32 { |
|
| 277 | + | let mut code: [Op; 16] = [Op::Halt; 16]; |
|
| 278 | + | code[0] = Op::Push(5); |
|
| 279 | + | code[1] = Op::Store(0); // x = 5 |
|
| 280 | + | code[2] = Op::Push(7); |
|
| 281 | + | code[3] = Op::Store(1); // y = 7 |
|
| 282 | + | code[4] = Op::Load(0); // push x |
|
| 283 | + | code[5] = Op::Load(1); // push y |
|
| 284 | + | code[6] = Op::Add; // x + y |
|
| 285 | + | code[7] = Op::Halt; |
|
| 286 | + | ||
| 287 | + | let result: i32 = try! runProgram(&code[..], 8, stackBuf, localsBuf, framesBuf); |
|
| 288 | + | if result != 12 { |
|
| 289 | + | return 1; |
|
| 290 | + | } |
|
| 291 | + | return 0; |
|
| 292 | + | } |
|
| 293 | + | ||
| 294 | + | /// Test conditional jump: if 3 > 2 then push 42 else push 99. |
|
| 295 | + | fn testConditional(stackBuf: *mut [i32], localsBuf: *mut [i32], framesBuf: *mut [Frame]) -> i32 { |
|
| 296 | + | let mut code: [Op; 16] = [Op::Halt; 16]; |
|
| 297 | + | code[0] = Op::Push(3); |
|
| 298 | + | code[1] = Op::Push(2); |
|
| 299 | + | code[2] = Op::Gt; // 3 > 2 => 1 |
|
| 300 | + | code[3] = Op::JumpIfZero(6); // if false, jump to 6 |
|
| 301 | + | code[4] = Op::Push(42); // true branch |
|
| 302 | + | code[5] = Op::Jump(7); // skip false branch |
|
| 303 | + | code[6] = Op::Push(99); // false branch |
|
| 304 | + | code[7] = Op::Halt; |
|
| 305 | + | ||
| 306 | + | let result: i32 = try! runProgram(&code[..], 8, stackBuf, localsBuf, framesBuf); |
|
| 307 | + | if result != 42 { |
|
| 308 | + | return 1; |
|
| 309 | + | } |
|
| 310 | + | return 0; |
|
| 311 | + | } |
|
| 312 | + | ||
| 313 | + | /// Test loop: sum 1..5 using jumps. |
|
| 314 | + | /// local[0] = counter (starts at 1), local[1] = sum (starts at 0). |
|
| 315 | + | fn testLoop(stackBuf: *mut [i32], localsBuf: *mut [i32], framesBuf: *mut [Frame]) -> i32 { |
|
| 316 | + | let mut code: [Op; 32] = [Op::Halt; 32]; |
|
| 317 | + | code[0] = Op::Push(1); |
|
| 318 | + | code[1] = Op::Store(0); // counter = 1 |
|
| 319 | + | code[2] = Op::Push(0); |
|
| 320 | + | code[3] = Op::Store(1); // sum = 0 |
|
| 321 | + | // Loop start (pc = 4): |
|
| 322 | + | code[4] = Op::Load(0); // push counter |
|
| 323 | + | code[5] = Op::Push(6); |
|
| 324 | + | code[6] = Op::Lt; // counter < 6 |
|
| 325 | + | code[7] = Op::JumpIfZero(17); // if false, exit loop |
|
| 326 | + | code[8] = Op::Load(1); // push sum |
|
| 327 | + | code[9] = Op::Load(0); // push counter |
|
| 328 | + | code[10] = Op::Add; // sum + counter |
|
| 329 | + | code[11] = Op::Store(1); // sum = sum + counter |
|
| 330 | + | code[12] = Op::Load(0); // push counter |
|
| 331 | + | code[13] = Op::Push(1); |
|
| 332 | + | code[14] = Op::Add; // counter + 1 |
|
| 333 | + | code[15] = Op::Store(0); // counter = counter + 1 |
|
| 334 | + | code[16] = Op::Jump(4); // back to loop start |
|
| 335 | + | code[17] = Op::Load(1); // push sum |
|
| 336 | + | code[18] = Op::Halt; |
|
| 337 | + | ||
| 338 | + | let result: i32 = try! runProgram(&code[..], 19, stackBuf, localsBuf, framesBuf); |
|
| 339 | + | // sum = 1+2+3+4+5 = 15 |
|
| 340 | + | if result != 15 { |
|
| 341 | + | return 1; |
|
| 342 | + | } |
|
| 343 | + | return 0; |
|
| 344 | + | } |
|
| 345 | + | ||
| 346 | + | /// Test function call: call a function that computes n*2+1 for n=10. |
|
| 347 | + | fn testCall(stackBuf: *mut [i32], localsBuf: *mut [i32], framesBuf: *mut [Frame]) -> i32 { |
|
| 348 | + | let mut code: [Op; 32] = [Op::Halt; 32]; |
|
| 349 | + | ||
| 350 | + | // Main: push argument on stack, call function, halt. |
|
| 351 | + | code[0] = Op::Push(10); // push argument |
|
| 352 | + | code[1] = Op::Call(5); // call function at 5 |
|
| 353 | + | // After return, result is on stack. |
|
| 354 | + | code[2] = Op::Halt; |
|
| 355 | + | code[3] = Op::Halt; // padding |
|
| 356 | + | code[4] = Op::Halt; // padding |
|
| 357 | + | ||
| 358 | + | // Function at pc 5: |
|
| 359 | + | code[5] = Op::Store(0); // pop arg into local[0] |
|
| 360 | + | code[6] = Op::Load(0); // push local[0] |
|
| 361 | + | code[7] = Op::Push(2); |
|
| 362 | + | code[8] = Op::Mul; // n * 2 |
|
| 363 | + | code[9] = Op::Push(1); |
|
| 364 | + | code[10] = Op::Add; // n * 2 + 1 |
|
| 365 | + | code[11] = Op::Ret; // return (result on stack) |
|
| 366 | + | ||
| 367 | + | let result: i32 = try! runProgram(&code[..], 12, stackBuf, localsBuf, framesBuf); |
|
| 368 | + | // 10 * 2 + 1 = 21 |
|
| 369 | + | if result != 21 { |
|
| 370 | + | return 1; |
|
| 371 | + | } |
|
| 372 | + | return 0; |
|
| 373 | + | } |
|
| 374 | + | ||
| 375 | + | /// Test division by zero detection using try...catch with error binding. |
|
| 376 | + | fn testDivByZero(stackBuf: *mut [i32], localsBuf: *mut [i32], framesBuf: *mut [Frame]) -> i32 { |
|
| 377 | + | let mut code: [Op; 8] = [Op::Halt; 8]; |
|
| 378 | + | code[0] = Op::Push(42); |
|
| 379 | + | code[1] = Op::Push(0); |
|
| 380 | + | code[2] = Op::Div; |
|
| 381 | + | code[3] = Op::Halt; |
|
| 382 | + | ||
| 383 | + | let mut caught: i32 = 0; |
|
| 384 | + | try runProgram(&code[..], 4, stackBuf, localsBuf, framesBuf) catch e { |
|
| 385 | + | if e == VmError::DivByZero { |
|
| 386 | + | caught = 1; |
|
| 387 | + | } else { |
|
| 388 | + | caught = 2; |
|
| 389 | + | } |
|
| 390 | + | }; |
|
| 391 | + | if caught != 1 { |
|
| 392 | + | return 1; |
|
| 393 | + | } |
|
| 394 | + | return 0; |
|
| 395 | + | } |
|
| 396 | + | ||
| 397 | + | /// Test negation and equality. |
|
| 398 | + | fn testNegAndEq(stackBuf: *mut [i32], localsBuf: *mut [i32], framesBuf: *mut [Frame]) -> i32 { |
|
| 399 | + | let mut code: [Op; 16] = [Op::Halt; 16]; |
|
| 400 | + | code[0] = Op::Push(5); |
|
| 401 | + | code[1] = Op::Neg; // -5 |
|
| 402 | + | code[2] = Op::Push(-5); |
|
| 403 | + | code[3] = Op::Eq; // -5 == -5 => 1 |
|
| 404 | + | code[4] = Op::Halt; |
|
| 405 | + | ||
| 406 | + | let result: i32 = try! runProgram(&code[..], 5, stackBuf, localsBuf, framesBuf); |
|
| 407 | + | if result != 1 { |
|
| 408 | + | return 1; |
|
| 409 | + | } |
|
| 410 | + | return 0; |
|
| 411 | + | } |
|
| 412 | + | ||
| 413 | + | /// Test factorial using recursive calls: fact(6) = 720. |
|
| 414 | + | fn testFactorial(stackBuf: *mut [i32], localsBuf: *mut [i32], framesBuf: *mut [Frame]) -> i32 { |
|
| 415 | + | let mut code: [Op; 32] = [Op::Halt; 32]; |
|
| 416 | + | ||
| 417 | + | // Main: push 6, call fact, halt. |
|
| 418 | + | code[0] = Op::Push(6); |
|
| 419 | + | code[1] = Op::Call(4); // call fact at 4 |
|
| 420 | + | code[2] = Op::Halt; |
|
| 421 | + | code[3] = Op::Halt; // padding |
|
| 422 | + | ||
| 423 | + | // fact(n) at pc 4: |
|
| 424 | + | // Store arg to local[0] |
|
| 425 | + | // If n <= 1, push 1, return |
|
| 426 | + | // Else push n, push fact(n-1), multiply, return |
|
| 427 | + | code[4] = Op::Store(0); // local[0] = n |
|
| 428 | + | code[5] = Op::Load(0); // push n |
|
| 429 | + | code[6] = Op::Push(2); |
|
| 430 | + | code[7] = Op::Lt; // n < 2 |
|
| 431 | + | code[8] = Op::JumpIfZero(11); // if n >= 2, skip |
|
| 432 | + | code[9] = Op::Push(1); // base case: push 1 |
|
| 433 | + | code[10] = Op::Ret; |
|
| 434 | + | ||
| 435 | + | // Recursive case: |
|
| 436 | + | code[11] = Op::Load(0); // push n |
|
| 437 | + | code[12] = Op::Push(1); |
|
| 438 | + | code[13] = Op::Sub; // n - 1 |
|
| 439 | + | code[14] = Op::Call(4); // fact(n-1) |
|
| 440 | + | code[15] = Op::Load(0); // push n |
|
| 441 | + | code[16] = Op::Mul; // fact(n-1) * n |
|
| 442 | + | code[17] = Op::Ret; |
|
| 443 | + | ||
| 444 | + | let result: i32 = try! runProgram(&code[..], 18, stackBuf, localsBuf, framesBuf); |
|
| 445 | + | if result != 720 { |
|
| 446 | + | return 1; |
|
| 447 | + | } |
|
| 448 | + | return 0; |
|
| 449 | + | } |
|
| 450 | + | ||
| 451 | + | /// Test dup instruction. |
|
| 452 | + | fn testDup(stackBuf: *mut [i32], localsBuf: *mut [i32], framesBuf: *mut [Frame]) -> i32 { |
|
| 453 | + | let mut code: [Op; 8] = [Op::Halt; 8]; |
|
| 454 | + | code[0] = Op::Push(7); |
|
| 455 | + | code[1] = Op::Dup; |
|
| 456 | + | code[2] = Op::Add; // 7 + 7 = 14 |
|
| 457 | + | code[3] = Op::Halt; |
|
| 458 | + | ||
| 459 | + | let result: i32 = try! runProgram(&code[..], 4, stackBuf, localsBuf, framesBuf); |
|
| 460 | + | if result != 14 { |
|
| 461 | + | return 1; |
|
| 462 | + | } |
|
| 463 | + | return 0; |
|
| 464 | + | } |
|
| 465 | + | ||
| 466 | + | /// Test stack underflow detection using try...catch with error binding. |
|
| 467 | + | fn testStackUnderflow(stackBuf: *mut [i32], localsBuf: *mut [i32], framesBuf: *mut [Frame]) -> i32 { |
|
| 468 | + | let mut code: [Op; 4] = [Op::Halt; 4]; |
|
| 469 | + | code[0] = Op::Add; // nothing on stack |
|
| 470 | + | code[1] = Op::Halt; |
|
| 471 | + | ||
| 472 | + | let mut caught: i32 = 0; |
|
| 473 | + | try runProgram(&code[..], 2, stackBuf, localsBuf, framesBuf) catch e { |
|
| 474 | + | if e == VmError::StackUnderflow { |
|
| 475 | + | caught = 1; |
|
| 476 | + | } else { |
|
| 477 | + | caught = 2; |
|
| 478 | + | } |
|
| 479 | + | }; |
|
| 480 | + | if caught != 1 { |
|
| 481 | + | return 1; |
|
| 482 | + | } |
|
| 483 | + | return 0; |
|
| 484 | + | } |
|
| 485 | + | ||
| 486 | + | /// Test that try...catch on success path does not execute catch block. |
|
| 487 | + | fn testSuccessNoCatch(stackBuf: *mut [i32], localsBuf: *mut [i32], framesBuf: *mut [Frame]) -> i32 { |
|
| 488 | + | let mut code: [Op; 4] = [Op::Halt; 4]; |
|
| 489 | + | code[0] = Op::Push(99); |
|
| 490 | + | code[1] = Op::Halt; |
|
| 491 | + | ||
| 492 | + | let mut caught: i32 = 0; |
|
| 493 | + | let result: i32 = try runProgram(&code[..], 2, stackBuf, localsBuf, framesBuf) catch e { |
|
| 494 | + | caught = 1; |
|
| 495 | + | return 1; |
|
| 496 | + | }; |
|
| 497 | + | // Catch block should not have run. |
|
| 498 | + | if caught != 0 { |
|
| 499 | + | return 2; |
|
| 500 | + | } |
|
| 501 | + | // Should have the success value. |
|
| 502 | + | if result != 99 { |
|
| 503 | + | return 3; |
|
| 504 | + | } |
|
| 505 | + | return 0; |
|
| 506 | + | } |
|
| 507 | + | ||
| 508 | + | /// Test call overflow detection by exhausting frames. |
|
| 509 | + | fn testCallOverflow(stackBuf: *mut [i32], localsBuf: *mut [i32], framesBuf: *mut [Frame]) -> i32 { |
|
| 510 | + | let mut code: [Op; 4] = [Op::Halt; 4]; |
|
| 511 | + | // Infinite recursion: function calls itself. |
|
| 512 | + | code[0] = Op::Call(0); |
|
| 513 | + | code[1] = Op::Halt; |
|
| 514 | + | ||
| 515 | + | let mut caught: i32 = 0; |
|
| 516 | + | try runProgram(&code[..], 2, stackBuf, localsBuf, framesBuf) catch e { |
|
| 517 | + | if e == VmError::CallOverflow { |
|
| 518 | + | caught = 1; |
|
| 519 | + | } else { |
|
| 520 | + | caught = 2; |
|
| 521 | + | } |
|
| 522 | + | }; |
|
| 523 | + | if caught != 1 { |
|
| 524 | + | return 1; |
|
| 525 | + | } |
|
| 526 | + | return 0; |
|
| 527 | + | } |
|
| 528 | + | ||
| 529 | + | /// Test that catch with no binding works (discard the error). |
|
| 530 | + | fn testCatchNoBinding(stackBuf: *mut [i32], localsBuf: *mut [i32], framesBuf: *mut [Frame]) -> i32 { |
|
| 531 | + | let mut code: [Op; 4] = [Op::Halt; 4]; |
|
| 532 | + | code[0] = Op::Pop; // underflow |
|
| 533 | + | code[1] = Op::Halt; |
|
| 534 | + | ||
| 535 | + | // Catch without binding - just swallow the error. |
|
| 536 | + | try runProgram(&code[..], 2, stackBuf, localsBuf, framesBuf) catch {}; |
|
| 537 | + | return 0; |
|
| 538 | + | } |
|
| 539 | + | ||
| 540 | + | @default fn main() -> i32 { |
|
| 541 | + | let mut stackBuf: [i32; 64] = [0; 64]; |
|
| 542 | + | let mut localsBuf: [i32; 128] = [0; 128]; |
|
| 543 | + | let mut framesBuf: [Frame; 8] = [Frame { returnAddr: 0, localBase: 0 }; 8]; |
|
| 544 | + | ||
| 545 | + | let r1 = testArith(&mut stackBuf[..], &mut localsBuf[..], &mut framesBuf[..]); |
|
| 546 | + | if r1 != 0 { |
|
| 547 | + | return 10 + r1; |
|
| 548 | + | } |
|
| 549 | + | ||
| 550 | + | let r2 = testLocals(&mut stackBuf[..], &mut localsBuf[..], &mut framesBuf[..]); |
|
| 551 | + | if r2 != 0 { |
|
| 552 | + | return 20 + r2; |
|
| 553 | + | } |
|
| 554 | + | ||
| 555 | + | let r3 = testConditional(&mut stackBuf[..], &mut localsBuf[..], &mut framesBuf[..]); |
|
| 556 | + | if r3 != 0 { |
|
| 557 | + | return 30 + r3; |
|
| 558 | + | } |
|
| 559 | + | ||
| 560 | + | let r4 = testLoop(&mut stackBuf[..], &mut localsBuf[..], &mut framesBuf[..]); |
|
| 561 | + | if r4 != 0 { |
|
| 562 | + | return 40 + r4; |
|
| 563 | + | } |
|
| 564 | + | ||
| 565 | + | let r5 = testCall(&mut stackBuf[..], &mut localsBuf[..], &mut framesBuf[..]); |
|
| 566 | + | if r5 != 0 { |
|
| 567 | + | return 50 + r5; |
|
| 568 | + | } |
|
| 569 | + | ||
| 570 | + | let r6 = testDivByZero(&mut stackBuf[..], &mut localsBuf[..], &mut framesBuf[..]); |
|
| 571 | + | if r6 != 0 { |
|
| 572 | + | return 60 + r6; |
|
| 573 | + | } |
|
| 574 | + | ||
| 575 | + | let r7 = testNegAndEq(&mut stackBuf[..], &mut localsBuf[..], &mut framesBuf[..]); |
|
| 576 | + | if r7 != 0 { |
|
| 577 | + | return 70 + r7; |
|
| 578 | + | } |
|
| 579 | + | ||
| 580 | + | let r8 = testFactorial(&mut stackBuf[..], &mut localsBuf[..], &mut framesBuf[..]); |
|
| 581 | + | if r8 != 0 { |
|
| 582 | + | return 80 + r8; |
|
| 583 | + | } |
|
| 584 | + | ||
| 585 | + | let r9 = testDup(&mut stackBuf[..], &mut localsBuf[..], &mut framesBuf[..]); |
|
| 586 | + | if r9 != 0 { |
|
| 587 | + | return 90 + r9; |
|
| 588 | + | } |
|
| 589 | + | ||
| 590 | + | let r10 = testStackUnderflow(&mut stackBuf[..], &mut localsBuf[..], &mut framesBuf[..]); |
|
| 591 | + | if r10 != 0 { |
|
| 592 | + | return 100 + r10; |
|
| 593 | + | } |
|
| 594 | + | ||
| 595 | + | let r11 = testSuccessNoCatch(&mut stackBuf[..], &mut localsBuf[..], &mut framesBuf[..]); |
|
| 596 | + | if r11 != 0 { |
|
| 597 | + | return 110 + r11; |
|
| 598 | + | } |
|
| 599 | + | ||
| 600 | + | let r12 = testCallOverflow(&mut stackBuf[..], &mut localsBuf[..], &mut framesBuf[..]); |
|
| 601 | + | if r12 != 0 { |
|
| 602 | + | return 120 + r12; |
|
| 603 | + | } |
|
| 604 | + | ||
| 605 | + | let r13 = testCatchNoBinding(&mut stackBuf[..], &mut localsBuf[..], &mut framesBuf[..]); |
|
| 606 | + | if r13 != 0 { |
|
| 607 | + | return 130 + r13; |
|
| 608 | + | } |
|
| 609 | + | ||
| 610 | + | return 0; |
|
| 611 | + | } |
lib/std/arch/rv64/tests/ptr.assign.rad
added
+9 -0
| 1 | + | //! returns: 42 |
|
| 2 | + | @default fn main() -> i32 { |
|
| 3 | + | let mut x: i32 = 1; |
|
| 4 | + | let mut ptr: *mut i32 = &mut x; |
|
| 5 | + | ||
| 6 | + | *ptr = 42; |
|
| 7 | + | ||
| 8 | + | return x; |
|
| 9 | + | } |
lib/std/arch/rv64/tests/ptr.deref.rad
added
+30 -0
| 1 | + | //! returns: 65 |
|
| 2 | + | //! Test pointer dereference in various contexts. |
|
| 3 | + | ||
| 4 | + | fn derefArrayIndex(ary: [i32; 3]) -> i32 { |
|
| 5 | + | let x: *i32 = &ary[1]; |
|
| 6 | + | return *x; |
|
| 7 | + | } |
|
| 8 | + | ||
| 9 | + | fn derefSliceIndex(slc: *[i32]) -> i32 { |
|
| 10 | + | let x: *i32 = &slc[2]; |
|
| 11 | + | return *x; |
|
| 12 | + | } |
|
| 13 | + | ||
| 14 | + | fn derefBinop(x: i32, y: i32) -> i32 { |
|
| 15 | + | let px: *i32 = &x; |
|
| 16 | + | let py: *i32 = &y; |
|
| 17 | + | ||
| 18 | + | return *px + *py; |
|
| 19 | + | } |
|
| 20 | + | ||
| 21 | + | @default fn main() -> i32 { |
|
| 22 | + | let x: i32 = 42; |
|
| 23 | + | let y: *i32 = &x; |
|
| 24 | + | let z: i32 = *y; // 42 |
|
| 25 | + | let r: i32 = derefBinop(3, 6); // 9 |
|
| 26 | + | let a: i32 = derefArrayIndex([7, 8, 9]); // 8 |
|
| 27 | + | let s: i32 = derefSliceIndex(&[2, 4, 6]); // 6 |
|
| 28 | + | ||
| 29 | + | return z + r + a + s; |
|
| 30 | + | } |
lib/std/arch/rv64/tests/ptr.eq.rad
added
+45 -0
| 1 | + | ||
| 2 | + | record Point { |
|
| 3 | + | x: i32, |
|
| 4 | + | y: i32, |
|
| 5 | + | } |
|
| 6 | + | ||
| 7 | + | // Test that pointer equality uses address comparison, not value comparison. |
|
| 8 | + | fn testPtrSameAddress() -> bool { |
|
| 9 | + | let p1 = Point { x: 1, y: 2 }; |
|
| 10 | + | let a = &p1; |
|
| 11 | + | let b = &p1; |
|
| 12 | + | ||
| 13 | + | return a == b; // Same address, should be equal. |
|
| 14 | + | } |
|
| 15 | + | ||
| 16 | + | fn testPtrDifferentAddressSameValues() -> bool { |
|
| 17 | + | let p1 = Point { x: 1, y: 2 }; |
|
| 18 | + | let p2 = Point { x: 1, y: 2 }; // Same values as p1, but different address. |
|
| 19 | + | let a = &p1; |
|
| 20 | + | let b = &p2; |
|
| 21 | + | ||
| 22 | + | return not (a == b); // Different addresses, should NOT be equal. |
|
| 23 | + | } |
|
| 24 | + | ||
| 25 | + | fn testPtrDifferentAddressDifferentValues() -> bool { |
|
| 26 | + | let p1 = Point { x: 1, y: 2 }; |
|
| 27 | + | let p2 = Point { x: 3, y: 4 }; |
|
| 28 | + | let a = &p1; |
|
| 29 | + | let b = &p2; |
|
| 30 | + | ||
| 31 | + | return not (a == b); // Different addresses, should NOT be equal. |
|
| 32 | + | } |
|
| 33 | + | ||
| 34 | + | @default fn main() -> i32 { |
|
| 35 | + | if not testPtrSameAddress() { |
|
| 36 | + | return 1; |
|
| 37 | + | } |
|
| 38 | + | if not testPtrDifferentAddressSameValues() { |
|
| 39 | + | return 2; |
|
| 40 | + | } |
|
| 41 | + | if not testPtrDifferentAddressDifferentValues() { |
|
| 42 | + | return 3; |
|
| 43 | + | } |
|
| 44 | + | return 0; |
|
| 45 | + | } |
lib/std/arch/rv64/tests/ptr.mutate.rad
added
+19 -0
| 1 | + | //! returns: 42 |
|
| 2 | + | ||
| 3 | + | fn mutate1(ptr: *mut i32) { |
|
| 4 | + | *ptr = 39; |
|
| 5 | + | } |
|
| 6 | + | ||
| 7 | + | fn mutate2(ptr: *mut i32) { |
|
| 8 | + | *ptr = *ptr + 2; |
|
| 9 | + | *ptr = *ptr + 1; |
|
| 10 | + | } |
|
| 11 | + | ||
| 12 | + | @default fn main() -> i32 { |
|
| 13 | + | let mut ptr: i32 = 0; |
|
| 14 | + | ||
| 15 | + | mutate1(&mut ptr); |
|
| 16 | + | mutate2(&mut ptr); |
|
| 17 | + | ||
| 18 | + | return ptr; |
|
| 19 | + | } |
lib/std/arch/rv64/tests/ptr.opaque.rad
added
+64 -0
| 1 | + | //! Test basic opaque pointer usage (automatic coercion). |
|
| 2 | + | fn testOpaqueCasting() -> bool { |
|
| 3 | + | let x: u32 = 42; |
|
| 4 | + | let ptr: *u8 = &x as *u8; |
|
| 5 | + | let opq: *opaque = ptr; // Automatic coercion from *u8 to *opaque. |
|
| 6 | + | let back: *u8 = opq as *u8; |
|
| 7 | + | ||
| 8 | + | return back == ptr; |
|
| 9 | + | } |
|
| 10 | + | ||
| 11 | + | /// Test that opaque can be used in function parameters. |
|
| 12 | + | fn takesOpaque(ptr: *opaque, orig: *u8) -> bool { |
|
| 13 | + | let back: *u8 = ptr as *u8; |
|
| 14 | + | return back == orig; |
|
| 15 | + | } |
|
| 16 | + | ||
| 17 | + | fn testOpaqueParams() -> bool { |
|
| 18 | + | let x: u32 = 42; |
|
| 19 | + | let ptr: *u8 = &x as *u8; |
|
| 20 | + | return takesOpaque(ptr, ptr); // Automatic coercion in function call. |
|
| 21 | + | } |
|
| 22 | + | ||
| 23 | + | /// Test that opaque can be used in return types. |
|
| 24 | + | fn returnsOpaque(ptr: *u8) -> *opaque { |
|
| 25 | + | return ptr; // Automatic coercion in return. |
|
| 26 | + | } |
|
| 27 | + | ||
| 28 | + | fn testOpaqueReturn() -> bool { |
|
| 29 | + | let x: u32 = 42; |
|
| 30 | + | let ptr: *u8 = &x as *u8; |
|
| 31 | + | let opq: *opaque = returnsOpaque(ptr); |
|
| 32 | + | return opq as *u8 == ptr; |
|
| 33 | + | } |
|
| 34 | + | ||
| 35 | + | /// Test nullable opaque pointers. |
|
| 36 | + | fn testNullableOpaque() -> bool { |
|
| 37 | + | let mut opt: ?*opaque = nil; |
|
| 38 | + | if opt == nil { |
|
| 39 | + | let x: u32 = 42; |
|
| 40 | + | let ptr: *u8 = &x as *u8; |
|
| 41 | + | opt = ptr; // Automatic coercion in assignment. |
|
| 42 | + | ||
| 43 | + | if let p = opt { |
|
| 44 | + | return p as *u8 == ptr; |
|
| 45 | + | } |
|
| 46 | + | } |
|
| 47 | + | return false; |
|
| 48 | + | } |
|
| 49 | + | ||
| 50 | + | @default fn main() -> u32 { |
|
| 51 | + | if not testOpaqueCasting() { |
|
| 52 | + | return 1; |
|
| 53 | + | } |
|
| 54 | + | if not testOpaqueParams() { |
|
| 55 | + | return 2; |
|
| 56 | + | } |
|
| 57 | + | if not testOpaqueReturn() { |
|
| 58 | + | return 3; |
|
| 59 | + | } |
|
| 60 | + | if not testNullableOpaque() { |
|
| 61 | + | return 4; |
|
| 62 | + | } |
|
| 63 | + | return 0; |
|
| 64 | + | } |
lib/std/arch/rv64/tests/record.access.rad
added
+14 -0
| 1 | + | ||
| 2 | + | record Vector { |
|
| 3 | + | x: i32, |
|
| 4 | + | y: i32, |
|
| 5 | + | z: i32, |
|
| 6 | + | w: i32, |
|
| 7 | + | } |
|
| 8 | + | ||
| 9 | + | @default fn main() -> i32 { |
|
| 10 | + | let mut a: Vector = Vector { x: 11, y: 12, z: 13, w: 6 }; |
|
| 11 | + | let mut b: Vector = Vector { x: 1, y: 2, z: 3, w: 4 }; |
|
| 12 | + | ||
| 13 | + | return (a.x + a.y + a.z + a.w + b.x + b.y + b.z + b.w) - 52; |
|
| 14 | + | } |
lib/std/arch/rv64/tests/record.alignment.rad
added
+16 -0
| 1 | + | //! Test record alignment computation. |
|
| 2 | + | ||
| 3 | + | record R { |
|
| 4 | + | a: i32, |
|
| 5 | + | b: i32, |
|
| 6 | + | } |
|
| 7 | + | ||
| 8 | + | @default fn main() -> i32 { |
|
| 9 | + | if @alignOf(R) != 4 { |
|
| 10 | + | return 1; |
|
| 11 | + | } |
|
| 12 | + | if @sizeOf(R) != 8 { |
|
| 13 | + | return 2; |
|
| 14 | + | } |
|
| 15 | + | return 0; |
|
| 16 | + | } |
lib/std/arch/rv64/tests/record.array.elements.rad
added
+71 -0
| 1 | + | //! returns: 115 |
|
| 2 | + | // Testing structs with array elements. |
|
| 3 | + | ||
| 4 | + | record Vector { |
|
| 5 | + | data: [i32; 4], |
|
| 6 | + | } |
|
| 7 | + | ||
| 8 | + | record Matrix { |
|
| 9 | + | rows: [Vector; 3], |
|
| 10 | + | } |
|
| 11 | + | ||
| 12 | + | fn vectorSum(vec: Vector) -> i32 { |
|
| 13 | + | let mut sum: i32 = 0; |
|
| 14 | + | let mut i: u32 = 0; |
|
| 15 | + | ||
| 16 | + | while (i < vec.data.len) { |
|
| 17 | + | sum = sum + vec.data[i]; |
|
| 18 | + | i = i + 1; |
|
| 19 | + | } |
|
| 20 | + | return sum; |
|
| 21 | + | } |
|
| 22 | + | ||
| 23 | + | fn vectorScale(vec: Vector, scale: i32) -> Vector { |
|
| 24 | + | let mut result: Vector = Vector { data: [0, 0, 0, 0] }; |
|
| 25 | + | let mut i: u32 = 0; |
|
| 26 | + | ||
| 27 | + | while (i < vec.data.len) { |
|
| 28 | + | result.data[i] = vec.data[i] * scale; |
|
| 29 | + | i = i + 1; |
|
| 30 | + | } |
|
| 31 | + | return result; |
|
| 32 | + | } |
|
| 33 | + | ||
| 34 | + | // Sum of the diagonal elements. |
|
| 35 | + | fn matrixTrace(mat: Matrix) -> i32 { |
|
| 36 | + | let mut sum: i32 = 0; |
|
| 37 | + | let mut i: u32 = 0; |
|
| 38 | + | ||
| 39 | + | while (i < mat.rows.len) { |
|
| 40 | + | sum = sum + mat.rows[i].data[i]; |
|
| 41 | + | i = i + 1; |
|
| 42 | + | } |
|
| 43 | + | return sum; |
|
| 44 | + | } |
|
| 45 | + | ||
| 46 | + | @default fn main() -> i32 { |
|
| 47 | + | let mut v1: Vector = Vector { |
|
| 48 | + | data: [1, 2, 3, 4] |
|
| 49 | + | }; |
|
| 50 | + | let mut v2: Vector = vectorScale(v1, 2); |
|
| 51 | + | let mut m1: Matrix = Matrix { |
|
| 52 | + | rows: [ |
|
| 53 | + | Vector { data: [10, 11, 12, 13] }, |
|
| 54 | + | Vector { data: [14, 15, 16, 17] }, |
|
| 55 | + | Vector { data: [18, 19, 20, 21] } |
|
| 56 | + | ] |
|
| 57 | + | }; |
|
| 58 | + | // Sum of v1: 1 + 2 + 3 + 4 = 10. |
|
| 59 | + | let mut sumV1: i32 = vectorSum(v1); |
|
| 60 | + | // Sum of v2: 2 + 4 + 6 + 8 = 20. |
|
| 61 | + | let mut sumV2: i32 = vectorSum(v2); |
|
| 62 | + | // Trace of matrix: 10 + 15 + 20 = 45. |
|
| 63 | + | let mut trace: i32 = matrixTrace(m1); |
|
| 64 | + | // Modify element in the matrix. |
|
| 65 | + | m1.rows[0].data[0] = m1.rows[0].data[0] - 5; // 10 - 5 = 5 |
|
| 66 | + | // Calculate new trace: 5 + 15 + 20 = 40. |
|
| 67 | + | let mut newTrace: i32 = matrixTrace(m1); |
|
| 68 | + | ||
| 69 | + | // Return combined results: 10 + 20 + 45 + 40 = 115. |
|
| 70 | + | return sumV1 + sumV2 + trace + newTrace; |
|
| 71 | + | } |
lib/std/arch/rv64/tests/record.copy.rad
added
+178 -0
| 1 | + | //! Test record value semantics through copying, mutation, and function calls. |
|
| 2 | + | record S { |
|
| 3 | + | x: i16, |
|
| 4 | + | y: i16, |
|
| 5 | + | z: i16, |
|
| 6 | + | } |
|
| 7 | + | ||
| 8 | + | fn makeS(x: i16, y: i16, z: i16) -> S { |
|
| 9 | + | let s: S = S { x, y, z }; |
|
| 10 | + | return s; |
|
| 11 | + | } |
|
| 12 | + | ||
| 13 | + | fn returnS(s: S) -> S { |
|
| 14 | + | return s; |
|
| 15 | + | } |
|
| 16 | + | ||
| 17 | + | fn func1(s: S) -> i32 { |
|
| 18 | + | let mut m: S = s; |
|
| 19 | + | m.x = 112; |
|
| 20 | + | m.y = 582; |
|
| 21 | + | m.z = 281; |
|
| 22 | + | ||
| 23 | + | if m.x != 112 { |
|
| 24 | + | return 1; |
|
| 25 | + | } |
|
| 26 | + | if m.y != 582 { |
|
| 27 | + | return 2; |
|
| 28 | + | } |
|
| 29 | + | if m.z != 281 { |
|
| 30 | + | return 3; |
|
| 31 | + | } |
|
| 32 | + | return 0; |
|
| 33 | + | } |
|
| 34 | + | ||
| 35 | + | fn func2(s: S) -> i32 { |
|
| 36 | + | let p: i32 = 0; |
|
| 37 | + | let mut t: S = s; |
|
| 38 | + | let q: i32 = 0; |
|
| 39 | + | let mut u: S = s; |
|
| 40 | + | ||
| 41 | + | t.x = 12; |
|
| 42 | + | u.x = 13; |
|
| 43 | + | t.y = 52; |
|
| 44 | + | u.y = 54; |
|
| 45 | + | t.z = 21; |
|
| 46 | + | u.z = 25; |
|
| 47 | + | ||
| 48 | + | if t.x != 12 { |
|
| 49 | + | return 1; |
|
| 50 | + | } |
|
| 51 | + | if t.y != 52 { |
|
| 52 | + | return 2; |
|
| 53 | + | } |
|
| 54 | + | if t.z != 21 { |
|
| 55 | + | return 3; |
|
| 56 | + | } |
|
| 57 | + | if u.x != 13 { |
|
| 58 | + | return 4; |
|
| 59 | + | } |
|
| 60 | + | if u.y != 54 { |
|
| 61 | + | return 5; |
|
| 62 | + | } |
|
| 63 | + | if u.z != 25 { |
|
| 64 | + | return 6; |
|
| 65 | + | } |
|
| 66 | + | return 0; |
|
| 67 | + | } |
|
| 68 | + | ||
| 69 | + | fn func3(s: S) -> i32 { |
|
| 70 | + | let mut a: [S; 4] = [s; 4]; |
|
| 71 | + | let mut t: S = s; |
|
| 72 | + | ||
| 73 | + | t.x = 12; |
|
| 74 | + | t.y = 13; |
|
| 75 | + | t.z = 52; |
|
| 76 | + | ||
| 77 | + | a[1] = t; |
|
| 78 | + | a[3] = a[1]; |
|
| 79 | + | ||
| 80 | + | if a[0].x != 561 { |
|
| 81 | + | return 1; |
|
| 82 | + | } |
|
| 83 | + | if a[0].y != 938 { |
|
| 84 | + | return 2; |
|
| 85 | + | } |
|
| 86 | + | if a[0].z != 102 { |
|
| 87 | + | return 3; |
|
| 88 | + | } |
|
| 89 | + | if a[1].x != 12 { |
|
| 90 | + | return 4; |
|
| 91 | + | } |
|
| 92 | + | if a[1].y != 13 { |
|
| 93 | + | return 5; |
|
| 94 | + | } |
|
| 95 | + | if a[1].z != 52 { |
|
| 96 | + | return 6; |
|
| 97 | + | } |
|
| 98 | + | if a[2].x != 561 { |
|
| 99 | + | return 7; |
|
| 100 | + | } |
|
| 101 | + | if a[3].x != 12 { |
|
| 102 | + | return 8; |
|
| 103 | + | } |
|
| 104 | + | return 0; |
|
| 105 | + | } |
|
| 106 | + | ||
| 107 | + | fn func4(s: S) -> i32 { |
|
| 108 | + | let mut a: [S; 2] = undefined; |
|
| 109 | + | a[0] = s; |
|
| 110 | + | ||
| 111 | + | if a[0].x != 561 { |
|
| 112 | + | return 1; |
|
| 113 | + | } |
|
| 114 | + | if a[0].y != 938 { |
|
| 115 | + | return 2; |
|
| 116 | + | } |
|
| 117 | + | if a[0].z != 102 { |
|
| 118 | + | return 3; |
|
| 119 | + | } |
|
| 120 | + | return 0; |
|
| 121 | + | } |
|
| 122 | + | ||
| 123 | + | fn func5(s: S) -> i32 { |
|
| 124 | + | let mut a: [S; 2] = undefined; |
|
| 125 | + | let t: S = makeS(s.x, s.y, s.z); |
|
| 126 | + | a[0] = t; |
|
| 127 | + | ||
| 128 | + | if a[0].x != 561 { |
|
| 129 | + | return 1; |
|
| 130 | + | } |
|
| 131 | + | if a[0].y != 938 { |
|
| 132 | + | return 2; |
|
| 133 | + | } |
|
| 134 | + | if a[0].z != 102 { |
|
| 135 | + | return 3; |
|
| 136 | + | } |
|
| 137 | + | return 0; |
|
| 138 | + | } |
|
| 139 | + | ||
| 140 | + | @default fn main() -> i32 { |
|
| 141 | + | let s: S = S { x: 561, y: 938, z: 102 }; |
|
| 142 | + | ||
| 143 | + | let r1: i32 = func1(s); |
|
| 144 | + | if r1 != 0 { |
|
| 145 | + | return 10 + r1; |
|
| 146 | + | } |
|
| 147 | + | if s.x != 561 { |
|
| 148 | + | return 20; |
|
| 149 | + | } |
|
| 150 | + | if s.y != 938 { |
|
| 151 | + | return 21; |
|
| 152 | + | } |
|
| 153 | + | if s.z != 102 { |
|
| 154 | + | return 22; |
|
| 155 | + | } |
|
| 156 | + | ||
| 157 | + | let r2: i32 = func2(s); |
|
| 158 | + | if r2 != 0 { |
|
| 159 | + | return 30 + r2; |
|
| 160 | + | } |
|
| 161 | + | ||
| 162 | + | let r3: i32 = func3(s); |
|
| 163 | + | if r3 != 0 { |
|
| 164 | + | return 40 + r3; |
|
| 165 | + | } |
|
| 166 | + | ||
| 167 | + | let r4: i32 = func4(s); |
|
| 168 | + | if r4 != 0 { |
|
| 169 | + | return 50 + r4; |
|
| 170 | + | } |
|
| 171 | + | ||
| 172 | + | let r5: i32 = func5(s); |
|
| 173 | + | if r5 != 0 { |
|
| 174 | + | return 60 + r5; |
|
| 175 | + | } |
|
| 176 | + | ||
| 177 | + | return 0; |
|
| 178 | + | } |
lib/std/arch/rv64/tests/record.field.assign.rad
added
+12 -0
| 1 | + | ||
| 2 | + | record Vector { x: i32, y: i32 } |
|
| 3 | + | ||
| 4 | + | @default fn main() -> i32 { |
|
| 5 | + | let mut v: Vector = Vector { x: 3, y: 2 }; |
|
| 6 | + | ||
| 7 | + | v.x = 40; |
|
| 8 | + | v.y = 1; |
|
| 9 | + | v.y = v.y * 2; |
|
| 10 | + | ||
| 11 | + | return (v.x + v.y) - 42; |
|
| 12 | + | } |
lib/std/arch/rv64/tests/record.nested.calls.2.rad
added
+31 -0
| 1 | + | //! returns: 0 |
|
| 2 | + | //! Test nested record function calls with scaling and addition. |
|
| 3 | + | ||
| 4 | + | record Point { |
|
| 5 | + | x: i32, |
|
| 6 | + | y: i32, |
|
| 7 | + | } |
|
| 8 | + | ||
| 9 | + | fn add(p1: Point, p2: Point) -> Point { |
|
| 10 | + | return Point { x: p1.x + p2.x, y: p1.y + p2.y }; |
|
| 11 | + | } |
|
| 12 | + | ||
| 13 | + | fn scale(p: Point, factor: i32) -> Point { |
|
| 14 | + | return Point { x: p.x * factor, y: p.y * factor }; |
|
| 15 | + | } |
|
| 16 | + | ||
| 17 | + | @default fn main() -> i32 { |
|
| 18 | + | let mut p: Point = Point { x: 3, y: 4 }; |
|
| 19 | + | let mut q: Point = Point { x: 5, y: 6 }; |
|
| 20 | + | ||
| 21 | + | p = add( |
|
| 22 | + | scale(p, 2), // (6, 8) |
|
| 23 | + | scale(q, 2) // (10, 12) |
|
| 24 | + | ); // (16, 20) |
|
| 25 | + | ||
| 26 | + | // p.x + p.y = 16 + 20 = 36 |
|
| 27 | + | if p.x + p.y != 36 { |
|
| 28 | + | return 1; |
|
| 29 | + | } |
|
| 30 | + | return 0; |
|
| 31 | + | } |
lib/std/arch/rv64/tests/record.nested.calls.3.rad
added
+40 -0
| 1 | + | //! Test record passing through multiple nested function calls. |
|
| 2 | + | ||
| 3 | + | record Point { |
|
| 4 | + | x: i32, |
|
| 5 | + | y: i32, |
|
| 6 | + | } |
|
| 7 | + | ||
| 8 | + | fn id(x: i32) -> i32 { |
|
| 9 | + | return x; |
|
| 10 | + | } |
|
| 11 | + | ||
| 12 | + | fn add(p1: Point, p2: Point) -> Point { |
|
| 13 | + | return Point { x: p1.x + p2.x, y: p1.y + p2.y }; |
|
| 14 | + | } |
|
| 15 | + | ||
| 16 | + | fn scale(p: Point, factor: i32) -> Point { |
|
| 17 | + | return Point { x: p.x * factor, y: p.y * factor }; |
|
| 18 | + | } |
|
| 19 | + | ||
| 20 | + | fn more(p: Point) -> Point { |
|
| 21 | + | let mut one: i32 = id(1); |
|
| 22 | + | return add(p, scale(p, one)); |
|
| 23 | + | } |
|
| 24 | + | ||
| 25 | + | fn compute(p: Point) -> Point { |
|
| 26 | + | let mut one: i32 = id(1); |
|
| 27 | + | return more(scale(p, one)); |
|
| 28 | + | } |
|
| 29 | + | ||
| 30 | + | fn point() -> Point { |
|
| 31 | + | return Point { x: id(2), y: id(3) }; |
|
| 32 | + | } |
|
| 33 | + | ||
| 34 | + | @default fn main() -> i32 { |
|
| 35 | + | let result: i32 = compute(point()).y - compute(point()).x; |
|
| 36 | + | if result != 2 { |
|
| 37 | + | return 1; |
|
| 38 | + | } |
|
| 39 | + | return 0; |
|
| 40 | + | } |
lib/std/arch/rv64/tests/record.param.lit.rad
added
+23 -0
| 1 | + | // Test passing record literals directly into a function. |
|
| 2 | + | ||
| 3 | + | record Vector { |
|
| 4 | + | x: i32, |
|
| 5 | + | y: i32, |
|
| 6 | + | z: i32 |
|
| 7 | + | } |
|
| 8 | + | ||
| 9 | + | record Point { |
|
| 10 | + | x: i32, |
|
| 11 | + | y: i32, |
|
| 12 | + | } |
|
| 13 | + | ||
| 14 | + | fn compute(v: Vector, p: Point) -> i32 { |
|
| 15 | + | return (v.x + p.y) - 42; |
|
| 16 | + | } |
|
| 17 | + | ||
| 18 | + | @default fn main() -> i32 { |
|
| 19 | + | return compute( |
|
| 20 | + | Vector { x: 18, y: 0, z: 0 }, |
|
| 21 | + | Point { x: 0, y: 24 } |
|
| 22 | + | ); |
|
| 23 | + | } |
lib/std/arch/rv64/tests/record.ptr.access.rad
added
+15 -0
| 1 | + | ||
| 2 | + | record Point { |
|
| 3 | + | x: i32, |
|
| 4 | + | y: i32, |
|
| 5 | + | } |
|
| 6 | + | ||
| 7 | + | @default fn main() -> i32 { |
|
| 8 | + | let mut p: Point = Point { x: 11, y: 31 }; |
|
| 9 | + | let mut r: *Point = &p; |
|
| 10 | + | ||
| 11 | + | let mut x: i32 = r.x; |
|
| 12 | + | let mut y: i32 = r.y; |
|
| 13 | + | ||
| 14 | + | return (x + y) - 42; |
|
| 15 | + | } |
lib/std/arch/rv64/tests/record.ptr.mutate.rad
added
+19 -0
| 1 | + | //! returns: 42 |
|
| 2 | + | ||
| 3 | + | record Point { |
|
| 4 | + | x: i32, |
|
| 5 | + | y: i32, |
|
| 6 | + | } |
|
| 7 | + | ||
| 8 | + | fn mutate(p: *mut Point) -> void { |
|
| 9 | + | p.x = 4; |
|
| 10 | + | p.y = 38; |
|
| 11 | + | } |
|
| 12 | + | ||
| 13 | + | @default fn main() -> i32 { |
|
| 14 | + | let mut p: Point = Point { x: 0, y: 0 }; |
|
| 15 | + | ||
| 16 | + | mutate(&mut p); |
|
| 17 | + | ||
| 18 | + | return p.x + p.y; |
|
| 19 | + | } |
lib/std/arch/rv64/tests/record.shorthand.rad
added
+68 -0
| 1 | + | //! returns: 1 |
|
| 2 | + | //! Test shorthand field syntax in record literals and patterns. |
|
| 3 | + | ||
| 4 | + | record Point { |
|
| 5 | + | x: i32, |
|
| 6 | + | y: i32, |
|
| 7 | + | } |
|
| 8 | + | ||
| 9 | + | union Shape { |
|
| 10 | + | circle(i32), |
|
| 11 | + | rectangle { width: i32, height: i32 }, |
|
| 12 | + | } |
|
| 13 | + | ||
| 14 | + | /// Test shorthand record literal construction. |
|
| 15 | + | fn testRecordLiteralShorthand() -> bool { |
|
| 16 | + | let x: i32 = 10; |
|
| 17 | + | let y: i32 = 20; |
|
| 18 | + | ||
| 19 | + | // `{ x, y }` is equivalent to `{ x: x, y: y }`. |
|
| 20 | + | let p = Point { x, y }; |
|
| 21 | + | ||
| 22 | + | return p.x == 10 and p.y == 20; |
|
| 23 | + | } |
|
| 24 | + | ||
| 25 | + | /// Test mixing shorthand and explicit field names. |
|
| 26 | + | fn testMixedShorthand() -> bool { |
|
| 27 | + | let x: i32 = 5; |
|
| 28 | + | ||
| 29 | + | let p = Point { x, y: 15 }; |
|
| 30 | + | ||
| 31 | + | return p.x == 5 and p.y == 15; |
|
| 32 | + | } |
|
| 33 | + | ||
| 34 | + | /// Test shorthand pattern matching on union fields. |
|
| 35 | + | fn testPatternShorthand() -> bool { |
|
| 36 | + | let shape: Shape = Shape::rectangle { width: 100, height: 50 }; |
|
| 37 | + | ||
| 38 | + | match shape { |
|
| 39 | + | case Shape::rectangle { width, height } => { |
|
| 40 | + | return width == 100 and height == 50; |
|
| 41 | + | } |
|
| 42 | + | else => { |
|
| 43 | + | return false; |
|
| 44 | + | } |
|
| 45 | + | } |
|
| 46 | + | } |
|
| 47 | + | ||
| 48 | + | /// Test mixing shorthand and explicit bindings in patterns. |
|
| 49 | + | fn testPatternPartialShorthand() -> bool { |
|
| 50 | + | let shape: Shape = Shape::rectangle { width: 30, height: 40 }; |
|
| 51 | + | ||
| 52 | + | match shape { |
|
| 53 | + | case Shape::rectangle { width, height: h } => { |
|
| 54 | + | return width == 30 and h == 40; |
|
| 55 | + | } |
|
| 56 | + | else => { |
|
| 57 | + | return false; |
|
| 58 | + | } |
|
| 59 | + | } |
|
| 60 | + | } |
|
| 61 | + | ||
| 62 | + | @default fn main() -> bool { |
|
| 63 | + | return |
|
| 64 | + | testRecordLiteralShorthand() and |
|
| 65 | + | testMixedShorthand() and |
|
| 66 | + | testPatternShorthand() and |
|
| 67 | + | testPatternPartialShorthand(); |
|
| 68 | + | } |
lib/std/arch/rv64/tests/record.unlabeled.rad
added
+29 -0
| 1 | + | //! returns: 1 |
|
| 2 | + | // TODO: Test destructuring. |
|
| 3 | + | ||
| 4 | + | record S(i32); |
|
| 5 | + | record R(bool, u8); |
|
| 6 | + | ||
| 7 | + | fn passS(s: S) -> S { |
|
| 8 | + | return s; |
|
| 9 | + | } |
|
| 10 | + | ||
| 11 | + | fn passR(r: R) -> R { |
|
| 12 | + | return r; |
|
| 13 | + | } |
|
| 14 | + | ||
| 15 | + | @default fn main() -> i32 { |
|
| 16 | + | let s1: S = S(42); |
|
| 17 | + | let r1: R = R(true, 43); |
|
| 18 | + | ||
| 19 | + | let s2: S = passS(s1); |
|
| 20 | + | let r2: R = passR(r1); |
|
| 21 | + | ||
| 22 | + | if s2 != S(42) { |
|
| 23 | + | return 0; |
|
| 24 | + | } |
|
| 25 | + | if r2 != R(true, 43) { |
|
| 26 | + | return 0; |
|
| 27 | + | } |
|
| 28 | + | return 1; |
|
| 29 | + | } |
lib/std/arch/rv64/tests/ref.if.bug.rad
added
+27 -0
| 1 | + | //! returns: 0 |
|
| 2 | + | //! Regression test: address-of inside an if branch. |
|
| 3 | + | //! |
|
| 4 | + | //! When `&mut var` appears in only one branch of an if/else, the merge |
|
| 5 | + | //! block's phi merges the original integer value with a stack pointer. |
|
| 6 | + | ||
| 7 | + | fn set(ptr: *mut u32, val: u32) { |
|
| 8 | + | *ptr = val; |
|
| 9 | + | } |
|
| 10 | + | ||
| 11 | + | fn testIfBranch(cond: bool) -> u32 { |
|
| 12 | + | let mut val: u32 = 42; |
|
| 13 | + | if cond { |
|
| 14 | + | set(&mut val, 99); |
|
| 15 | + | } |
|
| 16 | + | return val; |
|
| 17 | + | } |
|
| 18 | + | ||
| 19 | + | @default fn main() -> i32 { |
|
| 20 | + | if testIfBranch(false) != 42 { |
|
| 21 | + | return 1; |
|
| 22 | + | } |
|
| 23 | + | if testIfBranch(true) != 99 { |
|
| 24 | + | return 2; |
|
| 25 | + | } |
|
| 26 | + | return 0; |
|
| 27 | + | } |
lib/std/arch/rv64/tests/ref.immut.loop.bug.rad
added
+31 -0
| 1 | + | //! returns: 0 |
|
| 2 | + | //! Regression test: address of immutable variable inside a loop. |
|
| 3 | + | //! |
|
| 4 | + | //! Even though the variable is not `mut`, taking its address inside a |
|
| 5 | + | //! loop can produce the same SSA/pointer phi conflict as with mutable |
|
| 6 | + | //! variables: the loop header merges the original integer with a pointer |
|
| 7 | + | //! from the stack slot created by `&x`. |
|
| 8 | + | ||
| 9 | + | fn read(ptr: *u32) -> u32 { |
|
| 10 | + | return *ptr; |
|
| 11 | + | } |
|
| 12 | + | ||
| 13 | + | fn testZeroIter(n: u32) -> u32 { |
|
| 14 | + | let val: u32 = 42; |
|
| 15 | + | let mut i: u32 = 0; |
|
| 16 | + | while i < n { |
|
| 17 | + | let _ = read(&val); |
|
| 18 | + | i = i + 1; |
|
| 19 | + | } |
|
| 20 | + | return val; |
|
| 21 | + | } |
|
| 22 | + | ||
| 23 | + | @default fn main() -> i32 { |
|
| 24 | + | if testZeroIter(0) != 42 { |
|
| 25 | + | return 1; |
|
| 26 | + | } |
|
| 27 | + | if testZeroIter(3) != 42 { |
|
| 28 | + | return 2; |
|
| 29 | + | } |
|
| 30 | + | return 0; |
|
| 31 | + | } |
lib/std/arch/rv64/tests/ref.mut.ptr.rad
added
+17 -0
| 1 | + | //! returns: 84 |
|
| 2 | + | //! Test mutable pointer references. |
|
| 3 | + | ||
| 4 | + | fn set(ptr: *mut i32) -> void { |
|
| 5 | + | *ptr = 42; |
|
| 6 | + | } |
|
| 7 | + | ||
| 8 | + | @default fn main() -> i32 { |
|
| 9 | + | let mut a: i32 = 0; |
|
| 10 | + | let p: *mut i32 = &mut a; |
|
| 11 | + | set(p); |
|
| 12 | + | ||
| 13 | + | let mut b: i32 = 0; |
|
| 14 | + | set(&mut b); |
|
| 15 | + | ||
| 16 | + | return a + b; |
|
| 17 | + | } |
lib/std/arch/rv64/tests/regalloc.callee.save.rad
added
+48 -0
| 1 | + | //! Regression test for the global callee-class limit bug. |
|
| 2 | + | //! |
|
| 3 | + | //! Creates 12 callee-class values where no single call site exceeds |
|
| 4 | + | //! 11 crossing values (so per-call-site spilling doesn't trigger), but |
|
| 5 | + | //! 12 callee-class values are simultaneously live during register |
|
| 6 | + | //! assignment, exceeding the 11 available callee-saved registers. |
|
| 7 | + | //! |
|
| 8 | + | //! The trick: at call N (backward walk), {v0..v(N-1)} cross; at the last |
|
| 9 | + | //! call, v0 is consumed as an argument so only {v1..v11} cross = 11. |
|
| 10 | + | //! Each call site has <= 11 crossing values, but callee-class union = |
|
| 11 | + | //! {v0..v10} | {v1..v11} = {v0..v11} = 12. Without the global |
|
| 12 | + | //! callee-class limit, v11 silently gets a caller-saved register and |
|
| 13 | + | //! is clobbered by the subsequent call. |
|
| 14 | + | ||
| 15 | + | fn id(x: i32) -> i32 { |
|
| 16 | + | return x; |
|
| 17 | + | } |
|
| 18 | + | ||
| 19 | + | fn consume(x: i32) -> i32 { |
|
| 20 | + | return x + 1; |
|
| 21 | + | } |
|
| 22 | + | ||
| 23 | + | @default fn main() -> i32 { |
|
| 24 | + | let v0: i32 = id(1); |
|
| 25 | + | let v1: i32 = id(2); |
|
| 26 | + | let v2: i32 = id(3); |
|
| 27 | + | let v3: i32 = id(4); |
|
| 28 | + | let v4: i32 = id(5); |
|
| 29 | + | let v5: i32 = id(6); |
|
| 30 | + | let v6: i32 = id(7); |
|
| 31 | + | let v7: i32 = id(8); |
|
| 32 | + | let v8: i32 = id(9); |
|
| 33 | + | let v9: i32 = id(10); |
|
| 34 | + | let v10: i32 = id(11); |
|
| 35 | + | let v11: i32 = id(12); |
|
| 36 | + | ||
| 37 | + | // v0's last use - consume it via a call. |
|
| 38 | + | // At this call: {v1..v11} cross = 11, exactly fitting numCalleeSaved. |
|
| 39 | + | let v12: i32 = consume(v0); |
|
| 40 | + | ||
| 41 | + | let total: i32 = v1 + v2 + v3 + v4 + v5 + v6 + v7 + v8 + v9 + v10 + v11 + v12; |
|
| 42 | + | // v1..v10 = 2+3+...+11 = 65, v11 = 12, v12 = consume(1) = 2 |
|
| 43 | + | // total = 65 + 12 + 2 = 79 |
|
| 44 | + | if total != 79 { |
|
| 45 | + | return 1; |
|
| 46 | + | } |
|
| 47 | + | return 0; |
|
| 48 | + | } |
lib/std/arch/rv64/tests/regalloc.spill.reuse.rad
added
+29 -0
| 1 | + | fn callPressure(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32, g: u32, h: u32) -> u32 { |
|
| 2 | + | return a + b + c + d + e + f + g + h; |
|
| 3 | + | } |
|
| 4 | + | ||
| 5 | + | fn assert(cond: bool) { |
|
| 6 | + | if not cond { |
|
| 7 | + | panic "assert"; |
|
| 8 | + | } |
|
| 9 | + | } |
|
| 10 | + | ||
| 11 | + | fn bump(old: u32) -> u32 { |
|
| 12 | + | let saved: u32 = old + 1; |
|
| 13 | + | ||
| 14 | + | let first: u32 = callPressure(1, 2, 3, 4, 5, 6, 7, 8); |
|
| 15 | + | let second: u32 = callPressure(8, 7, 6, 5, 4, 3, 2, 1); |
|
| 16 | + | ||
| 17 | + | if first == 0 or second == 0 { |
|
| 18 | + | return 99; |
|
| 19 | + | } |
|
| 20 | + | ||
| 21 | + | return saved; |
|
| 22 | + | } |
|
| 23 | + | ||
| 24 | + | @default fn main() -> i32 { |
|
| 25 | + | let result: u32 = bump(0); |
|
| 26 | + | assert(result == 1); |
|
| 27 | + | ||
| 28 | + | return 0; |
|
| 29 | + | } |
lib/std/arch/rv64/tests/reserve.loop.rad
added
+17 -0
| 1 | + | record Pair { a: i32, b: i32 } |
|
| 2 | + | ||
| 3 | + | @default fn main() -> i32 { |
|
| 4 | + | let mut total: i32 = 0; |
|
| 5 | + | let mut i: i32 = 0; |
|
| 6 | + | while i < 10000 { |
|
| 7 | + | let p: Pair = Pair { a: i, b: i + 1 }; |
|
| 8 | + | total = total + p.a + p.b; |
|
| 9 | + | i = i + 1; |
|
| 10 | + | } |
|
| 11 | + | // Expected: sum(i) + sum(i+1) for i=0..9999 |
|
| 12 | + | // = 2 * 9999*10000/2 + 10000 = 99990000 + 10000 = 100000000 |
|
| 13 | + | if total != 100000000 { |
|
| 14 | + | return 1; |
|
| 15 | + | } |
|
| 16 | + | return 0; |
|
| 17 | + | } |
lib/std/arch/rv64/tests/result.void.success.rad
added
+39 -0
| 1 | + | //! Test that Result<void, E> success returns work correctly. |
|
| 2 | + | ||
| 3 | + | union TestError { |
|
| 4 | + | Failed, |
|
| 5 | + | } |
|
| 6 | + | ||
| 7 | + | fn voidSuccess() throws (TestError) { |
|
| 8 | + | return; |
|
| 9 | + | } |
|
| 10 | + | ||
| 11 | + | fn voidError() throws (TestError) { |
|
| 12 | + | throw TestError::Failed; |
|
| 13 | + | } |
|
| 14 | + | ||
| 15 | + | @default fn main() -> u32 { |
|
| 16 | + | // Place a guard variable on the stack. |
|
| 17 | + | let mut guard: u32 = 42; |
|
| 18 | + | ||
| 19 | + | // Call function returning Result<void, TestError> with success. |
|
| 20 | + | try voidSuccess() catch { |
|
| 21 | + | return (1) - 42; |
|
| 22 | + | }; |
|
| 23 | + | ||
| 24 | + | // Guard should still be 42, not clobbered. |
|
| 25 | + | if guard != 42 { |
|
| 26 | + | return (2) - 42; |
|
| 27 | + | } |
|
| 28 | + | ||
| 29 | + | // Try the error path too. |
|
| 30 | + | try voidError() catch { |
|
| 31 | + | guard = 99; |
|
| 32 | + | }; |
|
| 33 | + | ||
| 34 | + | if guard != 99 { |
|
| 35 | + | return (3) - 42; |
|
| 36 | + | } |
|
| 37 | + | ||
| 38 | + | return (42) - 42; |
|
| 39 | + | } |
lib/std/arch/rv64/tests/slice.alloc.loop.rad
added
+37 -0
| 1 | + | //! Test returning a slice from a function and iterating over it. |
|
| 2 | + | //! Exercises the same code path as createBlock's vars initialization. |
|
| 3 | + | //! The slice is returned through a return buffer (> 8 bytes). |
|
| 4 | + | ||
| 5 | + | fn makeSlice(buf: *mut [u8], count: u32) -> *mut [u8] { |
|
| 6 | + | if count == 0 { |
|
| 7 | + | return &mut []; |
|
| 8 | + | } |
|
| 9 | + | return @sliceOf(&mut buf[0], count); |
|
| 10 | + | } |
|
| 11 | + | ||
| 12 | + | fn initSlice(buf: *mut [u8], count: u32) -> i32 { |
|
| 13 | + | let s = makeSlice(buf, count); |
|
| 14 | + | ||
| 15 | + | // This loop pattern matches createBlock's vars initialization. |
|
| 16 | + | for i in 0..s.len { |
|
| 17 | + | s[i] = 0; |
|
| 18 | + | } |
|
| 19 | + | ||
| 20 | + | return s.len as i32; |
|
| 21 | + | } |
|
| 22 | + | ||
| 23 | + | @default fn main() -> i32 { |
|
| 24 | + | let mut buf: [u8; 64] = undefined; |
|
| 25 | + | ||
| 26 | + | let n = initSlice(&mut buf[..], 5); |
|
| 27 | + | if n != 5 { |
|
| 28 | + | return 1; |
|
| 29 | + | } |
|
| 30 | + | ||
| 31 | + | let n2 = initSlice(&mut buf[..], 0); |
|
| 32 | + | if n2 != 0 { |
|
| 33 | + | return 2; |
|
| 34 | + | } |
|
| 35 | + | ||
| 36 | + | return 0; |
|
| 37 | + | } |
lib/std/arch/rv64/tests/slice.of.rad
added
+27 -0
| 1 | + | //! Test that @sliceOf produces a fat pointer with the correct length. |
|
| 2 | + | ||
| 3 | + | fn makeSlice(ptr: *mut i32, count: u32) -> *mut [i32] { |
|
| 4 | + | return @sliceOf(ptr, count); |
|
| 5 | + | } |
|
| 6 | + | ||
| 7 | + | @default fn main() -> i32 { |
|
| 8 | + | let mut arr: [i32; 4] = [10, 20, 30, 40]; |
|
| 9 | + | let s = makeSlice(&mut arr[0], 4); |
|
| 10 | + | ||
| 11 | + | if s.len != 4 { |
|
| 12 | + | return 1; |
|
| 13 | + | } |
|
| 14 | + | if s[0] != 10 { |
|
| 15 | + | return 2; |
|
| 16 | + | } |
|
| 17 | + | if s[3] != 40 { |
|
| 18 | + | return 3; |
|
| 19 | + | } |
|
| 20 | + | ||
| 21 | + | // Test with count=0. |
|
| 22 | + | let empty = makeSlice(&mut arr[0], 0); |
|
| 23 | + | if empty.len != 0 { |
|
| 24 | + | return 4; |
|
| 25 | + | } |
|
| 26 | + | return 0; |
|
| 27 | + | } |
lib/std/arch/rv64/tests/slice.subslice.rad
added
+56 -0
| 1 | + | //! returns: 42 |
|
| 2 | + | ||
| 3 | + | fn testBasicSubslice() -> bool { |
|
| 4 | + | let arr: [i32; 5] = [10, 20, 30, 40, 50]; |
|
| 5 | + | let slice: *[i32] = &arr[..]; // Full slice [10, 20, 30, 40, 50] |
|
| 6 | + | let subslice: *[i32] = slice[1..4]; // Sub-slice [20, 30, 40] |
|
| 7 | + | ||
| 8 | + | return subslice[0] == 20 and subslice[1] == 30 and subslice[2] == 40; |
|
| 9 | + | } |
|
| 10 | + | ||
| 11 | + | fn testNestedSubslice() -> bool { |
|
| 12 | + | let arr: [i32; 6] = [100, 200, 300, 400, 500, 600]; |
|
| 13 | + | let slice1: *[i32] = &arr[1..5]; // [200, 300, 400, 500] |
|
| 14 | + | let slice2: *[i32] = slice1[1..3]; // [300, 400] |
|
| 15 | + | let slice3: *[i32] = slice2[0..1]; // [300] |
|
| 16 | + | ||
| 17 | + | return slice3[0] == 300; |
|
| 18 | + | } |
|
| 19 | + | ||
| 20 | + | fn testSubsliceLength() -> bool { |
|
| 21 | + | let arr: [i32; 4] = [1, 2, 3, 4]; |
|
| 22 | + | let slice: *[i32] = &arr[..]; |
|
| 23 | + | let subslice: *[i32] = slice[1..3]; |
|
| 24 | + | ||
| 25 | + | return subslice.len == 2; |
|
| 26 | + | } |
|
| 27 | + | ||
| 28 | + | fn testEdgeCases() -> bool { |
|
| 29 | + | let arr: [i32; 3] = [7, 8, 9]; |
|
| 30 | + | let slice: *[i32] = &arr[..]; |
|
| 31 | + | ||
| 32 | + | // Test single element subslice |
|
| 33 | + | let single: *[i32] = slice[1..2]; |
|
| 34 | + | if (single[0] != 8 or single.len != 1) { |
|
| 35 | + | return false; |
|
| 36 | + | } |
|
| 37 | + | ||
| 38 | + | // Test empty subslice |
|
| 39 | + | let empty: *[i32] = slice[2..2]; |
|
| 40 | + | if (empty.len != 0) { |
|
| 41 | + | return false; |
|
| 42 | + | } |
|
| 43 | + | return true; |
|
| 44 | + | } |
|
| 45 | + | ||
| 46 | + | @default fn main() -> i32 { |
|
| 47 | + | if ( |
|
| 48 | + | testBasicSubslice() and |
|
| 49 | + | testNestedSubslice() and |
|
| 50 | + | testSubsliceLength() and |
|
| 51 | + | testEdgeCases() |
|
| 52 | + | ) { |
|
| 53 | + | return 42; |
|
| 54 | + | } |
|
| 55 | + | return 1; |
|
| 56 | + | } |
lib/std/arch/rv64/tests/spill.blockarg.clobber.rad
added
+130 -0
| 1 | + | //! Test that parallel block-argument shuffles at loop back-edges do not |
|
| 2 | + | //! clobber spilled sources. The compress loop carries many mutable variables |
|
| 3 | + | //! through block arguments; a bug in emitBlockArgs wrote spilled destinations |
|
| 4 | + | //! before the parallel-move pass had read their old values, corrupting the |
|
| 5 | + | //! rotation (e.g. new_f should receive old_e, but got new_e instead). |
|
| 6 | + | ||
| 7 | + | fn rotr(x: u32, n: u32) -> u32 { |
|
| 8 | + | return (x >> n) | (x << (32 - n)); |
|
| 9 | + | } |
|
| 10 | + | ||
| 11 | + | fn ch(e: u32, f: u32, g: u32) -> u32 { |
|
| 12 | + | return (e & f) ^ (~e & g); |
|
| 13 | + | } |
|
| 14 | + | ||
| 15 | + | fn maj(a: u32, b: u32, c: u32) -> u32 { |
|
| 16 | + | return (a & b) ^ (a & c) ^ (b & c); |
|
| 17 | + | } |
|
| 18 | + | ||
| 19 | + | fn bsig0(a: u32) -> u32 { |
|
| 20 | + | return rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22); |
|
| 21 | + | } |
|
| 22 | + | ||
| 23 | + | fn bsig1(e: u32) -> u32 { |
|
| 24 | + | return rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25); |
|
| 25 | + | } |
|
| 26 | + | ||
| 27 | + | fn ssig0(x: u32) -> u32 { |
|
| 28 | + | return rotr(x, 7) ^ rotr(x, 18) ^ (x >> 3); |
|
| 29 | + | } |
|
| 30 | + | ||
| 31 | + | fn ssig1(x: u32) -> u32 { |
|
| 32 | + | return rotr(x, 17) ^ rotr(x, 19) ^ (x >> 10); |
|
| 33 | + | } |
|
| 34 | + | ||
| 35 | + | record State { |
|
| 36 | + | h: [u32; 8], |
|
| 37 | + | w: [u32; 64], |
|
| 38 | + | } |
|
| 39 | + | ||
| 40 | + | fn prepareSchedule(s: *mut State, block: *[u32]) { |
|
| 41 | + | let mut i: u32 = 0; |
|
| 42 | + | while i < 16 { |
|
| 43 | + | s.w[i] = block[i]; |
|
| 44 | + | i = i + 1; |
|
| 45 | + | } |
|
| 46 | + | while i < 64 { |
|
| 47 | + | s.w[i] = ssig1(s.w[i - 2]) + s.w[i - 7] + ssig0(s.w[i - 15]) + s.w[i - 16]; |
|
| 48 | + | i = i + 1; |
|
| 49 | + | } |
|
| 50 | + | } |
|
| 51 | + | ||
| 52 | + | /// SHA-256 compression: 64 rounds with 8 rotating working variables. |
|
| 53 | + | /// Exercises heavy register pressure and spilled block-argument shuffles. |
|
| 54 | + | fn compress(s: *mut State, k: *[u32]) { |
|
| 55 | + | let mut a: u32 = s.h[0]; |
|
| 56 | + | let mut b: u32 = s.h[1]; |
|
| 57 | + | let mut c: u32 = s.h[2]; |
|
| 58 | + | let mut d: u32 = s.h[3]; |
|
| 59 | + | let mut e: u32 = s.h[4]; |
|
| 60 | + | let mut f: u32 = s.h[5]; |
|
| 61 | + | let mut g: u32 = s.h[6]; |
|
| 62 | + | let mut hh: u32 = s.h[7]; |
|
| 63 | + | ||
| 64 | + | let mut i: u32 = 0; |
|
| 65 | + | while i < 64 { |
|
| 66 | + | let t1 = hh + bsig1(e) + ch(e, f, g) + k[i] + s.w[i]; |
|
| 67 | + | let t2 = bsig0(a) + maj(a, b, c); |
|
| 68 | + | hh = g; |
|
| 69 | + | g = f; |
|
| 70 | + | f = e; |
|
| 71 | + | e = d + t1; |
|
| 72 | + | d = c; |
|
| 73 | + | c = b; |
|
| 74 | + | b = a; |
|
| 75 | + | a = t1 + t2; |
|
| 76 | + | i = i + 1; |
|
| 77 | + | } |
|
| 78 | + | ||
| 79 | + | s.h[0] = s.h[0] + a; |
|
| 80 | + | s.h[1] = s.h[1] + b; |
|
| 81 | + | s.h[2] = s.h[2] + c; |
|
| 82 | + | s.h[3] = s.h[3] + d; |
|
| 83 | + | s.h[4] = s.h[4] + e; |
|
| 84 | + | s.h[5] = s.h[5] + f; |
|
| 85 | + | s.h[6] = s.h[6] + g; |
|
| 86 | + | s.h[7] = s.h[7] + hh; |
|
| 87 | + | } |
|
| 88 | + | ||
| 89 | + | @default fn main() -> i32 { |
|
| 90 | + | let k: [u32; 64] = [ |
|
| 91 | + | 0x428A2F98, 0x71374491, 0xB5C0FBCF, 0xE9B5DBA5, |
|
| 92 | + | 0x3956C25B, 0x59F111F1, 0x923F82A4, 0xAB1C5ED5, |
|
| 93 | + | 0xD807AA98, 0x12835B01, 0x243185BE, 0x550C7DC3, |
|
| 94 | + | 0x72BE5D74, 0x80DEB1FE, 0x9BDC06A7, 0xC19BF174, |
|
| 95 | + | 0xE49B69C1, 0xEFBE4786, 0x0FC19DC6, 0x240CA1CC, |
|
| 96 | + | 0x2DE92C6F, 0x4A7484AA, 0x5CB0A9DC, 0x76F988DA, |
|
| 97 | + | 0x983E5152, 0xA831C66D, 0xB00327C8, 0xBF597FC7, |
|
| 98 | + | 0xC6E00BF3, 0xD5A79147, 0x06CA6351, 0x14292967, |
|
| 99 | + | 0x27B70A85, 0x2E1B2138, 0x4D2C6DFC, 0x53380D13, |
|
| 100 | + | 0x650A7354, 0x766A0ABB, 0x81C2C92E, 0x92722C85, |
|
| 101 | + | 0xA2BFE8A1, 0xA81A664B, 0xC24B8B70, 0xC76C51A3, |
|
| 102 | + | 0xD192E819, 0xD6990624, 0xF40E3585, 0x106AA070, |
|
| 103 | + | 0x19A4C116, 0x1E376C08, 0x2748774C, 0x34B0BCB5, |
|
| 104 | + | 0x391C0CB3, 0x4ED8AA4A, 0x5B9CCA4F, 0x682E6FF3, |
|
| 105 | + | 0x748F82EE, 0x78A5636F, 0x84C87814, 0x8CC70208, |
|
| 106 | + | 0x90BEFFFA, 0xA4506CEB, 0xBEF9A3F7, 0xC67178F2 |
|
| 107 | + | ]; |
|
| 108 | + | ||
| 109 | + | let mut s: State = State { |
|
| 110 | + | h: [0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, |
|
| 111 | + | 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19], |
|
| 112 | + | w: [0; 64] |
|
| 113 | + | }; |
|
| 114 | + | ||
| 115 | + | // Padded empty-message block. |
|
| 116 | + | let mut block: [u32; 16] = [0; 16]; |
|
| 117 | + | block[0] = 0x80000000; |
|
| 118 | + | ||
| 119 | + | prepareSchedule(&mut s, &block[..]); |
|
| 120 | + | compress(&mut s, &k[..]); |
|
| 121 | + | ||
| 122 | + | // SHA-256("") = e3b0c442 98fc1c14 ... |
|
| 123 | + | if s.h[0] != 0xE3B0C442 { |
|
| 124 | + | return 1; |
|
| 125 | + | } |
|
| 126 | + | if s.h[1] != 0x98FC1C14 { |
|
| 127 | + | return 2; |
|
| 128 | + | } |
|
| 129 | + | return 0; |
|
| 130 | + | } |
lib/std/arch/rv64/tests/spill.loop.rad
added
+69 -0
| 1 | + | //! Test register spilling in a loop with many live values. |
|
| 2 | + | //! Computes record layout from field tags, exercising alignment and offset |
|
| 3 | + | //! calculations that require multiple simultaneously live registers. |
|
| 4 | + | ||
| 5 | + | record Layout { |
|
| 6 | + | size: u32, |
|
| 7 | + | alignment: u32, |
|
| 8 | + | } |
|
| 9 | + | ||
| 10 | + | fn getLayout(tag: u8) -> Layout { |
|
| 11 | + | match tag { |
|
| 12 | + | case 0 => return Layout { size: 1, alignment: 1 }, |
|
| 13 | + | case 1 => return Layout { size: 2, alignment: 2 }, |
|
| 14 | + | case 2 => return Layout { size: 4, alignment: 4 }, |
|
| 15 | + | else => return Layout { size: 0, alignment: 0 }, |
|
| 16 | + | } |
|
| 17 | + | } |
|
| 18 | + | ||
| 19 | + | fn max(a: u32, b: u32) -> u32 { |
|
| 20 | + | if a > b { |
|
| 21 | + | return a; |
|
| 22 | + | } |
|
| 23 | + | return b; |
|
| 24 | + | } |
|
| 25 | + | ||
| 26 | + | fn alignUp(n: u32, a: u32) -> u32 { |
|
| 27 | + | if a == 0 { |
|
| 28 | + | return n; |
|
| 29 | + | } |
|
| 30 | + | return (n + a - 1) & (0 - a); |
|
| 31 | + | } |
|
| 32 | + | ||
| 33 | + | fn computeRecordLayout(tags: *[u8]) -> Layout { |
|
| 34 | + | let mut currentOffset: u32 = 0; |
|
| 35 | + | let mut maxAlignment: u32 = 1; |
|
| 36 | + | for i in 0..tags.len { |
|
| 37 | + | let layout = getLayout(tags[i]); |
|
| 38 | + | currentOffset = alignUp(currentOffset, layout.alignment); |
|
| 39 | + | currentOffset = currentOffset + layout.size; |
|
| 40 | + | maxAlignment = max(maxAlignment, layout.alignment); |
|
| 41 | + | } |
|
| 42 | + | return Layout { |
|
| 43 | + | size: alignUp(currentOffset, maxAlignment), |
|
| 44 | + | alignment: maxAlignment, |
|
| 45 | + | }; |
|
| 46 | + | } |
|
| 47 | + | ||
| 48 | + | @default fn main() -> i32 { |
|
| 49 | + | // Two u32 fields (tag 2 = size=4, align=4). |
|
| 50 | + | let mut tags: [u8; 2] = [2, 2]; |
|
| 51 | + | let layout = computeRecordLayout(&tags[0..2]); |
|
| 52 | + | if layout.alignment != 4 { |
|
| 53 | + | return 1; |
|
| 54 | + | } |
|
| 55 | + | if layout.size != 8 { |
|
| 56 | + | return 2; |
|
| 57 | + | } |
|
| 58 | + | ||
| 59 | + | // Mixed: u8 then u32. |
|
| 60 | + | let mut tags2: [u8; 2] = [0, 2]; |
|
| 61 | + | let layout2 = computeRecordLayout(&tags2[0..2]); |
|
| 62 | + | if layout2.alignment != 4 { |
|
| 63 | + | return 3; |
|
| 64 | + | } |
|
| 65 | + | if layout2.size != 8 { |
|
| 66 | + | return 4; |
|
| 67 | + | } |
|
| 68 | + | return 0; |
|
| 69 | + | } |
lib/std/arch/rv64/tests/stack.local.corrupt.rad
added
+17 -0
| 1 | + | //! Test that a local variable is not corrupted by a function call. |
|
| 2 | + | fn identity(x: i32) -> i32 { |
|
| 3 | + | return x; |
|
| 4 | + | } |
|
| 5 | + | ||
| 6 | + | @default fn main() -> i32 { |
|
| 7 | + | let mut local: i32 = 42; |
|
| 8 | + | let result = identity(10); |
|
| 9 | + | // Check that local wasn't corrupted by the call |
|
| 10 | + | if local != 42 { |
|
| 11 | + | return 1; |
|
| 12 | + | } |
|
| 13 | + | if result != 10 { |
|
| 14 | + | return 2; |
|
| 15 | + | } |
|
| 16 | + | return 0; |
|
| 17 | + | } |
lib/std/arch/rv64/tests/static.array.mutate.rad
added
+20 -0
| 1 | + | //! Test mutating a static array. |
|
| 2 | + | ||
| 3 | + | static BUFFER: [i32; 3] = [1, 2, 3]; |
|
| 4 | + | ||
| 5 | + | fn bump(slot: u32, amount: i32) -> i32 { |
|
| 6 | + | BUFFER[slot] = BUFFER[slot] + amount; |
|
| 7 | + | return BUFFER[0] + BUFFER[1] + BUFFER[2]; |
|
| 8 | + | } |
|
| 9 | + | ||
| 10 | + | @default fn main() -> i32 { |
|
| 11 | + | let a: i32 = bump(0, 4); |
|
| 12 | + | let b: i32 = bump(1, 5); |
|
| 13 | + | let c: i32 = bump(2, 6); |
|
| 14 | + | ||
| 15 | + | // a=10, b=15, c=21 => total=46 |
|
| 16 | + | if a + b + c != 46 { |
|
| 17 | + | return 1; |
|
| 18 | + | } |
|
| 19 | + | return 0; |
|
| 20 | + | } |
lib/std/arch/rv64/tests/static.basic.rad
added
+22 -0
| 1 | + | //! returns: 66 |
|
| 2 | + | ||
| 3 | + | static GLOBAL: i32 = 0; |
|
| 4 | + | ||
| 5 | + | fn updateCounter(i: i32) -> i32 { |
|
| 6 | + | static counter: i32 = 0; |
|
| 7 | + | ||
| 8 | + | counter = counter + i; |
|
| 9 | + | GLOBAL = GLOBAL + i * 10; |
|
| 10 | + | ||
| 11 | + | return counter; |
|
| 12 | + | } |
|
| 13 | + | ||
| 14 | + | @default fn main() -> i32 { |
|
| 15 | + | let mut n: i32 = 0; |
|
| 16 | + | ||
| 17 | + | n = updateCounter(1); |
|
| 18 | + | n = updateCounter(2); |
|
| 19 | + | n = updateCounter(3); |
|
| 20 | + | ||
| 21 | + | return n + GLOBAL; |
|
| 22 | + | } |
lib/std/arch/rv64/tests/static.fn.array.rad
added
+37 -0
| 1 | + | //! Test function pointers stored in a static (read-write) array. |
|
| 2 | + | //! Verifies that function references in rw data are correctly resolved |
|
| 3 | + | //! and callable. |
|
| 4 | + | //! returns: 0 |
|
| 5 | + | ||
| 6 | + | fn double(x: i32) -> i32 { |
|
| 7 | + | return x * 2; |
|
| 8 | + | } |
|
| 9 | + | ||
| 10 | + | fn negate(x: i32) -> i32 { |
|
| 11 | + | return 0 - x; |
|
| 12 | + | } |
|
| 13 | + | ||
| 14 | + | fn identity(x: i32) -> i32 { |
|
| 15 | + | return x; |
|
| 16 | + | } |
|
| 17 | + | ||
| 18 | + | static OPS: [fn(i32) -> i32; 3] = [double, negate, identity]; |
|
| 19 | + | ||
| 20 | + | @default fn main() -> i32 { |
|
| 21 | + | // double(5) == 10 |
|
| 22 | + | let r0 = OPS[0](5); |
|
| 23 | + | if r0 != 10 { |
|
| 24 | + | return 1; |
|
| 25 | + | } |
|
| 26 | + | // negate(5) == -5 |
|
| 27 | + | let r1 = OPS[1](5); |
|
| 28 | + | if r1 != 0 - 5 { |
|
| 29 | + | return 2; |
|
| 30 | + | } |
|
| 31 | + | // identity(5) == 5 |
|
| 32 | + | let r2 = OPS[2](5); |
|
| 33 | + | if r2 != 5 { |
|
| 34 | + | return 3; |
|
| 35 | + | } |
|
| 36 | + | return 0; |
|
| 37 | + | } |
lib/std/arch/rv64/tests/static.record.array.rad
added
+27 -0
| 1 | + | //! Test mutating a static record containing an array. |
|
| 2 | + | ||
| 3 | + | record Record { |
|
| 4 | + | values: [i32; 4], |
|
| 5 | + | bias: i32, |
|
| 6 | + | } |
|
| 7 | + | ||
| 8 | + | static TABLE: Record = Record { |
|
| 9 | + | values: [5, 1, 9, 2], |
|
| 10 | + | bias: 4, |
|
| 11 | + | }; |
|
| 12 | + | ||
| 13 | + | fn rebalance() -> i32 { |
|
| 14 | + | TABLE.values[0] = TABLE.values[0] + TABLE.bias; |
|
| 15 | + | TABLE.values[3] = TABLE.values[3] + 1; |
|
| 16 | + | return TABLE.values[0] + TABLE.values[3] + TABLE.bias; |
|
| 17 | + | } |
|
| 18 | + | ||
| 19 | + | @default fn main() -> i32 { |
|
| 20 | + | let first: i32 = rebalance(); |
|
| 21 | + | let second: i32 = TABLE.values[1] + TABLE.values[2]; |
|
| 22 | + | ||
| 23 | + | if first + second != 26 { |
|
| 24 | + | return 1; |
|
| 25 | + | } |
|
| 26 | + | return 0; |
|
| 27 | + | } |
lib/std/arch/rv64/tests/static.slice.index.assign.rad
added
+24 -0
| 1 | + | //! returns: 42 |
|
| 2 | + | ||
| 3 | + | record Inner { |
|
| 4 | + | value: i32, |
|
| 5 | + | } |
|
| 6 | + | ||
| 7 | + | record Container { |
|
| 8 | + | pad: i32, |
|
| 9 | + | items: [Inner; 3], |
|
| 10 | + | } |
|
| 11 | + | ||
| 12 | + | static container: Container = undefined; |
|
| 13 | + | ||
| 14 | + | @default fn main() -> i32 { |
|
| 15 | + | container.items[0].value = 10; |
|
| 16 | + | container.items[1].value = 20; |
|
| 17 | + | container.items[2].value = 30; |
|
| 18 | + | ||
| 19 | + | let slice: *mut [Inner] = &mut container.items[..]; |
|
| 20 | + | ||
| 21 | + | slice[1].value = 42; |
|
| 22 | + | ||
| 23 | + | return container.items[1].value; |
|
| 24 | + | } |
lib/std/arch/rv64/tests/static.slice.offset.rad
added
+36 -0
| 1 | + | //! Test slice indexing into a static table with offsets. |
|
| 2 | + | ||
| 3 | + | record Entry { |
|
| 4 | + | a: i32, |
|
| 5 | + | b: i32, |
|
| 6 | + | } |
|
| 7 | + | ||
| 8 | + | record Table { |
|
| 9 | + | scratch: *mut [Entry], |
|
| 10 | + | entries: *mut [Entry], |
|
| 11 | + | len: u32, |
|
| 12 | + | } |
|
| 13 | + | ||
| 14 | + | static SCRATCH: [Entry; 1] = [Entry { a: 0, b: 0 }]; |
|
| 15 | + | static STORAGE: [Entry; 2] = [ |
|
| 16 | + | Entry { a: 0, b: 0 }, |
|
| 17 | + | Entry { a: 0, b: 0 }, |
|
| 18 | + | ]; |
|
| 19 | + | static TBL: Table = undefined; |
|
| 20 | + | ||
| 21 | + | @default fn main() -> i32 { |
|
| 22 | + | TBL.scratch = &mut SCRATCH[..]; |
|
| 23 | + | TBL.entries = &mut STORAGE[..]; |
|
| 24 | + | TBL.len = 0; |
|
| 25 | + | ||
| 26 | + | TBL.entries[TBL.len] = Entry { a: 7, b: 9 }; |
|
| 27 | + | TBL.len = TBL.len + 1; |
|
| 28 | + | ||
| 29 | + | if STORAGE[0].a != 7 or STORAGE[0].b != 9 { |
|
| 30 | + | return 1; |
|
| 31 | + | } |
|
| 32 | + | if SCRATCH[0].a != 0 or SCRATCH[0].b != 0 { |
|
| 33 | + | return 2; |
|
| 34 | + | } |
|
| 35 | + | return 0; |
|
| 36 | + | } |
lib/std/arch/rv64/tests/string.basic.rad
added
+8 -0
| 1 | + | // Test string literals. |
|
| 2 | + | ||
| 3 | + | @default fn main() -> u32 { |
|
| 4 | + | let s: *[u8] = "fnord"; |
|
| 5 | + | ||
| 6 | + | // Return the length of the string. |
|
| 7 | + | return (s.len) - 5; |
|
| 8 | + | } |
lib/std/arch/rv64/tests/string.escape.rad
added
+30 -0
| 1 | + | //! Test string escape sequences. |
|
| 2 | + | @default fn main() -> i32 { |
|
| 3 | + | let s1: *[u8] = "Hello\tWorld!\n"; |
|
| 4 | + | if s1.len != 13 { |
|
| 5 | + | panic; |
|
| 6 | + | } |
|
| 7 | + | ||
| 8 | + | let s2: *[u8] = "\""; |
|
| 9 | + | if s2.len != 1 { |
|
| 10 | + | panic; |
|
| 11 | + | } |
|
| 12 | + | if s2[0] != '"' { |
|
| 13 | + | panic; |
|
| 14 | + | } |
|
| 15 | + | ||
| 16 | + | let s3: *[u8] = "\"\\\""; |
|
| 17 | + | if s3.len != 3 { |
|
| 18 | + | panic; |
|
| 19 | + | } |
|
| 20 | + | if s3[0] != '"' { |
|
| 21 | + | panic; |
|
| 22 | + | } |
|
| 23 | + | if s3[1] != '\\' { |
|
| 24 | + | panic; |
|
| 25 | + | } |
|
| 26 | + | if s3[2] != '"' { |
|
| 27 | + | panic; |
|
| 28 | + | } |
|
| 29 | + | return 0; |
|
| 30 | + | } |
lib/std/arch/rv64/tests/string.index.rad
added
+7 -0
| 1 | + | // Test string indexing. |
|
| 2 | + | ||
| 3 | + | @default fn main() -> u8 { |
|
| 4 | + | let s: *[u8] = "fnord"; |
|
| 5 | + | ||
| 6 | + | return (s[1] + s[2]) - 221; |
|
| 7 | + | } |
lib/std/arch/rv64/tests/switch.blockargs.clobber.rad
added
+56 -0
| 1 | + | //! Regression test: Switch block-arg moves must not clobber the switch value. |
|
| 2 | + | //! |
|
| 3 | + | //! When a union has two void variants followed by a payload variant, the |
|
| 4 | + | //! generated switch instruction has cases like: |
|
| 5 | + | //! |
|
| 6 | + | //! switch %tag (0 @merge(1)) (1 @merge(1)) (2 @payload) @unreachable; |
|
| 7 | + | //! |
|
| 8 | + | //! In the isel, `emitBlockArgs` for case 0 writes `1` (the merge arg) to the |
|
| 9 | + | //! merge block's parameter register. If the register allocator assigned that |
|
| 10 | + | //! parameter register to the same physical register as the switch value, the |
|
| 11 | + | //! tag is clobbered before subsequent case comparisons run. |
|
| 12 | + | ||
| 13 | + | union Val { |
|
| 14 | + | none, |
|
| 15 | + | empty, |
|
| 16 | + | data(i32), |
|
| 17 | + | } |
|
| 18 | + | ||
| 19 | + | @default fn main() -> i32 { |
|
| 20 | + | // Critical case: two payload values that differ. |
|
| 21 | + | // With the bug, the switch clobber makes this return true. |
|
| 22 | + | let a: Val = Val::data(42); |
|
| 23 | + | let b: Val = Val::data(99); |
|
| 24 | + | if a == b { |
|
| 25 | + | return 1; |
|
| 26 | + | } |
|
| 27 | + | ||
| 28 | + | // Same payload - should be equal. |
|
| 29 | + | let c: Val = Val::data(42); |
|
| 30 | + | let d: Val = Val::data(42); |
|
| 31 | + | if c != d { |
|
| 32 | + | return 2; |
|
| 33 | + | } |
|
| 34 | + | ||
| 35 | + | // Both void (none) - should be equal. |
|
| 36 | + | let e: Val = Val::none; |
|
| 37 | + | let f: Val = Val::none; |
|
| 38 | + | if e != f { |
|
| 39 | + | return 3; |
|
| 40 | + | } |
|
| 41 | + | ||
| 42 | + | // Both void (empty) - should be equal. |
|
| 43 | + | let g: Val = Val::empty; |
|
| 44 | + | let h: Val = Val::empty; |
|
| 45 | + | if g != h { |
|
| 46 | + | return 4; |
|
| 47 | + | } |
|
| 48 | + | ||
| 49 | + | // Different variants - should not be equal. |
|
| 50 | + | let i: Val = Val::none; |
|
| 51 | + | let j: Val = Val::data(42); |
|
| 52 | + | if i == j { |
|
| 53 | + | return 5; |
|
| 54 | + | } |
|
| 55 | + | return 0; |
|
| 56 | + | } |
lib/std/arch/rv64/tests/type.unify.rad
added
+189 -0
| 1 | + | //! returns: 0 |
|
| 2 | + | ||
| 3 | + | /// Verifies numeric casting for mixed-width integer arithmetic. |
|
| 4 | + | fn testNumericUnification() -> bool { |
|
| 5 | + | let a: i8 = 10; |
|
| 6 | + | let b: i32 = 20; |
|
| 7 | + | let sum1: i32 = a as i32 + b; |
|
| 8 | + | ||
| 9 | + | let c: u8 = 5; |
|
| 10 | + | let d: u16 = 10; |
|
| 11 | + | let sum2: u16 = c as u16 + d; |
|
| 12 | + | ||
| 13 | + | return true; |
|
| 14 | + | } |
|
| 15 | + | ||
| 16 | + | /// Verifies coercions and assignments for optional values. |
|
| 17 | + | fn testOptionalUnification() -> bool { |
|
| 18 | + | let opt: ?i32 = 42; |
|
| 19 | + | ||
| 20 | + | let value: i32 = 100; |
|
| 21 | + | let optValue: ?i32 = value; |
|
| 22 | + | ||
| 23 | + | let optExpr: ?i32 = 4 + 1; |
|
| 24 | + | let optComplex: ?i32 = (10 * 2) - 5; |
|
| 25 | + | ||
| 26 | + | let opt1: ?i32 = 10; |
|
| 27 | + | let opt2: ?i32 = 20; |
|
| 28 | + | let mut optResult: ?i32 = opt1; |
|
| 29 | + | optResult = opt2; |
|
| 30 | + | ||
| 31 | + | if let a = optExpr { |
|
| 32 | + | if let b = optComplex { |
|
| 33 | + | return a + b == 20; |
|
| 34 | + | } |
|
| 35 | + | } |
|
| 36 | + | return false; |
|
| 37 | + | } |
|
| 38 | + | ||
| 39 | + | /// Verifies assignment compatibility for arrays with identical element types and lengths. |
|
| 40 | + | fn testArrayUnification() -> bool { |
|
| 41 | + | let arr1: [i32; 3] = [1, 2, 3]; |
|
| 42 | + | let arr2: [i32; 3] = [4, 5, 6]; |
|
| 43 | + | let mut arrResult: [i32; 3] = arr1; |
|
| 44 | + | arrResult = arr2; |
|
| 45 | + | ||
| 46 | + | return true; |
|
| 47 | + | } |
|
| 48 | + | ||
| 49 | + | /// Verifies assignment compatibility for pointers with identical target types. |
|
| 50 | + | fn testPointerUnification() -> bool { |
|
| 51 | + | let x: i32 = 42; |
|
| 52 | + | let y: i32 = 24; |
|
| 53 | + | let ptr1: *i32 = &x; |
|
| 54 | + | let ptr2: *i32 = &y; |
|
| 55 | + | let mut ptrResult: *i32 = ptr1; |
|
| 56 | + | ptrResult = ptr2; |
|
| 57 | + | ||
| 58 | + | return true; |
|
| 59 | + | } |
|
| 60 | + | ||
| 61 | + | /// Verifies conversion from array references to slices. |
|
| 62 | + | fn testArrayToSlice() -> bool { |
|
| 63 | + | let arr: [i32; 3] = [1, 2, 3]; |
|
| 64 | + | let slice: *[i32] = &arr[..]; |
|
| 65 | + | ||
| 66 | + | return true; |
|
| 67 | + | } |
|
| 68 | + | ||
| 69 | + | /// Verifies mixed signed and unsigned arithmetic using an explicit cast. |
|
| 70 | + | fn testSignedUnsignedUnification() -> bool { |
|
| 71 | + | let signed: i16 = 10; |
|
| 72 | + | let unsigned: u16 = 20; |
|
| 73 | + | let result: u16 = signed as u16 + unsigned; |
|
| 74 | + | ||
| 75 | + | return result == 30; |
|
| 76 | + | } |
|
| 77 | + | ||
| 78 | + | /// Verifies explicit casts for wider integer type arithmetic. |
|
| 79 | + | fn testSizePromotion() -> bool { |
|
| 80 | + | let small: i8 = 5; |
|
| 81 | + | let large: i32 = 100; |
|
| 82 | + | let result: i32 = small as i32 + large; |
|
| 83 | + | ||
| 84 | + | let small_u: u8 = 10; |
|
| 85 | + | let large_u: u32 = 200; |
|
| 86 | + | let result2: u32 = small_u as u32 + large_u; |
|
| 87 | + | ||
| 88 | + | return true; |
|
| 89 | + | } |
|
| 90 | + | ||
| 91 | + | /// Verifies basic optional assignments between values of the same optional type. |
|
| 92 | + | fn testSimpleOptionalValues() -> bool { |
|
| 93 | + | let opt1: ?i32 = 10; |
|
| 94 | + | let opt2: ?i32 = 20; |
|
| 95 | + | let mut result: ?i32 = opt1; |
|
| 96 | + | result = opt2; |
|
| 97 | + | ||
| 98 | + | return true; |
|
| 99 | + | } |
|
| 100 | + | ||
| 101 | + | /// Verifies coercion from pointer values to optional pointer values. |
|
| 102 | + | fn testPointerToOptional() -> bool { |
|
| 103 | + | let value: i32 = 42; |
|
| 104 | + | let ptr: *i32 = &value; |
|
| 105 | + | let opt_ptr: ?*i32 = ptr; |
|
| 106 | + | ||
| 107 | + | return true; |
|
| 108 | + | } |
|
| 109 | + | ||
| 110 | + | /// Verifies creation of slices for different, but internally consistent, element types. |
|
| 111 | + | fn testSliceElementUnification() -> bool { |
|
| 112 | + | let arr_small: [i8; 3] = [1, 2, 3]; |
|
| 113 | + | let slice_small: *[i8] = &arr_small[..]; |
|
| 114 | + | ||
| 115 | + | let arr_large: [i32; 3] = [10, 20, 30]; |
|
| 116 | + | let slice_large: *[i32] = &arr_large[..]; |
|
| 117 | + | ||
| 118 | + | return true; |
|
| 119 | + | } |
|
| 120 | + | ||
| 121 | + | /// Verifies optional assignments involving `nil` and concrete values. |
|
| 122 | + | fn testNilUnification() -> bool { |
|
| 123 | + | let opt1: ?i32 = nil; |
|
| 124 | + | let opt2: ?i32 = 42; |
|
| 125 | + | let mut result: ?i32 = opt1; |
|
| 126 | + | result = opt2; |
|
| 127 | + | result = nil; |
|
| 128 | + | ||
| 129 | + | return true; |
|
| 130 | + | } |
|
| 131 | + | ||
| 132 | + | /// Verifies that arrays of different lengths remain distinct types. |
|
| 133 | + | fn testArrayLengthCompatibility() -> bool { |
|
| 134 | + | let short: [i32; 2] = [1, 2]; |
|
| 135 | + | let long: [i32; 3] = [1, 2, 3]; |
|
| 136 | + | ||
| 137 | + | return true; |
|
| 138 | + | } |
|
| 139 | + | ||
| 140 | + | /// Verifies repeated pointer assignments across multiple values of the same type. |
|
| 141 | + | fn testMultiplePointerAssignments() -> bool { |
|
| 142 | + | let value1: i32 = 42; |
|
| 143 | + | let value2: i32 = 24; |
|
| 144 | + | let value3: i32 = 100; |
|
| 145 | + | ||
| 146 | + | let ptr1: *i32 = &value1; |
|
| 147 | + | let ptr2: *i32 = &value2; |
|
| 148 | + | let ptr3: *i32 = &value3; |
|
| 149 | + | ||
| 150 | + | let mut result: *i32 = ptr1; |
|
| 151 | + | result = ptr2; |
|
| 152 | + | result = ptr3; |
|
| 153 | + | ||
| 154 | + | return true; |
|
| 155 | + | } |
|
| 156 | + | ||
| 157 | + | /// Verifies assignment compatibility for boolean values. |
|
| 158 | + | fn testBoolUnification() -> bool { |
|
| 159 | + | let b1: bool = true; |
|
| 160 | + | let b2: bool = false; |
|
| 161 | + | let mut result: bool = b1; |
|
| 162 | + | result = b2; |
|
| 163 | + | ||
| 164 | + | return true; |
|
| 165 | + | } |
|
| 166 | + | ||
| 167 | + | @default fn main() -> i32 { |
|
| 168 | + | let testResult: bool = |
|
| 169 | + | testNumericUnification() and |
|
| 170 | + | testOptionalUnification() and |
|
| 171 | + | testArrayUnification() and |
|
| 172 | + | testPointerUnification() and |
|
| 173 | + | testArrayToSlice() and |
|
| 174 | + | testSignedUnsignedUnification() and |
|
| 175 | + | testSizePromotion() and |
|
| 176 | + | testSimpleOptionalValues() and |
|
| 177 | + | testPointerToOptional() and |
|
| 178 | + | testSliceElementUnification() and |
|
| 179 | + | testNilUnification() and |
|
| 180 | + | testArrayLengthCompatibility() and |
|
| 181 | + | testMultiplePointerAssignments() and |
|
| 182 | + | testBoolUnification(); |
|
| 183 | + | ||
| 184 | + | if (testResult) { |
|
| 185 | + | return 0; |
|
| 186 | + | } else { |
|
| 187 | + | return 1; |
|
| 188 | + | } |
|
| 189 | + | } |
lib/std/arch/rv64/tests/undefined.rad
added
+35 -0
| 1 | + | //! Test undefined values for arrays and assignment. |
|
| 2 | + | @default fn main() -> i32 { |
|
| 3 | + | let mut ary: [u16; 32] = undefined; |
|
| 4 | + | let x: u32 = 8; |
|
| 5 | + | let y: u32 = 9; |
|
| 6 | + | ||
| 7 | + | ary[0] = 1; |
|
| 8 | + | ary[1] = 2; |
|
| 9 | + | ary[2] = 3; |
|
| 10 | + | ary[3] = 4; |
|
| 11 | + | ary[4] = 5; |
|
| 12 | + | ||
| 13 | + | if x != 8 { |
|
| 14 | + | return 1; |
|
| 15 | + | } |
|
| 16 | + | if y != 9 { |
|
| 17 | + | return 2; |
|
| 18 | + | } |
|
| 19 | + | if ary[0] != 1 { |
|
| 20 | + | return 3; |
|
| 21 | + | } |
|
| 22 | + | if ary[1] != 2 { |
|
| 23 | + | return 4; |
|
| 24 | + | } |
|
| 25 | + | if ary[2] != 3 { |
|
| 26 | + | return 5; |
|
| 27 | + | } |
|
| 28 | + | if ary[3] != 4 { |
|
| 29 | + | return 6; |
|
| 30 | + | } |
|
| 31 | + | if ary[4] != 5 { |
|
| 32 | + | return 7; |
|
| 33 | + | } |
|
| 34 | + | return 0; |
|
| 35 | + | } |
lib/std/arch/rv64/tests/union-tag.rad
added
+62 -0
| 1 | + | //! Test that union variant tags are stored correctly for unions with many variants. |
|
| 2 | + | ||
| 3 | + | union BigUnion { |
|
| 4 | + | V0, |
|
| 5 | + | V1, |
|
| 6 | + | V2, |
|
| 7 | + | V3, |
|
| 8 | + | V4, |
|
| 9 | + | V5, |
|
| 10 | + | V6, |
|
| 11 | + | V7, |
|
| 12 | + | V8, |
|
| 13 | + | V9, |
|
| 14 | + | V10, |
|
| 15 | + | V11, |
|
| 16 | + | V12, |
|
| 17 | + | V13, |
|
| 18 | + | V14 { a: i32, b: i32 }, |
|
| 19 | + | V15 { x: i32, y: bool }, |
|
| 20 | + | V16 { p: i32, q: bool }, |
|
| 21 | + | V17, |
|
| 22 | + | V18, |
|
| 23 | + | V19, |
|
| 24 | + | V20, |
|
| 25 | + | } |
|
| 26 | + | ||
| 27 | + | fn tag(u: *BigUnion) -> u8 { |
|
| 28 | + | let p = u as *opaque as *u8; |
|
| 29 | + | return *p; |
|
| 30 | + | } |
|
| 31 | + | ||
| 32 | + | @default fn main() -> i32 { |
|
| 33 | + | let v0 = BigUnion::V0; |
|
| 34 | + | if tag(&v0) != 0 { |
|
| 35 | + | return 1; |
|
| 36 | + | } |
|
| 37 | + | let v7 = BigUnion::V7; |
|
| 38 | + | if tag(&v7) != 7 { |
|
| 39 | + | return 2; |
|
| 40 | + | } |
|
| 41 | + | let v13 = BigUnion::V13; |
|
| 42 | + | if tag(&v13) != 13 { |
|
| 43 | + | return 3; |
|
| 44 | + | } |
|
| 45 | + | let v14 = BigUnion::V14 { a: 1, b: 2 }; |
|
| 46 | + | if tag(&v14) != 14 { |
|
| 47 | + | return 4; |
|
| 48 | + | } |
|
| 49 | + | let v15 = BigUnion::V15 { x: 3, y: true }; |
|
| 50 | + | if tag(&v15) != 15 { |
|
| 51 | + | return 5; |
|
| 52 | + | } |
|
| 53 | + | let v16 = BigUnion::V16 { p: 4, q: false }; |
|
| 54 | + | if tag(&v16) != 16 { |
|
| 55 | + | return 6; |
|
| 56 | + | } |
|
| 57 | + | let v20 = BigUnion::V20; |
|
| 58 | + | if tag(&v20) != 20 { |
|
| 59 | + | return 7; |
|
| 60 | + | } |
|
| 61 | + | return 0; |
|
| 62 | + | } |
lib/std/arch/rv64/tests/union.bitfield.rad
added
+46 -0
| 1 | + | //! returns: 42 |
|
| 2 | + | ||
| 3 | + | union Attribute { |
|
| 4 | + | None = 0b0, |
|
| 5 | + | Pub = 0b10, |
|
| 6 | + | Default = 0b100, |
|
| 7 | + | Extern = 0b1000, |
|
| 8 | + | Test = 0b10000, |
|
| 9 | + | } |
|
| 10 | + | ||
| 11 | + | fn hasFlag(bits: i32, flag: i32) -> bool { |
|
| 12 | + | return (bits & flag) == flag; |
|
| 13 | + | } |
|
| 14 | + | ||
| 15 | + | fn testCombineDefaults() -> bool { |
|
| 16 | + | let combined: i32 = (Attribute::Pub as i32) | (Attribute::Default as i32); |
|
| 17 | + | ||
| 18 | + | return hasFlag(combined, Attribute::Pub as i32) and |
|
| 19 | + | hasFlag(combined, Attribute::Default as i32) and |
|
| 20 | + | not hasFlag(combined, Attribute::Extern as i32); |
|
| 21 | + | } |
|
| 22 | + | ||
| 23 | + | fn testToggleExternFlag() -> bool { |
|
| 24 | + | let initial: i32 = (Attribute::Pub as i32) | (Attribute::Extern as i32); |
|
| 25 | + | let toggled: i32 = initial ^ (Attribute::Extern as i32); |
|
| 26 | + | ||
| 27 | + | return hasFlag(toggled, Attribute::Pub as i32) |
|
| 28 | + | and not hasFlag(toggled, Attribute::Extern as i32); |
|
| 29 | + | } |
|
| 30 | + | ||
| 31 | + | fn testMaskTestFlag() -> bool { |
|
| 32 | + | let mask: i32 = (Attribute::Pub as i32) | (Attribute::Test as i32); |
|
| 33 | + | let cleared: i32 = mask & (~(Attribute::Test as i32)); |
|
| 34 | + | ||
| 35 | + | return hasFlag(cleared, Attribute::Pub as i32) |
|
| 36 | + | and not hasFlag(cleared, Attribute::Test as i32); |
|
| 37 | + | } |
|
| 38 | + | ||
| 39 | + | @default fn main() -> i32 { |
|
| 40 | + | if testCombineDefaults() |
|
| 41 | + | and testToggleExternFlag() |
|
| 42 | + | and testMaskTestFlag() { |
|
| 43 | + | return 42; |
|
| 44 | + | } |
|
| 45 | + | return 1; |
|
| 46 | + | } |
lib/std/arch/rv64/tests/union.discriminant.cast.rad
added
+28 -0
| 1 | + | //! returns: 0 |
|
| 2 | + | //! Casting a void-union variant to `i32` should produce its explicit tag value. |
|
| 3 | + | ||
| 4 | + | union U { |
|
| 5 | + | A = 7, |
|
| 6 | + | B = 42, |
|
| 7 | + | C = 255, |
|
| 8 | + | } |
|
| 9 | + | ||
| 10 | + | @default fn main() -> i32 { |
|
| 11 | + | let a: i32 = U::A as i32; |
|
| 12 | + | let b: i32 = U::B as i32; |
|
| 13 | + | let c: i32 = U::C as i32; |
|
| 14 | + | ||
| 15 | + | if a != 7 { |
|
| 16 | + | return 1; |
|
| 17 | + | } |
|
| 18 | + | if b != 42 { |
|
| 19 | + | return 2; |
|
| 20 | + | } |
|
| 21 | + | if c != 255 { |
|
| 22 | + | return 3; |
|
| 23 | + | } |
|
| 24 | + | if (U::A as i32) + (U::B as i32) != 49 { |
|
| 25 | + | return 4; |
|
| 26 | + | } |
|
| 27 | + | return 0; |
|
| 28 | + | } |
lib/std/arch/rv64/tests/union.edge.case.2.rad
added
+41 -0
| 1 | + | ||
| 2 | + | union NodeValue { |
|
| 3 | + | Placeholder, |
|
| 4 | + | Nil, |
|
| 5 | + | Bool(bool), |
|
| 6 | + | } |
|
| 7 | + | ||
| 8 | + | fn nodeNil() -> NodeValue { |
|
| 9 | + | return NodeValue::Nil; |
|
| 10 | + | } |
|
| 11 | + | ||
| 12 | + | fn nodeBool(value: bool) -> NodeValue { |
|
| 13 | + | return NodeValue::Bool(value); |
|
| 14 | + | } |
|
| 15 | + | ||
| 16 | + | @default fn main() -> u32 { |
|
| 17 | + | match nodeBool(true) { |
|
| 18 | + | case NodeValue::Bool(t) => { |
|
| 19 | + | if t { |
|
| 20 | + | return 0; |
|
| 21 | + | } else { |
|
| 22 | + | return 1; |
|
| 23 | + | } |
|
| 24 | + | } |
|
| 25 | + | else => { |
|
| 26 | + | return 2; |
|
| 27 | + | } |
|
| 28 | + | } |
|
| 29 | + | ||
| 30 | + | match nodeNil() { |
|
| 31 | + | case NodeValue::Nil => { |
|
| 32 | + | return 0; |
|
| 33 | + | } |
|
| 34 | + | case NodeValue::Bool(t) => { |
|
| 35 | + | return 1; |
|
| 36 | + | } |
|
| 37 | + | else => { |
|
| 38 | + | return 2; |
|
| 39 | + | } |
|
| 40 | + | } |
|
| 41 | + | } |
lib/std/arch/rv64/tests/union.edge.case.3.rad
added
+36 -0
| 1 | + | //! Test union match through pointer dereference. |
|
| 2 | + | ||
| 3 | + | union Node { |
|
| 4 | + | Placeholder(u8), |
|
| 5 | + | Nil(u8), |
|
| 6 | + | Bool(bool), |
|
| 7 | + | } |
|
| 8 | + | ||
| 9 | + | record Parser { |
|
| 10 | + | root: Node, |
|
| 11 | + | } |
|
| 12 | + | ||
| 13 | + | fn node(p: *mut Parser, value: Node) -> *Node { |
|
| 14 | + | p.root = value; |
|
| 15 | + | return &p.root; |
|
| 16 | + | } |
|
| 17 | + | ||
| 18 | + | fn nodeBool(p: *mut Parser, value: bool) -> *Node { |
|
| 19 | + | return node(p, Node::Bool(value)); |
|
| 20 | + | } |
|
| 21 | + | ||
| 22 | + | @default fn main() -> u32 { |
|
| 23 | + | let mut parser = Parser { root: undefined }; |
|
| 24 | + | ||
| 25 | + | match *nodeBool(&mut parser, true) { |
|
| 26 | + | case Node::Bool(v) => { |
|
| 27 | + | if v != true { |
|
| 28 | + | return 1; |
|
| 29 | + | } |
|
| 30 | + | return 0; |
|
| 31 | + | } |
|
| 32 | + | else => { |
|
| 33 | + | return 2; |
|
| 34 | + | } |
|
| 35 | + | } |
|
| 36 | + | } |
lib/std/arch/rv64/tests/union.mixed.assign.rad
added
+57 -0
| 1 | + | //! Test union variant assignment and if-let-case matching. |
|
| 2 | + | ||
| 3 | + | union Mixed { |
|
| 4 | + | Payload(i32), |
|
| 5 | + | Standalone, |
|
| 6 | + | AnotherPayload(bool), |
|
| 7 | + | Final, |
|
| 8 | + | } |
|
| 9 | + | ||
| 10 | + | record Holder { |
|
| 11 | + | value: Mixed, |
|
| 12 | + | } |
|
| 13 | + | ||
| 14 | + | fn storePayload(holder: *mut Holder, value: i32) { |
|
| 15 | + | holder.value = Mixed::Payload(value); |
|
| 16 | + | } |
|
| 17 | + | ||
| 18 | + | fn storeFinal(holder: *mut Holder) { |
|
| 19 | + | holder.value = Mixed::Final; |
|
| 20 | + | } |
|
| 21 | + | ||
| 22 | + | fn checkIfLet(value: Mixed) -> i32 { |
|
| 23 | + | if let case Mixed::Payload(v) = value { |
|
| 24 | + | return v; |
|
| 25 | + | } |
|
| 26 | + | return 0; |
|
| 27 | + | } |
|
| 28 | + | ||
| 29 | + | @default fn main() -> i32 { |
|
| 30 | + | let mut holder = Holder { value: Mixed::Final }; |
|
| 31 | + | ||
| 32 | + | storePayload(&mut holder, 42); |
|
| 33 | + | let case Mixed::Payload(v) = holder.value |
|
| 34 | + | else return 1; |
|
| 35 | + | if v != 42 { |
|
| 36 | + | return 2; |
|
| 37 | + | } |
|
| 38 | + | ||
| 39 | + | if checkIfLet(Mixed::Standalone) != 0 { |
|
| 40 | + | return 3; |
|
| 41 | + | } |
|
| 42 | + | ||
| 43 | + | storeFinal(&mut holder); |
|
| 44 | + | if checkIfLet(holder.value) != 0 { |
|
| 45 | + | return 4; |
|
| 46 | + | } |
|
| 47 | + | ||
| 48 | + | if let case Mixed::Payload(_) = Mixed::Standalone { |
|
| 49 | + | return 5; |
|
| 50 | + | } |
|
| 51 | + | ||
| 52 | + | if let case Mixed::Payload(_) = Mixed::Final { |
|
| 53 | + | return 6; |
|
| 54 | + | } |
|
| 55 | + | ||
| 56 | + | return 0; |
|
| 57 | + | } |
443 more files not shown.