Initial commit

75704b0e772e56d6566a0771c76faac8556e520fb695390092799a7a69920eff
Checked in sources for the self-hosting Radiance compiler.
Licensed under the MIT license.
Alexis Sellier committed ago
.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.